diff options
Diffstat (limited to 'chromium/net/http/transport_security_state.cc')
-rw-r--r-- | chromium/net/http/transport_security_state.cc | 894 |
1 files changed, 894 insertions, 0 deletions
diff --git a/chromium/net/http/transport_security_state.cc b/chromium/net/http/transport_security_state.cc new file mode 100644 index 00000000000..d238e995568 --- /dev/null +++ b/chromium/net/http/transport_security_state.cc @@ -0,0 +1,894 @@ +// 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 |