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 |
| 156 bool TransportSecurityPersister::DeserializeFromCommandLine( |
| 157 const std::string& serialized) { |
| 158 // Purposefully ignore |dirty| because we do not want to persist entries |
| 159 // deserialized in this way. |
| 160 bool dirty; |
| 161 return Deserialize(serialized, true, &dirty, transport_security_state_); |
| 162 } |
| 163 |
| 164 // static |
| 165 bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
| 166 bool forced, |
| 167 bool* dirty, |
| 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 LOG(WARNING) << "Could not parse entry " << *i << "; skipping entry"; |
| 182 continue; |
| 183 } |
| 184 |
| 185 bool include_subdomains; |
| 186 std::string mode_string; |
| 187 double created; |
| 188 double expiry; |
| 189 double dynamic_spki_hashes_expiry = 0.0; |
| 190 |
| 191 if (!parsed->GetBoolean(kIncludeSubdomains, &include_subdomains) || |
| 192 !parsed->GetString(kMode, &mode_string) || |
| 193 !parsed->GetDouble(kExpiry, &expiry)) { |
| 194 LOG(WARNING) << "Could not parse some elements of entry " << *i |
| 195 << "; skipping entry"; |
| 196 continue; |
| 197 } |
| 198 |
| 199 // Don't fail if this key is not present. |
| 200 parsed->GetDouble(kDynamicSPKIHashesExpiry, |
| 201 &dynamic_spki_hashes_expiry); |
| 202 |
| 203 ListValue* pins_list = NULL; |
| 204 FingerprintVector static_spki_hashes; |
| 205 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. |
| 206 if (parsed->GetList(kStaticSPKIHashes, &pins_list)) |
| 207 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); |
| 208 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) |
| 209 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); |
| 210 |
| 211 FingerprintVector dynamic_spki_hashes; |
| 212 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) |
| 213 SPKIHashesFromListValue(*pins_list, &dynamic_spki_hashes); |
| 214 |
| 215 TransportSecurityState::DomainState::UpgradeMode mode; |
| 216 if (mode_string == kForceHTTPS || mode_string == kStrict) { |
| 217 mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; |
| 218 } else if (mode_string == kDefault || mode_string == kPinningOnly) { |
| 219 mode = TransportSecurityState::DomainState::MODE_DEFAULT; |
| 220 } else { |
| 221 LOG(WARNING) << "Unknown TransportSecurityState mode string " |
| 222 << mode_string << " found for entry " << *i |
| 223 << "; skipping entry"; |
| 224 continue; |
| 225 } |
| 226 |
| 227 base::Time expiry_time = base::Time::FromDoubleT(expiry); |
| 228 base::Time dynamic_spki_hashes_expiry_time = |
| 229 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); |
| 230 base::Time created_time; |
| 231 if (parsed->GetDouble(kCreated, &created)) { |
| 232 created_time = base::Time::FromDoubleT(created); |
| 233 } else { |
| 234 // We're migrating an old entry with no creation date. Make sure we |
| 235 // write the new date back in a reasonable time frame. |
| 236 dirtied = true; |
| 237 created_time = base::Time::Now(); |
| 238 } |
| 239 |
| 240 if (expiry_time <= current_time && |
| 241 dynamic_spki_hashes_expiry_time <= current_time) { |
| 242 // Make sure we dirty the state if we drop an entry. |
| 243 dirtied = true; |
| 244 continue; |
| 245 } |
| 246 |
| 247 std::string hashed = ExternalStringToHashedDomain(*i); |
| 248 if (hashed.empty()) { |
| 249 dirtied = true; |
| 250 continue; |
| 251 } |
| 252 |
| 253 TransportSecurityState::DomainState domain_state; |
| 254 domain_state.upgrade_mode = mode; |
| 255 domain_state.created = created_time; |
| 256 domain_state.upgrade_expiry = expiry_time; |
| 257 domain_state.include_subdomains = include_subdomains; |
| 258 domain_state.static_spki_hashes = static_spki_hashes; |
| 259 domain_state.dynamic_spki_hashes = dynamic_spki_hashes; |
| 260 domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; |
| 261 |
| 262 if (forced) |
| 263 state->ForcedHostsInsert(hashed, domain_state); |
| 264 else |
| 265 state->EnabledHostsInsert(hashed, domain_state); |
| 266 } |
| 267 |
| 268 *dirty = dirtied; |
| 269 return true; |
| 270 } |
| 271 |
| 272 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, |
| 273 bool* dirty) { |
| 274 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 275 |
| 276 transport_security_state_->Clear(); |
| 277 return Deserialize(serialized, false, dirty, transport_security_state_); |
| 278 } |
| 279 |
83 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 280 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
85 | 282 |
86 bool dirty = false; | 283 bool dirty = false; |
87 if (!transport_security_state_->LoadEntries(state, &dirty)) { | 284 if (!LoadEntries(state, &dirty)) { |
88 LOG(ERROR) << "Failed to deserialize state: " << state; | 285 LOG(ERROR) << "Failed to deserialize state: " << state; |
89 return; | 286 return; |
90 } | 287 } |
91 if (dirty) | 288 if (dirty) |
92 StateIsDirty(transport_security_state_); | 289 StateIsDirty(transport_security_state_); |
93 } | 290 } |
94 | 291 |
95 void TransportSecurityPersister::StateIsDirty( | 292 void TransportSecurityPersister::StateIsDirty( |
96 net::TransportSecurityState* state) { | 293 TransportSecurityState* state) { |
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
98 DCHECK_EQ(transport_security_state_, state); | 295 DCHECK_EQ(transport_security_state_, state); |
99 | 296 |
100 if (!readonly_) | 297 if (!readonly_) |
101 writer_.ScheduleWrite(this); | 298 writer_.ScheduleWrite(this); |
102 } | 299 } |
103 | 300 |
104 bool TransportSecurityPersister::SerializeData(std::string* data) { | 301 bool TransportSecurityPersister::SerializeData(std::string* output) { |
105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 302 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
106 return transport_security_state_->Serialise(data); | 303 |
| 304 DictionaryValue toplevel; |
| 305 base::Time now = base::Time::Now(); |
| 306 TransportSecurityState::Iterator state(*transport_security_state_); |
| 307 for (; state.HasNext(); state.Advance()) { |
| 308 const std::string& hostname = state.hostname(); |
| 309 const TransportSecurityState::DomainState& domain_state = |
| 310 state.domain_state(); |
| 311 |
| 312 DictionaryValue* serialized = new DictionaryValue; |
| 313 serialized->SetBoolean(kIncludeSubdomains, |
| 314 domain_state.include_subdomains); |
| 315 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT()); |
| 316 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT()); |
| 317 serialized->SetDouble(kDynamicSPKIHashesExpiry, |
| 318 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); |
| 319 |
| 320 switch (domain_state.upgrade_mode) { |
| 321 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: |
| 322 serialized->SetString(kMode, kForceHTTPS); |
| 323 break; |
| 324 case TransportSecurityState::DomainState::MODE_DEFAULT: |
| 325 serialized->SetString(kMode, kDefault); |
| 326 break; |
| 327 default: |
| 328 NOTREACHED() << "DomainState with unknown mode"; |
| 329 delete serialized; |
| 330 continue; |
| 331 } |
| 332 |
| 333 serialized->Set(kStaticSPKIHashes, |
| 334 SPKIHashesToListValue(domain_state.static_spki_hashes)); |
| 335 |
| 336 if (now < domain_state.dynamic_spki_hashes_expiry) { |
| 337 serialized->Set(kDynamicSPKIHashes, |
| 338 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); |
| 339 } |
| 340 |
| 341 toplevel.Set(HashedDomainToExternalString(hostname), serialized); |
| 342 } |
| 343 |
| 344 base::JSONWriter::WriteWithOptions(&toplevel, |
| 345 base::JSONWriter::OPTIONS_PRETTY_PRINT, |
| 346 output); |
| 347 return true; |
107 } | 348 } |
OLD | NEW |