- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / net / transport_security_persister.cc
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 "chrome/browser/net/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/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"
21
22 using content::BrowserThread;
23 using net::HashValue;
24 using net::HashValueTag;
25 using net::HashValueVector;
26 using net::TransportSecurityState;
27
28 namespace {
29
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()));
34   return pins;
35 }
36
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);
45     }
46   }
47 }
48
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) {
52   std::string out;
53   base::Base64Encode(hashed, &out);
54   return out;
55 }
56
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) {
60   std::string out;
61   if (!base::Base64Decode(external, &out) ||
62       out.size() != crypto::kSHA256Length) {
63     return std::string();
64   }
65
66   return out;
67 }
68
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";
83
84 }  // namespace
85
86 class TransportSecurityPersister::Loader {
87  public:
88   Loader(const base::WeakPtr<TransportSecurityPersister>& persister,
89          const base::FilePath& path)
90       : persister_(persister),
91         path_(path),
92         state_valid_(false) {
93   }
94
95   void Load() {
96     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
97     state_valid_ = base::ReadFileToString(path_, &state_);
98   }
99
100   void CompleteLoad() {
101     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
102
103     // Make sure we're deleted.
104     scoped_ptr<Loader> deleter(this);
105
106     if (!persister_.get() || !state_valid_)
107       return;
108     persister_->CompleteLoad(state_);
109   }
110
111  private:
112   base::WeakPtr<TransportSecurityPersister> persister_;
113
114   base::FilePath path_;
115
116   std::string state_;
117   bool state_valid_;
118
119   DISALLOW_COPY_AND_ASSIGN(Loader);
120 };
121
122 TransportSecurityPersister::TransportSecurityPersister(
123     TransportSecurityState* state,
124     const base::FilePath& profile_path,
125     bool readonly)
126     : transport_security_state_(state),
127       writer_(profile_path.AppendASCII("TransportSecurity"),
128               BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
129                   .get()),
130       readonly_(readonly),
131       weak_ptr_factory_(this) {
132   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
133
134   transport_security_state_->SetDelegate(this);
135
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)));
141 }
142
143 TransportSecurityPersister::~TransportSecurityPersister() {
144   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
145
146   if (writer_.HasPendingWrite())
147     writer_.DoScheduledWrite();
148
149   transport_security_state_->SetDelegate(NULL);
150 }
151
152 void TransportSecurityPersister::StateIsDirty(
153     TransportSecurityState* state) {
154   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
155   DCHECK_EQ(transport_security_state_, state);
156
157   if (!readonly_)
158     writer_.ScheduleWrite(this);
159 }
160
161 bool TransportSecurityPersister::SerializeData(std::string* output) {
162   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
163
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();
171
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());
181
182     switch (domain_state.upgrade_mode) {
183       case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
184         serialized->SetString(kMode, kForceHTTPS);
185         break;
186       case TransportSecurityState::DomainState::MODE_DEFAULT:
187         serialized->SetString(kMode, kDefault);
188         break;
189       default:
190         NOTREACHED() << "DomainState with unknown mode";
191         delete serialized;
192         continue;
193     }
194
195     serialized->Set(kStaticSPKIHashes,
196                     SPKIHashesToListValue(domain_state.static_spki_hashes));
197
198     if (now < domain_state.dynamic_spki_hashes_expiry) {
199       serialized->Set(kDynamicSPKIHashes,
200                       SPKIHashesToListValue(domain_state.dynamic_spki_hashes));
201     }
202
203     toplevel.Set(HashedDomainToExternalString(hostname), serialized);
204   }
205
206   base::JSONWriter::WriteWithOptions(&toplevel,
207                                      base::JSONWriter::OPTIONS_PRETTY_PRINT,
208                                      output);
209   return true;
210 }
211
212 bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
213                                              bool* dirty) {
214   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
215
216   transport_security_state_->ClearDynamicData();
217   return Deserialize(serialized, dirty, transport_security_state_);
218 }
219
220 // static
221 bool TransportSecurityPersister::Deserialize(const std::string& serialized,
222                                              bool* dirty,
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))
227     return false;
228
229   const base::Time current_time(base::Time::Now());
230   bool dirtied = false;
231
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";
236       continue;
237     }
238
239     std::string mode_string;
240     double created;
241     double expiry;
242     double dynamic_spki_hashes_expiry = 0.0;
243     TransportSecurityState::DomainState domain_state;
244
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;
256     }
257     if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
258       domain_state.pkp_include_subdomains = include_subdomains;
259       parsed_include_subdomains = true;
260     }
261
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";
267       continue;
268     }
269
270     // Don't fail if this key is not present.
271     parsed->GetDouble(kDynamicSPKIHashesExpiry,
272                       &dynamic_spki_hashes_expiry);
273
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);
280
281     if (parsed->GetList(kDynamicSPKIHashes, &pins_list))
282       SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes);
283
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;
290     } else {
291       LOG(WARNING) << "Unknown TransportSecurityState mode string "
292                    << mode_string << " found for entry " << i.key()
293                    << "; skipping entry";
294       continue;
295     }
296
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);
302     } else {
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.
305       dirtied = true;
306       domain_state.created = base::Time::Now();
307     }
308
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.
312       dirtied = true;
313       continue;
314     }
315
316     std::string hashed = ExternalStringToHashedDomain(i.key());
317     if (hashed.empty()) {
318       dirtied = true;
319       continue;
320     }
321
322     state->AddOrUpdateEnabledHosts(hashed, domain_state);
323   }
324
325   *dirty = dirtied;
326   return true;
327 }
328
329 void TransportSecurityPersister::CompleteLoad(const std::string& state) {
330   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
331
332   bool dirty = false;
333   if (!LoadEntries(state, &dirty)) {
334     LOG(ERROR) << "Failed to deserialize state: " << state;
335     return;
336   }
337   if (dirty)
338     StateIsDirty(transport_security_state_);
339 }