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