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 |