Home | History | Annotate | Download | only in http
      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 
     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 
     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.
     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.
     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 
     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 
     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 
    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 
    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 
    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 
    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
    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 
    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