xref: /aosp_15_r20/external/cronet/net/http/transport_security_persister_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/http/transport_security_persister.h"
6 
7 #include <map>
8 #include <memory>
9 #include <string>
10 #include <vector>
11 
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/json/json_writer.h"
16 #include "base/run_loop.h"
17 #include "base/strings/string_util.h"
18 #include "base/task/current_thread.h"
19 #include "base/task/sequenced_task_runner.h"
20 #include "base/task/thread_pool.h"
21 #include "base/test/scoped_feature_list.h"
22 #include "net/base/features.h"
23 #include "net/base/network_anonymization_key.h"
24 #include "net/base/schemeful_site.h"
25 #include "net/http/transport_security_state.h"
26 #include "net/test/test_with_task_environment.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "url/gurl.h"
29 
30 namespace net {
31 
32 namespace {
33 
34 const char kReportUri[] = "http://www.example.test/report";
35 
36 class TransportSecurityPersisterTest : public ::testing::Test,
37                                        public WithTaskEnvironment {
38  public:
TransportSecurityPersisterTest()39   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.
45     FastForwardBy(base::Days(3660));
46   }
47 
~TransportSecurityPersisterTest()48   ~TransportSecurityPersisterTest() override {
49     EXPECT_TRUE(base::CurrentIOThread::IsSet());
50     base::RunLoop().RunUntilIdle();
51   }
52 
SetUp()53   void SetUp() override {
54     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
55     transport_security_file_path_ =
56         temp_dir_.GetPath().AppendASCII("TransportSecurity");
57     ASSERT_TRUE(base::CurrentIOThread::IsSet());
58     scoped_refptr<base::SequencedTaskRunner> background_runner(
59         base::ThreadPool::CreateSequencedTaskRunner(
60             {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
61              base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
62     state_ = std::make_unique<TransportSecurityState>();
63     persister_ = std::make_unique<TransportSecurityPersister>(
64         state_.get(), std::move(background_runner),
65         transport_security_file_path_);
66   }
67 
68  protected:
69   base::FilePath transport_security_file_path_;
70   base::ScopedTempDir temp_dir_;
71   std::unique_ptr<TransportSecurityState> state_;
72   std::unique_ptr<TransportSecurityPersister> persister_;
73 };
74 
75 // Tests that LoadEntries() clears existing non-static entries.
TEST_F(TransportSecurityPersisterTest,LoadEntriesClearsExistingState)76 TEST_F(TransportSecurityPersisterTest, LoadEntriesClearsExistingState) {
77   TransportSecurityState::STSState sts_state;
78   const base::Time current_time(base::Time::Now());
79   const base::Time expiry = current_time + base::Seconds(1000);
80   static const char kYahooDomain[] = "yahoo.com";
81 
82   EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
83 
84   state_->AddHSTS(kYahooDomain, expiry, false /* include subdomains */);
85   EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
86 
87   persister_->LoadEntries("{\"version\":2}");
88 
89   EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
90 }
91 
92 // Tests that serializing -> deserializing -> reserializing results in the same
93 // output.
TEST_F(TransportSecurityPersisterTest,SerializeData1)94 TEST_F(TransportSecurityPersisterTest, SerializeData1) {
95   std::optional<std::string> output = persister_->SerializeData();
96 
97   ASSERT_TRUE(output);
98   persister_->LoadEntries(*output);
99 
100   std::optional<std::string> output2 = persister_->SerializeData();
101   ASSERT_TRUE(output2);
102   EXPECT_EQ(output, output2);
103 }
104 
TEST_F(TransportSecurityPersisterTest,SerializeData2)105 TEST_F(TransportSecurityPersisterTest, SerializeData2) {
106   TransportSecurityState::STSState sts_state;
107   const base::Time current_time(base::Time::Now());
108   const base::Time expiry = current_time + base::Seconds(1000);
109   static const char kYahooDomain[] = "yahoo.com";
110 
111   EXPECT_FALSE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
112 
113   bool include_subdomains = true;
114   state_->AddHSTS(kYahooDomain, expiry, include_subdomains);
115 
116   std::optional<std::string> output = persister_->SerializeData();
117   ASSERT_TRUE(output);
118   persister_->LoadEntries(*output);
119 
120   EXPECT_TRUE(state_->GetDynamicSTSState(kYahooDomain, &sts_state));
121   EXPECT_EQ(sts_state.upgrade_mode,
122             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
123   EXPECT_TRUE(state_->GetDynamicSTSState("foo.yahoo.com", &sts_state));
124   EXPECT_EQ(sts_state.upgrade_mode,
125             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
126   EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.yahoo.com", &sts_state));
127   EXPECT_EQ(sts_state.upgrade_mode,
128             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
129   EXPECT_TRUE(state_->GetDynamicSTSState("foo.bar.baz.yahoo.com", &sts_state));
130   EXPECT_EQ(sts_state.upgrade_mode,
131             TransportSecurityState::STSState::MODE_FORCE_HTTPS);
132 }
133 
TEST_F(TransportSecurityPersisterTest,SerializeData3)134 TEST_F(TransportSecurityPersisterTest, SerializeData3) {
135   const GURL report_uri(kReportUri);
136   // Add an entry.
137   base::Time expiry = base::Time::Now() + base::Seconds(1000);
138   bool include_subdomains = false;
139   state_->AddHSTS("www.example.com", expiry, include_subdomains);
140 
141   // Add another entry.
142   expiry = base::Time::Now() + base::Seconds(3000);
143   state_->AddHSTS("www.example.net", expiry, include_subdomains);
144 
145   // Save a copy of everything.
146   std::set<TransportSecurityState::HashedHost> sts_saved;
147   TransportSecurityState::STSStateIterator sts_iter(*state_);
148   while (sts_iter.HasNext()) {
149     sts_saved.insert(sts_iter.hostname());
150     sts_iter.Advance();
151   }
152 
153   std::optional<std::string> serialized = persister_->SerializeData();
154   ASSERT_TRUE(serialized);
155 
156   // Persist the data to the file.
157   base::RunLoop run_loop;
158   persister_->WriteNow(state_.get(), run_loop.QuitClosure());
159   run_loop.Run();
160 
161   // Read the data back.
162   std::string persisted;
163   EXPECT_TRUE(
164       base::ReadFileToString(transport_security_file_path_, &persisted));
165   EXPECT_EQ(persisted, serialized);
166   persister_->LoadEntries(persisted);
167 
168   // Check that states are the same as saved.
169   size_t count = 0;
170   TransportSecurityState::STSStateIterator sts_iter2(*state_);
171   while (sts_iter2.HasNext()) {
172     count++;
173     sts_iter2.Advance();
174   }
175   EXPECT_EQ(count, sts_saved.size());
176 }
177 
178 // Tests that deserializing bad data shouldn't result in any STS entries being
179 // added to the transport security state.
TEST_F(TransportSecurityPersisterTest,DeserializeBadData)180 TEST_F(TransportSecurityPersisterTest, DeserializeBadData) {
181   persister_->LoadEntries("");
182   EXPECT_EQ(0u, state_->num_sts_entries());
183 
184   persister_->LoadEntries("Foopy");
185   EXPECT_EQ(0u, state_->num_sts_entries());
186 
187   persister_->LoadEntries("15");
188   EXPECT_EQ(0u, state_->num_sts_entries());
189 
190   persister_->LoadEntries("[15]");
191   EXPECT_EQ(0u, state_->num_sts_entries());
192 
193   persister_->LoadEntries("{\"version\":1}");
194   EXPECT_EQ(0u, state_->num_sts_entries());
195 }
196 
TEST_F(TransportSecurityPersisterTest,DeserializeDataOldWithoutCreationDate)197 TEST_F(TransportSecurityPersisterTest, DeserializeDataOldWithoutCreationDate) {
198   // This is an old-style piece of transport state JSON, which has no creation
199   // date.
200   const std::string kInput =
201       "{ "
202       "\"G0EywIek2XnIhLrUjaK4TrHBT1+2TcixDVRXwM3/CCo=\": {"
203       "\"expiry\": 1266815027.983453, "
204       "\"include_subdomains\": false, "
205       "\"mode\": \"strict\" "
206       "}"
207       "}";
208   persister_->LoadEntries(kInput);
209   EXPECT_EQ(0u, state_->num_sts_entries());
210 }
211 
TEST_F(TransportSecurityPersisterTest,DeserializeDataOldMergedDictionary)212 TEST_F(TransportSecurityPersisterTest, DeserializeDataOldMergedDictionary) {
213   // 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 
249   persister_->LoadEntries(kInput);
250   EXPECT_EQ(0u, state_->num_sts_entries());
251 }
252 
TEST_F(TransportSecurityPersisterTest,DeserializeLegacyExpectCTData)253 TEST_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"));
275 }
276 
277 }  // namespace
278 
279 }  // namespace net
280