xref: /aosp_15_r20/external/cronet/net/base/isolation_info.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 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/base/isolation_info.h"
6 
7 #include <cstddef>
8 #include <optional>
9 
10 #include "base/check_op.h"
11 #include "base/unguessable_token.h"
12 #include "net/base/features.h"
13 #include "net/base/isolation_info.h"
14 #include "net/base/isolation_info.pb.h"
15 #include "net/base/network_anonymization_key.h"
16 #include "net/base/proxy_server.h"
17 
18 namespace net {
19 
20 namespace {
21 
22 // Checks that |origin| is consistent with |site_for_cookies|.
ValidateSameSite(const url::Origin & origin,const SiteForCookies & site_for_cookies)23 bool ValidateSameSite(const url::Origin& origin,
24                       const SiteForCookies& site_for_cookies) {
25   // If not sending SameSite cookies, or sending them for a non-scheme, consider
26   // all origins consistent. Note that SiteForCookies should never be created
27   // for websocket schemes for valid navigations, since frames can't be
28   // navigated to those schemes.
29   if (site_for_cookies.IsNull() ||
30       (site_for_cookies.scheme() != url::kHttpScheme &&
31        site_for_cookies.scheme() != url::kHttpsScheme)) {
32     return true;
33   }
34 
35   // Shouldn't send cookies for opaque origins.
36   if (origin.opaque())
37     return false;
38 
39   // TODO(https://crbug.com/1060631): GetURL() is expensive. Maybe make a
40   // version of IsFirstParty that works on origins?
41   return site_for_cookies.IsFirstParty(origin.GetURL());
42 }
43 
44 // Checks if these values are consistent. See IsolationInfo::Create() for
45 // descriptions of consistent sets of values. Also allows values used by the
46 // 0-argument constructor.
IsConsistent(IsolationInfo::RequestType request_type,const std::optional<url::Origin> & top_frame_origin,const std::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,const std::optional<base::UnguessableToken> & nonce)47 bool IsConsistent(IsolationInfo::RequestType request_type,
48                   const std::optional<url::Origin>& top_frame_origin,
49                   const std::optional<url::Origin>& frame_origin,
50                   const SiteForCookies& site_for_cookies,
51                   const std::optional<base::UnguessableToken>& nonce) {
52   // Check for the default-constructed case.
53   if (!top_frame_origin) {
54     return request_type == IsolationInfo::RequestType::kOther &&
55            !frame_origin && !nonce && site_for_cookies.IsNull();
56   }
57 
58   // As long as there is a |top_frame_origin|, |site_for_cookies| must be
59   // consistent with the |top_frame_origin|.
60   if (!ValidateSameSite(*top_frame_origin, site_for_cookies))
61     return false;
62 
63   // Validate frame `frame_origin`
64   // IsolationInfo must have a `frame_origin` when frame origins are enabled
65   // and the IsolationInfo is not default-constructed.
66   if (!frame_origin) {
67     return false;
68   }
69   switch (request_type) {
70     case IsolationInfo::RequestType::kMainFrame:
71       // TODO(https://crbug.com/1056706): Check that |top_frame_origin| and
72       // |frame_origin| are the same, once the ViewSource code creates a
73       // consistent IsolationInfo object.
74       //
75       // TODO(https://crbug.com/1060631): Once CreatePartial() is removed,
76       // check if SiteForCookies is non-null if the scheme is HTTP or HTTPS.
77       break;
78     case IsolationInfo::RequestType::kSubFrame:
79       // For subframe navigations, the subframe's origin may not be consistent
80       // with the SiteForCookies, so SameSite cookies may be sent if there's a
81       // redirect to main frames site.
82       break;
83     case IsolationInfo::RequestType::kOther:
84       // SiteForCookies must consistent with the frame origin as well for
85       // subresources.
86       return ValidateSameSite(*frame_origin, site_for_cookies);
87   }
88   return true;
89 }
90 
91 }  // namespace
92 
IsolationInfo()93 IsolationInfo::IsolationInfo()
94     : IsolationInfo(RequestType::kOther,
95                     /*top_frame_origin=*/std::nullopt,
96                     /*frame_origin=*/std::nullopt,
97                     SiteForCookies(),
98                     /*nonce=*/std::nullopt) {}
99 
100 IsolationInfo::IsolationInfo(const IsolationInfo&) = default;
101 IsolationInfo::IsolationInfo(IsolationInfo&&) = default;
102 IsolationInfo::~IsolationInfo() = default;
103 IsolationInfo& IsolationInfo::operator=(const IsolationInfo&) = default;
104 IsolationInfo& IsolationInfo::operator=(IsolationInfo&&) = default;
105 
CreateForInternalRequest(const url::Origin & top_frame_origin)106 IsolationInfo IsolationInfo::CreateForInternalRequest(
107     const url::Origin& top_frame_origin) {
108   return IsolationInfo(RequestType::kOther, top_frame_origin, top_frame_origin,
109                        SiteForCookies::FromOrigin(top_frame_origin),
110                        /*nonce=*/std::nullopt);
111 }
112 
CreateTransient()113 IsolationInfo IsolationInfo::CreateTransient() {
114   url::Origin opaque_origin;
115   return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin,
116                        SiteForCookies(), /*nonce=*/std::nullopt);
117 }
118 
CreateTransientWithNonce(const base::UnguessableToken & nonce)119 IsolationInfo IsolationInfo::CreateTransientWithNonce(
120     const base::UnguessableToken& nonce) {
121   url::Origin opaque_origin;
122   return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin,
123                        SiteForCookies(), nonce);
124 }
125 
Deserialize(const std::string & serialized)126 std::optional<IsolationInfo> IsolationInfo::Deserialize(
127     const std::string& serialized) {
128   proto::IsolationInfo proto;
129   if (!proto.ParseFromString(serialized))
130     return std::nullopt;
131 
132   std::optional<url::Origin> top_frame_origin;
133   if (proto.has_top_frame_origin())
134     top_frame_origin = url::Origin::Create(GURL(proto.top_frame_origin()));
135 
136   std::optional<url::Origin> frame_origin;
137   if (proto.has_frame_origin())
138     frame_origin = url::Origin::Create(GURL(proto.frame_origin()));
139 
140   return IsolationInfo::CreateIfConsistent(
141       static_cast<RequestType>(proto.request_type()),
142       std::move(top_frame_origin), std::move(frame_origin),
143       SiteForCookies::FromUrl(GURL(proto.site_for_cookies())),
144       /*nonce=*/std::nullopt);
145 }
146 
Create(RequestType request_type,const url::Origin & top_frame_origin,const url::Origin & frame_origin,const SiteForCookies & site_for_cookies,const std::optional<base::UnguessableToken> & nonce)147 IsolationInfo IsolationInfo::Create(
148     RequestType request_type,
149     const url::Origin& top_frame_origin,
150     const url::Origin& frame_origin,
151     const SiteForCookies& site_for_cookies,
152     const std::optional<base::UnguessableToken>& nonce) {
153   return IsolationInfo(request_type, top_frame_origin, frame_origin,
154                        site_for_cookies, nonce);
155 }
156 
DoNotUseCreatePartialFromNak(const net::NetworkAnonymizationKey & network_anonymization_key)157 IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak(
158     const net::NetworkAnonymizationKey& network_anonymization_key) {
159   if (!network_anonymization_key.IsFullyPopulated()) {
160     return IsolationInfo();
161   }
162 
163   url::Origin top_frame_origin =
164       network_anonymization_key.GetTopFrameSite()->site_as_origin_;
165 
166   std::optional<url::Origin> frame_origin;
167   if (network_anonymization_key.IsCrossSite()) {
168     // If we know that the origin is cross site to the top level site, create an
169     // empty origin to use as the frame origin for the isolation info. This
170     // should be cross site with the top level origin.
171     frame_origin = url::Origin();
172   } else {
173     // If we don't know that it's cross site to the top level site, use the top
174     // frame site to set the frame origin.
175     frame_origin = top_frame_origin;
176   }
177 
178   const std::optional<base::UnguessableToken>& nonce =
179       network_anonymization_key.GetNonce();
180 
181   auto isolation_info = IsolationInfo::Create(
182       IsolationInfo::RequestType::kOther, top_frame_origin,
183       frame_origin.value(), SiteForCookies(), nonce);
184   // TODO(crbug/1343856): DCHECK isolation info is fully populated.
185   return isolation_info;
186 }
187 
CreateIfConsistent(RequestType request_type,const std::optional<url::Origin> & top_frame_origin,const std::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,const std::optional<base::UnguessableToken> & nonce)188 std::optional<IsolationInfo> IsolationInfo::CreateIfConsistent(
189     RequestType request_type,
190     const std::optional<url::Origin>& top_frame_origin,
191     const std::optional<url::Origin>& frame_origin,
192     const SiteForCookies& site_for_cookies,
193     const std::optional<base::UnguessableToken>& nonce) {
194   if (!IsConsistent(request_type, top_frame_origin, frame_origin,
195                     site_for_cookies, nonce)) {
196     return std::nullopt;
197   }
198   return IsolationInfo(request_type, top_frame_origin, frame_origin,
199                        site_for_cookies, nonce);
200 }
201 
CreateForRedirect(const url::Origin & new_origin) const202 IsolationInfo IsolationInfo::CreateForRedirect(
203     const url::Origin& new_origin) const {
204   if (request_type_ == RequestType::kOther)
205     return *this;
206 
207   if (request_type_ == RequestType::kSubFrame) {
208     return IsolationInfo(request_type_, top_frame_origin_, new_origin,
209                          site_for_cookies_, nonce_);
210   }
211 
212   DCHECK_EQ(RequestType::kMainFrame, request_type_);
213   return IsolationInfo(request_type_, new_origin, new_origin,
214                        SiteForCookies::FromOrigin(new_origin), nonce_);
215 }
216 
frame_origin() const217 const std::optional<url::Origin>& IsolationInfo::frame_origin() const {
218   return frame_origin_;
219 }
220 
frame_origin_for_testing() const221 const std::optional<url::Origin>& IsolationInfo::frame_origin_for_testing()
222     const {
223   return frame_origin_;
224 }
225 
IsEqualForTesting(const IsolationInfo & other) const226 bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const {
227   return (request_type_ == other.request_type_ &&
228           top_frame_origin_ == other.top_frame_origin_ &&
229           frame_origin_ == other.frame_origin_ &&
230           network_isolation_key_ == other.network_isolation_key_ &&
231           network_anonymization_key_ == other.network_anonymization_key_ &&
232           nonce_ == other.nonce_ &&
233           site_for_cookies_.IsEquivalent(other.site_for_cookies_));
234 }
235 
Serialize() const236 std::string IsolationInfo::Serialize() const {
237   if (network_isolation_key().IsTransient())
238     return "";
239 
240   proto::IsolationInfo info;
241 
242   info.set_request_type(static_cast<int32_t>(request_type_));
243 
244   if (top_frame_origin_)
245     info.set_top_frame_origin(top_frame_origin_->Serialize());
246 
247   if (frame_origin_)
248     info.set_frame_origin(frame_origin_->Serialize());
249 
250   info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec());
251 
252   return info.SerializeAsString();
253 }
254 
DebugString() const255 std::string IsolationInfo::DebugString() const {
256   std::string s;
257   s += "request_type: ";
258   switch (request_type_) {
259     case IsolationInfo::RequestType::kMainFrame:
260       s += "kMainFrame";
261       break;
262     case IsolationInfo::RequestType::kSubFrame:
263       s += "kSubFrame";
264       break;
265     case IsolationInfo::RequestType::kOther:
266       s += "kOther";
267       break;
268   }
269 
270   s += "; top_frame_origin: ";
271   if (top_frame_origin_) {
272     s += top_frame_origin_.value().GetDebugString(true);
273   } else {
274     s += "(none)";
275   }
276 
277   s += "; frame_origin: ";
278   if (frame_origin_) {
279     s += frame_origin_.value().GetDebugString(true);
280   } else {
281     s += "(none)";
282   }
283 
284   s += "; network_anonymization_key: ";
285   s += network_anonymization_key_.ToDebugString();
286 
287   s += "; network_isolation_key: ";
288   s += network_isolation_key_.ToDebugString();
289 
290   s += "; nonce: ";
291   if (nonce_) {
292     s += nonce_.value().ToString();
293   } else {
294     s += "(none)";
295   }
296 
297   s += "; site_for_cookies: ";
298   s += site_for_cookies_.ToDebugString();
299 
300   return s;
301 }
302 
303 NetworkAnonymizationKey
CreateNetworkAnonymizationKeyForIsolationInfo(const std::optional<url::Origin> & top_frame_origin,const std::optional<url::Origin> & frame_origin,const std::optional<base::UnguessableToken> & nonce) const304 IsolationInfo::CreateNetworkAnonymizationKeyForIsolationInfo(
305     const std::optional<url::Origin>& top_frame_origin,
306     const std::optional<url::Origin>& frame_origin,
307     const std::optional<base::UnguessableToken>& nonce) const {
308   if (!top_frame_origin) {
309     return NetworkAnonymizationKey();
310   }
311   SchemefulSite top_frame_site(*top_frame_origin);
312   SchemefulSite frame_site(*frame_origin);
313 
314   return NetworkAnonymizationKey::CreateFromFrameSite(top_frame_site,
315                                                       frame_site, nonce);
316 }
317 
IsolationInfo(RequestType request_type,const std::optional<url::Origin> & top_frame_origin,const std::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,const std::optional<base::UnguessableToken> & nonce)318 IsolationInfo::IsolationInfo(RequestType request_type,
319                              const std::optional<url::Origin>& top_frame_origin,
320                              const std::optional<url::Origin>& frame_origin,
321                              const SiteForCookies& site_for_cookies,
322                              const std::optional<base::UnguessableToken>& nonce)
323     : request_type_(request_type),
324       top_frame_origin_(top_frame_origin),
325       frame_origin_(frame_origin),
326       network_isolation_key_(
327           !top_frame_origin
328               ? NetworkIsolationKey()
329               : NetworkIsolationKey(SchemefulSite(*top_frame_origin),
330                                     SchemefulSite(*frame_origin),
331                                     nonce)),
332       network_anonymization_key_(
333           CreateNetworkAnonymizationKeyForIsolationInfo(top_frame_origin,
334                                                         frame_origin,
335                                                         nonce)),
336       site_for_cookies_(site_for_cookies),
337       nonce_(nonce) {
338   DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_,
339                       site_for_cookies_, nonce));
340 }
341 
342 }  // namespace net
343