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
Deserialize(const std::string & serialized)119 std::optional<IsolationInfo> IsolationInfo::Deserialize(
120 const std::string& serialized) {
121 proto::IsolationInfo proto;
122 if (!proto.ParseFromString(serialized))
123 return std::nullopt;
124
125 std::optional<url::Origin> top_frame_origin;
126 if (proto.has_top_frame_origin())
127 top_frame_origin = url::Origin::Create(GURL(proto.top_frame_origin()));
128
129 std::optional<url::Origin> frame_origin;
130 if (proto.has_frame_origin())
131 frame_origin = url::Origin::Create(GURL(proto.frame_origin()));
132
133 return IsolationInfo::CreateIfConsistent(
134 static_cast<RequestType>(proto.request_type()),
135 std::move(top_frame_origin), std::move(frame_origin),
136 SiteForCookies::FromUrl(GURL(proto.site_for_cookies())),
137 /*nonce=*/std::nullopt);
138 }
139
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)140 IsolationInfo IsolationInfo::Create(
141 RequestType request_type,
142 const url::Origin& top_frame_origin,
143 const url::Origin& frame_origin,
144 const SiteForCookies& site_for_cookies,
145 const std::optional<base::UnguessableToken>& nonce) {
146 return IsolationInfo(request_type, top_frame_origin, frame_origin,
147 site_for_cookies, nonce);
148 }
149
DoNotUseCreatePartialFromNak(const net::NetworkAnonymizationKey & network_anonymization_key)150 IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak(
151 const net::NetworkAnonymizationKey& network_anonymization_key) {
152 if (!network_anonymization_key.IsFullyPopulated()) {
153 return IsolationInfo();
154 }
155
156 url::Origin top_frame_origin =
157 network_anonymization_key.GetTopFrameSite()->site_as_origin_;
158
159 std::optional<url::Origin> frame_origin;
160 if (network_anonymization_key.IsCrossSite()) {
161 // If we know that the origin is cross site to the top level site, create an
162 // empty origin to use as the frame origin for the isolation info. This
163 // should be cross site with the top level origin.
164 frame_origin = url::Origin();
165 } else {
166 // If we don't know that it's cross site to the top level site, use the top
167 // frame site to set the frame origin.
168 frame_origin = top_frame_origin;
169 }
170
171 const std::optional<base::UnguessableToken>& nonce =
172 network_anonymization_key.GetNonce();
173
174 auto isolation_info = IsolationInfo::Create(
175 IsolationInfo::RequestType::kOther, top_frame_origin,
176 frame_origin.value(), SiteForCookies(), nonce);
177 // TODO(crbug/1343856): DCHECK isolation info is fully populated.
178 return isolation_info;
179 }
180
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)181 std::optional<IsolationInfo> IsolationInfo::CreateIfConsistent(
182 RequestType request_type,
183 const std::optional<url::Origin>& top_frame_origin,
184 const std::optional<url::Origin>& frame_origin,
185 const SiteForCookies& site_for_cookies,
186 const std::optional<base::UnguessableToken>& nonce) {
187 if (!IsConsistent(request_type, top_frame_origin, frame_origin,
188 site_for_cookies, nonce)) {
189 return std::nullopt;
190 }
191 return IsolationInfo(request_type, top_frame_origin, frame_origin,
192 site_for_cookies, nonce);
193 }
194
CreateForRedirect(const url::Origin & new_origin) const195 IsolationInfo IsolationInfo::CreateForRedirect(
196 const url::Origin& new_origin) const {
197 if (request_type_ == RequestType::kOther)
198 return *this;
199
200 if (request_type_ == RequestType::kSubFrame) {
201 return IsolationInfo(request_type_, top_frame_origin_, new_origin,
202 site_for_cookies_, nonce_);
203 }
204
205 DCHECK_EQ(RequestType::kMainFrame, request_type_);
206 return IsolationInfo(request_type_, new_origin, new_origin,
207 SiteForCookies::FromOrigin(new_origin), nonce_);
208 }
209
frame_origin() const210 const std::optional<url::Origin>& IsolationInfo::frame_origin() const {
211 return frame_origin_;
212 }
213
frame_origin_for_testing() const214 const std::optional<url::Origin>& IsolationInfo::frame_origin_for_testing()
215 const {
216 return frame_origin_;
217 }
218
IsEqualForTesting(const IsolationInfo & other) const219 bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const {
220 return (request_type_ == other.request_type_ &&
221 top_frame_origin_ == other.top_frame_origin_ &&
222 frame_origin_ == other.frame_origin_ &&
223 network_isolation_key_ == other.network_isolation_key_ &&
224 network_anonymization_key_ == other.network_anonymization_key_ &&
225 nonce_ == other.nonce_ &&
226 site_for_cookies_.IsEquivalent(other.site_for_cookies_));
227 }
228
Serialize() const229 std::string IsolationInfo::Serialize() const {
230 if (network_isolation_key().IsTransient())
231 return "";
232
233 proto::IsolationInfo info;
234
235 info.set_request_type(static_cast<int32_t>(request_type_));
236
237 if (top_frame_origin_)
238 info.set_top_frame_origin(top_frame_origin_->Serialize());
239
240 if (frame_origin_)
241 info.set_frame_origin(frame_origin_->Serialize());
242
243 info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec());
244
245 return info.SerializeAsString();
246 }
247
DebugString() const248 std::string IsolationInfo::DebugString() const {
249 std::string s;
250 s += "request_type: ";
251 switch (request_type_) {
252 case IsolationInfo::RequestType::kMainFrame:
253 s += "kMainFrame";
254 break;
255 case IsolationInfo::RequestType::kSubFrame:
256 s += "kSubFrame";
257 break;
258 case IsolationInfo::RequestType::kOther:
259 s += "kOther";
260 break;
261 }
262
263 s += "; top_frame_origin: ";
264 if (top_frame_origin_) {
265 s += top_frame_origin_.value().GetDebugString(true);
266 } else {
267 s += "(none)";
268 }
269
270 s += "; frame_origin: ";
271 if (frame_origin_) {
272 s += frame_origin_.value().GetDebugString(true);
273 } else {
274 s += "(none)";
275 }
276
277 s += "; network_anonymization_key: ";
278 s += network_anonymization_key_.ToDebugString();
279
280 s += "; network_isolation_key: ";
281 s += network_isolation_key_.ToDebugString();
282
283 s += "; nonce: ";
284 if (nonce_) {
285 s += nonce_.value().ToString();
286 } else {
287 s += "(none)";
288 }
289
290 s += "; site_for_cookies: ";
291 s += site_for_cookies_.ToDebugString();
292
293 return s;
294 }
295
296 NetworkAnonymizationKey
CreateNetworkAnonymizationKeyForIsolationInfo(const std::optional<url::Origin> & top_frame_origin,const std::optional<url::Origin> & frame_origin,const std::optional<base::UnguessableToken> & nonce) const297 IsolationInfo::CreateNetworkAnonymizationKeyForIsolationInfo(
298 const std::optional<url::Origin>& top_frame_origin,
299 const std::optional<url::Origin>& frame_origin,
300 const std::optional<base::UnguessableToken>& nonce) const {
301 if (!top_frame_origin) {
302 return NetworkAnonymizationKey();
303 }
304 SchemefulSite top_frame_site(*top_frame_origin);
305 SchemefulSite frame_site(*frame_origin);
306
307 return NetworkAnonymizationKey::CreateFromFrameSite(top_frame_site,
308 frame_site, nonce);
309 }
310
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)311 IsolationInfo::IsolationInfo(RequestType request_type,
312 const std::optional<url::Origin>& top_frame_origin,
313 const std::optional<url::Origin>& frame_origin,
314 const SiteForCookies& site_for_cookies,
315 const std::optional<base::UnguessableToken>& nonce)
316 : request_type_(request_type),
317 top_frame_origin_(top_frame_origin),
318 frame_origin_(frame_origin),
319 network_isolation_key_(
320 !top_frame_origin
321 ? NetworkIsolationKey()
322 : NetworkIsolationKey(SchemefulSite(*top_frame_origin),
323 SchemefulSite(*frame_origin),
324 nonce)),
325 network_anonymization_key_(
326 CreateNetworkAnonymizationKeyForIsolationInfo(top_frame_origin,
327 frame_origin,
328 nonce)),
329 site_for_cookies_(site_for_cookies),
330 nonce_(nonce) {
331 DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_,
332 site_for_cookies_, nonce));
333 }
334
335 } // namespace net
336