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