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