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 "net/cookies/cookie_partition_key.h"
6
7 #include <ostream>
8 #include <tuple>
9
10 #include "base/feature_list.h"
11 #include "base/logging.h"
12 #include "base/types/optional_util.h"
13 #include "net/base/cronet_buildflags.h"
14 #include "net/base/features.h"
15 #include "net/cookies/cookie_constants.h"
16 #include "net/cookies/cookie_util.h"
17 #include "net/cookies/site_for_cookies.h"
18
19 #if !BUILDFLAG(CRONET_BUILD)
20 #include "mojo/public/cpp/bindings/default_construct_tag.h"
21 #endif
22
23 namespace net {
24
25 namespace {
26
WarnAndCreateUnexpected(const std::string & message)27 base::unexpected<std::string> WarnAndCreateUnexpected(
28 const std::string& message) {
29 DLOG(WARNING) << message;
30 return base::unexpected(message);
31 }
32
SerializeSchemefulSite(const SchemefulSite & site)33 std::string SerializeSchemefulSite(const SchemefulSite& site) {
34 return site.GetURL().SchemeIsFile() ? site.SerializeFileSiteWithHost()
35 : site.Serialize();
36 }
37
38 } // namespace
39
SerializedCookiePartitionKey(base::PassKey<CookiePartitionKey> key,const std::string & site,bool has_cross_site_ancestor)40 CookiePartitionKey::SerializedCookiePartitionKey::SerializedCookiePartitionKey(
41 base::PassKey<CookiePartitionKey> key,
42 const std::string& site,
43 bool has_cross_site_ancestor)
44 : top_level_site_(site),
45 has_cross_site_ancestor_(has_cross_site_ancestor) {}
46
47 const std::string&
TopLevelSite() const48 CookiePartitionKey::SerializedCookiePartitionKey::TopLevelSite() const {
49 return top_level_site_;
50 }
51
GetDebugString() const52 std::string CookiePartitionKey::SerializedCookiePartitionKey::GetDebugString()
53 const {
54 std::string out = TopLevelSite();
55 if (base::FeatureList::IsEnabled(
56 features::kAncestorChainBitEnabledInPartitionedCookies)) {
57 base::StrAppend(
58 &out, {", ", has_cross_site_ancestor() ? "cross-site" : "same-site"});
59 }
60 return out;
61 }
62
63 #if !BUILDFLAG(CRONET_BUILD)
CookiePartitionKey(mojo::DefaultConstruct::Tag)64 CookiePartitionKey::CookiePartitionKey(mojo::DefaultConstruct::Tag) {}
65 #endif
has_cross_site_ancestor() const66 bool CookiePartitionKey::SerializedCookiePartitionKey::has_cross_site_ancestor()
67 const {
68 return has_cross_site_ancestor_;
69 }
70
71 // static
BoolToAncestorChainBit(bool cross_site)72 CookiePartitionKey::AncestorChainBit CookiePartitionKey::BoolToAncestorChainBit(
73 bool cross_site) {
74 return cross_site ? AncestorChainBit::kCrossSite
75 : AncestorChainBit::kSameSite;
76 }
77
CookiePartitionKey(const SchemefulSite & site,std::optional<base::UnguessableToken> nonce,AncestorChainBit ancestor_chain_bit)78 CookiePartitionKey::CookiePartitionKey(
79 const SchemefulSite& site,
80 std::optional<base::UnguessableToken> nonce,
81 AncestorChainBit ancestor_chain_bit)
82 : site_(site), nonce_(nonce), ancestor_chain_bit_(ancestor_chain_bit) {
83 }
84
CookiePartitionKey(bool from_script)85 CookiePartitionKey::CookiePartitionKey(bool from_script)
86 : from_script_(from_script) {}
87
88 CookiePartitionKey::CookiePartitionKey(const CookiePartitionKey& other) =
89 default;
90
91 CookiePartitionKey::CookiePartitionKey(CookiePartitionKey&& other) = default;
92
93 CookiePartitionKey& CookiePartitionKey::operator=(
94 const CookiePartitionKey& other) = default;
95
96 CookiePartitionKey& CookiePartitionKey::operator=(CookiePartitionKey&& other) =
97 default;
98
99 CookiePartitionKey::~CookiePartitionKey() = default;
100
operator ==(const CookiePartitionKey & other) const101 bool CookiePartitionKey::operator==(const CookiePartitionKey& other) const {
102 AncestorChainBit this_bit = MaybeAncestorChainBit();
103 AncestorChainBit other_bit = other.MaybeAncestorChainBit();
104
105 return std::tie(site_, nonce_, this_bit) ==
106 std::tie(other.site_, other.nonce_, other_bit);
107 }
108
operator !=(const CookiePartitionKey & other) const109 bool CookiePartitionKey::operator!=(const CookiePartitionKey& other) const {
110 return !(*this == other);
111 }
112
operator <(const CookiePartitionKey & other) const113 bool CookiePartitionKey::operator<(const CookiePartitionKey& other) const {
114 AncestorChainBit this_bit = MaybeAncestorChainBit();
115 AncestorChainBit other_bit = other.MaybeAncestorChainBit();
116 return std::tie(site_, nonce_, this_bit) <
117 std::tie(other.site_, other.nonce_, other_bit);
118 }
119
120 // static
121 base::expected<CookiePartitionKey::SerializedCookiePartitionKey, std::string>
Serialize(const std::optional<CookiePartitionKey> & in)122 CookiePartitionKey::Serialize(const std::optional<CookiePartitionKey>& in) {
123 if (!in) {
124 return base::ok(SerializedCookiePartitionKey(
125 base::PassKey<CookiePartitionKey>(), kEmptyCookiePartitionKey, true));
126 }
127
128 if (!in->IsSerializeable()) {
129 return WarnAndCreateUnexpected("CookiePartitionKey is not serializeable");
130 }
131
132 return base::ok(SerializedCookiePartitionKey(
133 base::PassKey<CookiePartitionKey>(), SerializeSchemefulSite(in->site_),
134 in->IsThirdParty()));
135 }
136
FromNetworkIsolationKey(const NetworkIsolationKey & network_isolation_key,const SiteForCookies & site_for_cookies,const SchemefulSite & request_site,bool main_frame_navigation)137 std::optional<CookiePartitionKey> CookiePartitionKey::FromNetworkIsolationKey(
138 const NetworkIsolationKey& network_isolation_key,
139 const SiteForCookies& site_for_cookies,
140 const SchemefulSite& request_site,
141 bool main_frame_navigation) {
142 if (cookie_util::PartitionedCookiesDisabledByCommandLine()) {
143 return std::nullopt;
144 }
145
146 const std::optional<base::UnguessableToken>& nonce =
147 network_isolation_key.GetNonce();
148
149 // Use frame site for nonced partitions. Since the nonce is unique, this
150 // still creates a unique partition key. The reason we use the frame site is
151 // to align CookiePartitionKey's implementation of nonced partitions with
152 // StorageKey's. See https://crbug.com/1440765.
153 const std::optional<SchemefulSite>& partition_key_site =
154 nonce ? network_isolation_key.GetFrameSiteForCookiePartitionKey(
155 NetworkIsolationKey::CookiePartitionKeyPassKey())
156 : network_isolation_key.GetTopFrameSite();
157 if (!partition_key_site) {
158 return std::nullopt;
159 }
160
161 // When a main_frame_navigation occurs, the ancestor chain bit value should
162 // always be kSameSite, unless there is a nonce, since a main frame has no
163 // ancestor, context: crbug.com/(337206302).
164 AncestorChainBit ancestor_chain_bit;
165 if (nonce) {
166 ancestor_chain_bit = AncestorChainBit::kCrossSite;
167 } else if (main_frame_navigation) {
168 ancestor_chain_bit = AncestorChainBit::kSameSite;
169 } else if (site_for_cookies.IsNull()) {
170 ancestor_chain_bit = AncestorChainBit::kCrossSite;
171 } else {
172 ancestor_chain_bit = BoolToAncestorChainBit(
173 !site_for_cookies.IsFirstParty(request_site.GetURL()));
174 }
175
176 return CookiePartitionKey(*partition_key_site, nonce, ancestor_chain_bit);
177 }
178
179 // static
FromStorageKeyComponents(const SchemefulSite & site,AncestorChainBit ancestor_chain_bit,const std::optional<base::UnguessableToken> & nonce)180 std::optional<CookiePartitionKey> CookiePartitionKey::FromStorageKeyComponents(
181 const SchemefulSite& site,
182 AncestorChainBit ancestor_chain_bit,
183 const std::optional<base::UnguessableToken>& nonce) {
184 if (cookie_util::PartitionedCookiesDisabledByCommandLine()) {
185 return std::nullopt;
186 }
187 return CookiePartitionKey::FromWire(site, ancestor_chain_bit, nonce);
188 }
189
190 // static
191 base::expected<std::optional<CookiePartitionKey>, std::string>
FromStorage(const std::string & top_level_site,bool has_cross_site_ancestor)192 CookiePartitionKey::FromStorage(const std::string& top_level_site,
193 bool has_cross_site_ancestor) {
194 if (top_level_site == kEmptyCookiePartitionKey) {
195 return base::ok(std::nullopt);
196 }
197
198 base::expected<CookiePartitionKey, std::string> key = DeserializeInternal(
199 top_level_site, BoolToAncestorChainBit(has_cross_site_ancestor),
200 ParsingMode::kStrict);
201 if (!key.has_value()) {
202 DLOG(WARNING) << key.error();
203 }
204
205 return key;
206 }
207
208 // static
209 base::expected<CookiePartitionKey, std::string>
FromUntrustedInput(const std::string & top_level_site,bool has_cross_site_ancestor)210 CookiePartitionKey::FromUntrustedInput(const std::string& top_level_site,
211 bool has_cross_site_ancestor) {
212 if (top_level_site.empty()) {
213 return WarnAndCreateUnexpected("top_level_site is unexpectedly empty");
214 }
215
216 base::expected<CookiePartitionKey, std::string> key = DeserializeInternal(
217 top_level_site, BoolToAncestorChainBit(has_cross_site_ancestor),
218 ParsingMode::kLoose);
219 if (!key.has_value()) {
220 return WarnAndCreateUnexpected(key.error());
221 }
222 return key;
223 }
224
225 base::expected<CookiePartitionKey, std::string>
DeserializeInternal(const std::string & top_level_site,CookiePartitionKey::AncestorChainBit has_cross_site_ancestor,CookiePartitionKey::ParsingMode parsing_mode)226 CookiePartitionKey::DeserializeInternal(
227 const std::string& top_level_site,
228 CookiePartitionKey::AncestorChainBit has_cross_site_ancestor,
229 CookiePartitionKey::ParsingMode parsing_mode) {
230 if (cookie_util::PartitionedCookiesDisabledByCommandLine()) {
231 return WarnAndCreateUnexpected("Partitioned cookies are disabled");
232 }
233
234 auto schemeful_site = SchemefulSite::Deserialize(top_level_site);
235 if (schemeful_site.opaque()) {
236 return WarnAndCreateUnexpected(
237 "Cannot deserialize opaque origin to CookiePartitionKey");
238 } else if (parsing_mode == ParsingMode::kStrict &&
239 SerializeSchemefulSite(schemeful_site) != top_level_site) {
240 return WarnAndCreateUnexpected(
241 "Cannot deserialize malformed top_level_site to CookiePartitionKey");
242 }
243 return base::ok(CookiePartitionKey(schemeful_site, std::nullopt,
244 has_cross_site_ancestor));
245 }
246
IsSerializeable() const247 bool CookiePartitionKey::IsSerializeable() const {
248 // We should not try to serialize a partition key created by a renderer.
249 DCHECK(!from_script_);
250 return !site_.opaque() && !nonce_.has_value();
251 }
252
MaybeAncestorChainBit() const253 CookiePartitionKey::AncestorChainBit CookiePartitionKey::MaybeAncestorChainBit()
254 const {
255 return ancestor_chain_enabled_ ? ancestor_chain_bit_
256 : AncestorChainBit::kCrossSite;
257 }
258
operator <<(std::ostream & os,const CookiePartitionKey & cpk)259 std::ostream& operator<<(std::ostream& os, const CookiePartitionKey& cpk) {
260 os << cpk.site();
261 if (cpk.nonce().has_value()) {
262 os << ",nonced";
263 }
264 os << (cpk.IsThirdParty() ? ",cross_site" : ",same_site");
265 return os;
266 }
267
268 } // namespace net
269