Make Ctrl + T from apps focus the new tab's omnibox on Linux and ChromeOS.
[chromium-blink-merge.git] / net / http / transport_security_persister.cc
blob08aa07e1c9112ae1d8046dfb59f3303d4240aea9
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/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 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";
81 std::string LoadState(const base::FilePath& path) {
82 std::string result;
83 if (!base::ReadFileToString(path, &result)) {
84 return "";
86 return result;
89 } // namespace
91 TransportSecurityPersister::TransportSecurityPersister(
92 TransportSecurityState* state,
93 const base::FilePath& profile_path,
94 const scoped_refptr<base::SequencedTaskRunner>& background_runner,
95 bool readonly)
96 : transport_security_state_(state),
97 writer_(profile_path.AppendASCII("TransportSecurity"), background_runner),
98 foreground_runner_(base::MessageLoop::current()->message_loop_proxy()),
99 background_runner_(background_runner),
100 readonly_(readonly),
101 weak_ptr_factory_(this) {
102 transport_security_state_->SetDelegate(this);
104 base::PostTaskAndReplyWithResult(
105 background_runner_.get(), FROM_HERE,
106 base::Bind(&LoadState, writer_.path()),
107 base::Bind(&TransportSecurityPersister::CompleteLoad,
108 weak_ptr_factory_.GetWeakPtr()));
111 TransportSecurityPersister::~TransportSecurityPersister() {
112 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
114 if (writer_.HasPendingWrite())
115 writer_.DoScheduledWrite();
117 transport_security_state_->SetDelegate(NULL);
120 void TransportSecurityPersister::StateIsDirty(
121 TransportSecurityState* state) {
122 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
123 DCHECK_EQ(transport_security_state_, state);
125 if (!readonly_)
126 writer_.ScheduleWrite(this);
129 bool TransportSecurityPersister::SerializeData(std::string* output) {
130 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
132 base::DictionaryValue toplevel;
133 base::Time now = base::Time::Now();
134 TransportSecurityState::Iterator state(*transport_security_state_);
135 for (; state.HasNext(); state.Advance()) {
136 const std::string& hostname = state.hostname();
137 const TransportSecurityState::DomainState& domain_state =
138 state.domain_state();
140 base::DictionaryValue* serialized = new base::DictionaryValue;
141 serialized->SetBoolean(kStsIncludeSubdomains,
142 domain_state.sts.include_subdomains);
143 serialized->SetBoolean(kPkpIncludeSubdomains,
144 domain_state.pkp.include_subdomains);
145 serialized->SetDouble(kStsObserved,
146 domain_state.sts.last_observed.ToDoubleT());
147 serialized->SetDouble(kPkpObserved,
148 domain_state.pkp.last_observed.ToDoubleT());
149 serialized->SetDouble(kExpiry, domain_state.sts.expiry.ToDoubleT());
150 serialized->SetDouble(kDynamicSPKIHashesExpiry,
151 domain_state.pkp.expiry.ToDoubleT());
153 switch (domain_state.sts.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;
166 if (now < domain_state.pkp.expiry) {
167 serialized->Set(kDynamicSPKIHashes,
168 SPKIHashesToListValue(domain_state.pkp.spki_hashes));
171 toplevel.Set(HashedDomainToExternalString(hostname), serialized);
174 base::JSONWriter::WriteWithOptions(&toplevel,
175 base::JSONWriter::OPTIONS_PRETTY_PRINT,
176 output);
177 return true;
180 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
181 bool* dirty) {
182 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
184 transport_security_state_->ClearDynamicData();
185 return Deserialize(serialized, dirty, transport_security_state_);
188 // static
189 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
190 bool* dirty,
191 TransportSecurityState* state) {
192 scoped_ptr<base::Value> value(base::JSONReader::Read(serialized));
193 base::DictionaryValue* dict_value = NULL;
194 if (!value.get() || !value->GetAsDictionary(&dict_value))
195 return false;
197 const base::Time current_time(base::Time::Now());
198 bool dirtied = false;
200 for (base::DictionaryValue::Iterator i(*dict_value);
201 !i.IsAtEnd(); i.Advance()) {
202 const base::DictionaryValue* parsed = NULL;
203 if (!i.value().GetAsDictionary(&parsed)) {
204 LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
205 continue;
208 TransportSecurityState::DomainState domain_state;
210 // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
211 // kPkpIncludeSubdomains. Parse at least one of these properties,
212 // preferably the new ones.
213 bool include_subdomains = false;
214 bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
215 &include_subdomains);
216 domain_state.sts.include_subdomains = include_subdomains;
217 domain_state.pkp.include_subdomains = include_subdomains;
218 if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
219 domain_state.sts.include_subdomains = include_subdomains;
220 parsed_include_subdomains = true;
222 if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
223 domain_state.pkp.include_subdomains = include_subdomains;
224 parsed_include_subdomains = true;
227 std::string mode_string;
228 double expiry = 0;
229 if (!parsed_include_subdomains ||
230 !parsed->GetString(kMode, &mode_string) ||
231 !parsed->GetDouble(kExpiry, &expiry)) {
232 LOG(WARNING) << "Could not parse some elements of entry " << i.key()
233 << "; skipping entry";
234 continue;
237 // Don't fail if this key is not present.
238 double dynamic_spki_hashes_expiry = 0;
239 parsed->GetDouble(kDynamicSPKIHashesExpiry,
240 &dynamic_spki_hashes_expiry);
242 const base::ListValue* pins_list = NULL;
243 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) {
244 SPKIHashesFromListValue(*pins_list, &domain_state.pkp.spki_hashes);
247 if (mode_string == kForceHTTPS || mode_string == kStrict) {
248 domain_state.sts.upgrade_mode =
249 TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
250 } else if (mode_string == kDefault || mode_string == kPinningOnly) {
251 domain_state.sts.upgrade_mode =
252 TransportSecurityState::DomainState::MODE_DEFAULT;
253 } else {
254 LOG(WARNING) << "Unknown TransportSecurityState mode string "
255 << mode_string << " found for entry " << i.key()
256 << "; skipping entry";
257 continue;
260 domain_state.sts.expiry = base::Time::FromDoubleT(expiry);
261 domain_state.pkp.expiry =
262 base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
264 double sts_observed;
265 double pkp_observed;
266 if (parsed->GetDouble(kStsObserved, &sts_observed)) {
267 domain_state.sts.last_observed = base::Time::FromDoubleT(sts_observed);
268 } else if (parsed->GetDouble(kCreated, &sts_observed)) {
269 // kCreated is a legacy synonym for both kStsObserved and kPkpObserved.
270 domain_state.sts.last_observed = base::Time::FromDoubleT(sts_observed);
271 } else {
272 // We're migrating an old entry with no observation date. Make sure we
273 // write the new date back in a reasonable time frame.
274 dirtied = true;
275 domain_state.sts.last_observed = base::Time::Now();
277 if (parsed->GetDouble(kPkpObserved, &pkp_observed)) {
278 domain_state.pkp.last_observed = base::Time::FromDoubleT(pkp_observed);
279 } else if (parsed->GetDouble(kCreated, &pkp_observed)) {
280 domain_state.pkp.last_observed = base::Time::FromDoubleT(pkp_observed);
281 } else {
282 dirtied = true;
283 domain_state.pkp.last_observed = base::Time::Now();
286 if (domain_state.sts.expiry <= current_time &&
287 domain_state.pkp.expiry <= current_time) {
288 // Make sure we dirty the state if we drop an entry.
289 dirtied = true;
290 continue;
293 std::string hashed = ExternalStringToHashedDomain(i.key());
294 if (hashed.empty()) {
295 dirtied = true;
296 continue;
299 state->AddOrUpdateEnabledHosts(hashed, domain_state);
302 *dirty = dirtied;
303 return true;
306 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
307 DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
309 if (state.empty())
310 return;
312 bool dirty = false;
313 if (!LoadEntries(state, &dirty)) {
314 LOG(ERROR) << "Failed to deserialize state: " << state;
315 return;
317 if (dirty)
318 StateIsDirty(transport_security_state_);
321 } // namespace net