// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/isolation_info.h" #include #include #include "base/check_op.h" #include "base/unguessable_token.h" #include "net/base/features.h" #include "net/base/isolation_info.h" #include "net/base/isolation_info.pb.h" #include "net/base/network_anonymization_key.h" #include "net/base/proxy_server.h" namespace net { namespace { // Checks that |origin| is consistent with |site_for_cookies|. bool ValidateSameSite(const url::Origin& origin, const SiteForCookies& site_for_cookies) { // If not sending SameSite cookies, or sending them for a non-scheme, consider // all origins consistent. Note that SiteForCookies should never be created // for websocket schemes for valid navigations, since frames can't be // navigated to those schemes. if (site_for_cookies.IsNull() || (site_for_cookies.scheme() != url::kHttpScheme && site_for_cookies.scheme() != url::kHttpsScheme)) { return true; } // Shouldn't send cookies for opaque origins. if (origin.opaque()) return false; // TODO(https://crbug.com/1060631): GetURL() is expensive. Maybe make a // version of IsFirstParty that works on origins? return site_for_cookies.IsFirstParty(origin.GetURL()); } // Checks if these values are consistent. See IsolationInfo::Create() for // descriptions of consistent sets of values. Also allows values used by the // 0-argument constructor. bool IsConsistent(IsolationInfo::RequestType request_type, const std::optional& top_frame_origin, const std::optional& frame_origin, const SiteForCookies& site_for_cookies, const std::optional& nonce) { // Check for the default-constructed case. if (!top_frame_origin) { return request_type == IsolationInfo::RequestType::kOther && !frame_origin && !nonce && site_for_cookies.IsNull(); } // As long as there is a |top_frame_origin|, |site_for_cookies| must be // consistent with the |top_frame_origin|. if (!ValidateSameSite(*top_frame_origin, site_for_cookies)) return false; // Validate frame `frame_origin` // IsolationInfo must have a `frame_origin` when frame origins are enabled // and the IsolationInfo is not default-constructed. if (!frame_origin) { return false; } switch (request_type) { case IsolationInfo::RequestType::kMainFrame: // TODO(https://crbug.com/1056706): Check that |top_frame_origin| and // |frame_origin| are the same, once the ViewSource code creates a // consistent IsolationInfo object. // // TODO(https://crbug.com/1060631): Once CreatePartial() is removed, // check if SiteForCookies is non-null if the scheme is HTTP or HTTPS. break; case IsolationInfo::RequestType::kSubFrame: // For subframe navigations, the subframe's origin may not be consistent // with the SiteForCookies, so SameSite cookies may be sent if there's a // redirect to main frames site. break; case IsolationInfo::RequestType::kOther: // SiteForCookies must consistent with the frame origin as well for // subresources. return ValidateSameSite(*frame_origin, site_for_cookies); } return true; } } // namespace IsolationInfo::IsolationInfo() : IsolationInfo(RequestType::kOther, /*top_frame_origin=*/std::nullopt, /*frame_origin=*/std::nullopt, SiteForCookies(), /*nonce=*/std::nullopt) {} IsolationInfo::IsolationInfo(const IsolationInfo&) = default; IsolationInfo::IsolationInfo(IsolationInfo&&) = default; IsolationInfo::~IsolationInfo() = default; IsolationInfo& IsolationInfo::operator=(const IsolationInfo&) = default; IsolationInfo& IsolationInfo::operator=(IsolationInfo&&) = default; IsolationInfo IsolationInfo::CreateForInternalRequest( const url::Origin& top_frame_origin) { return IsolationInfo(RequestType::kOther, top_frame_origin, top_frame_origin, SiteForCookies::FromOrigin(top_frame_origin), /*nonce=*/std::nullopt); } IsolationInfo IsolationInfo::CreateTransient() { url::Origin opaque_origin; return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin, SiteForCookies(), /*nonce=*/std::nullopt); } IsolationInfo IsolationInfo::CreateTransientWithNonce( const base::UnguessableToken& nonce) { url::Origin opaque_origin; return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin, SiteForCookies(), nonce); } std::optional IsolationInfo::Deserialize( const std::string& serialized) { proto::IsolationInfo proto; if (!proto.ParseFromString(serialized)) return std::nullopt; std::optional top_frame_origin; if (proto.has_top_frame_origin()) top_frame_origin = url::Origin::Create(GURL(proto.top_frame_origin())); std::optional frame_origin; if (proto.has_frame_origin()) frame_origin = url::Origin::Create(GURL(proto.frame_origin())); return IsolationInfo::CreateIfConsistent( static_cast(proto.request_type()), std::move(top_frame_origin), std::move(frame_origin), SiteForCookies::FromUrl(GURL(proto.site_for_cookies())), /*nonce=*/std::nullopt); } IsolationInfo IsolationInfo::Create( RequestType request_type, const url::Origin& top_frame_origin, const url::Origin& frame_origin, const SiteForCookies& site_for_cookies, const std::optional& nonce) { return IsolationInfo(request_type, top_frame_origin, frame_origin, site_for_cookies, nonce); } IsolationInfo IsolationInfo::DoNotUseCreatePartialFromNak( const net::NetworkAnonymizationKey& network_anonymization_key) { if (!network_anonymization_key.IsFullyPopulated()) { return IsolationInfo(); } url::Origin top_frame_origin = network_anonymization_key.GetTopFrameSite()->site_as_origin_; std::optional frame_origin; if (network_anonymization_key.IsCrossSite()) { // If we know that the origin is cross site to the top level site, create an // empty origin to use as the frame origin for the isolation info. This // should be cross site with the top level origin. frame_origin = url::Origin(); } else { // If we don't know that it's cross site to the top level site, use the top // frame site to set the frame origin. frame_origin = top_frame_origin; } const std::optional& nonce = network_anonymization_key.GetNonce(); auto isolation_info = IsolationInfo::Create( IsolationInfo::RequestType::kOther, top_frame_origin, frame_origin.value(), SiteForCookies(), nonce); // TODO(crbug/1343856): DCHECK isolation info is fully populated. return isolation_info; } std::optional IsolationInfo::CreateIfConsistent( RequestType request_type, const std::optional& top_frame_origin, const std::optional& frame_origin, const SiteForCookies& site_for_cookies, const std::optional& nonce) { if (!IsConsistent(request_type, top_frame_origin, frame_origin, site_for_cookies, nonce)) { return std::nullopt; } return IsolationInfo(request_type, top_frame_origin, frame_origin, site_for_cookies, nonce); } IsolationInfo IsolationInfo::CreateForRedirect( const url::Origin& new_origin) const { if (request_type_ == RequestType::kOther) return *this; if (request_type_ == RequestType::kSubFrame) { return IsolationInfo(request_type_, top_frame_origin_, new_origin, site_for_cookies_, nonce_); } DCHECK_EQ(RequestType::kMainFrame, request_type_); return IsolationInfo(request_type_, new_origin, new_origin, SiteForCookies::FromOrigin(new_origin), nonce_); } const std::optional& IsolationInfo::frame_origin() const { return frame_origin_; } const std::optional& IsolationInfo::frame_origin_for_testing() const { return frame_origin_; } bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const { return (request_type_ == other.request_type_ && top_frame_origin_ == other.top_frame_origin_ && frame_origin_ == other.frame_origin_ && network_isolation_key_ == other.network_isolation_key_ && network_anonymization_key_ == other.network_anonymization_key_ && nonce_ == other.nonce_ && site_for_cookies_.IsEquivalent(other.site_for_cookies_)); } std::string IsolationInfo::Serialize() const { if (network_isolation_key().IsTransient()) return ""; proto::IsolationInfo info; info.set_request_type(static_cast(request_type_)); if (top_frame_origin_) info.set_top_frame_origin(top_frame_origin_->Serialize()); if (frame_origin_) info.set_frame_origin(frame_origin_->Serialize()); info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec()); return info.SerializeAsString(); } std::string IsolationInfo::DebugString() const { std::string s; s += "request_type: "; switch (request_type_) { case IsolationInfo::RequestType::kMainFrame: s += "kMainFrame"; break; case IsolationInfo::RequestType::kSubFrame: s += "kSubFrame"; break; case IsolationInfo::RequestType::kOther: s += "kOther"; break; } s += "; top_frame_origin: "; if (top_frame_origin_) { s += top_frame_origin_.value().GetDebugString(true); } else { s += "(none)"; } s += "; frame_origin: "; if (frame_origin_) { s += frame_origin_.value().GetDebugString(true); } else { s += "(none)"; } s += "; network_anonymization_key: "; s += network_anonymization_key_.ToDebugString(); s += "; network_isolation_key: "; s += network_isolation_key_.ToDebugString(); s += "; nonce: "; if (nonce_) { s += nonce_.value().ToString(); } else { s += "(none)"; } s += "; site_for_cookies: "; s += site_for_cookies_.ToDebugString(); return s; } NetworkAnonymizationKey IsolationInfo::CreateNetworkAnonymizationKeyForIsolationInfo( const std::optional& top_frame_origin, const std::optional& frame_origin, const std::optional& nonce) const { if (!top_frame_origin) { return NetworkAnonymizationKey(); } SchemefulSite top_frame_site(*top_frame_origin); SchemefulSite frame_site(*frame_origin); return NetworkAnonymizationKey::CreateFromFrameSite(top_frame_site, frame_site, nonce); } IsolationInfo::IsolationInfo(RequestType request_type, const std::optional& top_frame_origin, const std::optional& frame_origin, const SiteForCookies& site_for_cookies, const std::optional& nonce) : request_type_(request_type), top_frame_origin_(top_frame_origin), frame_origin_(frame_origin), network_isolation_key_( !top_frame_origin ? NetworkIsolationKey() : NetworkIsolationKey(SchemefulSite(*top_frame_origin), SchemefulSite(*frame_origin), nonce)), network_anonymization_key_( CreateNetworkAnonymizationKeyForIsolationInfo(top_frame_origin, frame_origin, nonce)), site_for_cookies_(site_for_cookies), nonce_(nonce) { DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_, site_for_cookies_, nonce)); } } // namespace net