Home | History | Annotate | Download | only in base
      1 // Copyright (c) 2011 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/base/transport_security_state.h"
      6 
      7 #include "base/base64.h"
      8 #include "base/command_line.h"
      9 #include "base/json/json_reader.h"
     10 #include "base/json/json_writer.h"
     11 #include "base/logging.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/sha1.h"
     14 #include "base/string_number_conversions.h"
     15 #include "base/string_split.h"
     16 #include "base/string_tokenizer.h"
     17 #include "base/string_util.h"
     18 #include "base/utf_string_conversions.h"
     19 #include "base/values.h"
     20 #include "crypto/sha2.h"
     21 #include "googleurl/src/gurl.h"
     22 #include "net/base/dns_util.h"
     23 #include "net/base/net_switches.h"
     24 
     25 namespace net {
     26 
     27 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365;  // 1 year
     28 
     29 TransportSecurityState::TransportSecurityState()
     30     : delegate_(NULL) {
     31 }
     32 
     33 static std::string HashHost(const std::string& canonicalized_host) {
     34   char hashed[crypto::SHA256_LENGTH];
     35   crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed));
     36   return std::string(hashed, sizeof(hashed));
     37 }
     38 
     39 void TransportSecurityState::EnableHost(const std::string& host,
     40                                         const DomainState& state) {
     41   const std::string canonicalized_host = CanonicalizeHost(host);
     42   if (canonicalized_host.empty())
     43     return;
     44 
     45   // TODO(cevans) -- we likely want to permit a host to override a built-in,
     46   // for at least the case where the override is stricter (i.e. includes
     47   // subdomains, or includes certificate pinning).
     48   DomainState temp;
     49   if (IsPreloadedSTS(canonicalized_host, true, &temp))
     50     return;
     51 
     52   // Use the original creation date if we already have this host.
     53   DomainState state_copy(state);
     54   DomainState existing_state;
     55   if (IsEnabledForHost(&existing_state, host, true))
     56     state_copy.created = existing_state.created;
     57 
     58   // We don't store these values.
     59   state_copy.preloaded = false;
     60   state_copy.domain.clear();
     61 
     62   enabled_hosts_[HashHost(canonicalized_host)] = state_copy;
     63   DirtyNotify();
     64 }
     65 
     66 bool TransportSecurityState::DeleteHost(const std::string& host) {
     67   const std::string canonicalized_host = CanonicalizeHost(host);
     68   if (canonicalized_host.empty())
     69     return false;
     70 
     71   std::map<std::string, DomainState>::iterator i = enabled_hosts_.find(
     72       HashHost(canonicalized_host));
     73   if (i != enabled_hosts_.end()) {
     74     enabled_hosts_.erase(i);
     75     DirtyNotify();
     76     return true;
     77   }
     78   return false;
     79 }
     80 
     81 // IncludeNUL converts a char* to a std::string and includes the terminating
     82 // NUL in the result.
     83 static std::string IncludeNUL(const char* in) {
     84   return std::string(in, strlen(in) + 1);
     85 }
     86 
     87 bool TransportSecurityState::IsEnabledForHost(DomainState* result,
     88                                               const std::string& host,
     89                                               bool sni_available) {
     90   const std::string canonicalized_host = CanonicalizeHost(host);
     91   if (canonicalized_host.empty())
     92     return false;
     93 
     94   if (IsPreloadedSTS(canonicalized_host, sni_available, result))
     95     return result->mode != DomainState::MODE_NONE;
     96 
     97   *result = DomainState();
     98 
     99   base::Time current_time(base::Time::Now());
    100 
    101   for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
    102     std::string hashed_domain(HashHost(IncludeNUL(&canonicalized_host[i])));
    103 
    104     std::map<std::string, DomainState>::iterator j =
    105         enabled_hosts_.find(hashed_domain);
    106     if (j == enabled_hosts_.end())
    107       continue;
    108 
    109     if (current_time > j->second.expiry) {
    110       enabled_hosts_.erase(j);
    111       DirtyNotify();
    112       continue;
    113     }
    114 
    115     *result = j->second;
    116     result->domain = DNSDomainToString(
    117         canonicalized_host.substr(i, canonicalized_host.size() - i));
    118 
    119     // If we matched the domain exactly, it doesn't matter what the value of
    120     // include_subdomains is.
    121     if (i == 0)
    122       return true;
    123 
    124     return j->second.include_subdomains;
    125   }
    126 
    127   return false;
    128 }
    129 
    130 void TransportSecurityState::DeleteSince(const base::Time& time) {
    131   bool dirtied = false;
    132 
    133   std::map<std::string, DomainState>::iterator i = enabled_hosts_.begin();
    134   while (i != enabled_hosts_.end()) {
    135     if (i->second.created >= time) {
    136       dirtied = true;
    137       enabled_hosts_.erase(i++);
    138     } else {
    139       i++;
    140     }
    141   }
    142 
    143   if (dirtied)
    144     DirtyNotify();
    145 }
    146 
    147 // MaxAgeToInt converts a string representation of a number of seconds into a
    148 // int. We use strtol in order to handle overflow correctly. The string may
    149 // contain an arbitary number which we should truncate correctly rather than
    150 // throwing a parse failure.
    151 static bool MaxAgeToInt(std::string::const_iterator begin,
    152                         std::string::const_iterator end,
    153                         int* result) {
    154   const std::string s(begin, end);
    155   char* endptr;
    156   long int i = strtol(s.data(), &endptr, 10 /* base */);
    157   if (*endptr || i < 0)
    158     return false;
    159   if (i > TransportSecurityState::kMaxHSTSAgeSecs)
    160     i = TransportSecurityState::kMaxHSTSAgeSecs;
    161   *result = i;
    162   return true;
    163 }
    164 
    165 // "Strict-Transport-Security" ":"
    166 //     "max-age" "=" delta-seconds [ ";" "includeSubDomains" ]
    167 bool TransportSecurityState::ParseHeader(const std::string& value,
    168                                          int* max_age,
    169                                          bool* include_subdomains) {
    170   DCHECK(max_age);
    171   DCHECK(include_subdomains);
    172 
    173   int max_age_candidate = 0;
    174 
    175   enum ParserState {
    176     START,
    177     AFTER_MAX_AGE_LABEL,
    178     AFTER_MAX_AGE_EQUALS,
    179     AFTER_MAX_AGE,
    180     AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER,
    181     AFTER_INCLUDE_SUBDOMAINS,
    182   } state = START;
    183 
    184   StringTokenizer tokenizer(value, " \t=;");
    185   tokenizer.set_options(StringTokenizer::RETURN_DELIMS);
    186   while (tokenizer.GetNext()) {
    187     DCHECK(!tokenizer.token_is_delim() || tokenizer.token().length() == 1);
    188     switch (state) {
    189       case START:
    190         if (IsAsciiWhitespace(*tokenizer.token_begin()))
    191           continue;
    192         if (!LowerCaseEqualsASCII(tokenizer.token(), "max-age"))
    193           return false;
    194         state = AFTER_MAX_AGE_LABEL;
    195         break;
    196 
    197       case AFTER_MAX_AGE_LABEL:
    198         if (IsAsciiWhitespace(*tokenizer.token_begin()))
    199           continue;
    200         if (*tokenizer.token_begin() != '=')
    201           return false;
    202         DCHECK(tokenizer.token().length() ==  1);
    203         state = AFTER_MAX_AGE_EQUALS;
    204         break;
    205 
    206       case AFTER_MAX_AGE_EQUALS:
    207         if (IsAsciiWhitespace(*tokenizer.token_begin()))
    208           continue;
    209         if (!MaxAgeToInt(tokenizer.token_begin(),
    210                          tokenizer.token_end(),
    211                          &max_age_candidate))
    212           return false;
    213         state = AFTER_MAX_AGE;
    214         break;
    215 
    216       case AFTER_MAX_AGE:
    217         if (IsAsciiWhitespace(*tokenizer.token_begin()))
    218           continue;
    219         if (*tokenizer.token_begin() != ';')
    220           return false;
    221         state = AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER;
    222         break;
    223 
    224       case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER:
    225         if (IsAsciiWhitespace(*tokenizer.token_begin()))
    226           continue;
    227         if (!LowerCaseEqualsASCII(tokenizer.token(), "includesubdomains"))
    228           return false;
    229         state = AFTER_INCLUDE_SUBDOMAINS;
    230         break;
    231 
    232       case AFTER_INCLUDE_SUBDOMAINS:
    233         if (!IsAsciiWhitespace(*tokenizer.token_begin()))
    234           return false;
    235         break;
    236 
    237       default:
    238         NOTREACHED();
    239     }
    240   }
    241 
    242   // We've consumed all the input.  Let's see what state we ended up in.
    243   switch (state) {
    244     case START:
    245     case AFTER_MAX_AGE_LABEL:
    246     case AFTER_MAX_AGE_EQUALS:
    247       return false;
    248     case AFTER_MAX_AGE:
    249       *max_age = max_age_candidate;
    250       *include_subdomains = false;
    251       return true;
    252     case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER:
    253       return false;
    254     case AFTER_INCLUDE_SUBDOMAINS:
    255       *max_age = max_age_candidate;
    256       *include_subdomains = true;
    257       return true;
    258     default:
    259       NOTREACHED();
    260       return false;
    261   }
    262 }
    263 
    264 void TransportSecurityState::SetDelegate(
    265     TransportSecurityState::Delegate* delegate) {
    266   delegate_ = delegate;
    267 }
    268 
    269 // This function converts the binary hashes, which we store in
    270 // |enabled_hosts_|, to a base64 string which we can include in a JSON file.
    271 static std::string HashedDomainToExternalString(const std::string& hashed) {
    272   std::string out;
    273   CHECK(base::Base64Encode(hashed, &out));
    274   return out;
    275 }
    276 
    277 // This inverts |HashedDomainToExternalString|, above. It turns an external
    278 // string (from a JSON file) into an internal (binary) string.
    279 static std::string ExternalStringToHashedDomain(const std::string& external) {
    280   std::string out;
    281   if (!base::Base64Decode(external, &out) ||
    282       out.size() != crypto::SHA256_LENGTH) {
    283     return std::string();
    284   }
    285 
    286   return out;
    287 }
    288 
    289 bool TransportSecurityState::Serialise(std::string* output) {
    290   DictionaryValue toplevel;
    291   for (std::map<std::string, DomainState>::const_iterator
    292        i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) {
    293     DictionaryValue* state = new DictionaryValue;
    294     state->SetBoolean("include_subdomains", i->second.include_subdomains);
    295     state->SetDouble("created", i->second.created.ToDoubleT());
    296     state->SetDouble("expiry", i->second.expiry.ToDoubleT());
    297 
    298     switch (i->second.mode) {
    299       case DomainState::MODE_STRICT:
    300         state->SetString("mode", "strict");
    301         break;
    302       case DomainState::MODE_OPPORTUNISTIC:
    303         state->SetString("mode", "opportunistic");
    304         break;
    305       case DomainState::MODE_SPDY_ONLY:
    306         state->SetString("mode", "spdy-only");
    307         break;
    308       default:
    309         NOTREACHED() << "DomainState with unknown mode";
    310         delete state;
    311         continue;
    312     }
    313 
    314     ListValue* pins = new ListValue;
    315     for (std::vector<SHA1Fingerprint>::const_iterator
    316          j = i->second.public_key_hashes.begin();
    317          j != i->second.public_key_hashes.end(); ++j) {
    318       std::string hash_str(reinterpret_cast<const char*>(j->data),
    319                            sizeof(j->data));
    320       std::string b64;
    321       base::Base64Encode(hash_str, &b64);
    322       pins->Append(new StringValue("sha1/" + b64));
    323     }
    324     state->Set("public_key_hashes", pins);
    325 
    326     toplevel.Set(HashedDomainToExternalString(i->first), state);
    327   }
    328 
    329   base::JSONWriter::Write(&toplevel, true /* pretty print */, output);
    330   return true;
    331 }
    332 
    333 bool TransportSecurityState::LoadEntries(const std::string& input,
    334                                          bool* dirty) {
    335   enabled_hosts_.clear();
    336   return Deserialise(input, dirty, &enabled_hosts_);
    337 }
    338 
    339 // static
    340 bool TransportSecurityState::Deserialise(
    341     const std::string& input,
    342     bool* dirty,
    343     std::map<std::string, DomainState>* out) {
    344   scoped_ptr<Value> value(
    345       base::JSONReader::Read(input, false /* do not allow trailing commas */));
    346   if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
    347     return false;
    348 
    349   DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get());
    350   const base::Time current_time(base::Time::Now());
    351   bool dirtied = false;
    352 
    353   for (DictionaryValue::key_iterator i = dict_value->begin_keys();
    354        i != dict_value->end_keys(); ++i) {
    355     DictionaryValue* state;
    356     if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &state))
    357       continue;
    358 
    359     bool include_subdomains;
    360     std::string mode_string;
    361     double created;
    362     double expiry;
    363 
    364     if (!state->GetBoolean("include_subdomains", &include_subdomains) ||
    365         !state->GetString("mode", &mode_string) ||
    366         !state->GetDouble("expiry", &expiry)) {
    367       continue;
    368     }
    369 
    370     ListValue* pins_list = NULL;
    371     std::vector<SHA1Fingerprint> public_key_hashes;
    372     if (state->GetList("public_key_hashes", &pins_list)) {
    373       size_t num_pins = pins_list->GetSize();
    374       for (size_t i = 0; i < num_pins; ++i) {
    375         std::string type_and_base64;
    376         std::string hash_str;
    377         SHA1Fingerprint hash;
    378         if (pins_list->GetString(i, &type_and_base64) &&
    379             type_and_base64.find("sha1/") == 0 &&
    380             base::Base64Decode(
    381                 type_and_base64.substr(5, type_and_base64.size() - 5),
    382                 &hash_str) &&
    383             hash_str.size() == base::SHA1_LENGTH) {
    384           memcpy(hash.data, hash_str.data(), sizeof(hash.data));
    385           public_key_hashes.push_back(hash);
    386         }
    387       }
    388     }
    389 
    390     DomainState::Mode mode;
    391     if (mode_string == "strict") {
    392       mode = DomainState::MODE_STRICT;
    393     } else if (mode_string == "opportunistic") {
    394       mode = DomainState::MODE_OPPORTUNISTIC;
    395     } else if (mode_string == "spdy-only") {
    396       mode = DomainState::MODE_SPDY_ONLY;
    397     } else if (mode_string == "none") {
    398       mode = DomainState::MODE_NONE;
    399     } else {
    400       LOG(WARNING) << "Unknown TransportSecurityState mode string found: "
    401                    << mode_string;
    402       continue;
    403     }
    404 
    405     base::Time expiry_time = base::Time::FromDoubleT(expiry);
    406     base::Time created_time;
    407     if (state->GetDouble("created", &created)) {
    408       created_time = base::Time::FromDoubleT(created);
    409     } else {
    410       // We're migrating an old entry with no creation date. Make sure we
    411       // write the new date back in a reasonable time frame.
    412       dirtied = true;
    413       created_time = base::Time::Now();
    414     }
    415 
    416     if (expiry_time <= current_time) {
    417       // Make sure we dirty the state if we drop an entry.
    418       dirtied = true;
    419       continue;
    420     }
    421 
    422     std::string hashed = ExternalStringToHashedDomain(*i);
    423     if (hashed.empty()) {
    424       dirtied = true;
    425       continue;
    426     }
    427 
    428     DomainState new_state;
    429     new_state.mode = mode;
    430     new_state.created = created_time;
    431     new_state.expiry = expiry_time;
    432     new_state.include_subdomains = include_subdomains;
    433     new_state.public_key_hashes = public_key_hashes;
    434     (*out)[hashed] = new_state;
    435   }
    436 
    437   *dirty = dirtied;
    438   return true;
    439 }
    440 
    441 TransportSecurityState::~TransportSecurityState() {
    442 }
    443 
    444 void TransportSecurityState::DirtyNotify() {
    445   if (delegate_)
    446     delegate_->StateIsDirty(this);
    447 }
    448 
    449 // static
    450 std::string TransportSecurityState::CanonicalizeHost(const std::string& host) {
    451   // We cannot perform the operations as detailed in the spec here as |host|
    452   // has already undergone IDN processing before it reached us. Thus, we check
    453   // that there are no invalid characters in the host and lowercase the result.
    454 
    455   std::string new_host;
    456   if (!DNSDomainFromDot(host, &new_host)) {
    457     // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole
    458     // name is >255 bytes. However, search terms can have those properties.
    459     return std::string();
    460   }
    461 
    462   for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
    463     const unsigned label_length = static_cast<unsigned>(new_host[i]);
    464     if (!label_length)
    465       break;
    466 
    467     for (size_t j = 0; j < label_length; ++j) {
    468       // RFC 3490, 4.1, step 3
    469       if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j]))
    470         return std::string();
    471 
    472       new_host[i + 1 + j] = tolower(new_host[i + 1 + j]);
    473     }
    474 
    475     // step 3(b)
    476     if (new_host[i + 1] == '-' ||
    477         new_host[i + label_length] == '-') {
    478       return std::string();
    479     }
    480   }
    481 
    482   return new_host;
    483 }
    484 
    485 // IsPreloadedSTS returns true if the canonicalized hostname should always be
    486 // considered to have STS enabled.
    487 // static
    488 bool TransportSecurityState::IsPreloadedSTS(
    489     const std::string& canonicalized_host,
    490     bool sni_available,
    491     DomainState* out) {
    492   out->preloaded = true;
    493   out->mode = DomainState::MODE_STRICT;
    494   out->created = base::Time::FromTimeT(0);
    495   out->expiry = out->created;
    496   out->include_subdomains = false;
    497 
    498   std::map<std::string, DomainState> hosts;
    499   std::string cmd_line_hsts
    500 #ifdef ANDROID
    501       ;
    502 #else
    503       = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    504           switches::kHstsHosts);
    505 #endif
    506   if (!cmd_line_hsts.empty()) {
    507     bool dirty;
    508     Deserialise(cmd_line_hsts, &dirty, &hosts);
    509   }
    510 
    511   // In the medium term this list is likely to just be hardcoded here. This,
    512   // slightly odd, form removes the need for additional relocations records.
    513   static const struct {
    514     uint8 length;
    515     bool include_subdomains;
    516     char dns_name[30];
    517   } kPreloadedSTS[] = {
    518     {16, false, "\003www\006paypal\003com"},
    519     {16, false, "\003www\006elanex\003biz"},
    520     {12, true,  "\006jottit\003com"},
    521     {19, true,  "\015sunshinepress\003org"},
    522     {21, false, "\003www\013noisebridge\003net"},
    523     {10, false, "\004neg9\003org"},
    524     {12, true, "\006riseup\003net"},
    525     {11, false, "\006factor\002cc"},
    526     {22, false, "\007members\010mayfirst\003org"},
    527     {22, false, "\007support\010mayfirst\003org"},
    528     {17, false, "\002id\010mayfirst\003org"},
    529     {20, false, "\005lists\010mayfirst\003org"},
    530     {19, true, "\015splendidbacon\003com"},
    531     {19, true, "\006health\006google\003com"},
    532     {21, true, "\010checkout\006google\003com"},
    533     {19, true, "\006chrome\006google\003com"},
    534     {26, false, "\006latest\006chrome\006google\003com"},
    535     {28, false, "\016aladdinschools\007appspot\003com"},
    536     {14, true, "\011ottospora\002nl"},
    537     {17, true, "\004docs\006google\003com"},
    538     {18, true, "\005sites\006google\003com"},
    539     {25, true, "\014spreadsheets\006google\003com"},
    540     {22, false, "\011appengine\006google\003com"},
    541     {25, false, "\003www\017paycheckrecords\003com"},
    542     {20, true, "\006market\007android\003com"},
    543     {14, false, "\010lastpass\003com"},
    544     {18, false, "\003www\010lastpass\003com"},
    545     {14, true, "\010keyerror\003com"},
    546     {22, true, "\011encrypted\006google\003com"},
    547     {13, false, "\010entropia\002de"},
    548     {17, false, "\003www\010entropia\002de"},
    549     {21, true, "\010accounts\006google\003com"},
    550 #if defined(OS_CHROMEOS)
    551     {17, true, "\004mail\006google\003com"},
    552     {13, false, "\007twitter\003com"},
    553     {17, false, "\003www\007twitter\003com"},
    554     {17, false, "\003api\007twitter\003com"},
    555     {17, false, "\003dev\007twitter\003com"},
    556     {22, false, "\010business\007twitter\003com"},
    557 #endif
    558   };
    559   static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS);
    560 
    561   static const struct {
    562     uint8 length;
    563     bool include_subdomains;
    564     char dns_name[30];
    565   } kPreloadedSNISTS[] = {
    566     {11, false, "\005gmail\003com"},
    567     {16, false, "\012googlemail\003com"},
    568     {15, false, "\003www\005gmail\003com"},
    569     {20, false, "\003www\012googlemail\003com"},
    570   };
    571   static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS);
    572 
    573   for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
    574     std::string host_sub_chunk(&canonicalized_host[i],
    575                                canonicalized_host.size() - i);
    576     out->domain = DNSDomainToString(host_sub_chunk);
    577     std::string hashed_host(HashHost(host_sub_chunk));
    578     if (hosts.find(hashed_host) != hosts.end()) {
    579       *out = hosts[hashed_host];
    580       out->domain = DNSDomainToString(host_sub_chunk);
    581       out->preloaded = true;
    582       return true;
    583     }
    584     for (size_t j = 0; j < kNumPreloadedSTS; j++) {
    585       if (kPreloadedSTS[j].length == canonicalized_host.size() - i &&
    586           memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i],
    587                  kPreloadedSTS[j].length) == 0) {
    588         if (!kPreloadedSTS[j].include_subdomains && i != 0)
    589           return false;
    590         out->include_subdomains = kPreloadedSTS[j].include_subdomains;
    591         return true;
    592       }
    593     }
    594     if (sni_available) {
    595       for (size_t j = 0; j < kNumPreloadedSNISTS; j++) {
    596         if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i &&
    597             memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i],
    598                    kPreloadedSNISTS[j].length) == 0) {
    599           if (!kPreloadedSNISTS[j].include_subdomains && i != 0)
    600             return false;
    601           out->include_subdomains = kPreloadedSNISTS[j].include_subdomains;
    602           return true;
    603         }
    604       }
    605     }
    606   }
    607 
    608   return false;
    609 }
    610 
    611 static std::string HashesToBase64String(
    612     const std::vector<net::SHA1Fingerprint>& hashes) {
    613   std::vector<std::string> hashes_strs;
    614   for (std::vector<net::SHA1Fingerprint>::const_iterator
    615        i = hashes.begin(); i != hashes.end(); i++) {
    616     std::string s;
    617     const std::string hash_str(reinterpret_cast<const char*>(i->data),
    618                                sizeof(i->data));
    619     base::Base64Encode(hash_str, &s);
    620     hashes_strs.push_back(s);
    621   }
    622 
    623   return JoinString(hashes_strs, ',');
    624 }
    625 
    626 TransportSecurityState::DomainState::DomainState()
    627     : mode(MODE_STRICT),
    628       created(base::Time::Now()),
    629       include_subdomains(false),
    630       preloaded(false) {
    631 }
    632 
    633 TransportSecurityState::DomainState::~DomainState() {
    634 }
    635 
    636 bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted(
    637     const std::vector<net::SHA1Fingerprint>& hashes) {
    638   if (public_key_hashes.empty())
    639     return true;
    640 
    641   for (std::vector<net::SHA1Fingerprint>::const_iterator
    642        i = hashes.begin(); i != hashes.end(); ++i) {
    643     for (std::vector<net::SHA1Fingerprint>::const_iterator
    644          j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) {
    645       if (i->Equals(*j))
    646         return true;
    647     }
    648   }
    649 
    650   LOG(ERROR) << "Rejecting public key chain for domain " << domain
    651              << ". Validated chain: " << HashesToBase64String(hashes)
    652              << ", expected: " << HashesToBase64String(public_key_hashes);
    653 
    654   return false;
    655 }
    656 
    657 }  // namespace
    658