1 // Copyright 2021 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 #ifndef NET_COOKIES_COOKIE_PARTITION_KEY_H_ 6 #define NET_COOKIES_COOKIE_PARTITION_KEY_H_ 7 8 #include <optional> 9 #include <string> 10 11 #include "base/types/expected.h" 12 #include "net/base/cronet_buildflags.h" 13 #include "net/base/features.h" 14 #include "net/base/net_export.h" 15 #include "net/base/network_isolation_key.h" 16 #include "net/base/schemeful_site.h" 17 #include "url/gurl.h" 18 19 #if !BUILDFLAG(CRONET_BUILD) 20 #include "mojo/public/cpp/bindings/default_construct_tag.h" 21 #endif 22 23 namespace net { 24 25 class SiteForCookies; 26 27 class NET_EXPORT CookiePartitionKey { 28 public: 29 class NET_EXPORT SerializedCookiePartitionKey { 30 public: 31 const std::string& TopLevelSite() const; 32 bool has_cross_site_ancestor() const; 33 34 std::string GetDebugString() const; 35 36 // This constructor does not check if the values being serialized are valid. 37 // The caller of this function must ensure that only valid values are passed 38 // to this method. 39 SerializedCookiePartitionKey(base::PassKey<CookiePartitionKey> key, 40 const std::string& site, 41 bool has_cross_site_ancestor); 42 43 private: 44 std::string top_level_site_; 45 bool has_cross_site_ancestor_; 46 }; 47 48 // An enumerated value representing whether any frame in the PartitionKey's 49 // ancestor chain (including the top-level document's site) is cross-site with 50 // the current frame. These values are persisted to disk. Entries should not 51 // be renumbered and numeric values should never be reused. 52 enum class AncestorChainBit { 53 // All frames in the ancestor chain are pairwise same-site. 54 kSameSite = 0, 55 // At least one frame in the ancestor chain is cross-site with 56 // the current frame. 57 kCrossSite = 1, 58 }; 59 60 static AncestorChainBit BoolToAncestorChainBit(bool val); 61 62 CookiePartitionKey() = delete; 63 #if !BUILDFLAG(CRONET_BUILD) 64 explicit CookiePartitionKey(mojo::DefaultConstruct::Tag); 65 #endif 66 CookiePartitionKey(const CookiePartitionKey& other); 67 CookiePartitionKey(CookiePartitionKey&& other); 68 CookiePartitionKey& operator=(const CookiePartitionKey& other); 69 CookiePartitionKey& operator=(CookiePartitionKey&& other); 70 ~CookiePartitionKey(); 71 72 bool operator==(const CookiePartitionKey& other) const; 73 bool operator!=(const CookiePartitionKey& other) const; 74 bool operator<(const CookiePartitionKey& other) const; 75 76 // Methods for serializing and deserializing a partition key to/from a string. 77 // This is currently used for: 78 // - Storing persistent partitioned cookies 79 // - Loading partitioned cookies into Java code 80 // - Sending cookie partition keys as strings in the DevTools protocol 81 // 82 // This function returns true if the partition key is not opaque and if nonce_ 83 // is not present. We do not want to serialize cookies with opaque origins or 84 // nonce in their partition key to disk, because if the browser session ends 85 // we will not be able to attach the saved cookie to any future requests. This 86 // is because opaque origins' nonces are only stored in volatile memory. 87 // 88 // TODO(crbug.com/40188414) Investigate ways to persist partition keys with 89 // opaque origins if a browser session is restored. 90 [[nodiscard]] static base::expected<SerializedCookiePartitionKey, std::string> 91 Serialize(const std::optional<CookiePartitionKey>& in); 92 93 static CookiePartitionKey FromURLForTesting( 94 const GURL& url, 95 AncestorChainBit ancestor_chain_bit = AncestorChainBit::kCrossSite, 96 std::optional<base::UnguessableToken> nonce = std::nullopt) { 97 return CookiePartitionKey(SchemefulSite(url), nonce, ancestor_chain_bit); 98 } 99 100 // Create a partition key from a network isolation key. Partition key is 101 // derived from the key's top-frame site. For scripts, the request_site 102 // is the url of the context running the code. 103 static std::optional<CookiePartitionKey> FromNetworkIsolationKey( 104 const NetworkIsolationKey& network_isolation_key, 105 const SiteForCookies& site_for_cookies, 106 const SchemefulSite& request_site, 107 bool main_frame_navigation); 108 109 // Create a new CookiePartitionKey from the site of an existing 110 // CookiePartitionKey. This should only be used for sites of partition keys 111 // which were already created using Deserialize or FromNetworkIsolationKey. 112 static CookiePartitionKey FromWire( 113 const SchemefulSite& site, 114 AncestorChainBit ancestor_chain_bit, 115 std::optional<base::UnguessableToken> nonce = std::nullopt) { 116 return CookiePartitionKey(site, nonce, ancestor_chain_bit); 117 } 118 119 // Create a new CookiePartitionKey in a script running in a renderer. We do 120 // not trust the renderer to provide us with a cookie partition key, so we let 121 // the renderer use this method to indicate the cookie is partitioned but the 122 // key still needs to be determined. 123 // 124 // When the browser is ingesting cookie partition keys from the renderer, 125 // either the `from_script_` flag should be set or the cookie partition key 126 // should match the browser's. Otherwise the renderer may be compromised. 127 // 128 // TODO(crbug.com/40188414) Consider removing this factory method and 129 // `from_script_` flag when BlinkStorageKey is available in 130 // ServiceWorkerGlobalScope. FromScript()131 static std::optional<CookiePartitionKey> FromScript() { 132 return std::make_optional(CookiePartitionKey(true)); 133 } 134 135 // Create a new CookiePartitionKey from the components of a StorageKey. 136 // Forwards to FromWire, but unlike that method in this one the optional nonce 137 // argument has no default. It also checks that cookie partitioning is enabled 138 // before returning a valid key, which FromWire does not check. 139 [[nodiscard]] static std::optional<CookiePartitionKey> 140 FromStorageKeyComponents(const SchemefulSite& top_level_site, 141 AncestorChainBit ancestor_chain_bit, 142 const std::optional<base::UnguessableToken>& nonce); 143 144 // FromStorage is a factory method which is meant for creating a new 145 // CookiePartitionKey using properties of a previously existing 146 // CookiePartitionKey that was already ingested into storage. This should NOT 147 // be used to create a new CookiePartitionKey that was not previously saved in 148 // storage. 149 [[nodiscard]] static base::expected<std::optional<CookiePartitionKey>, 150 std::string> 151 FromStorage(const std::string& top_level_site, bool has_cross_site_ancestor); 152 153 // This method should be used when the data provided is expected to be 154 // non-null but might be invalid or comes from a potentially untrustworthy 155 // source (such as user-supplied data). 156 // 157 // This reserves FromStorage to handle cases that can result in a null key 158 // (and perfectly validly, like in the case when the top_level_site is empty). 159 [[nodiscard]] static base::expected<CookiePartitionKey, std::string> 160 FromUntrustedInput(const std::string& top_level_site, 161 bool has_cross_site_ancestor); 162 site()163 const SchemefulSite& site() const { return site_; } 164 from_script()165 bool from_script() const { return from_script_; } 166 167 // Returns true if the current partition key can be serialized to a string. 168 // Cookie partition keys whose internal site is opaque cannot be serialized. 169 bool IsSerializeable() const; 170 nonce()171 const std::optional<base::UnguessableToken>& nonce() const { return nonce_; } 172 HasNonce(const std::optional<CookiePartitionKey> & key)173 static bool HasNonce(const std::optional<CookiePartitionKey>& key) { 174 return key && key->nonce(); 175 } 176 IsThirdParty()177 bool IsThirdParty() const { 178 return ancestor_chain_bit_ == AncestorChainBit::kCrossSite; 179 } 180 181 private: 182 // Used by DeserializeInternal to determine how strict the context should be 183 // about inconsistencies in the input. 184 enum class ParsingMode { 185 // The top_level_site string must be serialized exactly as a SchemefulSite 186 // would be. 187 // Use this when reading from storage. 188 kStrict = 0, 189 // The top_level_site string must be coercible to a SchemefulSite. 190 // Use this for user input. 191 kLoose = 1, 192 }; 193 194 explicit CookiePartitionKey(const SchemefulSite& site, 195 std::optional<base::UnguessableToken> nonce, 196 AncestorChainBit ancestor_chain_bit); 197 explicit CookiePartitionKey(bool from_script); 198 199 // This method holds the deserialization logic for validating input from 200 // DeserializeForTesting and FromUntrustedInput which can be used to pass 201 // unserializable top_level_site values. 202 [[nodiscard]] static base::expected<CookiePartitionKey, std::string> 203 DeserializeInternal( 204 const std::string& top_level_site, 205 CookiePartitionKey::AncestorChainBit has_cross_site_ancestor, 206 CookiePartitionKey::ParsingMode parsing_mode); 207 208 AncestorChainBit MaybeAncestorChainBit() const; 209 210 SchemefulSite site_; 211 bool from_script_ = false; 212 // crbug.com/328043119 remove code associated with 213 // kAncestorChainBitEnabledInPartitionedCookies 214 // when feature is no longer needed. 215 bool ancestor_chain_enabled_ = base::FeatureList::IsEnabled( 216 features::kAncestorChainBitEnabledInPartitionedCookies); 217 218 // Having a nonce is a way to force a transient opaque `CookiePartitionKey` 219 // for non-opaque origins. 220 std::optional<base::UnguessableToken> nonce_; 221 AncestorChainBit ancestor_chain_bit_ = AncestorChainBit::kCrossSite; 222 }; 223 224 // Used so that CookiePartitionKeys can be the arguments of DCHECK_EQ. 225 NET_EXPORT std::ostream& operator<<(std::ostream& os, 226 const CookiePartitionKey& cpk); 227 228 } // namespace net 229 230 #endif // NET_COOKIES_COOKIE_PARTITION_KEY_H_ 231