Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/transport_security_persister.h" | 5 #include "chrome/browser/transport_security_persister.h" |
| 6 | 6 |
| 7 #include "base/base64.h" | |
| 7 #include "base/bind.h" | 8 #include "base/bind.h" |
| 8 #include "base/file_path.h" | 9 #include "base/file_path.h" |
| 9 #include "base/file_util.h" | 10 #include "base/file_util.h" |
| 11 #include "base/json/json_reader.h" | |
| 12 #include "base/json/json_writer.h" | |
| 10 #include "base/message_loop.h" | 13 #include "base/message_loop.h" |
| 11 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 15 #include "base/values.h" | |
| 12 #include "chrome/common/chrome_paths.h" | 16 #include "chrome/common/chrome_paths.h" |
| 13 #include "content/public/browser/browser_thread.h" | 17 #include "content/public/browser/browser_thread.h" |
| 18 #include "crypto/sha2.h" | |
| 14 #include "net/base/transport_security_state.h" | 19 #include "net/base/transport_security_state.h" |
| 20 #include "net/base/x509_certificate.h" | |
| 15 | 21 |
| 16 using content::BrowserThread; | 22 using content::BrowserThread; |
| 23 using net::Fingerprint; | |
| 24 using net::FingerprintVector; | |
| 25 using net::TransportSecurityState; | |
| 17 | 26 |
| 18 class TransportSecurityPersister::Loader { | 27 class TransportSecurityPersister::Loader { |
| 19 public: | 28 public: |
| 20 Loader(const base::WeakPtr<TransportSecurityPersister>& persister, | 29 Loader(const base::WeakPtr<TransportSecurityPersister>& persister, |
| 21 const FilePath& path) | 30 const FilePath& path) |
| 22 : persister_(persister), | 31 : persister_(persister), |
| 23 path_(path), | 32 path_(path), |
| 24 state_valid_(false) { | 33 state_valid_(false) { |
| 25 } | 34 } |
| 26 | 35 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 45 | 54 |
| 46 FilePath path_; | 55 FilePath path_; |
| 47 | 56 |
| 48 std::string state_; | 57 std::string state_; |
| 49 bool state_valid_; | 58 bool state_valid_; |
| 50 | 59 |
| 51 DISALLOW_COPY_AND_ASSIGN(Loader); | 60 DISALLOW_COPY_AND_ASSIGN(Loader); |
| 52 }; | 61 }; |
| 53 | 62 |
| 54 TransportSecurityPersister::TransportSecurityPersister( | 63 TransportSecurityPersister::TransportSecurityPersister( |
| 55 net::TransportSecurityState* state, | 64 TransportSecurityState* state, |
| 56 const FilePath& profile_path, | 65 const FilePath& profile_path, |
| 57 bool readonly) | 66 bool readonly) |
| 58 : transport_security_state_(state), | 67 : transport_security_state_(state), |
| 59 writer_(profile_path.AppendASCII("TransportSecurity"), | 68 writer_(profile_path.AppendASCII("TransportSecurity"), |
| 60 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)), | 69 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)), |
| 61 readonly_(readonly), | 70 readonly_(readonly), |
| 62 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { | 71 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { |
| 63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 64 | 73 |
| 65 transport_security_state_->SetDelegate(this); | 74 transport_security_state_->SetDelegate(this); |
| 66 | 75 |
| 67 Loader* loader = new Loader(weak_ptr_factory_.GetWeakPtr(), writer_.path()); | 76 Loader* loader = new Loader(weak_ptr_factory_.GetWeakPtr(), writer_.path()); |
| 68 BrowserThread::PostTaskAndReply( | 77 BrowserThread::PostTaskAndReply( |
| 69 BrowserThread::FILE, FROM_HERE, | 78 BrowserThread::FILE, FROM_HERE, |
| 70 base::Bind(&Loader::Load, base::Unretained(loader)), | 79 base::Bind(&Loader::Load, base::Unretained(loader)), |
| 71 base::Bind(&Loader::CompleteLoad, base::Unretained(loader))); | 80 base::Bind(&Loader::CompleteLoad, base::Unretained(loader))); |
| 72 } | 81 } |
| 73 | 82 |
| 74 TransportSecurityPersister::~TransportSecurityPersister() { | 83 TransportSecurityPersister::~TransportSecurityPersister() { |
| 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 76 | 85 |
| 77 if (writer_.HasPendingWrite()) | 86 if (writer_.HasPendingWrite()) |
| 78 writer_.DoScheduledWrite(); | 87 writer_.DoScheduledWrite(); |
| 79 | 88 |
| 80 transport_security_state_->SetDelegate(NULL); | 89 transport_security_state_->SetDelegate(NULL); |
| 81 } | 90 } |
| 82 | 91 |
| 92 namespace { | |
|
Ryan Sleevi
2012/04/26 19:21:12
Place unnamed namespaces at the beginning of the f
palmer
2012/04/27 23:52:34
Done.
| |
| 93 | |
| 94 ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) { | |
| 95 ListValue* pins = new ListValue; | |
| 96 | |
| 97 for (FingerprintVector::const_iterator i = hashes.begin(); | |
| 98 i != hashes.end(); ++i) { | |
| 99 std::string hash_str(reinterpret_cast<const char*>(i->data), | |
| 100 sizeof(i->data)); | |
| 101 std::string b64; | |
| 102 base::Base64Encode(hash_str, &b64); | |
| 103 pins->Append(new StringValue("sha1/" + b64)); | |
| 104 } | |
| 105 | |
| 106 return pins; | |
| 107 } | |
| 108 | |
| 109 void SPKIHashesFromListValue(const ListValue& pins, FingerprintVector* hashes) { | |
| 110 size_t num_pins = pins.GetSize(); | |
| 111 for (size_t i = 0; i < num_pins; ++i) { | |
| 112 std::string type_and_base64; | |
| 113 Fingerprint fingerprint; | |
| 114 if (pins.GetString(i, &type_and_base64) && | |
| 115 TransportSecurityState::ParsePin(type_and_base64, &fingerprint)) { | |
| 116 hashes->push_back(fingerprint); | |
| 117 } | |
| 118 } | |
| 119 } | |
| 120 | |
| 121 // This function converts the binary hashes to a base64 string which we can | |
| 122 // include in a JSON file. | |
| 123 std::string HashedDomainToExternalString(const std::string& hashed) { | |
| 124 std::string out; | |
| 125 base::Base64Encode(hashed, &out); | |
| 126 return out; | |
| 127 } | |
| 128 | |
| 129 // This inverts |HashedDomainToExternalString|, above. It turns an external | |
| 130 // string (from a JSON file) into an internal (binary) string. | |
| 131 std::string ExternalStringToHashedDomain(const std::string& external) { | |
| 132 std::string out; | |
| 133 if (!base::Base64Decode(external, &out) || | |
| 134 out.size() != crypto::kSHA256Length) { | |
| 135 return std::string(); | |
| 136 } | |
| 137 | |
| 138 return out; | |
| 139 } | |
| 140 | |
| 141 const char kIncludeSubdomains[] = "include_subdomains"; | |
| 142 const char kMode[] = "mode"; | |
| 143 const char kExpiry[] = "expiry"; | |
| 144 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry"; | |
| 145 const char kStaticSPKIHashes[] = "static_spki_hashes"; | |
| 146 const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes"; | |
| 147 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes"; | |
| 148 const char kForceHTTPS[] = "force-https"; | |
| 149 const char kStrict[] = "strict"; | |
| 150 const char kDefault[] = "default"; | |
| 151 const char kPinningOnly[] = "pinning-only"; | |
| 152 const char kCreated[] = "created"; | |
| 153 | |
| 154 } // anonymous namespce | |
| 155 | |
| 156 bool TransportSecurityPersister::DeserializeFromCommandLine( | |
|
Ryan Sleevi
2012/04/26 19:21:12
nit: Fix the ordering of this file so that definit
palmer
2012/04/27 23:52:34
Will do.
| |
| 157 const std::string& serialized) { | |
| 158 // We purposefully ignore |dirty| because we do not want to persist | |
|
Ryan Sleevi
2012/04/26 19:21:12
nit: Drop the we
palmer
2012/04/27 23:52:34
Done.
| |
| 159 // entries deserialized in this way. | |
| 160 bool dirty; | |
| 161 return Deserialize(serialized, &dirty, true, transport_security_state_); | |
| 162 } | |
| 163 | |
| 164 // static | |
| 165 bool TransportSecurityPersister::Deserialize(const std::string& serialized, | |
| 166 bool* dirty, | |
| 167 bool forced, | |
| 168 TransportSecurityState* state) { | |
| 169 scoped_ptr<Value> value(base::JSONReader::Read(serialized)); | |
| 170 DictionaryValue* dict_value; | |
| 171 if (!value.get() || !value->GetAsDictionary(&dict_value)) | |
| 172 return false; | |
| 173 | |
| 174 const base::Time current_time(base::Time::Now()); | |
| 175 bool dirtied = false; | |
| 176 | |
| 177 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); | |
| 178 i != dict_value->end_keys(); ++i) { | |
| 179 DictionaryValue* parsed; | |
| 180 if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &parsed)) | |
| 181 continue; | |
| 182 | |
| 183 bool include_subdomains; | |
| 184 std::string mode_string; | |
| 185 double created; | |
| 186 double expiry; | |
| 187 double dynamic_spki_hashes_expiry = 0.0; | |
| 188 | |
| 189 if (!parsed->GetBoolean(kIncludeSubdomains, &include_subdomains) || | |
| 190 !parsed->GetString(kMode, &mode_string) || | |
| 191 !parsed->GetDouble(kExpiry, &expiry)) { | |
|
Ryan Sleevi
2012/04/26 19:21:12
So input data failures aren't fatal?
Should there
palmer
2012/04/27 23:52:34
The code as it is here is merely moved, not change
| |
| 192 continue; | |
| 193 } | |
| 194 | |
| 195 // Don't fail if this key is not present. | |
| 196 parsed->GetDouble(kDynamicSPKIHashesExpiry, | |
| 197 &dynamic_spki_hashes_expiry); | |
| 198 | |
| 199 ListValue* pins_list = NULL; | |
| 200 FingerprintVector static_spki_hashes; | |
| 201 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. | |
| 202 if (parsed->GetList(kStaticSPKIHashes, &pins_list)) | |
| 203 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); | |
| 204 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) | |
| 205 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); | |
| 206 | |
| 207 FingerprintVector dynamic_spki_hashes; | |
| 208 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) | |
| 209 SPKIHashesFromListValue(*pins_list, &dynamic_spki_hashes); | |
| 210 | |
| 211 TransportSecurityState::DomainState::UpgradeMode mode; | |
| 212 if (mode_string == kForceHTTPS || mode_string == kStrict) { | |
| 213 mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; | |
| 214 } else if (mode_string == kDefault || mode_string == kPinningOnly) { | |
| 215 mode = TransportSecurityState::DomainState::MODE_DEFAULT; | |
| 216 } else { | |
| 217 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " | |
| 218 << mode_string; | |
| 219 continue; | |
| 220 } | |
| 221 | |
| 222 base::Time expiry_time = base::Time::FromDoubleT(expiry); | |
| 223 base::Time dynamic_spki_hashes_expiry_time = | |
| 224 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); | |
| 225 base::Time created_time; | |
| 226 if (parsed->GetDouble(kCreated, &created)) { | |
| 227 created_time = base::Time::FromDoubleT(created); | |
| 228 } else { | |
| 229 // We're migrating an old entry with no creation date. Make sure we | |
| 230 // write the new date back in a reasonable time frame. | |
| 231 dirtied = true; | |
| 232 created_time = base::Time::Now(); | |
| 233 } | |
| 234 | |
| 235 if (expiry_time <= current_time && | |
| 236 dynamic_spki_hashes_expiry_time <= current_time) { | |
| 237 // Make sure we dirty the state if we drop an entry. | |
| 238 dirtied = true; | |
| 239 continue; | |
| 240 } | |
| 241 | |
| 242 std::string hashed = ExternalStringToHashedDomain(*i); | |
| 243 if (hashed.empty()) { | |
| 244 dirtied = true; | |
| 245 continue; | |
| 246 } | |
| 247 | |
| 248 TransportSecurityState::DomainState domain_state; | |
| 249 domain_state.upgrade_mode = mode; | |
| 250 domain_state.created = created_time; | |
| 251 domain_state.upgrade_expiry = expiry_time; | |
| 252 domain_state.include_subdomains = include_subdomains; | |
| 253 domain_state.static_spki_hashes = static_spki_hashes; | |
| 254 domain_state.dynamic_spki_hashes = dynamic_spki_hashes; | |
| 255 domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; | |
| 256 | |
| 257 if (forced) | |
| 258 state->GetForcedHosts()[hashed] = domain_state; | |
| 259 else | |
| 260 state->GetEnabledHosts()[hashed] = domain_state; | |
| 261 } | |
| 262 | |
| 263 *dirty = dirtied; | |
| 264 return true; | |
| 265 } | |
| 266 | |
| 267 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, | |
| 268 bool* dirty) { | |
| 269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 270 | |
| 271 transport_security_state_->Clear(); | |
| 272 return Deserialize(serialized, dirty, false, transport_security_state_); | |
| 273 } | |
| 274 | |
| 83 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 275 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
| 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 85 | 277 |
| 86 bool dirty = false; | 278 bool dirty = false; |
| 87 if (!transport_security_state_->LoadEntries(state, &dirty)) { | 279 if (!LoadEntries(state, &dirty)) { |
| 88 LOG(ERROR) << "Failed to deserialize state: " << state; | 280 LOG(ERROR) << "Failed to deserialize state: " << state; |
| 89 return; | 281 return; |
| 90 } | 282 } |
| 91 if (dirty) | 283 if (dirty) |
| 92 StateIsDirty(transport_security_state_); | 284 StateIsDirty(transport_security_state_); |
| 93 } | 285 } |
| 94 | 286 |
| 95 void TransportSecurityPersister::StateIsDirty( | 287 void TransportSecurityPersister::StateIsDirty( |
| 96 net::TransportSecurityState* state) { | 288 TransportSecurityState* state) { |
| 97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 98 DCHECK_EQ(transport_security_state_, state); | 290 DCHECK_EQ(transport_security_state_, state); |
| 99 | 291 |
| 100 if (!readonly_) | 292 if (!readonly_) |
| 101 writer_.ScheduleWrite(this); | 293 writer_.ScheduleWrite(this); |
| 102 } | 294 } |
| 103 | 295 |
| 104 bool TransportSecurityPersister::SerializeData(std::string* data) { | 296 bool TransportSecurityPersister::SerializeData(std::string* output) { |
| 105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 106 return transport_security_state_->Serialise(data); | 298 |
| 299 DictionaryValue toplevel; | |
| 300 base::Time now = base::Time::Now(); | |
| 301 TransportSecurityState::Iterator state(*transport_security_state_); | |
| 302 for (; state.HasNext(); state.Advance()) { | |
| 303 const std::string& hostname = state.hostname(); | |
| 304 const TransportSecurityState::DomainState& domain_state = | |
| 305 state.domain_state(); | |
| 306 | |
| 307 DictionaryValue* serialized = new DictionaryValue; | |
| 308 serialized->SetBoolean(kIncludeSubdomains, | |
| 309 domain_state.include_subdomains); | |
| 310 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT()); | |
| 311 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT()); | |
| 312 serialized->SetDouble(kDynamicSPKIHashesExpiry, | |
| 313 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); | |
| 314 | |
| 315 switch (domain_state.upgrade_mode) { | |
| 316 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: | |
| 317 serialized->SetString(kMode, kForceHTTPS); | |
| 318 break; | |
| 319 case TransportSecurityState::DomainState::MODE_DEFAULT: | |
| 320 serialized->SetString(kMode, kDefault); | |
| 321 break; | |
| 322 default: | |
| 323 NOTREACHED() << "DomainState with unknown mode"; | |
| 324 delete serialized; | |
| 325 continue; | |
| 326 } | |
| 327 | |
| 328 serialized->Set(kStaticSPKIHashes, | |
| 329 SPKIHashesToListValue(domain_state.static_spki_hashes)); | |
| 330 | |
| 331 if (now < domain_state.dynamic_spki_hashes_expiry) { | |
| 332 serialized->Set(kDynamicSPKIHashes, | |
| 333 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); | |
| 334 } | |
| 335 | |
| 336 toplevel.Set(HashedDomainToExternalString(hostname), serialized); | |
| 337 } | |
| 338 | |
| 339 base::JSONWriter::WriteWithOptions(&toplevel, | |
| 340 base::JSONWriter::OPTIONS_PRETTY_PRINT, | |
| 341 output); | |
| 342 return true; | |
| 107 } | 343 } |
| OLD | NEW |