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

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, 8 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;
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698