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 namespace { | |
Ryan Sleevi
2012/04/26 19:21:12
Place unnamed namespaces at the beginning of the f
palmer
2012/04/27 23:52:34
Done.
| |
93 | |
94 ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) { | |
95 ListValue* pins = new ListValue; | |
96 | |
97 for (FingerprintVector::const_iterator i = hashes.begin(); | |
98 i != hashes.end(); ++i) { | |
99 std::string hash_str(reinterpret_cast<const char*>(i->data), | |
100 sizeof(i->data)); | |
101 std::string b64; | |
102 base::Base64Encode(hash_str, &b64); | |
103 pins->Append(new StringValue("sha1/" + b64)); | |
104 } | |
105 | |
106 return pins; | |
107 } | |
108 | |
109 void SPKIHashesFromListValue(const ListValue& pins, FingerprintVector* hashes) { | |
110 size_t num_pins = pins.GetSize(); | |
111 for (size_t i = 0; i < num_pins; ++i) { | |
112 std::string type_and_base64; | |
113 Fingerprint fingerprint; | |
114 if (pins.GetString(i, &type_and_base64) && | |
115 TransportSecurityState::ParsePin(type_and_base64, &fingerprint)) { | |
116 hashes->push_back(fingerprint); | |
117 } | |
118 } | |
119 } | |
120 | |
121 // This function converts the binary hashes to a base64 string which we can | |
122 // include in a JSON file. | |
123 std::string HashedDomainToExternalString(const std::string& hashed) { | |
124 std::string out; | |
125 base::Base64Encode(hashed, &out); | |
126 return out; | |
127 } | |
128 | |
129 // This inverts |HashedDomainToExternalString|, above. It turns an external | |
130 // string (from a JSON file) into an internal (binary) string. | |
131 std::string ExternalStringToHashedDomain(const std::string& external) { | |
132 std::string out; | |
133 if (!base::Base64Decode(external, &out) || | |
134 out.size() != crypto::kSHA256Length) { | |
135 return std::string(); | |
136 } | |
137 | |
138 return out; | |
139 } | |
140 | |
141 const char kIncludeSubdomains[] = "include_subdomains"; | |
142 const char kMode[] = "mode"; | |
143 const char kExpiry[] = "expiry"; | |
144 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry"; | |
145 const char kStaticSPKIHashes[] = "static_spki_hashes"; | |
146 const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes"; | |
147 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes"; | |
148 const char kForceHTTPS[] = "force-https"; | |
149 const char kStrict[] = "strict"; | |
150 const char kDefault[] = "default"; | |
151 const char kPinningOnly[] = "pinning-only"; | |
152 const char kCreated[] = "created"; | |
153 | |
154 } // anonymous namespce | |
155 | |
156 bool TransportSecurityPersister::DeserializeFromCommandLine( | |
Ryan Sleevi
2012/04/26 19:21:12
nit: Fix the ordering of this file so that definit
palmer
2012/04/27 23:52:34
Will do.
| |
157 const std::string& serialized) { | |
158 // We purposefully ignore |dirty| because we do not want to persist | |
Ryan Sleevi
2012/04/26 19:21:12
nit: Drop the we
palmer
2012/04/27 23:52:34
Done.
| |
159 // entries deserialized in this way. | |
160 bool dirty; | |
161 return Deserialize(serialized, &dirty, true, transport_security_state_); | |
162 } | |
163 | |
164 // static | |
165 bool TransportSecurityPersister::Deserialize(const std::string& serialized, | |
166 bool* dirty, | |
167 bool forced, | |
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 continue; | |
182 | |
183 bool include_subdomains; | |
184 std::string mode_string; | |
185 double created; | |
186 double expiry; | |
187 double dynamic_spki_hashes_expiry = 0.0; | |
188 | |
189 if (!parsed->GetBoolean(kIncludeSubdomains, &include_subdomains) || | |
190 !parsed->GetString(kMode, &mode_string) || | |
191 !parsed->GetDouble(kExpiry, &expiry)) { | |
Ryan Sleevi
2012/04/26 19:21:12
So input data failures aren't fatal?
Should there
palmer
2012/04/27 23:52:34
The code as it is here is merely moved, not change
| |
192 continue; | |
193 } | |
194 | |
195 // Don't fail if this key is not present. | |
196 parsed->GetDouble(kDynamicSPKIHashesExpiry, | |
197 &dynamic_spki_hashes_expiry); | |
198 | |
199 ListValue* pins_list = NULL; | |
200 FingerprintVector static_spki_hashes; | |
201 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. | |
202 if (parsed->GetList(kStaticSPKIHashes, &pins_list)) | |
203 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); | |
204 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) | |
205 SPKIHashesFromListValue(*pins_list, &static_spki_hashes); | |
206 | |
207 FingerprintVector dynamic_spki_hashes; | |
208 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) | |
209 SPKIHashesFromListValue(*pins_list, &dynamic_spki_hashes); | |
210 | |
211 TransportSecurityState::DomainState::UpgradeMode mode; | |
212 if (mode_string == kForceHTTPS || mode_string == kStrict) { | |
213 mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; | |
214 } else if (mode_string == kDefault || mode_string == kPinningOnly) { | |
215 mode = TransportSecurityState::DomainState::MODE_DEFAULT; | |
216 } else { | |
217 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " | |
218 << mode_string; | |
219 continue; | |
220 } | |
221 | |
222 base::Time expiry_time = base::Time::FromDoubleT(expiry); | |
223 base::Time dynamic_spki_hashes_expiry_time = | |
224 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); | |
225 base::Time created_time; | |
226 if (parsed->GetDouble(kCreated, &created)) { | |
227 created_time = base::Time::FromDoubleT(created); | |
228 } else { | |
229 // We're migrating an old entry with no creation date. Make sure we | |
230 // write the new date back in a reasonable time frame. | |
231 dirtied = true; | |
232 created_time = base::Time::Now(); | |
233 } | |
234 | |
235 if (expiry_time <= current_time && | |
236 dynamic_spki_hashes_expiry_time <= current_time) { | |
237 // Make sure we dirty the state if we drop an entry. | |
238 dirtied = true; | |
239 continue; | |
240 } | |
241 | |
242 std::string hashed = ExternalStringToHashedDomain(*i); | |
243 if (hashed.empty()) { | |
244 dirtied = true; | |
245 continue; | |
246 } | |
247 | |
248 TransportSecurityState::DomainState domain_state; | |
249 domain_state.upgrade_mode = mode; | |
250 domain_state.created = created_time; | |
251 domain_state.upgrade_expiry = expiry_time; | |
252 domain_state.include_subdomains = include_subdomains; | |
253 domain_state.static_spki_hashes = static_spki_hashes; | |
254 domain_state.dynamic_spki_hashes = dynamic_spki_hashes; | |
255 domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; | |
256 | |
257 if (forced) | |
258 state->GetForcedHosts()[hashed] = domain_state; | |
259 else | |
260 state->GetEnabledHosts()[hashed] = domain_state; | |
261 } | |
262 | |
263 *dirty = dirtied; | |
264 return true; | |
265 } | |
266 | |
267 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, | |
268 bool* dirty) { | |
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
270 | |
271 transport_security_state_->Clear(); | |
272 return Deserialize(serialized, dirty, false, transport_security_state_); | |
273 } | |
274 | |
83 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 275 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
85 | 277 |
86 bool dirty = false; | 278 bool dirty = false; |
87 if (!transport_security_state_->LoadEntries(state, &dirty)) { | 279 if (!LoadEntries(state, &dirty)) { |
88 LOG(ERROR) << "Failed to deserialize state: " << state; | 280 LOG(ERROR) << "Failed to deserialize state: " << state; |
89 return; | 281 return; |
90 } | 282 } |
91 if (dirty) | 283 if (dirty) |
92 StateIsDirty(transport_security_state_); | 284 StateIsDirty(transport_security_state_); |
93 } | 285 } |
94 | 286 |
95 void TransportSecurityPersister::StateIsDirty( | 287 void TransportSecurityPersister::StateIsDirty( |
96 net::TransportSecurityState* state) { | 288 TransportSecurityState* state) { |
97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
98 DCHECK_EQ(transport_security_state_, state); | 290 DCHECK_EQ(transport_security_state_, state); |
99 | 291 |
100 if (!readonly_) | 292 if (!readonly_) |
101 writer_.ScheduleWrite(this); | 293 writer_.ScheduleWrite(this); |
102 } | 294 } |
103 | 295 |
104 bool TransportSecurityPersister::SerializeData(std::string* data) { | 296 bool TransportSecurityPersister::SerializeData(std::string* output) { |
105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
106 return transport_security_state_->Serialise(data); | 298 |
299 DictionaryValue toplevel; | |
300 base::Time now = base::Time::Now(); | |
301 TransportSecurityState::Iterator state(*transport_security_state_); | |
302 for (; state.HasNext(); state.Advance()) { | |
303 const std::string& hostname = state.hostname(); | |
304 const TransportSecurityState::DomainState& domain_state = | |
305 state.domain_state(); | |
306 | |
307 DictionaryValue* serialized = new DictionaryValue; | |
308 serialized->SetBoolean(kIncludeSubdomains, | |
309 domain_state.include_subdomains); | |
310 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT()); | |
311 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT()); | |
312 serialized->SetDouble(kDynamicSPKIHashesExpiry, | |
313 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); | |
314 | |
315 switch (domain_state.upgrade_mode) { | |
316 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: | |
317 serialized->SetString(kMode, kForceHTTPS); | |
318 break; | |
319 case TransportSecurityState::DomainState::MODE_DEFAULT: | |
320 serialized->SetString(kMode, kDefault); | |
321 break; | |
322 default: | |
323 NOTREACHED() << "DomainState with unknown mode"; | |
324 delete serialized; | |
325 continue; | |
326 } | |
327 | |
328 serialized->Set(kStaticSPKIHashes, | |
329 SPKIHashesToListValue(domain_state.static_spki_hashes)); | |
330 | |
331 if (now < domain_state.dynamic_spki_hashes_expiry) { | |
332 serialized->Set(kDynamicSPKIHashes, | |
333 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); | |
334 } | |
335 | |
336 toplevel.Set(HashedDomainToExternalString(hostname), serialized); | |
337 } | |
338 | |
339 base::JSONWriter::WriteWithOptions(&toplevel, | |
340 base::JSONWriter::OPTIONS_PRETTY_PRINT, | |
341 output); | |
342 return true; | |
107 } | 343 } |
OLD | NEW |