Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / net / http / transport_security_persister.cc
blob78898c327e589e249b4dc8be2ecd1e03f794471a
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/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/location.h"
14 #include "base/sequenced_task_runner.h"
15 #include "base/task_runner_util.h"
16 #include "base/thread_task_runner_handle.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 namespace net {
24 namespace {
26 base::ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
27 base::ListValue* pins = new base::ListValue;
28 for (size_t i = 0; i != hashes.size(); i++)
29 pins->Append(new base::StringValue(hashes[i].ToString()));
30 return pins;
33 void SPKIHashesFromListValue(const base::ListValue& pins,
34 HashValueVector* hashes) {
35 size_t num_pins = pins.GetSize();
36 for (size_t i = 0; i < num_pins; ++i) {
37 std::string type_and_base64;
38 HashValue fingerprint;
39 if (pins.GetString(i, &type_and_base64) &&
40 fingerprint.FromString(type_and_base64)) {
41 hashes->push_back(fingerprint);
46 // This function converts the binary hashes to a base64 string which we can
47 // include in a JSON file.
48 std::string HashedDomainToExternalString(const std::string& hashed) {
49 std::string out;
50 base::Base64Encode(hashed, &out);
51 return out;
54 // This inverts |HashedDomainToExternalString|, above. It turns an external
55 // string (from a JSON file) into an internal (binary) string.
56 std::string ExternalStringToHashedDomain(const std::string& external) {
57 std::string out;
58 if (!base::Base64Decode(external, &out) ||
59 out.size() != crypto::kSHA256Length) {
60 return std::string();
63 return out;
66 const char kIncludeSubdomains[] = "include_subdomains";
67 const char kStsIncludeSubdomains[] = "sts_include_subdomains";
68 const char kPkpIncludeSubdomains[] = "pkp_include_subdomains";
69 const char kMode[] = "mode";
70 const char kExpiry[] = "expiry";
71 const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry";
72 const char kDynamicSPKIHashes[] = "dynamic_spki_hashes";
73 const char kForceHTTPS[] = "force-https";
74 const char kStrict[] = "strict";
75 const char kDefault[] = "default";
76 const char kPinningOnly[] = "pinning-only";
77 const char kCreated[] = "created";
78 const char kStsObserved[] = "sts_observed";
79 const char kPkpObserved[] = "pkp_observed";
80 const char kReportUri[] = "report-uri";
82 std::string LoadState(const base::FilePath& path) {
83 std::string result;
84 if (!base::ReadFileToString(path, &result)) {
85 return "";
87 return result;
90 } // namespace
92 TransportSecurityPersister::TransportSecurityPersister(
93 TransportSecurityState* state,
94 const base::FilePath& profile_path,
95 const scoped_refptr<base::SequencedTaskRunner>& background_runner,
96 bool readonly)
97 : transport_security_state_(state),
98 writer_(profile_path.AppendASCII("TransportSecurity"), background_runner),
99 foreground_runner_(base::ThreadTaskRunnerHandle::Get()),
100 background_runner_(background_runner),
101 readonly_(readonly),
102 weak_ptr_factory_(this) {
103 transport_security_state_->SetDelegate(this);
105 base::PostTaskAndReplyWithResult(
106 background_runner_.get(), FROM_HERE,
107 base::Bind(&LoadState, writer_.path()),
108 base::Bind(&TransportSecurityPersister::CompleteLoad,
109 weak_ptr_factory_.GetWeakPtr()));
112 TransportSecurityPersister::~TransportSecurityPersister() {
113 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
115 if (writer_.HasPendingWrite())
116 writer_.DoScheduledWrite();
118 transport_security_state_->SetDelegate(NULL);
121 void TransportSecurityPersister::StateIsDirty(
122 TransportSecurityState* state) {
123 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
124 DCHECK_EQ(transport_security_state_, state);
126 if (!readonly_)
127 writer_.ScheduleWrite(this);
130 bool TransportSecurityPersister::SerializeData(std::string* output) {
131 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
133 base::DictionaryValue toplevel;
134 base::Time now = base::Time::Now();
136 // TODO(davidben): Fix the serialization format by splitting the on-disk
137 // representation of the STS and PKP states. https://crbug.com/470295.
138 TransportSecurityState::STSStateIterator sts_iterator(
139 *transport_security_state_);
140 for (; sts_iterator.HasNext(); sts_iterator.Advance()) {
141 const std::string& hostname = sts_iterator.hostname();
142 const TransportSecurityState::STSState& sts_state =
143 sts_iterator.domain_state();
145 const std::string key = HashedDomainToExternalString(hostname);
146 scoped_ptr<base::DictionaryValue> serialized(new base::DictionaryValue);
147 PopulateEntryWithDefaults(serialized.get());
149 serialized->SetBoolean(kStsIncludeSubdomains, sts_state.include_subdomains);
150 serialized->SetDouble(kStsObserved, sts_state.last_observed.ToDoubleT());
151 serialized->SetDouble(kExpiry, sts_state.expiry.ToDoubleT());
153 switch (sts_state.upgrade_mode) {
154 case TransportSecurityState::STSState::MODE_FORCE_HTTPS:
155 serialized->SetString(kMode, kForceHTTPS);
156 break;
157 case TransportSecurityState::STSState::MODE_DEFAULT:
158 serialized->SetString(kMode, kDefault);
159 break;
160 default:
161 NOTREACHED() << "STSState with unknown mode";
162 continue;
165 toplevel.Set(key, serialized.Pass());
168 TransportSecurityState::PKPStateIterator pkp_iterator(
169 *transport_security_state_);
170 for (; pkp_iterator.HasNext(); pkp_iterator.Advance()) {
171 const std::string& hostname = pkp_iterator.hostname();
172 const TransportSecurityState::PKPState& pkp_state =
173 pkp_iterator.domain_state();
175 // See if the current |hostname| already has STS state and, if so, update
176 // that entry.
177 const std::string key = HashedDomainToExternalString(hostname);
178 base::DictionaryValue* serialized = nullptr;
179 if (!toplevel.GetDictionary(key, &serialized)) {
180 scoped_ptr<base::DictionaryValue> serialized_scoped(
181 new base::DictionaryValue);
182 serialized = serialized_scoped.get();
183 PopulateEntryWithDefaults(serialized);
184 toplevel.Set(key, serialized_scoped.Pass());
187 serialized->SetBoolean(kPkpIncludeSubdomains, pkp_state.include_subdomains);
188 serialized->SetDouble(kPkpObserved, pkp_state.last_observed.ToDoubleT());
189 serialized->SetDouble(kDynamicSPKIHashesExpiry,
190 pkp_state.expiry.ToDoubleT());
192 // TODO(svaldez): Historically, both SHA-1 and SHA-256 hashes were
193 // accepted in pins. Per spec, only SHA-256 is accepted now, however
194 // existing serialized pins are still processed. Migrate historical pins
195 // with SHA-1 hashes properly, either by dropping just the bad hashes or
196 // the entire pin. See https://crbug.com/448501.
197 if (now < pkp_state.expiry) {
198 serialized->Set(kDynamicSPKIHashes,
199 SPKIHashesToListValue(pkp_state.spki_hashes));
202 serialized->SetString(kReportUri, pkp_state.report_uri.spec());
205 base::JSONWriter::WriteWithOptions(
206 toplevel, base::JSONWriter::OPTIONS_PRETTY_PRINT, output);
207 return true;
210 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
211 bool* dirty) {
212 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
214 transport_security_state_->ClearDynamicData();
215 return Deserialize(serialized, dirty, transport_security_state_);
218 // static
219 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
220 bool* dirty,
221 TransportSecurityState* state) {
222 scoped_ptr<base::Value> value = base::JSONReader::Read(serialized);
223 base::DictionaryValue* dict_value = NULL;
224 if (!value.get() || !value->GetAsDictionary(&dict_value))
225 return false;
227 const base::Time current_time(base::Time::Now());
228 bool dirtied = false;
230 for (base::DictionaryValue::Iterator i(*dict_value);
231 !i.IsAtEnd(); i.Advance()) {
232 const base::DictionaryValue* parsed = NULL;
233 if (!i.value().GetAsDictionary(&parsed)) {
234 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
235 continue;
238 TransportSecurityState::STSState sts_state;
239 TransportSecurityState::PKPState pkp_state;
241 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
242 // kPkpIncludeSubdomains. Parse at least one of these properties,
243 // preferably the new ones.
244 bool include_subdomains = false;
245 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
246 &include_subdomains);
247 sts_state.include_subdomains = include_subdomains;
248 pkp_state.include_subdomains = include_subdomains;
249 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
250 sts_state.include_subdomains = include_subdomains;
251 parsed_include_subdomains = true;
253 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
254 pkp_state.include_subdomains = include_subdomains;
255 parsed_include_subdomains = true;
258 std::string mode_string;
259 double expiry = 0;
260 if (!parsed_include_subdomains ||
261 !parsed->GetString(kMode, &mode_string) ||
262 !parsed->GetDouble(kExpiry, &expiry)) {
263 LOG(WARNING) << "Could not parse some elements of entry " << i.key()
264 << "; skipping entry";
265 continue;
268 // Don't fail if this key is not present.
269 double dynamic_spki_hashes_expiry = 0;
270 parsed->GetDouble(kDynamicSPKIHashesExpiry,
271 &dynamic_spki_hashes_expiry);
273 const base::ListValue* pins_list = NULL;
274 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) {
275 SPKIHashesFromListValue(*pins_list, &pkp_state.spki_hashes);
278 if (mode_string == kForceHTTPS || mode_string == kStrict) {
279 sts_state.upgrade_mode =
280 TransportSecurityState::STSState::MODE_FORCE_HTTPS;
281 } else if (mode_string == kDefault || mode_string == kPinningOnly) {
282 sts_state.upgrade_mode = TransportSecurityState::STSState::MODE_DEFAULT;
283 } else {
284 LOG(WARNING) << "Unknown TransportSecurityState mode string "
285 << mode_string << " found for entry " << i.key()
286 << "; skipping entry";
287 continue;
290 sts_state.expiry = base::Time::FromDoubleT(expiry);
291 pkp_state.expiry = base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
293 // Don't fail if this key is not present.
294 std::string report_uri_str;
295 parsed->GetString(kReportUri, &report_uri_str);
296 GURL report_uri(report_uri_str);
297 if (report_uri.is_valid())
298 pkp_state.report_uri = report_uri;
300 double sts_observed;
301 double pkp_observed;
302 if (parsed->GetDouble(kStsObserved, &sts_observed)) {
303 sts_state.last_observed = base::Time::FromDoubleT(sts_observed);
304 } else if (parsed->GetDouble(kCreated, &sts_observed)) {
305 // kCreated is a legacy synonym for both kStsObserved and kPkpObserved.
306 sts_state.last_observed = base::Time::FromDoubleT(sts_observed);
307 } else {
308 // We're migrating an old entry with no observation date. Make sure we
309 // write the new date back in a reasonable time frame.
310 dirtied = true;
311 sts_state.last_observed = base::Time::Now();
313 if (parsed->GetDouble(kPkpObserved, &pkp_observed)) {
314 pkp_state.last_observed = base::Time::FromDoubleT(pkp_observed);
315 } else if (parsed->GetDouble(kCreated, &pkp_observed)) {
316 pkp_state.last_observed = base::Time::FromDoubleT(pkp_observed);
317 } else {
318 dirtied = true;
319 pkp_state.last_observed = base::Time::Now();
322 bool has_sts =
323 sts_state.expiry > current_time && sts_state.ShouldUpgradeToSSL();
324 bool has_pkp =
325 pkp_state.expiry > current_time && pkp_state.HasPublicKeyPins();
326 if (!has_sts && !has_pkp) {
327 // Make sure we dirty the state if we drop an entry. The entries can only
328 // be dropped when both the STS and PKP states are expired or invalid.
329 dirtied = true;
330 continue;
333 std::string hashed = ExternalStringToHashedDomain(i.key());
334 if (hashed.empty()) {
335 dirtied = true;
336 continue;
339 // Until the on-disk storage is split, there will always be 'null' entries.
340 // We only register entries that have actual state.
341 if (has_sts)
342 state->AddOrUpdateEnabledSTSHosts(hashed, sts_state);
343 if (has_pkp)
344 state->AddOrUpdateEnabledPKPHosts(hashed, pkp_state);
347 *dirty = dirtied;
348 return true;
351 void TransportSecurityPersister::PopulateEntryWithDefaults(
352 base::DictionaryValue* host) {
353 host->Clear();
355 // STS default values.
356 host->SetBoolean(kStsIncludeSubdomains, false);
357 host->SetDouble(kStsObserved, 0.0);
358 host->SetDouble(kExpiry, 0.0);
359 host->SetString(kMode, kDefault);
361 // PKP default values.
362 host->SetBoolean(kPkpIncludeSubdomains, false);
363 host->SetDouble(kPkpObserved, 0.0);
364 host->SetDouble(kDynamicSPKIHashesExpiry, 0.0);
367 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
368 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
370 if (state.empty())
371 return;
373 bool dirty = false;
374 if (!LoadEntries(state, &dirty)) {
375 LOG(ERROR) << "Failed to deserialize state: " << state;
376 return;
378 if (dirty)
379 StateIsDirty(transport_security_state_);
382 } // namespace net