1 // Copyright 2021 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 <string>
6 #include <tuple>
7
8 #include "base/test/scoped_feature_list.h"
9 #include "net/base/features.h"
10 #include "net/cookies/cookie_constants.h"
11 #include "net/cookies/cookie_partition_key.h"
12 #include "net/cookies/site_for_cookies.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace net {
16
17 class CookiePartitionKeyTest : public testing::TestWithParam<bool> {
18 protected:
19 // testing::Test
SetUp()20 void SetUp() override {
21 scoped_feature_list_.InitWithFeatureState(
22 features::kAncestorChainBitEnabledInPartitionedCookies,
23 AncestorChainBitEnabled());
24 }
25
AncestorChainBitEnabled()26 bool AncestorChainBitEnabled() { return GetParam(); }
27
28 private:
29 base::test::ScopedFeatureList scoped_feature_list_;
30 };
31
32 INSTANTIATE_TEST_SUITE_P(/* no label */,
33 CookiePartitionKeyTest,
34 ::testing::Bool());
35
TEST_P(CookiePartitionKeyTest,TestFromStorage)36 TEST_P(CookiePartitionKeyTest, TestFromStorage) {
37 struct {
38 const std::string top_level_site;
39 bool third_party;
40 const std::optional<CookiePartitionKey> expected_output;
41 } cases[] = {
42 {/*empty site*/
43 "", true, CookiePartitionKey::FromURLForTesting(GURL(""))},
44 /*invalid site*/
45 {"Invalid", true, std::nullopt},
46 /*valid site: cross site*/
47 {"https://toplevelsite.com", true,
48 CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"))},
49 /*valid site: same site*/
50 {"https://toplevelsite.com", false,
51 CookiePartitionKey::FromURLForTesting(
52 GURL("https://toplevelsite.com"),
53 CookiePartitionKey::AncestorChainBit::kSameSite)}};
54 for (const auto& tc : cases) {
55 base::expected<std::optional<CookiePartitionKey>, std::string> got =
56 CookiePartitionKey::FromStorage(tc.top_level_site, tc.third_party);
57 EXPECT_EQ(got.has_value(), tc.expected_output.has_value());
58 if (!tc.top_level_site.empty() && tc.expected_output.has_value()) {
59 ASSERT_TRUE(got.has_value()) << "Expected result to have value.";
60 EXPECT_EQ(got.value()->IsThirdParty(), tc.third_party);
61 }
62 }
63 }
64
TEST_P(CookiePartitionKeyTest,TestFromUntrustedInput)65 TEST_P(CookiePartitionKeyTest, TestFromUntrustedInput) {
66 const std::string kFullURL = "https://subdomain.toplevelsite.com/index.html";
67 const std::string kValidSite = "https://toplevelsite.com";
68 struct {
69 std::string top_level_site;
70 CookiePartitionKey::AncestorChainBit has_cross_site_ancestor;
71 bool partition_key_created;
72 bool expected_third_party;
73 } cases[] = {
74 {/*empty site*/
75 "", CookiePartitionKey::AncestorChainBit::kCrossSite, false, true},
76 {/*empty site : same site ancestor*/
77 "", CookiePartitionKey::AncestorChainBit::kSameSite, false, false},
78 {/*valid site*/
79 kValidSite, CookiePartitionKey::AncestorChainBit::kCrossSite, true,
80 true},
81 {/*valid site: same site ancestor*/
82 kValidSite, CookiePartitionKey::AncestorChainBit::kSameSite, true,
83 false},
84 {/*invalid site (missing scheme)*/
85 "toplevelsite.com", CookiePartitionKey::AncestorChainBit::kCrossSite,
86 false, true},
87 {/*invalid site (missing scheme): same site ancestor*/
88 "toplevelsite.com", CookiePartitionKey::AncestorChainBit::kSameSite,
89 false, false},
90 {/*invalid site*/
91 "abc123foobar!!", CookiePartitionKey::AncestorChainBit::kCrossSite,
92 false, true},
93 {/*invalid site: same site ancestor*/
94 "abc123foobar!!", CookiePartitionKey::AncestorChainBit::kSameSite, false,
95 false},
96 };
97
98 for (const auto& tc : cases) {
99 base::expected<CookiePartitionKey, std::string> got =
100 CookiePartitionKey::FromUntrustedInput(
101 tc.top_level_site,
102 tc.has_cross_site_ancestor ==
103 CookiePartitionKey::AncestorChainBit::kCrossSite);
104 EXPECT_EQ(got.has_value(), tc.partition_key_created);
105 if (tc.partition_key_created) {
106 EXPECT_EQ(got->site().Serialize(), kValidSite);
107 EXPECT_EQ(got->IsThirdParty(), tc.expected_third_party);
108 }
109 }
110 }
111
TEST_P(CookiePartitionKeyTest,Serialization)112 TEST_P(CookiePartitionKeyTest, Serialization) {
113 base::UnguessableToken nonce = base::UnguessableToken::Create();
114 struct {
115 std::optional<CookiePartitionKey> input;
116 std::string expected_output_top_level_site;
117 bool expected_success;
118 bool expected_cross_site;
119 } cases[] = {
120 // No partition key
121 {std::nullopt, kEmptyCookiePartitionKey, true, true},
122 // Partition key present
123 {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")),
124 "https://toplevelsite.com", true, true},
125 // Local file URL
126 {CookiePartitionKey::FromURLForTesting(GURL("file:///path/to/file.txt")),
127 "file://", true, true},
128 // File URL with host
129 {CookiePartitionKey::FromURLForTesting(
130 GURL("file://toplevelsite.com/path/to/file.pdf")),
131 "file://toplevelsite.com", true, true},
132 // Opaque origin
133 {CookiePartitionKey::FromURLForTesting(GURL()), "", false, true},
134 // AncestorChain::kSameSite
135 {CookiePartitionKey::FromURLForTesting(
136 GURL("https://toplevelsite.com"),
137 CookiePartitionKey::AncestorChainBit::kSameSite, std::nullopt),
138 "https://toplevelsite.com", true, false},
139 // AncestorChain::kCrossSite
140 {CookiePartitionKey::FromURLForTesting(
141 GURL("https://toplevelsite.com"),
142 CookiePartitionKey::AncestorChainBit::kCrossSite, std::nullopt),
143 "https://toplevelsite.com", true, true},
144 // With nonce
145 {CookiePartitionKey::FromNetworkIsolationKey(
146 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
147 SchemefulSite(GURL("https://cookiesite.com")),
148 nonce),
149 SiteForCookies::FromUrl(GURL::EmptyGURL()),
150 SchemefulSite(GURL("https://toplevelsite.com"))),
151 "", false, true},
152 // Same site no nonce from NIK
153 {CookiePartitionKey::FromNetworkIsolationKey(
154 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
155 SchemefulSite(GURL("https://toplevelsite.com"))),
156 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
157 SchemefulSite(GURL("https://toplevelsite.com"))),
158 "https://toplevelsite.com", true, false},
159 // Different request_site results in cross site ancestor
160 {CookiePartitionKey::FromNetworkIsolationKey(
161 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
162 SchemefulSite(GURL("https://toplevelsite.com"))),
163 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
164 SchemefulSite(GURL("https://differentOrigin.com"))),
165 "https://toplevelsite.com", true, true},
166 // Same site with nonce from NIK
167 {CookiePartitionKey::FromNetworkIsolationKey(
168 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
169 SchemefulSite(GURL("https://toplevelsite.com")),
170 nonce),
171 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
172 SchemefulSite(GURL("https://toplevelsite.com"))),
173 "", false, true},
174 // Invalid partition key
175 {std::make_optional(
176 CookiePartitionKey::FromURLForTesting(GURL("abc123foobar!!"))),
177 "", false, true},
178 };
179
180 for (const auto& tc : cases) {
181 base::expected<CookiePartitionKey::SerializedCookiePartitionKey,
182 std::string>
183 got = CookiePartitionKey::Serialize(tc.input);
184
185 EXPECT_EQ(tc.expected_success, got.has_value());
186 if (got.has_value()) {
187 // TODO (crbug.com/41486025) once ancestor chain bit is implemented update
188 // test to check bit's value.
189 EXPECT_EQ(tc.expected_output_top_level_site, got->TopLevelSite());
190 EXPECT_EQ(tc.expected_cross_site, got->has_cross_site_ancestor());
191 }
192 }
193 }
194
TEST_P(CookiePartitionKeyTest,FromNetworkIsolationKey)195 TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey) {
196 const SchemefulSite kTopLevelSite =
197 SchemefulSite(GURL("https://toplevelsite.com"));
198 const SchemefulSite kCookieSite =
199 SchemefulSite(GURL("https://cookiesite.com"));
200 const base::UnguessableToken kNonce = base::UnguessableToken::Create();
201
202 struct TestCase {
203 const std::string desc;
204 const NetworkIsolationKey network_isolation_key;
205 const std::optional<CookiePartitionKey> expected;
206 const SiteForCookies site_for_cookies;
207 const SchemefulSite request_site;
208 } test_cases[] = {
209 {"Empty", NetworkIsolationKey(), std::nullopt,
210 SiteForCookies::FromUrl(GURL::EmptyGURL()), SchemefulSite(GURL(""))},
211 {"WithTopLevelSite", NetworkIsolationKey(kTopLevelSite, kCookieSite),
212 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL()),
213 SiteForCookies::FromUrl(GURL::EmptyGURL()),
214 SchemefulSite(kTopLevelSite)},
215 {"WithNonce", NetworkIsolationKey(kTopLevelSite, kCookieSite, kNonce),
216 CookiePartitionKey::FromURLForTesting(
217 kCookieSite.GetURL(),
218 CookiePartitionKey::AncestorChainBit::kCrossSite, kNonce),
219 SiteForCookies::FromUrl(GURL::EmptyGURL()),
220 SchemefulSite(kTopLevelSite)},
221 {"WithCrossSiteAncestorSameSite",
222 NetworkIsolationKey(kTopLevelSite, kTopLevelSite),
223 CookiePartitionKey::FromURLForTesting(
224 kTopLevelSite.GetURL(),
225 CookiePartitionKey::AncestorChainBit::kSameSite, std::nullopt),
226 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
227 SchemefulSite(kTopLevelSite)},
228 {"Nonced first party NIK results in kCrossSite partition key",
229 NetworkIsolationKey(kTopLevelSite, kTopLevelSite, kNonce),
230 CookiePartitionKey::FromURLForTesting(
231 kTopLevelSite.GetURL(),
232 CookiePartitionKey::AncestorChainBit::kCrossSite, kNonce),
233 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
234 SchemefulSite(kTopLevelSite)},
235 {"WithCrossSiteAncestorNotSameSite",
236 NetworkIsolationKey(kTopLevelSite, kTopLevelSite),
237 CookiePartitionKey::FromURLForTesting(
238 kTopLevelSite.GetURL(),
239 CookiePartitionKey::AncestorChainBit::kCrossSite, std::nullopt),
240 SiteForCookies::FromUrl(GURL::EmptyGURL()), kCookieSite}};
241
242 base::test::ScopedFeatureList feature_list;
243 feature_list.InitWithFeatureState(
244 features::kAncestorChainBitEnabledInPartitionedCookies,
245 AncestorChainBitEnabled());
246
247 for (const auto& test_case : test_cases) {
248 SCOPED_TRACE(test_case.desc);
249
250 std::optional<CookiePartitionKey> got =
251 CookiePartitionKey::FromNetworkIsolationKey(
252 test_case.network_isolation_key, test_case.site_for_cookies,
253 test_case.request_site);
254
255 EXPECT_EQ(test_case.expected, got);
256 if (got) {
257 EXPECT_EQ(test_case.network_isolation_key.GetNonce(), got->nonce());
258 }
259 }
260 }
261
TEST_P(CookiePartitionKeyTest,FromWire)262 TEST_P(CookiePartitionKeyTest, FromWire) {
263 struct TestCase {
264 const GURL url;
265 const std::optional<base::UnguessableToken> nonce;
266 const CookiePartitionKey::AncestorChainBit ancestor_chain_bit;
267 } test_cases[] = {
268 {GURL("https://foo.com"), std::nullopt,
269 CookiePartitionKey::AncestorChainBit::kCrossSite},
270 {GURL("https://foo.com"), std::nullopt,
271 CookiePartitionKey::AncestorChainBit::kSameSite},
272 {GURL(), std::nullopt, CookiePartitionKey::AncestorChainBit::kCrossSite},
273 {GURL("https://foo.com"), base::UnguessableToken::Create(),
274 CookiePartitionKey::AncestorChainBit::kCrossSite}};
275
276 for (const auto& test_case : test_cases) {
277 auto want = CookiePartitionKey::FromURLForTesting(
278 test_case.url, test_case.ancestor_chain_bit, test_case.nonce);
279 auto got = CookiePartitionKey::FromWire(
280 want.site(),
281 want.IsThirdParty() ? CookiePartitionKey::AncestorChainBit::kCrossSite
282 : CookiePartitionKey::AncestorChainBit::kSameSite,
283 want.nonce());
284 EXPECT_EQ(want, got);
285 EXPECT_FALSE(got.from_script());
286 }
287 }
288
TEST_P(CookiePartitionKeyTest,FromStorageKeyComponents)289 TEST_P(CookiePartitionKeyTest, FromStorageKeyComponents) {
290 struct TestCase {
291 const GURL url;
292 const std::optional<base::UnguessableToken> nonce = std::nullopt;
293 const CookiePartitionKey::AncestorChainBit ancestor_chain_bit;
294 } test_cases[] = {
295 {GURL("https://foo.com"), std::nullopt,
296 CookiePartitionKey::AncestorChainBit::kCrossSite},
297 {GURL("https://foo.com"), std::nullopt,
298 CookiePartitionKey::AncestorChainBit::kSameSite},
299 {GURL(), std::nullopt, CookiePartitionKey::AncestorChainBit::kCrossSite},
300 {GURL("https://foo.com"), base::UnguessableToken::Create(),
301 CookiePartitionKey::AncestorChainBit::kCrossSite}};
302
303 for (const auto& test_case : test_cases) {
304 auto want = CookiePartitionKey::FromURLForTesting(
305 test_case.url, test_case.ancestor_chain_bit, test_case.nonce);
306 std::optional<CookiePartitionKey> got =
307 CookiePartitionKey::FromStorageKeyComponents(
308 want.site(),
309 want.IsThirdParty()
310 ? CookiePartitionKey::AncestorChainBit::kCrossSite
311 : CookiePartitionKey::AncestorChainBit::kSameSite,
312 want.nonce());
313 EXPECT_EQ(got, want);
314 }
315 }
316
TEST_P(CookiePartitionKeyTest,FromScript)317 TEST_P(CookiePartitionKeyTest, FromScript) {
318 auto key = CookiePartitionKey::FromScript();
319 EXPECT_TRUE(key);
320 EXPECT_TRUE(key->from_script());
321 EXPECT_TRUE(key->site().opaque());
322 EXPECT_TRUE(key->IsThirdParty());
323
324 auto key2 = CookiePartitionKey::FromScript();
325 EXPECT_TRUE(key2);
326 EXPECT_TRUE(key2->from_script());
327 EXPECT_TRUE(key2->site().opaque());
328 EXPECT_TRUE(key2->IsThirdParty());
329
330 // The keys should not be equal because they get created with different opaque
331 // sites. Test both the '==' and '!=' operators here.
332 EXPECT_FALSE(key == key2);
333 EXPECT_TRUE(key != key2);
334 }
335
TEST_P(CookiePartitionKeyTest,IsSerializeable)336 TEST_P(CookiePartitionKeyTest, IsSerializeable) {
337 EXPECT_FALSE(CookiePartitionKey::FromURLForTesting(GURL()).IsSerializeable());
338 EXPECT_TRUE(
339 CookiePartitionKey::FromURLForTesting(GURL("https://www.example.com"))
340 .IsSerializeable());
341 }
342
TEST_P(CookiePartitionKeyTest,Equality)343 TEST_P(CookiePartitionKeyTest, Equality) {
344 // Same eTLD+1 but different scheme are not equal.
345 EXPECT_NE(CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")),
346 CookiePartitionKey::FromURLForTesting(GURL("http://foo.com")));
347
348 // Different subdomains of the same site are equal.
349 EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://a.foo.com")),
350 CookiePartitionKey::FromURLForTesting(GURL("https://b.foo.com")));
351 }
352
TEST_P(CookiePartitionKeyTest,Equality_WithAncestorChain)353 TEST_P(CookiePartitionKeyTest, Equality_WithAncestorChain) {
354 CookiePartitionKey key1 = CookiePartitionKey::FromURLForTesting(
355 GURL("https://foo.com"), CookiePartitionKey::AncestorChainBit::kSameSite,
356 std::nullopt);
357 CookiePartitionKey key2 = CookiePartitionKey::FromURLForTesting(
358 GURL("https://foo.com"), CookiePartitionKey::AncestorChainBit::kCrossSite,
359 std::nullopt);
360
361 EXPECT_EQ((key1 == key2), !AncestorChainBitEnabled());
362 EXPECT_EQ(key1,
363 CookiePartitionKey::FromURLForTesting(
364 GURL("https://foo.com"),
365 CookiePartitionKey::AncestorChainBit::kSameSite, std::nullopt));
366 }
367
TEST_P(CookiePartitionKeyTest,Equality_WithNonce)368 TEST_P(CookiePartitionKeyTest, Equality_WithNonce) {
369 SchemefulSite top_level_site =
370 SchemefulSite(GURL("https://toplevelsite.com"));
371 SchemefulSite frame_site = SchemefulSite(GURL("https://cookiesite.com"));
372 base::UnguessableToken nonce1 = base::UnguessableToken::Create();
373 base::UnguessableToken nonce2 = base::UnguessableToken::Create();
374 EXPECT_NE(nonce1, nonce2);
375 auto key1 = CookiePartitionKey::FromNetworkIsolationKey(
376 NetworkIsolationKey(top_level_site, frame_site, nonce1), SiteForCookies(),
377 top_level_site);
378 EXPECT_TRUE(key1.has_value());
379
380 auto key2 = CookiePartitionKey::FromNetworkIsolationKey(
381 NetworkIsolationKey(top_level_site, frame_site, nonce2), SiteForCookies(),
382 top_level_site);
383 EXPECT_TRUE(key1.has_value() && key2.has_value());
384 EXPECT_NE(key1, key2);
385
386 auto key3 = CookiePartitionKey::FromNetworkIsolationKey(
387 NetworkIsolationKey(top_level_site, frame_site, nonce1), SiteForCookies(),
388 top_level_site);
389 EXPECT_EQ(key1, key3);
390
391 auto unnonced_key = CookiePartitionKey::FromNetworkIsolationKey(
392 NetworkIsolationKey(top_level_site, frame_site), SiteForCookies(),
393 frame_site);
394 EXPECT_NE(key1, unnonced_key);
395 }
396
TEST_P(CookiePartitionKeyTest,Localhost)397 TEST_P(CookiePartitionKeyTest, Localhost) {
398 SchemefulSite top_level_site(GURL("https://localhost:8000"));
399
400 auto key = CookiePartitionKey::FromNetworkIsolationKey(
401 NetworkIsolationKey(top_level_site, top_level_site), SiteForCookies(),
402 top_level_site);
403 EXPECT_TRUE(key.has_value());
404
405 SchemefulSite frame_site(GURL("https://cookiesite.com"));
406 key = CookiePartitionKey::FromNetworkIsolationKey(
407 NetworkIsolationKey(top_level_site, frame_site), SiteForCookies(),
408 top_level_site);
409 EXPECT_TRUE(key.has_value());
410 }
411
412 // Test that creating nonced partition keys works with both types of
413 // NetworkIsolationKey modes. See https://crbug.com/1442260.
TEST_P(CookiePartitionKeyTest,NetworkIsolationKeyMode)414 TEST_P(CookiePartitionKeyTest, NetworkIsolationKeyMode) {
415 const net::SchemefulSite kTopFrameSite(GURL("https://a.com"));
416 const net::SchemefulSite kFrameSite(GURL("https://b.com"));
417 const auto kNonce = base::UnguessableToken::Create();
418
419 SiteForCookies site_for_cookies =
420 SiteForCookies::FromUrl(GURL("https://a.com"));
421
422 { // Frame site mode.
423 base::test::ScopedFeatureList feature_list;
424
425 feature_list.InitWithFeatureState(
426 features::kEnableCrossSiteFlagNetworkIsolationKey, false);
427
428 const auto key = CookiePartitionKey::FromNetworkIsolationKey(
429 NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce),
430 site_for_cookies, kTopFrameSite);
431 EXPECT_TRUE(key);
432 EXPECT_EQ(key->site(), kFrameSite);
433 EXPECT_EQ(key->nonce().value(), kNonce);
434 EXPECT_TRUE(key->IsThirdParty());
435 }
436
437 { // Cross-site flag mode.
438 base::test::ScopedFeatureList feature_list;
439 feature_list.InitWithFeatureStates(
440 {{net::features::kEnableCrossSiteFlagNetworkIsolationKey, true},
441 {features::kAncestorChainBitEnabledInPartitionedCookies,
442 AncestorChainBitEnabled()}});
443
444 const auto key = CookiePartitionKey::FromNetworkIsolationKey(
445 NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce),
446 site_for_cookies, kTopFrameSite);
447 EXPECT_TRUE(key);
448 EXPECT_EQ(key->site(), kFrameSite);
449 EXPECT_EQ(key->nonce().value(), kNonce);
450 EXPECT_TRUE(key->IsThirdParty());
451 }
452 }
453 } // namespace net
454