blob: b315f7d9af2456a7c93ab4d605fc67220ac3a1a2 [file] [log] [blame]
// 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