// 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 #include #else // !defined(USE_OPENSSL) #include #include #include #include #include #endif #include #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(new_host[i]); if (!label_length) break; for (size_t j = 0; j < label_length; ++j) { new_host[i + 1 + j] = tolower(new_host[i + 1 + j]); } } 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, DOMAIN_LAVABIT_COM, DOMAIN_GOOGLETAGMANAGER_COM, // 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