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 "chrome/browser/net/transport_security_persister.h"
7 #include "base/base64.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/path_service.h"
15 #include "base/values.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "crypto/sha2.h"
19 #include "net/cert/x509_certificate.h"
20 #include "net/http/transport_security_state.h"
22 using content::BrowserThread;
24 using net::HashValueTag;
25 using net::HashValueVector;
26 using net::TransportSecurityState;
30 ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
31 ListValue* pins = new ListValue;
32 for (size_t i = 0; i != hashes.size(); i++)
33 pins->Append(new StringValue(hashes[i].ToString()));
37 void SPKIHashesFromListValue(const ListValue& pins, 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) {
53 base::Base64Encode(hashed, &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) {
61 if (!base::Base64Decode(external, &out) ||
62 out.size() != crypto::kSHA256Length) {
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";
86 class TransportSecurityPersister::Loader {
88 Loader(const base::WeakPtr<TransportSecurityPersister>& persister,
89 const base::FilePath& path)
90 : persister_(persister),
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
97 state_valid_ = base::ReadFileToString(path_, &state_);
100 void CompleteLoad() {
101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
103 // Make sure we're deleted.
104 scoped_ptr<Loader> deleter(this);
106 if (!persister_.get() || !state_valid_)
108 persister_->CompleteLoad(state_);
112 base::WeakPtr<TransportSecurityPersister> persister_;
114 base::FilePath path_;
119 DISALLOW_COPY_AND_ASSIGN(Loader);
122 TransportSecurityPersister::TransportSecurityPersister(
123 TransportSecurityState* state,
124 const base::FilePath& profile_path,
126 : transport_security_state_(state),
127 writer_(profile_path.AppendASCII("TransportSecurity"),
128 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
131 weak_ptr_factory_(this) {
132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
134 transport_security_state_->SetDelegate(this);
136 Loader* loader = new Loader(weak_ptr_factory_.GetWeakPtr(), writer_.path());
137 BrowserThread::PostTaskAndReply(
138 BrowserThread::FILE, FROM_HERE,
139 base::Bind(&Loader::Load, base::Unretained(loader)),
140 base::Bind(&Loader::CompleteLoad, base::Unretained(loader)));
143 TransportSecurityPersister::~TransportSecurityPersister() {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
146 if (writer_.HasPendingWrite())
147 writer_.DoScheduledWrite();
149 transport_security_state_->SetDelegate(NULL);
152 void TransportSecurityPersister::StateIsDirty(
153 TransportSecurityState* state) {
154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
155 DCHECK_EQ(transport_security_state_, state);
158 writer_.ScheduleWrite(this);
161 bool TransportSecurityPersister::SerializeData(std::string* output) {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
164 DictionaryValue toplevel;
165 base::Time now = base::Time::Now();
166 TransportSecurityState::Iterator state(*transport_security_state_);
167 for (; state.HasNext(); state.Advance()) {
168 const std::string& hostname = state.hostname();
169 const TransportSecurityState::DomainState& domain_state =
170 state.domain_state();
172 DictionaryValue* serialized = new DictionaryValue;
173 serialized->SetBoolean(kStsIncludeSubdomains,
174 domain_state.sts_include_subdomains);
175 serialized->SetBoolean(kPkpIncludeSubdomains,
176 domain_state.pkp_include_subdomains);
177 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT());
178 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT());
179 serialized->SetDouble(kDynamicSPKIHashesExpiry,
180 domain_state.dynamic_spki_hashes_expiry.ToDoubleT());
182 switch (domain_state.upgrade_mode) {
183 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
184 serialized->SetString(kMode, kForceHTTPS);
186 case TransportSecurityState::DomainState::MODE_DEFAULT:
187 serialized->SetString(kMode, kDefault);
190 NOTREACHED() << "DomainState with unknown mode";
195 serialized->Set(kStaticSPKIHashes,
196 SPKIHashesToListValue(domain_state.static_spki_hashes));
198 if (now < domain_state.dynamic_spki_hashes_expiry) {
199 serialized->Set(kDynamicSPKIHashes,
200 SPKIHashesToListValue(domain_state.dynamic_spki_hashes));
203 toplevel.Set(HashedDomainToExternalString(hostname), serialized);
206 base::JSONWriter::WriteWithOptions(&toplevel,
207 base::JSONWriter::OPTIONS_PRETTY_PRINT,
212 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
216 transport_security_state_->ClearDynamicData();
217 return Deserialize(serialized, dirty, transport_security_state_);
221 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
223 TransportSecurityState* state) {
224 scoped_ptr<Value> value(base::JSONReader::Read(serialized));
225 DictionaryValue* dict_value = NULL;
226 if (!value.get() || !value->GetAsDictionary(&dict_value))
229 const base::Time current_time(base::Time::Now());
230 bool dirtied = false;
232 for (DictionaryValue::Iterator i(*dict_value); !i.IsAtEnd(); i.Advance()) {
233 const DictionaryValue* parsed = NULL;
234 if (!i.value().GetAsDictionary(&parsed)) {
235 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
239 std::string mode_string;
242 double dynamic_spki_hashes_expiry = 0.0;
243 TransportSecurityState::DomainState domain_state;
245 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
246 // kPkpIncludeSubdomains. Parse at least one of these properties,
247 // preferably the new ones.
248 bool include_subdomains = false;
249 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
250 &include_subdomains);
251 domain_state.sts_include_subdomains = include_subdomains;
252 domain_state.pkp_include_subdomains = include_subdomains;
253 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
254 domain_state.sts_include_subdomains = include_subdomains;
255 parsed_include_subdomains = true;
257 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
258 domain_state.pkp_include_subdomains = include_subdomains;
259 parsed_include_subdomains = true;
262 if (!parsed_include_subdomains ||
263 !parsed->GetString(kMode, &mode_string) ||
264 !parsed->GetDouble(kExpiry, &expiry)) {
265 LOG(WARNING) << "Could not parse some elements of entry " << i.key()
266 << "; skipping entry";
270 // Don't fail if this key is not present.
271 parsed->GetDouble(kDynamicSPKIHashesExpiry,
272 &dynamic_spki_hashes_expiry);
274 const ListValue* pins_list = NULL;
275 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes.
276 if (parsed->GetList(kStaticSPKIHashes, &pins_list))
277 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
278 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list))
279 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
281 if (parsed->GetList(kDynamicSPKIHashes, &pins_list))
282 SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes);
284 if (mode_string == kForceHTTPS || mode_string == kStrict) {
285 domain_state.upgrade_mode =
286 TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
287 } else if (mode_string == kDefault || mode_string == kPinningOnly) {
288 domain_state.upgrade_mode =
289 TransportSecurityState::DomainState::MODE_DEFAULT;
291 LOG(WARNING) << "Unknown TransportSecurityState mode string "
292 << mode_string << " found for entry " << i.key()
293 << "; skipping entry";
297 domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry);
298 domain_state.dynamic_spki_hashes_expiry =
299 base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
300 if (parsed->GetDouble(kCreated, &created)) {
301 domain_state.created = base::Time::FromDoubleT(created);
303 // We're migrating an old entry with no creation date. Make sure we
304 // write the new date back in a reasonable time frame.
306 domain_state.created = base::Time::Now();
309 if (domain_state.upgrade_expiry <= current_time &&
310 domain_state.dynamic_spki_hashes_expiry <= current_time) {
311 // Make sure we dirty the state if we drop an entry.
316 std::string hashed = ExternalStringToHashedDomain(i.key());
317 if (hashed.empty()) {
322 state->AddOrUpdateEnabledHosts(hashed, domain_state);
329 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
333 if (!LoadEntries(state, &dirty)) {
334 LOG(ERROR) << "Failed to deserialize state: " << state;
338 StateIsDirty(transport_security_state_);