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

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

Issue 59693008: Give mojo_shell a TransportSecurityPersister (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: sprinkle some NET_EXPORT Created 7 years, 1 month 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
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/net/transport_security_persister.h"
6
7 #include "base/base64.h"
8 #include "base/bind.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "base/sequenced_task_runner.h"
16 #include "base/task_runner_util.h"
17 #include "base/values.h"
18 #include "crypto/sha2.h"
19 #include "net/cert/x509_certificate.h"
20 #include "net/http/transport_security_state.h"
21
22 using net::HashValue;
23 using net::HashValueTag;
24 using net::HashValueVector;
25 using net::TransportSecurityState;
26
27 namespace {
28
29 ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
30 ListValue* pins = new ListValue;
31 for (size_t i = 0; i != hashes.size(); i++)
32 pins->Append(new StringValue(hashes[i].ToString()));
33 return pins;
34 }
35
36 void SPKIHashesFromListValue(const ListValue& pins, HashValueVector* hashes) {
37 size_t num_pins = pins.GetSize();
38 for (size_t i = 0; i < num_pins; ++i) {
39 std::string type_and_base64;
40 HashValue fingerprint;
41 if (pins.GetString(i, &type_and_base64) &&
42 fingerprint.FromString(type_and_base64)) {
43 hashes->push_back(fingerprint);
44 }
45 }
46 }
47
48 // This function converts the binary hashes to a base64 string which we can
49 // include in a JSON file.
50 std::string HashedDomainToExternalString(const std::string& hashed) {
51 std::string out;
52 base::Base64Encode(hashed, &out);
53 return out;
54 }
55
56 // This inverts |HashedDomainToExternalString|, above. It turns an external
57 // string (from a JSON file) into an internal (binary) string.
58 std::string ExternalStringToHashedDomain(const std::string& external) {
59 std::string out;
60 if (!base::Base64Decode(external, &out) ||
61 out.size() != crypto::kSHA256Length) {
62 return std::string();
63 }
64
65 return out;
66 }
67
68 const char kIncludeSubdomains[] = "include_subdomains";
69 const char kStsIncludeSubdomains[] = "sts_include_subdomains";
70 const char kPkpIncludeSubdomains[] = "pkp_include_subdomains";
71 const char kMode[] = "mode";
72 const char kExpiry[] = "expiry";
73 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry";
74 const char kStaticSPKIHashes[] = "static_spki_hashes";
75 const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes";
76 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes";
77 const char kForceHTTPS[] = "force-https";
78 const char kStrict[] = "strict";
79 const char kDefault[] = "default";
80 const char kPinningOnly[] = "pinning-only";
81 const char kCreated[] = "created";
82
83 std::string LoadState(const base::FilePath& path) {
84 std::string result;
85 if (!base::ReadFileToString(path, &result)) {
86 return "";
87 }
88 return result;
89 }
90
91 } // namespace
92
93 TransportSecurityPersister::TransportSecurityPersister(
94 TransportSecurityState* state,
95 const base::FilePath& profile_path,
96 base::SequencedTaskRunner* background_runner,
97 bool readonly)
98 : transport_security_state_(state),
99 writer_(profile_path.AppendASCII("TransportSecurity"), background_runner),
100 foreground_runner_(base::MessageLoop::current()->message_loop_proxy()),
101 background_runner_(background_runner),
102 readonly_(readonly),
103 weak_ptr_factory_(this) {
104 transport_security_state_->SetDelegate(this);
105
106 base::PostTaskAndReplyWithResult(
107 background_runner_,
108 FROM_HERE,
109 base::Bind(&::LoadState, writer_.path()),
110 base::Bind(&TransportSecurityPersister::CompleteLoad,
111 weak_ptr_factory_.GetWeakPtr()));
112 }
113
114 TransportSecurityPersister::~TransportSecurityPersister() {
115 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
116
117 if (writer_.HasPendingWrite())
118 writer_.DoScheduledWrite();
119
120 transport_security_state_->SetDelegate(NULL);
121 }
122
123 void TransportSecurityPersister::StateIsDirty(
124 TransportSecurityState* state) {
125 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
126 DCHECK_EQ(transport_security_state_, state);
127
128 if (!readonly_)
129 writer_.ScheduleWrite(this);
130 }
131
132 bool TransportSecurityPersister::SerializeData(std::string* output) {
133 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
134
135 DictionaryValue toplevel;
136 base::Time now = base::Time::Now();
137 TransportSecurityState::Iterator state(*transport_security_state_);
138 for (; state.HasNext(); state.Advance()) {
139 const std::string& hostname = state.hostname();
140 const TransportSecurityState::DomainState& domain_state =
141 state.domain_state();
142
143 DictionaryValue* serialized = new DictionaryValue;
144 serialized->SetBoolean(kStsIncludeSubdomains,
145 domain_state.sts_include_subdomains);
146 serialized->SetBoolean(kPkpIncludeSubdomains,
147 domain_state.pkp_include_subdomains);
148 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT());
149 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT());
150 serialized->SetDouble(kDynamicSPKIHashesExpiry,
151 domain_state.dynamic_spki_hashes_expiry.ToDoubleT());
152
153 switch (domain_state.upgrade_mode) {
154 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
155 serialized->SetString(kMode, kForceHTTPS);
156 break;
157 case TransportSecurityState::DomainState::MODE_DEFAULT:
158 serialized->SetString(kMode, kDefault);
159 break;
160 default:
161 NOTREACHED() << "DomainState with unknown mode";
162 delete serialized;
163 continue;
164 }
165
166 serialized->Set(kStaticSPKIHashes,
167 SPKIHashesToListValue(domain_state.static_spki_hashes));
168
169 if (now < domain_state.dynamic_spki_hashes_expiry) {
170 serialized->Set(kDynamicSPKIHashes,
171 SPKIHashesToListValue(domain_state.dynamic_spki_hashes));
172 }
173
174 toplevel.Set(HashedDomainToExternalString(hostname), serialized);
175 }
176
177 base::JSONWriter::WriteWithOptions(&toplevel,
178 base::JSONWriter::OPTIONS_PRETTY_PRINT,
179 output);
180 return true;
181 }
182
183 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
184 bool* dirty) {
185 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
186
187 transport_security_state_->ClearDynamicData();
188 return Deserialize(serialized, dirty, transport_security_state_);
189 }
190
191 // static
192 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
193 bool* dirty,
194 TransportSecurityState* state) {
195 scoped_ptr<Value> value(base::JSONReader::Read(serialized));
196 DictionaryValue* dict_value = NULL;
197 if (!value.get() || !value->GetAsDictionary(&dict_value))
198 return false;
199
200 const base::Time current_time(base::Time::Now());
201 bool dirtied = false;
202
203 for (DictionaryValue::Iterator i(*dict_value); !i.IsAtEnd(); i.Advance()) {
204 const DictionaryValue* parsed = NULL;
205 if (!i.value().GetAsDictionary(&parsed)) {
206 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
207 continue;
208 }
209
210 std::string mode_string;
211 double created;
212 double expiry;
213 double dynamic_spki_hashes_expiry = 0.0;
214 TransportSecurityState::DomainState domain_state;
215
216 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
217 // kPkpIncludeSubdomains. Parse at least one of these properties,
218 // preferably the new ones.
219 bool include_subdomains = false;
220 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
221 &include_subdomains);
222 domain_state.sts_include_subdomains = include_subdomains;
223 domain_state.pkp_include_subdomains = include_subdomains;
224 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
225 domain_state.sts_include_subdomains = include_subdomains;
226 parsed_include_subdomains = true;
227 }
228 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
229 domain_state.pkp_include_subdomains = include_subdomains;
230 parsed_include_subdomains = true;
231 }
232
233 if (!parsed_include_subdomains ||
234 !parsed->GetString(kMode, &mode_string) ||
235 !parsed->GetDouble(kExpiry, &expiry)) {
236 LOG(WARNING) << "Could not parse some elements of entry " << i.key()
237 << "; skipping entry";
238 continue;
239 }
240
241 // Don't fail if this key is not present.
242 parsed->GetDouble(kDynamicSPKIHashesExpiry,
243 &dynamic_spki_hashes_expiry);
244
245 const ListValue* pins_list = NULL;
246 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes.
247 if (parsed->GetList(kStaticSPKIHashes, &pins_list))
248 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
249 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list))
250 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
251
252 if (parsed->GetList(kDynamicSPKIHashes, &pins_list))
253 SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes);
254
255 if (mode_string == kForceHTTPS || mode_string == kStrict) {
256 domain_state.upgrade_mode =
257 TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
258 } else if (mode_string == kDefault || mode_string == kPinningOnly) {
259 domain_state.upgrade_mode =
260 TransportSecurityState::DomainState::MODE_DEFAULT;
261 } else {
262 LOG(WARNING) << "Unknown TransportSecurityState mode string "
263 << mode_string << " found for entry " << i.key()
264 << "; skipping entry";
265 continue;
266 }
267
268 domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry);
269 domain_state.dynamic_spki_hashes_expiry =
270 base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
271 if (parsed->GetDouble(kCreated, &created)) {
272 domain_state.created = base::Time::FromDoubleT(created);
273 } else {
274 // We're migrating an old entry with no creation date. Make sure we
275 // write the new date back in a reasonable time frame.
276 dirtied = true;
277 domain_state.created = base::Time::Now();
278 }
279
280 if (domain_state.upgrade_expiry <= current_time &&
281 domain_state.dynamic_spki_hashes_expiry <= current_time) {
282 // Make sure we dirty the state if we drop an entry.
283 dirtied = true;
284 continue;
285 }
286
287 std::string hashed = ExternalStringToHashedDomain(i.key());
288 if (hashed.empty()) {
289 dirtied = true;
290 continue;
291 }
292
293 state->AddOrUpdateEnabledHosts(hashed, domain_state);
294 }
295
296 *dirty = dirtied;
297 return true;
298 }
299
300 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
301 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
302
303 bool dirty = false;
304 if (!LoadEntries(state, &dirty)) {
305 LOG(ERROR) << "Failed to deserialize state: " << state;
306 return;
307 }
308 if (dirty)
309 StateIsDirty(transport_security_state_);
310 }
OLDNEW
« no previous file with comments | « chrome/browser/net/transport_security_persister.h ('k') | chrome/browser/net/transport_security_persister_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698