| // 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_persister.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "net/http/transport_security_state.h" |
| #include "net/test/test_with_scoped_task_environment.h" |
| #include "starboard/memory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kReportUri[] = "http://www.example.test/report"; |
| |
| class TransportSecurityPersisterTest : public TestWithScopedTaskEnvironment { |
| public: |
| TransportSecurityPersisterTest() = default; |
| |
| ~TransportSecurityPersisterTest() override { |
| EXPECT_TRUE(base::MessageLoopForIO::IsCurrent()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| ASSERT_TRUE(base::MessageLoopForIO::IsCurrent()); |
| persister_ = std::make_unique<TransportSecurityPersister>( |
| &state_, temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| protected: |
| base::ScopedTempDir temp_dir_; |
| TransportSecurityState state_; |
| std::unique_ptr<TransportSecurityPersister> persister_; |
| }; |
| |
| // Tests that LoadEntries() clears existing non-static entries. |
| TEST_F(TransportSecurityPersisterTest, LoadEntriesClearsExistingState) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| std::string output; |
| bool dirty; |
| |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| static const char kYahooDomain[] = "yahoo.com"; |
| |
| EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state)); |
| EXPECT_FALSE(state_.GetDynamicPKPState(kYahooDomain, &pkp_state)); |
| |
| state_.AddHSTS(kYahooDomain, expiry, false /* include subdomains */); |
| HashValue spki(HASH_VALUE_SHA256); |
| SbMemorySet(spki.data(), 0, spki.size()); |
| HashValueVector dynamic_spki_hashes; |
| dynamic_spki_hashes.push_back(spki); |
| state_.AddHPKP(kYahooDomain, expiry, false, dynamic_spki_hashes, GURL()); |
| state_.AddExpectCT(kYahooDomain, expiry, true /* enforce */, GURL()); |
| |
| EXPECT_TRUE(state_.GetDynamicSTSState(kYahooDomain, &sts_state)); |
| EXPECT_TRUE(state_.GetDynamicPKPState(kYahooDomain, &pkp_state)); |
| EXPECT_TRUE(state_.GetDynamicExpectCTState(kYahooDomain, &expect_ct_state)); |
| |
| EXPECT_TRUE(persister_->LoadEntries("{}", &dirty)); |
| EXPECT_FALSE(dirty); |
| |
| EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state)); |
| EXPECT_FALSE(state_.GetDynamicPKPState(kYahooDomain, &pkp_state)); |
| EXPECT_FALSE(state_.GetDynamicExpectCTState(kYahooDomain, &expect_ct_state)); |
| } |
| |
| TEST_F(TransportSecurityPersisterTest, SerializeData1) { |
| std::string output; |
| bool dirty; |
| |
| EXPECT_TRUE(persister_->SerializeData(&output)); |
| EXPECT_TRUE(persister_->LoadEntries(output, &dirty)); |
| EXPECT_FALSE(dirty); |
| } |
| |
| TEST_F(TransportSecurityPersisterTest, SerializeData2) { |
| TransportSecurityState::STSState sts_state; |
| TransportSecurityState::PKPState pkp_state; |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| static const char kYahooDomain[] = "yahoo.com"; |
| |
| EXPECT_FALSE( |
| state_.GetStaticDomainState(kYahooDomain, &sts_state, &pkp_state)); |
| EXPECT_FALSE(state_.GetDynamicSTSState(kYahooDomain, &sts_state)); |
| EXPECT_FALSE(state_.GetDynamicPKPState(kYahooDomain, &pkp_state)); |
| |
| bool include_subdomains = true; |
| state_.AddHSTS(kYahooDomain, expiry, include_subdomains); |
| |
| std::string output; |
| bool dirty; |
| EXPECT_TRUE(persister_->SerializeData(&output)); |
| EXPECT_TRUE(persister_->LoadEntries(output, &dirty)); |
| |
| EXPECT_TRUE(state_.GetDynamicSTSState(kYahooDomain, &sts_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_TRUE(state_.GetDynamicSTSState("foo.yahoo.com", &sts_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_TRUE(state_.GetDynamicSTSState("foo.bar.yahoo.com", &sts_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_TRUE(state_.GetDynamicSTSState("foo.bar.baz.yahoo.com", &sts_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| EXPECT_FALSE(state_.GetStaticDomainState("com", &sts_state, &pkp_state)); |
| } |
| |
| TEST_F(TransportSecurityPersisterTest, SerializeData3) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const GURL report_uri(kReportUri); |
| // Add an entry. |
| HashValue fp1(HASH_VALUE_SHA256); |
| SbMemorySet(fp1.data(), 0, fp1.size()); |
| HashValue fp2(HASH_VALUE_SHA256); |
| SbMemorySet(fp2.data(), 1, fp2.size()); |
| base::Time expiry = |
| base::Time::Now() + base::TimeDelta::FromSeconds(1000); |
| HashValueVector dynamic_spki_hashes; |
| dynamic_spki_hashes.push_back(fp1); |
| dynamic_spki_hashes.push_back(fp2); |
| bool include_subdomains = false; |
| state_.AddHSTS("www.example.com", expiry, include_subdomains); |
| state_.AddHPKP("www.example.com", expiry, include_subdomains, |
| dynamic_spki_hashes, report_uri); |
| state_.AddExpectCT("www.example.com", expiry, true /* enforce */, GURL()); |
| |
| // Add another entry. |
| SbMemorySet(fp1.data(), 2, fp1.size()); |
| SbMemorySet(fp2.data(), 3, fp2.size()); |
| expiry = |
| base::Time::Now() + base::TimeDelta::FromSeconds(3000); |
| dynamic_spki_hashes.push_back(fp1); |
| dynamic_spki_hashes.push_back(fp2); |
| state_.AddHSTS("www.example.net", expiry, include_subdomains); |
| state_.AddHPKP("www.example.net", expiry, include_subdomains, |
| dynamic_spki_hashes, report_uri); |
| state_.AddExpectCT("www.example.net", expiry, false /* enforce */, |
| report_uri); |
| |
| // Save a copy of everything. |
| std::set<std::string> sts_saved; |
| TransportSecurityState::STSStateIterator sts_iter(state_); |
| while (sts_iter.HasNext()) { |
| sts_saved.insert(sts_iter.hostname()); |
| sts_iter.Advance(); |
| } |
| |
| std::set<std::string> pkp_saved; |
| TransportSecurityState::PKPStateIterator pkp_iter(state_); |
| while (pkp_iter.HasNext()) { |
| pkp_saved.insert(pkp_iter.hostname()); |
| pkp_iter.Advance(); |
| } |
| |
| std::set<std::string> expect_ct_saved; |
| TransportSecurityState::ExpectCTStateIterator expect_ct_iter(state_); |
| while (expect_ct_iter.HasNext()) { |
| expect_ct_saved.insert(expect_ct_iter.hostname()); |
| expect_ct_iter.Advance(); |
| } |
| |
| std::string serialized; |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| |
| // Persist the data to the file. For the test to be fast and not flaky, we |
| // just do it directly rather than call persister_->StateIsDirty. (That uses |
| // ImportantFileWriter, which has an asynchronous commit interval rather |
| // than block.) Use a different basename just for cleanliness. |
| base::FilePath path = |
| temp_dir_.GetPath().AppendASCII("TransportSecurityPersisterTest"); |
| EXPECT_EQ(static_cast<int>(serialized.size()), |
| base::WriteFile(path, serialized.c_str(), serialized.size())); |
| |
| // Read the data back. |
| std::string persisted; |
| EXPECT_TRUE(base::ReadFileToString(path, &persisted)); |
| EXPECT_EQ(persisted, serialized); |
| bool dirty; |
| EXPECT_TRUE(persister_->LoadEntries(persisted, &dirty)); |
| EXPECT_FALSE(dirty); |
| |
| // Check that states are the same as saved. |
| size_t count = 0; |
| TransportSecurityState::STSStateIterator sts_iter2(state_); |
| while (sts_iter2.HasNext()) { |
| count++; |
| sts_iter2.Advance(); |
| } |
| EXPECT_EQ(count, sts_saved.size()); |
| |
| count = 0; |
| TransportSecurityState::PKPStateIterator pkp_iter2(state_); |
| while (pkp_iter2.HasNext()) { |
| count++; |
| pkp_iter2.Advance(); |
| } |
| EXPECT_EQ(count, pkp_saved.size()); |
| |
| count = 0; |
| TransportSecurityState::ExpectCTStateIterator expect_ct_iter2(state_); |
| while (expect_ct_iter2.HasNext()) { |
| count++; |
| expect_ct_iter2.Advance(); |
| } |
| EXPECT_EQ(count, expect_ct_saved.size()); |
| } |
| |
| TEST_F(TransportSecurityPersisterTest, SerializeDataOld) { |
| // This is an old-style piece of transport state JSON, which has no creation |
| // date. |
| std::string output = |
| "{ " |
| "\"NiyD+3J1r6z1wjl2n1ALBu94Zj9OsEAMo0kCN8js0Uk=\": {" |
| "\"expiry\": 1266815027.983453, " |
| "\"include_subdomains\": false, " |
| "\"mode\": \"strict\" " |
| "}" |
| "}"; |
| bool dirty; |
| EXPECT_TRUE(persister_->LoadEntries(output, &dirty)); |
| EXPECT_TRUE(dirty); |
| } |
| |
| TEST_F(TransportSecurityPersisterTest, PublicKeyPins) { |
| const GURL report_uri(kReportUri); |
| TransportSecurityState::PKPState pkp_state; |
| static const char kTestDomain[] = "example.com"; |
| |
| EXPECT_FALSE(state_.GetDynamicPKPState(kTestDomain, &pkp_state)); |
| HashValueVector hashes; |
| std::string failure_log; |
| EXPECT_FALSE(pkp_state.CheckPublicKeyPins(hashes, &failure_log)); |
| |
| HashValue sha256(HASH_VALUE_SHA256); |
| SbMemorySet(sha256.data(), '1', sha256.size()); |
| pkp_state.spki_hashes.push_back(sha256); |
| |
| EXPECT_FALSE(pkp_state.CheckPublicKeyPins(hashes, &failure_log)); |
| |
| hashes.push_back(sha256); |
| EXPECT_TRUE(pkp_state.CheckPublicKeyPins(hashes, &failure_log)); |
| |
| hashes[0].data()[0] = '2'; |
| EXPECT_FALSE(pkp_state.CheckPublicKeyPins(hashes, &failure_log)); |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| bool include_subdomains = false; |
| state_.AddHSTS(kTestDomain, expiry, include_subdomains); |
| state_.AddHPKP(kTestDomain, expiry, include_subdomains, pkp_state.spki_hashes, |
| report_uri); |
| std::string serialized; |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| bool dirty; |
| EXPECT_TRUE(persister_->LoadEntries(serialized, &dirty)); |
| |
| TransportSecurityState::PKPState new_pkp_state; |
| EXPECT_TRUE(state_.GetDynamicPKPState(kTestDomain, &new_pkp_state)); |
| EXPECT_EQ(1u, new_pkp_state.spki_hashes.size()); |
| EXPECT_EQ(sha256.tag(), new_pkp_state.spki_hashes[0].tag()); |
| EXPECT_EQ(0, SbMemoryCompare(new_pkp_state.spki_hashes[0].data(), |
| sha256.data(), sha256.size())); |
| EXPECT_EQ(report_uri, new_pkp_state.report_uri); |
| } |
| |
| // Tests that dynamic Expect-CT state is serialized and deserialized correctly. |
| TEST_F(TransportSecurityPersisterTest, ExpectCT) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const GURL report_uri(kReportUri); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| static const char kTestDomain[] = "example.test"; |
| |
| EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state)); |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL()); |
| std::string serialized; |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| bool dirty; |
| // LoadEntries() clears existing dynamic data before loading entries from |
| // |serialized|. |
| EXPECT_TRUE(persister_->LoadEntries(serialized, &dirty)); |
| |
| TransportSecurityState::ExpectCTState new_expect_ct_state; |
| EXPECT_TRUE( |
| state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state)); |
| EXPECT_TRUE(new_expect_ct_state.enforce); |
| EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty()); |
| EXPECT_EQ(expiry, new_expect_ct_state.expiry); |
| |
| // Update the state for the domain and check that it is |
| // serialized/deserialized correctly. |
| state_.AddExpectCT(kTestDomain, expiry, false /* enforce */, report_uri); |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| EXPECT_TRUE(persister_->LoadEntries(serialized, &dirty)); |
| EXPECT_TRUE( |
| state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state)); |
| EXPECT_FALSE(new_expect_ct_state.enforce); |
| EXPECT_EQ(report_uri, new_expect_ct_state.report_uri); |
| EXPECT_EQ(expiry, new_expect_ct_state.expiry); |
| } |
| |
| // Tests that dynamic Expect-CT state is serialized and deserialized correctly |
| // when there is also PKP and STS data present. |
| TEST_F(TransportSecurityPersisterTest, ExpectCTWithSTSAndPKPDataPresent) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const GURL report_uri(kReportUri); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| static const char kTestDomain[] = "example.test"; |
| |
| EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state)); |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| state_.AddHSTS(kTestDomain, expiry, false /* include subdomains */); |
| state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL()); |
| HashValue spki_hash(HASH_VALUE_SHA256); |
| SbMemorySet(spki_hash.data(), 0, spki_hash.size()); |
| HashValueVector dynamic_spki_hashes; |
| dynamic_spki_hashes.push_back(spki_hash); |
| state_.AddHPKP(kTestDomain, expiry, false /* include subdomains */, |
| dynamic_spki_hashes, GURL()); |
| |
| std::string serialized; |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| bool dirty; |
| // LoadEntries() clears existing dynamic data before loading entries from |
| // |serialized|. |
| EXPECT_TRUE(persister_->LoadEntries(serialized, &dirty)); |
| |
| TransportSecurityState::ExpectCTState new_expect_ct_state; |
| EXPECT_TRUE( |
| state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state)); |
| EXPECT_TRUE(new_expect_ct_state.enforce); |
| EXPECT_TRUE(new_expect_ct_state.report_uri.is_empty()); |
| EXPECT_EQ(expiry, new_expect_ct_state.expiry); |
| // Check that STS and PKP state are loaded properly as well. |
| TransportSecurityState::STSState sts_state; |
| EXPECT_TRUE(state_.GetDynamicSTSState(kTestDomain, &sts_state)); |
| EXPECT_EQ(sts_state.upgrade_mode, |
| TransportSecurityState::STSState::MODE_FORCE_HTTPS); |
| TransportSecurityState::PKPState pkp_state; |
| EXPECT_TRUE(state_.GetDynamicPKPState(kTestDomain, &pkp_state)); |
| EXPECT_EQ(1u, pkp_state.spki_hashes.size()); |
| EXPECT_EQ(0, SbMemoryCompare(pkp_state.spki_hashes[0].data(), |
| spki_hash.data(), spki_hash.size())); |
| } |
| |
| // Tests that Expect-CT state is not serialized and persisted when the feature |
| // is disabled. |
| TEST_F(TransportSecurityPersisterTest, ExpectCTDisabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| const GURL report_uri(kReportUri); |
| TransportSecurityState::ExpectCTState expect_ct_state; |
| static const char kTestDomain[] = "example.test"; |
| |
| EXPECT_FALSE(state_.GetDynamicExpectCTState(kTestDomain, &expect_ct_state)); |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| state_.AddExpectCT(kTestDomain, expiry, true /* enforce */, GURL()); |
| std::string serialized; |
| EXPECT_TRUE(persister_->SerializeData(&serialized)); |
| bool dirty; |
| EXPECT_TRUE(persister_->LoadEntries(serialized, &dirty)); |
| |
| TransportSecurityState::ExpectCTState new_expect_ct_state; |
| EXPECT_FALSE( |
| state_.GetDynamicExpectCTState(kTestDomain, &new_expect_ct_state)); |
| } |
| |
| } // namespace |
| |
| } // namespace net |