Skip to content

Commit

Permalink
Add support for Ad-Auction-Result-Nonce header for PA B&A
Browse files Browse the repository at this point in the history
Add support for the alternate authorization flow for Protected
Audiences Bidding and Auction response (as described in
WICG/turtledove#1233).

This feature is behind the FledgeBiddingAndAuctionNonceSupport
feature flag which is going to be enabled by default (for a
waterfall rollout in M133).

Bug: 385128725
Change-Id: Id3c622241c82ed0b71037bfeb1ca5432cd6e66dc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6109795
Reviewed-by: Maks Orlovich <morlovich@chromium.org>
Commit-Queue: Russ Hamilton <behamilton@google.com>
Reviewed-by: Brendon Tiszka <tiszka@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1403077}
  • Loading branch information
brusshamilton authored and Chromium LUCI CQ committed Jan 7, 2025
1 parent 27c7fe6 commit 12855e2
Show file tree
Hide file tree
Showing 65 changed files with 430 additions and 70 deletions.
36 changes: 36 additions & 0 deletions content/browser/interest_group/ad_auction_headers_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace content {

const char kAdAuctionRequestHeaderKey[] = "Sec-Ad-Auction-Fetch";
const char kAdAuctionResultResponseHeaderKey[] = "Ad-Auction-Result";
const char kAdAuctionResultNonceResponseHeaderKey[] = "Ad-Auction-Result-Nonce";
const char kAdAuctionSignalsResponseHeaderKey[] = "Ad-Auction-Signals";
const char kAdAuctionAdditionalBidResponseHeaderKey[] =
"Ad-Auction-Additional-Bid";
Expand Down Expand Up @@ -198,6 +199,26 @@ std::vector<std::string> ParseAdAuctionResultResponseHeader(
return parsed_results;
}

// Please note: before modifying this function, please acknowledge this is
// processing untrusted content from a non sandboxed process. So please keep
// this function simple and avoid adding custom logic.
//
// Fuzzer: ad_auction_headers_util_fuzzer
std::vector<std::string> ParseAdAuctionResultNonceResponseHeader(
const std::string& ad_auction_result_nonces) {
std::vector<std::string> parsed_results;
for (const auto& result :
base::SplitString(ad_auction_result_nonces, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
base::Uuid result_uuid = base::Uuid::ParseCaseInsensitive(result);
if (!result_uuid.is_valid()) {
continue;
}
parsed_results.emplace_back(result_uuid.AsLowercaseString());
}
return parsed_results;
}

// Please note: before modifying this function, please acknowledge this is
// processing untrusted content from a non sandboxed process. So please keep
// this function simple and avoid adding custom logic.
Expand Down Expand Up @@ -319,6 +340,20 @@ void ProcessAdAuctionResponseHeaders(
}
// We intentionally leave the `Ad-Auction-Result` response header in place.

if (base::FeatureList::IsEnabled(
features::kFledgeBiddingAndAuctionNonceSupport)) {
if (std::optional<std::string> ad_auction_results =
headers->GetNormalizedHeader(
kAdAuctionResultNonceResponseHeaderKey)) {
for (const std::string& nonce :
ParseAdAuctionResultNonceResponseHeader(*ad_auction_results)) {
ad_auction_page_data->AddAuctionResultNonceWitnessForOrigin(
request_origin, nonce);
}
}
}
headers->RemoveHeader(kAdAuctionResultNonceResponseHeaderKey);

if (base::FeatureList::IsEnabled(blink::features::kAdAuctionSignals)) {
if (std::optional<std::string> ad_auction_signals =
headers->GetNormalizedHeader(kAdAuctionSignalsResponseHeaderKey)) {
Expand Down Expand Up @@ -360,6 +395,7 @@ void RemoveAdAuctionResponseHeaders(
return;
}
// We intentionally leave the `Ad-Auction-Result` response header in place.
headers->RemoveHeader(kAdAuctionResultNonceResponseHeaderKey);
headers->RemoveHeader(kAdAuctionSignalsResponseHeaderKey);
headers->RemoveHeader(kAdAuctionAdditionalBidResponseHeaderKey);
}
Expand Down
12 changes: 11 additions & 1 deletion content/browser/interest_group/ad_auction_headers_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ enum class AdAuctionHeadersIsEligibleOutcomeForMetrics {
// signals, and additional bids from their associated response headers.
extern const char kAdAuctionRequestHeaderKey[];

// Response header keys associated with auction result, signals, and
// Response header keys associated with auction result, nonce, signals, and
// additional bids, respectively.
extern const char CONTENT_EXPORT kAdAuctionResultResponseHeaderKey[];
extern const char CONTENT_EXPORT kAdAuctionResultNonceResponseHeaderKey[];
extern const char CONTENT_EXPORT kAdAuctionSignalsResponseHeaderKey[];
extern const char CONTENT_EXPORT kAdAuctionAdditionalBidResponseHeaderKey[];

Expand Down Expand Up @@ -72,6 +73,15 @@ CONTENT_EXPORT bool IsAdAuctionHeadersEligibleForNavigation(
CONTENT_EXPORT std::vector<std::string> ParseAdAuctionResultResponseHeader(
const std::string& ad_auction_results);

// NOTE: Exposed only for fuzz testing. This is used by
// `ProcessAdAuctionResponseHeaders`, declared below.
//
// Splits and parses the `Ad-Auction-Result-Nonce` response header,
// and returns the results. This function processes untrusted content, in an
// unsafe language, from an unsandboxed process, hence the fuzz test coverage.
CONTENT_EXPORT std::vector<std::string> ParseAdAuctionResultNonceResponseHeader(
const std::string& ad_auction_result_nonces);

// NOTE: Exposed only for fuzz testing. This is used by
// `ProcessAdAuctionResponseHeaders`, declared below.
//
Expand Down
101 changes: 95 additions & 6 deletions content/browser/interest_group/ad_auction_headers_util_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ namespace {

constexpr char kLegitimateAdAuctionResponse[] =
"ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0=";
constexpr char kLegitimateAdAuctionNonceResponse[] =
"00000000-0000-0000-0000-000000000000";
constexpr char kLegitimateAdAuctionSignals[] =
R"([{"adSlot":"slot1", "sellerSignals":{"signal1":"value1"}}])";

Expand Down Expand Up @@ -357,6 +359,17 @@ class ProcessAdAuctionResponseHeadersTest : public RenderViewHostTestHarness {
response);
}

bool WitnessedAuctionResultNonceForOrigin(const url::Origin& origin,
const std::string& nonce) {
Page& page = web_contents()->GetPrimaryPage();

AdAuctionPageData* ad_auction_page_data =
PageUserData<AdAuctionPageData>::GetOrCreateForPage(page);

return ad_auction_page_data->WitnessedAuctionResultNonceForOrigin(origin,
nonce);
}

const scoped_refptr<HeaderDirectFromSellerSignals::Result>
ParseAndFindAdAuctionSignals(const url::Origin& origin,
const std::string& ad_slot) {
Expand Down Expand Up @@ -414,15 +427,15 @@ TEST_F(ProcessAdAuctionResponseHeadersTest,
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey, "alsoInvalid");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();

// Unlike the signals and additional bid headers, the result header is not
// removed when it's stored in the browser.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));

ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);

// Unlike the signals and additional bid headers, the result header is not
// removed when it's stored in the browser.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));

EXPECT_TRUE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
Base64UrlDecode(kLegitimateAdAuctionResponse)));
Expand All @@ -443,16 +456,89 @@ TEST_F(ProcessAdAuctionResponseHeadersTest,
"invalid-response-header");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();

ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);

// Unlike the signals and additional bid headers, the result header is not
// removed when it's stored in the browser.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));

EXPECT_FALSE(WitnessedAuctionResultForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
"invalid-response-header"));
}

class ProcessAdAuctionNonceResponseHeadersTest
: public ProcessAdAuctionResponseHeadersTest {
public:
ProcessAdAuctionNonceResponseHeadersTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kFledgeBiddingAndAuctionNonceSupport);
}

private:
base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(ProcessAdAuctionNonceResponseHeadersTest,
MultipleAdAuctionResultNonces_AllWitnessed) {
const char kLegitimateAdAuctionNonceResponse2[] =
"1000000A-0000-0000-0000-000000000000";
const char kLegitimateAdAuctionNonceResponse3[] =
"2000000b-0000-0000-0000-000000000000";

net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
kLegitimateAdAuctionNonceResponse);
std::string concatenated_header =
base::StrCat({kLegitimateAdAuctionNonceResponse2, ",", "invalid", ",",
kLegitimateAdAuctionNonceResponse3});
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
concatenated_header);
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
"alsoInvalid");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();

ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);

EXPECT_FALSE(WitnessedAuctionResultForOrigin(
// The result nonce header is removed when it's stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));

EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
kLegitimateAdAuctionNonceResponse));

EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
// lower-case version of kLegitimateAdAuctionNonceResponse2
"1000000a-0000-0000-0000-000000000000"));

EXPECT_TRUE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
kLegitimateAdAuctionNonceResponse3));
}

TEST_F(ProcessAdAuctionNonceResponseHeadersTest,
InvalidAdAuctionResultNonceResponseHeader) {
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
"invalid-response-header");
scoped_refptr<net::HttpResponseHeaders> headers = headers_builder.Build();

ProcessAdAuctionResponseHeaders(
url::Origin::Create(GURL("https://foo1.com")),
*static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame()),
headers);

// The result header is not removed when it's stored in the browser.
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));

EXPECT_FALSE(WitnessedAuctionResultNonceForOrigin(
url::Origin::Create(GURL("https://foo1.com")),
"invalid-response-header"));
}
Expand Down Expand Up @@ -849,6 +935,8 @@ TEST(RemoveAdAuctionResponseHeadersTest,
net::HttpResponseHeaders::Builder headers_builder({1, 1}, "200 OK");
headers_builder.AddHeader(kAdAuctionResultResponseHeaderKey,
kLegitimateAdAuctionResponse);
headers_builder.AddHeader(kAdAuctionResultNonceResponseHeaderKey,
kLegitimateAdAuctionNonceResponse);
headers_builder.AddHeader(kAdAuctionSignalsResponseHeaderKey,
R"([{"adSlot":"slot1"}])");
headers_builder.AddHeader(kAdAuctionAdditionalBidResponseHeaderKey,
Expand All @@ -857,8 +945,9 @@ TEST(RemoveAdAuctionResponseHeadersTest,

RemoveAdAuctionResponseHeaders(headers);

// Only the signals and additional bid headers are removed.
// Only the nonce, signals and additional bid headers are removed.
EXPECT_TRUE(headers->HasHeader(kAdAuctionResultResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionResultNonceResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionSignalsResponseHeaderKey));
EXPECT_FALSE(headers->HasHeader(kAdAuctionAdditionalBidResponseHeaderKey));
}
Expand Down
17 changes: 17 additions & 0 deletions content/browser/interest_group/ad_auction_page_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ bool AdAuctionPageData::WitnessedAuctionResultForOrigin(
return it->second.contains(response);
}

void AdAuctionPageData::AddAuctionResultNonceWitnessForOrigin(
const url::Origin& origin,
const std::string& nonce) {
origin_auction_result_nonce_map_[origin].insert(nonce);
}

bool AdAuctionPageData::WitnessedAuctionResultNonceForOrigin(
const url::Origin& origin,
const std::string& nonce) const {
auto it = origin_auction_result_nonce_map_.find(origin);
if (it == origin_auction_result_nonce_map_.end()) {
return false;
}

return it->second.contains(nonce);
}

void AdAuctionPageData::AddAuctionSignalsWitnessForOrigin(
const url::Origin& origin,
const std::string& response) {
Expand Down
7 changes: 7 additions & 0 deletions content/browser/interest_group/ad_auction_page_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ class CONTENT_EXPORT AdAuctionPageData
bool WitnessedAuctionResultForOrigin(const url::Origin& origin,
const std::string& response) const;

void AddAuctionResultNonceWitnessForOrigin(const url::Origin& origin,
const std::string& nonce);

bool WitnessedAuctionResultNonceForOrigin(const url::Origin& origin,
const std::string& nonce) const;

void AddAuctionSignalsWitnessForOrigin(const url::Origin& origin,
const std::string& response);

Expand Down Expand Up @@ -136,6 +142,7 @@ class CONTENT_EXPORT AdAuctionPageData
std::vector<std::string> errors);

std::map<url::Origin, std::set<std::string>> origin_auction_result_map_;
std::map<url::Origin, std::set<std::string>> origin_auction_result_nonce_map_;
HeaderDirectFromSellerSignals header_direct_from_seller_signals_;
std::map<url::Origin,
std::map<std::string, std::vector<SignedAdditionalBidWithMetadata>>>
Expand Down
13 changes: 4 additions & 9 deletions content/browser/interest_group/auction_runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,14 @@ void AuctionRunner::ResolvedAuctionAdResponsePromise(
return;
}
config->server_response->got_response = true;
AdAuctionPageData* page_data = ad_auction_page_data_callback_.Run();
if (!page_data) {
// There's no page data attached so we can't decode the response. There's
// no way the auction can proceed.
FailAuction(false);
return;
}

if (auction_id->is_main_auction()) {
auction_.HandleServerResponse(std::move(response), *page_data);
auction_.HandleServerResponse(std::move(response),
ad_auction_page_data_callback_);
} else {
auction_.HandleComponentServerResponse(auction_id->get_component_auction(),
std::move(response), *page_data);
std::move(response),
ad_auction_page_data_callback_);
}
}

Expand Down
12 changes: 12 additions & 0 deletions content/browser/interest_group/bidding_and_auction_response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "base/containers/adapters.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/uuid.h"
#include "base/values.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_pa_report_util.h"
Expand Down Expand Up @@ -105,6 +106,17 @@ std::optional<BiddingAndAuctionResponse> BiddingAndAuctionResponse::TryParse(
return std::nullopt;
}

if (base::FeatureList::IsEnabled(
features::kFledgeBiddingAndAuctionNonceSupport)) {
const std::string* nonce = input_dict->FindString("nonce");
if (nonce) {
base::Uuid nonce_uuid = base::Uuid::ParseCaseInsensitive(*nonce);
if (nonce_uuid.is_valid()) {
output.nonce = nonce_uuid.AsLowercaseString();
}
}
}

base::Value::Dict* error_struct = input_dict->FindDict("error");
if (error_struct) {
std::string* message = error_struct->FindString("message");
Expand Down
4 changes: 4 additions & 0 deletions content/browser/interest_group/bidding_and_auction_response.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ struct CONTENT_EXPORT BiddingAndAuctionResponse {
// auctions start the bidding phase.
AuctionResult result = AuctionResult::kInvalidServerResponse;

// Nonce used to match against the Ad-Auction-Result-Nonce header in a fetch
// response from the seller.
std::optional<std::string> nonce;

bool is_chaff = false; // indicates this response should be ignored.
// TODO(behamilton): Add support for creative dimensions to the response from
// the Bidding and Auction server.
Expand Down
Loading

0 comments on commit 12855e2

Please sign in to comment.