diff options
author | palmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-01 19:39:48 +0000 |
---|---|---|
committer | palmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-01 19:39:48 +0000 |
commit | f43b89f30c817107bc595f45098d908f84bf9baa (patch) | |
tree | 9b606ccecd8b30b1f72c52576c2cb034bb6c7244 /net | |
parent | 9eedb4825fd27cec7086f9be09a08eb8248ca868 (diff) | |
download | chromium_src-f43b89f30c817107bc595f45098d908f84bf9baa.zip chromium_src-f43b89f30c817107bc595f45098d908f84bf9baa.tar.gz chromium_src-f43b89f30c817107bc595f45098d908f84bf9baa.tar.bz2 |
Refactor TransportSecurityState.
Do some minor "gcl lint" cleanup while here.
BUG=113280, 120373
TEST=net_unittests, browser_tests, unit_tests TransportSecurityPersisterTest.*
Review URL: http://codereview.chromium.org/9415040
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@134754 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/transport_security_state.cc | 678 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 375 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 987 | ||||
-rw-r--r-- | net/base/x509_cert_types.h | 7 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 50 | ||||
-rw-r--r-- | net/socket_stream/socket_stream.cc | 6 | ||||
-rw-r--r-- | net/socket_stream/socket_stream_job.cc | 2 | ||||
-rw-r--r-- | net/url_request/url_request_context_builder.cc | 2 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.cc | 50 | ||||
-rw-r--r-- | net/url_request/url_request_unittest.cc | 30 | ||||
-rw-r--r-- | net/websockets/websocket_job_spdy2_unittest.cc | 9 | ||||
-rw-r--r-- | net/websockets/websocket_job_spdy3_unittest.cc | 9 |
12 files changed, 719 insertions, 1486 deletions
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index b5fce7b..b2450dd 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -19,8 +19,6 @@ #include <utility> #include "base/base64.h" -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" @@ -33,7 +31,6 @@ #include "base/values.h" #include "crypto/sha2.h" #include "googleurl/src/gurl.h" -#include "net/base/asn1_util.h" #include "net/base/dns_util.h" #include "net/base/ssl_info.h" #include "net/base/x509_certificate.h" @@ -47,20 +44,16 @@ namespace net { const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year -TransportSecurityState::TransportSecurityState(const std::string& hsts_hosts) - : delegate_(NULL) { - if (!hsts_hosts.empty()) { - bool dirty; - Deserialise(hsts_hosts, &dirty, &forced_hosts_); - } -} - static 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)); } +TransportSecurityState::TransportSecurityState() + : delegate_(NULL) { +} + void TransportSecurityState::SetDelegate( TransportSecurityState::Delegate* delegate) { delegate_ = delegate; @@ -74,24 +67,19 @@ void TransportSecurityState::EnableHost(const std::string& host, if (canonicalized_host.empty()) return; - // Only override a preloaded state if the new state describes a more strict - // policy. TODO(palmer): Reconsider this? DomainState existing_state; - if (IsPreloadedSTS(canonicalized_host, true, &existing_state) && - canonicalized_host == CanonicalizeHost(existing_state.domain) && - existing_state.IsMoreStrict(state)) { - return; - } - // Use the original creation date if we already have this host. + // 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(&existing_state, host, true) && + if (GetDomainState(host, false /* sni_enabled */, &existing_state) && !existing_state.created.is_null()) { state_copy.created = existing_state.created; } - // We don't store these values. - state_copy.preloaded = false; + // 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; @@ -115,27 +103,9 @@ bool TransportSecurityState::DeleteHost(const std::string& host) { return false; } -bool TransportSecurityState::HasPinsForHost(DomainState* result, - const std::string& host, - bool sni_available) { - DCHECK(CalledOnValidThread()); - - return HasMetadata(result, host, sni_available) && - (!result->dynamic_spki_hashes.empty() || - !result->preloaded_spki_hashes.empty()); -} - -bool TransportSecurityState::GetDomainState(DomainState* result, - const std::string& host, - bool sni_available) { - DCHECK(CalledOnValidThread()); - - return HasMetadata(result, host, sni_available); -} - -bool TransportSecurityState::HasMetadata(DomainState* result, - const std::string& host, - bool sni_available) { +bool TransportSecurityState::GetDomainState(const std::string& host, + bool sni_enabled, + DomainState* result) { DCHECK(CalledOnValidThread()); DomainState state; @@ -143,7 +113,8 @@ bool TransportSecurityState::HasMetadata(DomainState* result, if (canonicalized_host.empty()) return false; - bool has_preload = IsPreloadedSTS(canonicalized_host, sni_available, &state); + bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled, + &state); std::string canonicalized_preload = CanonicalizeHost(state.domain); base::Time current_time(base::Time::Now()); @@ -162,7 +133,7 @@ bool TransportSecurityState::HasMetadata(DomainState* result, if (j == enabled_hosts_.end()) continue; - if (current_time > j->second.expiry && + if (current_time > j->second.upgrade_expiry && current_time > j->second.dynamic_spki_hashes_expiry) { enabled_hosts_.erase(j); DirtyNotify(); @@ -290,23 +261,6 @@ static bool ParseAndAppendPin(const std::string& value, return true; } -// static -bool TransportSecurityState::GetPublicKeyHash( - const X509Certificate& cert, SHA1Fingerprint* spki_hash) { - std::string der_bytes; - if (!X509Certificate::GetDEREncoded(cert.os_cert_handle(), &der_bytes)) - return false; - - base::StringPiece spki; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) - return false; - - base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(spki.data()), - spki.size(), spki_hash->data); - - return true; -} - struct FingerprintsEqualPredicate { explicit FingerprintsEqualPredicate(const SHA1Fingerprint& fingerprint) : fingerprint_(fingerprint) {} @@ -368,13 +322,12 @@ static bool IsPinListValid(const FingerprintVector& pins, // "Public-Key-Pins" ":" // "max-age" "=" delta-seconds ";" // "pin-" algo "=" base64 [ ";" ... ] -// -// static -bool TransportSecurityState::ParsePinsHeader(const std::string& value, - const SSLInfo& ssl_info, - DomainState* state) { +bool TransportSecurityState::DomainState::ParsePinsHeader( + const base::Time& now, + const std::string& value, + const SSLInfo& ssl_info) { bool parsed_max_age = false; - int max_age = 0; + int max_age_candidate = 0; FingerprintVector pins; std::string source = value; @@ -389,11 +342,12 @@ bool TransportSecurityState::ParsePinsHeader(const std::string& value, if (LowerCaseEqualsASCII(equals.first, "max-age")) { if (equals.second.empty() || - !MaxAgeToInt(equals.second.begin(), equals.second.end(), &max_age)) { + !MaxAgeToInt(equals.second.begin(), equals.second.end(), + &max_age_candidate)) { return false; } - if (max_age > kMaxHSTSAgeSecs) - max_age = kMaxHSTSAgeSecs; + if (max_age_candidate > kMaxHSTSAgeSecs) + max_age_candidate = kMaxHSTSAgeSecs; parsed_max_age = true; } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) { if (!ParseAndAppendPin(equals.second, &pins)) @@ -410,15 +364,14 @@ bool TransportSecurityState::ParsePinsHeader(const std::string& value, if (!parsed_max_age || !IsPinListValid(pins, ssl_info)) return false; - state->max_age = max_age; - state->dynamic_spki_hashes_expiry = - base::Time::Now() + base::TimeDelta::FromSeconds(max_age); + dynamic_spki_hashes_expiry = + now + base::TimeDelta::FromSeconds(max_age_candidate); - state->dynamic_spki_hashes.clear(); - if (max_age > 0) { + dynamic_spki_hashes.clear(); + if (max_age_candidate > 0) { for (FingerprintVector::const_iterator i = pins.begin(); - i != pins.end(); i++) { - state->dynamic_spki_hashes.push_back(*i); + i != pins.end(); ++i) { + dynamic_spki_hashes.push_back(*i); } } @@ -427,14 +380,9 @@ bool TransportSecurityState::ParsePinsHeader(const std::string& value, // "Strict-Transport-Security" ":" // "max-age" "=" delta-seconds [ ";" "includeSubDomains" ] -// -// static -bool TransportSecurityState::ParseHeader(const std::string& value, - int* max_age, - bool* include_subdomains) { - DCHECK(max_age); - DCHECK(include_subdomains); - +bool TransportSecurityState::DomainState::ParseSTSHeader( + const base::Time& now, + const std::string& value) { int max_age_candidate = 0; enum ParserState { @@ -511,14 +459,18 @@ bool TransportSecurityState::ParseHeader(const std::string& value, case AFTER_MAX_AGE_EQUALS: return false; case AFTER_MAX_AGE: - *max_age = max_age_candidate; - *include_subdomains = false; + upgrade_expiry = + now + base::TimeDelta::FromSeconds(max_age_candidate); + include_subdomains = false; + upgrade_mode = MODE_FORCE_HTTPS; return true; case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER: return false; case AFTER_INCLUDE_SUBDOMAINS: - *max_age = max_age_candidate; - *include_subdomains = true; + upgrade_expiry = + now + base::TimeDelta::FromSeconds(max_age_candidate); + include_subdomains = true; + upgrade_mode = MODE_FORCE_HTTPS; return true; default: NOTREACHED(); @@ -526,370 +478,6 @@ bool TransportSecurityState::ParseHeader(const std::string& value, } } -// Side pinning and superfluous certificates: -// -// In SSLClientSocketNSS::DoVerifyCertComplete we look for certificates with a -// Subject of CN=meta. When we find one we'll currently try and parse side -// pinned key from it. -// -// A side pin is a key which can be pinned to, but also can be kept offline and -// still held by the site owner. The CN=meta certificate is just a backwards -// compatiable method of carrying a lump of bytes to the client. (We could use -// a TLS extension just as well, but it's a lot easier for admins to add extra -// certificates to the chain.) - -// A TagMap represents the simple key-value structure that we use. Keys are -// 32-bit ints. Values are byte strings. -typedef std::map<uint32, base::StringPiece> TagMap; - -// ParseTags parses a list of key-value pairs from |in| to |out| and advances -// |in| past the data. The key-value pair data is: -// u16le num_tags -// u32le tag[num_tags] -// u16le lengths[num_tags] -// ...data... -static bool ParseTags(base::StringPiece* in, TagMap *out) { - // Many part of Chrome already assume little-endian. This is just to help - // anyone who should try to port it in the future. -#if defined(__BYTE_ORDER) - // Linux check - COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian); -#elif defined(__BIG_ENDIAN__) - // Mac check - #error assumes little endian -#endif - - uint16 num_tags_16; - if (in->size() < sizeof(num_tags_16)) - return false; - - memcpy(&num_tags_16, in->data(), sizeof(num_tags_16)); - in->remove_prefix(sizeof(num_tags_16)); - unsigned num_tags = num_tags_16; - - if (in->size() < 6 * num_tags) - return false; - - const uint32* tags = reinterpret_cast<const uint32*>(in->data()); - const uint16* lens = reinterpret_cast<const uint16*>( - in->data() + 4*num_tags); - in->remove_prefix(6*num_tags); - - uint32 prev_tag = 0; - for (unsigned i = 0; i < num_tags; i++) { - size_t len = lens[i]; - uint32 tag = tags[i]; - - if (in->size() < len) - return false; - // tags must be in ascending order. - if (i > 0 && prev_tag >= tag) - return false; - (*out)[tag] = base::StringPiece(in->data(), len); - in->remove_prefix(len); - prev_tag = tag; - } - - return true; -} - -// GetTag extracts the data associated with |tag| in |tags|. -static bool GetTag(uint32 tag, const TagMap& tags, base::StringPiece* out) { - TagMap::const_iterator i = tags.find(tag); - if (i == tags.end()) - return false; - - *out = i->second; - return true; -} - -// kP256SubjectPublicKeyInfoPrefix can be prepended onto a P256 elliptic curve -// point in X9.62 format in order to make a valid SubjectPublicKeyInfo. The -// ASN.1 interpretation of these bytes is: -// -// 0:d=0 hl=2 l= 89 cons: SEQUENCE -// 2:d=1 hl=2 l= 19 cons: SEQUENCE -// 4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey -// 13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 -// 23:d=1 hl=2 l= 66 prim: BIT STRING -static const uint8 kP256SubjectPublicKeyInfoPrefix[] = { - 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, - 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, - 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, - 0x42, 0x00, -}; - -// VerifySignature returns true iff |sig| is a valid signature of -// |hash| by |pubkey|. The actual implementation is crypto library -// specific. -static bool VerifySignature(const base::StringPiece& pubkey, - const base::StringPiece& sig, - const base::StringPiece& hash); - -#if defined(USE_OPENSSL) - -static EVP_PKEY* DecodeX962P256PublicKey( - const base::StringPiece& pubkey_bytes) { - // The public key is an X9.62 encoded P256 point. - if (pubkey_bytes.size() != 1 + 2*32) - return NULL; - - std::string pubkey_spki( - reinterpret_cast<const char*>(kP256SubjectPublicKeyInfoPrefix), - sizeof(kP256SubjectPublicKeyInfoPrefix)); - pubkey_spki += pubkey_bytes.as_string(); - - EVP_PKEY* ret = NULL; - const unsigned char* der_pubkey = - reinterpret_cast<const unsigned char*>(pubkey_spki.data()); - d2i_PUBKEY(&ret, &der_pubkey, pubkey_spki.size()); - return ret; -} - -static bool VerifySignature(const base::StringPiece& pubkey, - const base::StringPiece& sig, - const base::StringPiece& hash) { - crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> secpubkey( - DecodeX962P256PublicKey(pubkey)); - if (!secpubkey.get()) - return false; - - - crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ec_key( - EVP_PKEY_get1_EC_KEY(secpubkey.get())); - if (!ec_key.get()) - return false; - - return ECDSA_verify(0, reinterpret_cast<const unsigned char*>(hash.data()), - hash.size(), - reinterpret_cast<const unsigned char*>(sig.data()), - sig.size(), ec_key.get()) == 1; -} - -#else - -// DecodeX962P256PublicKey parses an uncompressed, X9.62 format, P256 elliptic -// curve point from |pubkey_bytes| and returns it as a SECKEYPublicKey. -static SECKEYPublicKey* DecodeX962P256PublicKey( - const base::StringPiece& pubkey_bytes) { - // The public key is an X9.62 encoded P256 point. - if (pubkey_bytes.size() != 1 + 2*32) - return NULL; - - std::string pubkey_spki( - reinterpret_cast<const char*>(kP256SubjectPublicKeyInfoPrefix), - sizeof(kP256SubjectPublicKeyInfoPrefix)); - pubkey_spki += pubkey_bytes.as_string(); - - SECItem der; - memset(&der, 0, sizeof(der)); - der.data = reinterpret_cast<uint8*>(const_cast<char*>(pubkey_spki.data())); - der.len = pubkey_spki.size(); - - CERTSubjectPublicKeyInfo* spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&der); - if (!spki) - return NULL; - SECKEYPublicKey* public_key = SECKEY_ExtractPublicKey(spki); - SECKEY_DestroySubjectPublicKeyInfo(spki); - - return public_key; -} - -static bool VerifySignature(const base::StringPiece& pubkey, - const base::StringPiece& sig, - const base::StringPiece& hash) { - SECKEYPublicKey* secpubkey = DecodeX962P256PublicKey(pubkey); - if (!secpubkey) - return false; - - SECItem sigitem; - memset(&sigitem, 0, sizeof(sigitem)); - sigitem.data = reinterpret_cast<uint8*>(const_cast<char*>(sig.data())); - sigitem.len = sig.size(); - - // |decoded_sigitem| is newly allocated, as is the data that it points to. - SECItem* decoded_sigitem = DSAU_DecodeDerSigToLen( - &sigitem, SECKEY_SignatureLen(secpubkey)); - - if (!decoded_sigitem) { - SECKEY_DestroyPublicKey(secpubkey); - return false; - } - - SECItem hashitem; - memset(&hashitem, 0, sizeof(hashitem)); - hashitem.data = reinterpret_cast<unsigned char*>( - const_cast<char*>(hash.data())); - hashitem.len = hash.size(); - - SECStatus rv = PK11_Verify(secpubkey, decoded_sigitem, &hashitem, NULL); - SECKEY_DestroyPublicKey(secpubkey); - SECITEM_FreeItem(decoded_sigitem, PR_TRUE); - return rv == SECSuccess; -} - -#endif // !defined(USE_OPENSSL) - -// These are the tag values that we use. Tags are little-endian on the wire and -// these values correspond to the ASCII of the name. -static const uint32 kTagALGO = 0x4f474c41; -static const uint32 kTagP256 = 0x36353250; -static const uint32 kTagPUBK = 0x4b425550; -static const uint32 kTagSIG = 0x474953; -static const uint32 kTagSPIN = 0x4e495053; - -// static -bool TransportSecurityState::ParseSidePin( - const base::StringPiece& leaf_spki, - const base::StringPiece& in_side_info, - FingerprintVector* out_pub_key_hash) { - base::StringPiece side_info(in_side_info); - - TagMap outer; - if (!ParseTags(&side_info, &outer)) - return false; - // trailing data is not allowed - if (side_info.size()) - return false; - - base::StringPiece side_pin_bytes; - if (!GetTag(kTagSPIN, outer, &side_pin_bytes)) - return false; - - bool have_parsed_a_key = false; - uint8 leaf_spki_hash[crypto::kSHA256Length]; - bool have_leaf_spki_hash = false; - - while (side_pin_bytes.size() > 0) { - TagMap side_pin; - if (!ParseTags(&side_pin_bytes, &side_pin)) - return false; - - base::StringPiece algo, pubkey, sig; - if (!GetTag(kTagALGO, side_pin, &algo) || - !GetTag(kTagPUBK, side_pin, &pubkey) || - !GetTag(kTagSIG, side_pin, &sig)) { - return false; - } - - if (algo.size() != sizeof(kTagP256) || - 0 != memcmp(algo.data(), &kTagP256, sizeof(kTagP256))) { - // We don't support anything but P256 at the moment. - continue; - } - - if (!have_leaf_spki_hash) { - crypto::SHA256HashString( - leaf_spki.as_string(), leaf_spki_hash, sizeof(leaf_spki_hash)); - have_leaf_spki_hash = true; - } - - if (VerifySignature(pubkey, sig, base::StringPiece( - reinterpret_cast<const char*>(leaf_spki_hash), - sizeof(leaf_spki_hash)))) { - SHA1Fingerprint fpr; - base::SHA1HashBytes( - reinterpret_cast<const uint8*>(pubkey.data()), - pubkey.size(), - fpr.data); - out_pub_key_hash->push_back(fpr); - have_parsed_a_key = true; - } - } - - return have_parsed_a_key; -} - -// This function converts the binary hashes, which we store in -// |enabled_hosts_|, to a base64 string which we can include in a JSON file. -static std::string HashedDomainToExternalString(const std::string& hashed) { - std::string out; - CHECK(base::Base64Encode(hashed, &out)); - return out; -} - -// This inverts |HashedDomainToExternalString|, above. It turns an external -// string (from a JSON file) into an internal (binary) string. -static std::string ExternalStringToHashedDomain(const std::string& external) { - std::string out; - if (!base::Base64Decode(external, &out) || - out.size() != crypto::kSHA256Length) { - return std::string(); - } - - return out; -} - -static ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) { - ListValue* pins = new ListValue; - - for (FingerprintVector::const_iterator i = hashes.begin(); - i != hashes.end(); ++i) { - std::string hash_str(reinterpret_cast<const char*>(i->data), - sizeof(i->data)); - std::string b64; - base::Base64Encode(hash_str, &b64); - pins->Append(new StringValue("sha1/" + b64)); - } - - return pins; -} - -bool TransportSecurityState::Serialise(std::string* output) { - DCHECK(CalledOnValidThread()); - - DictionaryValue toplevel; - base::Time now = base::Time::Now(); - for (std::map<std::string, DomainState>::const_iterator - i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) { - DictionaryValue* state = new DictionaryValue; - state->SetBoolean("include_subdomains", i->second.include_subdomains); - state->SetDouble("created", i->second.created.ToDoubleT()); - state->SetDouble("expiry", i->second.expiry.ToDoubleT()); - state->SetDouble("dynamic_spki_hashes_expiry", - i->second.dynamic_spki_hashes_expiry.ToDoubleT()); - - switch (i->second.mode) { - case DomainState::MODE_STRICT: - state->SetString("mode", "strict"); - break; - case DomainState::MODE_SPDY_ONLY: - state->SetString("mode", "spdy-only"); - break; - case DomainState::MODE_PINNING_ONLY: - state->SetString("mode", "pinning-only"); - break; - default: - NOTREACHED() << "DomainState with unknown mode"; - delete state; - continue; - } - - state->Set("preloaded_spki_hashes", - SPKIHashesToListValue(i->second.preloaded_spki_hashes)); - - if (now < i->second.dynamic_spki_hashes_expiry) { - state->Set("dynamic_spki_hashes", - SPKIHashesToListValue(i->second.dynamic_spki_hashes)); - } - - toplevel.Set(HashedDomainToExternalString(i->first), state); - } - - base::JSONWriter::WriteWithOptions(&toplevel, - base::JSONWriter::OPTIONS_PRETTY_PRINT, - output); - return true; -} - -bool TransportSecurityState::LoadEntries(const std::string& input, - bool* dirty) { - DCHECK(CalledOnValidThread()); - - enabled_hosts_.clear(); - return Deserialise(input, dirty, &enabled_hosts_); -} - static bool AddHash(const std::string& type_and_base64, FingerprintVector* out) { SHA1Fingerprint hash; @@ -901,116 +489,7 @@ static bool AddHash(const std::string& type_and_base64, return true; } -static void SPKIHashesFromListValue(FingerprintVector* hashes, - const ListValue& pins) { - size_t num_pins = pins.GetSize(); - for (size_t i = 0; i < num_pins; ++i) { - std::string type_and_base64; - if (pins.GetString(i, &type_and_base64)) - AddHash(type_and_base64, hashes); - } -} - -// static -bool TransportSecurityState::Deserialise( - const std::string& input, - bool* dirty, - std::map<std::string, DomainState>* out) { - scoped_ptr<Value> value(base::JSONReader::Read(input)); - if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) - return false; - - DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); - const base::Time current_time(base::Time::Now()); - bool dirtied = false; - - for (DictionaryValue::key_iterator i = dict_value->begin_keys(); - i != dict_value->end_keys(); ++i) { - DictionaryValue* state; - if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &state)) - continue; - - bool include_subdomains; - std::string mode_string; - double created; - double expiry; - double dynamic_spki_hashes_expiry = 0.0; - - if (!state->GetBoolean("include_subdomains", &include_subdomains) || - !state->GetString("mode", &mode_string) || - !state->GetDouble("expiry", &expiry)) { - continue; - } - - // Don't fail if this key is not present. - (void) state->GetDouble("dynamic_spki_hashes_expiry", - &dynamic_spki_hashes_expiry); - - ListValue* pins_list = NULL; - FingerprintVector preloaded_spki_hashes; - if (state->GetList("preloaded_spki_hashes", &pins_list)) - SPKIHashesFromListValue(&preloaded_spki_hashes, *pins_list); - - FingerprintVector dynamic_spki_hashes; - if (state->GetList("dynamic_spki_hashes", &pins_list)) - SPKIHashesFromListValue(&dynamic_spki_hashes, *pins_list); - - DomainState::Mode mode; - if (mode_string == "strict") { - mode = DomainState::MODE_STRICT; - } else if (mode_string == "spdy-only") { - mode = DomainState::MODE_SPDY_ONLY; - } else if (mode_string == "pinning-only") { - mode = DomainState::MODE_PINNING_ONLY; - } else { - LOG(WARNING) << "Unknown TransportSecurityState mode string found: " - << mode_string; - continue; - } - - base::Time expiry_time = base::Time::FromDoubleT(expiry); - base::Time dynamic_spki_hashes_expiry_time = - base::Time::FromDoubleT(dynamic_spki_hashes_expiry); - base::Time created_time; - if (state->GetDouble("created", &created)) { - created_time = base::Time::FromDoubleT(created); - } else { - // We're migrating an old entry with no creation date. Make sure we - // write the new date back in a reasonable time frame. - dirtied = true; - created_time = base::Time::Now(); - } - - if (expiry_time <= current_time && - dynamic_spki_hashes_expiry_time <= current_time) { - // Make sure we dirty the state if we drop an entry. - dirtied = true; - continue; - } - - std::string hashed = ExternalStringToHashedDomain(*i); - if (hashed.empty()) { - dirtied = true; - continue; - } - - DomainState new_state; - new_state.mode = mode; - new_state.created = created_time; - new_state.expiry = expiry_time; - new_state.include_subdomains = include_subdomains; - new_state.preloaded_spki_hashes = preloaded_spki_hashes; - new_state.dynamic_spki_hashes = dynamic_spki_hashes; - new_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time; - (*out)[hashed] = new_state; - } - - *dirty = dirtied; - return true; -} - -TransportSecurityState::~TransportSecurityState() { -} +TransportSecurityState::~TransportSecurityState() {} void TransportSecurityState::DirtyNotify() { DCHECK(CalledOnValidThread()); @@ -1122,11 +601,11 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, out->include_subdomains = entries[j].include_subdomains; *ret = true; if (!entries[j].https_required) - out->mode = TransportSecurityState::DomainState::MODE_PINNING_ONLY; + out->upgrade_mode = TransportSecurityState::DomainState::MODE_DEFAULT; if (entries[j].pins.required_hashes) { const char* const* hash = entries[j].pins.required_hashes; while (*hash) { - bool ok = AddHash(*hash, &out->preloaded_spki_hashes); + bool ok = AddHash(*hash, &out->static_spki_hashes); DCHECK(ok) << " failed to parse " << *hash; hash++; } @@ -1134,7 +613,7 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, if (entries[j].pins.excluded_hashes) { const char* const* hash = entries[j].pins.excluded_hashes; while (*hash) { - bool ok = AddHash(*hash, &out->bad_preloaded_spki_hashes); + bool ok = AddHash(*hash, &out->bad_static_spki_hashes); DCHECK(ok) << " failed to parse " << *hash; hash++; } @@ -1177,7 +656,7 @@ static const struct HSTSPreload* GetHSTSPreload( // static bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host, - bool sni_available) { + bool sni_enabled) { std::string canonicalized_host = CanonicalizeHost(host); const struct HSTSPreload* entry = GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); @@ -1185,7 +664,7 @@ bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host, if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) return true; - if (sni_available) { + if (sni_enabled) { entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, kNumPreloadedSNISTS); if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) @@ -1215,16 +694,13 @@ void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) { entry->second_level_domain_name, DOMAIN_NUM_EVENTS); } -// IsPreloadedSTS returns true if the canonicalized hostname should always be -// considered to have STS enabled. -bool TransportSecurityState::IsPreloadedSTS( +bool TransportSecurityState::GetStaticDomainState( const std::string& canonicalized_host, - bool sni_available, + bool sni_enabled, DomainState* out) { DCHECK(CalledOnValidThread()); - out->preloaded = true; - out->mode = DomainState::MODE_STRICT; + out->upgrade_mode = DomainState::MODE_FORCE_HTTPS; out->include_subdomains = false; for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { @@ -1235,7 +711,6 @@ bool TransportSecurityState::IsPreloadedSTS( if (forced_hosts_.find(hashed_host) != forced_hosts_.end()) { *out = forced_hosts_[hashed_host]; out->domain = DNSDomainToString(host_sub_chunk); - out->preloaded = true; return true; } bool ret; @@ -1243,7 +718,7 @@ bool TransportSecurityState::IsPreloadedSTS( &ret)) { return ret; } - if (sni_available && + if (sni_enabled && HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i, out, &ret)) { return ret; @@ -1253,6 +728,16 @@ bool TransportSecurityState::IsPreloadedSTS( return false; } +void TransportSecurityState::AddOrUpdateEnabledHosts(std::string hashed_host, + const DomainState& state) { + enabled_hosts_[hashed_host] = state; +} + +void TransportSecurityState::AddOrUpdateForcedHosts(std::string hashed_host, + const DomainState& state) { + forced_hosts_[hashed_host] = state; +} + static std::string HashesToBase64String( const FingerprintVector& hashes) { std::vector<std::string> hashes_strs; @@ -1269,33 +754,31 @@ static std::string HashesToBase64String( } TransportSecurityState::DomainState::DomainState() - : mode(MODE_STRICT), + : upgrade_mode(MODE_FORCE_HTTPS), created(base::Time::Now()), - include_subdomains(false), - preloaded(false) { + include_subdomains(false) { } TransportSecurityState::DomainState::~DomainState() { } bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( - const FingerprintVector& hashes) { - - if (HashesIntersect(bad_preloaded_spki_hashes, hashes)) { + const FingerprintVector& hashes) const { + 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_preloaded_spki_hashes); + << HashesToBase64String(bad_static_spki_hashes); return false; } - if (!(dynamic_spki_hashes.empty() && preloaded_spki_hashes.empty()) && + if (!(dynamic_spki_hashes.empty() && static_spki_hashes.empty()) && !HashesIntersect(dynamic_spki_hashes, hashes) && - !HashesIntersect(preloaded_spki_hashes, hashes)) { + !HashesIntersect(static_spki_hashes, hashes)) { LOG(ERROR) << "Rejecting public key chain for domain " << domain << ". Validated chain: " << HashesToBase64String(hashes) << ", expected: " << HashesToBase64String(dynamic_spki_hashes) - << " or: " << HashesToBase64String(preloaded_spki_hashes); + << " or: " << HashesToBase64String(static_spki_hashes); return false; } @@ -1303,20 +786,21 @@ bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( return true; } -bool TransportSecurityState::DomainState::IsMoreStrict( - const TransportSecurityState::DomainState& other) { - if (this->dynamic_spki_hashes.empty() && !other.dynamic_spki_hashes.empty()) - return false; - - if (!this->include_subdomains && other.include_subdomains) - return false; +bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS() const { + return upgrade_mode == MODE_FORCE_HTTPS; +} +bool TransportSecurityState::DomainState::Equals( + const DomainState& other) const { + // TODO(palmer): Implement this + (void) other; return true; } -bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS() - const { - return mode == MODE_STRICT; +bool TransportSecurityState::DomainState::HasPins() const { + return static_spki_hashes.size() > 0 || + bad_static_spki_hashes.size() > 0 || + dynamic_spki_hashes.size() > 0; } } // namespace diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index d2c8e2b..b013170 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -8,6 +8,7 @@ #include <map> #include <string> +#include <utility> #include <vector> #include "base/basictypes.h" @@ -22,255 +23,271 @@ namespace net { class SSLInfo; -typedef std::vector<SHA1Fingerprint> FingerprintVector; - -// TransportSecurityState +// Tracks which hosts have enabled strict transport security and/or public +// key pins. +// +// This object manages the in-memory store. Register a Delegate with +// |SetDelegate| to persist the state to disk. // -// Tracks which hosts have enabled *-Transport-Security. This object manages -// the in-memory store. A separate object must register itself with this object -// in order to persist the state to disk. +// HTTP strict transport security (HSTS) is defined in +// http://tools.ietf.org/html/ietf-websec-strict-transport-sec, and +// HTTP-based dynamic public key pinning (HPKP) is defined in +// http://tools.ietf.org/html/ietf-websec-key-pinning. class NET_EXPORT TransportSecurityState : NON_EXPORTED_BASE(public base::NonThreadSafe) { public: - // If non-empty, |hsts_hosts| is a JSON-formatted string to treat as if it - // were a built-in entry (same format as persisted metadata in the - // TransportSecurityState file). - explicit TransportSecurityState(const std::string& hsts_hosts); + class Delegate { + public: + // This function may not block and may be called with internal locks held. + // Thus it must not reenter the TransportSecurityState object. + virtual void StateIsDirty(TransportSecurityState* state) = 0; + + protected: + virtual ~Delegate() {} + }; + + TransportSecurityState(); ~TransportSecurityState(); - // A DomainState is the information that we persist about a given domain. - struct NET_EXPORT DomainState { - enum Mode { - // Strict mode implies: - // * We generate internal redirects from HTTP -> HTTPS. - // * Certificate issues are fatal. - MODE_STRICT = 0, - // This used to be opportunistic HTTPS, but we removed support. - MODE_OPPORTUNISTIC_REMOVED = 1, - // SPDY_ONLY (aka X-Bodge-Transport-Security) is a hopefully temporary - // measure. It implies: - // * We'll request HTTP URLs over HTTPS iff we have SPDY support. - // * Certificate issues are fatal. - MODE_SPDY_ONLY = 2, - // Pinning means there are no HTTP -> HTTPS redirects, however certificate - // issues are still fatal and there may be public key pins. - MODE_PINNING_ONLY = 3, + // A DomainState describes the transport security state (required upgrade + // to HTTPS, and/or any public key pins). + class NET_EXPORT DomainState { + public: + enum UpgradeMode { + // These numbers must match those in hsts_view.js, function modeToString. + MODE_FORCE_HTTPS = 0, + MODE_DEFAULT = 1, }; DomainState(); ~DomainState(); + // Parses |value| as a Public-Key-Pins header. If successful, returns true + // and updates the |dynamic_spki_hashes| and |dynamic_spki_hashes_expiry| + // fields; otherwise, returns false without updating any fields. + // Interprets the max-age directive relative to |now|. + bool ParsePinsHeader(const base::Time& now, + const std::string& value, + const SSLInfo& ssl_info); + + // Parses |value| as a Strict-Transport-Security header. If successful, + // returns true and updates the |upgrade_mode|, |upgrade_expiry| and + // |include_subdomains| fields; otherwise, returns false without updating + // any fields. Interprets the max-age directive relative to |now|. + bool ParseSTSHeader(const base::Time& now, const std::string& value); + // Takes a set of SubjectPublicKeyInfo |hashes| and returns true if: - // 1) |bad_preloaded_spki_hashes| does not intersect |hashes|; AND - // 2) Both |preloaded_spki_hashes| and |dynamic_spki_hashes| are empty + // 1) |bad_static_spki_hashes| does not intersect |hashes|; AND + // 2) Both |static_spki_hashes| and |dynamic_spki_hashes| are empty // or at least one of them intersects |hashes|. // - // |{dynamic,preloaded}_spki_hashes| contain trustworthy public key - // hashes, any one of which is sufficient to validate the certificate - // chain in question. The public keys could be of a root CA, intermediate - // CA, or leaf certificate, depending on the security vs. disaster - // recovery tradeoff selected. (Pinning only to leaf certifiates increases + // |{dynamic,static}_spki_hashes| contain trustworthy public key hashes, + // any one of which is sufficient to validate the certificate chain in + // question. The public keys could be of a root CA, intermediate CA, or + // leaf certificate, depending on the security vs. disaster recovery + // tradeoff selected. (Pinning only to leaf certifiates increases // security because you no longer trust any CAs, but it hampers disaster // recovery because you can't just get a new certificate signed by the // CA.) // - // |bad_preloaded_spki_hashes| contains public keys that we don't want to + // |bad_static_spki_hashes| contains public keys that we don't want to // trust. - bool IsChainOfPublicKeysPermitted(const FingerprintVector& hashes); + bool IsChainOfPublicKeysPermitted(const FingerprintVector& hashes) const; - // Returns true if |this| describes a more strict policy than |other|. - // Used to see if a dynamic DomainState should override a preloaded one. - bool IsMoreStrict(const DomainState& other); + // Returns true if any of the FingerprintVectors |static_spki_hashes|, + // |bad_static_spki_hashes|, or |dynamic_spki_hashes| contains any + // items. + bool HasPins() const; // ShouldRedirectHTTPToHTTPS returns true iff, given the |mode| of this // DomainState, HTTP requests should be internally redirected to HTTPS. bool ShouldRedirectHTTPToHTTPS() const; - Mode mode; - base::Time created; // when this host entry was first created - base::Time expiry; // the absolute time (UTC) when this record expires - bool include_subdomains; // subdomains included? + bool Equals(const DomainState& other) const; + + UpgradeMode upgrade_mode; + + // The absolute time (UTC) when this DomainState was first created. + // + // Static entries do not have a created time. + base::Time created; + + // The absolute time (UTC) when the |upgrade_mode|, if set to + // UPGRADE_ALWAYS, downgrades to UPGRADE_NEVER. + base::Time upgrade_expiry; - // Optional; hashes of preloaded "pinned" SubjectPublicKeyInfos. Unless - // both are empty, at least one of |preloaded_spki_hashes| and + // Are subdomains subject to this DomainState? + // + // TODO(palmer): Decide if we should have separate |pin_subdomains| and + // |upgrade_subdomains|. Alternately, and perhaps better, is to separate + // DomainState into UpgradeState and PinState (requiring also changing the + // serialization format?). + bool include_subdomains; + + // Optional; hashes of static pinned SubjectPublicKeyInfos. Unless both + // are empty, at least one of |static_spki_hashes| and // |dynamic_spki_hashes| MUST intersect with the set of SPKIs in the TLS // server's certificate chain. // - // |dynamic_spki_hashes| take precedence over |preloaded_spki_hashes|. - // That is, when performing pin validation, first check dynamic and then - // check preloaded. - FingerprintVector preloaded_spki_hashes; + // |dynamic_spki_hashes| take precedence over |static_spki_hashes|. + // That is, |IsChainOfPublicKeysPermitted| first checks dynamic pins and + // then checks static pins. + FingerprintVector static_spki_hashes; - // Optional; hashes of dynamically pinned SubjectPublicKeyInfos. (They - // could be set e.g. by an HTTP header or by a superfluous certificate.) + // Optional; hashes of dynamically pinned SubjectPublicKeyInfos. FingerprintVector dynamic_spki_hashes; // The absolute time (UTC) when the |dynamic_spki_hashes| expire. base::Time dynamic_spki_hashes_expiry; - // The max-age directive of the Public-Key-Pins header as parsed. Do not - // persist this; it is only for testing. TODO(palmer): Therefore, get rid - // of it and find a better way to test. - int max_age; - - // Optional; hashes of preloaded known-bad SubjectPublicKeyInfos which + // Optional; hashes of static known-bad SubjectPublicKeyInfos which // MUST NOT intersect with the set of SPKIs in the TLS server's // certificate chain. - FingerprintVector bad_preloaded_spki_hashes; + FingerprintVector bad_static_spki_hashes; - // The following members are not valid when stored in |enabled_hosts_|. - bool preloaded; // is this a preloaded entry? - std::string domain; // the domain which matched + // The following members are not valid when stored in |enabled_hosts_|: + + // The domain which matched during a search for this DomainState entry. + // Updated by |GetDomainState| and |GetStaticDomainState|. + std::string domain; }; - class Delegate { + class Iterator { public: - // This function may not block and may be called with internal locks held. - // Thus it must not reenter the TransportSecurityState object. - virtual void StateIsDirty(TransportSecurityState* state) = 0; - - protected: - virtual ~Delegate() {} + explicit Iterator(const TransportSecurityState& state) + : iterator_(state.enabled_hosts_.begin()), + end_(state.enabled_hosts_.end()) { + } + ~Iterator() {} + + bool HasNext() const { return iterator_ != end_; } + void Advance() { ++iterator_; } + const std::string& hostname() const { return iterator_->first; } + const DomainState& domain_state() const { return iterator_->second; } + + private: + std::map<std::string, DomainState>::const_iterator iterator_; + std::map<std::string, DomainState>::const_iterator end_; }; + // Assign a |Delegate| for persisting the transport security state. If + // |NULL|, state will not be persisted. Caller owns |delegate|. void SetDelegate(Delegate* delegate); - // Enable TransportSecurity for |host|. + // Enable TransportSecurity for |host|. |state| supercedes any previous + // state for the |host|, including static entries. + // + // The new state for |host| is persisted using the Delegate (if any). void EnableHost(const std::string& host, const DomainState& state); - // Delete any entry for |host|. If |host| doesn't have an exact entry then no - // action is taken. Returns true iff an entry was deleted. + // Delete any entry for |host|. If |host| doesn't have an exact entry then + // no action is taken. Does not delete static entries. Returns true iff an + // entry was deleted. + // + // The new state for |host| is persisted using the Delegate (if any). bool DeleteHost(const std::string& host); - // Returns true if |host| has TransportSecurity enabled. Before operating - // on this result, consult |result->mode|, as the expected behavior of - // TransportSecurity can significantly differ based on mode. + // Deletes all records created since a given time. + void DeleteSince(const base::Time& time); + + // Returns true and updates |*result| iff there is a DomainState for + // |host|. + // + // If |sni_enabled| is true, searches the static pins defined for + // SNI-using hosts as well as the rest of the pins. // - // If |sni_available| is true, searches the preloads defined for SNI-using - // hosts as well as the usual preload list. + // If |host| matches both an exact entry and is a subdomain of another + // entry, the exact match determines the return value. // - // Note that |*result| is always overwritten on every call. - // TODO(palmer): Only update |*result| on success. - bool GetDomainState(DomainState* result, - const std::string& host, - bool sni_available); - - // Returns true if there are any certificates pinned for |host|. - // If so, updates the |preloaded_spki_hashes|, |dynamic_spki_hashes|, and - // |bad_preloaded_spki_hashes| fields of |*result| with the pins. + // Note that this method is not const because it opportunistically removes + // entries that have expired. + bool GetDomainState(const std::string& host, + bool sni_enabled, + DomainState* result); + + // Returns true and updates |*result| iff there is a static DomainState for + // |host|. // - // Note that |*result| is always overwritten on every call, regardless of - // whether or not pins are enabled. + // |GetStaticDomainState| is identical to |GetDomainState| except that it + // searches only the statically-defined transport security state, ignoring + // all dynamically-added DomainStates. // - // If |sni_available| is true, searches the preloads defined for SNI-using - // hosts as well as the usual preload list. + // If |sni_enabled| is true, searches the static pins defined for + // SNI-using hosts as well as the rest of the pins. // - // TODO(palmer): Only update |*result| if pins exist. - bool HasPinsForHost(DomainState* result, - const std::string& host, - bool sni_available); - - // Returns true and updates |*result| if there is any |DomainState| - // metadata for |host| in the local TransportSecurityState database; - // returns false otherwise. TODO(palmer): Unlike the other - // TransportSecurityState lookup functions in this class (e.g - // |HasPinsForHost|, |GetDomainState|), |*result| is updated iff metadata - // is found. The other functions are buggy and will be fixed to behave - // like this one. + // If |host| matches both an exact entry and is a subdomain of another + // entry, the exact match determines the return value. // - // If |sni_available| is true, searches the preloads defined for SNI-using - // hosts as well as the usual preload list. - bool HasMetadata(DomainState* result, - const std::string& host, - bool sni_available); - - // Returns true if we have a preloaded certificate pin for the |host| and if - // its set of required certificates is the set we expect for Google + // Note that this method is not const because it opportunistically removes + // entries that have expired. + bool GetStaticDomainState(const std::string& host, + bool sni_enabled, + DomainState* result); + + // Removed all DomainState records. + void Clear() { enabled_hosts_.clear(); } + + // Inserts |state| into |enabled_hosts_| under the key |hashed_host|. + // |hashed_host| is already in the internal representation + // HashHost(CanonicalizeHost(host)); thus, most callers will use + // |EnableHost|. + void AddOrUpdateEnabledHosts(std::string hashed_host, + const DomainState& state); + + // Inserts |state| into |forced_hosts_| under the key |hashed_host|. + // |hashed_host| is already in the internal representation + // HashHost(CanonicalizeHost(host)); thus, most callers will use + // |EnableHost|. + void AddOrUpdateForcedHosts(std::string hashed_host, + const DomainState& state); + + // Returns true iff we have any static public key pins for the |host| and + // iff its set of required pins is the set we expect for Google // properties. // - // If |sni_available| is true, searches the preloads defined for SNI-using - // hosts as well as the usual preload list. + // If |sni_enabled| is true, searches the static pins defined for + // SNI-using hosts as well as the rest of the pins. // - // Note that like HasMetadata, if |host| matches both an exact entry and is a - // subdomain of another entry, the exact match determines the return value. + // If |host| matches both an exact entry and is a subdomain of another + // entry, the exact match determines the return value. static bool IsGooglePinnedProperty(const std::string& host, - bool sni_available); - - // Reports UMA statistics upon public key pin failure. Reports only down to - // the second-level domain of |host| (e.g. google.com if |host| is - // mail.google.com), and only if |host| is a preloaded STS host. - static void ReportUMAOnPinFailure(const std::string& host); - - // Parses |cert|'s Subject Public Key Info structure, hashes it, and writes - // the hash into |spki_hash|. Returns true on parse success, false on - // failure. - static bool GetPublicKeyHash(const X509Certificate& cert, - SHA1Fingerprint* spki_hash); - - // Decodes a pin string |value| (e.g. "sha1/hvfkN/qlp/zhXR3cuerq6jd2Z7g=") - // and populates |out|. - static bool ParsePin(const std::string& value, SHA1Fingerprint* out); - - // Deletes all records created since a given time. - void DeleteSince(const base::Time& time); + bool sni_enabled); - // Parses |value| as a Public-Key-Pins header. If successful, returns |true| - // and updates the |dynamic_spki_hashes| and |dynamic_spki_hashes_expiry| - // fields of |*state|; otherwise, returns |false| without updating |*state|. - static bool ParsePinsHeader(const std::string& value, - const SSLInfo& ssl_info, - DomainState* state); - - // Returns |true| if |value| parses as a valid *-Transport-Security - // header value. The values of max-age and and includeSubDomains are - // returned in |max_age| and |include_subdomains|, respectively. The out - // parameters are not modified if the function returns |false|. - static bool ParseHeader(const std::string& value, - int* max_age, - bool* include_subdomains); - - // ParseSidePin attempts to parse a side pin from |side_info| which signs the - // SubjectPublicKeyInfo in |leaf_spki|. A side pin is a way for a site to - // sign their public key with a key that is offline but still controlled by - // them. If successful, the hash of the public key used to sign |leaf_spki| - // is put into |out_pub_key_hash|. - static bool ParseSidePin(const base::StringPiece& leaf_spki, - const base::StringPiece& side_info, - FingerprintVector* out_pub_key_hash); - - bool Serialise(std::string* output); - // Existing non-preloaded entries are cleared and repopulated from the - // passed JSON string. - bool LoadEntries(const std::string& state, bool* dirty); + // Decodes a pin string |value| (e.g. "sha1/hvfkN/qlp/zhXR3cuerq6jd2Z7g="). + // If parsing succeeded, updates |*out| and returns true; otherwise returns + // false without updating |*out|. + static bool ParsePin(const std::string& value, Fingerprint* out); // The maximum number of seconds for which we'll cache an HSTS request. static const long int kMaxHSTSAgeSecs; - private: - FRIEND_TEST_ALL_PREFIXES(TransportSecurityStateTest, IsPreloaded); + // Converts |hostname| from dotted form ("www.google.com") to the form + // used in DNS: "\x03www\x06google\x03com", lowercases that, and returns + // the result. + static std::string CanonicalizeHost(const std::string& hostname); + + // Send an UMA report on pin validation failure, if the host is in a + // statically-defined list of domains. + // + // TODO(palmer): This doesn't really belong here, and should be moved into + // the exactly one call site. This requires unifying |struct HSTSPreload| + // (an implementation detail of this class) with a more generic + // representation of first-class DomainStates, and exposing the preloads + // to the caller with |GetStaticDomainState|. + static void ReportUMAOnPinFailure(const std::string& host); - // If we have a callback configured, call it to let our serialiser know that - // our state is dirty. + private: + // If a Delegate is present, notify it that the internal state has + // changed. void DirtyNotify(); - bool IsPreloadedSTS(const std::string& canonicalized_host, - bool sni_available, - DomainState* out); - - static std::string CanonicalizeHost(const std::string& host); - static bool Deserialise(const std::string& state, - bool* dirty, - std::map<std::string, DomainState>* out); - - // The set of hosts that have enabled TransportSecurity. The keys here - // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form - // ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com" + + // The set of hosts that have enabled TransportSecurity. std::map<std::string, DomainState> enabled_hosts_; - // These hosts are extra rules to treat as built-in, passed in the - // constructor (typically originating from the command line). + // Extra entries, provided by the user at run-time, to treat as if they + // were static. std::map<std::string, DomainState> forced_hosts_; - // Our delegate who gets notified when we are dirtied, or NULL. Delegate* delegate_; DISALLOW_COPY_AND_ASSIGN(TransportSecurityState); diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index 9956c27..df3e528 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -43,84 +43,72 @@ class TransportSecurityStateTest : public testing::Test { }; TEST_F(TransportSecurityStateTest, BogusHeaders) { - int max_age = 42; - bool include_subdomains = false; - - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "abc", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " abc", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " abc ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age=", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age =", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age= ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age = ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age = xy", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - " max-age = 3488a923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488a923 ", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-ag=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-aged=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age==3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "amax-age=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=-3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923;", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 e", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923=includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomainx", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain=", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain=true", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomainsx", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomains x", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=34889.23 includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(TransportSecurityState::ParseHeader( - "max-age=34889 includesubdomains", &max_age, &include_subdomains)); - - EXPECT_EQ(max_age, 42); - EXPECT_FALSE(include_subdomains); + TransportSecurityState::DomainState state; + base::Time now = base::Time::Now(); + + EXPECT_FALSE(state.ParseSTSHeader(now, "")); + EXPECT_FALSE(state.ParseSTSHeader(now, " ")); + EXPECT_FALSE(state.ParseSTSHeader(now, "abc")); + EXPECT_FALSE(state.ParseSTSHeader(now, " abc")); + EXPECT_FALSE(state.ParseSTSHeader(now, " abc ")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age ")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age=")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age =")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age= ")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = ")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = xy")); + EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = 3488a923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488a923 ")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-ag=3488923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-aged=3488923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age==3488923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "amax-age=3488923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=-3488923")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923;")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 e")); + EXPECT_FALSE(state.ParseSTSHeader( + now, "max-age=3488923 includesubdomain")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923includesubdomains")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923=includesubdomains")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainx")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomain=")); + EXPECT_FALSE(state.ParseSTSHeader( + now, "max-age=3488923 includesubdomain=true")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainsx")); + EXPECT_FALSE(state.ParseSTSHeader( + now, "max-age=3488923 includesubdomains x")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889.23 includesubdomains")); + EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889 includesubdomains")); + + // Check that |state| was not updated by expecting the default + // values for its predictable fields. + EXPECT_EQ(state.upgrade_mode, + TransportSecurityState::DomainState::MODE_FORCE_HTTPS); + EXPECT_FALSE(state.include_subdomains); +} + +static bool GetPublicKeyHash(const net::X509Certificate::OSCertHandle& cert, + SHA1Fingerprint* fingerprint) { + std::string der_bytes; + if (!net::X509Certificate::GetDEREncoded(cert, &der_bytes)) + return false; + + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) + return false; + + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(spki.data()), + spki.size(), fingerprint->data); + return true; } static std::string GetPinFromCert(X509Certificate* cert) { SHA1Fingerprint spki_hash; - if (!TransportSecurityState::GetPublicKeyHash(*cert, &spki_hash)) - return ""; + EXPECT_TRUE(GetPublicKeyHash(cert->os_cert_handle(), &spki_hash)); + std::string base64; base::Base64Encode(base::StringPiece(reinterpret_cast<char*>(spki_hash.data), sizeof(spki_hash.data)), @@ -130,142 +118,132 @@ static std::string GetPinFromCert(X509Certificate* cert) { TEST_F(TransportSecurityStateTest, BogusPinsHeaders) { TransportSecurityState::DomainState state; - state.max_age = 42; SSLInfo ssl_info; ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem"); std::string good_pin = GetPinFromCert(ssl_info.cert); + base::Time now = base::Time::Now(); // The backup pin is fake --- it just has to not be in the chain. std::string backup_pin = "pin-sha1=" + HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g="); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "abc", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " abc", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " abc ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age=", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age =", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age= ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age = ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age = xy", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - " max-age = 3488a923", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=3488a923 ", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( + EXPECT_FALSE(state.ParsePinsHeader(now, "", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "abc", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " abc", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " abc ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age=", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age =", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age= ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = xy", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader( + now, + " max-age = 3488a923", + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488a923 ", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-ag=3488923pins=" + good_pin + "," + backup_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-aged=3488923" + backup_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-aged=3488923; " + backup_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923" + backup_pin, + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + backup_pin, + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + backup_pin + ";" + backup_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + good_pin + ";" + good_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-aged=3488923; " + good_pin, - ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age==3488923", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "amax-age=3488923", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=-3488923", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=3488923;", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=3488923 e", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=3488923 includesubdomain", ssl_info, &state)); - EXPECT_FALSE(TransportSecurityState::ParsePinsHeader( - "max-age=34889.23", ssl_info, &state)); - - EXPECT_EQ(state.max_age, 42); + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + good_pin, + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age==3488923", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "amax-age=3488923", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=-3488923", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923;", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923 e", ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader( + now, + "max-age=3488923 includesubdomain", + ssl_info)); + EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=34889.23", ssl_info)); + + // Check that |state| was not updated by expecting the default + // values for its predictable fields. + EXPECT_EQ(state.upgrade_mode, + TransportSecurityState::DomainState::MODE_FORCE_HTTPS); + EXPECT_FALSE(state.include_subdomains); } -TEST_F(TransportSecurityStateTest, ValidHeaders) { - int max_age = 42; - bool include_subdomains = true; - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - "max-age=243", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 243); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - " Max-agE = 567", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 567); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - " mAx-aGe = 890 ", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 890); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - "max-age=123;incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 123); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - "max-age=394082; incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 394082); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - "max-age=39408299 ;incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l)); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - "max-age=394082038 ; incLudesUbdOmains", &max_age, - &include_subdomains)); - EXPECT_EQ(max_age, - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l)); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( - " max-age=0 ; incLudesUbdOmains ", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 0); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(TransportSecurityState::ParseHeader( +TEST_F(TransportSecurityStateTest, ValidSTSHeaders) { + TransportSecurityState::DomainState state; + base::Time expiry; + base::Time now = base::Time::Now(); + + EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=243")); + expiry = now + base::TimeDelta::FromSeconds(243); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_FALSE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader(now, " Max-agE = 567")); + expiry = now + base::TimeDelta::FromSeconds(567); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_FALSE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader(now, " mAx-aGe = 890 ")); + expiry = now + base::TimeDelta::FromSeconds(890); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_FALSE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=123;incLudesUbdOmains")); + expiry = now + base::TimeDelta::FromSeconds(123); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=394082; incLudesUbdOmains")); + expiry = now + base::TimeDelta::FromSeconds(394082); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader( + now, "max-age=39408299 ;incLudesUbdOmains")); + expiry = now + base::TimeDelta::FromSeconds( + std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l)); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader( + now, "max-age=394082038 ; incLudesUbdOmains")); + expiry = now + base::TimeDelta::FromSeconds( + std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l)); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader( + now, " max-age=0 ; incLudesUbdOmains ")); + expiry = now + base::TimeDelta::FromSeconds(0); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); + + EXPECT_TRUE(state.ParseSTSHeader( + now, " max-age=999999999999999999999999999999999999999999999 ;" - " incLudesUbdOmains ", - &max_age, &include_subdomains)); - EXPECT_EQ(max_age, TransportSecurityState::kMaxHSTSAgeSecs); - EXPECT_TRUE(include_subdomains); + " incLudesUbdOmains ")); + expiry = now + base::TimeDelta::FromSeconds( + TransportSecurityState::kMaxHSTSAgeSecs); + EXPECT_EQ(expiry, state.upgrade_expiry); + EXPECT_TRUE(state.include_subdomains); } TEST_F(TransportSecurityStateTest, ValidPinsHeaders) { TransportSecurityState::DomainState state; - state.max_age = 42; + base::Time expiry; + base::Time now = base::Time::Now(); // Set up a realistic SSLInfo with a realistic cert chain. FilePath certs_dir = GetTestCertsDirectory(); @@ -309,211 +287,165 @@ TEST_F(TransportSecurityStateTest, ValidPinsHeaders) { std::string backup_pin = "pin-sha1=" + HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g="); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, "max-age=243; " + good_pin + ";" + backup_pin, - ssl_info, &state)); - EXPECT_EQ(state.max_age, 243); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(243); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, " " + good_pin + "; " + backup_pin + " ; Max-agE = 567", - ssl_info, &state)); - EXPECT_EQ(state.max_age, 567); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(567); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, good_pin + ";" + backup_pin + " ; mAx-aGe = 890 ", - ssl_info, &state)); - EXPECT_EQ(state.max_age, 890); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(890); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, good_pin + ";" + backup_pin + "; max-age=123;IGNORED;", - ssl_info, &state)); - EXPECT_EQ(state.max_age, 123); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(123); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, "max-age=394082;" + backup_pin + ";" + good_pin + "; ", - ssl_info, &state)); - EXPECT_EQ(state.max_age, 394082); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(394082); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, "max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ", - ssl_info, &state)); - EXPECT_EQ(state.max_age, - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l)); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds( + std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l)); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, "max-age=39408038 ; cybers=39408038 ; " + good_pin + ";" + backup_pin + "; ", - ssl_info, &state)); - EXPECT_EQ(state.max_age, - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l)); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds( + std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l)); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, " max-age=0 ; " + good_pin + ";" + backup_pin, - ssl_info, &state)); - EXPECT_EQ(state.max_age, 0); + ssl_info)); + expiry = now + base::TimeDelta::FromSeconds(0); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); - EXPECT_TRUE(TransportSecurityState::ParsePinsHeader( + EXPECT_TRUE(state.ParsePinsHeader( + now, " max-age=999999999999999999999999999999999999999999999 ; " + backup_pin + ";" + good_pin + "; ", - ssl_info, &state)); - EXPECT_EQ(state.max_age, TransportSecurityState::kMaxHSTSAgeSecs); + ssl_info)); + expiry = now + + base::TimeDelta::FromSeconds(TransportSecurityState::kMaxHSTSAgeSecs); + EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry); } TEST_F(TransportSecurityStateTest, SimpleMatches) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); - domain_state.expiry = expiry; + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); + domain_state.upgrade_expiry = expiry; state.EnableHost("yahoo.com", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); + EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state)); } TEST_F(TransportSecurityStateTest, MatchesCase1) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); - domain_state.expiry = expiry; + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); + domain_state.upgrade_expiry = expiry; state.EnableHost("YAhoo.coM", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); + EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state)); } TEST_F(TransportSecurityStateTest, MatchesCase2) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - EXPECT_FALSE(state.GetDomainState(&domain_state, "YAhoo.coM", true)); - domain_state.expiry = expiry; + EXPECT_FALSE(state.GetDomainState("YAhoo.coM", true, &domain_state)); + domain_state.upgrade_expiry = expiry; state.EnableHost("yahoo.com", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "YAhoo.coM", true)); + EXPECT_TRUE(state.GetDomainState("YAhoo.coM", true, &domain_state)); } TEST_F(TransportSecurityStateTest, SubdomainMatches) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); - domain_state.expiry = expiry; + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); + domain_state.upgrade_expiry = expiry; domain_state.include_subdomains = true; state.EnableHost("yahoo.com", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); - EXPECT_TRUE(state.GetDomainState(&domain_state, "foo.yahoo.com", true)); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "foo.bar.yahoo.com", - true)); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "foo.bar.baz.yahoo.com", - true)); - EXPECT_FALSE(state.GetDomainState(&domain_state, "com", true)); -} - -TEST_F(TransportSecurityStateTest, Serialise1) { - TransportSecurityState state(""); - std::string output; - bool dirty; - state.Serialise(&output); - EXPECT_TRUE(state.LoadEntries(output, &dirty)); - EXPECT_FALSE(dirty); -} - -TEST_F(TransportSecurityStateTest, Serialise2) { - TransportSecurityState state(""); - TransportSecurityState::DomainState domain_state; - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); - domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT; - domain_state.expiry = expiry; - domain_state.include_subdomains = true; - state.EnableHost("yahoo.com", domain_state); - - std::string output; - bool dirty; - state.Serialise(&output); - EXPECT_TRUE(state.LoadEntries(output, &dirty)); - - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); - EXPECT_EQ(domain_state.mode, - TransportSecurityState::DomainState::MODE_STRICT); - EXPECT_TRUE(state.GetDomainState(&domain_state, "foo.yahoo.com", true)); - EXPECT_EQ(domain_state.mode, - TransportSecurityState::DomainState::MODE_STRICT); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "foo.bar.yahoo.com", - true)); - EXPECT_EQ(domain_state.mode, - TransportSecurityState::DomainState::MODE_STRICT); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "foo.bar.baz.yahoo.com", - true)); - EXPECT_EQ(domain_state.mode, - TransportSecurityState::DomainState::MODE_STRICT); - EXPECT_FALSE(state.GetDomainState(&domain_state, "com", true)); + EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state)); + EXPECT_TRUE(state.GetDomainState("foo.yahoo.com", true, &domain_state)); + EXPECT_TRUE(state.GetDomainState("foo.bar.yahoo.com", true, &domain_state)); + EXPECT_TRUE(state.GetDomainState("foo.bar.baz.yahoo.com", true, + &domain_state)); + EXPECT_FALSE(state.GetDomainState("com", true, &domain_state)); } TEST_F(TransportSecurityStateTest, DeleteSince) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); const base::Time older = current_time - base::TimeDelta::FromSeconds(1000); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); - domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT; - domain_state.expiry = expiry; + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); + domain_state.upgrade_mode = + TransportSecurityState::DomainState::MODE_FORCE_HTTPS; + domain_state.upgrade_expiry = expiry; state.EnableHost("yahoo.com", domain_state); state.DeleteSince(expiry); - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); + EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state)); state.DeleteSince(older); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); } TEST_F(TransportSecurityStateTest, DeleteHost) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT; - domain_state.expiry = expiry; + domain_state.upgrade_mode = + TransportSecurityState::DomainState::MODE_FORCE_HTTPS; + domain_state.upgrade_expiry = expiry; state.EnableHost("yahoo.com", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "yahoo.com", true)); - EXPECT_FALSE(state.GetDomainState(&domain_state, "example.com", true)); + EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state)); + EXPECT_FALSE(state.GetDomainState("example.com", true, &domain_state)); EXPECT_TRUE(state.DeleteHost("yahoo.com")); - EXPECT_FALSE(state.GetDomainState(&domain_state, "yahoo.com", true)); -} - -TEST_F(TransportSecurityStateTest, SerialiseOld) { - TransportSecurityState state(""); - // This is an old-style piece of transport state JSON, which has no creation - // date. - std::string output = - "{ " - "\"NiyD+3J1r6z1wjl2n1ALBu94Zj9OsEAMo0kCN8js0Uk=\": {" - "\"expiry\": 1266815027.983453, " - "\"include_subdomains\": false, " - "\"mode\": \"strict\" " - "}" - "}"; - bool dirty; - EXPECT_TRUE(state.LoadEntries(output, &dirty)); - EXPECT_TRUE(dirty); + EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state)); } TEST_F(TransportSecurityStateTest, IsPreloaded) { - TransportSecurityState state(""); - const std::string paypal = TransportSecurityState::CanonicalizeHost("paypal.com"); const std::string www_paypal = @@ -527,67 +459,77 @@ TEST_F(TransportSecurityStateTest, IsPreloaded) { const std::string aypal = TransportSecurityState::CanonicalizeHost("aypal.com"); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - EXPECT_FALSE(state.IsPreloadedSTS(paypal, true, &domain_state)); - EXPECT_TRUE(state.IsPreloadedSTS(www_paypal, true, &domain_state)); + + EXPECT_FALSE(state.GetStaticDomainState(paypal, true, &domain_state)); + EXPECT_TRUE(state.GetStaticDomainState(www_paypal, true, &domain_state)); EXPECT_FALSE(domain_state.include_subdomains); - EXPECT_FALSE(state.IsPreloadedSTS(a_www_paypal, true, &domain_state)); - EXPECT_FALSE(state.IsPreloadedSTS(abc_paypal, true, &domain_state)); - EXPECT_FALSE(state.IsPreloadedSTS(example, true, &domain_state)); - EXPECT_FALSE(state.IsPreloadedSTS(aypal, true, &domain_state)); + EXPECT_FALSE(state.GetStaticDomainState(a_www_paypal, true, &domain_state)); + EXPECT_FALSE(state.GetStaticDomainState(abc_paypal, true, &domain_state)); + EXPECT_FALSE(state.GetStaticDomainState(example, true, &domain_state)); + EXPECT_FALSE(state.GetStaticDomainState(aypal, true, &domain_state)); } TEST_F(TransportSecurityStateTest, PreloadedDomainSet) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; // The domain wasn't being set, leading to a blank string in the // chrome://net-internals/#hsts UI. So test that. - EXPECT_TRUE(state.GetDomainState(&domain_state, - "market.android.com", - true)); + EXPECT_TRUE(state.GetDomainState("market.android.com", true, &domain_state)); EXPECT_EQ(domain_state.domain, "market.android.com"); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "sub.market.android.com", - true)); + EXPECT_TRUE(state.GetDomainState("sub.market.android.com", true, + &domain_state)); EXPECT_EQ(domain_state.domain, "market.android.com"); } static bool ShouldRedirect(const char* hostname) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - return state.GetDomainState(&domain_state, hostname, true /* SNI ok */) && + return state.GetDomainState(hostname, true /* SNI ok */, &domain_state) && domain_state.ShouldRedirectHTTPToHTTPS(); } -static bool HasState(const char *hostname) { - TransportSecurityState state(""); +static bool HasState(const char* hostname) { + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - return state.GetDomainState(&domain_state, hostname, true /* SNI ok */); + return state.GetDomainState(hostname, true /* SNI ok */, &domain_state); } -static bool HasPins(const char *hostname) { - TransportSecurityState state(""); +static bool HasPins(const char* hostname, bool sni_enabled) { + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - return state.HasPinsForHost(&domain_state, hostname, true /* SNI ok */); + if (!state.GetDomainState(hostname, sni_enabled, &domain_state)) + return false; + + return domain_state.HasPins(); +} + +static bool HasPins(const char* hostname) { + return HasPins(hostname, true); } static bool OnlyPinning(const char *hostname) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - return state.HasPinsForHost(&domain_state, hostname, true /* SNI ok */) && + if (!state.GetDomainState(hostname, true /* SNI ok */, &domain_state)) + return false; + + return (domain_state.static_spki_hashes.size() > 0 || + domain_state.bad_static_spki_hashes.size() > 0 || + domain_state.dynamic_spki_hashes.size() > 0) && !domain_state.ShouldRedirectHTTPToHTTPS(); } TEST_F(TransportSecurityStateTest, Preloaded) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; // We do more extensive checks for the first domain. - EXPECT_TRUE(state.GetDomainState(&domain_state, "www.paypal.com", true)); - EXPECT_EQ(domain_state.mode, - TransportSecurityState::DomainState::MODE_STRICT); - EXPECT_TRUE(domain_state.preloaded); + EXPECT_TRUE(state.GetDomainState("www.paypal.com", true, &domain_state)); + EXPECT_EQ(domain_state.upgrade_mode, + TransportSecurityState::DomainState::MODE_FORCE_HTTPS); EXPECT_FALSE(domain_state.include_subdomains); EXPECT_FALSE(HasState("paypal.com")); @@ -646,16 +588,13 @@ TEST_F(TransportSecurityStateTest, Preloaded) { EXPECT_TRUE(OnlyPinning("googlegroups.com")); // Tests for domains that don't work without SNI. - EXPECT_FALSE(state.GetDomainState(&domain_state, "gmail.com", false)); - EXPECT_FALSE(state.GetDomainState(&domain_state, "www.gmail.com", false)); - EXPECT_FALSE(state.GetDomainState(&domain_state, "m.gmail.com", false)); - EXPECT_FALSE(state.GetDomainState(&domain_state, "googlemail.com", false)); - EXPECT_FALSE(state.GetDomainState(&domain_state, - "www.googlemail.com", - false)); - EXPECT_FALSE(state.GetDomainState(&domain_state, - "m.googlemail.com", - false)); + EXPECT_FALSE(state.GetDomainState("gmail.com", false, &domain_state)); + EXPECT_FALSE(state.GetDomainState("www.gmail.com", false, &domain_state)); + EXPECT_FALSE(state.GetDomainState("m.gmail.com", false, &domain_state)); + EXPECT_FALSE(state.GetDomainState("googlemail.com", false, &domain_state)); + EXPECT_FALSE(state.GetDomainState("www.googlemail.com", false, + &domain_state)); + EXPECT_FALSE(state.GetDomainState("m.googlemail.com", false, &domain_state)); // Other hosts: @@ -753,22 +692,17 @@ TEST_F(TransportSecurityStateTest, Preloaded) { EXPECT_TRUE(ShouldRedirect("www.dropcam.com")); EXPECT_FALSE(HasState("foo.dropcam.com")); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "torproject.org", - false)); - EXPECT_FALSE(domain_state.preloaded_spki_hashes.empty()); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "www.torproject.org", - false)); - EXPECT_FALSE(domain_state.preloaded_spki_hashes.empty()); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "check.torproject.org", - false)); - EXPECT_FALSE(domain_state.preloaded_spki_hashes.empty()); - EXPECT_TRUE(state.GetDomainState(&domain_state, - "blog.torproject.org", - false)); - EXPECT_FALSE(domain_state.preloaded_spki_hashes.empty()); + EXPECT_TRUE(state.GetDomainState("torproject.org", false, &domain_state)); + EXPECT_FALSE(domain_state.static_spki_hashes.empty()); + EXPECT_TRUE(state.GetDomainState("www.torproject.org", false, + &domain_state)); + EXPECT_FALSE(domain_state.static_spki_hashes.empty()); + EXPECT_TRUE(state.GetDomainState("check.torproject.org", false, + &domain_state)); + EXPECT_FALSE(domain_state.static_spki_hashes.empty()); + EXPECT_TRUE(state.GetDomainState("blog.torproject.org", false, + &domain_state)); + EXPECT_FALSE(domain_state.static_spki_hashes.empty()); EXPECT_TRUE(ShouldRedirect("ebanking.indovinabank.com.vn")); EXPECT_TRUE(ShouldRedirect("foo.ebanking.indovinabank.com.vn")); @@ -837,119 +771,65 @@ TEST_F(TransportSecurityStateTest, Preloaded) { } TEST_F(TransportSecurityStateTest, LongNames) { - TransportSecurityState state(""); + TransportSecurityState state; const char kLongName[] = "lookupByWaveIdHashAndWaveIdIdAndWaveIdDomainAndWaveletIdIdAnd" "WaveletIdDomainAndBlipBlipid"; TransportSecurityState::DomainState domain_state; // Just checks that we don't hit a NOTREACHED. - EXPECT_FALSE(state.GetDomainState(&domain_state, kLongName, true)); + EXPECT_FALSE(state.GetDomainState(kLongName, true, &domain_state)); } -TEST_F(TransportSecurityStateTest, PublicKeyHashes) { - TransportSecurityState state(""); +TEST_F(TransportSecurityStateTest, BuiltinCertPins) { + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - EXPECT_FALSE(state.GetDomainState(&domain_state, "example.com", false)); - FingerprintVector hashes; - EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(hashes)); - - SHA1Fingerprint hash; - memset(hash.data, '1', sizeof(hash.data)); - domain_state.preloaded_spki_hashes.push_back(hash); - EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(hashes)); - hashes.push_back(hash); - EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(hashes)); - hashes[0].data[0] = '2'; - EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(hashes)); + EXPECT_TRUE(state.GetDomainState("chrome.google.com", true, &domain_state)); + EXPECT_TRUE(HasPins("chrome.google.com")); - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - domain_state.expiry = expiry; - state.EnableHost("example.com", domain_state); - std::string ser; - EXPECT_TRUE(state.Serialise(&ser)); - bool dirty; - EXPECT_TRUE(state.LoadEntries(ser, &dirty)); - EXPECT_TRUE(state.GetDomainState(&domain_state, "example.com", false)); - EXPECT_EQ(1u, domain_state.preloaded_spki_hashes.size()); - EXPECT_EQ(0, memcmp(domain_state.preloaded_spki_hashes[0].data, hash.data, - sizeof(hash.data))); -} - -TEST_F(TransportSecurityStateTest, BuiltinCertPins) { - TransportSecurityState state(""); - TransportSecurityState::DomainState domain_state; - EXPECT_TRUE(state.GetDomainState(&domain_state, - "chrome.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "chrome.google.com", true)); FingerprintVector hashes; - // This essential checks that a built-in list does exist. + // Checks that a built-in list does exist. EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(hashes)); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, "www.paypal.com", true)); - - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "docs.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "1.docs.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "sites.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "drive.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "spreadsheets.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "health.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "checkout.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "appengine.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "market.android.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "encrypted.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "accounts.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "profiles.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "mail.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "chatenabled.mail.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "talkgadget.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "hostedtalkgadget.google.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "talk.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "plus.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "groups.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "apis.google.com", true)); - - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "ssl.gstatic.com", true)); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, "www.gstatic.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "ssl.google-analytics.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "www.googleplex.com", true)); + EXPECT_FALSE(HasPins("www.paypal.com")); + + EXPECT_TRUE(HasPins("docs.google.com")); + EXPECT_TRUE(HasPins("1.docs.google.com")); + EXPECT_TRUE(HasPins("sites.google.com")); + EXPECT_TRUE(HasPins("drive.google.com")); + EXPECT_TRUE(HasPins("spreadsheets.google.com")); + EXPECT_TRUE(HasPins("health.google.com")); + EXPECT_TRUE(HasPins("checkout.google.com")); + EXPECT_TRUE(HasPins("appengine.google.com")); + EXPECT_TRUE(HasPins("market.android.com")); + EXPECT_TRUE(HasPins("encrypted.google.com")); + EXPECT_TRUE(HasPins("accounts.google.com")); + EXPECT_TRUE(HasPins("profiles.google.com")); + EXPECT_TRUE(HasPins("mail.google.com")); + EXPECT_TRUE(HasPins("chatenabled.mail.google.com")); + EXPECT_TRUE(HasPins("talkgadget.google.com")); + EXPECT_TRUE(HasPins("hostedtalkgadget.google.com")); + EXPECT_TRUE(HasPins("talk.google.com")); + EXPECT_TRUE(HasPins("plus.google.com")); + EXPECT_TRUE(HasPins("groups.google.com")); + EXPECT_TRUE(HasPins("apis.google.com")); + + EXPECT_TRUE(HasPins("ssl.gstatic.com")); + EXPECT_FALSE(HasPins("www.gstatic.com")); + EXPECT_TRUE(HasPins("ssl.google-analytics.com")); + EXPECT_TRUE(HasPins("www.googleplex.com")); // Disabled in order to help track down pinning failures --agl - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "twitter.com", true)); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, "foo.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "www.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "api.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "oauth.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "mobile.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "dev.twitter.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "business.twitter.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "platform.twitter.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "si0.twimg.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "twimg0-a.akamaihd.net", - true)); + EXPECT_TRUE(HasPins("twitter.com")); + EXPECT_FALSE(HasPins("foo.twitter.com")); + EXPECT_TRUE(HasPins("www.twitter.com")); + EXPECT_TRUE(HasPins("api.twitter.com")); + EXPECT_TRUE(HasPins("oauth.twitter.com")); + EXPECT_TRUE(HasPins("mobile.twitter.com")); + EXPECT_TRUE(HasPins("dev.twitter.com")); + EXPECT_TRUE(HasPins("business.twitter.com")); + EXPECT_TRUE(HasPins("platform.twitter.com")); + EXPECT_TRUE(HasPins("si0.twimg.com")); + EXPECT_TRUE(HasPins("twimg0-a.akamaihd.net")); } static bool AddHash(const std::string& type_and_base64, @@ -995,9 +875,10 @@ TEST_F(TransportSecurityStateTest, PinValidationWithRejectedCerts) { EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); } - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "plus.google.com", true)); + EXPECT_TRUE(state.GetDomainState("plus.google.com", true, &domain_state)); + EXPECT_TRUE(domain_state.HasPins()); EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(good_hashes)); EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(bad_hashes)); @@ -1030,75 +911,41 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); } - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "blog.torproject.org", true)); + EXPECT_TRUE(state.GetDomainState("blog.torproject.org", true, &domain_state)); + EXPECT_TRUE(domain_state.HasPins()); EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(good_hashes)); EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(bad_hashes)); } TEST_F(TransportSecurityStateTest, OptionalHSTSCertPins) { - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; + EXPECT_FALSE(ShouldRedirect("www.google-analytics.com")); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, - "www.google-analytics.com", - false)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "www.google-analytics.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "www.google.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "mail-attachment.googleusercontent.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "www.youtube.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "i.ytimg.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "googleapis.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "ajax.googleapis.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "googleadservices.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "pagead2.googleadservices.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "googlecode.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "kibbles.googlecode.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "appspot.com", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, - "googlesyndication.com", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "doubleclick.net", true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "ad.doubleclick.net", true)); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, - "learn.doubleclick.net", - true)); - EXPECT_TRUE(state.HasPinsForHost(&domain_state, "a.googlegroups.com", true)); - EXPECT_FALSE(state.HasPinsForHost(&domain_state, - "a.googlegroups.com", - false)); -} -TEST_F(TransportSecurityStateTest, ForcePreloads) { - // This is a docs.google.com override. - std::string preload("{" - "\"4AGT3lHihuMSd5rUj7B4u6At0jlSH3HFePovjPR+oLE=\": {" - "\"created\": 0.0," - "\"expiry\": 2000000000.0," - "\"include_subdomains\": false," - "\"mode\": \"pinning-only\"" - "}}"); - - TransportSecurityState state(preload); - TransportSecurityState::DomainState domain_state; - EXPECT_FALSE(state.HasPinsForHost(&domain_state, "docs.google.com", true)); - EXPECT_TRUE(state.GetDomainState(&domain_state, "docs.google.com", true)); - EXPECT_FALSE(domain_state.ShouldRedirectHTTPToHTTPS()); + EXPECT_FALSE(HasPins("www.google-analytics.com", false)); + EXPECT_TRUE(HasPins("www.google-analytics.com")); + EXPECT_TRUE(HasPins("google.com")); + EXPECT_TRUE(HasPins("www.google.com")); + EXPECT_TRUE(HasPins("mail-attachment.googleusercontent.com")); + EXPECT_TRUE(HasPins("www.youtube.com")); + EXPECT_TRUE(HasPins("i.ytimg.com")); + EXPECT_TRUE(HasPins("googleapis.com")); + EXPECT_TRUE(HasPins("ajax.googleapis.com")); + EXPECT_TRUE(HasPins("googleadservices.com")); + EXPECT_TRUE(HasPins("pagead2.googleadservices.com")); + EXPECT_TRUE(HasPins("googlecode.com")); + EXPECT_TRUE(HasPins("kibbles.googlecode.com")); + EXPECT_TRUE(HasPins("appspot.com")); + EXPECT_TRUE(HasPins("googlesyndication.com")); + EXPECT_TRUE(HasPins("doubleclick.net")); + EXPECT_TRUE(HasPins("ad.doubleclick.net")); + EXPECT_FALSE(HasPins("learn.doubleclick.net")); + EXPECT_TRUE(HasPins("a.googlegroups.com")); + EXPECT_FALSE(HasPins("a.googlegroups.com", false)); } TEST_F(TransportSecurityStateTest, OverrideBuiltins) { @@ -1106,14 +953,14 @@ TEST_F(TransportSecurityStateTest, OverrideBuiltins) { EXPECT_FALSE(ShouldRedirect("google.com")); EXPECT_FALSE(ShouldRedirect("www.google.com")); - TransportSecurityState state(""); + TransportSecurityState state; TransportSecurityState::DomainState domain_state; const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - domain_state.expiry = expiry; + domain_state.upgrade_expiry = expiry; state.EnableHost("www.google.com", domain_state); - EXPECT_TRUE(state.GetDomainState(&domain_state, "www.google.com", true)); + EXPECT_TRUE(state.GetDomainState("www.google.com", true, &domain_state)); } static const uint8 kSidePinLeafSPKI[] = { @@ -1148,72 +995,6 @@ static const uint8 kSidePinExpectedHash[20] = { 0xc4, 0x09, 0x3d, 0x2d, 0x1d, 0xea, 0x1e, }; -TEST_F(TransportSecurityStateTest, ParseSidePins) { - base::StringPiece leaf_spki(reinterpret_cast<const char*>(kSidePinLeafSPKI), - sizeof(kSidePinLeafSPKI)); - base::StringPiece side_info(reinterpret_cast<const char*>(kSidePinInfo), - sizeof(kSidePinInfo)); - - FingerprintVector pub_key_hashes; - EXPECT_TRUE(TransportSecurityState::ParseSidePin( - leaf_spki, side_info, &pub_key_hashes)); - ASSERT_EQ(1u, pub_key_hashes.size()); - EXPECT_EQ(0, memcmp(pub_key_hashes[0].data, kSidePinExpectedHash, - sizeof(kSidePinExpectedHash))); -} - -TEST_F(TransportSecurityStateTest, ParseSidePinsFailsWithBadData) { - uint8 leaf_spki_copy[sizeof(kSidePinLeafSPKI)]; - memcpy(leaf_spki_copy, kSidePinLeafSPKI, sizeof(leaf_spki_copy)); - - uint8 side_info_copy[sizeof(kSidePinInfo)]; - memcpy(side_info_copy, kSidePinInfo, sizeof(kSidePinInfo)); - - base::StringPiece leaf_spki(reinterpret_cast<const char*>(leaf_spki_copy), - sizeof(leaf_spki_copy)); - base::StringPiece side_info(reinterpret_cast<const char*>(side_info_copy), - sizeof(side_info_copy)); - FingerprintVector pub_key_hashes; - - // Tweak |leaf_spki| and expect a failure. - leaf_spki_copy[10] ^= 1; - EXPECT_FALSE(TransportSecurityState::ParseSidePin( - leaf_spki, side_info, &pub_key_hashes)); - ASSERT_EQ(0u, pub_key_hashes.size()); - - // Undo the change to |leaf_spki| and tweak |side_info|. - leaf_spki_copy[10] ^= 1; - side_info_copy[30] ^= 1; - EXPECT_FALSE(TransportSecurityState::ParseSidePin( - leaf_spki, side_info, &pub_key_hashes)); - ASSERT_EQ(0u, pub_key_hashes.size()); -} - -TEST_F(TransportSecurityStateTest, DISABLED_ParseSidePinsFuzz) { - // Disabled because it's too slow for normal tests. Run manually when - // changing the underlying code. - - base::StringPiece leaf_spki(reinterpret_cast<const char*>(kSidePinLeafSPKI), - sizeof(kSidePinLeafSPKI)); - uint8 side_info_copy[sizeof(kSidePinInfo)]; - base::StringPiece side_info(reinterpret_cast<const char*>(side_info_copy), - sizeof(side_info_copy)); - FingerprintVector pub_key_hashes; - static const size_t bit_length = sizeof(kSidePinInfo) * 8; - - for (size_t bit_to_flip = 0; bit_to_flip < bit_length; bit_to_flip++) { - memcpy(side_info_copy, kSidePinInfo, sizeof(kSidePinInfo)); - - size_t byte = bit_to_flip >> 3; - size_t bit = bit_to_flip & 7; - side_info_copy[byte] ^= (1 << bit); - - EXPECT_FALSE(TransportSecurityState::ParseSidePin( - leaf_spki, side_info, &pub_key_hashes)); - ASSERT_EQ(0u, pub_key_hashes.size()); - } -} - TEST_F(TransportSecurityStateTest, GooglePinnedProperties) { EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( "www.example.com", true)); diff --git a/net/base/x509_cert_types.h b/net/base/x509_cert_types.h index 61af236..e10c395 100644 --- a/net/base/x509_cert_types.h +++ b/net/base/x509_cert_types.h @@ -37,6 +37,13 @@ struct NET_EXPORT SHA1Fingerprint { unsigned char data[20]; }; +// In the future there will be a generic Fingerprint type, with at least two +// implementations: SHA1 and SHA256. See http://crbug.com/117914. Until that +// work is done (in a separate patch) this typedef bridges the gap. +typedef SHA1Fingerprint Fingerprint; + +typedef std::vector<Fingerprint> FingerprintVector; + class NET_EXPORT SHA1FingerprintLessThan { public: bool operator() (const SHA1Fingerprint& lhs, diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index 37f9566..bd7b8e9c 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -1723,56 +1723,6 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time); } - PeerCertificateChain chain(nss_fd_); - for (unsigned i = 1; i < chain.size(); i++) { - if (strcmp(chain[i]->subjectName, "CN=meta") != 0) - continue; - - base::StringPiece leaf_der( - reinterpret_cast<char*>(server_cert_nss_->derCert.data), - server_cert_nss_->derCert.len); - base::StringPiece leaf_spki; - if (!asn1::ExtractSPKIFromDERCert(leaf_der, &leaf_spki)) - break; - - static SECOidTag side_data_tag; - static bool side_data_tag_valid; - if (!side_data_tag_valid) { - // It's harmless if multiple threads enter this block concurrently. - static const uint8 kSideDataOID[] = - // 1.3.6.1.4.1.11129.2.1.4 - // (iso.org.dod.internet.private.enterprises.google.googleSecurity. - // certificateExtensions.sideData) - {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x05}; - SECOidData oid_data; - memset(&oid_data, 0, sizeof(oid_data)); - oid_data.oid.data = const_cast<uint8*>(kSideDataOID); - oid_data.oid.len = sizeof(kSideDataOID); - oid_data.desc = "Certificate side data"; - oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; - side_data_tag = SECOID_AddEntry(&oid_data); - DCHECK_NE(SEC_OID_UNKNOWN, side_data_tag); - side_data_tag_valid = true; - } - - SECItem side_data_item; - SECStatus rv = CERT_FindCertExtension(chain[i], - side_data_tag, &side_data_item); - if (rv != SECSuccess) - continue; - - base::StringPiece side_data( - reinterpret_cast<char*>(side_data_item.data), - side_data_item.len); - - if (!TransportSecurityState::ParseSidePin( - leaf_spki, side_data, &side_pinned_public_keys_)) { - LOG(WARNING) << "Side pinning data failed to parse: " - << host_and_port_.host(); - } - break; - } - // We used to remember the intermediate CA certs in the NSS database // persistently. However, NSS opens a connection to the SQLite database // during NSS initialization and doesn't close the connection until NSS diff --git a/net/socket_stream/socket_stream.cc b/net/socket_stream/socket_stream.cc index c5a8b2a..51e92907 100644 --- a/net/socket_stream/socket_stream.cc +++ b/net/socket_stream/socket_stream.cc @@ -9,6 +9,7 @@ #include <set> #include <string> +#include <vector> #include "base/bind.h" #include "base/bind_helpers.h" @@ -1205,8 +1206,9 @@ int SocketStream::HandleCertificateError(int result) { const bool fatal = context_->transport_security_state() && context_->transport_security_state()->GetDomainState( - &domain_state, url_.host(), - SSLConfigService::IsSNIAvailable(context_->ssl_config_service())); + url_.host(), + SSLConfigService::IsSNIAvailable(context_->ssl_config_service()), + &domain_state); delegate_->OnSSLCertificateError(this, ssl_info, fatal); return ERR_IO_PENDING; diff --git a/net/socket_stream/socket_stream_job.cc b/net/socket_stream/socket_stream_job.cc index 6672df6..106656e 100644 --- a/net/socket_stream/socket_stream_job.cc +++ b/net/socket_stream/socket_stream_job.cc @@ -28,7 +28,7 @@ SocketStreamJob* SocketStreamJob::CreateSocketStreamJob( GURL socket_url(url); TransportSecurityState::DomainState domain_state; if (url.scheme() == "ws" && sts && sts->GetDomainState( - &domain_state, url.host(), SSLConfigService::IsSNIAvailable(ssl)) && + url.host(), SSLConfigService::IsSNIAvailable(ssl), &domain_state) && domain_state.ShouldRedirectHTTPToHTTPS()) { url_canon::Replacements<char> replacements; static const char kNewScheme[] = "wss"; diff --git a/net/url_request/url_request_context_builder.cc b/net/url_request/url_request_context_builder.cc index 552f7af..5f8a32c 100644 --- a/net/url_request/url_request_context_builder.cc +++ b/net/url_request/url_request_context_builder.cc @@ -214,7 +214,7 @@ scoped_refptr<URLRequestContext> URLRequestContextBuilder::Build() { storage->set_http_auth_handler_factory( net::HttpAuthHandlerRegistryFactory::CreateDefault(host_resolver)); storage->set_cookie_store(new CookieMonster(NULL, NULL)); - storage->set_transport_security_state(new net::TransportSecurityState("")); + storage->set_transport_security_state(new net::TransportSecurityState()); storage->set_http_server_properties(new net::HttpServerPropertiesImpl); storage->set_cert_verifier(CertVerifier::CreateDefault()); diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc index cb7a64d..8e6626a 100644 --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc @@ -144,10 +144,10 @@ URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request, if (scheme == "http" && request->context()->transport_security_state() && request->context()->transport_security_state()->GetDomainState( - &domain_state, request->url().host(), SSLConfigService::IsSNIAvailable( - request->context()->ssl_config_service())) && + request->context()->ssl_config_service()), + &domain_state) && domain_state.ShouldRedirectHTTPToHTTPS()) { DCHECK_EQ(request->url().scheme(), "http"); url_canon::Replacements<char> replacements; @@ -599,30 +599,21 @@ void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() { bool sni_available = SSLConfigService::IsSNIAvailable(ctx->ssl_config_service()); - if (!security_state->HasMetadata(&domain_state, host, sni_available)) { - // |HasMetadata| may have altered |domain_state| while searching. If not - // found, start with a fresh state. - domain_state = TransportSecurityState::DomainState(); - domain_state.mode = TransportSecurityState::DomainState::MODE_STRICT; - } + if (!security_state->GetDomainState(host, sni_available, &domain_state)) + // |GetDomainState| may have altered |domain_state| while searching. If + // not found, start with a fresh state. + domain_state.upgrade_mode = + TransportSecurityState::DomainState::MODE_FORCE_HTTPS; HttpResponseHeaders* headers = GetResponseHeaders(); std::string value; void* iter = NULL; + base::Time now = base::Time::Now(); while (headers->EnumerateHeader(&iter, "Strict-Transport-Security", &value)) { - int max_age; - bool include_subdomains; - if (TransportSecurityState::ParseHeader(value, &max_age, - &include_subdomains)) { - base::Time current_time(base::Time::Now()); - base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age); - - domain_state.expiry = current_time + max_age_delta; - domain_state.include_subdomains = include_subdomains; - + TransportSecurityState::DomainState domain_state; + if (domain_state.ParseSTSHeader(now, value)) security_state->EnableHost(host, domain_state); - } } } @@ -645,25 +636,23 @@ void URLRequestHttpJob::ProcessPublicKeyPinsHeader() { bool sni_available = SSLConfigService::IsSNIAvailable(ctx->ssl_config_service()); - if (!security_state->HasMetadata(&domain_state, host, sni_available)) { - // |HasMetadata| may have altered |domain_state| while searching. If not - // found, start with a fresh state. - domain_state = TransportSecurityState::DomainState(); - domain_state.mode = TransportSecurityState::DomainState::MODE_PINNING_ONLY; - } + if (!security_state->GetDomainState(host, sni_available, &domain_state)) + // |GetDomainState| may have altered |domain_state| while searching. If + // not found, start with a fresh state. + domain_state.upgrade_mode = + TransportSecurityState::DomainState::MODE_DEFAULT; HttpResponseHeaders* headers = GetResponseHeaders(); void* iter = NULL; std::string value; + base::Time now = base::Time::Now(); while (headers->EnumerateHeader(&iter, "Public-Key-Pins", &value)) { // Note that ParsePinsHeader updates |domain_state| (iff the header parses // correctly), but does not completely overwrite it. It just updates the // dynamic pinning metadata. - if (TransportSecurityState::ParsePinsHeader(value, ssl_info, - &domain_state)) { + if (domain_state.ParsePinsHeader(now, value, ssl_info)) security_state->EnableHost(host, domain_state); - } } } @@ -729,8 +718,9 @@ void URLRequestHttpJob::OnStartCompleted(int result) { const bool fatal = context_->transport_security_state() && context_->transport_security_state()->GetDomainState( - &domain_state, request_info_.url.host(), - SSLConfigService::IsSNIAvailable(context_->ssl_config_service())); + request_info_.url.host(), + SSLConfigService::IsSNIAvailable(context_->ssl_config_service()), + &domain_state); NotifySSLCertificateError(transaction_->GetResponseInfo()->ssl_info, fatal); } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { NotifyCertificateRequested( diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index d53c01c..204f223 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -479,8 +479,8 @@ class URLRequestTestHTTP : public URLRequestTest { void HTTPUploadDataOperationTest(const std::string& method) { const int kMsgSize = 20000; // multiple of 10 const int kIterations = 50; - char *uploadBytes = new char[kMsgSize+1]; - char *ptr = uploadBytes; + char* uploadBytes = new char[kMsgSize+1]; + char* ptr = uploadBytes; char marker = 'a'; for (int idx = 0; idx < kMsgSize/10; idx++) { memcpy(ptr, "----------", 10); @@ -1774,7 +1774,7 @@ TEST_F(HTTPSRequestTest, HTTPSPreloadedHSTSTest) { scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext(true)); context->set_network_delegate(&network_delegate); context->set_host_resolver(&host_resolver); - TransportSecurityState transport_security_state(""); + TransportSecurityState transport_security_state; context->set_transport_security_state(&transport_security_state); context->Init(); @@ -1817,10 +1817,10 @@ TEST_F(HTTPSRequestTest, HTTPSErrorsNoClobberTSSTest) { scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext(true)); context->set_network_delegate(&network_delegate); context->set_host_resolver(&host_resolver); - TransportSecurityState transport_security_state(""); + TransportSecurityState transport_security_state; TransportSecurityState::DomainState domain_state; - EXPECT_TRUE(transport_security_state.HasMetadata(&domain_state, - "www.google.com", true)); + EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true, + &domain_state)); context->set_transport_security_state(&transport_security_state); context->Init(); @@ -1842,17 +1842,17 @@ TEST_F(HTTPSRequestTest, HTTPSErrorsNoClobberTSSTest) { // Get a fresh copy of the state, and check that it hasn't been updated. TransportSecurityState::DomainState new_domain_state; - EXPECT_TRUE(transport_security_state.HasMetadata(&new_domain_state, - "www.google.com", true)); - EXPECT_EQ(new_domain_state.mode, domain_state.mode); + EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true, + &new_domain_state)); + EXPECT_EQ(new_domain_state.upgrade_mode, domain_state.upgrade_mode); EXPECT_EQ(new_domain_state.include_subdomains, domain_state.include_subdomains); - EXPECT_TRUE(FingerprintsEqual(new_domain_state.preloaded_spki_hashes, - domain_state.preloaded_spki_hashes)); + EXPECT_TRUE(FingerprintsEqual(new_domain_state.static_spki_hashes, + domain_state.static_spki_hashes)); EXPECT_TRUE(FingerprintsEqual(new_domain_state.dynamic_spki_hashes, domain_state.dynamic_spki_hashes)); - EXPECT_TRUE(FingerprintsEqual(new_domain_state.bad_preloaded_spki_hashes, - domain_state.bad_preloaded_spki_hashes)); + EXPECT_TRUE(FingerprintsEqual(new_domain_state.bad_static_spki_hashes, + domain_state.bad_static_spki_hashes)); } namespace { @@ -2571,8 +2571,8 @@ TEST_F(URLRequestTest, ResolveShortcutTest) { std::wstring lnk_path = app_path.value() + L".lnk"; HRESULT result; - IShellLink *shell = NULL; - IPersistFile *persist = NULL; + IShellLink* shell = NULL; + IPersistFile* persist = NULL; CoInitialize(NULL); // Temporarily create a shortcut for test diff --git a/net/websockets/websocket_job_spdy2_unittest.cc b/net/websockets/websocket_job_spdy2_unittest.cc index ebcc72f..05731f5 100644 --- a/net/websockets/websocket_job_spdy2_unittest.cc +++ b/net/websockets/websocket_job_spdy2_unittest.cc @@ -174,7 +174,7 @@ class MockCookieStore : public net::CookieStore { const net::CookieOptions& options) { std::string result; for (size_t i = 0; i < entries_.size(); i++) { - Entry &entry = entries_[i]; + Entry& entry = entries_[i]; if (url == entry.url) { if (!result.empty()) { result += "; "; @@ -250,11 +250,12 @@ class MockSSLConfigService : public net::SSLConfigService { class MockURLRequestContext : public net::URLRequestContext { public: explicit MockURLRequestContext(net::CookieStore* cookie_store) - : transport_security_state_(std::string()) { + : transport_security_state_() { set_cookie_store(cookie_store); set_transport_security_state(&transport_security_state_); net::TransportSecurityState::DomainState state; - state.expiry = base::Time::Now() + base::TimeDelta::FromSeconds(1000); + state.upgrade_expiry = base::Time::Now() + + base::TimeDelta::FromSeconds(1000); transport_security_state_.EnableHost("upgrademe.com", state); } @@ -268,7 +269,7 @@ class MockURLRequestContext : public net::URLRequestContext { class MockHttpTransactionFactory : public net::HttpTransactionFactory { public: - MockHttpTransactionFactory(net::OrderedSocketData* data) { + explicit MockHttpTransactionFactory(net::OrderedSocketData* data) { data_ = data; net::MockConnect connect_data(net::SYNCHRONOUS, net::OK); data_->set_connect_data(connect_data); diff --git a/net/websockets/websocket_job_spdy3_unittest.cc b/net/websockets/websocket_job_spdy3_unittest.cc index 6fd4767..9c908c8 100644 --- a/net/websockets/websocket_job_spdy3_unittest.cc +++ b/net/websockets/websocket_job_spdy3_unittest.cc @@ -178,7 +178,7 @@ class MockCookieStore : public net::CookieStore { const net::CookieOptions& options) { std::string result; for (size_t i = 0; i < entries_.size(); i++) { - Entry &entry = entries_[i]; + Entry& entry = entries_[i]; if (url == entry.url) { if (!result.empty()) { result += "; "; @@ -254,11 +254,12 @@ class MockSSLConfigService : public net::SSLConfigService { class MockURLRequestContext : public net::URLRequestContext { public: explicit MockURLRequestContext(net::CookieStore* cookie_store) - : transport_security_state_(std::string()) { + : transport_security_state_() { set_cookie_store(cookie_store); set_transport_security_state(&transport_security_state_); net::TransportSecurityState::DomainState state; - state.expiry = base::Time::Now() + base::TimeDelta::FromSeconds(1000); + state.upgrade_expiry = base::Time::Now() + + base::TimeDelta::FromSeconds(1000); transport_security_state_.EnableHost("upgrademe.com", state); } @@ -272,7 +273,7 @@ class MockURLRequestContext : public net::URLRequestContext { class MockHttpTransactionFactory : public net::HttpTransactionFactory { public: - MockHttpTransactionFactory(net::OrderedSocketData* data) { + explicit MockHttpTransactionFactory(net::OrderedSocketData* data) { data_ = data; net::MockConnect connect_data(net::SYNCHRONOUS, net::OK); data_->set_connect_data(connect_data); |