Chromium Code Reviews| Index: net/base/transport_security_state.cc |
| =================================================================== |
| --- net/base/transport_security_state.cc (revision 186438) |
| +++ net/base/transport_security_state.cc (working copy) |
| @@ -23,16 +23,13 @@ |
| #include "base/memory/scoped_ptr.h" |
| #include "base/metrics/histogram.h" |
| #include "base/sha1.h" |
| -#include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/time.h" |
| -#include "base/utf_string_conversions.h" |
| -#include "base/values.h" |
| #include "crypto/sha2.h" |
| #include "googleurl/src/gurl.h" |
| #include "net/base/dns_util.h" |
| #include "net/base/ssl_info.h" |
| -#include "net/base/x509_cert_types.h" |
| +#include "net/base/transport_security_state_preload.h" |
| #include "net/base/x509_certificate.h" |
| #include "net/http/http_security_headers.h" |
| @@ -54,12 +51,6 @@ |
| 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, |
| @@ -73,782 +64,421 @@ |
| 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; |
| +std::string HashHost(const std::string& host) { |
| + std::string lowercase = StringToLowerASCII(host); |
| + std::string old_style_canonicalized_name; |
| + if (!DNSDomainFromDot(lowercase, &old_style_canonicalized_name)) |
| + return std::string(""); |
|
Ryan Sleevi
2013/03/23 04:24:13
return std::string()
The "" technically incurs ov
unsafe
2013/03/23 08:19:39
Done.
|
| + |
| + char hashed[crypto::kSHA256Length]; |
| + crypto::SHA256HashString(old_style_canonicalized_name, hashed, |
| + sizeof(hashed)); |
| + return std::string(hashed, sizeof(hashed)); |
| } |
| +// Iterate over ("www.example.com", "example.com", "com") |
| +struct DomainNameIterator { |
| + explicit DomainNameIterator(const std::string& host) { |
| + name_ = StringToLowerASCII(host); |
| + index_ = 0; |
| + } |
| + |
| + bool AtEnd() { |
| + return name_[index_] == 0; |
| + } |
| + |
| + // Advance to NUL char, or after the next '.' |
| + void Advance() { |
| + if (AtEnd()) |
| + return; |
| + for (index_++; name_[index_] != '.' && name_[index_] != 0; index_++); |
| + if (name_[index_] == '.') |
| + index_++; |
| + } |
| + |
| + std::string GetName() { |
| + return name_.substr(index_); |
| + } |
| + |
| + bool IsFullHostname() { |
| + return index_ == 0; |
| + } |
| + |
| + std::string name_; // The full hostname, canonicalized to lowercase |
| + size_t index_; // Index into name_ |
| +}; |
| + |
| } // namespace |
| TransportSecurityState::TransportSecurityState() |
| : delegate_(NULL) { |
| } |
| -TransportSecurityState::Iterator::Iterator(const TransportSecurityState& state) |
| - : iterator_(state.enabled_hosts_.begin()), |
| - end_(state.enabled_hosts_.end()) { |
| -} |
| +TransportSecurityState::~TransportSecurityState() {} |
| -TransportSecurityState::Iterator::~Iterator() {} |
| - |
| void TransportSecurityState::SetDelegate( |
| TransportSecurityState::Delegate* delegate) { |
| delegate_ = delegate; |
| } |
| -void TransportSecurityState::EnableHost(const std::string& host, |
| - const DomainState& state) { |
| - DCHECK(CalledOnValidThread()); |
| +void TransportSecurityState::ClearDynamicData() { |
| + hsts_entries_.clear(); |
| + hpkp_entries_.clear(); |
| +} |
| - const std::string canonicalized_host = CanonicalizeHost(host); |
| - if (canonicalized_host.empty()) |
| - return; |
| - |
| - DomainState existing_state; |
| - |
| - // Use the original creation date if we already have this host. (But note |
| - // that statically-defined states have no |created| date. Therefore, we do |
| - // not bother to search the SNI-only static states.) |
| - DomainState state_copy(state); |
| - if (GetDomainState(host, false /* sni_enabled */, &existing_state) && |
| - !existing_state.created.is_null()) { |
| - state_copy.created = existing_state.created; |
| - } |
| - |
| - // 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(); |
| +void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) { |
| + DCHECK(CalledOnValidThread()); |
| + hsts_entries_.DeleteEntriesSince(time); |
| + hpkp_entries_.DeleteEntriesSince(time); |
| + CheckDirty(); |
| } |
| 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 deleted_hsts = DeleteHSTS(host); |
| + bool deleted_hpkp = DeleteHPKP(host); |
| + CheckDirty(); |
| + return deleted_hsts || deleted_hpkp; |
| } |
| bool TransportSecurityState::GetDomainState(const std::string& host, |
| bool sni_enabled, |
| - DomainState* result) { |
| + DomainState* result) const { |
| DCHECK(CalledOnValidThread()); |
| + bool found = false; |
| + const base::Time now = base::Time::Now(); |
| + DomainState dynamic_state; |
| - DomainState state; |
| - const std::string canonicalized_host = CanonicalizeHost(host); |
| - if (canonicalized_host.empty()) |
| - return false; |
| + found = GetPreloadDomainState(sni_enabled, now, host, result); |
| + found = GetDynamicDomainState(now, host, &dynamic_state) || found; |
|
Ryan Sleevi
2013/03/23 04:24:13
Why not simply
found |= GetDynamicDomainState
Be
unsafe
2013/03/23 08:19:39
its sort of a logical operation, bit arithmetic lo
|
| - bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled, |
| - &state); |
| - std::string canonicalized_preload = CanonicalizeHost(state.domain); |
| + // Merge dynamic state into preload state |
| + // Currently, HSTS and HPKP are set if either state has them set. |
| + // However, if both states have HPKP set, the preload pins take precedence. |
| + // This behavior may change (e.g. for the most-recent to take priority). |
| + if (!result->should_upgrade_ && dynamic_state.should_upgrade_) { |
| + result->should_upgrade_ = true; |
| + } |
|
Ryan Sleevi
2013/03/23 04:24:13
style: omit braces for one-liner
unsafe
2013/03/23 08:19:39
Done.
|
| + if (!result->has_public_key_pins_ && dynamic_state.has_public_key_pins_) { |
| + result->has_public_key_pins_ = true; |
| + result->public_key_pins_good_hashes_ = |
| + dynamic_state.public_key_pins_good_hashes_; |
|
Ryan Sleevi
2013/03/23 04:24:13
style: four spaces indent for continuation
unsafe
2013/03/23 08:19:39
Done.
|
| + } |
| + return found; |
| +} |
| - base::Time current_time(base::Time::Now()); |
| +bool TransportSecurityState::GetDynamicDomainState(const base::Time& now, |
| + const std::string& host, |
| + DomainState* result) const { |
| + DynamicEntry hsts_entry; |
| + HPKPEntry hpkp_entry; |
| + // Iterate over 'www.example.com", 'example.com", "com" |
| + for (DomainNameIterator iter(host); !iter.AtEnd(); iter.Advance()) { |
| + std::string hashed_host = HashHost(iter.GetName()); |
| + bool is_full_hostname = iter.IsFullHostname(); |
| - 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; |
| + // Get HSTS data from map |
| + if (!result->should_upgrade_ && |
| + hsts_entries_.GetEntry(now, hashed_host, is_full_hostname, |
| + &hsts_entry)) { |
| + result->should_upgrade_ = 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; |
| + // Get HPKP data from map |
| + if (!result->has_public_key_pins_ && |
| + hpkp_entries_.GetEntry(now, hashed_host, is_full_hostname, |
| + &hpkp_entry)) { |
| + result->has_public_key_pins_ = true; |
| + result->public_key_pins_good_hashes_ = hpkp_entry.good_hashes_; |
| } |
| - 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.include_subdomains) { |
| - *result = state; |
| + // If we've got all possible data, exit early |
| + if (result->should_upgrade_ && result->has_public_key_pins_) |
| return true; |
| - } |
| - |
| - return false; |
| } |
| - |
| - return false; |
| + return result->should_upgrade_ || result->has_public_key_pins_; |
| } |
| -void TransportSecurityState::ClearDynamicData() { |
| - enabled_hosts_.clear(); |
| -} |
| +bool TransportSecurityState::GetPreloadDomainState(bool sni_enabled, |
| + const base::Time& now, |
| + const std::string& host, |
| + DomainState* result) const { |
| +#ifdef PRELOADS_PRESENT |
|
Ryan Sleevi
2013/03/23 04:24:13
pedantry nit: #if defined() for non-header-guard i
unsafe
2013/03/23 08:19:39
Done.
|
| + const PreloadEntry* entries = kPreloadedEntries; |
| + size_t num_entries = kNumPreloaded; |
| -void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) { |
| - DCHECK(CalledOnValidThread()); |
| + if (!IsBuildTimely()) |
| + return false; |
| - 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++; |
| + for (int count = 0; count < 2; count++) { |
| + // If sni_enabled, then scan through SNI entries (if necessary) |
| + if (count == 1) { |
| + if (!sni_enabled) |
| + break; |
| + entries = kPreloadedEntriesSNI; |
| + num_entries = kNumPreloadedSNI; |
| } |
| - } |
| - if (dirtied) |
| - DirtyNotify(); |
| -} |
| + for (DomainNameIterator iter(host); !iter.AtEnd(); iter.Advance()) { |
| + std::string name = iter.GetName(); |
| + for (size_t index = 0; index < num_entries; index++) { |
| + const PreloadEntry& entry = entries[index]; |
| -TransportSecurityState::~TransportSecurityState() {} |
| + // If we find a relevant preload entry, populate the |
| + // entire DomainState from it and return |
| + if (entry.length == name.size() && |
| + memcmp(entry.dns_name, name.data(), entry.length) == 0 && |
| + (iter.IsFullHostname() || entry.include_subdomains)) { |
|
Ryan Sleevi
2013/03/23 04:24:13
nit: unnecessary newline
unsafe
2013/03/23 08:19:39
Done.
|
| -void TransportSecurityState::DirtyNotify() { |
| - DCHECK(CalledOnValidThread()); |
| + // should_upgrade |
|
Ryan Sleevi
2013/03/23 04:24:13
the comment here and on 237 seem unnecessary.
unsafe
2013/03/23 08:19:39
Done.
|
| + if (entry.https_required) |
| + result->should_upgrade_ = true; |
| - if (delegate_) |
| - delegate_->StateIsDirty(this); |
| -} |
| + // has_public_key_pins |
| + if (entry.pins.required_hashes || entry.pins.excluded_hashes) |
| + result->has_public_key_pins_ = true; |
| -// 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. |
| + HashValue hash(HASH_VALUE_SHA1); |
| + if (entry.pins.required_hashes) { |
| + const char* const* sha1_hashes = entry.pins.required_hashes; |
| + while (*sha1_hashes) { |
| + memcpy(hash.data(), *sha1_hashes, hash.size()); |
| + result->public_key_pins_good_hashes_.push_back(hash); |
| + sha1_hashes++; |
| + } |
| + } |
| + if (entry.pins.excluded_hashes) { |
| + const char* const* sha1_hashes = entry.pins.excluded_hashes; |
| + while (*sha1_hashes) { |
| + memcpy(hash.data(), *sha1_hashes, hash.size()); |
| + result->public_key_pins_bad_hashes_.push_back(hash); |
| + sha1_hashes++; |
| + } |
| + } |
| - 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(); |
| - } |
| + // is_google_pinned_property |
| + if (entry.pins.required_hashes == kGoogleAcceptableCerts) |
| + result->is_google_pinned_property_ = true; |
| - 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[34]; |
| - 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->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++; |
| + // report_uma_on_pin_failure |
| + if (entry.second_level_domain_name != DOMAIN_NOT_PINNED) { |
| + result->report_uma_on_pin_failure_ = true; |
| + result->second_level_domain_name_ = entry.second_level_domain_name; |
| } |
| + return true; |
| } |
| - 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; |
| } |
| } |
| +#endif |
| return false; |
| } |
| -#include "net/base/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, |
| +void TransportSecurityState::AddHSTSHeader(const std::string& host, |
| const std::string& value) { |
| - base::Time now = base::Time::Now(); |
| - TransportSecurityState::DomainState domain_state; |
| - if (ParseHSTSHeader(now, value, &domain_state.upgrade_expiry, |
| - &domain_state.include_subdomains)) { |
| - // Handle max-age == 0 |
| - if (now == domain_state.upgrade_expiry) |
| - domain_state.upgrade_mode = DomainState::MODE_DEFAULT; |
| + const base::Time now = base::Time::Now(); |
| + base::TimeDelta max_age; |
| + bool include_subdomains = false; |
| + if (ParseHSTSHeader(value, &max_age, &include_subdomains)) { |
| + if (max_age.InSeconds() == 0) |
| + DeleteHSTS(host); |
| else |
| - domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS; |
| - domain_state.created = now; |
| - EnableHost(host, domain_state); |
| - return true; |
| + AddHSTS(host, now, now + max_age, include_subdomains); |
| } |
| - return false; |
| } |
| -bool TransportSecurityState::AddHPKPHeader(const std::string& host, |
| +void TransportSecurityState::AddHPKPHeader(const std::string& host, |
| const std::string& value, |
| const SSLInfo& ssl_info) { |
| - base::Time now = base::Time::Now(); |
| - TransportSecurityState::DomainState domain_state; |
| - if (ParseHPKPHeader(now, value, ssl_info.public_key_hashes, |
| - &domain_state.dynamic_spki_hashes_expiry, |
| - &domain_state.dynamic_spki_hashes)) { |
| - domain_state.upgrade_mode = DomainState::MODE_DEFAULT; |
| - domain_state.created = now; |
| - EnableHost(host, domain_state); |
| - return true; |
| + const base::Time now = base::Time::Now(); |
| + base::TimeDelta max_age; |
| + HashValueVector public_key_pin_hashes; |
| + bool include_subdomains = false; // TODO(trevp) PARSE FROM HEADER |
| + if (ParseHPKPHeader(value, ssl_info.public_key_hashes, &max_age, |
| + &public_key_pin_hashes)) { |
| + if (max_age.InSeconds() == 0) |
| + DeleteHPKP(host); |
| + else |
| + AddHPKP(host, now, now + max_age, include_subdomains, |
| + public_key_pin_hashes); |
|
Ryan Sleevi
2013/03/23 04:24:13
style nit: because this line wraps, you should hav
unsafe
2013/03/23 08:19:39
Done.
|
| } |
| - return false; |
| } |
| bool TransportSecurityState::AddHSTS(const std::string& host, |
| + const base::Time& created, |
| const base::Time& expiry, |
| bool include_subdomains) { |
| - // 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.include_subdomains = include_subdomains; |
| - domain_state.upgrade_expiry = expiry; |
| - domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS; |
| - EnableHost(host, domain_state); |
| - return true; |
| + bool retval = AddHSTSHashedHost(HashHost(host), created, expiry, |
| + include_subdomains); |
|
Ryan Sleevi
2013/03/23 04:24:13
style nit: indent
style: just return (no need for
unsafe
2013/03/23 08:19:39
Done.
|
| + return retval; |
| } |
| bool TransportSecurityState::AddHPKP(const std::string& host, |
| + const base::Time& created, |
| const base::Time& expiry, |
| bool include_subdomains, |
| const HashValueVector& hashes) { |
| - // 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.include_subdomains = include_subdomains; |
| - domain_state.dynamic_spki_hashes_expiry = expiry; |
| - domain_state.dynamic_spki_hashes = hashes; |
| - EnableHost(host, domain_state); |
| - return true; |
| + return AddHPKPHashedHost(HashHost(host), created, expiry, |
| + include_subdomains, hashes); |
| } |
| -// 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); |
| +bool TransportSecurityState::AddHSTSHashedHost(const std::string& hashed_host, |
| + const base::Time& created, |
| + const base::Time& expiry, |
| + bool include_subdomains) { |
| - if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) |
| - return true; |
| + DynamicEntry entry(include_subdomains, created, expiry); |
| + bool retval = hsts_entries_.AddEntry(hashed_host, entry); |
|
Ryan Sleevi
2013/03/23 04:24:13
naming nit: s/retval/result/
unsafe
2013/03/23 08:19:39
Done.
|
| + CheckDirty(); |
| + return retval; |
| +} |
| - if (sni_enabled) { |
| - entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, |
| - kNumPreloadedSNISTS); |
| - if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) |
| - return true; |
| - } |
| +bool TransportSecurityState::AddHPKPHashedHost(const std::string& hashed_host, |
| + const base::Time& created, |
| + const base::Time& expiry, |
| + bool include_subdomains, |
| + const HashValueVector& hashes) { |
| + if (hashes.empty()) |
| + return false; |
| + HPKPEntry entry(include_subdomains, created, expiry, hashes); |
| + bool retval = hpkp_entries_.AddEntry(hashed_host, entry); |
| + CheckDirty(); |
| + return retval; |
| +} |
| - return false; |
| +bool TransportSecurityState::DeleteHSTS(const std::string& host) { |
| + bool retval = hsts_entries_.DeleteEntry(HashHost(host)); |
| + CheckDirty(); |
| + return retval; |
| } |
| -// static |
| -void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) { |
| - std::string canonicalized_host = CanonicalizeHost(host); |
| +bool TransportSecurityState::DeleteHPKP(const std::string& host) { |
| + bool retval = hpkp_entries_.DeleteEntry(HashHost(host)); |
| + CheckDirty(); |
| + return retval; |
| +} |
| - const struct HSTSPreload* entry = |
| - GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); |
| +const std::map<std::string, TransportSecurityState::DynamicEntry>& |
| +TransportSecurityState::GetHSTSEntries() const { |
| + return hsts_entries_; |
| +} |
| - 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); |
| +const std::map<std::string, TransportSecurityState::HPKPEntry>& |
| +TransportSecurityState::GetHPKPEntries() const { |
| + return hpkp_entries_; |
| } |
| -// 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) { |
| +void TransportSecurityState::CheckDirty() { |
| DCHECK(CalledOnValidThread()); |
| + if (delegate_ && (hsts_entries_.dirty || hpkp_entries_.dirty)) |
| + delegate_->StateIsDirty(this); |
| + hsts_entries_.dirty = false; |
| + hpkp_entries_.dirty = false; |
| +} |
| - out->upgrade_mode = DomainState::MODE_FORCE_HTTPS; |
| - out->include_subdomains = false; |
| +// DynamicEntry and subclasses (e.g. HPKPEntry) |
| - const bool is_build_timely = IsBuildTimely(); |
| +TransportSecurityState::DynamicEntry::DynamicEntry() |
| + : include_subdomains_(false), created_(), expiry_() { |
|
Ryan Sleevi
2013/03/23 04:24:13
style nit: each member initializer goes on a singl
|
| +} |
| - 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); |
| - std::string hashed_host(HashHost(host_sub_chunk)); |
| - if (forced_hosts_.find(hashed_host) != forced_hosts_.end()) { |
| - *out = forced_hosts_[hashed_host]; |
| - out->domain = DNSDomainToString(host_sub_chunk); |
| - return true; |
| - } |
| - 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; |
| - } |
| - } |
| +TransportSecurityState::DynamicEntry::~DynamicEntry() { |
| +} |
| - return false; |
| +TransportSecurityState::DynamicEntry::DynamicEntry(bool include_subdomains, |
| + const base::Time& created, |
| + const base::Time& expiry): |
| + include_subdomains_(include_subdomains), created_(created), expiry_(expiry) { |
|
Ryan Sleevi
2013/03/23 04:24:13
style nit:
TransportSecurityState::DynamicEntry:
|
| } |
| -void TransportSecurityState::AddOrUpdateEnabledHosts( |
| - const std::string& hashed_host, const DomainState& state) { |
| - enabled_hosts_[hashed_host] = state; |
| +TransportSecurityState::HPKPEntry::HPKPEntry() |
| + : DynamicEntry(), good_hashes_() { |
| } |
| -void TransportSecurityState::AddOrUpdateForcedHosts( |
| - const std::string& hashed_host, const DomainState& state) { |
| - forced_hosts_[hashed_host] = state; |
| +TransportSecurityState::HPKPEntry::~HPKPEntry() { |
| } |
| +TransportSecurityState::HPKPEntry::HPKPEntry( |
| + bool include_subdomains, |
| + const base::Time& created, |
| + const base::Time& expiry, |
| + const HashValueVector& good_hashes): |
| + DynamicEntry(include_subdomains, created, expiry), |
| + good_hashes_(good_hashes) { |
| +} |
| + |
| +// DomainState |
| + |
| TransportSecurityState::DomainState::DomainState() |
| - : upgrade_mode(MODE_FORCE_HTTPS), |
| - created(base::Time::Now()), |
| - include_subdomains(false) { |
| + : should_upgrade_(false), has_public_key_pins_(false), |
| + is_google_pinned_property_(false), report_uma_on_pin_failure_(false), |
| + public_key_pins_good_hashes_(), public_key_pins_bad_hashes_(), |
| + second_level_domain_name_(DOMAIN_NOT_PINNED) { |
| } |
| TransportSecurityState::DomainState::~DomainState() { |
| } |
| +bool TransportSecurityState::DomainState::HasPublicKeyPins() const { |
| + return has_public_key_pins_; |
| +} |
| + |
| 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) |
| + if (HashesIntersect(public_key_pins_bad_hashes_, hashes)) { |
| + LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
| + << HashesToBase64String(hashes) |
| << ", matches one or more bad hashes: " |
| - << HashesToBase64String(bad_static_spki_hashes); |
| + << HashesToBase64String(public_key_pins_bad_hashes_); |
| return false; |
| } |
| - // If there are no pins, then any valid chain is acceptable. |
| - if (dynamic_spki_hashes.empty() && static_spki_hashes.empty()) |
| + // If there are no good pins, then any valid chain is acceptable. |
| + // Otherwise, there has to be a match. |
| + if (public_key_pins_good_hashes_.empty() || |
| + HashesIntersect(public_key_pins_good_hashes_, hashes)) |
|
Ryan Sleevi
2013/03/23 04:24:13
style nit: braces
|
| 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); |
| + LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
| + << HashesToBase64String(hashes) |
| + << ", expected: " |
| + << HashesToBase64String(public_key_pins_good_hashes_); |
| return false; |
| } |
| bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { |
| - return upgrade_mode == MODE_FORCE_HTTPS; |
| + return should_upgrade_; |
| } |
| bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { |
| - return true; |
| + return should_upgrade_; |
| } |
| -bool TransportSecurityState::DomainState::Equals( |
| - const DomainState& other) const { |
| - // TODO(palmer): Implement this |
| - (void) other; |
| - return true; |
| +bool TransportSecurityState::DomainState::IsGooglePinnedProperty() const { |
| + return is_google_pinned_property_; |
| } |
| -bool TransportSecurityState::DomainState::HasPublicKeyPins() const { |
| - return static_spki_hashes.size() > 0 || |
| - bad_static_spki_hashes.size() > 0 || |
| - dynamic_spki_hashes.size() > 0; |
| +void TransportSecurityState::DomainState::ReportUMAOnPinFailure() const { |
| + if (report_uma_on_pin_failure_) { |
| + UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain", |
| + second_level_domain_name_, DOMAIN_NUM_EVENTS); |
| + } |
| } |
| +const HashValueVector& |
| +TransportSecurityState::DomainState::GetPublicKeyPinsGoodHashes() const { |
| + return public_key_pins_good_hashes_; |
| +} |
| + |
| +const HashValueVector& |
| +TransportSecurityState::DomainState::GetPublicKeyPinsBadHashes() const { |
| + return public_key_pins_bad_hashes_; |
| +} |
| + |
| } // namespace |