summaryrefslogtreecommitdiffstats
path: root/toolkit/components/resistfingerprinting/tests/browser/browser_fingerprinting_randomization_key.js
blob: f2d037491d90eb8da6618ffe9a42ca83b39478e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
let { ForgetAboutSite } = ChromeUtils.importESModule(
  "resource://gre/modules/ForgetAboutSite.sys.mjs"
);

requestLongerTimeout(2);

const TEST_DOMAIN = "https://example.com";
const TEST_DOMAIN_ANOTHER = "https://example.org";
const TEST_DOMAIN_THIRD = "https://example.net";

const TEST_PAGE =
  getRootDirectory(gTestPath).replace(
    "chrome://mochitests/content",
    TEST_DOMAIN
  ) + "testPage.html";
const TEST_DOMAIN_ANOTHER_PAGE =
  getRootDirectory(gTestPath).replace(
    "chrome://mochitests/content",
    TEST_DOMAIN_ANOTHER
  ) + "testPage.html";
const TEST_DOMAIN_THIRD_PAGE =
  getRootDirectory(gTestPath).replace(
    "chrome://mochitests/content",
    TEST_DOMAIN_THIRD
  ) + "testPage.html";

/**
 * A helper function to get the random key in a hex string format and test if
 * the random key works properly.
 *
 * @param {Browser} browser The browser element of the testing tab.
 * @param {string} firstPartyDomain The first-party domain loaded on the tab
 * @param {string} thirdPartyDomain The third-party domain to test
 * @returns {string} The random key hex string
 */
async function getRandomKeyHexFromBrowser(
  browser,
  firstPartyDomain,
  thirdPartyDomain
) {
  // Get the key from the cookieJarSettings of the browser element.
  let key = browser.cookieJarSettings.fingerprintingRandomizationKey;
  let keyHex = key.map(bytes => bytes.toString(16).padStart(2, "0")).join("");

  // Get the key from the cookieJarSettings of the top-level document.
  let keyTop = await SpecialPowers.spawn(browser, [], _ => {
    return content.document.cookieJarSettings.fingerprintingRandomizationKey;
  });
  let keyTopHex = keyTop
    .map(bytes => bytes.toString(16).padStart(2, "0"))
    .join("");

  is(
    keyTopHex,
    keyHex,
    "The fingerprinting random key should match between the browser element and the top-level document."
  );

  // Get the key from the cookieJarSettings of an about:blank iframe.
  let keyAboutBlank = await SpecialPowers.spawn(browser, [], async _ => {
    let ifr = content.document.createElement("iframe");

    let loaded = new content.Promise(resolve => {
      ifr.onload = resolve;
    });
    content.document.body.appendChild(ifr);
    ifr.src = "about:blank";

    await loaded;

    return SpecialPowers.spawn(ifr, [], _ => {
      return content.document.cookieJarSettings.fingerprintingRandomizationKey;
    });
  });

  let keyAboutBlankHex = keyAboutBlank
    .map(bytes => bytes.toString(16).padStart(2, "0"))
    .join("");
  is(
    keyAboutBlankHex,
    keyHex,
    "The fingerprinting random key should match between the browser element and the about:blank iframe document."
  );

  // Get the key from the cookieJarSettings of the javascript URL iframe
  // document.
  let keyJavascriptURL = await SpecialPowers.spawn(browser, [], async _ => {
    let ifr = content.document.getElementById("testFrame");

    return ifr.contentDocument.cookieJarSettings.fingerprintingRandomizationKey;
  });

  let keyJavascriptURLHex = keyJavascriptURL
    .map(bytes => bytes.toString(16).padStart(2, "0"))
    .join("");
  is(
    keyJavascriptURLHex,
    keyHex,
    "The fingerprinting random key should match between the browser element and the javascript URL iframe document."
  );

  // Get the key from the cookieJarSettings of an first-party iframe.
  let keyFirstPartyFrame = await SpecialPowers.spawn(
    browser,
    [firstPartyDomain],
    async domain => {
      let ifr = content.document.createElement("iframe");

      let loaded = new content.Promise(resolve => {
        ifr.onload = resolve;
      });
      content.document.body.appendChild(ifr);
      ifr.src = domain;

      await loaded;

      return SpecialPowers.spawn(ifr, [], _ => {
        return content.document.cookieJarSettings
          .fingerprintingRandomizationKey;
      });
    }
  );

  let keyFirstPartyFrameHex = keyFirstPartyFrame
    .map(bytes => bytes.toString(16).padStart(2, "0"))
    .join("");
  is(
    keyFirstPartyFrameHex,
    keyHex,
    "The fingerprinting random key should match between the browser element and the first-party iframe document."
  );

  // Get the key from the cookieJarSettings of an third-party iframe
  let keyThirdPartyFrame = await SpecialPowers.spawn(
    browser,
    [thirdPartyDomain],
    async domain => {
      let ifr = content.document.createElement("iframe");

      let loaded = new content.Promise(resolve => {
        ifr.onload = resolve;
      });
      content.document.body.appendChild(ifr);
      ifr.src = domain;

      await loaded;

      return SpecialPowers.spawn(ifr, [], _ => {
        return content.document.cookieJarSettings
          .fingerprintingRandomizationKey;
      });
    }
  );

  let keyThirdPartyFrameHex = keyThirdPartyFrame
    .map(bytes => bytes.toString(16).padStart(2, "0"))
    .join("");
  is(
    keyThirdPartyFrameHex,
    keyHex,
    "The fingerprinting random key should match between the browser element and the third-party iframe document."
  );

  return keyHex;
}

// Test accessing the fingerprinting randomization key will throw if
// fingerprinting resistance is disabled.
add_task(async function test_randomization_disabled_with_rfp_disabled() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.resistFingerprinting", false],
      ["privacy.resistFingerprinting.pbmode", false],
      ["privacy.fingerprintingProtection", false],
      ["privacy.fingerprintingProtection.pbmode", false],
    ],
  });

  // Ensure accessing the fingerprinting randomization key of the browser
  // element will throw if fingerprinting randomization is disabled.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);

  try {
    let key =
      tab.linkedBrowser.cookieJarSettings.fingerprintingRandomizationKey;
    ok(
      false,
      `Accessing the fingerprinting randomization key should throw when fingerprinting resistance is disabled. ${key}`
    );
  } catch (e) {
    ok(
      true,
      "It should throw when getting the key when fingerprinting resistance is disabled."
    );
  }

  // Ensure accessing the fingerprinting randomization key of the top-level
  // document will throw if fingerprinting randomization is disabled.
  try {
    await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
      return content.document.cookieJarSettings.fingerprintingRandomizationKey;
    });
  } catch (e) {
    ok(
      true,
      "It should throw when getting the key when fingerprinting resistance is disabled."
    );
  }

  BrowserTestUtils.removeTab(tab);
});

// Test the fingerprinting randomization key generation.
add_task(async function test_generate_randomization_key() {
  await SpecialPowers.pushPrefEnv({
    set: [["privacy.resistFingerprinting", true]],
  });

  for (let testPrivateWin of [true, false]) {
    let win = window;

    if (testPrivateWin) {
      win = await BrowserTestUtils.openNewBrowserWindow({
        private: true,
      });
    }

    let tabOne = await BrowserTestUtils.openNewForegroundTab(
      win.gBrowser,
      TEST_PAGE
    );
    let keyHexOne;

    try {
      keyHexOne = await getRandomKeyHexFromBrowser(
        tabOne.linkedBrowser,
        TEST_PAGE,
        TEST_DOMAIN_THIRD_PAGE
      );
      ok(true, `The fingerprinting random key: ${keyHexOne}`);
    } catch (e) {
      ok(
        false,
        "Shouldn't fail when getting the random key from the cookieJarSettings"
      );
    }

    // Open the test domain again and check if the key remains the same.
    let tabTwo = await BrowserTestUtils.openNewForegroundTab(
      win.gBrowser,
      TEST_PAGE
    );
    try {
      let keyHexTwo = await getRandomKeyHexFromBrowser(
        tabTwo.linkedBrowser,
        TEST_PAGE,
        TEST_DOMAIN_THIRD_PAGE
      );
      is(
        keyHexTwo,
        keyHexOne,
        `The key should remain the same after reopening the tab.`
      );
    } catch (e) {
      ok(
        false,
        "Shouldn't fail when getting the random key from the cookieJarSettings"
      );
    }

    // Open a tab with a different domain to see if the key changes.
    let tabAnother = await BrowserTestUtils.openNewForegroundTab(
      win.gBrowser,
      TEST_DOMAIN_ANOTHER_PAGE
    );
    try {
      let keyHexAnother = await getRandomKeyHexFromBrowser(
        tabAnother.linkedBrowser,
        TEST_DOMAIN_ANOTHER_PAGE,
        TEST_DOMAIN_THIRD_PAGE
      );
      isnot(
        keyHexAnother,
        keyHexOne,
        `The key should be different when loading a different domain`
      );
    } catch (e) {
      ok(
        false,
        "Shouldn't fail when getting the random key from the cookieJarSettings"
      );
    }

    BrowserTestUtils.removeTab(tabOne);
    BrowserTestUtils.removeTab(tabTwo);
    BrowserTestUtils.removeTab(tabAnother);
    if (testPrivateWin) {
      await BrowserTestUtils.closeWindow(win);
    }
  }
});

// Test the fingerprinting randomization key will change after private session
// ends.
add_task(async function test_reset_key_after_pbm_session_ends() {
  await SpecialPowers.pushPrefEnv({
    set: [["privacy.resistFingerprinting", true]],
  });

  let privateWin = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  // Open a tab in the private window.
  let tab = await BrowserTestUtils.openNewForegroundTab(
    privateWin.gBrowser,
    TEST_PAGE
  );

  let keyHex = await getRandomKeyHexFromBrowser(
    tab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  // Close the window and open another private window.
  BrowserTestUtils.removeTab(tab);

  let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
  await BrowserTestUtils.closeWindow(privateWin);
  await promisePBExit;

  privateWin = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  // Open a tab again in the new private window.
  tab = await BrowserTestUtils.openNewForegroundTab(
    privateWin.gBrowser,
    TEST_PAGE
  );

  let keyHexNew = await getRandomKeyHexFromBrowser(
    tab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  // Ensure the keys are different.
  isnot(keyHexNew, keyHex, "Ensure the new key is different from the old one.");

  BrowserTestUtils.removeTab(tab);
  await BrowserTestUtils.closeWindow(privateWin);
});

// Test accessing the fingerprinting randomization key will throw in normal
// windows if we exempt fingerprinting protection in normal windows.
add_task(async function test_randomization_with_exempted_normal_window() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.resistFingerprinting", false],
      ["privacy.resistFingerprinting.pbmode", true],
      ["privacy.fingerprintingProtection", false],
      ["privacy.fingerprintingProtection.pbmode", false],
    ],
  });

  // Ensure accessing the fingerprinting randomization key of the browser
  // element will throw if fingerprinting randomization is exempted from normal
  // windows.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);

  try {
    let key =
      tab.linkedBrowser.cookieJarSettings.fingerprintingRandomizationKey;
    ok(
      false,
      `Accessing the fingerprinting randomization key should throw when fingerprinting resistance is exempted in normal windows. ${key}`
    );
  } catch (e) {
    ok(
      true,
      "It should throw when getting the key when fingerprinting resistance is exempted in normal windows."
    );
  }

  // Ensure accessing the fingerprinting randomization key of the top-level
  // document will throw if fingerprinting randomization is exempted from normal
  // windows.
  try {
    await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
      return content.document.cookieJarSettings.fingerprintingRandomizationKey;
    });
  } catch (e) {
    ok(
      true,
      "It should throw when getting the key when fingerprinting resistance is exempted in normal windows."
    );
  }

  BrowserTestUtils.removeTab(tab);

  // Open a private window and check the key can be accessed there.
  let privateWin = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  // Open a tab in the private window.
  tab = await BrowserTestUtils.openNewForegroundTab(
    privateWin.gBrowser,
    TEST_PAGE
  );

  // Access the key, this shouldn't throw an error.
  await getRandomKeyHexFromBrowser(
    tab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  BrowserTestUtils.removeTab(tab);
  await BrowserTestUtils.closeWindow(privateWin);
});

// Test that the random key gets reset when the site data gets cleared.
add_task(async function test_reset_random_key_when_clear_site_data() {
  // Enable fingerprinting randomization key generation.
  await SpecialPowers.pushPrefEnv({
    set: [["privacy.resistFingerprinting", true]],
  });

  // Open a tab and get randomization key from the test domain.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);

  let keyHex = await getRandomKeyHexFromBrowser(
    tab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  // Open another tab and get randomization key from another domain.
  let anotherTab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    TEST_DOMAIN_ANOTHER_PAGE
  );

  let keyHexAnother = await getRandomKeyHexFromBrowser(
    anotherTab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  BrowserTestUtils.removeTab(tab);
  BrowserTestUtils.removeTab(anotherTab);

  // Call ForgetAboutSite for the test domain.
  await ForgetAboutSite.removeDataFromDomain("example.com");

  // Open the tab for the test domain again and verify the key is reset.
  tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
  let keyHexNew = await getRandomKeyHexFromBrowser(
    tab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  // Open the tab for another domain again and verify the key is intact.
  anotherTab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    TEST_DOMAIN_ANOTHER_PAGE
  );
  let keyHexAnotherNew = await getRandomKeyHexFromBrowser(
    anotherTab.linkedBrowser,
    TEST_PAGE,
    TEST_DOMAIN_THIRD_PAGE
  );

  // Ensure the keys are different for the test domain.
  isnot(keyHexNew, keyHex, "Ensure the new key is different from the old one.");

  // Ensure the key for another domain isn't changed.
  is(
    keyHexAnother,
    keyHexAnotherNew,
    "Ensure the key of another domain isn't reset."
  );

  BrowserTestUtils.removeTab(tab);
  BrowserTestUtils.removeTab(anotherTab);
});