1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "net/http/transport_security_persister.h" 6 7 #include "base/base64.h" 8 #include "base/bind.h" 9 #include "base/file_util.h" 10 #include "base/files/file_path.h" 11 #include "base/json/json_reader.h" 12 #include "base/json/json_writer.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/message_loop/message_loop_proxy.h" 15 #include "base/sequenced_task_runner.h" 16 #include "base/task_runner_util.h" 17 #include "base/values.h" 18 #include "crypto/sha2.h" 19 #include "net/cert/x509_certificate.h" 20 #include "net/http/transport_security_state.h" 21 22 using net::HashValue; 23 using net::HashValueTag; 24 using net::HashValueVector; 25 using net::TransportSecurityState; 26 27 namespace { 28 29 ListValue* SPKIHashesToListValue(const HashValueVector& hashes) { 30 ListValue* pins = new ListValue; 31 for (size_t i = 0; i != hashes.size(); i++) 32 pins->Append(new StringValue(hashes[i].ToString())); 33 return pins; 34 } 35 36 void SPKIHashesFromListValue(const ListValue& pins, HashValueVector* hashes) { 37 size_t num_pins = pins.GetSize(); 38 for (size_t i = 0; i < num_pins; ++i) { 39 std::string type_and_base64; 40 HashValue fingerprint; 41 if (pins.GetString(i, &type_and_base64) && 42 fingerprint.FromString(type_and_base64)) { 43 hashes->push_back(fingerprint); 44 } 45 } 46 } 47 48 // This function converts the binary hashes to a base64 string which we can 49 // include in a JSON file. 50 std::string HashedDomainToExternalString(const std::string& hashed) { 51 std::string out; 52 base::Base64Encode(hashed, &out); 53 return out; 54 } 55 56 // This inverts |HashedDomainToExternalString|, above. It turns an external 57 // string (from a JSON file) into an internal (binary) string. 58 std::string ExternalStringToHashedDomain(const std::string& external) { 59 std::string out; 60 if (!base::Base64Decode(external, &out) || 61 out.size() != crypto::kSHA256Length) { 62 return std::string(); 63 } 64 65 return out; 66 } 67 68 const char kIncludeSubdomains[] = "include_subdomains"; 69 const char kStsIncludeSubdomains[] = "sts_include_subdomains"; 70 const char kPkpIncludeSubdomains[] = "pkp_include_subdomains"; 71 const char kMode[] = "mode"; 72 const char kExpiry[] = "expiry"; 73 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry"; 74 const char kStaticSPKIHashes[] = "static_spki_hashes"; 75 const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes"; 76 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes"; 77 const char kForceHTTPS[] = "force-https"; 78 const char kStrict[] = "strict"; 79 const char kDefault[] = "default"; 80 const char kPinningOnly[] = "pinning-only"; 81 const char kCreated[] = "created"; 82 83 std::string LoadState(const base::FilePath& path) { 84 std::string result; 85 if (!base::ReadFileToString(path, &result)) { 86 return ""; 87 } 88 return result; 89 } 90 91 } // namespace 92 93 94 namespace net { 95 96 TransportSecurityPersister::TransportSecurityPersister( 97 TransportSecurityState* state, 98 const base::FilePath& profile_path, 99 base::SequencedTaskRunner* background_runner, 100 bool readonly) 101 : transport_security_state_(state), 102 writer_(profile_path.AppendASCII("TransportSecurity"), background_runner), 103 foreground_runner_(base::MessageLoop::current()->message_loop_proxy()), 104 background_runner_(background_runner), 105 readonly_(readonly), 106 weak_ptr_factory_(this) { 107 transport_security_state_->SetDelegate(this); 108 109 base::PostTaskAndReplyWithResult( 110 background_runner_, 111 FROM_HERE, 112 base::Bind(&::LoadState, writer_.path()), 113 base::Bind(&TransportSecurityPersister::CompleteLoad, 114 weak_ptr_factory_.GetWeakPtr())); 115 } 116 117 TransportSecurityPersister::~TransportSecurityPersister() { 118 DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); 119 120 if (writer_.HasPendingWrite()) 121 writer_.DoScheduledWrite(); 122 123 transport_security_state_->SetDelegate(NULL); 124 } 125 126 void TransportSecurityPersister::StateIsDirty( 127 TransportSecurityState* state) { 128 DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); 129 DCHECK_EQ(transport_security_state_, state); 130 131 if (!readonly_) 132 writer_.ScheduleWrite(this); 133 } 134 135 bool TransportSecurityPersister::SerializeData(std::string* output) { 136 DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); 137 138 DictionaryValue toplevel; 139 base::Time now = base::Time::Now(); 140 TransportSecurityState::Iterator state(*transport_security_state_); 141 for (; state.HasNext(); state.Advance()) { 142 const std::string& hostname = state.hostname(); 143 const TransportSecurityState::DomainState& domain_state = 144 state.domain_state(); 145 146 DictionaryValue* serialized = new DictionaryValue; 147 serialized->SetBoolean(kStsIncludeSubdomains, 148 domain_state.sts_include_subdomains); 149 serialized->SetBoolean(kPkpIncludeSubdomains, 150 domain_state.pkp_include_subdomains); 151 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT()); 152 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT()); 153 serialized->SetDouble(kDynamicSPKIHashesExpiry, 154 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); 155 156 switch (domain_state.upgrade_mode) { 157 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: 158 serialized->SetString(kMode, kForceHTTPS); 159 break; 160 case TransportSecurityState::DomainState::MODE_DEFAULT: 161 serialized->SetString(kMode, kDefault); 162 break; 163 default: 164 NOTREACHED() << "DomainState with unknown mode"; 165 delete serialized; 166 continue; 167 } 168 169 serialized->Set(kStaticSPKIHashes, 170 SPKIHashesToListValue(domain_state.static_spki_hashes)); 171 172 if (now < domain_state.dynamic_spki_hashes_expiry) { 173 serialized->Set(kDynamicSPKIHashes, 174 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); 175 } 176 177 toplevel.Set(HashedDomainToExternalString(hostname), serialized); 178 } 179 180 base::JSONWriter::WriteWithOptions(&toplevel, 181 base::JSONWriter::OPTIONS_PRETTY_PRINT, 182 output); 183 return true; 184 } 185 186 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, 187 bool* dirty) { 188 DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); 189 190 transport_security_state_->ClearDynamicData(); 191 return Deserialize(serialized, dirty, transport_security_state_); 192 } 193 194 // static 195 bool TransportSecurityPersister::Deserialize(const std::string& serialized, 196 bool* dirty, 197 TransportSecurityState* state) { 198 scoped_ptr<Value> value(base::JSONReader::Read(serialized)); 199 DictionaryValue* dict_value = NULL; 200 if (!value.get() || !value->GetAsDictionary(&dict_value)) 201 return false; 202 203 const base::Time current_time(base::Time::Now()); 204 bool dirtied = false; 205 206 for (DictionaryValue::Iterator i(*dict_value); !i.IsAtEnd(); i.Advance()) { 207 const DictionaryValue* parsed = NULL; 208 if (!i.value().GetAsDictionary(&parsed)) { 209 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry"; 210 continue; 211 } 212 213 std::string mode_string; 214 double created; 215 double expiry; 216 double dynamic_spki_hashes_expiry = 0.0; 217 TransportSecurityState::DomainState domain_state; 218 219 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and 220 // kPkpIncludeSubdomains. Parse at least one of these properties, 221 // preferably the new ones. 222 bool include_subdomains = false; 223 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains, 224 &include_subdomains); 225 domain_state.sts_include_subdomains = include_subdomains; 226 domain_state.pkp_include_subdomains = include_subdomains; 227 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) { 228 domain_state.sts_include_subdomains = include_subdomains; 229 parsed_include_subdomains = true; 230 } 231 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) { 232 domain_state.pkp_include_subdomains = include_subdomains; 233 parsed_include_subdomains = true; 234 } 235 236 if (!parsed_include_subdomains || 237 !parsed->GetString(kMode, &mode_string) || 238 !parsed->GetDouble(kExpiry, &expiry)) { 239 LOG(WARNING) << "Could not parse some elements of entry " << i.key() 240 << "; skipping entry"; 241 continue; 242 } 243 244 // Don't fail if this key is not present. 245 parsed->GetDouble(kDynamicSPKIHashesExpiry, 246 &dynamic_spki_hashes_expiry); 247 248 const ListValue* pins_list = NULL; 249 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. 250 if (parsed->GetList(kStaticSPKIHashes, &pins_list)) 251 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes); 252 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) 253 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes); 254 255 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) 256 SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes); 257 258 if (mode_string == kForceHTTPS || mode_string == kStrict) { 259 domain_state.upgrade_mode = 260 TransportSecurityState::DomainState::MODE_FORCE_HTTPS; 261 } else if (mode_string == kDefault || mode_string == kPinningOnly) { 262 domain_state.upgrade_mode = 263 TransportSecurityState::DomainState::MODE_DEFAULT; 264 } else { 265 LOG(WARNING) << "Unknown TransportSecurityState mode string " 266 << mode_string << " found for entry " << i.key() 267 << "; skipping entry"; 268 continue; 269 } 270 271 domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry); 272 domain_state.dynamic_spki_hashes_expiry = 273 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); 274 if (parsed->GetDouble(kCreated, &created)) { 275 domain_state.created = base::Time::FromDoubleT(created); 276 } else { 277 // We're migrating an old entry with no creation date. Make sure we 278 // write the new date back in a reasonable time frame. 279 dirtied = true; 280 domain_state.created = base::Time::Now(); 281 } 282 283 if (domain_state.upgrade_expiry <= current_time && 284 domain_state.dynamic_spki_hashes_expiry <= current_time) { 285 // Make sure we dirty the state if we drop an entry. 286 dirtied = true; 287 continue; 288 } 289 290 std::string hashed = ExternalStringToHashedDomain(i.key()); 291 if (hashed.empty()) { 292 dirtied = true; 293 continue; 294 } 295 296 state->AddOrUpdateEnabledHosts(hashed, domain_state); 297 } 298 299 *dirty = dirtied; 300 return true; 301 } 302 303 void TransportSecurityPersister::CompleteLoad(const std::string& state) { 304 DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); 305 306 if (state.empty()) 307 return; 308 309 bool dirty = false; 310 if (!LoadEntries(state, &dirty)) { 311 LOG(ERROR) << "Failed to deserialize state: " << state; 312 return; 313 } 314 if (dirty) 315 StateIsDirty(transport_security_state_); 316 } 317 318 } // namespace net 319