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"
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"
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()));
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
) {
50 base::Base64Encode(hashed
, &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
) {
58 if (!base::Base64Decode(external
, &out
) ||
59 out
.size() != crypto::kSHA256Length
) {
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
) {
84 if (!base::ReadFileToString(path
, &result
)) {
92 TransportSecurityPersister::TransportSecurityPersister(
93 TransportSecurityState
* state
,
94 const base::FilePath
& profile_path
,
95 const scoped_refptr
<base::SequencedTaskRunner
>& background_runner
,
97 : transport_security_state_(state
),
98 writer_(profile_path
.AppendASCII("TransportSecurity"), background_runner
),
99 foreground_runner_(base::ThreadTaskRunnerHandle::Get()),
100 background_runner_(background_runner
),
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
);
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
);
157 case TransportSecurityState::STSState::MODE_DEFAULT
:
158 serialized
->SetString(kMode
, kDefault
);
161 NOTREACHED() << "STSState with unknown mode";
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
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
);
210 bool TransportSecurityPersister::LoadEntries(const std::string
& serialized
,
212 DCHECK(foreground_runner_
->RunsTasksOnCurrentThread());
214 transport_security_state_
->ClearDynamicData();
215 return Deserialize(serialized
, dirty
, transport_security_state_
);
219 bool TransportSecurityPersister::Deserialize(const std::string
& serialized
,
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
))
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";
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
;
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";
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
;
284 LOG(WARNING
) << "Unknown TransportSecurityState mode string "
285 << mode_string
<< " found for entry " << i
.key()
286 << "; skipping entry";
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
;
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
);
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.
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
);
319 pkp_state
.last_observed
= base::Time::Now();
323 sts_state
.expiry
> current_time
&& sts_state
.ShouldUpgradeToSSL();
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.
333 std::string hashed
= ExternalStringToHashedDomain(i
.key());
334 if (hashed
.empty()) {
339 // Until the on-disk storage is split, there will always be 'null' entries.
340 // We only register entries that have actual state.
342 state
->AddOrUpdateEnabledSTSHosts(hashed
, sts_state
);
344 state
->AddOrUpdateEnabledPKPHosts(hashed
, pkp_state
);
351 void TransportSecurityPersister::PopulateEntryWithDefaults(
352 base::DictionaryValue
* host
) {
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());
374 if (!LoadEntries(state
, &dirty
)) {
375 LOG(ERROR
) << "Failed to deserialize state: " << state
;
379 StateIsDirty(transport_security_state_
);