Home | History | Annotate | Download | only in net
      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_ = file_util::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 }
    340