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
9 #include "base/check_op.h"
10 #include "base/unguessable_token.h"
11 #include "net/base/features.h"
12 #include "net/base/isolation_info.h"
13 #include "net/base/isolation_info.pb.h"
14 #include "net/base/network_anonymization_key.h"
15 #include "net/base/proxy_server.h"
16 #include "third_party/abseil-cpp/absl/types/optional.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 absl::optional<url::Origin> & top_frame_origin,const absl::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,absl::optional<std::set<SchemefulSite>> party_context,const absl::optional<base::UnguessableToken> & nonce)47 bool IsConsistent(IsolationInfo::RequestType request_type,
48 const absl::optional<url::Origin>& top_frame_origin,
49 const absl::optional<url::Origin>& frame_origin,
50 const SiteForCookies& site_for_cookies,
51 absl::optional<std::set<SchemefulSite>> party_context,
52 const absl::optional<base::UnguessableToken>& nonce) {
53 // Check for the default-constructed case.
54 if (!top_frame_origin) {
55 return request_type == IsolationInfo::RequestType::kOther &&
56 !frame_origin && !nonce && site_for_cookies.IsNull() &&
57 !party_context;
58 }
59
60 // As long as there is a |top_frame_origin|, |site_for_cookies| must be
61 // consistent with the |top_frame_origin|.
62 if (!ValidateSameSite(*top_frame_origin, site_for_cookies))
63 return false;
64
65 // Validate frame `frame_origin`
66 // IsolationInfo must have a `frame_origin` when frame origins are enabled
67 // and the IsolationInfo is not default-constructed.
68 if (!frame_origin) {
69 return false;
70 }
71 switch (request_type) {
72 case IsolationInfo::RequestType::kMainFrame:
73 // TODO(https://crbug.com/1056706): Check that |top_frame_origin| and
74 // |frame_origin| are the same, once the ViewSource code creates a
75 // consistent IsolationInfo object.
76 //
77 // TODO(https://crbug.com/1060631): Once CreatePartial() is removed,
78 // check if SiteForCookies is non-null if the scheme is HTTP or HTTPS.
79 //
80 // TODO(https://crbug.com/1151947): Once CreatePartial() is removed,
81 // check if party_context is non-null and empty.
82 break;
83 case IsolationInfo::RequestType::kSubFrame:
84 // For subframe navigations, the subframe's origin may not be consistent
85 // with the SiteForCookies, so SameSite cookies may be sent if there's a
86 // redirect to main frames site.
87 break;
88 case IsolationInfo::RequestType::kOther:
89 // SiteForCookies must consistent with the frame origin as well for
90 // subresources.
91 return ValidateSameSite(*frame_origin, site_for_cookies);
92 }
93 return true;
94 }
95
96 } // namespace
97
IsolationInfo()98 IsolationInfo::IsolationInfo()
99 : IsolationInfo(RequestType::kOther,
100 /*top_frame_origin=*/absl::nullopt,
101 /*frame_origin=*/absl::nullopt,
102 SiteForCookies(),
103 /*nonce=*/absl::nullopt,
104 /*party_context=*/absl::nullopt) {}
105
106 IsolationInfo::IsolationInfo(const IsolationInfo&) = default;
107 IsolationInfo::IsolationInfo(IsolationInfo&&) = default;
108 IsolationInfo::~IsolationInfo() = default;
109 IsolationInfo& IsolationInfo::operator=(const IsolationInfo&) = default;
110 IsolationInfo& IsolationInfo::operator=(IsolationInfo&&) = default;
111
CreateForInternalRequest(const url::Origin & top_frame_origin)112 IsolationInfo IsolationInfo::CreateForInternalRequest(
113 const url::Origin& top_frame_origin) {
114 return IsolationInfo(RequestType::kOther, top_frame_origin, top_frame_origin,
115 SiteForCookies::FromOrigin(top_frame_origin),
116 /*nonce=*/absl::nullopt,
117 /*party_context=*/std::set<SchemefulSite>());
118 }
119
CreateTransient()120 IsolationInfo IsolationInfo::CreateTransient() {
121 url::Origin opaque_origin;
122 return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin,
123 SiteForCookies(), /*nonce=*/absl::nullopt,
124 /*party_context=*/absl::nullopt);
125 }
126
Deserialize(const std::string & serialized)127 absl::optional<IsolationInfo> IsolationInfo::Deserialize(
128 const std::string& serialized) {
129 proto::IsolationInfo proto;
130 if (!proto.ParseFromString(serialized))
131 return absl::nullopt;
132
133 absl::optional<url::Origin> top_frame_origin;
134 if (proto.has_top_frame_origin())
135 top_frame_origin = url::Origin::Create(GURL(proto.top_frame_origin()));
136
137 absl::optional<url::Origin> frame_origin;
138 if (proto.has_frame_origin())
139 frame_origin = url::Origin::Create(GURL(proto.frame_origin()));
140
141 absl::optional<std::set<SchemefulSite>> party_context;
142 if (proto.has_party_context()) {
143 party_context = std::set<SchemefulSite>();
144 for (const auto& site : proto.party_context().site()) {
145 party_context->insert(SchemefulSite::Deserialize(site));
146 }
147 }
148
149 return IsolationInfo::CreateIfConsistent(
150 static_cast<RequestType>(proto.request_type()),
151 std::move(top_frame_origin), std::move(frame_origin),
152 SiteForCookies::FromUrl(GURL(proto.site_for_cookies())),
153 std::move(party_context), /*nonce=*/absl::nullopt);
154 }
155
Create(RequestType request_type,const url::Origin & top_frame_origin,const url::Origin & frame_origin,const SiteForCookies & site_for_cookies,absl::optional<std::set<SchemefulSite>> party_context,const absl::optional<base::UnguessableToken> & nonce)156 IsolationInfo IsolationInfo::Create(
157 RequestType request_type,
158 const url::Origin& top_frame_origin,
159 const url::Origin& frame_origin,
160 const SiteForCookies& site_for_cookies,
161 absl::optional<std::set<SchemefulSite>> party_context,
162 const absl::optional<base::UnguessableToken>& nonce) {
163 return IsolationInfo(request_type, top_frame_origin, frame_origin,
164 site_for_cookies, nonce, std::move(party_context));
165 }
166
DoNotUseCreatePartialFromNak(const net::NetworkAnonymizationKey & network_anonymization_key)167 IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak(
168 const net::NetworkAnonymizationKey& network_anonymization_key) {
169 if (!network_anonymization_key.IsFullyPopulated()) {
170 return IsolationInfo();
171 }
172
173 url::Origin top_frame_origin =
174 network_anonymization_key.GetTopFrameSite()->site_as_origin_;
175
176 absl::optional<url::Origin> frame_origin;
177 if (network_anonymization_key.IsCrossSite()) {
178 // If we know that the origin is cross site to the top level site, create an
179 // empty origin to use as the frame origin for the isolation info. This
180 // should be cross site with the top level origin.
181 frame_origin = url::Origin();
182 } else {
183 // If we don't know that it's cross site to the top level site, use the top
184 // frame site to set the frame origin.
185 frame_origin = top_frame_origin;
186 }
187
188 const absl::optional<base::UnguessableToken>& nonce =
189 network_anonymization_key.GetNonce();
190
191 auto isolation_info = IsolationInfo::Create(
192 IsolationInfo::RequestType::kOther, top_frame_origin,
193 frame_origin.value(), SiteForCookies(),
194 /*party_context=*/absl::nullopt, nonce);
195 // TODO(crbug/1343856): DCHECK isolation info is fully populated.
196 return isolation_info;
197 }
198
CreateIfConsistent(RequestType request_type,const absl::optional<url::Origin> & top_frame_origin,const absl::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,absl::optional<std::set<SchemefulSite>> party_context,const absl::optional<base::UnguessableToken> & nonce)199 absl::optional<IsolationInfo> IsolationInfo::CreateIfConsistent(
200 RequestType request_type,
201 const absl::optional<url::Origin>& top_frame_origin,
202 const absl::optional<url::Origin>& frame_origin,
203 const SiteForCookies& site_for_cookies,
204 absl::optional<std::set<SchemefulSite>> party_context,
205 const absl::optional<base::UnguessableToken>& nonce) {
206 if (!IsConsistent(request_type, top_frame_origin, frame_origin,
207 site_for_cookies, party_context, nonce)) {
208 return absl::nullopt;
209 }
210 return IsolationInfo(request_type, top_frame_origin, frame_origin,
211 site_for_cookies, nonce, std::move(party_context));
212 }
213
CreateForRedirect(const url::Origin & new_origin) const214 IsolationInfo IsolationInfo::CreateForRedirect(
215 const url::Origin& new_origin) const {
216 if (request_type_ == RequestType::kOther)
217 return *this;
218
219 if (request_type_ == RequestType::kSubFrame) {
220 return IsolationInfo(request_type_, top_frame_origin_, new_origin,
221 site_for_cookies_, nonce_, party_context_);
222 }
223
224 DCHECK_EQ(RequestType::kMainFrame, request_type_);
225 DCHECK(!party_context_ || party_context_->empty());
226 return IsolationInfo(request_type_, new_origin, new_origin,
227 SiteForCookies::FromOrigin(new_origin), nonce_,
228 party_context_);
229 }
230
frame_origin() const231 const absl::optional<url::Origin>& IsolationInfo::frame_origin() const {
232 return frame_origin_;
233 }
234
frame_origin_for_testing() const235 const absl::optional<url::Origin>& IsolationInfo::frame_origin_for_testing()
236 const {
237 return frame_origin_;
238 }
239
IsEqualForTesting(const IsolationInfo & other) const240 bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const {
241 return (request_type_ == other.request_type_ &&
242 top_frame_origin_ == other.top_frame_origin_ &&
243 frame_origin_ == other.frame_origin_ &&
244 network_isolation_key_ == other.network_isolation_key_ &&
245 network_anonymization_key_ == other.network_anonymization_key_ &&
246 nonce_ == other.nonce_ &&
247 site_for_cookies_.IsEquivalent(other.site_for_cookies_) &&
248 party_context_ == other.party_context_);
249 }
250
ToDoUseTopFrameOriginAsWell(const url::Origin & incorrectly_used_frame_origin)251 IsolationInfo IsolationInfo::ToDoUseTopFrameOriginAsWell(
252 const url::Origin& incorrectly_used_frame_origin) {
253 return IsolationInfo(
254 RequestType::kOther, incorrectly_used_frame_origin,
255 incorrectly_used_frame_origin,
256 SiteForCookies::FromOrigin(incorrectly_used_frame_origin),
257 /*nonce=*/absl::nullopt, /*party_context=*/std::set<SchemefulSite>());
258 }
259
Serialize() const260 std::string IsolationInfo::Serialize() const {
261 if (network_isolation_key().IsTransient())
262 return "";
263
264 proto::IsolationInfo info;
265
266 info.set_request_type(static_cast<int32_t>(request_type_));
267
268 if (top_frame_origin_)
269 info.set_top_frame_origin(top_frame_origin_->Serialize());
270
271 if (frame_origin_)
272 info.set_frame_origin(frame_origin_->Serialize());
273
274 info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec());
275
276 if (party_context_) {
277 auto* pc = info.mutable_party_context();
278 for (const auto& site : *party_context_) {
279 pc->add_site(site.Serialize());
280 }
281 }
282
283 return info.SerializeAsString();
284 }
285
DebugString() const286 std::string IsolationInfo::DebugString() const {
287 std::string s;
288 s += "request_type: ";
289 switch (request_type_) {
290 case IsolationInfo::RequestType::kMainFrame:
291 s += "kMainFrame";
292 break;
293 case IsolationInfo::RequestType::kSubFrame:
294 s += "kSubFrame";
295 break;
296 case IsolationInfo::RequestType::kOther:
297 s += "kOther";
298 break;
299 }
300
301 s += "; top_frame_origin: ";
302 if (top_frame_origin_) {
303 s += top_frame_origin_.value().GetDebugString(true);
304 } else {
305 s += "(none)";
306 }
307
308 s += "; frame_origin: ";
309 if (frame_origin_) {
310 s += frame_origin_.value().GetDebugString(true);
311 } else {
312 s += "(none)";
313 }
314
315 s += "; network_anonymization_key: ";
316 s += network_anonymization_key_.ToDebugString();
317
318 s += "; network_isolation_key: ";
319 s += network_isolation_key_.ToDebugString();
320
321 s += "; party_context: ";
322 if (party_context_) {
323 s += "{";
324 for (auto& site : party_context_.value()) {
325 s += site.GetDebugString();
326 s += ", ";
327 }
328 s += "}";
329 } else {
330 s += "(none)";
331 }
332
333 s += "; nonce: ";
334 if (nonce_) {
335 s += nonce_.value().ToString();
336 } else {
337 s += "(none)";
338 }
339
340 s += "; site_for_cookies: ";
341 s += site_for_cookies_.ToDebugString();
342
343 return s;
344 }
345
346 NetworkAnonymizationKey
CreateNetworkAnonymizationKeyForIsolationInfo(const absl::optional<url::Origin> & top_frame_origin,const absl::optional<url::Origin> & frame_origin,const absl::optional<base::UnguessableToken> & nonce) const347 IsolationInfo::CreateNetworkAnonymizationKeyForIsolationInfo(
348 const absl::optional<url::Origin>& top_frame_origin,
349 const absl::optional<url::Origin>& frame_origin,
350 const absl::optional<base::UnguessableToken>& nonce) const {
351 if (!top_frame_origin) {
352 return NetworkAnonymizationKey();
353 }
354 SchemefulSite top_frame_site(*top_frame_origin);
355 SchemefulSite frame_site(*frame_origin);
356
357 return NetworkAnonymizationKey::CreateFromFrameSite(top_frame_site,
358 frame_site, nonce);
359 }
360
IsolationInfo(RequestType request_type,const absl::optional<url::Origin> & top_frame_origin,const absl::optional<url::Origin> & frame_origin,const SiteForCookies & site_for_cookies,const absl::optional<base::UnguessableToken> & nonce,absl::optional<std::set<SchemefulSite>> party_context)361 IsolationInfo::IsolationInfo(
362 RequestType request_type,
363 const absl::optional<url::Origin>& top_frame_origin,
364 const absl::optional<url::Origin>& frame_origin,
365 const SiteForCookies& site_for_cookies,
366 const absl::optional<base::UnguessableToken>& nonce,
367 absl::optional<std::set<SchemefulSite>> party_context)
368 : request_type_(request_type),
369 top_frame_origin_(top_frame_origin),
370 frame_origin_(frame_origin),
371 network_isolation_key_(
372 !top_frame_origin
373 ? NetworkIsolationKey()
374 : NetworkIsolationKey(SchemefulSite(*top_frame_origin),
375 SchemefulSite(*frame_origin),
376 nonce)),
377 network_anonymization_key_(
378 CreateNetworkAnonymizationKeyForIsolationInfo(top_frame_origin,
379 frame_origin,
380 nonce)),
381 site_for_cookies_(site_for_cookies),
382 nonce_(nonce),
383 party_context_(party_context.has_value() &&
384 party_context->size() > kPartyContextMaxSize
385 ? absl::nullopt
386 : party_context) {
387 DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_,
388 site_for_cookies_, party_context_, nonce));
389 }
390
391 } // namespace net
392