Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(378)

Side by Side Diff: chrome/browser/transport_security_persister.cc

Issue 9415040: Refactor TransportSecurityState. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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 }
OLDNEW
« no previous file with comments | « chrome/browser/transport_security_persister.h ('k') | chrome/browser/transport_security_persister_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698