forked from mirrors/gecko-dev
In RFP mode, canvas image extraction leads to an all-white image, replace that with a random (sample 32 bytes of randomness and fill the buffer with that) 'poison pill'. This helps defeat naive fingerprinters by producing a random image on every try. This feature is toggled using a new, default on, pref `privacy.resistFingerprinting.randomDataOnCanvasExtract`. Updated `browser_canvas_fingerprinting_resistance.js` to test this new feature as well. Updates and replaces D66308. Differential Revision: https://phabricator.services.mozilla.com/D72716
383 lines
12 KiB
JavaScript
383 lines
12 KiB
JavaScript
/**
|
|
* When "privacy.resistFingerprinting" is set to true, user permission is
|
|
* required for canvas data extraction.
|
|
* This tests whether the site permission prompt for canvas data extraction
|
|
* works properly.
|
|
* When "privacy.resistFingerprinting.randomDataOnCanvasExtract" is true,
|
|
* canvas data extraction results in random data, and when it is false, canvas
|
|
* data extraction results in all-white data.
|
|
*/
|
|
"use strict";
|
|
|
|
const kUrl = "https://example.com/";
|
|
const kPrincipal = Services.scriptSecurityManager.createContentPrincipal(
|
|
Services.io.newURI(kUrl),
|
|
{}
|
|
);
|
|
const kPermission = "canvas";
|
|
|
|
function initTab() {
|
|
let contentWindow = content.wrappedJSObject;
|
|
|
|
let drawCanvas = (fillStyle, id) => {
|
|
let contentDocument = contentWindow.document;
|
|
let width = 64,
|
|
height = 64;
|
|
let canvas = contentDocument.createElement("canvas");
|
|
if (id) {
|
|
canvas.setAttribute("id", id);
|
|
}
|
|
canvas.setAttribute("width", width);
|
|
canvas.setAttribute("height", height);
|
|
contentDocument.body.appendChild(canvas);
|
|
|
|
let context = canvas.getContext("2d");
|
|
context.fillStyle = fillStyle;
|
|
context.fillRect(0, 0, width, height);
|
|
|
|
if (id) {
|
|
let button = contentDocument.createElement("button");
|
|
button.addEventListener("click", function() {
|
|
canvas.toDataURL();
|
|
});
|
|
button.setAttribute("id", "clickme");
|
|
button.innerHTML = "Click Me!";
|
|
contentDocument.body.appendChild(button);
|
|
}
|
|
|
|
return canvas;
|
|
};
|
|
|
|
let placeholder = drawCanvas("white");
|
|
contentWindow.kPlaceholderData = placeholder.toDataURL();
|
|
let canvas = drawCanvas("cyan", "canvas-id-canvas");
|
|
contentWindow.kPlacedData = canvas.toDataURL();
|
|
is(
|
|
canvas.toDataURL(),
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = false, canvas data == placed data"
|
|
);
|
|
isnot(
|
|
canvas.toDataURL(),
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = false, canvas data != placeholder data"
|
|
);
|
|
}
|
|
|
|
function enableResistFingerprinting(
|
|
randomDataOnCanvasExtract,
|
|
autoDeclineNoInput
|
|
) {
|
|
return SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["privacy.resistFingerprinting", true],
|
|
[
|
|
"privacy.resistFingerprinting.randomDataOnCanvasExtract",
|
|
randomDataOnCanvasExtract,
|
|
],
|
|
[
|
|
"privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts",
|
|
autoDeclineNoInput,
|
|
],
|
|
],
|
|
});
|
|
}
|
|
|
|
function promisePopupShown() {
|
|
return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
|
|
}
|
|
|
|
function promisePopupHidden() {
|
|
return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
|
|
}
|
|
|
|
function extractCanvasData(randomDataOnCanvasExtract, grantPermission) {
|
|
let contentWindow = content.wrappedJSObject;
|
|
let canvas = contentWindow.document.getElementById("canvas-id-canvas");
|
|
let canvasData = canvas.toDataURL();
|
|
if (grantPermission) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission granted, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
} else if (grantPermission === false) {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission denied, canvas data == placeholderdata"
|
|
);
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, permission denied, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, requesting permission, canvas data == placeholderdata"
|
|
);
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, requesting permission, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function triggerCommand(button) {
|
|
let notifications = PopupNotifications.panel.children;
|
|
let notification = notifications[0];
|
|
EventUtils.synthesizeMouseAtCenter(notification[button], {});
|
|
}
|
|
|
|
function triggerMainCommand() {
|
|
triggerCommand("button");
|
|
}
|
|
|
|
function triggerSecondaryCommand() {
|
|
triggerCommand("secondaryButton");
|
|
}
|
|
|
|
function testPermission() {
|
|
return Services.perms.testPermissionFromPrincipal(kPrincipal, kPermission);
|
|
}
|
|
|
|
async function withNewTabNoInput(
|
|
randomDataOnCanvasExtract,
|
|
grantPermission,
|
|
browser
|
|
) {
|
|
await SpecialPowers.spawn(browser, [], initTab);
|
|
await enableResistFingerprinting(randomDataOnCanvasExtract, false);
|
|
let popupShown = promisePopupShown();
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[randomDataOnCanvasExtract],
|
|
extractCanvasData
|
|
);
|
|
await popupShown;
|
|
let popupHidden = promisePopupHidden();
|
|
if (grantPermission) {
|
|
triggerMainCommand();
|
|
await popupHidden;
|
|
is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
|
|
} else {
|
|
triggerSecondaryCommand();
|
|
await popupHidden;
|
|
is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
|
|
}
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[randomDataOnCanvasExtract, grantPermission],
|
|
extractCanvasData
|
|
);
|
|
await SpecialPowers.popPrefEnv();
|
|
}
|
|
|
|
async function doTestNoInput(randomDataOnCanvasExtract, grantPermission) {
|
|
await BrowserTestUtils.withNewTab(
|
|
kUrl,
|
|
withNewTabNoInput.bind(null, randomDataOnCanvasExtract, grantPermission)
|
|
);
|
|
Services.perms.removeFromPrincipal(kPrincipal, kPermission);
|
|
}
|
|
|
|
// With auto-declining disabled (not the default)
|
|
// Tests clicking "Don't Allow" button of the permission prompt.
|
|
add_task(doTestNoInput.bind(null, true, false));
|
|
add_task(doTestNoInput.bind(null, false, false));
|
|
|
|
// Tests clicking "Allow" button of the permission prompt.
|
|
add_task(doTestNoInput.bind(null, true, true));
|
|
add_task(doTestNoInput.bind(null, false, true));
|
|
|
|
async function withNewTabAutoBlockNoInput(randomDataOnCanvasExtract, browser) {
|
|
await SpecialPowers.spawn(browser, [], initTab);
|
|
await enableResistFingerprinting(randomDataOnCanvasExtract, true);
|
|
|
|
let noShowHandler = () => {
|
|
ok(false, "The popup notification should not show in this case.");
|
|
};
|
|
PopupNotifications.panel.addEventListener("popupshown", noShowHandler, {
|
|
once: true,
|
|
});
|
|
|
|
let promisePopupObserver = TestUtils.topicObserved(
|
|
"PopupNotifications-updateNotShowing"
|
|
);
|
|
|
|
// Try to extract canvas data without user inputs.
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[randomDataOnCanvasExtract],
|
|
extractCanvasData
|
|
);
|
|
|
|
await promisePopupObserver;
|
|
info("There should be no popup shown on the panel.");
|
|
|
|
// Check that the icon of canvas permission is shown.
|
|
let canvasNotification = PopupNotifications.getNotification(
|
|
"canvas-permissions-prompt",
|
|
browser
|
|
);
|
|
|
|
is(
|
|
canvasNotification.anchorElement.getAttribute("showing"),
|
|
"true",
|
|
"The canvas permission icon is correctly shown."
|
|
);
|
|
PopupNotifications.panel.removeEventListener("popupshown", noShowHandler);
|
|
|
|
await SpecialPowers.popPrefEnv();
|
|
}
|
|
|
|
async function doTestAutoBlockNoInput(randomDataOnCanvasExtract) {
|
|
await BrowserTestUtils.withNewTab(
|
|
kUrl,
|
|
withNewTabAutoBlockNoInput.bind(null, randomDataOnCanvasExtract)
|
|
);
|
|
}
|
|
|
|
add_task(doTestAutoBlockNoInput.bind(null, true));
|
|
add_task(doTestAutoBlockNoInput.bind(null, false));
|
|
|
|
function extractCanvasDataUserInput(
|
|
randomDataOnCanvasExtract,
|
|
grantPermission
|
|
) {
|
|
let contentWindow = content.wrappedJSObject;
|
|
let canvas = contentWindow.document.getElementById("canvas-id-canvas");
|
|
let canvasData = canvas.toDataURL();
|
|
if (grantPermission) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission granted, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
} else if (grantPermission === false) {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission denied, canvas data == placeholderdata"
|
|
);
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, permission denied, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlacedData,
|
|
"privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
|
|
);
|
|
if (!randomDataOnCanvasExtract) {
|
|
is(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, requesting permission, canvas data == placeholderdata"
|
|
);
|
|
} else {
|
|
isnot(
|
|
canvasData,
|
|
contentWindow.kPlaceholderData,
|
|
"privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, requesting permission, canvas data != placeholderdata"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function withNewTabInput(
|
|
randomDataOnCanvasExtract,
|
|
grantPermission,
|
|
browser
|
|
) {
|
|
await SpecialPowers.spawn(browser, [], initTab);
|
|
await enableResistFingerprinting(randomDataOnCanvasExtract, true);
|
|
let popupShown = promisePopupShown();
|
|
await SpecialPowers.spawn(browser, [], function(host) {
|
|
E10SUtils.wrapHandlingUserInput(content, true, function() {
|
|
var button = content.document.getElementById("clickme");
|
|
button.click();
|
|
});
|
|
});
|
|
await popupShown;
|
|
let popupHidden = promisePopupHidden();
|
|
if (grantPermission) {
|
|
triggerMainCommand();
|
|
await popupHidden;
|
|
is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
|
|
} else {
|
|
triggerSecondaryCommand();
|
|
await popupHidden;
|
|
is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
|
|
}
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[randomDataOnCanvasExtract, grantPermission],
|
|
extractCanvasDataUserInput
|
|
);
|
|
await SpecialPowers.popPrefEnv();
|
|
}
|
|
|
|
async function doTestInput(
|
|
randomDataOnCanvasExtract,
|
|
grantPermission,
|
|
autoDeclineNoInput
|
|
) {
|
|
await BrowserTestUtils.withNewTab(
|
|
kUrl,
|
|
withNewTabInput.bind(null, randomDataOnCanvasExtract, grantPermission)
|
|
);
|
|
Services.perms.removeFromPrincipal(kPrincipal, kPermission);
|
|
}
|
|
|
|
// With auto-declining enabled (the default)
|
|
// Tests clicking "Don't Allow" button of the permission prompt.
|
|
add_task(doTestInput.bind(null, true, false));
|
|
add_task(doTestInput.bind(null, false, false));
|
|
|
|
// Tests clicking "Allow" button of the permission prompt.
|
|
add_task(doTestInput.bind(null, true, true));
|
|
add_task(doTestInput.bind(null, false, true));
|