blob: 061f000e2a113898b13b3b694eb2d2d7aa6ba03b [file] [log] [blame]
Avi Drissman64595482022-09-14 20:52:291// Copyright 2012 The Chromium Authors
[email protected]f43b89f32012-05-01 19:39:482// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]ae4a77fa2013-11-13 00:09:465#include "net/http/transport_security_persister.h"
[email protected]f43b89f32012-05-01 19:39:486
[email protected]83980c72012-05-09 17:57:237#include <map>
danakj1fd259a02016-04-16 03:17:098#include <memory>
[email protected]f43b89f32012-05-01 19:39:489#include <string>
[email protected]ede03212012-09-07 12:52:2610#include <vector>
[email protected]f43b89f32012-05-01 19:39:4811
[email protected]57999812013-02-24 05:40:5212#include "base/files/file_path.h"
thestigd8df0332014-09-04 06:33:2913#include "base/files/file_util.h"
[email protected]ea1a3f62012-11-16 20:34:2314#include "base/files/scoped_temp_dir.h"
Matt Menke6e2bf4e2021-03-05 14:45:4515#include "base/json/json_writer.h"
fdoray6ef45cf2016-08-25 15:36:3716#include "base/run_loop.h"
Matt Menke6e2bf4e2021-03-05 14:45:4517#include "base/strings/string_util.h"
Carlos Caballerob25fe8472020-07-17 10:27:1718#include "base/task/current_thread.h"
Sean Mahere672a662023-01-09 21:42:2819#include "base/task/sequenced_task_runner.h"
Gabriel Charette99f5df32021-03-19 19:55:5520#include "base/task/thread_pool.h"
estarka3682322017-04-19 00:33:5621#include "base/test/scoped_feature_list.h"
Matt Menkead7fa0b2020-06-06 08:50:0122#include "net/base/features.h"
Brianna Goldsteind22b0642022-10-11 16:30:5023#include "net/base/network_anonymization_key.h"
Matt Menke4807a9a2020-11-21 00:14:4124#include "net/base/schemeful_site.h"
[email protected]536fd0b2013-03-14 17:41:5725#include "net/http/transport_security_state.h"
Gabriel Charettec7108742019-08-23 03:31:4026#include "net/test/test_with_task_environment.h"
[email protected]f43b89f32012-05-01 19:39:4827#include "testing/gtest/include/gtest/gtest.h"
Matt Menke911e448d2020-05-29 00:03:3828#include "url/gurl.h"
[email protected]f43b89f32012-05-01 19:39:4829
ttuttle859dc7a2015-04-23 19:42:2930namespace net {
31
32namespace {
[email protected]f43b89f32012-05-01 19:39:4833
estark83487b62015-07-27 17:11:1434const char kReportUri[] = "http://www.example.test/report";
35
Emily Starkfcd1ed2e2022-12-07 18:37:4636class TransportSecurityPersisterTest : public ::testing::Test,
Matt Menke911e448d2020-05-29 00:03:3837 public WithTaskEnvironment {
[email protected]f43b89f32012-05-01 19:39:4838 public:
Matt Menke911e448d2020-05-29 00:03:3839 TransportSecurityPersisterTest()
40 : WithTaskEnvironment(
41 base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
42 // Mock out time so that entries with hard-coded json data can be
43 // successfully loaded. Use a large enough value that dynamically created
44 // entries have at least somewhat interesting expiration times.
Peter Kastinge5a38ed2021-10-02 03:06:3545 FastForwardBy(base::Days(3660));
Matt Menke911e448d2020-05-29 00:03:3846 }
[email protected]f43b89f32012-05-01 19:39:4847
dcheng67be2b1f2014-10-27 21:47:2948 ~TransportSecurityPersisterTest() override {
Carlos Caballerob25fe8472020-07-17 10:27:1749 EXPECT_TRUE(base::CurrentIOThread::IsSet());
fdoray6ef45cf2016-08-25 15:36:3750 base::RunLoop().RunUntilIdle();
[email protected]179dbf252012-05-15 19:59:2551 }
52
dcheng67be2b1f2014-10-27 21:47:2953 void SetUp() override {
[email protected]83980c72012-05-09 17:57:2354 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
Will Harris62f94bd52021-08-11 16:48:4055 transport_security_file_path_ =
56 temp_dir_.GetPath().AppendASCII("TransportSecurity");
Carlos Caballerob25fe8472020-07-17 10:27:1757 ASSERT_TRUE(base::CurrentIOThread::IsSet());
Christian Dullweber836696e2020-05-12 18:21:5558 scoped_refptr<base::SequencedTaskRunner> background_runner(
59 base::ThreadPool::CreateSequencedTaskRunner(
60 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
61 base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
Matt Menkead7fa0b2020-06-06 08:50:0162 state_ = std::make_unique<TransportSecurityState>();
Matt Menkeffa8fc22017-11-08 11:22:1563 persister_ = std::make_unique<TransportSecurityPersister>(
Will Harris62f94bd52021-08-11 16:48:4064 state_.get(), std::move(background_runner),
65 transport_security_file_path_);
[email protected]83980c72012-05-09 17:57:2366 }
[email protected]f43b89f32012-05-01 19:39:4867
[email protected]83980c72012-05-09 17:57:2368 protected:
Will Harris62f94bd52021-08-11 16:48:4069 base::FilePath transport_security_file_path_;
[email protected]ea1a3f62012-11-16 20:34:2370 base::ScopedTempDir temp_dir_;
Matt Menkead7fa0b2020-06-06 08:50:0171 std::unique_ptr<TransportSecurityState> state_;
danakj1fd259a02016-04-16 03:17:0972 std::unique_ptr<TransportSecurityPersister> persister_;
[email protected]f43b89f32012-05-01 19:39:4873};
74
estarka3682322017-04-19 00:33:5675// Tests that LoadEntries() clears existing non-static entries.
Emily Starkfcd1ed2e2022-12-07 18:37:4676TEST_F(TransportSecurityPersisterTest, LoadEntriesClearsExistingState) {
estarka3682322017-04-19 00:33:5677 TransportSecurityState::STSState sts_state;
estarka3682322017-04-19 00:33:5678 const base::Time current_time(base::Time::Now());
Peter Kastinge5a38ed2021-10-02 03:06:3579 const base::Time expiry = current_time + base::Seconds(1000);
estarka3682322017-04-19 00:33:5680 static const char kYahooDomain[] = "yahoo.com";
81
Matt Menkead7fa0b2020-06-06 08:50:0182 EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
estarka3682322017-04-19 00:33:5683
Matt Menkead7fa0b2020-06-06 08:50:0184 state_->AddHSTS(kYahooDomain, expiry, false /* include subdomains */);
Matt Menkead7fa0b2020-06-06 08:50:0185 EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
estarka3682322017-04-19 00:33:5686
Chris Thompson2e85f54e2021-05-18 22:40:1887 persister_->LoadEntries("{\"version\":2}");
estarka3682322017-04-19 00:33:5688
Matt Menkead7fa0b2020-06-06 08:50:0189 EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
estarka3682322017-04-19 00:33:5690}
91
Chris Thompson2e85f54e2021-05-18 22:40:1892// Tests that serializing -> deserializing -> reserializing results in the same
93// output.
Emily Starkfcd1ed2e2022-12-07 18:37:4694TEST_F(TransportSecurityPersisterTest, SerializeData1) {
Claudio DeSouzaf3385c732023-03-13 12:36:2395 absl::optional<std::string> output = persister_->SerializeData();
[email protected]f43b89f32012-05-01 19:39:4896
Claudio DeSouzaf3385c732023-03-13 12:36:2397 ASSERT_TRUE(output);
98 persister_->LoadEntries(*output);
Chris Thompson2e85f54e2021-05-18 22:40:1899
Claudio DeSouzaf3385c732023-03-13 12:36:23100 absl::optional<std::string> output2 = persister_->SerializeData();
101 ASSERT_TRUE(output2);
Chris Thompson2e85f54e2021-05-18 22:40:18102 EXPECT_EQ(output, output2);
[email protected]f43b89f32012-05-01 19:39:48103}
104
Emily Starkfcd1ed2e2022-12-07 18:37:46105TEST_F(TransportSecurityPersisterTest, SerializeData2) {
martijnc0d6b622015-06-30 19:14:40106 TransportSecurityState::STSState sts_state;
[email protected]f43b89f32012-05-01 19:39:48107 const base::Time current_time(base::Time::Now());
Peter Kastinge5a38ed2021-10-02 03:06:35108 const base::Time expiry = current_time + base::Seconds(1000);
[email protected]83980c72012-05-09 17:57:23109 static const char kYahooDomain[] = "yahoo.com";
[email protected]f43b89f32012-05-01 19:39:48110
Matt Menkead7fa0b2020-06-06 08:50:01111 EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
[email protected]474f079e2013-03-02 19:11:20112
113 bool include_subdomains = true;
Matt Menkead7fa0b2020-06-06 08:50:01114 state_->AddHSTS(kYahooDomain, expiry, include_subdomains);
[email protected]f43b89f32012-05-01 19:39:48115
Claudio DeSouzaf3385c732023-03-13 12:36:23116 absl::optional<std::string> output = persister_->SerializeData();
117 ASSERT_TRUE(output);
118 persister_->LoadEntries(*output);
[email protected]f43b89f32012-05-01 19:39:48119
Matt Menkead7fa0b2020-06-06 08:50:01120 EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
martijnc0d6b622015-06-30 19:14:40121 EXPECT_EQ(sts_state.upgrade_mode,
122 TransportSecurityState::STSState::MODE_FORCE_HTTPS);
Matt Menkead7fa0b2020-06-06 08:50:01123 EXPECT_TRUE(state_->GetDynamicSTSState("foo.yahoo.com", &sts_state));
martijnc0d6b622015-06-30 19:14:40124 EXPECT_EQ(sts_state.upgrade_mode,
125 TransportSecurityState::STSState::MODE_FORCE_HTTPS);
Matt Menkead7fa0b2020-06-06 08:50:01126 EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.yahoo.com", &sts_state));
martijnc0d6b622015-06-30 19:14:40127 EXPECT_EQ(sts_state.upgrade_mode,
128 TransportSecurityState::STSState::MODE_FORCE_HTTPS);
Matt Menkead7fa0b2020-06-06 08:50:01129 EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.baz.yahoo.com", &sts_state));
martijnc0d6b622015-06-30 19:14:40130 EXPECT_EQ(sts_state.upgrade_mode,
131 TransportSecurityState::STSState::MODE_FORCE_HTTPS);
[email protected]f43b89f32012-05-01 19:39:48132}
133
Emily Starkfcd1ed2e2022-12-07 18:37:46134TEST_F(TransportSecurityPersisterTest, SerializeData3) {
estark83487b62015-07-27 17:11:14135 const GURL report_uri(kReportUri);
[email protected]f43b89f32012-05-01 19:39:48136 // Add an entry.
Peter Kastinge5a38ed2021-10-02 03:06:35137 base::Time expiry = base::Time::Now() + base::Seconds(1000);
[email protected]474f079e2013-03-02 19:11:20138 bool include_subdomains = false;
Matt Menkead7fa0b2020-06-06 08:50:01139 state_->AddHSTS("www.example.com", expiry, include_subdomains);
[email protected]f43b89f32012-05-01 19:39:48140
141 // Add another entry.
Peter Kastinge5a38ed2021-10-02 03:06:35142 expiry = base::Time::Now() + base::Seconds(3000);
Matt Menkead7fa0b2020-06-06 08:50:01143 state_->AddHSTS("www.example.net", expiry, include_subdomains);
[email protected]f43b89f32012-05-01 19:39:48144
145 // Save a copy of everything.
Eric Orth0eeeef182022-12-01 23:49:57146 std::set<TransportSecurityState::HashedHost> sts_saved;
Matt Menkead7fa0b2020-06-06 08:50:01147 TransportSecurityState::STSStateIterator sts_iter(*state_);
martijnc0d6b622015-06-30 19:14:40148 while (sts_iter.HasNext()) {
149 sts_saved.insert(sts_iter.hostname());
150 sts_iter.Advance();
151 }
152
Claudio DeSouzaf3385c732023-03-13 12:36:23153 absl::optional<std::string> serialized = persister_->SerializeData();
154 ASSERT_TRUE(serialized);
[email protected]f43b89f32012-05-01 19:39:48155
Christian Dullweberee89d672018-11-29 15:12:28156 // Persist the data to the file.
157 base::RunLoop run_loop;
Matt Menkead7fa0b2020-06-06 08:50:01158 persister_->WriteNow(state_.get(), run_loop.QuitClosure());
Christian Dullweberee89d672018-11-29 15:12:28159 run_loop.Run();
[email protected]f43b89f32012-05-01 19:39:48160
161 // Read the data back.
162 std::string persisted;
Will Harris62f94bd52021-08-11 16:48:40163 EXPECT_TRUE(
164 base::ReadFileToString(transport_security_file_path_, &persisted));
[email protected]f43b89f32012-05-01 19:39:48165 EXPECT_EQ(persisted, serialized);
Chris Thompson2e85f54e2021-05-18 22:40:18166 persister_->LoadEntries(persisted);
[email protected]f43b89f32012-05-01 19:39:48167
168 // Check that states are the same as saved.
169 size_t count = 0;
Matt Menkead7fa0b2020-06-06 08:50:01170 TransportSecurityState::STSStateIterator sts_iter2(*state_);
martijnc0d6b622015-06-30 19:14:40171 while (sts_iter2.HasNext()) {
[email protected]f43b89f32012-05-01 19:39:48172 count++;
martijnc0d6b622015-06-30 19:14:40173 sts_iter2.Advance();
[email protected]f43b89f32012-05-01 19:39:48174 }
martijnc0d6b622015-06-30 19:14:40175 EXPECT_EQ(count, sts_saved.size());
[email protected]f43b89f32012-05-01 19:39:48176}
177
Emily Starkfcd1ed2e2022-12-07 18:37:46178// Tests that deserializing bad data shouldn't result in any STS entries being
179// added to the transport security state.
180TEST_F(TransportSecurityPersisterTest, DeserializeBadData) {
Chris Thompson2e85f54e2021-05-18 22:40:18181 persister_->LoadEntries("");
Chris Thompson2e85f54e2021-05-18 22:40:18182 EXPECT_EQ(0u, state_->num_sts_entries());
183
184 persister_->LoadEntries("Foopy");
Chris Thompson2e85f54e2021-05-18 22:40:18185 EXPECT_EQ(0u, state_->num_sts_entries());
186
187 persister_->LoadEntries("15");
Chris Thompson2e85f54e2021-05-18 22:40:18188 EXPECT_EQ(0u, state_->num_sts_entries());
189
190 persister_->LoadEntries("[15]");
Chris Thompson2e85f54e2021-05-18 22:40:18191 EXPECT_EQ(0u, state_->num_sts_entries());
192
193 persister_->LoadEntries("{\"version\":1}");
Chris Thompson2e85f54e2021-05-18 22:40:18194 EXPECT_EQ(0u, state_->num_sts_entries());
Matt Menke911e448d2020-05-29 00:03:38195}
196
Emily Starkfcd1ed2e2022-12-07 18:37:46197TEST_F(TransportSecurityPersisterTest, DeserializeDataOldWithoutCreationDate) {
[email protected]f43b89f32012-05-01 19:39:48198 // This is an old-style piece of transport state JSON, which has no creation
199 // date.
Matt Menke911e448d2020-05-29 00:03:38200 const std::string kInput =
[email protected]f43b89f32012-05-01 19:39:48201 "{ "
Matt Menke911e448d2020-05-29 00:03:38202 "\"G0EywIek2XnIhLrUjaK4TrHBT1+2TcixDVRXwM3/CCo=\": {"
[email protected]f43b89f32012-05-01 19:39:48203 "\"expiry\": 1266815027.983453, "
204 "\"include_subdomains\": false, "
205 "\"mode\": \"strict\" "
206 "}"
207 "}";
Chris Thompson2e85f54e2021-05-18 22:40:18208 persister_->LoadEntries(kInput);
Chris Thompson2e85f54e2021-05-18 22:40:18209 EXPECT_EQ(0u, state_->num_sts_entries());
Matt Menke911e448d2020-05-29 00:03:38210}
211
Emily Starkfcd1ed2e2022-12-07 18:37:46212TEST_F(TransportSecurityPersisterTest, DeserializeDataOldMergedDictionary) {
Matt Menke911e448d2020-05-29 00:03:38213 // This is an old-style piece of transport state JSON, which uses a single
214 // unversioned host-keyed dictionary of merged ExpectCT and HSTS data.
215 const std::string kInput =
216 "{"
217 " \"CxLbri+JPdi5pZ8/a/2rjyzq+IYs07WJJ1yxjB4Lpw0=\": {"
218 " \"expect_ct\": {"
219 " \"expect_ct_enforce\": true,"
220 " \"expect_ct_expiry\": 1590512843.283966,"
221 " \"expect_ct_observed\": 1590511843.284064,"
222 " \"expect_ct_report_uri\": \"https://expect_ct.test/report_uri\""
223 " },"
224 " \"expiry\": 0.0,"
225 " \"mode\": \"default\","
226 " \"sts_include_subdomains\": false,"
227 " \"sts_observed\": 0.0"
228 " },"
229 " \"DkgjGShIBmYtgJcJf5lfX3rTr2S6dqyF+O8IAgjuleE=\": {"
230 " \"expiry\": 1590512843.283966,"
231 " \"mode\": \"force-https\","
232 " \"sts_include_subdomains\": false,"
233 " \"sts_observed\": 1590511843.284025"
234 " },"
235 " \"M5lkNV3JBeoPMlKrTOKRYT+mrUsZCS5eoQWsc9/r1MU=\": {"
236 " \"expect_ct\": {"
237 " \"expect_ct_enforce\": true,"
238 " \"expect_ct_expiry\": 1590512843.283966,"
239 " \"expect_ct_observed\": 1590511843.284098,"
240 " \"expect_ct_report_uri\": \"\""
241 " },"
242 " \"expiry\": 1590512843.283966,"
243 " \"mode\": \"force-https\","
244 " \"sts_include_subdomains\": true,"
245 " \"sts_observed\": 1590511843.284091"
246 " }"
247 "}";
248
Chris Thompson2e85f54e2021-05-18 22:40:18249 persister_->LoadEntries(kInput);
Chris Thompson2e85f54e2021-05-18 22:40:18250 EXPECT_EQ(0u, state_->num_sts_entries());
[email protected]f43b89f32012-05-01 19:39:48251}
252
Emily Starkfcd1ed2e2022-12-07 18:37:46253TEST_F(TransportSecurityPersisterTest, DeserializeLegacyExpectCTData) {
254 const std::string kHost = "CxLbri+JPdi5pZ8/a/2rjyzq+IYs07WJJ1yxjB4Lpw0=";
255 const std::string kInput =
256 R"({"version":2, "sts": [{ "host": ")" + kHost +
257 R"(", "mode": "force-https", "sts_include_subdomains": false, )"
258 R"("sts_observed": 0.0, "expiry": 4825336765.0}], "expect_ct": [{"host":)"
259 R"("CxLbri+JPdi5pZ8/a/2rjyzq+IYs07WJJ1yxjB4Lpw0=", "nak": "test", )"
260 R"("expect_ct_observed": 0.0, "expect_ct_expiry": 4825336765.0, )"
261 R"("expect_ct_enforce": true, "expect_ct_report_uri": ""}]})";
262 LOG(ERROR) << kInput;
263 constexpr auto kDefaultFileWriterCommitInterval = base::Seconds(10);
264 persister_->LoadEntries(kInput);
265 FastForwardBy(kDefaultFileWriterCommitInterval + base::Seconds(1));
266 EXPECT_EQ(1u, state_->num_sts_entries());
267 // Now read the data and check that there are no Expect-CT entries.
268 std::string persisted;
269 ASSERT_TRUE(
270 base::ReadFileToString(transport_security_file_path_, &persisted));
271 // Smoke test that the file contains some data as expected...
272 ASSERT_NE(std::string::npos, persisted.find(kHost));
273 // But it shouldn't contain any Expect-CT data.
274 EXPECT_EQ(std::string::npos, persisted.find("expect_ct"));
estarka3682322017-04-19 00:33:56275}
276
ttuttle859dc7a2015-04-23 19:42:29277} // namespace
278
279} // namespace net