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