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) { |
| 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) { |
| 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)); |
| 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)) |
| 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", |
| 174 &dynamic_spki_hashes_expiry); |
| 175 |
| 176 ListValue* pins_list = NULL; |
| 177 FingerprintVector static_spki_hashes; |
| 178 if (parsed->GetList("preloaded_spki_hashes", &pins_list)) |
| 179 SPKIHashesFromListValue(&static_spki_hashes, *pins_list); |
| 180 |
| 181 FingerprintVector dynamic_spki_hashes; |
| 182 if (parsed->GetList("dynamic_spki_hashes", &pins_list)) |
| 183 SPKIHashesFromListValue(&dynamic_spki_hashes, *pins_list); |
| 184 |
| 185 TransportSecurityState::DomainState::UpgradeMode mode; |
| 186 if (mode_string == "strict") { |
| 187 mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; |
| 188 } else if (mode_string == "spdy-only") { |
| 189 mode = TransportSecurityState::DomainState::MODE_DEFAULT; |
| 190 } else if (mode_string == "pinning-only") { |
| 191 mode = TransportSecurityState::DomainState::MODE_DEFAULT; |
| 192 } else { |
| 193 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " |
| 194 << mode_string; |
| 195 continue; |
| 196 } |
| 197 |
| 198 base::Time expiry_time = base::Time::FromDoubleT(expiry); |
| 199 base::Time dynamic_spki_hashes_expiry_time = |
| 200 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); |
| 201 base::Time created_time; |
| 202 if (parsed->GetDouble("created", &created)) { |
| 203 created_time = base::Time::FromDoubleT(created); |
| 204 } else { |
| 205 // We're migrating an old entry with no creation date. Make sure we |
| 206 // write the new date back in a reasonable time frame. |
| 207 dirtied = true; |
| 208 created_time = base::Time::Now(); |
| 209 } |
| 210 |
| 211 if (expiry_time <= current_time && |
| 212 dynamic_spki_hashes_expiry_time <= current_time) { |
| 213 // Make sure we dirty the state if we drop an entry. |
| 214 dirtied = true; |
| 215 continue; |
| 216 } |
| 217 |
| 218 std::string hashed = ExternalStringToHashedDomain(*i); |
| 219 if (hashed.empty()) { |
| 220 dirtied = true; |
| 221 continue; |
| 222 } |
| 223 |
| 224 TransportSecurityState::DomainState domain_state; |
| 225 domain_state.upgrade_mode = mode; |
| 226 domain_state.created = created_time; |
| 227 domain_state.upgrade_expiry = expiry_time; |
| 228 domain_state.include_subdomains = include_subdomains; |
| 229 domain_state.static_spki_hashes = static_spki_hashes; |
| 230 domain_state.dynamic_spki_hashes = dynamic_spki_hashes; |
| 231 domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; |
| 232 state->EnableHost(hashed, domain_state); |
| 233 } |
| 234 |
| 235 *dirty = dirtied; |
| 236 return true; |
| 237 } |
| 238 |
| 239 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, |
| 240 bool* dirty, |
| 241 TransportSecurityState* state) { |
| 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 243 |
| 244 state->Clear(); |
| 245 return Deserialize(serialized, dirty, state); |
| 246 } |
| 247 |
83 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 248 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
85 | 250 |
86 bool dirty = false; | 251 bool dirty = false; |
87 if (!transport_security_state_->LoadEntries(state, &dirty)) { | 252 if (!LoadEntries(state, &dirty, transport_security_state_)) { |
88 LOG(ERROR) << "Failed to deserialize state: " << state; | 253 LOG(ERROR) << "Failed to deserialize state: " << state; |
89 return; | 254 return; |
90 } | 255 } |
91 if (dirty) | 256 if (dirty) |
92 StateIsDirty(transport_security_state_); | 257 StateIsDirty(transport_security_state_); |
93 } | 258 } |
94 | 259 |
95 void TransportSecurityPersister::StateIsDirty( | 260 void TransportSecurityPersister::StateIsDirty( |
96 net::TransportSecurityState* state) { | 261 TransportSecurityState* state) { |
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
98 DCHECK_EQ(transport_security_state_, state); | 263 DCHECK_EQ(transport_security_state_, state); |
99 | 264 |
100 if (!readonly_) | 265 if (!readonly_) |
101 writer_.ScheduleWrite(this); | 266 writer_.ScheduleWrite(this); |
102 } | 267 } |
103 | 268 |
| 269 bool TransportSecurityPersister::Serialize(std::string* output) const { |
| 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 271 |
| 272 DictionaryValue toplevel; |
| 273 base::Time now = base::Time::Now(); |
| 274 TransportSecurityState::Iterator state(*transport_security_state_); |
| 275 for (; state.HasNext(); state.Advance()) { |
| 276 const std::string& hostname = state.hostname(); |
| 277 const TransportSecurityState::DomainState& domain_state = |
| 278 state.domain_state(); |
| 279 |
| 280 DictionaryValue* serialized = new DictionaryValue; |
| 281 serialized->SetBoolean("include_subdomains", |
| 282 domain_state.include_subdomains); |
| 283 serialized->SetDouble("created", domain_state.created.ToDoubleT()); |
| 284 serialized->SetDouble("expiry", domain_state.upgrade_expiry.ToDoubleT()); |
| 285 serialized->SetDouble("dynamic_spki_hashes_expiry", |
| 286 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); |
| 287 |
| 288 switch (domain_state.upgrade_mode) { |
| 289 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: |
| 290 serialized->SetString("mode", "strict"); |
| 291 break; |
| 292 case TransportSecurityState::DomainState::MODE_DEFAULT: |
| 293 serialized->SetString("mode", "pinning-only"); |
| 294 break; |
| 295 default: |
| 296 NOTREACHED() << "DomainState with unknown mode"; |
| 297 delete serialized; |
| 298 continue; |
| 299 } |
| 300 |
| 301 serialized->Set("preloaded_spki_hashes", |
| 302 SPKIHashesToListValue(domain_state.static_spki_hashes)); |
| 303 |
| 304 if (now < domain_state.dynamic_spki_hashes_expiry) { |
| 305 serialized->Set("dynamic_spki_hashes", |
| 306 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); |
| 307 } |
| 308 |
| 309 toplevel.Set(HashedDomainToExternalString(hostname), serialized); |
| 310 } |
| 311 |
| 312 base::JSONWriter::WriteWithOptions(&toplevel, |
| 313 base::JSONWriter::OPTIONS_PRETTY_PRINT, |
| 314 output); |
| 315 return true; |
| 316 } |
| 317 |
104 bool TransportSecurityPersister::SerializeData(std::string* data) { | 318 bool TransportSecurityPersister::SerializeData(std::string* data) { |
105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
106 return transport_security_state_->Serialise(data); | 320 return Serialize(data); |
107 } | 321 } |
OLD | NEW |