Skip to content

Commit

Permalink
Add ToValue and FromValue to NetworkAnonymizationKey.
Browse files Browse the repository at this point in the history
Bug: 1343856
Change-Id: Ibd656e4899eddf37324aa86f5855e40ba9f6afd2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3924176
Commit-Queue: Brianna Goldstein <brgoldstein@google.com>
Reviewed-by: Matt Menke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1052759}
  • Loading branch information
brgoldstein authored and Chromium LUCI CQ committed Sep 29, 2022
1 parent 28c1eb4 commit 62aeced
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 0 deletions.
100 changes: 100 additions & 0 deletions net/base/network_anonymization_key.cc
Expand Up @@ -4,6 +4,7 @@
#include "net/base/network_anonymization_key.h"
#include "base/feature_list.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"
Expand Down Expand Up @@ -159,9 +160,108 @@ bool NetworkAnonymizationKey::IsCrossSiteFlagSchemeEnabled() {
net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
}

bool NetworkAnonymizationKey::ToValue(base::Value* out_value) const {
if (IsEmpty()) {
*out_value = base::Value(base::Value::Type::LIST);
return true;
}

if (IsTransient())
return false;

absl::optional<std::string> top_frame_value =
SerializeSiteWithNonce(*top_frame_site_);
if (!top_frame_value)
return false;
base::Value::List list;
list.Append(std::move(top_frame_value).value());

absl::optional<std::string> frame_value =
IsFrameSiteEnabled() ? SerializeSiteWithNonce(*frame_site_)
: absl::nullopt;

// Append frame site for tripe key scheme or is_cross_site flag for double key
// with cross site flag scheme.
if (IsFrameSiteEnabled()) {
if (!frame_value.has_value()) {
return false;
}
list.Append(std::move(frame_value).value());
} else if (IsCrossSiteFlagSchemeEnabled()) {
const bool is_cross_site = GetIsCrossSite();
list.Append(is_cross_site);
}

*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 top_level_site is valid for any key scheme
if (list.size() < 1 || !list[0].is_string()) {
return false;
}
absl::optional<SchemefulSite> top_frame_site =
SchemefulSite::DeserializeWithNonce(list[0].GetString());
if (!top_frame_site) {
return false;
}

absl::optional<SchemefulSite> frame_site = absl::nullopt;
absl::optional<bool> is_cross_site = absl::nullopt;

// If double key scheme is enabled `list` must be of length 1. list[0] will be
// top_frame_site.
if (IsDoubleKeySchemeEnabled()) {
if (list.size() != 1) {
return false;
}
} else if (IsCrossSiteFlagSchemeEnabled()) {
// If double key + is cross site scheme is enabled `list` must be of
// length 2. list[0] will be top_frame_site and list[1] will be
// is_cross_site.
if (list.size() != 2 || !list[1].is_bool()) {
return false;
}
is_cross_site = list[1].GetBool();
} else {
// If neither alternative scheme is enabled we expect a valid triple keyed
// NetworkAnonymizationKey. `list` must be of length 2. list[0] will be
// top_frame_site and list[1] will be frame_site.
if (list.size() != 2 || !list[1].is_string()) {
return false;
}
frame_site = SchemefulSite::DeserializeWithNonce(list[1].GetString());
if (!frame_site) {
return false;
}
}

*network_anonymization_key =
NetworkAnonymizationKey(std::move(top_frame_site.value()),
std::move(frame_site), std::move(is_cross_site));
return true;
}

std::string NetworkAnonymizationKey::GetSiteDebugString(
const absl::optional<SchemefulSite>& site) const {
return site ? site->GetDebugString() : "null";
}

absl::optional<std::string> NetworkAnonymizationKey::SerializeSiteWithNonce(
const SchemefulSite& site) {
return *(const_cast<SchemefulSite&>(site).SerializeWithNonce());
}

} // namespace net
19 changes: 19 additions & 0 deletions net/base/network_anonymization_key.h
Expand Up @@ -15,6 +15,10 @@
#include "net/base/schemeful_site.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace base {
class Value;
}

namespace net {

// NetworkAnonymizationKey will be used to partition shared network state based
Expand Down Expand Up @@ -198,9 +202,24 @@ class NET_EXPORT NetworkAnonymizationKey {
// cross site from the top level site.
static bool IsCrossSiteFlagSchemeEnabled();

// Returns a representation of |this| as a base::Value. Returns false on
// failure. Succeeds if either IsEmpty() or !IsTransient().
[[nodiscard]] bool ToValue(base::Value* out_value) const;

// Inverse of ToValue(). Writes the result to |network_anonymization_key|.
// Returns false on failure. Fails on values that could not have been produced
// by ToValue(), like transient origins.
[[nodiscard]] static bool FromValue(
const base::Value& value,
NetworkAnonymizationKey* out_network_anonymization_key);

private:
std::string GetSiteDebugString(
const absl::optional<SchemefulSite>& site) const;

static absl::optional<std::string> SerializeSiteWithNonce(
const SchemefulSite& site);

// The origin/etld+1 of the top frame of the page making the request. This
// will always be populated unless all other fields are also nullopt.
absl::optional<SchemefulSite> top_frame_site_;
Expand Down
181 changes: 181 additions & 0 deletions net/base/network_anonymization_key_unittest.cc
Expand Up @@ -9,6 +9,7 @@
#include "base/values.h"
#include "net/base/features.h"
#include "net/base/schemeful_site.h"
#include "network_anonymization_key.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
Expand Down Expand Up @@ -498,4 +499,184 @@ TEST_P(NetworkAnonymizationKeyTest, Equality) {
EXPECT_TRUE(empty_key < key);
}

TEST_P(NetworkAnonymizationKeyTest, ValueRoundTripCrossSite) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
NetworkAnonymizationKey original_key(/*top_frame_site=*/kTestSiteA,
/*frame_site=*/kTestSiteB,
/*is_cross_site=*/true);
base::Value value;
ASSERT_TRUE(original_key.ToValue(&value));

// Fill initial value with opaque data, to make sure it's overwritten.
NetworkAnonymizationKey from_value_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(value, &from_value_key));
EXPECT_EQ(original_key, from_value_key);
}

TEST_P(NetworkAnonymizationKeyTest, ValueRoundTripSameSite) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
NetworkAnonymizationKey original_key(/*top_frame_site=*/kTestSiteA,
/*frame_site=*/kTestSiteA,
/*is_cross_site=*/false);
base::Value value;
ASSERT_TRUE(original_key.ToValue(&value));

// Fill initial value with opaque data, to make sure it's overwritten.
NetworkAnonymizationKey from_value_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(value, &from_value_key));
EXPECT_EQ(original_key, from_value_key);
}

TEST_P(NetworkAnonymizationKeyTest, ValueRoundTripOpaqueFrameSite) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
NetworkAnonymizationKey original_key(/*top_frame_site=*/kTestSiteA,
/*frame_site=*/kOpaqueSite,
/*is_cross_site=*/false);
base::Value value;
if (!NetworkAnonymizationKey::IsFrameSiteEnabled()) {
ASSERT_TRUE(original_key.ToValue(&value));
NetworkAnonymizationKey from_value_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(value, &from_value_key));
EXPECT_EQ(original_key, from_value_key);
} else {
// If we expect a valid frame site we should fail to serialize the garbage
// value.
ASSERT_FALSE(original_key.ToValue(&value));
}
}

TEST_P(NetworkAnonymizationKeyTest, DeserializeValueWIthGarbageFrameSite) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
base::Value::List invalid_value;
invalid_value.Append("http://a.test/");
invalid_value.Append("data:text/html,junk");

// If we expect a valid frame site we should fail to deserialize the garbage
// value.
if (NetworkAnonymizationKey::IsFrameSiteEnabled()) {
NetworkAnonymizationKey from_value_key = NetworkAnonymizationKey();
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(
base::Value(std::move(invalid_value)), &from_value_key));
}
}

TEST_P(NetworkAnonymizationKeyTest, TransientValueRoundTrip) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
NetworkAnonymizationKey original_key =
NetworkAnonymizationKey::CreateTransient();
base::Value value;
ASSERT_FALSE(original_key.ToValue(&value));
}

TEST_P(NetworkAnonymizationKeyTest, EmptyValueRoundTrip) {
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
NetworkAnonymizationKey original_key;
base::Value value;
ASSERT_TRUE(original_key.ToValue(&value));

// Fill initial value with opaque data, to make sure it's overwritten.
NetworkAnonymizationKey from_value_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(value, &from_value_key));
EXPECT_EQ(original_key, from_value_key);
}

TEST(NetworkAnonymizationKeyFeatureShiftTest,
ValueRoundTripKeySchemeMissmatch) {
base::test::ScopedFeatureList scoped_feature_list_;
const SchemefulSite kOpaqueSite = SchemefulSite(GURL("data:text/html,junk"));
const SchemefulSite kTestSiteA = SchemefulSite(GURL("http://a.test/"));
const SchemefulSite kTestSiteB = SchemefulSite(GURL("http://b.test/"));
NetworkAnonymizationKey expected_failure_nak = NetworkAnonymizationKey();

// Turn double keying off.
scoped_feature_list_.InitAndDisableFeature(
net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);

// Create a triple key.
NetworkAnonymizationKey original_triple_key(/*top_frame_site=*/kTestSiteA,
/*frame_site=*/kTestSiteB);

// Serialize key to value while triple keying is enabled.
base::Value triple_key_value;
ASSERT_TRUE(original_triple_key.ToValue(&triple_key_value));

// Convert it back to a triple keyed NetworkAnonymizationKey.
NetworkAnonymizationKey from_value_triple_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(triple_key_value,
&from_value_triple_key));
EXPECT_EQ(original_triple_key, from_value_triple_key);

// Turn double keying on.
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeature(
net::features::kEnableDoubleKeyNetworkAnonymizationKey);

// Check that deserializing the triple keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(triple_key_value,
&expected_failure_nak));

// Create a double keyed NetworkAnonymizationKey.
NetworkAnonymizationKey original_double_key(/*top_frame_site=*/kTestSiteA);
// Serialize key to value while double keying is enabled.
base::Value double_key_value;
ASSERT_TRUE(original_double_key.ToValue(&double_key_value));

// Convert it back to a double keyed NetworkAnonymizationKey.
NetworkAnonymizationKey from_value_double_key = NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(double_key_value,
&from_value_double_key));
EXPECT_EQ(original_double_key, from_value_double_key);

// Turn double keying + cross site flag on.
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeature(
net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);

// Check that deserializing the triple keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(triple_key_value,
&expected_failure_nak));

// Check that deserializing the double keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(double_key_value,
&expected_failure_nak));

// Create a cross site double key + cross site flag NetworkAnonymizationKey.
NetworkAnonymizationKey original_cross_site_double_key(
/*top_frame_site=*/kTestSiteA,
/*frame_site=*/kTestSiteB, false);
// Serialize key to value while double key + cross site flag is enabled.
base::Value cross_site_double_key_value;
ASSERT_TRUE(
original_cross_site_double_key.ToValue(&cross_site_double_key_value));

// Convert it back to a double keyed NetworkAnonymizationKey.
NetworkAnonymizationKey from_value_cross_site_double_key =
NetworkAnonymizationKey();
EXPECT_TRUE(NetworkAnonymizationKey::FromValue(
cross_site_double_key_value, &from_value_cross_site_double_key));
EXPECT_EQ(original_cross_site_double_key, from_value_cross_site_double_key);

// Turn double keying on.
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeature(
net::features::kEnableDoubleKeyNetworkAnonymizationKey);

// Check that deserializing the cross site double keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(cross_site_double_key_value,
&expected_failure_nak));

// Turn triple keying back on.
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndDisableFeature(
net::features::kEnableDoubleKeyNetworkAnonymizationKey);

// Check that deserializing the double keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(double_key_value,
&expected_failure_nak));

// Check that deserializing the cross site double keyed value fails.
EXPECT_FALSE(NetworkAnonymizationKey::FromValue(cross_site_double_key_value,
&expected_failure_nak));
}

} // namespace net
4 changes: 4 additions & 0 deletions net/base/schemeful_site.h
Expand Up @@ -150,6 +150,10 @@ class NET_EXPORT SchemefulSite {
// use opaque origins.
friend class NetworkIsolationKey;

// Needed to serialize opaque and non-transient NetworkAnonymizationKeys,
// which use opaque origins.
friend class NetworkAnonymizationKey;

// Needed to create a bogus origin from a site.
// TODO(https://crbug.com/1148927): Give IsolationInfos empty origins instead,
// in this case, and unfriend IsolationInfo.
Expand Down

0 comments on commit 62aeced

Please sign in to comment.