blob: 21d884283b7f70864294088bafee9527e649286d [file] [log] [blame]
// Copyright 2022 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/network_anonymization_key.h"
#include <atomic>
#include <optional>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/types/pass_key.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "net/base/features.h"
#include "net/base/net_export.h"
#include "net/base/network_isolation_key.h"
#include "net/base/network_isolation_partition.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/site_for_cookies.h"
namespace net {
namespace {
// True if network state partitioning should be enabled regardless of feature
// settings.
bool g_partition_by_default = false;
// True if NAK::IsPartitioningEnabled has been called, and the value of
// `g_partition_by_default` cannot be changed.
constinit std::atomic<bool> g_partition_by_default_locked = false;
} // namespace
NetworkAnonymizationKey::NetworkAnonymizationKey()
: data_(Data::GetEmptyData()) {}
NetworkAnonymizationKey::NetworkAnonymizationKey(
const NetworkAnonymizationKey& network_anonymization_key) = default;
NetworkAnonymizationKey::NetworkAnonymizationKey(
NetworkAnonymizationKey&& network_anonymization_key) = default;
NetworkAnonymizationKey& NetworkAnonymizationKey::operator=(
const NetworkAnonymizationKey& network_anonymization_key) = default;
NetworkAnonymizationKey& NetworkAnonymizationKey::operator=(
NetworkAnonymizationKey&& network_anonymization_key) = default;
NetworkAnonymizationKey::NetworkAnonymizationKey(
const std::optional<SchemefulSite>& top_frame_site,
bool is_cross_site,
std::optional<base::UnguessableToken> nonce,
NetworkIsolationPartition network_isolation_partition)
: data_(base::MakeRefCounted<Data>(top_frame_site,
is_cross_site,
std::move(nonce),
network_isolation_partition)) {}
NetworkAnonymizationKey::~NetworkAnonymizationKey() = default;
NetworkAnonymizationKey NetworkAnonymizationKey::CreateFromFrameSite(
const SchemefulSite& top_frame_site,
const SchemefulSite& frame_site,
std::optional<base::UnguessableToken> nonce,
NetworkIsolationPartition network_isolation_partition) {
bool is_cross_site = top_frame_site != frame_site;
return NetworkAnonymizationKey(top_frame_site, is_cross_site, nonce,
network_isolation_partition);
}
NetworkAnonymizationKey NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
const net::NetworkIsolationKey& network_isolation_key) {
return NetworkAnonymizationKey(
network_isolation_key.GetTopFrameSite(),
network_isolation_key.GetTopFrameSite() !=
network_isolation_key.GetFrameSiteForNetworkAnonymizationKey(
base::PassKey<NetworkAnonymizationKey>()),
network_isolation_key.GetNonce(),
network_isolation_key.GetNetworkIsolationPartition());
}
NetworkAnonymizationKey NetworkAnonymizationKey::CreateTransient() {
SchemefulSite site_with_opaque_origin;
return NetworkAnonymizationKey(site_with_opaque_origin, false);
}
std::string NetworkAnonymizationKey::ToDebugString() const {
if (!IsFullyPopulated()) {
return "null";
}
std::string str = GetSiteDebugString(GetTopFrameSite());
str += IsCrossSite() ? " cross_site" : " same_site";
// Currently, if the NAK has a nonce it will be marked transient. For debug
// purposes we will print the value but if called via
// `NetworkAnonymizationKey::ToString` we will have already returned "".
if (GetNonce().has_value()) {
str += " (with nonce " + GetNonce()->ToString() + ")";
}
if (network_isolation_partition() != NetworkIsolationPartition::kGeneral) {
str +=
" (" +
NetworkIsolationPartitionToDebugString(network_isolation_partition()) +
")";
}
return str;
}
bool NetworkAnonymizationKey::IsEmpty() const {
return data_->is_empty();
}
bool NetworkAnonymizationKey::IsFullyPopulated() const {
return !IsEmpty();
}
bool NetworkAnonymizationKey::IsTransient() const {
if (!IsFullyPopulated())
return true;
return GetTopFrameSite()->opaque() || GetNonce().has_value();
}
bool NetworkAnonymizationKey::ToValue(base::Value* out_value) const {
if (IsEmpty()) {
*out_value = base::Value(base::Value::Type::LIST);
return true;
}
if (IsTransient())
return false;
std::optional<std::string> top_frame_value =
SerializeSiteWithNonce(*GetTopFrameSite());
if (!top_frame_value)
return false;
base::Value::List list;
list.Append(std::move(top_frame_value).value());
list.Append(IsCrossSite());
list.Append(base::strict_cast<int32_t>(network_isolation_partition()));
*out_value = base::Value(std::move(list));
return true;
}
bool NetworkAnonymizationKey::FromValue(
const base::Value& value,
NetworkAnonymizationKey* network_anonymization_key) {
if (!value.is_list()) {
return false;
}
const base::Value::List& list = value.GetList();
if (list.empty()) {
*network_anonymization_key = NetworkAnonymizationKey();
return true;
}
// Check the format.
// While migrating to using NetworkIsolationPartition, continue supporting
// values of length 2 for a few months.
// TODO(abigailkatcoff): Stop support for lists of length 2 after a few
// months.
if (list.size() < 2 || list.size() > 3 || !list[0].is_string() ||
!list[1].is_bool()) {
return false;
}
// Check top_level_site is valid for any key scheme
std::optional<SchemefulSite> top_frame_site =
SchemefulSite::DeserializeWithNonce(
base::PassKey<NetworkAnonymizationKey>(), list[0].GetString());
if (!top_frame_site) {
return false;
}
bool is_cross_site = list[1].GetBool();
NetworkIsolationPartition network_isolation_partition =
NetworkIsolationPartition::kGeneral;
if (list.size() == 3) {
if (!list[2].is_int() ||
list[2].GetInt() >
base::strict_cast<int32_t>(NetworkIsolationPartition::kMaxValue) ||
list[2].GetInt() < 0) {
return false;
}
network_isolation_partition =
static_cast<NetworkIsolationPartition>(list[2].GetInt());
}
*network_anonymization_key = NetworkAnonymizationKey(
top_frame_site.value(), is_cross_site, /*nonce=*/std::nullopt,
network_isolation_partition);
return true;
}
std::string NetworkAnonymizationKey::GetSiteDebugString(
const std::optional<SchemefulSite>& site) const {
return site ? site->GetDebugString() : "null";
}
std::optional<std::string> NetworkAnonymizationKey::SerializeSiteWithNonce(
const SchemefulSite& site) {
return *(const_cast<SchemefulSite&>(site).SerializeWithNonce(
base::PassKey<NetworkAnonymizationKey>()));
}
// static
bool NetworkAnonymizationKey::IsPartitioningEnabled() {
g_partition_by_default_locked.store(true, std::memory_order_relaxed);
return g_partition_by_default ||
base::FeatureList::IsEnabled(
features::kPartitionConnectionsByNetworkIsolationKey);
}
// static
void NetworkAnonymizationKey::PartitionByDefault() {
DCHECK(!g_partition_by_default_locked.load(std::memory_order_relaxed));
// Only set the global if none of the relevant features are overridden.
if (!base::FeatureList::GetInstance()->IsFeatureOverridden(
"PartitionConnectionsByNetworkIsolationKey")) {
g_partition_by_default = true;
}
}
// static
void NetworkAnonymizationKey::ClearGlobalsForTesting() {
g_partition_by_default = false;
g_partition_by_default_locked.store(false);
}
NET_EXPORT std::ostream& operator<<(std::ostream& os,
const NetworkAnonymizationKey& nak) {
os << nak.ToDebugString();
return os;
}
// static
scoped_refptr<NetworkAnonymizationKey::Data>
NetworkAnonymizationKey::Data::GetEmptyData() {
static base::NoDestructor<scoped_refptr<NetworkAnonymizationKey::Data>>
empty_data(base::MakeRefCounted<Data>(base::PassKey<Data>()));
return *empty_data;
}
NetworkAnonymizationKey::Data::Data(base::PassKey<Data>)
: is_cross_site_(false),
network_isolation_partition_(NetworkIsolationPartition::kGeneral) {
CHECK(is_empty());
}
NetworkAnonymizationKey::Data::Data(
const std::optional<SchemefulSite>& top_frame_site,
bool is_cross_site,
std::optional<base::UnguessableToken> nonce,
NetworkIsolationPartition network_isolation_partition)
: top_frame_site_(top_frame_site),
is_cross_site_(is_cross_site),
nonce_(std::move(nonce)),
network_isolation_partition_(network_isolation_partition) {}
NetworkAnonymizationKey::Data::~Data() = default;
} // namespace net