Skip to content

Commit

Permalink
Web Platform Tests for B&A auctions with k-anonymity enforcement
Browse files Browse the repository at this point in the history
Bug: 41495800
Change-Id: I24929462e13d0ac56dd3002dd94d1d99ccfeef39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6034458
Commit-Queue: Russ Hamilton <[email protected]>
Reviewed-by: Maks Orlovich <[email protected]>
Reviewed-by: Koji Ishii <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1386832}
  • Loading branch information
brusshamilton authored and chromium-wpt-export-bot committed Nov 22, 2024
1 parent d8bd5c4 commit 4057c74
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 11 deletions.
15 changes: 15 additions & 0 deletions fledge/tentative/get-interest-group-auction-data.https.window.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,18 @@ subsetTest(promise_test, async test => {
assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3].length, 1);
assert_equals(decoded.message.interestGroups[OTHER_ORIGIN3][0].name, 'o3');
}, 'getInterestGroupAdAuctionData() uses perBuyerConfig to select buyers');

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
await joinInterestGroup(test, uuid);

const result = await navigator.getInterestGroupAdAuctionData(
{seller: window.location.origin});
assert_true(result.requestId !== null);
assert_true(result.request.length > 0);

let decoded = await BA.decodeInterestGroupData(result.request);

assert_own_property(decoded.message, 'enforceKAnon');
assert_equals(decoded.message.enforceKAnon, true);
}, 'getInterestGroupAdAuctionData() requests k-anon.');
2 changes: 2 additions & 0 deletions fledge/tentative/resources/ba-fledge-util.sub.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ function _sortForCbor(input) {
out[i] = _sortForCbor(input[i]);
}
return out;
} else if (input instanceof Uint8Array) {
return input;
} else {
let keys = Object.getOwnPropertyNames(input).sort((a, b) => {
// CBOR order compares lengths before values.
Expand Down
328 changes: 317 additions & 11 deletions fledge/tentative/server-response.https.window.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// META: variant=?45-48
// META: variant=?49-52
// META: variant=?53-56
// META: variant=?57-60
// META: variant=?61-64

// These tests focus on the serverResponse field in AuctionConfig, e.g.
// auctions involving bidding and auction services.
Expand Down Expand Up @@ -425,8 +427,8 @@ subsetTest(promise_test, async test => {

// Runs responseMutator on a minimal correct server response, and expects
// either success/failure based on expectWin.
async function testWithMutatedServerResponse(test, expectWin, responseMutator,
igMutator = undefined) {
async function testWithMutatedServerResponse(
test, expectWin, responseMutator, igMutator = undefined) {
const uuid = generateUuid(test);
const adA = createTrackerURL(window.location.origin, uuid, 'track_get', 'a');
const adB = createTrackerURL(window.location.origin, uuid, 'track_get', 'b');
Expand Down Expand Up @@ -648,11 +650,14 @@ subsetTest(promise_test, async test => {
}, 'Basic B&A auction - ad component URL not in ad');

subsetTest(promise_test, async test => {
await testWithMutatedServerResponse(test, /*expectSuccess=*/ true, msg => {
msg.components = ['https://example.org'];
}, ig => {
ig.adComponents = [{renderURL: 'https://example.org/'}];
});
await testWithMutatedServerResponse(
test, /*expectSuccess=*/ true,
msg => {
msg.components = ['https://example.org'];
},
ig => {
ig.adComponents = [{renderURL: 'https://example.org/'}];
});
}, 'Basic B&A auction - ad component URL in ad');

subsetTest(promise_test, async test => {
Expand Down Expand Up @@ -1040,9 +1045,8 @@ subsetTest(promise_test, async test => {

subsetTest(promise_test, async test => {
await testWithMutatedServerResponse(test, /*expectSuccess=*/ true, msg => {
msg.updateGroups = {
[window.location.origin]: [
{index: 2048, updateIfOlderThanMs: 1000}]};
msg.updateGroups =
{[window.location.origin]: [{index: 2048, updateIfOlderThanMs: 1000}]};
});
}, 'Basic B&A auction - updateIfOlderThanMs - invalid index');

Expand All @@ -1052,10 +1056,312 @@ subsetTest(promise_test, async test => {
msg.updateGroups = {
[window.location.origin]: [
{index: 0, updateIfOlderThanMs: 1000},
{index: 1, updateIfOlderThanMs: 10000}]};
{index: 1, updateIfOlderThanMs: 10000}
]
};
});
}, 'Basic B&A auction - updateIfOlderThanMs');

/////////////////////////////////////////////////////////////////////////////
//
// K-anonymity support tests
//
/////////////////////////////////////////////////////////////////////////////

// Runs responseMutator on a minimal correct server response, and expects
// either success/failure based on expectWin.
async function kAnonTestWithMutatedServerResponse(
test, expectWin, responseMutator, igMutator = undefined) {
const uuid = generateUuid(test);
const adA = createTrackerURL(window.location.origin, uuid, 'track_get', 'a');
const adB = createTrackerURL(window.location.origin, uuid, 'track_get', 'b');
const adsArray =
[{renderURL: adA, adRenderId: 'a'}, {renderURL: adB, adRenderId: 'b'}];
let ig = {
owner: window.location.origin,
name: DEFAULT_INTEREST_GROUP_NAME,
ads: adsArray,
biddingLogicURL: createBiddingScriptURL({allowComponentAuction: true})
};
if (igMutator) {
igMutator(ig, uuid);
}

const encoder = new TextEncoder();
const adARenderKAnonKey =
encoder.encode(`AdBid\n${ig.owner}/\n${ig.biddingLogicURL}\n${adA}`);
const adBRenderKAnonKey =
encoder.encode(`AdBid\n${ig.owner}/\n${ig.biddingLogicURL}\n${adB}`);
const adARenderKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adARenderKAnonKey));
const adBRenderKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adBRenderKAnonKey));

const adANameReportingIdKAnonKey = encoder.encode(
`NameReport\n${ig.owner}/\n${ig.biddingLogicURL}\n${adA}\n${ig.name}`);
const adBNameReportingIdKAnonKey = encoder.encode(
`NameReport\n${ig.owner}/\n${ig.biddingLogicURL}\n${adB}\n${ig.name}`);
const adANameReportingIdKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adANameReportingIdKAnonKey));
const adBNameReportingIdKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adBNameReportingIdKAnonKey));

const adABuyerReportingIdKAnonKey = encoder.encode(`BuyerReportId\n${
ig.owner}/\n${ig.biddingLogicURL}\n${adA}\n${adA.buyerReportingId}`);
const adBBuyerReportingIdKAnonKey = encoder.encode(`BuyerReportId\n${
ig.owner}/\n${ig.biddingLogicURL}\n${adB}\n${adB.buyerReportingId}`);
const adABuyerReportingIdKAnonKeyHash =
new Uint8Array(await window.crypto.subtle.digest(
'SHA-256', adABuyerReportingIdKAnonKey));
const adBBuyerReportingIdKAnonKeyHash =
new Uint8Array(await window.crypto.subtle.digest(
'SHA-256', adBBuyerReportingIdKAnonKey));

const adABASReportingIdKAnonKey =
encoder.encode(`BuyerAndSellerReportId\n${ig.owner}/\n${
ig.biddingLogicURL}\n${adA}\n${adA.buyerAndSellerReportingId}`);
const adBBASReportingIdKAnonKey =
encoder.encode(`BuyerAndSellerReportId\n${ig.owner}/\n${
ig.biddingLogicURL}\n${adB}\n${adB.buyerAndSellerReportingId}`);
const adABASReportingIdKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adABASReportingIdKAnonKey));
const adBBASReportingIdKAnonKeyHash = new Uint8Array(
await window.crypto.subtle.digest('SHA-256', adBBASReportingIdKAnonKey));

const hashes = {
adARenderKAnonKeyHash: adARenderKAnonKeyHash,
adBRenderKAnonKeyHash: adBRenderKAnonKeyHash,
adANameReportingIdKAnonKeyHash: adANameReportingIdKAnonKeyHash,
adBNameReportingIdKAnonKeyHash: adBNameReportingIdKAnonKeyHash,
adABuyerReportingIdKAnonKeyHash: adABuyerReportingIdKAnonKeyHash,
adBBuyerReportingIdKAnonKeyHash: adBBuyerReportingIdKAnonKeyHash,
adABASReportingIdKAnonKeyHash: adABASReportingIdKAnonKeyHash,
adBBASReportingIdKAnonKeyHash: adBBASReportingIdKAnonKeyHash
};

await joinInterestGroup(test, uuid, ig);

const result = await navigator.getInterestGroupAdAuctionData(
{seller: window.location.origin});
assert_true(result.requestId !== null);
assert_true(result.request.length > 0);

let decoded = await BA.decodeInterestGroupData(result.request);

let serverResponseMsg = {
'biddingGroups': {},
'adRenderURL': ig.ads[0].renderURL,
'interestGroupName': DEFAULT_INTEREST_GROUP_NAME,
'interestGroupOwner': window.location.origin,
};
serverResponseMsg.biddingGroups[window.location.origin] = [0];

await responseMutator(serverResponseMsg, ig, hashes, uuid);

let serverResponse =
await BA.encodeServerResponse(serverResponseMsg, decoded);

let hashString = await BA.payloadHash(serverResponse);
await BA.authorizeServerResponseHashes([hashString]);

let auctionResult = await navigator.runAdAuction({
'seller': window.location.origin,
'interestGroupBuyers': [window.location.origin],
'requestId': result.requestId,
'serverResponse': serverResponse,
'resolveToConfig': true,
});
if (expectWin) {
expectSuccess(auctionResult);
return auctionResult;
} else {
expectNoWinner(auctionResult);
}
}

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ true, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
};
});
}, 'Basic B&A auction - winner with candidates');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: new Uint8Array(),
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
};
});
}, 'Basic B&A auction - winner with bad render hash');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: new Uint8Array(),
};
});
}, 'Basic B&A auction - winner with bad reporting hash');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
delete msg.adRenderURL;
delete msg.interestGroupName;
delete msg.interestGroupOwner;
msg.kAnonGhostWinners = [{
kAnonJoinCandidates: {
// missing adRenderURLHash
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
}]
});
}, 'Basic B&A auction - invalid ghost winner');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
delete msg.adRenderURL;
delete msg.interestGroupName;
delete msg.interestGroupOwner;
msg.kAnonGhostWinners = [{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
}]
});
}, 'Basic B&A auction - only ghost winner');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
delete msg.adRenderURL;
delete msg.interestGroupName;
delete msg.interestGroupOwner;
msg.kAnonGhostWinners = [
{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
},
{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adBRenderKAnonKeyHash,
reportingIdHash: hashes.adBNameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
}
]
});
}, 'Basic B&A auction - multiple ghost winners');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ false, (msg, ig, hashes) => {
delete msg.adRenderURL;
delete msg.interestGroupName;
delete msg.interestGroupOwner;
msg.kAnonGhostWinners = [
{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
},
{
kAnonJoinCandidates: {
// missing adRenderURLHash
reportingIdHash: hashes.adBNameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
}
]
});
}, 'Basic B&A auction - second ghost winner invalid');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ true, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
};
msg.kAnonGhostWinners = [{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adBRenderKAnonKeyHash,
reportingIdHash: hashes.adBNameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
}];
});
}, 'Basic B&A auction - winner with ghost winner');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ true, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
};
msg.kAnonGhostWinners = [{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adBRenderKAnonKeyHash,
reportingIdHash: hashes.adBNameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
ghostWinnerForTopLevelAuction: {
// missing adRenderURL
modifiedBid: 100,
},
}];
});
}, 'Basic B&A auction - invalid GhostWinnerForTopLevelAuction');

subsetTest(promise_test, async test => {
await kAnonTestWithMutatedServerResponse(
test, /*expectSuccess=*/ true, (msg, ig, hashes) => {
msg.kAnonWinnerJoinCandidates = {
adRenderURLHash: hashes.adARenderKAnonKeyHash,
reportingIdHash: hashes.adANameReportingIdKAnonKeyHash,
};
msg.kAnonGhostWinners = [{
kAnonJoinCandidates: {
adRenderURLHash: hashes.adBRenderKAnonKeyHash,
reportingIdHash: hashes.adBNameReportingIdKAnonKeyHash,
},
interestGroupIndex: 0,
owner: window.location.origin,
ghostWinnerForTopLevelAuction: {
adRenderURL: ig.ads[1].renderURL,
modifiedBid: 100,
},
}];
});
}, 'Basic B&A auction - winner with full ghost winner');

// TODO(behamilton): Add Multi-seller k-anon tests.
// TODO(behamilton): Add k-anon tests with different reporting IDs.

/* Some things that are not currently tested that probably should be; this is
not exhaustive, merely to keep track of things that come to mind as tests are
written:
Expand Down

0 comments on commit 4057c74

Please sign in to comment.