tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

server-response-private-aggregation.https.window.js (8266B)


      1 // META: script=/resources/testdriver.js
      2 // META: script=/resources/testdriver-vendor.js
      3 // META: script=/common/get-host-info.sub.js
      4 // META: script=/common/utils.js
      5 // META: script=resources/ba-fledge-util.sub.js
      6 // META: script=resources/fledge-util.sub.js
      7 // META: script=third_party/cbor-js/cbor.js
      8 // META: script=/common/subset-tests.js
      9 // META: timeout=long
     10 // META: variant=?1-6
     11 // META: variant=?7-last
     12 
     13 "use strict";
     14 
     15 // These tests focus on the paggResponse field in AuctionConfig's
     16 // serverResponse, i.e. auctions involving private aggregation reporting. NOTE:
     17 // Due to debug mode being disabled for B&A's Private Aggregation reports, these
     18 // tests just exercise the code paths and ensure that correct number of reports
     19 // are sent -- they don't otherwise verify report content.
     20 
     21 // To better isolate from private aggregation tests run in parallel,
     22 // don't use the usual origin here.
     23 const MAIN_ORIGIN = OTHER_ORIGIN1;
     24 const MAIN_PATH = '/.well-known/private-aggregation/report-protected-audience';
     25 
     26 const BUCKET_ONE = new Uint8Array([
     27  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     28  0x00, 0x00, 0x01
     29 ]);
     30 
     31 function BigEndianInteger128ToUint8Array(val) {
     32  let buffer = new Uint8Array(16);
     33  for (let i = 15; i >= 0; i--) {
     34    buffer[i] = Number(val & 0xFFn);
     35    val >>= 8n;
     36  }
     37  return buffer;
     38 }
     39 
     40 function createSimplePerOriginPAggResponse(
     41    reportingOrigin = MAIN_ORIGIN, igIndex = 0, event = 'reserved.win',
     42    bucket = BUCKET_ONE, value = 10, filteringId = null) {
     43  let contribution = {};
     44  if (bucket !== null) {
     45    contribution.bucket = bucket;
     46  }
     47  if (value !== null) {
     48    contribution.value = value;
     49  }
     50  if (filteringId !== null) {
     51    contribution.filteringId = filteringId;
     52  }
     53  return {
     54    'reportingOrigin': reportingOrigin,
     55    'igContributions': [{
     56      'igIndex': igIndex,
     57      'eventContributions': [{'event': event, 'contributions': [contribution]}]
     58    }]
     59  };
     60 }
     61 
     62 async function privateAggregationTestWithMutatedServerResponse(
     63    test, expectWin, paggResponse, timeout = 5000 /*ms*/,
     64    ownerOverride = null) {
     65  await resetReports(MAIN_ORIGIN + MAIN_PATH);
     66  let result = await BA.testWithMutatedServerResponse(
     67      test, expectWin,
     68      (msg, uuid) => {
     69        msg.paggResponse = paggResponse;
     70      },
     71      (ig, uuid) => {
     72        ig.ads[0].renderURL = createRenderURL(uuid);
     73      },
     74      ownerOverride);
     75  createAndNavigateFencedFrame(test, result);
     76  const reports = await pollReports(MAIN_PATH, timeout);
     77  return reports;
     78 }
     79 
     80 async function testInvalidPAggResponseFields(
     81    test, reportingOrigin = MAIN_ORIGIN, igIndex = 0, event = 'reserved.win',
     82    bucket = '1', value = 10, filteringId = null) {
     83  const paggResponse = [createSimplePerOriginPAggResponse(
     84      reportingOrigin, igIndex, event, bucket, value, filteringId)];
     85 
     86  let reports = await privateAggregationTestWithMutatedServerResponse(
     87      test,
     88      /*expectWin=*/ true, paggResponse, /*timeout=*/ 5000, MAIN_ORIGIN);
     89  assert_equals(reports, null);
     90 }
     91 
     92 // The next few methods are modified from Chrome-specific
     93 // wpt_internal/private-aggregation/resources/utils.js
     94 
     95 const resetReports = url => {
     96  url = `${url}?clear_stash=true`;
     97  const options = {
     98    method: 'POST',
     99    mode: 'no-cors',
    100  };
    101  return fetch(url, options);
    102 };
    103 
    104 const delay = ms => new Promise(resolve => step_timeout(resolve, ms));
    105 
    106 async function pollReports(path, wait_for = 1, timeout = 5000 /*ms*/) {
    107  const targetUrl = new URL(path, MAIN_ORIGIN);
    108  const endTime = performance.now() + timeout;
    109  const outReports = [];
    110 
    111  do {
    112    const response = await fetch(targetUrl);
    113    assert_true(response.ok, 'pollReports() fetch response should be OK.');
    114    const reports = await response.json();
    115    outReports.push(...reports);
    116    if (outReports.length >= wait_for) {
    117      break;
    118    }
    119    await delay(/*ms=*/ 100);
    120  } while (performance.now() < endTime);
    121 
    122  return outReports.length ? outReports : null;
    123 };
    124 
    125 /**
    126 * Verifies that a report's aggregation_service_payloads has the expected
    127 * fields. Currently for B&A's PAgg reports, debug mode is disabled, so we
    128 * cannot check contributions in payload.
    129 */
    130 const verifyAggregationServicePayloads = (aggregation_service_payloads) => {
    131  assert_equals(aggregation_service_payloads.length, 1);
    132  const payload_obj = aggregation_service_payloads[0];
    133 
    134  assert_own_property(payload_obj, 'key_id');
    135  assert_own_property(payload_obj, 'payload');
    136  // Check the payload is base64 encoded. We do not decrypt the payload to
    137  // test its contents.
    138  atob(payload_obj.payload);
    139 
    140  // Check there are no extra keys
    141  assert_equals(Object.keys(payload_obj).length, expected_payload ? 3 : 2);
    142 };
    143 
    144 /**
    145 * Verifies that a report has the expected fields. The `expected_payload` should
    146 * be undefined.
    147 */
    148 const verifyReport = (report, reporting_origin) => {
    149  assert_own_property(report, 'shared_info');
    150  let shared_info = JSON.parse(report.shared_info);
    151  assert_own_property(shared_info, 'reporting_origin');
    152  assert_equals(shared_info.reporting_origin, reporting_origin);
    153  assert_own_property(report, 'aggregation_service_payloads');
    154  assert_own_property(report, 'aggregation_coordinator_origin');
    155  // TODO(qingxinwu): Maybe add tests for coordinator origin.
    156 
    157  assert_not_own_property(report, 'debug_key');
    158 
    159  // Check there are no extra keys
    160  let expected_length = 3;
    161  assert_equals(Object.keys(report).length, expected_length);
    162 };
    163 
    164 subsetTest(promise_test, async test => {
    165  await testInvalidPAggResponseFields(test, 'http://non-https.com');
    166 }, 'Private aggregation - invalid reporting origin');
    167 
    168 subsetTest(
    169    promise_test,
    170    async test => {await testInvalidPAggResponseFields(test, MAIN_ORIGIN, 100)},
    171    'Private aggregation - invalid index');
    172 
    173 subsetTest(promise_test, async test => {
    174  await testInvalidPAggResponseFields(
    175      test, MAIN_ORIGIN, 0, 'reserved.not-supported');
    176 }, 'Private aggregation - invalid event');
    177 
    178 subsetTest(promise_test, async test => {
    179  await testInvalidPAggResponseFields(
    180      test, MAIN_ORIGIN, 0, 'reserved.win', /*bucket=*/ null);
    181 }, 'Private aggregation - missing required bucket');
    182 
    183 subsetTest(promise_test, async test => {
    184  await testInvalidPAggResponseFields(
    185      test, MAIN_ORIGIN, 0, 'reserved.win', new Uint8Array([
    186        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    187        0x00, 0x00, 0x00, 0x00, 0x01
    188      ]));
    189 }, 'Private aggregation - bucket is bigger than 128 bits');
    190 
    191 subsetTest(promise_test, async test => {
    192  await testInvalidPAggResponseFields(
    193      test, MAIN_ORIGIN, 0, 'reserved.win', BUCKET_ONE, /*value=*/ null);
    194 }, 'Private aggregation - missing required value');
    195 
    196 subsetTest(promise_test, async test => {
    197  await testInvalidPAggResponseFields(
    198      test, MAIN_ORIGIN, 0, 'reserved.win', BUCKET_ONE, 10, 10000);
    199 }, 'Private aggregation - invalid filteringId');
    200 
    201 subsetTest(promise_test, async test => {
    202  const paggResponse = [createSimplePerOriginPAggResponse()];
    203 
    204  let reports = await privateAggregationTestWithMutatedServerResponse(
    205      test,
    206      /*expectWin=*/ true, paggResponse, /*timeout=*/ 6000, MAIN_ORIGIN);
    207  assert_equals(reports.length, 1);
    208  let report = JSON.parse(reports[0]);
    209  verifyReport(report, MAIN_ORIGIN);
    210 }, 'Private aggregation - successfully sent report');
    211 
    212 // TODO(qingxinwu): may add a test for custom event type if possible.
    213 
    214 subsetTest(promise_test, async test => {
    215  const paggResponse = [{
    216    'reportingOrigin': MAIN_ORIGIN,
    217    'igContributions': [{
    218      'igIndex': 0,
    219      'eventContributions': [
    220        {
    221          'event': 'reserved.win',
    222          'contributions': [{'value': 10}, {'bucket': BUCKET_ONE, 'value': 11}]
    223        },
    224        {
    225          'event': 'reserved.not-supported',
    226          'contributions':
    227              [{'bucket': BigEndianInteger128ToUint8Array(2n), 'value': 22}]
    228        },
    229      ]
    230    }]
    231  }];
    232 
    233  let reports = await privateAggregationTestWithMutatedServerResponse(
    234      test,
    235      /*expectWin=*/ true, paggResponse, /*timeout=*/ 6000, MAIN_ORIGIN);
    236  assert_equals(reports.length, 1);
    237  let report = JSON.parse(reports[0]);
    238  verifyReport(report, MAIN_ORIGIN);
    239 }, 'Private aggregation - invalid contributions do not affect valid ones');
    240 
    241 // TODO(qingxinwu): privateAggregation multi-seller.