|  | // 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 |