Web MIDI: enable receiving functionality in Linux and Chrome OS
[chromium-blink-merge.git] / net / http / transport_security_persister.cc
blobd93291f4e7e29742e13e8004e646e42716a5c9bc
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.
5 #include "net/http/transport_security_persister.h"
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"
22 using net::HashValue;
23 using net::HashValueTag;
24 using net::HashValueVector;
25 using net::TransportSecurityState;
27 namespace {
29 base::ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
30 base::ListValue* pins = new base::ListValue;
31 for (size_t i = 0; i != hashes.size(); i++)
32 pins->Append(new base::StringValue(hashes[i].ToString()));
33 return pins;
36 void SPKIHashesFromListValue(const base::ListValue& pins,
37 HashValueVector* hashes) {
38 size_t num_pins = pins.GetSize();
39 for (size_t i = 0; i < num_pins; ++i) {
40 std::string type_and_base64;
41 HashValue fingerprint;
42 if (pins.GetString(i, &type_and_base64) &&
43 fingerprint.FromString(type_and_base64)) {
44 hashes->push_back(fingerprint);
49 // This function converts the binary hashes to a base64 string which we can
50 // include in a JSON file.
51 std::string HashedDomainToExternalString(const std::string& hashed) {
52 std::string out;
53 base::Base64Encode(hashed, &out);
54 return out;
57 // This inverts |HashedDomainToExternalString|, above. It turns an external
58 // string (from a JSON file) into an internal (binary) string.
59 std::string ExternalStringToHashedDomain(const std::string& external) {
60 std::string out;
61 if (!base::Base64Decode(external, &out) ||
62 out.size() != crypto::kSHA256Length) {
63 return std::string();
66 return out;
69 const char kIncludeSubdomains[] = "include_subdomains";
70 const char kStsIncludeSubdomains[] = "sts_include_subdomains";
71 const char kPkpIncludeSubdomains[] = "pkp_include_subdomains";
72 const char kMode[] = "mode";
73 const char kExpiry[] = "expiry";
74 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry";
75 const char kStaticSPKIHashes[] = "static_spki_hashes";
76 const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes";
77 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes";
78 const char kForceHTTPS[] = "force-https";
79 const char kStrict[] = "strict";
80 const char kDefault[] = "default";
81 const char kPinningOnly[] = "pinning-only";
82 const char kCreated[] = "created";
83 const char kStsObserved[] = "sts_observed";
84 const char kPkpObserved[] = "pkp_observed";
86 std::string LoadState(const base::FilePath& path) {
87 std::string result;
88 if (!base::ReadFileToString(path, &result)) {
89 return "";
91 return result;
94 } // namespace
97 namespace net {
99 TransportSecurityPersister::TransportSecurityPersister(
100 TransportSecurityState* state,
101 const base::FilePath& profile_path,
102 base::SequencedTaskRunner* background_runner,
103 bool readonly)
104 : transport_security_state_(state),
105 writer_(profile_path.AppendASCII("TransportSecurity"), background_runner),
106 foreground_runner_(base::MessageLoop::current()->message_loop_proxy()),
107 background_runner_(background_runner),
108 readonly_(readonly),
109 weak_ptr_factory_(this) {
110 transport_security_state_->SetDelegate(this);
112 base::PostTaskAndReplyWithResult(
113 background_runner_,
114 FROM_HERE,
115 base::Bind(&::LoadState, writer_.path()),
116 base::Bind(&TransportSecurityPersister::CompleteLoad,
117 weak_ptr_factory_.GetWeakPtr()));
120 TransportSecurityPersister::~TransportSecurityPersister() {
121 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
123 if (writer_.HasPendingWrite())
124 writer_.DoScheduledWrite();
126 transport_security_state_->SetDelegate(NULL);
129 void TransportSecurityPersister::StateIsDirty(
130 TransportSecurityState* state) {
131 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
132 DCHECK_EQ(transport_security_state_, state);
134 if (!readonly_)
135 writer_.ScheduleWrite(this);
138 bool TransportSecurityPersister::SerializeData(std::string* output) {
139 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
141 base::DictionaryValue toplevel;
142 base::Time now = base::Time::Now();
143 TransportSecurityState::Iterator state(*transport_security_state_);
144 for (; state.HasNext(); state.Advance()) {
145 const std::string& hostname = state.hostname();
146 const TransportSecurityState::DomainState& domain_state =
147 state.domain_state();
149 base::DictionaryValue* serialized = new base::DictionaryValue;
150 serialized->SetBoolean(kStsIncludeSubdomains,
151 domain_state.sts_include_subdomains);
152 serialized->SetBoolean(kPkpIncludeSubdomains,
153 domain_state.pkp_include_subdomains);
154 serialized->SetDouble(kStsObserved, domain_state.sts_observed.ToDoubleT());
155 serialized->SetDouble(kPkpObserved, domain_state.pkp_observed.ToDoubleT());
156 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT());
157 serialized->SetDouble(kDynamicSPKIHashesExpiry,
158 domain_state.dynamic_spki_hashes_expiry.ToDoubleT());
160 switch (domain_state.upgrade_mode) {
161 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
162 serialized->SetString(kMode, kForceHTTPS);
163 break;
164 case TransportSecurityState::DomainState::MODE_DEFAULT:
165 serialized->SetString(kMode, kDefault);
166 break;
167 default:
168 NOTREACHED() << "DomainState with unknown mode";
169 delete serialized;
170 continue;
173 serialized->Set(kStaticSPKIHashes,
174 SPKIHashesToListValue(domain_state.static_spki_hashes));
176 if (now < domain_state.dynamic_spki_hashes_expiry) {
177 serialized->Set(kDynamicSPKIHashes,
178 SPKIHashesToListValue(domain_state.dynamic_spki_hashes));
181 toplevel.Set(HashedDomainToExternalString(hostname), serialized);
184 base::JSONWriter::WriteWithOptions(&toplevel,
185 base::JSONWriter::OPTIONS_PRETTY_PRINT,
186 output);
187 return true;
190 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
191 bool* dirty) {
192 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
194 transport_security_state_->ClearDynamicData();
195 return Deserialize(serialized, dirty, transport_security_state_);
198 // static
199 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
200 bool* dirty,
201 TransportSecurityState* state) {
202 scoped_ptr<base::Value> value(base::JSONReader::Read(serialized));
203 base::DictionaryValue* dict_value = NULL;
204 if (!value.get() || !value->GetAsDictionary(&dict_value))
205 return false;
207 const base::Time current_time(base::Time::Now());
208 bool dirtied = false;
210 for (base::DictionaryValue::Iterator i(*dict_value);
211 !i.IsAtEnd(); i.Advance()) {
212 const base::DictionaryValue* parsed = NULL;
213 if (!i.value().GetAsDictionary(&parsed)) {
214 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
215 continue;
218 std::string mode_string;
219 double expiry;
220 double dynamic_spki_hashes_expiry = 0.0;
221 TransportSecurityState::DomainState domain_state;
223 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
224 // kPkpIncludeSubdomains. Parse at least one of these properties,
225 // preferably the new ones.
226 bool include_subdomains = false;
227 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
228 &include_subdomains);
229 domain_state.sts_include_subdomains = include_subdomains;
230 domain_state.pkp_include_subdomains = include_subdomains;
231 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
232 domain_state.sts_include_subdomains = include_subdomains;
233 parsed_include_subdomains = true;
235 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
236 domain_state.pkp_include_subdomains = include_subdomains;
237 parsed_include_subdomains = true;
240 if (!parsed_include_subdomains ||
241 !parsed->GetString(kMode, &mode_string) ||
242 !parsed->GetDouble(kExpiry, &expiry)) {
243 LOG(WARNING) << "Could not parse some elements of entry " << i.key()
244 << "; skipping entry";
245 continue;
248 // Don't fail if this key is not present.
249 parsed->GetDouble(kDynamicSPKIHashesExpiry,
250 &dynamic_spki_hashes_expiry);
252 const base::ListValue* pins_list = NULL;
253 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes.
254 if (parsed->GetList(kStaticSPKIHashes, &pins_list))
255 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
256 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list))
257 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
259 if (parsed->GetList(kDynamicSPKIHashes, &pins_list))
260 SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes);
262 if (mode_string == kForceHTTPS || mode_string == kStrict) {
263 domain_state.upgrade_mode =
264 TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
265 } else if (mode_string == kDefault || mode_string == kPinningOnly) {
266 domain_state.upgrade_mode =
267 TransportSecurityState::DomainState::MODE_DEFAULT;
268 } else {
269 LOG(WARNING) << "Unknown TransportSecurityState mode string "
270 << mode_string << " found for entry " << i.key()
271 << "; skipping entry";
272 continue;
275 domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry);
276 domain_state.dynamic_spki_hashes_expiry =
277 base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
279 double sts_observed;
280 double pkp_observed;
281 if (parsed->GetDouble(kStsObserved, &sts_observed)) {
282 domain_state.sts_observed = base::Time::FromDoubleT(sts_observed);
283 } else if (parsed->GetDouble(kCreated, &sts_observed)) {
284 // kCreated is a legacy synonym for both kStsObserved and kPkpObserved.
285 domain_state.sts_observed = base::Time::FromDoubleT(sts_observed);
286 } else {
287 // We're migrating an old entry with no observation date. Make sure we
288 // write the new date back in a reasonable time frame.
289 dirtied = true;
290 domain_state.sts_observed = base::Time::Now();
292 if (parsed->GetDouble(kPkpObserved, &pkp_observed)) {
293 domain_state.pkp_observed = base::Time::FromDoubleT(pkp_observed);
294 } else if (parsed->GetDouble(kCreated, &pkp_observed)) {
295 domain_state.pkp_observed = base::Time::FromDoubleT(pkp_observed);
296 } else {
297 dirtied = true;
298 domain_state.pkp_observed = base::Time::Now();
301 if (domain_state.upgrade_expiry <= current_time &&
302 domain_state.dynamic_spki_hashes_expiry <= current_time) {
303 // Make sure we dirty the state if we drop an entry.
304 dirtied = true;
305 continue;
308 std::string hashed = ExternalStringToHashedDomain(i.key());
309 if (hashed.empty()) {
310 dirtied = true;
311 continue;
314 state->AddOrUpdateEnabledHosts(hashed, domain_state);
317 *dirty = dirtied;
318 return true;
321 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
322 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
324 if (state.empty())
325 return;
327 bool dirty = false;
328 if (!LoadEntries(state, &dirty)) {
329 LOG(ERROR) << "Failed to deserialize state: " << state;
330 return;
332 if (dirty)
333 StateIsDirty(transport_security_state_);
336 } // namespace net