Index: net/http/transport_security_state.cc |
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc |
deleted file mode 100644 |
index a174e9875ad25b45e34c31cb4a3512e10dfa2736..0000000000000000000000000000000000000000 |
--- a/net/http/transport_security_state.cc |
+++ /dev/null |
@@ -1,964 +0,0 @@ |
-// 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/metrics/sparse_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), enable_static_pins_(true) { |
-// Static pinning is only enabled for official builds to make sure that |
-// others don't end up with pins that cannot be easily updated. |
-#if !defined(OFFICIAL_BUILD) || defined(OS_ANDROID) || defined(OS_IOS) |
- enable_static_pins_ = false; |
-#endif |
- DCHECK(CalledOnValidThread()); |
-} |
- |
-TransportSecurityState::Iterator::Iterator(const TransportSecurityState& state) |
- : iterator_(state.enabled_hosts_.begin()), |
- end_(state.enabled_hosts_.end()) { |
-} |
- |
-TransportSecurityState::Iterator::~Iterator() {} |
- |
-bool TransportSecurityState::ShouldSSLErrorsBeFatal(const std::string& host) { |
- DomainState state; |
- if (GetStaticDomainState(host, &state)) |
- return true; |
- return GetDynamicDomainState(host, &state); |
-} |
- |
-bool TransportSecurityState::ShouldUpgradeToSSL(const std::string& host) { |
- DomainState dynamic_state; |
- if (GetDynamicDomainState(host, &dynamic_state)) |
- return dynamic_state.ShouldUpgradeToSSL(); |
- |
- DomainState static_state; |
- if (GetStaticDomainState(host, &static_state) && |
- static_state.ShouldUpgradeToSSL()) { |
- return true; |
- } |
- |
- return false; |
-} |
- |
-bool TransportSecurityState::CheckPublicKeyPins( |
- const std::string& host, |
- bool is_issued_by_known_root, |
- const HashValueVector& public_key_hashes, |
- std::string* pinning_failure_log) { |
- // Perform pin validation if, and only if, all these conditions obtain: |
- // |
- // * the server's certificate chain chains up to a known root (i.e. not a |
- // user-installed trust anchor); and |
- // * the server actually has public key pins. |
- if (!is_issued_by_known_root || !HasPublicKeyPins(host)) { |
- return true; |
- } |
- |
- bool pins_are_valid = CheckPublicKeyPinsImpl( |
- host, public_key_hashes, pinning_failure_log); |
- if (!pins_are_valid) { |
- LOG(ERROR) << *pinning_failure_log; |
- ReportUMAOnPinFailure(host); |
- } |
- |
- UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", pins_are_valid); |
- return pins_are_valid; |
-} |
- |
-bool TransportSecurityState::HasPublicKeyPins(const std::string& host) { |
- DomainState dynamic_state; |
- if (GetDynamicDomainState(host, &dynamic_state)) |
- return dynamic_state.HasPublicKeyPins(); |
- |
- DomainState static_state; |
- if (GetStaticDomainState(host, &static_state)) { |
- if (static_state.HasPublicKeyPins()) |
- return true; |
- } |
- |
- return false; |
-} |
- |
-void TransportSecurityState::SetDelegate( |
- TransportSecurityState::Delegate* delegate) { |
- DCHECK(CalledOnValidThread()); |
- delegate_ = delegate; |
-} |
- |
-void TransportSecurityState::AddHSTSInternal( |
- const std::string& host, |
- TransportSecurityState::DomainState::UpgradeMode upgrade_mode, |
- const base::Time& expiry, |
- bool include_subdomains) { |
- DCHECK(CalledOnValidThread()); |
- |
- // Copy-and-modify the existing DomainState for this host (if any). |
- 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.sts.last_observed = base::Time::Now(); |
- domain_state.sts.include_subdomains = include_subdomains; |
- domain_state.sts.expiry = expiry; |
- domain_state.sts.upgrade_mode = upgrade_mode; |
- EnableHost(host, domain_state); |
-} |
- |
-void TransportSecurityState::AddHPKPInternal(const std::string& host, |
- const base::Time& last_observed, |
- const base::Time& expiry, |
- bool include_subdomains, |
- const HashValueVector& hashes) { |
- DCHECK(CalledOnValidThread()); |
- |
- // Copy-and-modify the existing DomainState for this host (if any). |
- 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.pkp.last_observed = last_observed; |
- domain_state.pkp.expiry = expiry; |
- domain_state.pkp.include_subdomains = include_subdomains; |
- domain_state.pkp.spki_hashes = hashes; |
- EnableHost(host, domain_state); |
-} |
- |
-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.sts.domain.clear(); |
- state_copy.pkp.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; |
-} |
- |
-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()) { |
- // Clear STS and PKP state independently. |
- if (i->second.sts.last_observed >= time) { |
- dirtied = true; |
- i->second.sts.upgrade_mode = DomainState::MODE_DEFAULT; |
- } |
- if (i->second.pkp.last_observed >= time) { |
- dirtied = true; |
- i->second.pkp.spki_hashes.clear(); |
- i->second.pkp.expiry = base::Time(); |
- } |
- |
- // If both are now invalid, drop the entry altogether. |
- if (!i->second.ShouldUpgradeToSSL() && !i->second.HasPublicKeyPins()) { |
- dirtied = true; |
- enabled_hosts_.erase(i++); |
- continue; |
- } |
- |
- ++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) { |
- new_host[i + 1 + j] = static_cast<char>(tolower(new_host[i + 1 + j])); |
- } |
- } |
- |
- return new_host; |
-} |
- |
-// BitReader is a class that allows a bytestring to be read bit-by-bit. |
-class BitReader { |
- public: |
- BitReader(const uint8* bytes, size_t num_bits) |
- : bytes_(bytes), |
- num_bits_(num_bits), |
- num_bytes_((num_bits + 7) / 8), |
- current_byte_index_(0), |
- num_bits_used_(8) {} |
- |
- // Next sets |*out| to the next bit from the input. It returns false if no |
- // more bits are available or true otherwise. |
- bool Next(bool* out) { |
- if (num_bits_used_ == 8) { |
- if (current_byte_index_ >= num_bytes_) { |
- return false; |
- } |
- current_byte_ = bytes_[current_byte_index_++]; |
- num_bits_used_ = 0; |
- } |
- |
- *out = 1 & (current_byte_ >> (7 - num_bits_used_)); |
- num_bits_used_++; |
- return true; |
- } |
- |
- // Read sets the |num_bits| least-significant bits of |*out| to the value of |
- // the next |num_bits| bits from the input. It returns false if there are |
- // insufficient bits in the input or true otherwise. |
- bool Read(unsigned num_bits, uint32* out) { |
- DCHECK_LE(num_bits, 32u); |
- |
- uint32 ret = 0; |
- for (unsigned i = 0; i < num_bits; ++i) { |
- bool bit; |
- if (!Next(&bit)) { |
- return false; |
- } |
- ret |= static_cast<uint32>(bit) << (num_bits - 1 - i); |
- } |
- |
- *out = ret; |
- return true; |
- } |
- |
- // Unary sets |*out| to the result of decoding a unary value from the input. |
- // It returns false if there were insufficient bits in the input and true |
- // otherwise. |
- bool Unary(size_t* out) { |
- size_t ret = 0; |
- |
- for (;;) { |
- bool bit; |
- if (!Next(&bit)) { |
- return false; |
- } |
- if (!bit) { |
- break; |
- } |
- ret++; |
- } |
- |
- *out = ret; |
- return true; |
- } |
- |
- // Seek sets the current offest in the input to bit number |offset|. It |
- // returns true if |offset| is within the range of the input and false |
- // otherwise. |
- bool Seek(size_t offset) { |
- if (offset >= num_bits_) { |
- return false; |
- } |
- current_byte_index_ = offset / 8; |
- current_byte_ = bytes_[current_byte_index_++]; |
- num_bits_used_ = offset % 8; |
- return true; |
- } |
- |
- private: |
- const uint8* const bytes_; |
- const size_t num_bits_; |
- const size_t num_bytes_; |
- // current_byte_index_ contains the current byte offset in |bytes_|. |
- size_t current_byte_index_; |
- // current_byte_ contains the current byte of the input. |
- uint8 current_byte_; |
- // num_bits_used_ contains the number of bits of |current_byte_| that have |
- // been read. |
- unsigned num_bits_used_; |
-}; |
- |
-// HuffmanDecoder is a very simple Huffman reader. The input Huffman tree is |
-// simply encoded as a series of two-byte structures. The first byte determines |
-// the "0" pointer for that node and the second the "1" pointer. Each byte |
-// either has the MSB set, in which case the bottom 7 bits are the value for |
-// that position, or else the bottom seven bits contain the index of a node. |
-// |
-// The tree is decoded by walking rather than a table-driven approach. |
-class HuffmanDecoder { |
- public: |
- HuffmanDecoder(const uint8* tree, size_t tree_bytes) |
- : tree_(tree), |
- tree_bytes_(tree_bytes) {} |
- |
- bool Decode(BitReader* reader, char* out) { |
- const uint8* current = &tree_[tree_bytes_-2]; |
- |
- for (;;) { |
- bool bit; |
- if (!reader->Next(&bit)) { |
- return false; |
- } |
- |
- uint8 b = current[bit]; |
- if (b & 0x80) { |
- *out = static_cast<char>(b & 0x7f); |
- return true; |
- } |
- |
- unsigned offset = static_cast<unsigned>(b) * 2; |
- DCHECK_LT(offset, tree_bytes_); |
- if (offset >= tree_bytes_) { |
- return false; |
- } |
- |
- current = &tree_[offset]; |
- } |
- } |
- |
- private: |
- const uint8* const tree_; |
- const size_t tree_bytes_; |
-}; |
- |
-#include "net/http/transport_security_state_static.h" |
- |
-// PreloadResult is the result of resolving a specific name in the preloaded |
-// data. |
-struct PreloadResult { |
- uint32 pinset_id; |
- uint32 domain_id; |
- // hostname_offset contains the number of bytes from the start of the given |
- // hostname where the name of the matching entry starts. |
- size_t hostname_offset; |
- bool sts_include_subdomains; |
- bool pkp_include_subdomains; |
- bool force_https; |
- bool has_pins; |
-}; |
- |
-// DecodeHSTSPreloadRaw resolves |hostname| in the preloaded data. It returns |
-// false on internal error and true otherwise. After a successful return, |
-// |*out_found| is true iff a relevant entry has been found. If so, |*out| |
-// contains the details. |
-// |
-// Don't call this function, call DecodeHSTSPreload, below. |
-// |
-// Although this code should be robust, it never processes attacker-controlled |
-// data -- it only operates on the preloaded data built into the binary. |
-// |
-// The preloaded data is represented as a trie and matches the hostname |
-// backwards. Each node in the trie starts with a number of characters, which |
-// must match exactly. After that is a dispatch table which maps the next |
-// character in the hostname to another node in the trie. |
-// |
-// In the dispatch table, the zero character represents the "end of string" |
-// (which is the *beginning* of a hostname since we process it backwards). The |
-// value in that case is special -- rather than an offset to another trie node, |
-// it contains the HSTS information: whether subdomains are included, pinsets |
-// etc. If an "end of string" matches a period in the hostname then the |
-// information is remembered because, if no more specific node is found, then |
-// that information applies to the hostname. |
-// |
-// Dispatch tables are always given in order, but the "end of string" (zero) |
-// value always comes before an entry for '.'. |
-bool DecodeHSTSPreloadRaw(const std::string& hostname, |
- bool* out_found, |
- PreloadResult* out) { |
- HuffmanDecoder huffman(kHSTSHuffmanTree, sizeof(kHSTSHuffmanTree)); |
- BitReader reader(kPreloadedHSTSData, kPreloadedHSTSBits); |
- size_t bit_offset = kHSTSRootPosition; |
- static const char kEndOfString = 0; |
- static const char kEndOfTable = 127; |
- |
- *out_found = false; |
- |
- if (hostname.empty()) { |
- return true; |
- } |
- // hostname_offset contains one more than the index of the current character |
- // in the hostname that is being considered. It's one greater so that we can |
- // represent the position just before the beginning (with zero). |
- size_t hostname_offset = hostname.size(); |
- |
- for (;;) { |
- // Seek to the desired location. |
- if (!reader.Seek(bit_offset)) { |
- return false; |
- } |
- |
- // Decode the unary length of the common prefix. |
- size_t prefix_length; |
- if (!reader.Unary(&prefix_length)) { |
- return false; |
- } |
- |
- // Match each character in the prefix. |
- for (size_t i = 0; i < prefix_length; ++i) { |
- if (hostname_offset == 0) { |
- // We can't match the terminator with a prefix string. |
- return true; |
- } |
- |
- char c; |
- if (!huffman.Decode(&reader, &c)) { |
- return false; |
- } |
- if (hostname[hostname_offset - 1] != c) { |
- return true; |
- } |
- hostname_offset--; |
- } |
- |
- bool is_first_offset = true; |
- size_t current_offset = 0; |
- |
- // Next is the dispatch table. |
- for (;;) { |
- char c; |
- if (!huffman.Decode(&reader, &c)) { |
- return false; |
- } |
- if (c == kEndOfTable) { |
- // No exact match. |
- return true; |
- } |
- |
- if (c == kEndOfString) { |
- PreloadResult tmp; |
- if (!reader.Next(&tmp.sts_include_subdomains) || |
- !reader.Next(&tmp.force_https) || |
- !reader.Next(&tmp.has_pins)) { |
- return false; |
- } |
- |
- tmp.pkp_include_subdomains = tmp.sts_include_subdomains; |
- |
- if (tmp.has_pins) { |
- if (!reader.Read(4, &tmp.pinset_id) || |
- !reader.Read(9, &tmp.domain_id) || |
- (!tmp.sts_include_subdomains && |
- !reader.Next(&tmp.pkp_include_subdomains))) { |
- return false; |
- } |
- } |
- |
- tmp.hostname_offset = hostname_offset; |
- |
- if (hostname_offset == 0 || hostname[hostname_offset - 1] == '.') { |
- *out_found = |
- tmp.sts_include_subdomains || tmp.pkp_include_subdomains; |
- *out = tmp; |
- |
- if (hostname_offset > 0) { |
- out->force_https &= tmp.sts_include_subdomains; |
- } else { |
- *out_found = true; |
- return true; |
- } |
- } |
- |
- continue; |
- } |
- |
- // The entries in a dispatch table are in order thus we can tell if there |
- // will be no match if the current character past the one that we want. |
- if (hostname_offset == 0 || hostname[hostname_offset-1] < c) { |
- return true; |
- } |
- |
- if (is_first_offset) { |
- // The first offset is backwards from the current position. |
- uint32 jump_delta_bits; |
- uint32 jump_delta; |
- if (!reader.Read(5, &jump_delta_bits) || |
- !reader.Read(jump_delta_bits, &jump_delta)) { |
- return false; |
- } |
- |
- if (bit_offset < jump_delta) { |
- return false; |
- } |
- |
- current_offset = bit_offset - jump_delta; |
- is_first_offset = false; |
- } else { |
- // Subsequent offsets are forward from the target of the first offset. |
- uint32 is_long_jump; |
- if (!reader.Read(1, &is_long_jump)) { |
- return false; |
- } |
- |
- uint32 jump_delta; |
- if (!is_long_jump) { |
- if (!reader.Read(7, &jump_delta)) { |
- return false; |
- } |
- } else { |
- uint32 jump_delta_bits; |
- if (!reader.Read(4, &jump_delta_bits) || |
- !reader.Read(jump_delta_bits + 8, &jump_delta)) { |
- return false; |
- } |
- } |
- |
- current_offset += jump_delta; |
- if (current_offset >= bit_offset) { |
- return false; |
- } |
- } |
- |
- DCHECK_LT(0u, hostname_offset); |
- if (hostname[hostname_offset - 1] == c) { |
- bit_offset = current_offset; |
- hostname_offset--; |
- break; |
- } |
- } |
- } |
-} |
- |
-bool DecodeHSTSPreload(const std::string& hostname, |
- PreloadResult* out) { |
- bool found; |
- if (!DecodeHSTSPreloadRaw(hostname, &found, out)) { |
- DCHECK(false) << "Internal error in DecodeHSTSPreloadRaw for hostname " |
- << hostname; |
- return false; |
- } |
- |
- return found; |
-} |
- |
-bool TransportSecurityState::AddHSTSHeader(const std::string& host, |
- const std::string& value) { |
- DCHECK(CalledOnValidThread()); |
- |
- base::Time now = base::Time::Now(); |
- base::TimeDelta max_age; |
- bool include_subdomains; |
- if (!ParseHSTSHeader(value, &max_age, &include_subdomains)) { |
- return false; |
- } |
- |
- // Handle max-age == 0. |
- DomainState::UpgradeMode upgrade_mode; |
- if (max_age.InSeconds() == 0) { |
- upgrade_mode = DomainState::MODE_DEFAULT; |
- } else { |
- upgrade_mode = DomainState::MODE_FORCE_HTTPS; |
- } |
- |
- AddHSTSInternal(host, upgrade_mode, now + max_age, include_subdomains); |
- return true; |
-} |
- |
-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; |
- bool include_subdomains; |
- HashValueVector spki_hashes; |
- if (!ParseHPKPHeader(value, ssl_info.public_key_hashes, &max_age, |
- &include_subdomains, &spki_hashes)) { |
- return false; |
- } |
- // Handle max-age == 0. |
- if (max_age.InSeconds() == 0) |
- spki_hashes.clear(); |
- AddHPKPInternal(host, now, now + max_age, include_subdomains, spki_hashes); |
- return true; |
-} |
- |
-void TransportSecurityState::AddHSTS(const std::string& host, |
- const base::Time& expiry, |
- bool include_subdomains) { |
- DCHECK(CalledOnValidThread()); |
- AddHSTSInternal(host, DomainState::MODE_FORCE_HTTPS, expiry, |
- include_subdomains); |
-} |
- |
-void TransportSecurityState::AddHPKP(const std::string& host, |
- const base::Time& expiry, |
- bool include_subdomains, |
- const HashValueVector& hashes) { |
- DCHECK(CalledOnValidThread()); |
- AddHPKPInternal(host, base::Time::Now(), expiry, include_subdomains, hashes); |
-} |
- |
-// static |
-bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host) { |
- PreloadResult result; |
- return DecodeHSTSPreload(host, &result) && result.has_pins && |
- kPinsets[result.pinset_id].accepted_pins == kGoogleAcceptableCerts; |
-} |
- |
-// static |
-void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) { |
- PreloadResult result; |
- if (!DecodeHSTSPreload(host, &result) || |
- !result.has_pins) { |
- return; |
- } |
- |
- DCHECK(result.domain_id != DOMAIN_NOT_PINNED); |
- |
- UMA_HISTOGRAM_SPARSE_SLOWLY( |
- "Net.PublicKeyPinFailureDomain", result.domain_id); |
-} |
- |
-// static |
-bool TransportSecurityState::IsBuildTimely() { |
- // If the build metadata aren't embedded in the binary then we can't use the |
- // build time to determine if the build is timely, return true by default. If |
- // we're building an official build then keep using the build time, even if |
- // it's invalid it'd be a date in the past and this function will return |
- // false. |
-#if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD) |
- return true; |
-#else |
- 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 */; |
-#endif |
-} |
- |
-bool TransportSecurityState::CheckPublicKeyPinsImpl( |
- const std::string& host, |
- const HashValueVector& hashes, |
- std::string* failure_log) { |
- DomainState dynamic_state; |
- if (GetDynamicDomainState(host, &dynamic_state)) |
- return dynamic_state.CheckPublicKeyPins(hashes, failure_log); |
- |
- DomainState static_state; |
- if (GetStaticDomainState(host, &static_state)) |
- return static_state.CheckPublicKeyPins(hashes, failure_log); |
- |
- // HasPublicKeyPins should have returned true in order for this method |
- // to have been called, so if we fall through to here, it's an error. |
- return false; |
-} |
- |
-bool TransportSecurityState::GetStaticDomainState(const std::string& host, |
- DomainState* out) const { |
- DCHECK(CalledOnValidThread()); |
- |
- out->sts.upgrade_mode = DomainState::MODE_FORCE_HTTPS; |
- out->sts.include_subdomains = false; |
- out->pkp.include_subdomains = false; |
- |
- if (!IsBuildTimely()) |
- return false; |
- |
- PreloadResult result; |
- if (!DecodeHSTSPreload(host, &result)) |
- return false; |
- |
- out->sts.domain = host.substr(result.hostname_offset); |
- out->pkp.domain = out->sts.domain; |
- out->sts.include_subdomains = result.sts_include_subdomains; |
- out->sts.last_observed = base::GetBuildTime(); |
- out->sts.upgrade_mode = |
- TransportSecurityState::DomainState::MODE_DEFAULT; |
- if (result.force_https) { |
- out->sts.upgrade_mode = |
- TransportSecurityState::DomainState::MODE_FORCE_HTTPS; |
- } |
- |
- if (enable_static_pins_ && result.has_pins) { |
- out->pkp.include_subdomains = result.pkp_include_subdomains; |
- out->pkp.last_observed = base::GetBuildTime(); |
- |
- if (result.pinset_id >= arraysize(kPinsets)) |
- return false; |
- const Pinset *pinset = &kPinsets[result.pinset_id]; |
- |
- if (pinset->accepted_pins) { |
- const char* const* sha1_hash = pinset->accepted_pins; |
- while (*sha1_hash) { |
- AddHash(*sha1_hash, &out->pkp.spki_hashes); |
- sha1_hash++; |
- } |
- } |
- if (pinset->rejected_pins) { |
- const char* const* sha1_hash = pinset->rejected_pins; |
- while (*sha1_hash) { |
- AddHash(*sha1_hash, &out->pkp.bad_spki_hashes); |
- sha1_hash++; |
- } |
- } |
- } |
- |
- return true; |
-} |
- |
-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()); |
- |
- bool found_sts = false; |
- bool found_pkp = false; |
- 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 both halves of the entry are invalid, drop it. |
- if (current_time > j->second.sts.expiry && |
- current_time > j->second.pkp.expiry) { |
- enabled_hosts_.erase(j); |
- DirtyNotify(); |
- continue; |
- } |
- |
- // If this is the most specific STS match, add it to the result. |
- if (!found_sts && (i == 0 || j->second.sts.include_subdomains) && |
- current_time <= j->second.sts.expiry && |
- j->second.ShouldUpgradeToSSL()) { |
- found_sts = true; |
- state.sts = j->second.sts; |
- state.sts.domain = DNSDomainToString(host_sub_chunk); |
- } |
- |
- // If this is the most specific PKP match, add it to the result. |
- if (!found_pkp && (i == 0 || j->second.pkp.include_subdomains) && |
- current_time <= j->second.pkp.expiry && j->second.HasPublicKeyPins()) { |
- found_pkp = true; |
- state.pkp = j->second.pkp; |
- state.pkp.domain = DNSDomainToString(host_sub_chunk); |
- } |
- |
- if (found_sts && found_pkp) |
- break; |
- } |
- |
- if (!found_sts && !found_pkp) |
- return false; |
- |
- *result = state; |
- return true; |
-} |
- |
-void TransportSecurityState::AddOrUpdateEnabledHosts( |
- const std::string& hashed_host, const DomainState& state) { |
- DCHECK(CalledOnValidThread()); |
- enabled_hosts_[hashed_host] = state; |
-} |
- |
-TransportSecurityState::DomainState::DomainState() { |
- sts.upgrade_mode = MODE_DEFAULT; |
- sts.include_subdomains = false; |
- pkp.include_subdomains = false; |
-} |
- |
-TransportSecurityState::DomainState::~DomainState() { |
-} |
- |
-bool TransportSecurityState::DomainState::CheckPublicKeyPins( |
- const HashValueVector& hashes, std::string* failure_log) 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()) { |
- failure_log->append( |
- "Rejecting empty public key chain for public-key-pinned domains: " + |
- pkp.domain); |
- return false; |
- } |
- |
- if (HashesIntersect(pkp.bad_spki_hashes, hashes)) { |
- failure_log->append("Rejecting public key chain for domain " + pkp.domain + |
- ". Validated chain: " + HashesToBase64String(hashes) + |
- ", matches one or more bad hashes: " + |
- HashesToBase64String(pkp.bad_spki_hashes)); |
- return false; |
- } |
- |
- // If there are no pins, then any valid chain is acceptable. |
- if (pkp.spki_hashes.empty()) |
- return true; |
- |
- if (HashesIntersect(pkp.spki_hashes, hashes)) { |
- return true; |
- } |
- |
- failure_log->append("Rejecting public key chain for domain " + pkp.domain + |
- ". Validated chain: " + HashesToBase64String(hashes) + |
- ", expected: " + HashesToBase64String(pkp.spki_hashes)); |
- return false; |
-} |
- |
-bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { |
- return sts.upgrade_mode == MODE_FORCE_HTTPS; |
-} |
- |
-bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { |
- // Both HSTS and HPKP cause fatal SSL errors, so enable this on the presense |
- // of either. (If neither is active, no DomainState will be returned.) |
- return true; |
-} |
- |
-bool TransportSecurityState::DomainState::HasPublicKeyPins() const { |
- return pkp.spki_hashes.size() > 0 || pkp.bad_spki_hashes.size() > 0; |
-} |
- |
-TransportSecurityState::DomainState::STSState::STSState() { |
-} |
- |
-TransportSecurityState::DomainState::STSState::~STSState() { |
-} |
- |
-TransportSecurityState::DomainState::PKPState::PKPState() { |
-} |
- |
-TransportSecurityState::DomainState::PKPState::~PKPState() { |
-} |
- |
-} // namespace |