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 static ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) { | |
|
Ryan Sleevi
2012/03/28 00:50:32
nit: The dominant pattern in Chromium code is to u
palmer
2012/04/10 23:25:51
Done.
| |
| 93 ListValue* pins = new ListValue; | |
| 94 | |
| 95 for (FingerprintVector::const_iterator i = hashes.begin(); | |
| 96 i != hashes.end(); ++i) { | |
| 97 std::string hash_str(reinterpret_cast<const char*>(i->data), | |
| 98 sizeof(i->data)); | |
| 99 std::string b64; | |
| 100 base::Base64Encode(hash_str, &b64); | |
| 101 pins->Append(new StringValue("sha1/" + b64)); | |
| 102 } | |
| 103 | |
| 104 return pins; | |
| 105 } | |
| 106 | |
| 107 static void SPKIHashesFromListValue(FingerprintVector* hashes, | |
| 108 const ListValue& pins) { | |
|
Ryan Sleevi
2012/03/28 00:50:32
nit: Parameter order: in -> out
http://google-sty
palmer
2012/04/10 23:25:51
Done.
| |
| 109 size_t num_pins = pins.GetSize(); | |
| 110 for (size_t i = 0; i < num_pins; ++i) { | |
| 111 std::string type_and_base64; | |
| 112 Fingerprint fingerprint; | |
| 113 if (pins.GetString(i, &type_and_base64) && | |
| 114 TransportSecurityState::ParsePin(type_and_base64, &fingerprint)) { | |
| 115 hashes->push_back(fingerprint); | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 // This function converts the binary hashes, which we store in | |
| 121 // |enabled_hosts_|, to a base64 string which we can include in a JSON file. | |
| 122 static std::string HashedDomainToExternalString(const std::string& hashed) { | |
| 123 std::string out; | |
| 124 CHECK(base::Base64Encode(hashed, &out)); | |
|
Ryan Sleevi
2012/03/28 00:50:32
Do you really want to crash the browser process he
palmer
2012/04/10 23:25:51
Done.
| |
| 125 return out; | |
| 126 } | |
| 127 | |
| 128 // This inverts |HashedDomainToExternalString|, above. It turns an external | |
| 129 // string (from a JSON file) into an internal (binary) string. | |
| 130 static std::string ExternalStringToHashedDomain(const std::string& external) { | |
| 131 std::string out; | |
| 132 if (!base::Base64Decode(external, &out) || | |
| 133 out.size() != crypto::kSHA256Length) { | |
| 134 return std::string(); | |
| 135 } | |
| 136 | |
| 137 return out; | |
| 138 } | |
| 139 | |
| 140 // static | |
| 141 bool TransportSecurityPersister::Deserialize(const std::string& serialized, | |
| 142 bool* dirty, | |
| 143 TransportSecurityState* state) { | |
| 144 scoped_ptr<Value> value( | |
| 145 base::JSONReader::Read(serialized, | |
| 146 false /* do not allow trailing commas */)); | |
| 147 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) | |
|
Ryan Sleevi
2012/03/28 00:50:32
nit: You can replace 147 - 150 with:
DictionaryVa
palmer
2012/04/10 23:25:51
Done.
| |
| 148 return false; | |
| 149 | |
| 150 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); | |
| 151 const base::Time current_time(base::Time::Now()); | |
| 152 bool dirtied = false; | |
| 153 | |
| 154 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); | |
| 155 i != dict_value->end_keys(); ++i) { | |
| 156 DictionaryValue* parsed; | |
| 157 if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &parsed)) | |
| 158 continue; | |
| 159 | |
| 160 bool include_subdomains; | |
| 161 std::string mode_string; | |
| 162 double created; | |
| 163 double expiry; | |
| 164 double dynamic_spki_hashes_expiry = 0.0; | |
| 165 | |
| 166 if (!parsed->GetBoolean("include_subdomains", &include_subdomains) || | |
| 167 !parsed->GetString("mode", &mode_string) || | |
| 168 !parsed->GetDouble("expiry", &expiry)) { | |
| 169 continue; | |
| 170 } | |
| 171 | |
| 172 // Don't fail if this key is not present. | |
| 173 (void) parsed->GetDouble("dynamic_spki_hashes_expiry", | |
|
Ryan Sleevi
2012/03/28 00:50:32
This method isn't WARN_UNUSED_RESULT, so is this (
palmer
2012/04/10 23:25:51
No, it's not needed. Removed.
| |
| 174 &dynamic_spki_hashes_expiry); | |
| 175 | |
| 176 ListValue* pins_list = NULL; | |
| 177 FingerprintVector static_spki_hashes; | |
| 178 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. | |
| 179 if (parsed->GetList("static_spki_hashes", &pins_list)) | |
| 180 SPKIHashesFromListValue(&static_spki_hashes, *pins_list); | |
| 181 else if (parsed->GetList("preloaded_spki_hashes", &pins_list)) | |
| 182 SPKIHashesFromListValue(&static_spki_hashes, *pins_list); | |
| 183 | |
| 184 FingerprintVector dynamic_spki_hashes; | |
| 185 if (parsed->GetList("dynamic_spki_hashes", &pins_list)) | |
|
Ryan Sleevi
2012/03/28 00:50:32
nit: Since you re-use these strings in both serial
palmer
2012/04/10 23:25:51
Done.
| |
| 186 SPKIHashesFromListValue(&dynamic_spki_hashes, *pins_list); | |
| 187 | |
| 188 TransportSecurityState::DomainState::UpgradeMode mode; | |
| 189 if (mode_string == "force-https" || mode_string == "strict") { | |
| 190 mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; | |
| 191 } else if (mode_string == "default" || mode_string == "pinning-only") { | |
| 192 mode = TransportSecurityState::DomainState::MODE_DEFAULT; | |
| 193 } else { | |
| 194 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " | |
| 195 << mode_string; | |
|
Ryan Sleevi
2012/03/28 00:50:32
What happened to spdy-only?
Looks like you're dro
palmer
2012/04/10 23:25:51
It had been deprecated for a long time, and a sear
| |
| 196 continue; | |
| 197 } | |
| 198 | |
| 199 base::Time expiry_time = base::Time::FromDoubleT(expiry); | |
| 200 base::Time dynamic_spki_hashes_expiry_time = | |
| 201 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); | |
| 202 base::Time created_time; | |
| 203 if (parsed->GetDouble("created", &created)) { | |
| 204 created_time = base::Time::FromDoubleT(created); | |
| 205 } else { | |
| 206 // We're migrating an old entry with no creation date. Make sure we | |
| 207 // write the new date back in a reasonable time frame. | |
| 208 dirtied = true; | |
| 209 created_time = base::Time::Now(); | |
| 210 } | |
| 211 | |
| 212 if (expiry_time <= current_time && | |
| 213 dynamic_spki_hashes_expiry_time <= current_time) { | |
| 214 // Make sure we dirty the state if we drop an entry. | |
| 215 dirtied = true; | |
| 216 continue; | |
| 217 } | |
| 218 | |
| 219 std::string hashed = ExternalStringToHashedDomain(*i); | |
| 220 if (hashed.empty()) { | |
| 221 dirtied = true; | |
| 222 continue; | |
| 223 } | |
| 224 | |
| 225 TransportSecurityState::DomainState domain_state; | |
| 226 domain_state.upgrade_mode = mode; | |
| 227 domain_state.created = created_time; | |
| 228 domain_state.upgrade_expiry = expiry_time; | |
| 229 domain_state.include_subdomains = include_subdomains; | |
| 230 domain_state.static_spki_hashes = static_spki_hashes; | |
| 231 domain_state.dynamic_spki_hashes = dynamic_spki_hashes; | |
| 232 domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; | |
| 233 state->EnableHost(hashed, domain_state); | |
| 234 } | |
| 235 | |
| 236 *dirty = dirtied; | |
| 237 return true; | |
| 238 } | |
| 239 | |
| 240 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, | |
| 241 bool* dirty, | |
| 242 TransportSecurityState* state) { | |
| 243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 244 | |
| 245 state->Clear(); | |
| 246 return Deserialize(serialized, dirty, state); | |
| 247 } | |
| 248 | |
| 83 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 249 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
| 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 85 | 251 |
| 86 bool dirty = false; | 252 bool dirty = false; |
| 87 if (!transport_security_state_->LoadEntries(state, &dirty)) { | 253 if (!LoadEntries(state, &dirty, transport_security_state_)) { |
| 88 LOG(ERROR) << "Failed to deserialize state: " << state; | 254 LOG(ERROR) << "Failed to deserialize state: " << state; |
| 89 return; | 255 return; |
| 90 } | 256 } |
| 91 if (dirty) | 257 if (dirty) |
| 92 StateIsDirty(transport_security_state_); | 258 StateIsDirty(transport_security_state_); |
| 93 } | 259 } |
| 94 | 260 |
| 95 void TransportSecurityPersister::StateIsDirty( | 261 void TransportSecurityPersister::StateIsDirty( |
| 96 net::TransportSecurityState* state) { | 262 TransportSecurityState* state) { |
| 97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 98 DCHECK_EQ(transport_security_state_, state); | 264 DCHECK_EQ(transport_security_state_, state); |
| 99 | 265 |
| 100 if (!readonly_) | 266 if (!readonly_) |
| 101 writer_.ScheduleWrite(this); | 267 writer_.ScheduleWrite(this); |
| 102 } | 268 } |
| 103 | 269 |
| 270 bool TransportSecurityPersister::Serialize(std::string* output) const { | |
| 271 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 272 | |
| 273 DictionaryValue toplevel; | |
| 274 base::Time now = base::Time::Now(); | |
| 275 TransportSecurityState::Iterator state(*transport_security_state_); | |
| 276 for (; state.HasNext(); state.Advance()) { | |
| 277 const std::string& hostname = state.hostname(); | |
| 278 const TransportSecurityState::DomainState& domain_state = | |
| 279 state.domain_state(); | |
| 280 | |
| 281 DictionaryValue* serialized = new DictionaryValue; | |
| 282 serialized->SetBoolean("include_subdomains", | |
| 283 domain_state.include_subdomains); | |
| 284 serialized->SetDouble("created", domain_state.created.ToDoubleT()); | |
| 285 serialized->SetDouble("expiry", domain_state.upgrade_expiry.ToDoubleT()); | |
| 286 serialized->SetDouble("dynamic_spki_hashes_expiry", | |
| 287 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); | |
| 288 | |
| 289 switch (domain_state.upgrade_mode) { | |
| 290 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: | |
| 291 serialized->SetString("mode", "force-https"); | |
| 292 break; | |
| 293 case TransportSecurityState::DomainState::MODE_DEFAULT: | |
| 294 serialized->SetString("mode", "default"); | |
| 295 break; | |
| 296 default: | |
| 297 NOTREACHED() << "DomainState with unknown mode"; | |
| 298 delete serialized; | |
| 299 continue; | |
| 300 } | |
| 301 | |
| 302 serialized->Set("static_spki_hashes", | |
| 303 SPKIHashesToListValue(domain_state.static_spki_hashes)); | |
| 304 | |
| 305 if (now < domain_state.dynamic_spki_hashes_expiry) { | |
| 306 serialized->Set("dynamic_spki_hashes", | |
| 307 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); | |
| 308 } | |
| 309 | |
| 310 toplevel.Set(HashedDomainToExternalString(hostname), serialized); | |
| 311 } | |
| 312 | |
| 313 base::JSONWriter::WriteWithOptions(&toplevel, | |
| 314 base::JSONWriter::OPTIONS_PRETTY_PRINT, | |
| 315 output); | |
| 316 return true; | |
| 317 } | |
| 318 | |
| 104 bool TransportSecurityPersister::SerializeData(std::string* data) { | 319 bool TransportSecurityPersister::SerializeData(std::string* data) { |
| 105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 106 return transport_security_state_->Serialise(data); | 321 return Serialize(data); |
| 107 } | 322 } |
| OLD | NEW |