text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "net/http/transport_security_state.h"
  
  #if defined(USE_OPENSSL)
  #include <openssl/ecdsa.h>
  #include <openssl/ssl.h>
  #else  // !defined(USE_OPENSSL)
  #include <cryptohi.h>
  #include <hasht.h>
  #include <keyhi.h>
  #include <nspr.h>
  #include <pk11pub.h>
  #endif
  
  #include <algorithm>
  
  #include "base/base64.h"
  #include "base/build_time.h"
  #include "base/logging.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/metrics/histogram.h"
  #include "base/sha1.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/time/time.h"
  #include "base/values.h"
  #include "crypto/sha2.h"
  #include "net/base/dns_util.h"
  #include "net/cert/x509_cert_types.h"
  #include "net/cert/x509_certificate.h"
  #include "net/http/http_security_headers.h"
  #include "net/ssl/ssl_info.h"
  #include "url/gurl.h"
  
  #if defined(USE_OPENSSL)
  #include "crypto/openssl_util.h"
  #endif
  
  namespace net {
  
  namespace {
  
  std::string HashesToBase64String(const HashValueVector& hashes) {
    std::string str;
    for (size_t i = 0; i != hashes.size(); ++i) {
      if (i != 0)
        str += ",";
      str += hashes[i].ToString();
    }
    return str;
  }
  
  std::string HashHost(const std::string& canonicalized_host) {
    char hashed[crypto::kSHA256Length];
    crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed));
    return std::string(hashed, sizeof(hashed));
  }
  
  // Returns true if the intersection of |a| and |b| is not empty. If either
  // |a| or |b| is empty, returns false.
  bool HashesIntersect(const HashValueVector& a,
                       const HashValueVector& b) {
    for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) {
      HashValueVector::const_iterator j =
          std::find_if(b.begin(), b.end(), HashValuesEqual(*i));
      if (j != b.end())
        return true;
    }
    return false;
  }
  
  bool AddHash(const char* sha1_hash,
               HashValueVector* out) {
    HashValue hash(HASH_VALUE_SHA1);
    memcpy(hash.data(), sha1_hash, hash.size());
    out->push_back(hash);
    return true;
  }
  
  }  // namespace
  
  TransportSecurityState::TransportSecurityState()
    : delegate_(NULL) {
    DCHECK(CalledOnValidThread());
  }
  
  TransportSecurityState::Iterator::Iterator(const TransportSecurityState& state)
      : iterator_(state.enabled_hosts_.begin()),
        end_(state.enabled_hosts_.end()) {
  }
  
  TransportSecurityState::Iterator::~Iterator() {}
  
  void TransportSecurityState::SetDelegate(
      TransportSecurityState::Delegate* delegate) {
    DCHECK(CalledOnValidThread());
    delegate_ = delegate;
  }
  
  void TransportSecurityState::EnableHost(const std::string& host,
                                          const DomainState& state) {
    DCHECK(CalledOnValidThread());
  
    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
      return;
  
    DomainState state_copy(state);
    // No need to store this value since it is redundant. (|canonicalized_host|
    // is the map key.)
    state_copy.domain.clear();
  
    enabled_hosts_[HashHost(canonicalized_host)] = state_copy;
    DirtyNotify();
  }
  
  bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) {
    DCHECK(CalledOnValidThread());
  
    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
      return false;
  
    DomainStateMap::iterator i = enabled_hosts_.find(
        HashHost(canonicalized_host));
    if (i != enabled_hosts_.end()) {
      enabled_hosts_.erase(i);
      DirtyNotify();
      return true;
    }
    return false;
  }
  
  bool TransportSecurityState::GetDomainState(const std::string& host,
                                              bool sni_enabled,
                                              DomainState* result) {
    DCHECK(CalledOnValidThread());
  
    DomainState state;
    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
      return false;
  
    bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled,
                                            &state);
    std::string canonicalized_preload = CanonicalizeHost(state.domain);
    GetDynamicDomainState(host, &state);
  
    base::Time current_time(base::Time::Now());
  
    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
      std::string host_sub_chunk(&canonicalized_host[i],
                                 canonicalized_host.size() - i);
      // Exact match of a preload always wins.
      if (has_preload && host_sub_chunk == canonicalized_preload) {
        *result = state;
        return true;
      }
  
      DomainStateMap::iterator j =
          enabled_hosts_.find(HashHost(host_sub_chunk));
      if (j == enabled_hosts_.end())
        continue;
  
      if (current_time > j->second.upgrade_expiry &&
          current_time > j->second.dynamic_spki_hashes_expiry) {
        enabled_hosts_.erase(j);
        DirtyNotify();
        continue;
      }
  
      state = j->second;
      state.domain = DNSDomainToString(host_sub_chunk);
  
      // Succeed if we matched the domain exactly or if subdomain matches are
      // allowed.
      if (i == 0 || j->second.sts_include_subdomains ||
          j->second.pkp_include_subdomains) {
        *result = state;
        return true;
      }
  
      return false;
    }
  
    return false;
  }
  
  void TransportSecurityState::ClearDynamicData() {
    DCHECK(CalledOnValidThread());
    enabled_hosts_.clear();
  }
  
  void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) {
    DCHECK(CalledOnValidThread());
  
    bool dirtied = false;
  
    DomainStateMap::iterator i = enabled_hosts_.begin();
    while (i != enabled_hosts_.end()) {
      if (i->second.created >= time) {
        dirtied = true;
        enabled_hosts_.erase(i++);
      } else {
        i++;
      }
    }
  
    if (dirtied)
      DirtyNotify();
  }
  
  TransportSecurityState::~TransportSecurityState() {
    DCHECK(CalledOnValidThread());
  }
  
  void TransportSecurityState::DirtyNotify() {
    DCHECK(CalledOnValidThread());
  
    if (delegate_)
      delegate_->StateIsDirty(this);
  }
  
  // static
  std::string TransportSecurityState::CanonicalizeHost(const std::string& host) {
    // We cannot perform the operations as detailed in the spec here as |host|
    // has already undergone IDN processing before it reached us. Thus, we check
    // that there are no invalid characters in the host and lowercase the result.
  
    std::string new_host;
    if (!DNSDomainFromDot(host, &new_host)) {
      // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole
      // name is >255 bytes. However, search terms can have those properties.
      return std::string();
    }
  
    for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
      const unsigned label_length = static_cast<unsigned>(new_host[i]);
      if (!label_length)
        break;
  
      for (size_t j = 0; j < label_length; ++j) {
        // RFC 3490, 4.1, step 3
        if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j]))
          return std::string();
  
        new_host[i + 1 + j] = tolower(new_host[i + 1 + j]);
      }
  
      // step 3(b)
      if (new_host[i + 1] == '-' ||
          new_host[i + label_length] == '-') {
        return std::string();
      }
    }
  
    return new_host;
  }
  
  // |ReportUMAOnPinFailure| uses these to report which domain was associated
  // with the public key pinning failure.
  //
  // DO NOT CHANGE THE ORDERING OF THESE NAMES OR REMOVE ANY OF THEM. Add new
  // domains at the END of the listing (but before DOMAIN_NUM_EVENTS).
  enum SecondLevelDomainName {
    DOMAIN_NOT_PINNED,
  
    DOMAIN_GOOGLE_COM,
    DOMAIN_ANDROID_COM,
    DOMAIN_GOOGLE_ANALYTICS_COM,
    DOMAIN_GOOGLEPLEX_COM,
    DOMAIN_YTIMG_COM,
    DOMAIN_GOOGLEUSERCONTENT_COM,
    DOMAIN_YOUTUBE_COM,
    DOMAIN_GOOGLEAPIS_COM,
    DOMAIN_GOOGLEADSERVICES_COM,
    DOMAIN_GOOGLECODE_COM,
    DOMAIN_APPSPOT_COM,
    DOMAIN_GOOGLESYNDICATION_COM,
    DOMAIN_DOUBLECLICK_NET,
    DOMAIN_GSTATIC_COM,
    DOMAIN_GMAIL_COM,
    DOMAIN_GOOGLEMAIL_COM,
    DOMAIN_GOOGLEGROUPS_COM,
  
    DOMAIN_TORPROJECT_ORG,
  
    DOMAIN_TWITTER_COM,
    DOMAIN_TWIMG_COM,
  
    DOMAIN_AKAMAIHD_NET,
  
    DOMAIN_TOR2WEB_ORG,
  
    DOMAIN_YOUTU_BE,
    DOMAIN_GOOGLECOMMERCE_COM,
    DOMAIN_URCHIN_COM,
    DOMAIN_GOO_GL,
    DOMAIN_G_CO,
    DOMAIN_GOOGLE_AC,
    DOMAIN_GOOGLE_AD,
    DOMAIN_GOOGLE_AE,
    DOMAIN_GOOGLE_AF,
    DOMAIN_GOOGLE_AG,
    DOMAIN_GOOGLE_AM,
    DOMAIN_GOOGLE_AS,
    DOMAIN_GOOGLE_AT,
    DOMAIN_GOOGLE_AZ,
    DOMAIN_GOOGLE_BA,
    DOMAIN_GOOGLE_BE,
    DOMAIN_GOOGLE_BF,
    DOMAIN_GOOGLE_BG,
    DOMAIN_GOOGLE_BI,
    DOMAIN_GOOGLE_BJ,
    DOMAIN_GOOGLE_BS,
    DOMAIN_GOOGLE_BY,
    DOMAIN_GOOGLE_CA,
    DOMAIN_GOOGLE_CAT,
    DOMAIN_GOOGLE_CC,
    DOMAIN_GOOGLE_CD,
    DOMAIN_GOOGLE_CF,
    DOMAIN_GOOGLE_CG,
    DOMAIN_GOOGLE_CH,
    DOMAIN_GOOGLE_CI,
    DOMAIN_GOOGLE_CL,
    DOMAIN_GOOGLE_CM,
    DOMAIN_GOOGLE_CN,
    DOMAIN_CO_AO,
    DOMAIN_CO_BW,
    DOMAIN_CO_CK,
    DOMAIN_CO_CR,
    DOMAIN_CO_HU,
    DOMAIN_CO_ID,
    DOMAIN_CO_IL,
    DOMAIN_CO_IM,
    DOMAIN_CO_IN,
    DOMAIN_CO_JE,
    DOMAIN_CO_JP,
    DOMAIN_CO_KE,
    DOMAIN_CO_KR,
    DOMAIN_CO_LS,
    DOMAIN_CO_MA,
    DOMAIN_CO_MZ,
    DOMAIN_CO_NZ,
    DOMAIN_CO_TH,
    DOMAIN_CO_TZ,
    DOMAIN_CO_UG,
    DOMAIN_CO_UK,
    DOMAIN_CO_UZ,
    DOMAIN_CO_VE,
    DOMAIN_CO_VI,
    DOMAIN_CO_ZA,
    DOMAIN_CO_ZM,
    DOMAIN_CO_ZW,
    DOMAIN_COM_AF,
    DOMAIN_COM_AG,
    DOMAIN_COM_AI,
    DOMAIN_COM_AR,
    DOMAIN_COM_AU,
    DOMAIN_COM_BD,
    DOMAIN_COM_BH,
    DOMAIN_COM_BN,
    DOMAIN_COM_BO,
    DOMAIN_COM_BR,
    DOMAIN_COM_BY,
    DOMAIN_COM_BZ,
    DOMAIN_COM_CN,
    DOMAIN_COM_CO,
    DOMAIN_COM_CU,
    DOMAIN_COM_CY,
    DOMAIN_COM_DO,
    DOMAIN_COM_EC,
    DOMAIN_COM_EG,
    DOMAIN_COM_ET,
    DOMAIN_COM_FJ,
    DOMAIN_COM_GE,
    DOMAIN_COM_GH,
    DOMAIN_COM_GI,
    DOMAIN_COM_GR,
    DOMAIN_COM_GT,
    DOMAIN_COM_HK,
    DOMAIN_COM_IQ,
    DOMAIN_COM_JM,
    DOMAIN_COM_JO,
    DOMAIN_COM_KH,
    DOMAIN_COM_KW,
    DOMAIN_COM_LB,
    DOMAIN_COM_LY,
    DOMAIN_COM_MT,
    DOMAIN_COM_MX,
    DOMAIN_COM_MY,
    DOMAIN_COM_NA,
    DOMAIN_COM_NF,
    DOMAIN_COM_NG,
    DOMAIN_COM_NI,
    DOMAIN_COM_NP,
    DOMAIN_COM_NR,
    DOMAIN_COM_OM,
    DOMAIN_COM_PA,
    DOMAIN_COM_PE,
    DOMAIN_COM_PH,
    DOMAIN_COM_PK,
    DOMAIN_COM_PL,
    DOMAIN_COM_PR,
    DOMAIN_COM_PY,
    DOMAIN_COM_QA,
    DOMAIN_COM_RU,
    DOMAIN_COM_SA,
    DOMAIN_COM_SB,
    DOMAIN_COM_SG,
    DOMAIN_COM_SL,
    DOMAIN_COM_SV,
    DOMAIN_COM_TJ,
    DOMAIN_COM_TN,
    DOMAIN_COM_TR,
    DOMAIN_COM_TW,
    DOMAIN_COM_UA,
    DOMAIN_COM_UY,
    DOMAIN_COM_VC,
    DOMAIN_COM_VE,
    DOMAIN_COM_VN,
    DOMAIN_GOOGLE_CV,
    DOMAIN_GOOGLE_CZ,
    DOMAIN_GOOGLE_DE,
    DOMAIN_GOOGLE_DJ,
    DOMAIN_GOOGLE_DK,
    DOMAIN_GOOGLE_DM,
    DOMAIN_GOOGLE_DZ,
    DOMAIN_GOOGLE_EE,
    DOMAIN_GOOGLE_ES,
    DOMAIN_GOOGLE_FI,
    DOMAIN_GOOGLE_FM,
    DOMAIN_GOOGLE_FR,
    DOMAIN_GOOGLE_GA,
    DOMAIN_GOOGLE_GE,
    DOMAIN_GOOGLE_GG,
    DOMAIN_GOOGLE_GL,
    DOMAIN_GOOGLE_GM,
    DOMAIN_GOOGLE_GP,
    DOMAIN_GOOGLE_GR,
    DOMAIN_GOOGLE_GY,
    DOMAIN_GOOGLE_HK,
    DOMAIN_GOOGLE_HN,
    DOMAIN_GOOGLE_HR,
    DOMAIN_GOOGLE_HT,
    DOMAIN_GOOGLE_HU,
    DOMAIN_GOOGLE_IE,
    DOMAIN_GOOGLE_IM,
    DOMAIN_GOOGLE_INFO,
    DOMAIN_GOOGLE_IQ,
    DOMAIN_GOOGLE_IS,
    DOMAIN_GOOGLE_IT,
    DOMAIN_IT_AO,
    DOMAIN_GOOGLE_JE,
    DOMAIN_GOOGLE_JO,
    DOMAIN_GOOGLE_JOBS,
    DOMAIN_GOOGLE_JP,
    DOMAIN_GOOGLE_KG,
    DOMAIN_GOOGLE_KI,
    DOMAIN_GOOGLE_KZ,
    DOMAIN_GOOGLE_LA,
    DOMAIN_GOOGLE_LI,
    DOMAIN_GOOGLE_LK,
    DOMAIN_GOOGLE_LT,
    DOMAIN_GOOGLE_LU,
    DOMAIN_GOOGLE_LV,
    DOMAIN_GOOGLE_MD,
    DOMAIN_GOOGLE_ME,
    DOMAIN_GOOGLE_MG,
    DOMAIN_GOOGLE_MK,
    DOMAIN_GOOGLE_ML,
    DOMAIN_GOOGLE_MN,
    DOMAIN_GOOGLE_MS,
    DOMAIN_GOOGLE_MU,
    DOMAIN_GOOGLE_MV,
    DOMAIN_GOOGLE_MW,
    DOMAIN_GOOGLE_NE,
    DOMAIN_NE_JP,
    DOMAIN_GOOGLE_NET,
    DOMAIN_GOOGLE_NL,
    DOMAIN_GOOGLE_NO,
    DOMAIN_GOOGLE_NR,
    DOMAIN_GOOGLE_NU,
    DOMAIN_OFF_AI,
    DOMAIN_GOOGLE_PK,
    DOMAIN_GOOGLE_PL,
    DOMAIN_GOOGLE_PN,
    DOMAIN_GOOGLE_PS,
    DOMAIN_GOOGLE_PT,
    DOMAIN_GOOGLE_RO,
    DOMAIN_GOOGLE_RS,
    DOMAIN_GOOGLE_RU,
    DOMAIN_GOOGLE_RW,
    DOMAIN_GOOGLE_SC,
    DOMAIN_GOOGLE_SE,
    DOMAIN_GOOGLE_SH,
    DOMAIN_GOOGLE_SI,
    DOMAIN_GOOGLE_SK,
    DOMAIN_GOOGLE_SM,
    DOMAIN_GOOGLE_SN,
    DOMAIN_GOOGLE_SO,
    DOMAIN_GOOGLE_ST,
    DOMAIN_GOOGLE_TD,
    DOMAIN_GOOGLE_TG,
    DOMAIN_GOOGLE_TK,
    DOMAIN_GOOGLE_TL,
    DOMAIN_GOOGLE_TM,
    DOMAIN_GOOGLE_TN,
    DOMAIN_GOOGLE_TO,
    DOMAIN_GOOGLE_TP,
    DOMAIN_GOOGLE_TT,
    DOMAIN_GOOGLE_US,
    DOMAIN_GOOGLE_UZ,
    DOMAIN_GOOGLE_VG,
    DOMAIN_GOOGLE_VU,
    DOMAIN_GOOGLE_WS,
  
    DOMAIN_CHROMIUM_ORG,
  
    DOMAIN_CRYPTO_CAT,
  
    // Boundary value for UMA_HISTOGRAM_ENUMERATION:
    DOMAIN_NUM_EVENTS
  };
  
  // PublicKeyPins contains a number of SubjectPublicKeyInfo hashes for a site.
  // The validated certificate chain for the site must not include any of
  // |excluded_hashes| and must include one or more of |required_hashes|.
  struct PublicKeyPins {
    const char* const* required_hashes;
    const char* const* excluded_hashes;
  };
  
  struct HSTSPreload {
    uint8 length;
    bool include_subdomains;
    char dns_name[38];
    bool https_required;
    PublicKeyPins pins;
    SecondLevelDomainName second_level_domain_name;
  };
  
  static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
                         const std::string& canonicalized_host, size_t i,
                         TransportSecurityState::DomainState* out, bool* ret) {
    for (size_t j = 0; j < num_entries; j++) {
      if (entries[j].length == canonicalized_host.size() - i &&
          memcmp(entries[j].dns_name, &canonicalized_host[i],
                 entries[j].length) == 0) {
        if (!entries[j].include_subdomains && i != 0) {
          *ret = false;
        } else {
          out->sts_include_subdomains = entries[j].include_subdomains;
          out->pkp_include_subdomains = entries[j].include_subdomains;
          *ret = true;
          if (!entries[j].https_required)
            out->upgrade_mode = TransportSecurityState::DomainState::MODE_DEFAULT;
          if (entries[j].pins.required_hashes) {
            const char* const* sha1_hash = entries[j].pins.required_hashes;
            while (*sha1_hash) {
              AddHash(*sha1_hash, &out->static_spki_hashes);
              sha1_hash++;
            }
          }
          if (entries[j].pins.excluded_hashes) {
            const char* const* sha1_hash = entries[j].pins.excluded_hashes;
            while (*sha1_hash) {
              AddHash(*sha1_hash, &out->bad_static_spki_hashes);
              sha1_hash++;
            }
          }
        }
        return true;
      }
    }
    return false;
  }
  
  #include "net/http/transport_security_state_static.h"
  
  // Returns the HSTSPreload entry for the |canonicalized_host| in |entries|,
  // or NULL if there is none. Prefers exact hostname matches to those that
  // match only because HSTSPreload.include_subdomains is true.
  //
  // |canonicalized_host| should be the hostname as canonicalized by
  // CanonicalizeHost.
  static const struct HSTSPreload* GetHSTSPreload(
      const std::string& canonicalized_host,
      const struct HSTSPreload* entries,
      size_t num_entries) {
    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
      for (size_t j = 0; j < num_entries; j++) {
        const struct HSTSPreload* entry = entries + j;
  
        if (i != 0 && !entry->include_subdomains)
          continue;
  
        if (entry->length == canonicalized_host.size() - i &&
            memcmp(entry->dns_name, &canonicalized_host[i], entry->length) == 0) {
          return entry;
        }
      }
    }
  
    return NULL;
  }
  
  bool TransportSecurityState::AddHSTSHeader(const std::string& host,
                                             const std::string& value) {
    DCHECK(CalledOnValidThread());
  
    base::Time now = base::Time::Now();
    base::TimeDelta max_age;
    TransportSecurityState::DomainState domain_state;
    GetDynamicDomainState(host, &domain_state);
    if (ParseHSTSHeader(value, &max_age, &domain_state.sts_include_subdomains)) {
      // Handle max-age == 0
      if (max_age.InSeconds() == 0)
        domain_state.upgrade_mode = DomainState::MODE_DEFAULT;
      else
        domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS;
      domain_state.created = now;
      domain_state.upgrade_expiry = now + max_age;
      EnableHost(host, domain_state);
      return true;
    }
    return false;
  }
  
  bool TransportSecurityState::AddHPKPHeader(const std::string& host,
                                             const std::string& value,
                                             const SSLInfo& ssl_info) {
    DCHECK(CalledOnValidThread());
  
    base::Time now = base::Time::Now();
    base::TimeDelta max_age;
    TransportSecurityState::DomainState domain_state;
    GetDynamicDomainState(host, &domain_state);
    if (ParseHPKPHeader(value, ssl_info.public_key_hashes,
                        &max_age, &domain_state.pkp_include_subdomains,
                        &domain_state.dynamic_spki_hashes)) {
      // TODO(palmer): http://crbug.com/243865 handle max-age == 0.
      domain_state.created = now;
      domain_state.dynamic_spki_hashes_expiry = now + max_age;
      EnableHost(host, domain_state);
      return true;
    }
    return false;
  }
  
  bool TransportSecurityState::AddHSTS(const std::string& host,
                                       const base::Time& expiry,
                                       bool include_subdomains) {
    DCHECK(CalledOnValidThread());
  
    // Copy-and-modify the existing DomainState for this host (if any).
    TransportSecurityState::DomainState domain_state;
    const std::string canonicalized_host = CanonicalizeHost(host);
    const std::string hashed_host = HashHost(canonicalized_host);
    DomainStateMap::const_iterator i = enabled_hosts_.find(
        hashed_host);
    if (i != enabled_hosts_.end())
      domain_state = i->second;
  
    domain_state.created = base::Time::Now();
    domain_state.sts_include_subdomains = include_subdomains;
    domain_state.upgrade_expiry = expiry;
    domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS;
    EnableHost(host, domain_state);
    return true;
  }
  
  bool TransportSecurityState::AddHPKP(const std::string& host,
                                       const base::Time& expiry,
                                       bool include_subdomains,
                                       const HashValueVector& hashes) {
    DCHECK(CalledOnValidThread());
  
    // Copy-and-modify the existing DomainState for this host (if any).
    TransportSecurityState::DomainState domain_state;
    const std::string canonicalized_host = CanonicalizeHost(host);
    const std::string hashed_host = HashHost(canonicalized_host);
    DomainStateMap::const_iterator i = enabled_hosts_.find(
        hashed_host);
    if (i != enabled_hosts_.end())
      domain_state = i->second;
  
    domain_state.created = base::Time::Now();
    domain_state.pkp_include_subdomains = include_subdomains;
    domain_state.dynamic_spki_hashes_expiry = expiry;
    domain_state.dynamic_spki_hashes = hashes;
    EnableHost(host, domain_state);
    return true;
  }
  
  // static
  bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host,
                                                      bool sni_enabled) {
    std::string canonicalized_host = CanonicalizeHost(host);
    const struct HSTSPreload* entry =
        GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
  
    if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
      return true;
  
    if (sni_enabled) {
      entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
                             kNumPreloadedSNISTS);
      if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
        return true;
    }
  
    return false;
  }
  
  // static
  void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) {
    std::string canonicalized_host = CanonicalizeHost(host);
  
    const struct HSTSPreload* entry =
        GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
  
    if (!entry) {
      entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
                             kNumPreloadedSNISTS);
    }
  
    if (!entry) {
      // We don't care to report pin failures for dynamic pins.
      return;
    }
  
    DCHECK(entry);
    DCHECK(entry->pins.required_hashes);
    DCHECK(entry->second_level_domain_name != DOMAIN_NOT_PINNED);
  
    UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain",
                              entry->second_level_domain_name, DOMAIN_NUM_EVENTS);
  }
  
  // static
  bool TransportSecurityState::IsBuildTimely() {
    const base::Time build_time = base::GetBuildTime();
    // We consider built-in information to be timely for 10 weeks.
    return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */;
  }
  
  bool TransportSecurityState::GetStaticDomainState(
      const std::string& canonicalized_host,
      bool sni_enabled,
      DomainState* out) {
    DCHECK(CalledOnValidThread());
  
    out->upgrade_mode = DomainState::MODE_FORCE_HTTPS;
    out->sts_include_subdomains = false;
    out->pkp_include_subdomains = false;
  
    const bool is_build_timely = IsBuildTimely();
  
    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
      std::string host_sub_chunk(&canonicalized_host[i],
                                 canonicalized_host.size() - i);
      out->domain = DNSDomainToString(host_sub_chunk);
      bool ret;
      if (is_build_timely &&
          HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out,
                     &ret)) {
        return ret;
      }
      if (sni_enabled &&
          is_build_timely &&
          HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i,
                     out, &ret)) {
        return ret;
      }
    }
  
    return false;
  }
  
  bool TransportSecurityState::GetDynamicDomainState(const std::string& host,
                                                     DomainState* result) {
    DCHECK(CalledOnValidThread());
  
    DomainState state;
    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
      return false;
  
    base::Time current_time(base::Time::Now());
  
    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
      std::string host_sub_chunk(&canonicalized_host[i],
                                 canonicalized_host.size() - i);
      DomainStateMap::iterator j =
          enabled_hosts_.find(HashHost(host_sub_chunk));
      if (j == enabled_hosts_.end())
        continue;
  
      if (current_time > j->second.upgrade_expiry &&
          current_time > j->second.dynamic_spki_hashes_expiry) {
        enabled_hosts_.erase(j);
        DirtyNotify();
        continue;
      }
  
      state = j->second;
      state.domain = DNSDomainToString(host_sub_chunk);
  
      // Succeed if we matched the domain exactly or if subdomain matches are
      // allowed.
      if (i == 0 || j->second.sts_include_subdomains ||
          j->second.pkp_include_subdomains) {
        *result = state;
        return true;
      }
  
      return false;
    }
  
    return false;
  }
  
  
  void TransportSecurityState::AddOrUpdateEnabledHosts(
      const std::string& hashed_host, const DomainState& state) {
    DCHECK(CalledOnValidThread());
    enabled_hosts_[hashed_host] = state;
  }
  
  TransportSecurityState::DomainState::DomainState()
      : upgrade_mode(MODE_DEFAULT),
        created(base::Time::Now()),
        sts_include_subdomains(false),
        pkp_include_subdomains(false) {
  }
  
  TransportSecurityState::DomainState::~DomainState() {
  }
  
  bool TransportSecurityState::DomainState::CheckPublicKeyPins(
      const HashValueVector& hashes) const {
    // Validate that hashes is not empty. By the time this code is called (in
    // production), that should never happen, but it's good to be defensive.
    // And, hashes *can* be empty in some test scenarios.
    if (hashes.empty()) {
      LOG(ERROR) << "Rejecting empty public key chain for public-key-pinned "
                    "domain " << domain;
      return false;
    }
  
    if (HashesIntersect(bad_static_spki_hashes, hashes)) {
      LOG(ERROR) << "Rejecting public key chain for domain " << domain
                 << ". Validated chain: " << HashesToBase64String(hashes)
                 << ", matches one or more bad hashes: "
                 << HashesToBase64String(bad_static_spki_hashes);
      return false;
    }
  
    // If there are no pins, then any valid chain is acceptable.
    if (dynamic_spki_hashes.empty() && static_spki_hashes.empty())
      return true;
  
    if (HashesIntersect(dynamic_spki_hashes, hashes) ||
        HashesIntersect(static_spki_hashes, hashes)) {
      return true;
    }
  
    LOG(ERROR) << "Rejecting public key chain for domain " << domain
               << ". Validated chain: " << HashesToBase64String(hashes)
               << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
               << " or: " << HashesToBase64String(static_spki_hashes);
    return false;
  }
  
  bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const {
    return upgrade_mode == MODE_FORCE_HTTPS;
  }
  
  bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const {
    return true;
  }
  
  bool TransportSecurityState::DomainState::HasPublicKeyPins() const {
    return static_spki_hashes.size() > 0 ||
           bad_static_spki_hashes.size() > 0 ||
           dynamic_spki_hashes.size() > 0;
  }
  
  }  // namespace
  