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
|
/** \file
* \brief Main script launched when a page is being loaded by a browser
*
* \author Copyright (C) 2020 Libor Polcak
* \author Copyright (C) 2021 Matus Svancar
* \author Copyright (C) 2021 Giorgio Maone
* \author Copyright (C) 2021 Marek Salon
* \author Copyright (C) 2023 Martin Zmitko
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
*/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
var wrappersPort;
var pageConfiguration = null;
function configureInjection({currentLevel, fpdWrappers, fpdTrackCallers, domainHash}) {
if (pageConfiguration) return; // one shot
pageConfiguration = {currentLevel};
if(browser.extension.inIncognitoContext){
// Redefine the domainHash for incognito context:
// Compute the SHA256 hash of the original hash so that the incognito hash is:
// * significantly different to the original domainHash,
// * computationally difficult to revert,
// * the same for all incognito windows (for the same domain).
var hash = sha256.create();
hash.update(JSON.stringify(domainHash));
domainHash = hash.hex();
}
// Append argument reporting setting to JSS wrapper definitions
fp_append_reporting_to_jss_wrappers(fpdWrappers);
// Generate wrapping code
var code = wrap_code(currentLevel.wrappers, fpdTrackCallers);
// Generate FPD wrapping code
if (fpdWrappers) {
if (!code) {
code = fp_generate_wrapping_code(fpdWrappers, fpdTrackCallers);
}
else {
code = fp_update_wrapping_code(code, currentLevel.wrappers, fpdWrappers, fpdTrackCallers);
}
}
// Insert farbling WASM module into wrapped code if enabled, only when farbling is actually used
if (currentLevel.wasm && (currentLevel.audiobuffer === 1 || currentLevel.htmlcanvaselement === 1)) {
code = insert_wasm_code(code);
}
var aleaCode = `(() => {
var domainHash = ${JSON.stringify(domainHash)};
${crc16}
${alea}
var prng = alea(domainHash); // Do not use this in wrappers, create your own prng to generate repeatable sequences
${code}
})()`;
try {
wrappersPort = patchWindow(aleaCode);
wrappersPort.onMessage = msg => {
if (msg.wrapperName) {
let {wrapperName, wrapperType, wrapperArgs, stack} = msg;
// pass access logs to FPD background script
browser.runtime.sendMessage({
purpose: "fp-detection",
resource: wrapperName,
type: wrapperType,
args: wrapperArgs,
stack: stack,
});
}
}
return true;
} catch (e) {
console.error(e, `Trying to run\n${aleaCode}`)
}
return false;
}
/**
* See https://pagure.io/JShelter/paper2022/c/a7e7e88edecfa19c3a52542b553bf1dc9b4388a9?branch=cnil,
* https://pagure.io/JShelter/webextension/issue/70 and
* https://pagure.io/JShelter/webextension/issue/46#comment-793783
* for more information on the early injection mechanism.
*/
if ("configuration" in window) {
configureInjection(configuration);
} else {
/// Get current level configuration from the background script
configureInjection(browser.runtime.sendSyncMessage({
message: "get wrapping for URL",
url: window.location.href
}
));
}
/**
* Event listener that listens for background script messages.
*
* \param callback Function that clears certain storage facilities.
*/
browser.runtime.onMessage.addListener(function (message) {
if (message.cleanStorage) {
localStorage.clear();
sessionStorage.clear();
window.name = "";
if (!message.ignoreWorkaround) {
// clear indexedDB (only Chrome)
if (window.indexedDB && indexedDB.databases) {
indexedDB.databases().then(dbs => {
dbs.forEach(db => indexedDB.deleteDatabase(db.name))
}).catch(err => console.error(err));
}
// clear cacheStorage
if (window.caches) {
caches.keys().then((names) => {
for (let name of names) {
caches.delete(name);
}
}).catch(err => console.error(err));
}
// clear cookies (only JS)
// Source: https://stackoverflow.com/a/66698063/17661959
document.cookie.replace(
/(?<=^|;).+?(?=\=|;|$)/g,
name => location.hostname
.split(/\.(?=[^\.]+\.)/)
.reduceRight((acc, val, i, arr) => i ? arr[i]='.'+val+acc : (arr[i]='', arr), '')
.map(domain => document.cookie=`${name}=;${location.protocol == 'https:' ? 'Secure;' : ''}max-age=0;path=/;domain=${domain}`)
);
}
// clear storages of all injected windows (using BrowsingData)
browser.runtime.sendMessage({
purpose: "fpd-clear-storage",
url: window.location.href
});
}
});
|