| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/http/transport_security_state.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/callback_helpers.h" |
| #include "base/files/file_path.h" |
| #include "base/json/json_reader.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_param_associator.h" |
| #include "base/rand_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_entropy_provider.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "crypto/openssl_util.h" |
| #include "crypto/sha2.h" |
| #include "net/base/features.h" |
| #include "net/base/hash_value.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/asn1_util.h" |
| #include "net/cert/cert_verifier.h" |
| #include "net/cert/cert_verify_result.h" |
| #include "net/cert/ct_policy_status.h" |
| #include "net/cert/test_root_certs.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/extras/preload_data/decoder.h" |
| #include "net/http/http_status_code.h" |
| #include "net/http/http_util.h" |
| #include "net/http/transport_security_state_source.h" |
| #include "net/net_buildflags.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "net/tools/huffman_trie/bit_writer.h" |
| #include "net/tools/huffman_trie/trie/trie_bit_buffer.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| namespace test_default { |
| #include "net/http/transport_security_state_static_unittest_default.h" |
| } |
| namespace test1 { |
| #include "net/http/transport_security_state_static_unittest1.h" |
| } |
| namespace test2 { |
| #include "net/http/transport_security_state_static_unittest2.h" |
| } |
| namespace test3 { |
| #include "net/http/transport_security_state_static_unittest3.h" |
| } |
| |
| const char kHost[] = "example.test"; |
| const uint16_t kPort = 443; |
| const char kReportUri[] = "http://report-example.test/test"; |
| const char kExpectCTStaticHostname[] = "expect-ct.preloaded.test"; |
| const char kExpectCTStaticReportURI[] = |
| "http://report-uri.preloaded.test/expect-ct"; |
| |
| const char* const kGoodPath[] = { |
| "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", |
| "sha256/fzP+pVAbH0hRoUphJKenIP8+2tD/d2QH9J+kQNieM6Q=", |
| "sha256/9vRUVdjloCa4wXUKfDWotV5eUXYD7vu0v0z9SRzQdzg=", |
| "sha256/Nn8jk5By4Vkq6BeOVZ7R7AC6XUUBZsWmUbJR1f1Y5FY=", |
| nullptr, |
| }; |
| |
| const char* const kBadPath[] = { |
| "sha256/1111111111111111111111111111111111111111111=", |
| "sha256/2222222222222222222222222222222222222222222=", |
| "sha256/3333333333333333333333333333333333333333333=", |
| nullptr, |
| }; |
| |
| // Constructs a SignedCertificateTimestampAndStatus with the given information |
| // and appends it to |sct_list|. |
| void MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::Origin origin, |
| const std::string& log_id, |
| const std::string& extensions, |
| const std::string& signature_data, |
| const base::Time& timestamp, |
| ct::SCTVerifyStatus status, |
| SignedCertificateTimestampAndStatusList* sct_list) { |
| scoped_refptr<net::ct::SignedCertificateTimestamp> sct( |
| new net::ct::SignedCertificateTimestamp()); |
| sct->version = net::ct::SignedCertificateTimestamp::V1; |
| sct->log_id = log_id; |
| sct->extensions = extensions; |
| sct->timestamp = timestamp; |
| sct->signature.signature_data = signature_data; |
| sct->origin = origin; |
| sct_list->push_back(net::SignedCertificateTimestampAndStatus(sct, status)); |
| } |
| |
| // A mock ReportSenderInterface that just remembers the latest report |
| // URI and report to be sent. |
| class MockCertificateReportSender |
| : public TransportSecurityState::ReportSenderInterface { |
| public: |
| MockCertificateReportSender() = default; |
| ~MockCertificateReportSender() override = default; |
| |
| void Send( |
| const GURL& report_uri, |
| base::StringPiece content_type, |
| base::StringPiece report, |
| const NetworkIsolationKey& network_isolation_key, |
| base::OnceCallback<void()> success_callback, |
| base::OnceCallback<void(const GURL&, int, int)> error_callback) override { |
| latest_report_uri_ = report_uri; |
| latest_report_.assign(report.data(), report.size()); |
| latest_content_type_.assign(content_type.data(), content_type.size()); |
| latest_network_isolation_key_ = network_isolation_key; |
| } |
| |
| void Clear() { |
| latest_report_uri_ = GURL(); |
| latest_report_ = std::string(); |
| latest_content_type_ = std::string(); |
| latest_network_isolation_key_ = NetworkIsolationKey(); |
| } |
| |
| const GURL& latest_report_uri() { return latest_report_uri_; } |
| const std::string& latest_report() { return latest_report_; } |
| const std::string& latest_content_type() { return latest_content_type_; } |
| const NetworkIsolationKey& latest_network_isolation_key() { |
| return latest_network_isolation_key_; |
| } |
| |
| private: |
| GURL latest_report_uri_; |
| std::string latest_report_; |
| std::string latest_content_type_; |
| NetworkIsolationKey latest_network_isolation_key_; |
| }; |
| |
| // A mock ReportSenderInterface that simulates a net error on every report sent. |
| class MockFailingCertificateReportSender |
| : public TransportSecurityState::ReportSenderInterface { |
| public: |
| MockFailingCertificateReportSender() : net_error_(ERR_CONNECTION_FAILED) {} |
| ~MockFailingCertificateReportSender() override = default; |
| |
| int net_error() { return net_error_; } |
| |
| // TransportSecurityState::ReportSenderInterface: |
| void Send( |
| const GURL& report_uri, |
| base::StringPiece content_type, |
| base::StringPiece report, |
| const NetworkIsolationKey& network_isolation_key, |
| base::OnceCallback<void()> success_callback, |
| base::OnceCallback<void(const GURL&, int, int)> error_callback) override { |
| ASSERT_FALSE(error_callback.is_null()); |
| std::move(error_callback).Run(report_uri, net_error_, 0); |
| } |
| |
| private: |
| const int net_error_; |
| }; |
| |
| // A mock ExpectCTReporter that remembers the latest violation that was |
| // reported and the number of violations reported. |
| class MockExpectCTReporter : public TransportSecurityState::ExpectCTReporter { |
| public: |
| MockExpectCTReporter() : num_failures_(0) {} |
| ~MockExpectCTReporter() override = default; |
| |
| void OnExpectCTFailed( |
| const HostPortPair& host_port_pair, |
| const GURL& report_uri, |
| base::Time expiration, |
| const X509Certificate* validated_certificate_chain, |
| const X509Certificate* served_certificate_chain, |
| const SignedCertificateTimestampAndStatusList& |
| signed_certificate_timestamps, |
| const NetworkIsolationKey& network_isolation_key) override { |
| num_failures_++; |
| host_port_pair_ = host_port_pair; |
| report_uri_ = report_uri; |
| expiration_ = expiration; |
| served_certificate_chain_ = served_certificate_chain; |
| validated_certificate_chain_ = validated_certificate_chain; |
| signed_certificate_timestamps_ = signed_certificate_timestamps; |
| network_isolation_key_ = network_isolation_key; |
| } |
| |
| const HostPortPair& host_port_pair() const { return host_port_pair_; } |
| const GURL& report_uri() const { return report_uri_; } |
| const base::Time& expiration() const { return expiration_; } |
| uint32_t num_failures() const { return num_failures_; } |
| const X509Certificate* served_certificate_chain() const { |
| return served_certificate_chain_; |
| } |
| const X509Certificate* validated_certificate_chain() const { |
| return validated_certificate_chain_; |
| } |
| const SignedCertificateTimestampAndStatusList& signed_certificate_timestamps() |
| const { |
| return signed_certificate_timestamps_; |
| } |
| const NetworkIsolationKey& network_isolation_key() const { |
| return network_isolation_key_; |
| } |
| |
| private: |
| HostPortPair host_port_pair_; |
| GURL report_uri_; |
| base::Time expiration_; |
| uint32_t num_failures_; |
| raw_ptr<const X509Certificate> served_certificate_chain_; |
| raw_ptr<const X509Certificate> validated_certificate_chain_; |
| SignedCertificateTimestampAndStatusList signed_certificate_timestamps_; |
| NetworkIsolationKey network_isolation_key_; |
| }; |
| |
| class MockRequireCTDelegate : public TransportSecurityState::RequireCTDelegate { |
| public: |
| MOCK_METHOD3(IsCTRequiredForHost, |
| CTRequirementLevel(const std::string& hostname, |
| const X509Certificate* chain, |
| const HashValueVector& hashes)); |
| }; |
| |
| void CompareCertificateChainWithList( |
| const scoped_refptr<X509Certificate>& cert_chain, |
| const base::Value* cert_list) { |
| ASSERT_TRUE(cert_chain); |
| ASSERT_TRUE(cert_list->is_list()); |
| std::vector<std::string> pem_encoded_chain; |
| cert_chain->GetPEMEncodedChain(&pem_encoded_chain); |
| ASSERT_EQ(pem_encoded_chain.size(), cert_list->GetListDeprecated().size()); |
| |
| for (size_t i = 0; i < pem_encoded_chain.size(); i++) { |
| const std::string& list_cert = |
| cert_list->GetListDeprecated()[i].GetString(); |
| EXPECT_EQ(pem_encoded_chain[i], list_cert); |
| } |
| } |
| |
| void CheckHPKPReport( |
| const std::string& report, |
| const HostPortPair& host_port_pair, |
| bool include_subdomains, |
| const std::string& noted_hostname, |
| const scoped_refptr<X509Certificate>& served_certificate_chain, |
| const scoped_refptr<X509Certificate>& validated_certificate_chain, |
| const HashValueVector& known_pins) { |
| absl::optional<base::Value> value = base::JSONReader::Read(report); |
| ASSERT_TRUE(value.has_value()); |
| const base::Value& report_dict = value.value(); |
| ASSERT_TRUE(report_dict.is_dict()); |
| |
| const std::string* report_hostname = report_dict.FindStringKey("hostname"); |
| ASSERT_TRUE(report_hostname); |
| EXPECT_EQ(host_port_pair.host(), *report_hostname); |
| |
| absl::optional<int> report_port = report_dict.FindIntKey("port"); |
| ASSERT_TRUE(report_port.has_value()); |
| EXPECT_EQ(host_port_pair.port(), report_port.value()); |
| |
| absl::optional<bool> report_include_subdomains = |
| report_dict.FindBoolKey("include-subdomains"); |
| ASSERT_TRUE(report_include_subdomains.has_value()); |
| EXPECT_EQ(include_subdomains, report_include_subdomains.value()); |
| |
| const std::string* report_noted_hostname = |
| report_dict.FindStringKey("noted-hostname"); |
| ASSERT_TRUE(report_noted_hostname); |
| EXPECT_EQ(noted_hostname, *report_noted_hostname); |
| |
| // TODO(estark): check times in RFC3339 format. |
| |
| const std::string* report_expiration = |
| report_dict.FindStringKey("effective-expiration-date"); |
| ASSERT_TRUE(report_expiration); |
| EXPECT_FALSE(report_expiration->empty()); |
| |
| const std::string* report_date = report_dict.FindStringKey("date-time"); |
| ASSERT_TRUE(report_date); |
| EXPECT_FALSE(report_date->empty()); |
| |
| const base::Value* report_served_certificate_chain = |
| report_dict.FindKey("served-certificate-chain"); |
| ASSERT_TRUE(report_served_certificate_chain); |
| ASSERT_NO_FATAL_FAILURE(CompareCertificateChainWithList( |
| served_certificate_chain, report_served_certificate_chain)); |
| |
| const base::Value* report_validated_certificate_chain = |
| report_dict.FindKey("validated-certificate-chain"); |
| ASSERT_TRUE(report_validated_certificate_chain); |
| ASSERT_NO_FATAL_FAILURE(CompareCertificateChainWithList( |
| validated_certificate_chain, report_validated_certificate_chain)); |
| } |
| |
| bool operator==(const TransportSecurityState::STSState& lhs, |
| const TransportSecurityState::STSState& rhs) { |
| return lhs.last_observed == rhs.last_observed && lhs.expiry == rhs.expiry && |
| lhs.upgrade_mode == rhs.upgrade_mode && |
| lhs.include_subdomains == rhs.include_subdomains && |
| lhs.domain == rhs.domain; |
| } |
| |
| bool operator==(const TransportSecurityState::PKPState& lhs, |
| const TransportSecurityState::PKPState& rhs) { |
| return lhs.last_observed == rhs.last_observed && lhs.expiry == rhs.expiry && |
| lhs.spki_hashes == rhs.spki_hashes && |
| lhs.bad_spki_hashes == rhs.bad_spki_hashes && |
| lhs.include_subdomains == rhs.include_subdomains && |
| lhs.domain == rhs.domain && lhs.report_uri == rhs.report_uri; |
| } |
| |
| // Creates a unique new host name every time it's called. Tests should not |
| // depend on the exact domain names, as they may vary depending on what other |
| // tests have been run by the same process. Intended for Expect-CT pruning |
| // tests, which add a lot of domains. |
| std::string CreateUniqueHostName() { |
| static int count = 0; |
| return base::StringPrintf("%i.test", ++count); |
| } |
| |
| // As with CreateUniqueHostName(), returns a unique NetworkIsolationKey for use |
| // with Expect-CT pruning tests. |
| NetworkIsolationKey CreateUniqueNetworkIsolationKey(bool is_transient) { |
| if (is_transient) |
| return NetworkIsolationKey::CreateTransient(); |
| SchemefulSite site = SchemefulSite(url::Origin::CreateFromNormalizedTuple( |
| "https", CreateUniqueHostName(), 443)); |
| return NetworkIsolationKey(site /* top_frame_site */, site /* frame_site */); |
| } |
| |
| } // namespace |
| |
| class TransportSecurityStateTest : public ::testing::Test, |
| public WithTaskEnvironment { |
| public: |
| TransportSecurityStateTest() |
| : WithTaskEnvironment( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) { |
| SetTransportSecurityStateSourceForTesting(&test_default::kHSTSSource); |
| // Need mocked out time for pruning tests. Don't start with a |
| // time of 0, as code doesn't generally expect it. |
| FastForwardBy(base::Days(1)); |
| } |
| |
| ~TransportSecurityStateTest() override { |
| SetTransportSecurityStateSourceForTesting(nullptr); |
| } |
| |
| void SetUp() override { crypto::EnsureOpenSSLInit(); } |
| |
| static void DisableStaticPins(TransportSecurityState* state) { |
| state->enable_static_pins_ = false; |
| } |
| |
| static void EnableStaticPins(TransportSecurityState* state) { |
| state->enable_static_pins_ = true; |
| } |
| |
| static void EnableStaticExpectCT(TransportSecurityState* state) { |
| state->enable_static_expect_ct_ = true; |
| } |
| |
| static HashValueVector GetSampleSPKIHashes() { |
| HashValueVector spki_hashes; |
| HashValue hash(HASH_VALUE_SHA256); |
| memset(hash.data(), 0, hash.size()); |
| spki_hashes.push_back(hash); |
| return spki_hashes; |
| } |
| |
| static HashValue GetSampleSPKIHash(uint8_t value) { |
| HashValue hash(HASH_VALUE_SHA256); |
| memset(hash.data(), value, hash.size()); |
| return hash; |
| } |
| |
| protected: |
| bool GetStaticDomainState(TransportSecurityState* state, |
| const std::string& host, |
| TransportSecurityState::STSState* sts_result, |
| TransportSecurityState::PKPState* pkp_result) { |
| bool ret = state->GetStaticSTSState(host, sts_result); |
| if (state->GetStaticPKPState(host, pkp_result)) |
| ret = true; |
| return ret; |
| } |
| |
| bool GetExpectCTState(TransportSecurityState* state, |
| const std::string& host, |
| TransportSecurityState::ExpectCTState* result) { |
| return state->GetStaticExpectCTState(host, result); |
| } |
| }; |
| |
| TEST_F(TransportSecurityStateTest, DomainNameOddities) { |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| // DNS suffix search tests. Some DNS resolvers allow a terminal "." to |
| // indicate not perform DNS suffix searching. Ensure that regardless |
| // of how this is treated at the resolver layer, or at the URL/origin |
| // layer (that is, whether they are treated as equivalent or distinct), |
| // ensure that for policy matching, something lacking a terminal "." |
| // is equivalent to something with a terminal "." |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.com")); |
| |
| state.AddHSTS("example.com", expiry, true /* include_subdomains */); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| // Trailing '.' should be equivalent; it's just a resolver hint |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com.")); |
| // Leading '.' should be invalid |
| EXPECT_FALSE(state.ShouldUpgradeToSSL(".example.com")); |
| // Subdomains should work regardless |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("sub.example.com")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("sub.example.com.")); |
| // But invalid subdomains should be rejected |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("sub..example.com")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("sub..example.com.")); |
| |
| // Now try the inverse form |
| TransportSecurityState state2; |
| state2.AddHSTS("example.net.", expiry, true /* include_subdomains */); |
| EXPECT_TRUE(state2.ShouldUpgradeToSSL("example.net.")); |
| EXPECT_TRUE(state2.ShouldUpgradeToSSL("example.net")); |
| EXPECT_TRUE(state2.ShouldUpgradeToSSL("sub.example.net.")); |
| EXPECT_TRUE(state2.ShouldUpgradeToSSL("sub.example.net")); |
| |
| // Finally, test weird things |
| TransportSecurityState state3; |
| state3.AddHSTS("", expiry, true /* include_subdomains */); |
| EXPECT_FALSE(state3.ShouldUpgradeToSSL("")); |
| EXPECT_FALSE(state3.ShouldUpgradeToSSL(".")); |
| EXPECT_FALSE(state3.ShouldUpgradeToSSL("...")); |
| // Make sure it didn't somehow apply HSTS to the world |
| EXPECT_FALSE(state3.ShouldUpgradeToSSL("example.org")); |
| |
| TransportSecurityState state4; |
| state4.AddHSTS(".", expiry, true /* include_subdomains */); |
| EXPECT_FALSE(state4.ShouldUpgradeToSSL("")); |
| EXPECT_FALSE(state4.ShouldUpgradeToSSL(".")); |
| EXPECT_FALSE(state4.ShouldUpgradeToSSL("...")); |
| EXPECT_FALSE(state4.ShouldUpgradeToSSL("example.org")); |
| |
| // Now do the same for preloaded entries |
| TransportSecurityState state5; |
| EXPECT_TRUE(state5.ShouldUpgradeToSSL("hsts-preloaded.test")); |
| EXPECT_TRUE(state5.ShouldUpgradeToSSL("hsts-preloaded.test.")); |
| EXPECT_FALSE(state5.ShouldUpgradeToSSL("hsts-preloaded..test")); |
| EXPECT_FALSE(state5.ShouldUpgradeToSSL("hsts-preloaded..test.")); |
| } |
| |
| TEST_F(TransportSecurityStateTest, SimpleMatches) { |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.com")); |
| bool include_subdomains = false; |
| state.AddHSTS("example.com", expiry, include_subdomains); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("example.com")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("foo.example.com")); |
| EXPECT_FALSE(state.ShouldSSLErrorsBeFatal("foo.example.com")); |
| } |
| |
| TEST_F(TransportSecurityStateTest, MatchesCase1) { |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.com")); |
| bool include_subdomains = false; |
| state.AddHSTS("EXample.coM", expiry, include_subdomains); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| } |
| |
| TEST_F(TransportSecurityStateTest, MatchesCase2) { |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| // Check dynamic entries |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("EXample.coM")); |
| bool include_subdomains = false; |
| state.AddHSTS("example.com", expiry, include_subdomains); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("EXample.coM")); |
| |
| // Check static entries |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("hStS-prelOAded.tEsT")); |
| EXPECT_TRUE( |
| state.ShouldUpgradeToSSL("inClude-subDOmaIns-hsts-prEloaDed.TesT")); |
| } |
| |
| TEST_F(TransportSecurityStateTest, SubdomainMatches) { |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.test")); |
| bool include_subdomains = true; |
| state.AddHSTS("example.test", expiry, include_subdomains); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.bar.example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.bar.baz.example.test")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("test")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("notexample.test")); |
| } |
| |
| // Tests that a more-specific HSTS rule without the includeSubDomains bit does |
| // not override a less-specific rule with includeSubDomains. Applicability is |
| // checked before specificity. See https://crbug.com/821811. |
| TEST_F(TransportSecurityStateTest, STSSubdomainNoOverride) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| const base::Time older = current_time - base::Seconds(1000); |
| |
| state.AddHSTS("example.test", expiry, true); |
| state.AddHSTS("foo.example.test", expiry, false); |
| |
| // The example.test rule applies to the entire domain, including subdomains of |
| // foo.example.test. |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("bar.foo.example.test")); |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("bar.foo.example.test")); |
| |
| // Expire the foo.example.test rule. |
| state.AddHSTS("foo.example.test", older, false); |
| |
| // The example.test rule still applies. |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("bar.foo.example.test")); |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("bar.foo.example.test")); |
| } |
| |
| // Tests that a more-specific HPKP rule overrides a less-specific rule |
| // with it, regardless of the includeSubDomains bit. Note this behavior does not |
| // match HSTS. See https://crbug.com/821811. |
| TEST_F(TransportSecurityStateTest, PKPSubdomainCarveout) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| const base::Time older = current_time - base::Seconds(1000); |
| |
| state.AddHPKP("example.test", expiry, true, GetSampleSPKIHashes(), |
| report_uri); |
| state.AddHPKP("foo.example.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| EXPECT_TRUE(state.HasPublicKeyPins("example.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("foo.example.test")); |
| |
| // The foo.example.test rule overrides the example1.test rule, so |
| // bar.foo.example.test has no HPKP state. |
| EXPECT_FALSE(state.HasPublicKeyPins("bar.foo.example.test")); |
| EXPECT_FALSE(state.ShouldSSLErrorsBeFatal("bar.foo.example.test")); |
| |
| // Expire the foo.example.test rule. |
| state.AddHPKP("foo.example.test", older, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| // Now the base example.test rule applies to bar.foo.example.test. |
| EXPECT_TRUE(state.HasPublicKeyPins("bar.foo.example.test")); |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("bar.foo.example.test")); |
| } |
| |
| TEST_F(TransportSecurityStateTest, FatalSSLErrors) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| state.AddHSTS("example1.test", expiry, false); |
| state.AddHPKP("example2.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| // The presense of either HSTS or HPKP is enough to make SSL errors fatal. |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("example1.test")); |
| EXPECT_TRUE(state.ShouldSSLErrorsBeFatal("example2.test")); |
| } |
| |
| // Tests that HPKP and HSTS state both expire. Also tests that expired entries |
| // are pruned. |
| TEST_F(TransportSecurityStateTest, Expiration) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| const base::Time older = current_time - base::Seconds(1000); |
| |
| // Note: this test assumes that inserting an entry with an expiration time in |
| // the past works and is pruned on query. |
| state.AddHSTS("example1.test", older, false); |
| EXPECT_TRUE(TransportSecurityState::STSStateIterator(state).HasNext()); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example1.test")); |
| // Querying |state| for a domain should flush out expired entries. |
| EXPECT_FALSE(TransportSecurityState::STSStateIterator(state).HasNext()); |
| |
| state.AddHPKP("example1.test", older, false, GetSampleSPKIHashes(), |
| report_uri); |
| EXPECT_TRUE(state.has_dynamic_pkp_state()); |
| EXPECT_FALSE(state.HasPublicKeyPins("example1.test")); |
| // Querying |state| for a domain should flush out expired entries. |
| EXPECT_FALSE(state.has_dynamic_pkp_state()); |
| |
| state.AddHSTS("example1.test", older, false); |
| state.AddHPKP("example1.test", older, false, GetSampleSPKIHashes(), |
| report_uri); |
| EXPECT_TRUE(TransportSecurityState::STSStateIterator(state).HasNext()); |
| EXPECT_TRUE(state.has_dynamic_pkp_state()); |
| EXPECT_FALSE(state.ShouldSSLErrorsBeFatal("example1.test")); |
| // Querying |state| for a domain should flush out expired entries. |
| EXPECT_FALSE(TransportSecurityState::STSStateIterator(state).HasNext()); |
| EXPECT_FALSE(state.has_dynamic_pkp_state()); |
| |
| // Test that HSTS can outlive HPKP. |
| state.AddHSTS("example1.test", expiry, false); |
| state.AddHPKP("example1.test", older, false, GetSampleSPKIHashes(), |
| report_uri); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example1.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example1.test")); |
| |
| // Test that HPKP can outlive HSTS. |
| state.AddHSTS("example2.test", older, false); |
| state.AddHPKP("example2.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example2.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("example2.test")); |
| } |
| |
| // Tests that HPKP and HSTS state are queried independently for subdomain |
| // matches. |
| TEST_F(TransportSecurityStateTest, IndependentSubdomain) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| state.AddHSTS("example1.test", expiry, true); |
| state.AddHPKP("example1.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| state.AddHSTS("example2.test", expiry, false); |
| state.AddHPKP("example2.test", expiry, true, GetSampleSPKIHashes(), |
| report_uri); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example1.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("foo.example1.test")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("foo.example2.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("foo.example2.test")); |
| } |
| |
| // Tests that HPKP and HSTS state are inserted and overridden independently. |
| TEST_F(TransportSecurityStateTest, IndependentInsertion) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| // Place an includeSubdomains HSTS entry below a normal HPKP entry. |
| state.AddHSTS("example1.test", expiry, true); |
| state.AddHPKP("foo.example1.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example1.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("foo.example1.test")); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example1.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example1.test")); |
| |
| // Drop the includeSubdomains from the HSTS entry. |
| state.AddHSTS("example1.test", expiry, false); |
| |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("foo.example1.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("foo.example1.test")); |
| |
| // Place an includeSubdomains HPKP entry below a normal HSTS entry. |
| state.AddHSTS("foo.example2.test", expiry, false); |
| state.AddHPKP("example2.test", expiry, true, GetSampleSPKIHashes(), |
| report_uri); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example2.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("foo.example2.test")); |
| |
| // Drop the includeSubdomains from the HSTS entry. |
| state.AddHPKP("example2.test", expiry, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("foo.example2.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("foo.example2.test")); |
| } |
| |
| // Tests that GetDynamic[PKP|STS]State returns the correct data and that the |
| // states are not mixed together. |
| TEST_F(TransportSecurityStateTest, DynamicDomainState) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry1 = current_time + base::Seconds(1000); |
| const base::Time expiry2 = current_time + base::Seconds(2000); |
| |
| state.AddHSTS("example.com", expiry1, true); |
| state.AddHPKP("foo.example.com", expiry2, false, GetSampleSPKIHashes(), |
| report_uri); |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| ASSERT_TRUE(state.GetDynamicSTSState("foo.example.com", &sts_state)); |
| ASSERT_TRUE(state.GetDynamicPKPState("foo.example.com", &pkp_state)); |
| EXPECT_TRUE(sts_state.ShouldUpgradeToSSL()); |
| EXPECT_TRUE(pkp_state.HasPublicKeyPins()); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_FALSE(pkp_state.include_subdomains); |
| EXPECT_EQ(expiry1, sts_state.expiry); |
| EXPECT_EQ(expiry2, pkp_state.expiry); |
| EXPECT_EQ("example.com", sts_state.domain); |
| EXPECT_EQ("foo.example.com", pkp_state.domain); |
| } |
| |
| // Tests that new pins always override previous pins. This should be true for |
| // both pins at the same domain or includeSubdomains pins at a parent domain. |
| TEST_F(TransportSecurityStateTest, NewPinsOverride) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState state; |
| TransportSecurityState::PKPState pkp_state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| HashValue hash1(HASH_VALUE_SHA256); |
| memset(hash1.data(), 0x01, hash1.size()); |
| HashValue hash2(HASH_VALUE_SHA256); |
| memset(hash2.data(), 0x02, hash1.size()); |
| HashValue hash3(HASH_VALUE_SHA256); |
| memset(hash3.data(), 0x03, hash1.size()); |
| |
| state.AddHPKP("example.com", expiry, true, HashValueVector(1, hash1), |
| report_uri); |
| |
| ASSERT_TRUE(state.GetDynamicPKPState("foo.example.com", &pkp_state)); |
| ASSERT_EQ(1u, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], hash1); |
| |
| state.AddHPKP("foo.example.com", expiry, false, HashValueVector(1, hash2), |
| report_uri); |
| |
| ASSERT_TRUE(state.GetDynamicPKPState("foo.example.com", &pkp_state)); |
| ASSERT_EQ(1u, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], hash2); |
| |
| state.AddHPKP("foo.example.com", expiry, false, HashValueVector(1, hash3), |
| report_uri); |
| |
| ASSERT_TRUE(state.GetDynamicPKPState("foo.example.com", &pkp_state)); |
| ASSERT_EQ(1u, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], hash3); |
| } |
| |
| TEST_F(TransportSecurityStateTest, DeleteAllDynamicDataBetween) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| const base::Time older = current_time - base::Seconds(1000); |
| |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example.com")); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.com", NetworkIsolationKey(), &expect_ct_state)); |
| bool include_subdomains = false; |
| state.AddHSTS("example.com", expiry, include_subdomains); |
| state.AddHPKP("example.com", expiry, include_subdomains, |
| GetSampleSPKIHashes(), GURL()); |
| state.AddExpectCT("example.com", expiry, true, GURL(), NetworkIsolationKey()); |
| |
| state.DeleteAllDynamicDataBetween(expiry, base::Time::Max(), |
| base::DoNothing()); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_TRUE(state.HasPublicKeyPins("example.com")); |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example.com", NetworkIsolationKey(), &expect_ct_state)); |
| state.DeleteAllDynamicDataBetween(older, current_time, base::DoNothing()); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_TRUE(state.HasPublicKeyPins("example.com")); |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example.com", NetworkIsolationKey(), &expect_ct_state)); |
| state.DeleteAllDynamicDataBetween(base::Time(), current_time, |
| base::DoNothing()); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_TRUE(state.HasPublicKeyPins("example.com")); |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example.com", NetworkIsolationKey(), &expect_ct_state)); |
| state.DeleteAllDynamicDataBetween(older, base::Time::Max(), |
| base::DoNothing()); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example.com")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example.com")); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.com", NetworkIsolationKey(), &expect_ct_state)); |
| |
| // Dynamic data in |state| should be empty now. |
| EXPECT_FALSE(TransportSecurityState::STSStateIterator(state).HasNext()); |
| EXPECT_FALSE(state.has_dynamic_pkp_state()); |
| EXPECT_FALSE(TransportSecurityState::ExpectCTStateIterator(state).HasNext()); |
| } |
| |
| TEST_F(TransportSecurityStateTest, DeleteDynamicDataForHost) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /* enabled_features */ |
| {TransportSecurityState::kDynamicExpectCTFeature, |
| features::kPartitionExpectCTStateByNetworkIsolationKey}, |
| /* disabled_features */ |
| {}); |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| bool include_subdomains = false; |
| |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| state.AddHSTS("example1.test", expiry, include_subdomains); |
| state.AddHPKP("example1.test", expiry, include_subdomains, |
| GetSampleSPKIHashes(), GURL()); |
| state.AddExpectCT("example1.test", expiry, true, GURL(), |
| NetworkIsolationKey()); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("example1.test")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example2.test")); |
| EXPECT_TRUE(state.HasPublicKeyPins("example1.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example2.test")); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example1.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example2.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example1.test", network_isolation_key, &expect_ct_state)); |
| state.AddExpectCT("example1.test", expiry, true, GURL(), |
| network_isolation_key); |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example1.test", network_isolation_key, &expect_ct_state)); |
| |
| EXPECT_TRUE(state.DeleteDynamicDataForHost("example1.test")); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL("example1.test")); |
| EXPECT_FALSE(state.HasPublicKeyPins("example1.test")); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example1.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example1.test", network_isolation_key, &expect_ct_state)); |
| } |
| |
| TEST_F(TransportSecurityStateTest, LongNames) { |
| TransportSecurityState state; |
| const char kLongName[] = |
| "lookupByWaveIdHashAndWaveIdIdAndWaveIdDomainAndWaveletIdIdAnd" |
| "WaveletIdDomainAndBlipBlipid"; |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| // Just checks that we don't hit a NOTREACHED |
| EXPECT_FALSE(state.GetStaticSTSState(kLongName, &sts_state)); |
| EXPECT_FALSE(state.GetStaticPKPState(kLongName, &pkp_state)); |
| EXPECT_FALSE(state.GetDynamicSTSState(kLongName, &sts_state)); |
| EXPECT_FALSE(state.GetDynamicPKPState(kLongName, &pkp_state)); |
| } |
| |
| static bool AddHash(const std::string& type_and_base64, HashValueVector* out) { |
| HashValue hash; |
| if (!hash.FromString(type_and_base64)) |
| return false; |
| |
| out->push_back(hash); |
| return true; |
| } |
| |
| TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { |
| HashValueVector good_hashes, bad_hashes; |
| |
| for (size_t i = 0; kGoodPath[i]; i++) { |
| EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); |
| } |
| for (size_t i = 0; kBadPath[i]; i++) { |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| } |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| |
| TransportSecurityState::PKPState pkp_state; |
| EXPECT_TRUE(state.GetStaticPKPState("no-rejected-pins-pkp.preloaded.test", |
| &pkp_state)); |
| EXPECT_TRUE(pkp_state.HasPublicKeyPins()); |
| |
| std::string failure_log; |
| EXPECT_TRUE(pkp_state.CheckPublicKeyPins(good_hashes, &failure_log)); |
| EXPECT_FALSE(pkp_state.CheckPublicKeyPins(bad_hashes, &failure_log)); |
| } |
| |
| // Tests that pinning violations on preloaded pins trigger reports when |
| // the preloaded pin contains a report URI. |
| TEST_F(TransportSecurityStateTest, PreloadedPKPReportUri) { |
| const char kPreloadedPinDomain[] = "with-report-uri-pkp.preloaded.test"; |
| HostPortPair host_port_pair(kPreloadedPinDomain, kPort); |
| net::NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| |
| TransportSecurityState state; |
| MockCertificateReportSender mock_report_sender; |
| state.SetReportSender(&mock_report_sender); |
| |
| EnableStaticPins(&state); |
| |
| TransportSecurityState::PKPState pkp_state; |
| ASSERT_TRUE(state.GetStaticPKPState(kPreloadedPinDomain, &pkp_state)); |
| ASSERT_TRUE(pkp_state.HasPublicKeyPins()); |
| |
| GURL report_uri = pkp_state.report_uri; |
| ASSERT_TRUE(report_uri.is_valid()); |
| ASSERT_FALSE(report_uri.is_empty()); |
| |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter, as long as they are not the real google.com |
| // certs in the pins. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector bad_hashes; |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| // Trigger a violation and check that it sends a report. |
| std::string failure_log; |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri()); |
| |
| std::string report = mock_report_sender.latest_report(); |
| ASSERT_FALSE(report.empty()); |
| EXPECT_EQ("application/json; charset=utf-8", |
| mock_report_sender.latest_content_type()); |
| ASSERT_NO_FATAL_FAILURE(CheckHPKPReport( |
| report, host_port_pair, pkp_state.include_subdomains, pkp_state.domain, |
| cert1.get(), cert2.get(), pkp_state.spki_hashes)); |
| EXPECT_EQ(network_isolation_key, |
| mock_report_sender.latest_network_isolation_key()); |
| } |
| |
| // Tests that report URIs are thrown out if they point to the same host, |
| // over HTTPS, for which a pin was violated. |
| TEST_F(TransportSecurityStateTest, HPKPReportUriToSameHost) { |
| HostPortPair host_port_pair(kHost, kPort); |
| GURL https_report_uri("https://example.test/report"); |
| GURL http_report_uri("http://example.test/report"); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| TransportSecurityState state; |
| MockCertificateReportSender mock_report_sender; |
| state.SetReportSender(&mock_report_sender); |
| |
| const base::Time current_time = base::Time::Now(); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| HashValueVector good_hashes; |
| for (size_t i = 0; kGoodPath[i]; i++) |
| EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); |
| |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter, as long as they don't match the certs in the pins. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector bad_hashes; |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| state.AddHPKP(kHost, expiry, true, good_hashes, https_report_uri); |
| |
| // Trigger a violation and check that it does not send a report |
| // because the report-uri is HTTPS and same-host as the pins. |
| std::string failure_log; |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| EXPECT_TRUE(mock_report_sender.latest_report_uri().is_empty()); |
| |
| // An HTTP report uri to the same host should be okay. |
| state.AddHPKP("example.test", expiry, true, good_hashes, http_report_uri); |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| EXPECT_EQ(http_report_uri, mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(network_isolation_key, |
| mock_report_sender.latest_network_isolation_key()); |
| } |
| |
| // Tests that static (preloaded) expect CT state is read correctly. |
| TEST_F(TransportSecurityStateTest, PreloadedExpectCT) { |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_TRUE( |
| GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state)); |
| EXPECT_EQ(GURL(kExpectCTStaticReportURI), expect_ct_state.report_uri); |
| EXPECT_FALSE( |
| GetExpectCTState(&state, "hsts-preloaded.test", &expect_ct_state)); |
| } |
| |
| // Tests that the Expect CT reporter is not notified for invalid or absent |
| // header values. |
| TEST_F(TransportSecurityStateTest, InvalidExpectCTHeader) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("", host_port, ssl_info, NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| state.ProcessExpectCTHeader("blah blah", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is only notified about certificates |
| // chaining to public roots. |
| TEST_F(TransportSecurityStateTest, ExpectCTNonPublicRoot) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| ssl_info.is_issued_by_known_root = false; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| ssl_info.is_issued_by_known_root = true; |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified when compliance |
| // details aren't available. |
| TEST_F(TransportSecurityStateTest, ExpectCTComplianceNotAvailable) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified about compliant |
| // connections. |
| TEST_F(TransportSecurityStateTest, ExpectCTCompliantCert) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified for preloaded Expect-CT |
| // when the build is not timely. |
| TEST_F(TransportSecurityStateTest, PreloadedExpectCTBuildNotTimely) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| // Sanity-check that the reporter is notified if the build is timely and the |
| // connection is not compliant. |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified for dynamic Expect-CT when |
| // the build is not timely. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTBuildNotTimely) { |
| HostPortPair host_port("example.test", 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| const char kHeader[] = "max-age=10, report-uri=http://report.test"; |
| state.ProcessExpectCTHeader(kHeader, host_port, ssl_info, |
| NetworkIsolationKey()); |
| |
| // No report should have been sent and the state should not have been saved. |
| EXPECT_EQ(0u, reporter.num_failures()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| |
| // Sanity-check that the reporter is notified if the build is timely and the |
| // connection is not compliant. |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| state.ProcessExpectCTHeader(kHeader, host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified for a site that |
| // isn't preloaded. |
| TEST_F(TransportSecurityStateTest, ExpectCTNotPreloaded) { |
| HostPortPair host_port("not-expect-ct-preloaded.test", 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| host_port.set_host(kExpectCTStaticHostname); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect CT reporter is notified for noncompliant |
| // connections. |
| TEST_F(TransportSecurityStateTest, ExpectCTReporter) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert1); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, |
| &ssl_info.signed_certificate_timestamps); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| network_isolation_key); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ(host_port.host(), reporter.host_port_pair().host()); |
| EXPECT_EQ(host_port.port(), reporter.host_port_pair().port()); |
| EXPECT_TRUE(reporter.expiration().is_null()); |
| EXPECT_EQ(GURL(kExpectCTStaticReportURI), reporter.report_uri()); |
| EXPECT_EQ(cert1.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.validated_certificate_chain()); |
| ASSERT_EQ(1u, ssl_info.signed_certificate_timestamps.size()); |
| ASSERT_EQ(ssl_info.signed_certificate_timestamps.size(), |
| reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(ssl_info.signed_certificate_timestamps[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(ssl_info.signed_certificate_timestamps[0].sct, |
| reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| } |
| |
| // Tests that the Expect CT reporter is not notified for repeated noncompliant |
| // connections to the same preloaded host. |
| TEST_F(TransportSecurityStateTest, RepeatedExpectCTReportsForStaticExpectCT) { |
| HostPortPair host_port(kExpectCTStaticHostname, 443); |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl_info.unverified_cert = cert1; |
| ssl_info.cert = cert2; |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, |
| &ssl_info.signed_certificate_timestamps); |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| |
| // After processing a second header, the report should not be sent again. |
| state.ProcessExpectCTHeader("preload", host_port, ssl_info, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Simple test for the HSTS preload process. The trie (generated from |
| // transport_security_state_static_unittest1.json) contains 1 entry. Test that |
| // the lookup methods can find the entry and correctly decode the different |
| // preloaded states (HSTS, HPKP, and Expect-CT). |
| TEST_F(TransportSecurityStateTest, DecodePreloadedSingle) { |
| SetTransportSecurityStateSourceForTesting(&test1::kHSTSSource); |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticPins(&state); |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "hsts.example.com", &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL(), pkp_state.report_uri); |
| ASSERT_EQ(1u, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x1)); |
| ASSERT_EQ(1u, pkp_state.bad_spki_hashes.size()); |
| EXPECT_EQ(pkp_state.bad_spki_hashes[0], GetSampleSPKIHash(0x2)); |
| |
| TransportSecurityState::ExpectCTState ct_state; |
| EXPECT_FALSE(GetExpectCTState(&state, "hsts.example.com", &ct_state)); |
| } |
| |
| // More advanced test for the HSTS preload process where the trie (generated |
| // from transport_security_state_static_unittest2.json) contains multiple |
| // entries with a common prefix. Test that the lookup methods can find all |
| // entries and correctly decode the different preloaded states (HSTS, HPKP, |
| // and Expect-CT) for each entry. |
| TEST_F(TransportSecurityStateTest, DecodePreloadedMultiplePrefix) { |
| SetTransportSecurityStateSourceForTesting(&test2::kHSTSSource); |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticPins(&state); |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| TransportSecurityState::ExpectCTState ct_state; |
| |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "hsts.example.com", &sts_state, &pkp_state)); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state == TransportSecurityState::PKPState()); |
| EXPECT_FALSE(GetExpectCTState(&state, "hsts.example.com", &ct_state)); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "hpkp.example.com", &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state == TransportSecurityState::STSState()); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL("https://report.example.com/hpkp-upload"), |
| pkp_state.report_uri); |
| EXPECT_EQ(1U, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x1)); |
| EXPECT_EQ(0U, pkp_state.bad_spki_hashes.size()); |
| EXPECT_FALSE(GetExpectCTState(&state, "hpkp.example.com", &ct_state)); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_FALSE(GetStaticDomainState(&state, "expect-ct.example.com", &sts_state, |
| &pkp_state)); |
| EXPECT_TRUE(sts_state == TransportSecurityState::STSState()); |
| EXPECT_TRUE(pkp_state == TransportSecurityState::PKPState()); |
| EXPECT_TRUE(GetExpectCTState(&state, "expect-ct.example.com", &ct_state)); |
| EXPECT_EQ(GURL("https://report.example.com/ct-upload"), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "mix.example.com", &sts_state, &pkp_state)); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL(), pkp_state.report_uri); |
| EXPECT_EQ(1U, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x2)); |
| EXPECT_EQ(1U, pkp_state.bad_spki_hashes.size()); |
| EXPECT_EQ(pkp_state.bad_spki_hashes[0], GetSampleSPKIHash(0x1)); |
| EXPECT_TRUE(GetExpectCTState(&state, "mix.example.com", &ct_state)); |
| EXPECT_EQ(GURL("https://report.example.com/ct-upload-alt"), |
| ct_state.report_uri); |
| } |
| |
| // More advanced test for the HSTS preload process where the trie (generated |
| // from transport_security_state_static_unittest3.json) contains a mix of |
| // entries. Some entries share a prefix with the prefix also having its own |
| // preloaded state while others share no prefix. This results in a trie with |
| // several different internal structures. Test that the lookup methods can find |
| // all entries and correctly decode the different preloaded states (HSTS, HPKP, |
| // and Expect-CT) for each entry. |
| TEST_F(TransportSecurityStateTest, DecodePreloadedMultipleMix) { |
| SetTransportSecurityStateSourceForTesting(&test3::kHSTSSource); |
| |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticPins(&state); |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| TransportSecurityState::ExpectCTState ct_state; |
| |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "example.com", &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state == TransportSecurityState::PKPState()); |
| EXPECT_FALSE(GetExpectCTState(&state, "example.com", &ct_state)); |
| EXPECT_EQ(GURL(), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "hpkp.example.com", &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state == TransportSecurityState::STSState()); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL("https://report.example.com/hpkp-upload"), |
| pkp_state.report_uri); |
| EXPECT_EQ(1U, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x1)); |
| EXPECT_EQ(0U, pkp_state.bad_spki_hashes.size()); |
| EXPECT_FALSE(GetExpectCTState(&state, "hpkp.example.com", &ct_state)); |
| EXPECT_EQ(GURL(), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "example.org", &sts_state, &pkp_state)); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state == TransportSecurityState::PKPState()); |
| EXPECT_TRUE(GetExpectCTState(&state, "example.org", &ct_state)); |
| EXPECT_EQ(GURL("https://report.example.org/ct-upload"), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "badssl.com", &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state == TransportSecurityState::STSState()); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL("https://report.example.com/hpkp-upload"), |
| pkp_state.report_uri); |
| EXPECT_EQ(1U, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x1)); |
| EXPECT_EQ(0U, pkp_state.bad_spki_hashes.size()); |
| EXPECT_FALSE(GetExpectCTState(&state, "badssl.com", &ct_state)); |
| EXPECT_EQ(GURL(), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| EXPECT_TRUE( |
| GetStaticDomainState(&state, "mix.badssl.com", &sts_state, &pkp_state)); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state.include_subdomains); |
| EXPECT_EQ(GURL(), pkp_state.report_uri); |
| EXPECT_EQ(1U, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(pkp_state.spki_hashes[0], GetSampleSPKIHash(0x2)); |
| EXPECT_EQ(1U, pkp_state.bad_spki_hashes.size()); |
| EXPECT_EQ(pkp_state.bad_spki_hashes[0], GetSampleSPKIHash(0x1)); |
| EXPECT_TRUE(GetExpectCTState(&state, "mix.badssl.com", &ct_state)); |
| EXPECT_EQ(GURL("https://report.example.com/ct-upload"), ct_state.report_uri); |
| |
| sts_state = TransportSecurityState::STSState(); |
| pkp_state = TransportSecurityState::PKPState(); |
| ct_state = TransportSecurityState::ExpectCTState(); |
| |
| // This should be a simple entry in the context of |
| // TrieWriter::IsSimpleEntry(). |
| EXPECT_TRUE(GetStaticDomainState(&state, "simple-entry.example.com", |
| &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_EQ(TransportSecurityState::STSState::MODE_FORCE_HTTPS, |
| sts_state.upgrade_mode); |
| EXPECT_TRUE(pkp_state == TransportSecurityState::PKPState()); |
| EXPECT_FALSE(GetExpectCTState(&state, "simple-entry.example.com", &ct_state)); |
| } |
| |
| TEST_F(TransportSecurityStateTest, HstsHostBypassList) { |
| SetTransportSecurityStateSourceForTesting(&test_default::kHSTSSource); |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| std::string preloaded_tld = "example"; |
| std::string subdomain = "sub.example"; |
| |
| { |
| TransportSecurityState state; |
| // Check that "example" is preloaded with subdomains. |
| EXPECT_TRUE(state.ShouldUpgradeToSSL(preloaded_tld)); |
| EXPECT_TRUE(state.ShouldUpgradeToSSL(subdomain)); |
| } |
| |
| { |
| // Add "example" to the bypass list. |
| TransportSecurityState state({preloaded_tld}); |
| EXPECT_FALSE(state.ShouldUpgradeToSSL(preloaded_tld)); |
| // The preloaded entry should still apply to the subdomain. |
| EXPECT_TRUE(state.ShouldUpgradeToSSL(subdomain)); |
| } |
| } |
| |
| // Tests that TransportSecurityState always consults the RequireCTDelegate, |
| // if supplied. |
| TEST_F(TransportSecurityStateTest, RequireCTConsultsDelegate) { |
| using ::testing::_; |
| using ::testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| // Dummy cert to use as the validation chain. The contents do not matter. |
| scoped_refptr<X509Certificate> cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert); |
| |
| HashValueVector hashes; |
| hashes.push_back( |
| HashValue(X509Certificate::CalculateFingerprint256(cert->cert_buffer()))); |
| |
| // If CT is required, then the requirements are not met if the CT policy |
| // wasn't met, but are met if the policy was met or the build was out of |
| // date. |
| { |
| TransportSecurityState state; |
| const TransportSecurityState::CTRequirementsStatus original_status = |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey()); |
| |
| MockRequireCTDelegate always_require_delegate; |
| EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::REQUIRED)); |
| state.SetRequireCTDelegate(&always_require_delegate); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| NetworkIsolationKey())); |
| |
| state.SetRequireCTDelegate(nullptr); |
| EXPECT_EQ( |
| original_status, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| } |
| |
| // If CT is not required, then regardless of the CT state for the host, |
| // it should indicate CT is not required. |
| { |
| TransportSecurityState state; |
| const TransportSecurityState::CTRequirementsStatus original_status = |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey()); |
| |
| MockRequireCTDelegate never_require_delegate; |
| EXPECT_CALL(never_require_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| state.SetRequireCTDelegate(&never_require_delegate); |
| EXPECT_EQ( |
| TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| |
| state.SetRequireCTDelegate(nullptr); |
| EXPECT_EQ( |
| original_status, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| } |
| |
| // If the Delegate is in the default state, then it should return the same |
| // result as if there was no delegate in the first place. |
| { |
| TransportSecurityState state; |
| const TransportSecurityState::CTRequirementsStatus original_status = |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey()); |
| |
| MockRequireCTDelegate default_require_ct_delegate; |
| EXPECT_CALL(default_require_ct_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::DEFAULT)); |
| state.SetRequireCTDelegate(&default_require_ct_delegate); |
| EXPECT_EQ( |
| original_status, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| |
| state.SetRequireCTDelegate(nullptr); |
| EXPECT_EQ( |
| original_status, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| } |
| } |
| |
| enum class CTEmergencyDisableSwitchKind { |
| kFinchDrivenFeature, |
| kComponentUpdaterDrivenSwitch, |
| }; |
| |
| class CTEmergencyDisableTest |
| : public TransportSecurityStateTest, |
| public testing::WithParamInterface<CTEmergencyDisableSwitchKind> { |
| public: |
| void SetUp() override { |
| if (GetParam() == |
| CTEmergencyDisableSwitchKind::kComponentUpdaterDrivenSwitch) { |
| state_.SetCTEmergencyDisabled(true); |
| scoped_feature_list_.Init(); |
| } else { |
| ASSERT_EQ(GetParam(), CTEmergencyDisableSwitchKind::kFinchDrivenFeature); |
| scoped_feature_list_.InitAndDisableFeature( |
| TransportSecurityState::kCertificateTransparencyEnforcement); |
| } |
| } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| TransportSecurityState state_; |
| }; |
| |
| // Tests that the emergency disable flags cause CT to stop being required |
| // regardless of host or delegate status. |
| TEST_P(CTEmergencyDisableTest, CTEmergencyDisable) { |
| using ::testing::_; |
| using ::testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| // Dummy cert to use as the validation chain. The contents do not matter. |
| scoped_refptr<X509Certificate> cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert); |
| |
| HashValueVector hashes; |
| hashes.push_back( |
| HashValue(X509Certificate::CalculateFingerprint256(cert->cert_buffer()))); |
| |
| MockRequireCTDelegate always_require_delegate; |
| EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::REQUIRED)); |
| state_.SetRequireCTDelegate(&always_require_delegate); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state_.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state_.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state_.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state_.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| NetworkIsolationKey())); |
| |
| state_.SetRequireCTDelegate(nullptr); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state_.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, cert.get(), |
| cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| CTEmergencyDisable, |
| CTEmergencyDisableTest, |
| testing::Values(CTEmergencyDisableSwitchKind::kComponentUpdaterDrivenSwitch, |
| CTEmergencyDisableSwitchKind::kFinchDrivenFeature)); |
| |
| // Tests that the if the CT log list last update time is set, it is used for |
| // enforcement decisions. |
| TEST_F(TransportSecurityStateTest, CTTimestampUpdate) { |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticExpectCT(&state); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| // Initially the preloaded host should require CT. |
| EXPECT_TRUE( |
| GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state)); |
| |
| // Change the last updated time to a value greater than 10 weeks. |
| // We use a close value (70 days + 1 hour ago) to ensure rounding behavior is |
| // working properly. |
| state.SetCTLogListUpdateTime(base::Time::Now() - |
| (base::Days(70) + base::Hours(1))); |
| // CT should no longer be required. |
| EXPECT_FALSE( |
| GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state)); |
| |
| // CT should once again be required after the log list is newer than 70 days. |
| state.SetCTLogListUpdateTime(base::Time::Now() - |
| (base::Days(70) - base::Hours(1))); |
| EXPECT_TRUE( |
| GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state)); |
| } |
| |
| // Tests that Certificate Transparency is required for Symantec-issued |
| // certificates, unless the certificate was issued prior to 1 June 2016 |
| // or the issuing CA is permitted as independently operated. |
| TEST_F(TransportSecurityStateTest, RequireCTForSymantec) { |
| // Test certificates before and after the 1 June 2016 deadline. |
| scoped_refptr<X509Certificate> before_cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "pre_june_2016.pem"); |
| ASSERT_TRUE(before_cert); |
| scoped_refptr<X509Certificate> after_cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "post_june_2016.pem"); |
| ASSERT_TRUE(after_cert); |
| |
| const SHA256HashValue symantec_hash_value = { |
| {0xb2, 0xde, 0xf5, 0x36, 0x2a, 0xd3, 0xfa, 0xcd, 0x04, 0xbd, 0x29, |
| 0x04, 0x7a, 0x43, 0x84, 0x4f, 0x76, 0x70, 0x34, 0xea, 0x48, 0x92, |
| 0xf8, 0x0e, 0x56, 0xbe, 0xe6, 0x90, 0x24, 0x3e, 0x25, 0x02}}; |
| const SHA256HashValue google_hash_value = { |
| {0xec, 0x72, 0x29, 0x69, 0xcb, 0x64, 0x20, 0x0a, 0xb6, 0x63, 0x8f, |
| 0x68, 0xac, 0x53, 0x8e, 0x40, 0xab, 0xab, 0x5b, 0x19, 0xa6, 0x48, |
| 0x56, 0x61, 0x04, 0x2a, 0x10, 0x61, 0xc4, 0x61, 0x27, 0x76}}; |
| |
| TransportSecurityState state; |
| |
| HashValueVector hashes; |
| hashes.push_back(HashValue(symantec_hash_value)); |
| |
| // Certificates issued by Symantec prior to 1 June 2016 should not |
| // be required to be disclosed via CT. |
| EXPECT_EQ( |
| TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| |
| // ... but certificates issued after 1 June 2016 are required to be... |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| |
| // ... unless they were issued by an excluded intermediate. |
| hashes.push_back(HashValue(google_hash_value)); |
| EXPECT_EQ( |
| TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| |
| // And other certificates should remain unaffected. |
| SHA256HashValue unrelated_hash_value = {{0x01, 0x02}}; |
| HashValueVector unrelated_hashes; |
| unrelated_hashes.push_back(HashValue(unrelated_hash_value)); |
| |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, unrelated_hashes, |
| before_cert.get(), before_cert.get(), |
| SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, unrelated_hashes, |
| after_cert.get(), after_cert.get(), |
| SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| } |
| |
| // Tests that Certificate Transparency is required for all of the Symantec |
| // Managed CAs, regardless of when the certificate was issued. |
| TEST_F(TransportSecurityStateTest, RequireCTForSymantecManagedCAs) { |
| const SHA256HashValue symantec_hash_value = { |
| {0xb2, 0xde, 0xf5, 0x36, 0x2a, 0xd3, 0xfa, 0xcd, 0x04, 0xbd, 0x29, |
| 0x04, 0x7a, 0x43, 0x84, 0x4f, 0x76, 0x70, 0x34, 0xea, 0x48, 0x92, |
| 0xf8, 0x0e, 0x56, 0xbe, 0xe6, 0x90, 0x24, 0x3e, 0x25, 0x02}}; |
| const SHA256HashValue managed_hash_value = { |
| {0x7c, 0xac, 0x9a, 0x0f, 0xf3, 0x15, 0x38, 0x77, 0x50, 0xba, 0x8b, |
| 0xaf, 0xdb, 0x1c, 0x2b, 0xc2, 0x9b, 0x3f, 0x0b, 0xba, 0x16, 0x36, |
| 0x2c, 0xa9, 0x3a, 0x90, 0xf8, 0x4d, 0xa2, 0xdf, 0x5f, 0x3e}}; |
| |
| TransportSecurityState state; |
| |
| HashValueVector hashes; |
| hashes.push_back(HashValue(symantec_hash_value)); |
| hashes.push_back(HashValue(managed_hash_value)); |
| |
| // All certificates, both before and after the pre-existing 1 June 2016 |
| // date, are expected to be compliant. |
| scoped_refptr<X509Certificate> before_cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "pre_june_2016.pem"); |
| ASSERT_TRUE(before_cert); |
| |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, before_cert.get(), |
| before_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| |
| scoped_refptr<X509Certificate> after_cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "post_june_2016.pem"); |
| ASSERT_TRUE(after_cert); |
| |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| NetworkIsolationKey())); |
| EXPECT_EQ( |
| TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("www.example.com", 443), true, hashes, after_cert.get(), |
| after_cert.get(), SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| } |
| |
| // Tests that dynamic Expect-CT state is cleared from ClearDynamicData(). |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTStateCleared) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const std::string host("example.test"); |
| TransportSecurityState state; |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| const base::Time current_time = base::Time::Now(); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| state.AddExpectCT(host, expiry, true, GURL(), NetworkIsolationKey()); |
| EXPECT_TRUE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| EXPECT_TRUE(expect_ct_state.enforce); |
| EXPECT_TRUE(expect_ct_state.report_uri.is_empty()); |
| EXPECT_EQ(expiry, expect_ct_state.expiry); |
| |
| state.ClearDynamicData(); |
| EXPECT_FALSE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| } |
| |
| // Tests that dynamic Expect-CT state can be added and retrieved. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTState) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const std::string host("example.test"); |
| TransportSecurityState state; |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| const base::Time current_time = base::Time::Now(); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| // Test that Expect-CT state can be added and retrieved. |
| state.AddExpectCT(host, expiry, true, GURL(), NetworkIsolationKey()); |
| EXPECT_TRUE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| EXPECT_TRUE(expect_ct_state.enforce); |
| EXPECT_TRUE(expect_ct_state.report_uri.is_empty()); |
| EXPECT_EQ(expiry, expect_ct_state.expiry); |
| |
| // Test that Expect-CT can be updated (e.g. by changing |enforce| to false and |
| // adding a report-uri). |
| const GURL report_uri("https://example-report.test"); |
| state.AddExpectCT(host, expiry, false, report_uri, NetworkIsolationKey()); |
| EXPECT_TRUE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| EXPECT_FALSE(expect_ct_state.enforce); |
| EXPECT_EQ(report_uri, expect_ct_state.report_uri); |
| EXPECT_EQ(expiry, expect_ct_state.expiry); |
| |
| // Test that Expect-CT state is discarded when expired. |
| state.AddExpectCT(host, current_time - base::Seconds(1000), true, report_uri, |
| NetworkIsolationKey()); |
| EXPECT_FALSE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| } |
| |
| // Tests that the Expect-CT reporter is not notified for repeated dynamic |
| // Expect-CT violations for the same host/port. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTDeduping) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| SignedCertificateTimestampAndStatusList sct_list; |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| base::Time now = base::Time::Now(); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_EQ(GURL("http://foo.test"), expect_ct_state.report_uri); |
| EXPECT_TRUE(expect_ct_state.enforce); |
| EXPECT_LT(now, expect_ct_state.expiry); |
| // No report should be sent when the header was processed over a connection |
| // that complied with CT policy. |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| // The first time the host fails to meet CT requirements, a report should be |
| // sent. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| |
| // The second time it fails to meet CT requirements, a report should not be |
| // sent. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect-CT reporter is not notified for CT-compliant |
| // connections. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTCompliantConnection) { |
| const char kHeader[] = "max-age=123,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| SignedCertificateTimestampAndStatusList sct_list; |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| |
| // No report should be sent when the header was processed over a connection |
| // that complied with CT policy. |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| NetworkIsolationKey())); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| } |
| |
| // Tests that the Expect-CT reporter is not notified when the Expect-CT header |
| // is received repeatedly over non-compliant connections. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTHeaderProcessingDeduping) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| // The first time the header was received over a connection that failed to |
| // meet CT requirements, a report should be sent. |
| EXPECT_EQ(1u, reporter.num_failures()); |
| |
| // The second time the header was received, no report should be sent. |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| } |
| |
| // Tests that dynamic Expect-CT state cannot be added when the feature is not |
| // enabled. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTStateDisabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const std::string host("example.test"); |
| TransportSecurityState state; |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| const base::Time current_time = base::Time::Now(); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| |
| state.AddExpectCT(host, expiry, true, GURL(), NetworkIsolationKey()); |
| EXPECT_FALSE(state.GetDynamicExpectCTState(host, NetworkIsolationKey(), |
| &expect_ct_state)); |
| } |
| |
| // Tests that dynamic Expect-CT opt-ins are processed correctly (when the |
| // feature is enabled). |
| TEST_F(TransportSecurityStateTest, DynamicExpectCT) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| |
| // First test that the header is not processed when the feature is disabled. |
| { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState state; |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| } |
| |
| // Now test that the header is processed when the feature is enabled. |
| { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| base::Time now = base::Time::Now(); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_TRUE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_EQ(GURL("http://foo.test"), expect_ct_state.report_uri); |
| EXPECT_TRUE(expect_ct_state.enforce); |
| EXPECT_LT(now, expect_ct_state.expiry); |
| // No report should be sent when the header was processed over a connection |
| // that complied with CT policy. |
| EXPECT_EQ(0u, reporter.num_failures()); |
| } |
| } |
| |
| // Tests that dynamic Expect-CT is not processed for private roots. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTPrivateRoot) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = false; |
| ssl.ct_policy_compliance = ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| } |
| |
| // Tests that dynamic Expect-CT is not processed when CT compliance status |
| // wasn't computed. |
| TEST_F(TransportSecurityStateTest, DynamicExpectCTNoComplianceDetails) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE; |
| |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl.unverified_cert = cert1; |
| ssl.cert = cert2; |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| NetworkIsolationKey()); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| } |
| |
| // Tests that Expect-CT reports are sent when an Expect-CT header is received |
| // over a non-compliant connection. |
| TEST_F(TransportSecurityStateTest, |
| DynamicExpectCTHeaderProcessingNonCompliant) { |
| const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\""; |
| SSLInfo ssl; |
| ssl.is_issued_by_known_root = true; |
| ssl.ct_policy_compliance = ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| ssl.unverified_cert = cert1; |
| ssl.cert = cert2; |
| |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, |
| &ssl.signed_certificate_timestamps); |
| |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.ProcessExpectCTHeader(kHeader, HostPortPair("example.test", 443), ssl, |
| network_isolation_key); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_FALSE(state.GetDynamicExpectCTState( |
| "example.test", NetworkIsolationKey(), &expect_ct_state)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ("example.test", reporter.host_port_pair().host()); |
| EXPECT_TRUE(reporter.expiration().is_null()); |
| EXPECT_EQ(cert1.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.validated_certificate_chain()); |
| EXPECT_EQ(ssl.signed_certificate_timestamps.size(), |
| reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(ssl.signed_certificate_timestamps[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(ssl.signed_certificate_timestamps[0].sct, |
| reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| } |
| |
| // Tests that CheckCTRequirements() returns the correct response if a connection |
| // to a host violates an Expect-CT header, and that it reports violations. |
| TEST_F(TransportSecurityStateTest, CheckCTRequirementsWithExpectCT) { |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| SignedCertificateTimestampAndStatusList sct_list; |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list); |
| |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.AddExpectCT("example.test", expiry, true /* enforce */, |
| GURL("https://example-report.test"), network_isolation_key); |
| state.AddExpectCT("example-report-only.test", expiry, false /* enforce */, |
| GURL("https://example-report.test"), network_isolation_key); |
| state.AddExpectCT("example-enforce-only.test", expiry, true /* enforce */, |
| GURL(), network_isolation_key); |
| |
| // Test that a connection to an unrelated host is not affected. |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example2.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example2.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(0u, reporter.num_failures()); |
| |
| // A connection to an Expect-CT host should be closed and reported. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ("example.test", reporter.host_port_pair().host()); |
| EXPECT_EQ(443, reporter.host_port_pair().port()); |
| EXPECT_EQ(expiry, reporter.expiration()); |
| EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(sct_list[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| |
| // A compliant connection to an Expect-CT host should not be closed or |
| // reported. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY, |
| network_isolation_key)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| |
| // A connection to a report-only host should be reported only. |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example-report-only.test", 443), true, |
| HashValueVector(), cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(2u, reporter.num_failures()); |
| EXPECT_EQ("example-report-only.test", reporter.host_port_pair().host()); |
| EXPECT_EQ(443, reporter.host_port_pair().port()); |
| EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(sct_list[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| |
| // A connection to an enforce-only host should be closed but not reported. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example-enforce-only.test", 443), true, |
| HashValueVector(), cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(2u, reporter.num_failures()); |
| |
| // A connection with a private root should be neither enforced nor reported. |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), false, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(2u, reporter.num_failures()); |
| |
| // A connection with DISABLE_EXPECT_CT_REPORTS should not send a report. |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::DISABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(2u, reporter.num_failures()); |
| } |
| |
| // Tests that for a host that requires CT by delegate and is also |
| // Expect-CT-enabled, CheckCTRequirements() sends reports. |
| TEST_F(TransportSecurityStateTest, CheckCTRequirementsWithExpectCTAndDelegate) { |
| using ::testing::_; |
| using ::testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| SignedCertificateTimestampAndStatusList sct_list; |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.AddExpectCT("example.test", expiry, false /* enforce */, |
| GURL("https://example-report.test"), network_isolation_key); |
| |
| // A connection to an Expect-CT host, which also requires CT by the delegate, |
| // should be closed and reported. |
| MockRequireCTDelegate always_require_delegate; |
| EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::REQUIRED)); |
| state.SetRequireCTDelegate(&always_require_delegate); |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ("example.test", reporter.host_port_pair().host()); |
| EXPECT_EQ(443, reporter.host_port_pair().port()); |
| EXPECT_EQ(expiry, reporter.expiration()); |
| EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(sct_list[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| } |
| |
| // Tests that for a host that explicitly disabled CT by delegate and is also |
| // Expect-CT-enabled, CheckCTRequirements() sends reports. |
| TEST_F(TransportSecurityStateTest, |
| CheckCTRequirementsWithExpectCTAndDelegateDisables) { |
| using ::testing::_; |
| using ::testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| SignedCertificateTimestampAndStatusList sct_list; |
| MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log", |
| std::string(), std::string(), base::Time::Now(), |
| ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| TransportSecurityState state; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| state.AddExpectCT("example.test", expiry, false /* enforce */, |
| GURL("https://example-report.test"), network_isolation_key); |
| |
| // A connection to an Expect-CT host, which is exempted from the CT |
| // requirements by the delegate, should be reported but not closed. |
| MockRequireCTDelegate never_require_delegate; |
| EXPECT_CALL(never_require_delegate, IsCTRequiredForHost(_, _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| state.SetRequireCTDelegate(&never_require_delegate); |
| EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED, |
| state.CheckCTRequirements( |
| HostPortPair("example.test", 443), true, HashValueVector(), |
| cert1.get(), cert2.get(), sct_list, |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key)); |
| EXPECT_EQ(1u, reporter.num_failures()); |
| EXPECT_EQ("example.test", reporter.host_port_pair().host()); |
| EXPECT_EQ(443, reporter.host_port_pair().port()); |
| EXPECT_EQ(expiry, reporter.expiration()); |
| EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain()); |
| EXPECT_EQ(cert2.get(), reporter.served_certificate_chain()); |
| EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size()); |
| EXPECT_EQ(sct_list[0].status, |
| reporter.signed_certificate_timestamps()[0].status); |
| EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct); |
| EXPECT_EQ(network_isolation_key, reporter.network_isolation_key()); |
| } |
| |
| #if BUILDFLAG(INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST) |
| const char kSubdomain[] = "foo.example.test"; |
| |
| class TransportSecurityStateStaticTest : public TransportSecurityStateTest { |
| public: |
| TransportSecurityStateStaticTest() { |
| SetTransportSecurityStateSourceForTesting(nullptr); |
| } |
| }; |
| |
| static bool StaticShouldRedirect(const char* hostname) { |
| TransportSecurityState state; |
| TransportSecurityState::STSState sts_state; |
| return state.GetStaticSTSState(hostname, &sts_state) && |
| sts_state.ShouldUpgradeToSSL(); |
| } |
| |
| static bool HasStaticState(const char* hostname) { |
| TransportSecurityState state; |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| return state.GetStaticSTSState(hostname, &sts_state) || |
| state.GetStaticPKPState(hostname, &pkp_state); |
| } |
| |
| static bool HasStaticPublicKeyPins(const char* hostname) { |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticPins(&state); |
| TransportSecurityState::PKPState pkp_state; |
| if (!state.GetStaticPKPState(hostname, &pkp_state)) |
| return false; |
| |
| return pkp_state.HasPublicKeyPins(); |
| } |
| |
| static bool OnlyPinningInStaticState(const char* hostname) { |
| TransportSecurityState state; |
| TransportSecurityStateTest::EnableStaticPins(&state); |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| return HasStaticPublicKeyPins(hostname) && !StaticShouldRedirect(hostname); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, EnableStaticPins) { |
| TransportSecurityState state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| EnableStaticPins(&state); |
| |
| EXPECT_TRUE(state.GetStaticPKPState("chrome.google.com", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, DisableStaticPins) { |
| TransportSecurityState state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| DisableStaticPins(&state); |
| EXPECT_FALSE(state.GetStaticPKPState("chrome.google.com", &pkp_state)); |
| EXPECT_TRUE(pkp_state.spki_hashes.empty()); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, IsPreloaded) { |
| const std::string paypal = "paypal.com"; |
| const std::string www_paypal = "www.paypal.com"; |
| const std::string foo_paypal = "foo.paypal.com"; |
| const std::string a_www_paypal = "a.www.paypal.com"; |
| const std::string abc_paypal = "a.b.c.paypal.com"; |
| const std::string example = "example.com"; |
| const std::string aypal = "aypal.com"; |
| const std::string google = "google"; |
| const std::string www_google = "www.google"; |
| const std::string foo = "foo"; |
| const std::string bank = "example.bank"; |
| const std::string insurance = "sub.example.insurance"; |
| |
| TransportSecurityState state; |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| EXPECT_TRUE(GetStaticDomainState(&state, paypal, &sts_state, &pkp_state)); |
| EXPECT_TRUE(GetStaticDomainState(&state, www_paypal, &sts_state, &pkp_state)); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_TRUE(GetStaticDomainState(&state, google, &sts_state, &pkp_state)); |
| EXPECT_TRUE(GetStaticDomainState(&state, www_google, &sts_state, &pkp_state)); |
| EXPECT_TRUE(GetStaticDomainState(&state, foo, &sts_state, &pkp_state)); |
| EXPECT_TRUE(GetStaticDomainState(&state, bank, &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_TRUE(GetStaticDomainState(&state, insurance, &sts_state, &pkp_state)); |
| EXPECT_TRUE(sts_state.include_subdomains); |
| EXPECT_FALSE( |
| GetStaticDomainState(&state, a_www_paypal, &sts_state, &pkp_state)); |
| EXPECT_FALSE( |
| GetStaticDomainState(&state, abc_paypal, &sts_state, &pkp_state)); |
| EXPECT_FALSE(GetStaticDomainState(&state, example, &sts_state, &pkp_state)); |
| EXPECT_FALSE(GetStaticDomainState(&state, aypal, &sts_state, &pkp_state)); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, PreloadedDomainSet) { |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_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.GetStaticPKPState("market.android.com", &pkp_state)); |
| EXPECT_TRUE(state.GetStaticSTSState("market.android.com", &sts_state)); |
| EXPECT_EQ(sts_state.domain, "market.android.com"); |
| EXPECT_EQ(pkp_state.domain, "market.android.com"); |
| EXPECT_TRUE(state.GetStaticPKPState("sub.market.android.com", &pkp_state)); |
| EXPECT_TRUE(state.GetStaticSTSState("sub.market.android.com", &sts_state)); |
| EXPECT_EQ(sts_state.domain, "market.android.com"); |
| EXPECT_EQ(pkp_state.domain, "market.android.com"); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, Preloaded) { |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| // We do more extensive checks for the first domain. |
| EXPECT_TRUE(state.GetStaticSTSState("www.paypal.com", &sts_state)); |
| EXPECT_FALSE(state.GetStaticPKPState("www.paypal.com", &pkp_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_FALSE(pkp_state.include_subdomains); |
| |
| EXPECT_TRUE(HasStaticState("paypal.com")); |
| EXPECT_FALSE(HasStaticState("www2.paypal.com")); |
| |
| // Google hosts: |
| |
| EXPECT_TRUE(StaticShouldRedirect("chrome.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("checkout.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("wallet.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("docs.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("sites.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("drive.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("spreadsheets.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("appengine.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("market.android.com")); |
| EXPECT_TRUE(StaticShouldRedirect("encrypted.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("accounts.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("profiles.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("mail.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("chatenabled.mail.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("talkgadget.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("hostedtalkgadget.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("talk.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("plus.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("groups.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("apis.google.com")); |
| EXPECT_TRUE(StaticShouldRedirect("oauthaccountmanager.googleapis.com")); |
| EXPECT_TRUE(StaticShouldRedirect("passwordsleakcheck-pa.googleapis.com")); |
| EXPECT_TRUE(StaticShouldRedirect("ssl.google-analytics.com")); |
| EXPECT_TRUE(StaticShouldRedirect("google")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.google")); |
| EXPECT_TRUE(StaticShouldRedirect("foo")); |
| EXPECT_TRUE(StaticShouldRedirect("domaintest.foo")); |
| EXPECT_TRUE(StaticShouldRedirect("gmail.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.gmail.com")); |
| EXPECT_TRUE(StaticShouldRedirect("googlemail.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.googlemail.com")); |
| EXPECT_TRUE(StaticShouldRedirect("googleplex.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.googleplex.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.google-analytics.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.youtube.com")); |
| EXPECT_TRUE(StaticShouldRedirect("youtube.com")); |
| |
| // These domains used to be only HSTS when SNI was available. |
| EXPECT_TRUE(state.GetStaticSTSState("gmail.com", &sts_state)); |
| EXPECT_TRUE(state.GetStaticPKPState("gmail.com", &pkp_state)); |
| EXPECT_TRUE(state.GetStaticSTSState("www.gmail.com", &sts_state)); |
| EXPECT_TRUE(state.GetStaticPKPState("www.gmail.com", &pkp_state)); |
| EXPECT_TRUE(state.GetStaticSTSState("googlemail.com", &sts_state)); |
| EXPECT_TRUE(state.GetStaticPKPState("googlemail.com", &pkp_state)); |
| EXPECT_TRUE(state.GetStaticSTSState("www.googlemail.com", &sts_state)); |
| EXPECT_TRUE(state.GetStaticPKPState("www.googlemail.com", &pkp_state)); |
| |
| // fi.g.co should not force HTTPS because there are still HTTP-only services |
| // on it. |
| EXPECT_FALSE(StaticShouldRedirect("fi.g.co")); |
| |
| // Other hosts: |
| |
| EXPECT_TRUE(StaticShouldRedirect("aladdinschools.appspot.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("ottospora.nl")); |
| EXPECT_TRUE(StaticShouldRedirect("www.ottospora.nl")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("www.paycheckrecords.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("lastpass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.lastpass.com")); |
| EXPECT_FALSE(HasStaticState("blog.lastpass.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("keyerror.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.keyerror.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("entropia.de")); |
| EXPECT_TRUE(StaticShouldRedirect("www.entropia.de")); |
| EXPECT_FALSE(HasStaticState("foo.entropia.de")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("www.elanex.biz")); |
| EXPECT_FALSE(HasStaticState("elanex.biz")); |
| EXPECT_FALSE(HasStaticState("foo.elanex.biz")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("sunshinepress.org")); |
| EXPECT_TRUE(StaticShouldRedirect("www.sunshinepress.org")); |
| EXPECT_TRUE(StaticShouldRedirect("a.b.sunshinepress.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("www.noisebridge.net")); |
| EXPECT_FALSE(HasStaticState("noisebridge.net")); |
| EXPECT_FALSE(HasStaticState("foo.noisebridge.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("neg9.org")); |
| EXPECT_FALSE(HasStaticState("www.neg9.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("riseup.net")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.riseup.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("factor.cc")); |
| EXPECT_FALSE(HasStaticState("www.factor.cc")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("members.mayfirst.org")); |
| EXPECT_TRUE(StaticShouldRedirect("support.mayfirst.org")); |
| EXPECT_TRUE(StaticShouldRedirect("id.mayfirst.org")); |
| EXPECT_TRUE(StaticShouldRedirect("lists.mayfirst.org")); |
| EXPECT_FALSE(HasStaticState("www.mayfirst.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("romab.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.romab.com")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.romab.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("logentries.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.logentries.com")); |
| EXPECT_FALSE(HasStaticState("foo.logentries.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("stripe.com")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.stripe.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("cloudsecurityalliance.org")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.cloudsecurityalliance.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("login.sapo.pt")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.login.sapo.pt")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("mattmccutchen.net")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.mattmccutchen.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("betnet.fr")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.betnet.fr")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("uprotect.it")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.uprotect.it")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("cert.se")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.cert.se")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("crypto.is")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.crypto.is")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("simon.butcher.name")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.simon.butcher.name")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("linx.net")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.linx.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("dropcam.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.dropcam.com")); |
| EXPECT_FALSE(HasStaticState("foo.dropcam.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("ebanking.indovinabank.com.vn")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.ebanking.indovinabank.com.vn")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("epoxate.com")); |
| EXPECT_FALSE(HasStaticState("foo.epoxate.com")); |
| |
| EXPECT_FALSE(HasStaticState("foo.torproject.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("www.moneybookers.com")); |
| EXPECT_FALSE(HasStaticState("moneybookers.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("ledgerscope.net")); |
| EXPECT_TRUE(StaticShouldRedirect("www.ledgerscope.net")); |
| EXPECT_FALSE(HasStaticState("status.ledgerscope.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("foo.app.recurly.com")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.api.recurly.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("greplin.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.greplin.com")); |
| EXPECT_FALSE(HasStaticState("foo.greplin.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("luneta.nearbuysystems.com")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.luneta.nearbuysystems.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("ubertt.org")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.ubertt.org")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("pixi.me")); |
| EXPECT_TRUE(StaticShouldRedirect("www.pixi.me")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("grepular.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.grepular.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.mydigipass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.www.mydigipass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("developer.mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.developer.mydigipass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.developer.mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.www.developer.mydigipass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("sandbox.mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.sandbox.mydigipass.com")); |
| EXPECT_TRUE(StaticShouldRedirect("www.sandbox.mydigipass.com")); |
| EXPECT_FALSE(StaticShouldRedirect("foo.www.sandbox.mydigipass.com")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("bigshinylock.minazo.net")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.bigshinylock.minazo.net")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("crate.io")); |
| EXPECT_TRUE(StaticShouldRedirect("foo.crate.io")); |
| |
| EXPECT_TRUE(StaticShouldRedirect("sub.bank")); |
| EXPECT_TRUE(StaticShouldRedirect("sub.insurance")); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, PreloadedPins) { |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| |
| // We do more extensive checks for the first domain. |
| EXPECT_TRUE(state.GetStaticSTSState("www.paypal.com", &sts_state)); |
| EXPECT_FALSE(state.GetStaticPKPState("www.paypal.com", &pkp_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_FALSE(sts_state.include_subdomains); |
| EXPECT_FALSE(pkp_state.include_subdomains); |
| |
| EXPECT_TRUE(OnlyPinningInStaticState("www.google.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("foo.google.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("google.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("i.ytimg.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("ytimg.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googleusercontent.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("www.googleusercontent.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googleapis.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googleadservices.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googlecode.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("appspot.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googlesyndication.com")); |
| EXPECT_TRUE(OnlyPinningInStaticState("doubleclick.net")); |
| EXPECT_TRUE(OnlyPinningInStaticState("googlegroups.com")); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("torproject.org")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.torproject.org")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("check.torproject.org")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("blog.torproject.org")); |
| EXPECT_FALSE(HasStaticState("foo.torproject.org")); |
| |
| EXPECT_TRUE(state.GetStaticPKPState("torproject.org", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(state.GetStaticPKPState("www.torproject.org", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(state.GetStaticPKPState("check.torproject.org", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(state.GetStaticPKPState("blog.torproject.org", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com")); |
| |
| // Facebook has pinning and hsts on facebook.com, but only pinning on |
| // subdomains. |
| EXPECT_TRUE(state.GetStaticPKPState("facebook.com", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(StaticShouldRedirect("facebook.com")); |
| |
| EXPECT_TRUE(state.GetStaticPKPState("foo.facebook.com", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_FALSE(StaticShouldRedirect("foo.facebook.com")); |
| |
| // www.facebook.com and subdomains have both pinning and hsts. |
| EXPECT_TRUE(state.GetStaticPKPState("www.facebook.com", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(StaticShouldRedirect("www.facebook.com")); |
| |
| EXPECT_TRUE(state.GetStaticPKPState("foo.www.facebook.com", &pkp_state)); |
| EXPECT_FALSE(pkp_state.spki_hashes.empty()); |
| EXPECT_TRUE(StaticShouldRedirect("foo.www.facebook.com")); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, BuiltinCertPins) { |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| TransportSecurityState::PKPState pkp_state; |
| |
| EXPECT_TRUE(state.GetStaticPKPState("chrome.google.com", &pkp_state)); |
| EXPECT_TRUE(HasStaticPublicKeyPins("chrome.google.com")); |
| |
| HashValueVector hashes; |
| std::string failure_log; |
| // Checks that a built-in list does exist. |
| EXPECT_FALSE(pkp_state.CheckPublicKeyPins(hashes, &failure_log)); |
| EXPECT_FALSE(HasStaticPublicKeyPins("www.paypal.com")); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("docs.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("1.docs.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("sites.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("drive.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("spreadsheets.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("wallet.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("checkout.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("appengine.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("market.android.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("encrypted.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("accounts.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("profiles.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("mail.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("chatenabled.mail.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("talkgadget.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("hostedtalkgadget.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("talk.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("plus.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("groups.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("apis.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.google-analytics.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.youtube.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("youtube.com")); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("ssl.gstatic.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("gstatic.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.gstatic.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("ssl.google-analytics.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.googleplex.com")); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("twitter.com")); |
| EXPECT_FALSE(HasStaticPublicKeyPins("foo.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("api.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("oauth.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("mobile.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("dev.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("business.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("platform.twitter.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("si0.twimg.com")); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, OptionalHSTSCertPins) { |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| |
| EXPECT_TRUE(HasStaticPublicKeyPins("google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.google.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("mail-attachment.googleusercontent.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("www.youtube.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("i.ytimg.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("googleapis.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("ajax.googleapis.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("googleadservices.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("pagead2.googleadservices.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("googlecode.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("kibbles.googlecode.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("appspot.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("googlesyndication.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("doubleclick.net")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("ad.doubleclick.net")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("redirector.gvt1.com")); |
| EXPECT_TRUE(HasStaticPublicKeyPins("a.googlegroups.com")); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, OverrideBuiltins) { |
| EXPECT_TRUE(HasStaticPublicKeyPins("google.com")); |
| EXPECT_FALSE(StaticShouldRedirect("google.com")); |
| EXPECT_FALSE(StaticShouldRedirect("www.google.com")); |
| |
| TransportSecurityState state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::Seconds(1000); |
| state.AddHSTS("www.google.com", expiry, true); |
| |
| EXPECT_TRUE(state.ShouldUpgradeToSSL("www.google.com")); |
| } |
| |
| // Tests that redundant reports are rate-limited. |
| TEST_F(TransportSecurityStateStaticTest, HPKPReportRateLimiting) { |
| HostPortPair host_port_pair(kHost, kPort); |
| HostPortPair subdomain_host_port_pair(kSubdomain, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector good_hashes, bad_hashes; |
| |
| for (size_t i = 0; kGoodPath[i]; i++) |
| EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| MockCertificateReportSender mock_report_sender; |
| state.SetReportSender(&mock_report_sender); |
| |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| std::string failure_log; |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // A report should have been sent. Check that it contains the |
| // right information. |
| EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri()); |
| std::string report = mock_report_sender.latest_report(); |
| ASSERT_FALSE(report.empty()); |
| ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, host_port_pair, true, kHost, |
| cert1.get(), cert2.get(), |
| good_hashes)); |
| EXPECT_EQ(network_isolation_key, |
| mock_report_sender.latest_network_isolation_key()); |
| mock_report_sender.Clear(); |
| |
| // Now trigger the same violation; a duplicative report should not be |
| // sent. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| EXPECT_EQ(NetworkIsolationKey(), |
| mock_report_sender.latest_network_isolation_key()); |
| } |
| |
| TEST_F(TransportSecurityStateStaticTest, HPKPReporting) { |
| HostPortPair host_port_pair(kHost, kPort); |
| HostPortPair subdomain_host_port_pair(kSubdomain, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector good_hashes, bad_hashes; |
| |
| for (size_t i = 0; kGoodPath[i]; i++) |
| EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| MockCertificateReportSender mock_report_sender; |
| state.SetReportSender(&mock_report_sender); |
| |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| std::string failure_log; |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::DISABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // No report should have been sent because of the DISABLE_PIN_REPORTS |
| // argument. |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins(host_port_pair, true, good_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // No report should have been sent because there was no violation. |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| EXPECT_EQ(TransportSecurityState::PKPStatus::BYPASSED, |
| state.CheckPublicKeyPins(host_port_pair, false, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // No report should have been sent because the certificate chained to a |
| // non-public root. |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins(host_port_pair, false, good_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // No report should have been sent because there was no violation, even though |
| // the certificate chained to a local trust anchor. |
| EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri()); |
| EXPECT_EQ(std::string(), mock_report_sender.latest_report()); |
| |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // Now a report should have been sent. Check that it contains the |
| // right information. |
| EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri()); |
| std::string report = mock_report_sender.latest_report(); |
| ASSERT_FALSE(report.empty()); |
| EXPECT_EQ("application/json; charset=utf-8", |
| mock_report_sender.latest_content_type()); |
| ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, host_port_pair, true, kHost, |
| cert1.get(), cert2.get(), |
| good_hashes)); |
| mock_report_sender.Clear(); |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins(subdomain_host_port_pair, true, bad_hashes, |
| cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &failure_log)); |
| |
| // Now a report should have been sent for the subdomain. Check that it |
| // contains the right information. |
| EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri()); |
| report = mock_report_sender.latest_report(); |
| ASSERT_FALSE(report.empty()); |
| EXPECT_EQ("application/json; charset=utf-8", |
| mock_report_sender.latest_content_type()); |
| ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, subdomain_host_port_pair, |
| true, kHost, cert1.get(), cert2.get(), |
| good_hashes)); |
| EXPECT_EQ(network_isolation_key, |
| mock_report_sender.latest_network_isolation_key()); |
| } |
| |
| TEST_F(TransportSecurityStateTest, WriteSizeDecodeSize) { |
| for (size_t i = 0; i < 300; ++i) { |
| SCOPED_TRACE(i); |
| huffman_trie::TrieBitBuffer buffer; |
| buffer.WriteSize(i); |
| huffman_trie::BitWriter writer; |
| buffer.WriteToBitWriter(&writer); |
| size_t position = writer.position(); |
| writer.Flush(); |
| ASSERT_NE(writer.bytes().data(), nullptr); |
| extras::PreloadDecoder::BitReader reader(writer.bytes().data(), position); |
| size_t decoded_size; |
| EXPECT_TRUE(reader.DecodeSize(&decoded_size)); |
| EXPECT_EQ(i, decoded_size); |
| } |
| } |
| |
| TEST_F(TransportSecurityStateTest, DecodeSizeFour) { |
| // Test that BitReader::DecodeSize properly handles the number 4, including |
| // not over-reading input bytes. BitReader::Next only fails if there's not |
| // another byte to read from; if it reads past the number of bits in the |
| // buffer but is still in the last byte it will still succeed. For this |
| // reason, this test puts the encoding of 4 at the end of the byte to check |
| // that DecodeSize doesn't over-read. |
| // |
| // 4 is encoded as 0b010. Shifted right to fill one byte, it is 0x02, with 5 |
| // bits of padding. |
| uint8_t encoded = 0x02; |
| extras::PreloadDecoder::BitReader reader(&encoded, 8); |
| for (size_t i = 0; i < 5; ++i) { |
| bool unused; |
| ASSERT_TRUE(reader.Next(&unused)); |
| } |
| size_t decoded_size; |
| EXPECT_TRUE(reader.DecodeSize(&decoded_size)); |
| EXPECT_EQ(4u, decoded_size); |
| } |
| |
| #endif // BUILDFLAG(INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST) |
| |
| TEST_F(TransportSecurityStateTest, |
| PartitionExpectCTStateByNetworkIsolationKey) { |
| const char kDomain[] = "example.test"; |
| HostPortPair host_port_pair(kDomain, 443); |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| const base::Time expiry = base::Time::Now() + base::Seconds(1000); |
| |
| // Dummy cert to use as the validation chain. The contents do not matter. |
| scoped_refptr<X509Certificate> cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert); |
| HashValueVector hashes; |
| hashes.push_back( |
| HashValue(X509Certificate::CalculateFingerprint256(cert->cert_buffer()))); |
| |
| // An ExpectCT entry is set using network_isolation_key1, and then accessed |
| // using both keys. It should only be accessible using the other key when |
| // kPartitionExpectCTStateByNetworkIsolationKey is disabled. |
| NetworkIsolationKey network_isolation_key1 = |
| NetworkIsolationKey::CreateTransient(); |
| NetworkIsolationKey network_isolation_key2 = |
| NetworkIsolationKey::CreateTransient(); |
| |
| for (bool partition_expect_ct_state : {false, true}) { |
| base::test::ScopedFeatureList feature_list2; |
| if (partition_expect_ct_state) { |
| feature_list2.InitAndEnableFeature( |
| features::kPartitionExpectCTStateByNetworkIsolationKey); |
| } else { |
| feature_list2.InitAndDisableFeature( |
| features::kPartitionExpectCTStateByNetworkIsolationKey); |
| } |
| |
| // Add Expect-CT entry. |
| TransportSecurityState state; |
| state.AddExpectCT(kDomain, expiry, true, GURL(), network_isolation_key1); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| EXPECT_TRUE(state.GetDynamicExpectCTState(kDomain, network_isolation_key1, |
| &expect_ct_state)); |
| |
| // The Expect-CT entry should only be respected with |
| // |network_isolation_key2| when |
| // kPartitionExpectCTStateByNetworkIsolationKey is disabled. |
| EXPECT_EQ(!partition_expect_ct_state, |
| state.GetDynamicExpectCTState(kDomain, network_isolation_key2, |
| &expect_ct_state)); |
| EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET, |
| state.CheckCTRequirements( |
| host_port_pair, true, hashes, cert.get(), cert.get(), |
| SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key1)); |
| EXPECT_EQ(!partition_expect_ct_state, |
| TransportSecurityState::CT_REQUIREMENTS_NOT_MET == |
| state.CheckCTRequirements( |
| host_port_pair, true, hashes, cert.get(), cert.get(), |
| SignedCertificateTimestampAndStatusList(), |
| TransportSecurityState::ENABLE_EXPECT_CT_REPORTS, |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| network_isolation_key2)); |
| |
| // An Expect-CT header with |network_isolation_key2| should only overwrite |
| // the entry when |partition_expect_ct_state| is false. |
| SSLInfo ssl_info; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| MockExpectCTReporter reporter; |
| state.SetExpectCTReporter(&reporter); |
| const char kHeader[] = "max-age=0"; |
| state.ProcessExpectCTHeader(kHeader, host_port_pair, ssl_info, |
| network_isolation_key2); |
| EXPECT_EQ(partition_expect_ct_state, |
| state.GetDynamicExpectCTState(kDomain, network_isolation_key1, |
| &expect_ct_state)); |
| |
| // An Expect-CT header with |network_isolation_key1| should always overwrite |
| // the added entry. |
| state.ProcessExpectCTHeader(kHeader, host_port_pair, ssl_info, |
| network_isolation_key1); |
| EXPECT_FALSE(state.GetDynamicExpectCTState(kDomain, network_isolation_key1, |
| &expect_ct_state)); |
| } |
| } |
| |
| // Tests the eviction logic and priority of pruning resources, before applying |
| // the per-NetworkIsolationKey limit. |
| TEST_F(TransportSecurityStateTest, PruneExpectCTPriority) { |
| const GURL report_uri(kReportUri); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| // enabled_features |
| {TransportSecurityState::kDynamicExpectCTFeature, |
| features::kPartitionExpectCTStateByNetworkIsolationKey}, |
| // disabled_features |
| {}); |
| |
| // Each iteration adds two groups of |kGroupSize| entries, with specified |
| // parameters, and then enough entries are added for a third group to trigger |
| // pruning. |kGroupSize| is chosen so that exactly all the entries in the |
| // first group or the second will typically be pruned. Note that group 1 is |
| // always added before group 2. |
| const size_t kGroupSize = |
| features::kExpectCTPruneMax.Get() - features::kExpectCTPruneMin.Get(); |
| // This test requires |2 * kGroupSize| to be less than |kExpectCTPruneMax|. |
| ASSERT_LT(2 * kGroupSize, |
| static_cast<size_t>(features::kExpectCTPruneMax.Get())); |
| const size_t kThirdGroupSize = |
| features::kExpectCTPruneMax.Get() - 2 * kGroupSize; |
| |
| // Specifies where the entries of no groups or of only the first group are old |
| // enough to be pruned. |
| enum class GroupsOldEnoughToBePruned { |
| kNone, |
| kFirstGroupOnly, |
| kFirstAndSecondGroups, |
| }; |
| |
| const struct TestCase { |
| bool first_group_has_transient_nik; |
| bool second_group_has_transient_nik; |
| bool first_group_has_enforce; |
| bool second_group_has_enforce; |
| bool first_group_is_expired; |
| bool second_group_is_expired; |
| GroupsOldEnoughToBePruned groups_old_enough_to_be_pruned; |
| bool expect_first_group_retained; |
| bool expect_second_group_retained; |
| } kTestCases[] = { |
| // No entries are prunable, so will exceed features::kExpectCTPruneMax. |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| true /* expect_first_group_retained */, |
| true /* expect_second_group_retained */ |
| }, |
| |
| // Only second group is prunable, so it should end up empty. |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| false /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| true /* expect_first_group_retained */, |
| false /* expect_second_group_retained */ |
| }, |
| { |
| false /* first_group_has_transient_nik */, |
| true /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| true /* expect_first_group_retained */, |
| false /* expect_second_group_retained */ |
| }, |
| |
| // Only first group is prunable, so only it should be evicted. |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| false /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| false /* expect_first_group_retained */, |
| true /* expect_second_group_retained */ |
| }, |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, |
| GroupsOldEnoughToBePruned::kFirstGroupOnly, |
| false /* expect_first_group_retained */, |
| true /* expect_second_group_retained */ |
| }, |
| |
| // Both groups are prunable for the same reason, but group 1 is older |
| // (since group 1 is added first). |
| { |
| true /* first_group_has_transient_nik */, |
| true /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| false /* expect_first_group_retained */, |
| true /* expect_second_group_retained */ |
| }, |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, |
| GroupsOldEnoughToBePruned::kFirstAndSecondGroups, |
| false /* expect_first_group_retained */, |
| true /* expect_second_group_retained */ |
| }, |
| |
| // First group has enforce not set, second uses a transient NIK. First |
| // should take priority. |
| { |
| false /* first_group_has_transient_nik */, |
| true /* second_group_has_transient_nik */, |
| false /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, GroupsOldEnoughToBePruned::kNone, |
| true /* expect_first_group_retained */, |
| false /* expect_second_group_retained */ |
| }, |
| |
| // First group outside the non-prunable window, second has enforce set. |
| // not set. First should take priority. |
| { |
| false /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| true /* bool first_group_has_enforce */, |
| false /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| false /* second_group_is_expired */, |
| GroupsOldEnoughToBePruned::kFirstGroupOnly, |
| true /* expect_first_group_retained */, |
| false /* expect_second_group_retained */ |
| }, |
| |
| // Second group is expired, so it is evicted, even though the first group |
| // would otherwise be prunable and the second would not. |
| { |
| true /* first_group_has_transient_nik */, |
| false /* second_group_has_transient_nik */, |
| false /* bool first_group_has_enforce */, |
| true /* bool second_group_has_enforce */, |
| false /* first_group_is_expired */, |
| true /* second_group_is_expired */, |
| GroupsOldEnoughToBePruned::kFirstGroupOnly, |
| true /* expect_first_group_retained */, |
| false /* expect_second_group_retained */ |
| }, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| // Each test case simulates up to |features::kExpectCTSafeFromPruneDays |
| // + 1| days passing, so if an entry added for a test case should not expire |
| // over the course of running the test, its expiry date must be farther into |
| // the future than that. |
| base::Time unexpired_expiry_time = |
| base::Time::Now() + |
| base::Days(2 * features::kExpectCTSafeFromPruneDays.Get() + 1); |
| |
| // Always add entries unexpired. |
| base::Time first_group_expiry = |
| test_case.first_group_is_expired |
| ? base::Time::Now() + base::Milliseconds(1) |
| : unexpired_expiry_time; |
| |
| TransportSecurityState state; |
| base::Time first_group_observation_time = base::Time::Now(); |
| for (size_t i = 0; i < kGroupSize; ++i) { |
| // All entries use a unique NetworkIsolationKey, so |
| // NetworkIsolationKey-based pruning will do nothing. |
| state.AddExpectCT(CreateUniqueHostName(), first_group_expiry, |
| test_case.first_group_has_enforce, report_uri, |
| CreateUniqueNetworkIsolationKey( |
| test_case.first_group_has_transient_nik)); |
| } |
| |
| // Skip forward in time slightly, so the first group is always older than |
| // the first. |
| FastForwardBy(base::Seconds(1)); |
| |
| // If only the first group should be old enough to be pruned, wait until |
| // enough time for the group to be prunable has passed. |
| if (test_case.groups_old_enough_to_be_pruned == |
| GroupsOldEnoughToBePruned::kFirstGroupOnly) { |
| FastForwardBy(base::Days(features::kExpectCTSafeFromPruneDays.Get() + 1)); |
| } |
| |
| // Always add entries unexpired. |
| base::Time second_group_expiry = |
| test_case.second_group_is_expired |
| ? base::Time::Now() + base::Milliseconds(1) |
| : unexpired_expiry_time; |
| |
| base::Time second_group_observation_time = base::Time::Now(); |
| ASSERT_NE(first_group_observation_time, second_group_observation_time); |
| for (size_t i = 0; i < kGroupSize; ++i) { |
| state.AddExpectCT(CreateUniqueHostName(), second_group_expiry, |
| test_case.second_group_has_enforce, report_uri, |
| CreateUniqueNetworkIsolationKey( |
| test_case.second_group_has_transient_nik)); |
| } |
| |
| // Skip forward in time slightly, so the first group is always older than |
| // the first. This needs to be long enough so that if |
| // |second_group_is_expired| is true, the entry will expire. |
| FastForwardBy(base::Seconds(1)); |
| |
| // If both the first and second groups should be old enough to be pruned, |
| // wait until enough time has passed for both groups to prunable. |
| if (test_case.groups_old_enough_to_be_pruned == |
| GroupsOldEnoughToBePruned::kFirstAndSecondGroups) { |
| FastForwardBy(base::Days(features::kExpectCTSafeFromPruneDays.Get() + 1)); |
| } |
| |
| for (size_t i = 0; i < kThirdGroupSize; ++i) { |
| state.AddExpectCT( |
| CreateUniqueHostName(), base::Time::Now() + base::Seconds(1), |
| true /* enforce */, report_uri, |
| CreateUniqueNetworkIsolationKey(false /* is_transient */)); |
| } |
| |
| size_t first_group_size = 0; |
| size_t second_group_size = 0; |
| size_t third_group_size = 0; |
| for (TransportSecurityState::ExpectCTStateIterator iterator(state); |
| iterator.HasNext(); iterator.Advance()) { |
| if (iterator.domain_state().last_observed == |
| first_group_observation_time) { |
| ++first_group_size; |
| } else if (iterator.domain_state().last_observed == |
| second_group_observation_time) { |
| ++second_group_size; |
| } else { |
| ++third_group_size; |
| } |
| } |
| |
| EXPECT_EQ(test_case.expect_first_group_retained ? kGroupSize : 0, |
| first_group_size); |
| EXPECT_EQ(test_case.expect_second_group_retained ? kGroupSize : 0, |
| second_group_size); |
| EXPECT_EQ(kThirdGroupSize, third_group_size); |
| |
| // Make sure that |unexpired_expiry_time| was set correctly - if this fails, |
| // it will need to be increased to avoid unexpected entry expirations. |
| ASSERT_LT(base::Time::Now(), unexpired_expiry_time); |
| } |
| } |
| |
| // Test the delay between pruning Expect-CT entries. |
| TEST_F(TransportSecurityStateTest, PruneExpectCTDelay) { |
| const GURL report_uri(kReportUri); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| |
| TransportSecurityState state; |
| base::Time expiry = base::Time::Now() + base::Days(10); |
| // Add prunable entries until pruning is triggered. |
| for (int i = 0; i < features::kExpectCTPruneMax.Get(); ++i) { |
| state.AddExpectCT(CreateUniqueHostName(), expiry, false /* enforce */, |
| report_uri, |
| CreateUniqueNetworkIsolationKey(true /* is_transient */)); |
| } |
| // Should have removed enough entries to get down to kExpectCTPruneMin |
| // entries. |
| EXPECT_EQ(features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // Add more prunable entries, but pruning should not be triggered, due to the |
| // delay between subsequent pruning tasks. |
| for (int i = 0; i < features::kExpectCTPruneMax.Get(); ++i) { |
| state.AddExpectCT(CreateUniqueHostName(), expiry, false /* enforce */, |
| report_uri, |
| CreateUniqueNetworkIsolationKey(true /* is_transient */)); |
| } |
| EXPECT_EQ( |
| features::kExpectCTPruneMax.Get() + features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // Time passes, which does not trigger pruning. |
| FastForwardBy(base::Seconds(features::kExpectCTPruneDelaySecs.Get())); |
| EXPECT_EQ( |
| features::kExpectCTPruneMax.Get() + features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // Another entry is added, which triggers pruning, now that enough time has |
| // passed. |
| state.AddExpectCT(CreateUniqueHostName(), expiry, false /* enforce */, |
| report_uri, |
| CreateUniqueNetworkIsolationKey(true /* is_transient */)); |
| EXPECT_EQ(features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // More time passes. |
| FastForwardBy(base::Seconds(10 * features::kExpectCTPruneDelaySecs.Get())); |
| EXPECT_EQ(features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // When enough entries are added to trigger pruning, it runs immediately, |
| // since enough time has passed. |
| for (int i = 0; i < features::kExpectCTPruneMax.Get() - |
| features::kExpectCTPruneMin.Get(); |
| ++i) { |
| state.AddExpectCT(CreateUniqueHostName(), expiry, false /* enforce */, |
| report_uri, |
| CreateUniqueNetworkIsolationKey(true /* is_transient */)); |
| } |
| EXPECT_EQ(features::kExpectCTPruneMin.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| } |
| |
| // Test that Expect-CT pruning respects kExpectCTMaxEntriesPerNik, which is only |
| // applied if there are more than kExpectCTPruneMin entries after global |
| // pruning. |
| TEST_F(TransportSecurityStateTest, PruneExpectCTNetworkIsolationKeyLimit) { |
| const GURL report_uri(kReportUri); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| // enabled_features |
| {TransportSecurityState::kDynamicExpectCTFeature, |
| features::kPartitionExpectCTStateByNetworkIsolationKey}, |
| // disabled_features |
| {}); |
| |
| TransportSecurityState state; |
| |
| // Three different expiration times, which are used to distinguish entries |
| // added by each loop. No entries actually expire in this test. |
| base::Time expiry1 = base::Time::Now() + base::Days(10); |
| base::Time expiry2 = expiry1 + base::Days(10); |
| base::Time expiry3 = expiry2 + base::Days(10); |
| |
| // Add non-prunable entries using different non-transient NIKs. They should |
| // not be pruned because they are recently-observed enforce entries. |
| for (int i = 0; i < features::kExpectCTPruneMax.Get(); ++i) { |
| state.AddExpectCT( |
| CreateUniqueHostName(), expiry1, true /* enforce */, report_uri, |
| CreateUniqueNetworkIsolationKey(false /* is_transient */)); |
| } |
| EXPECT_EQ(features::kExpectCTPruneMax.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // Add kExpectCTMaxEntriesPerNik non-prunable entries with a single NIK, |
| // allowing pruning to run each time. No entries should be deleted. |
| NetworkIsolationKey network_isolation_key = |
| CreateUniqueNetworkIsolationKey(false /* is_transient */); |
| for (int i = 0; i < features::kExpectCTMaxEntriesPerNik.Get(); ++i) { |
| FastForwardBy(base::Seconds(features::kExpectCTPruneDelaySecs.Get())); |
| state.AddExpectCT(CreateUniqueHostName(), expiry2, true /* enforce */, |
| report_uri, network_isolation_key); |
| EXPECT_EQ(features::kExpectCTPruneMax.Get() + i + 1, |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| } |
| |
| // Add kExpectCTMaxEntriesPerNik non-prunable entries with the same NIK as |
| // before, allowing pruning to run each time. Each time, a single entry should |
| // be removed, resulting in the same total number of entries as before. |
| for (int i = 0; i < features::kExpectCTMaxEntriesPerNik.Get(); ++i) { |
| FastForwardBy(base::Seconds(features::kExpectCTPruneDelaySecs.Get())); |
| state.AddExpectCT(CreateUniqueHostName(), expiry3, true /* enforce */, |
| report_uri, network_isolation_key); |
| EXPECT_EQ(features::kExpectCTPruneMax.Get() + |
| features::kExpectCTMaxEntriesPerNik.Get(), |
| static_cast<int>(state.num_expect_ct_entries_for_testing())); |
| |
| // Count entries with |expiry2| and |expiry3|. For each loop iteration, an |
| // entry with |expiry2| should be replaced by one with |expiry3|. |
| int num_expiry2_entries = 0; |
| int num_expiry3_entries = 0; |
| for (TransportSecurityState::ExpectCTStateIterator iterator(state); |
| iterator.HasNext(); iterator.Advance()) { |
| if (iterator.domain_state().expiry == expiry2) { |
| EXPECT_EQ(network_isolation_key, iterator.network_isolation_key()); |
| ++num_expiry2_entries; |
| } else if (iterator.domain_state().expiry == expiry3) { |
| EXPECT_EQ(network_isolation_key, iterator.network_isolation_key()); |
| ++num_expiry3_entries; |
| } |
| } |
| EXPECT_EQ(features::kExpectCTMaxEntriesPerNik.Get() - i - 1, |
| num_expiry2_entries); |
| EXPECT_EQ(i + 1, num_expiry3_entries); |
| } |
| } |
| |
| TEST_F(TransportSecurityStateTest, UpdateKeyPinsListValidPin) { |
| HostPortPair host_port_pair(kHost, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector bad_hashes; |
| |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| std::string unused_failure_log; |
| |
| // Prior to updating the list, bad_hashes should be rejected. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| |
| // Update the pins list, adding bad_hashes to the accepted hashes for this |
| // host. |
| std::vector<std::vector<uint8_t>> accepted_hashes; |
| for (size_t i = 0; kBadPath[i]; i++) { |
| HashValue hash; |
| ASSERT_TRUE(hash.FromString(kBadPath[i])); |
| accepted_hashes.emplace_back(hash.data(), hash.data() + hash.size()); |
| } |
| TransportSecurityState::PinSet test_pinset( |
| /*name=*/"test", |
| /*static_spki_hashes=*/accepted_hashes, |
| /*bad_static_spki_hashes=*/{}, |
| /*report_uri=*/kReportUri); |
| TransportSecurityState::PinSetInfo test_pinsetinfo( |
| /*hostname=*/kHost, /*pinset_name=*/"test", |
| /*include_subdomains=*/false); |
| state.UpdatePinList({test_pinset}, {test_pinsetinfo}, base::Time::Now()); |
| |
| // Hashes should now be accepted. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| } |
| |
| TEST_F(TransportSecurityStateTest, UpdateKeyPinsListNotValidPin) { |
| HostPortPair host_port_pair(kHost, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector good_hashes; |
| |
| for (size_t i = 0; kGoodPath[i]; i++) |
| EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| std::string unused_failure_log; |
| |
| // Prior to updating the list, good_hashes should be accepted |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, good_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| |
| // Update the pins list, adding good_hashes to the rejected hashes for this |
| // host. |
| std::vector<std::vector<uint8_t>> rejected_hashes; |
| for (size_t i = 0; kGoodPath[i]; i++) { |
| HashValue hash; |
| ASSERT_TRUE(hash.FromString(kGoodPath[i])); |
| rejected_hashes.emplace_back(hash.data(), hash.data() + hash.size()); |
| } |
| TransportSecurityState::PinSet test_pinset( |
| /*name=*/"test", |
| /*static_spki_hashes=*/{}, |
| /*bad_static_spki_hashes=*/rejected_hashes, |
| /*report_uri=*/kReportUri); |
| TransportSecurityState::PinSetInfo test_pinsetinfo( |
| /*hostname=*/kHost, /* pinset_name=*/"test", |
| /*include_subdomains=*/false); |
| state.UpdatePinList({test_pinset}, {test_pinsetinfo}, base::Time::Now()); |
| |
| // Hashes should now be rejected. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, good_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| } |
| |
| TEST_F(TransportSecurityStateTest, UpdateKeyPinsEmptyList) { |
| HostPortPair host_port_pair(kHost, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector bad_hashes; |
| |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| std::string unused_failure_log; |
| |
| // Prior to updating the list, bad_hashes should be rejected. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| |
| // Update the pins list with an empty list. |
| state.UpdatePinList({}, {}, base::Time::Now()); |
| |
| // Hashes should now be accepted. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| } |
| |
| TEST_F(TransportSecurityStateTest, UpdateKeyPinsListTimestamp) { |
| HostPortPair host_port_pair(kHost, kPort); |
| GURL report_uri(kReportUri); |
| NetworkIsolationKey network_isolation_key = |
| NetworkIsolationKey::CreateTransient(); |
| // Two dummy certs to use as the server-sent and validated chains. The |
| // contents don't matter. |
| scoped_refptr<X509Certificate> cert1 = |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert1); |
| scoped_refptr<X509Certificate> cert2 = |
| ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); |
| ASSERT_TRUE(cert2); |
| |
| HashValueVector bad_hashes; |
| |
| for (size_t i = 0; kBadPath[i]; i++) |
| EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); |
| |
| TransportSecurityState state; |
| EnableStaticPins(&state); |
| std::string unused_failure_log; |
| |
| // Prior to updating the list, bad_hashes should be rejected. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| |
| // Update the pins list, with bad hashes as rejected, but a timestamp >70 days |
| // old. |
| std::vector<std::vector<uint8_t>> rejected_hashes; |
| for (size_t i = 0; kBadPath[i]; i++) { |
| HashValue hash; |
| ASSERT_TRUE(hash.FromString(kBadPath[i])); |
| rejected_hashes.emplace_back(hash.data(), hash.data() + hash.size()); |
| } |
| TransportSecurityState::PinSet test_pinset( |
| /*name=*/"test", |
| /*static_spki_hashes=*/{}, |
| /*bad_static_spki_hashes=*/rejected_hashes, |
| /*report_uri=*/kReportUri); |
| TransportSecurityState::PinSetInfo test_pinsetinfo( |
| /*hostname=*/kHost, /* pinset_name=*/"test", |
| /*include_subdomains=*/false); |
| state.UpdatePinList({test_pinset}, {test_pinsetinfo}, |
| base::Time::Now() - base::Days(70)); |
| |
| // Hashes should now be accepted. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::OK, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| |
| // Update the pins list again, with a timestamp <70 days old. |
| state.UpdatePinList({test_pinset}, {test_pinsetinfo}, |
| base::Time::Now() - base::Days(69)); |
| |
| // Hashes should now be rejected. |
| EXPECT_EQ(TransportSecurityState::PKPStatus::VIOLATED, |
| state.CheckPublicKeyPins( |
| host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), |
| TransportSecurityState::ENABLE_PIN_REPORTS, |
| network_isolation_key, &unused_failure_log)); |
| } |
| |
| } // namespace net |