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
|
/** \file
* \brief Wrappers for Media Capture and Streams standard
*
* \see https://www.w3.org/TR/mediacapture-streams/
*
* \author Copyright (C) 2021 Libor Polcak
* \author Copyright (C) 2021 Matus Svancar
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
* \license SPDX-License-Identifier: MPL-2.0
*/
//
// 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/>.
//
// Alternatively, the contents of this file may be used under the terms
// of the Mozilla Public License, v. 2.0, as described below:
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// \copyright Copyright (c) 2020 The Brave Authors.
/** \file
* This file contains wrapper for MediaDevices.enumerateDevices https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
* \ingroup wrappers
*
* The goal is to prevent fingerprinting by modifying return value of enumerateDevices.
*
* This wrapper operates with three levels of protection:
*
* * (0) - return promise with shuffled array
* * (1) - return promise with shuffled array with additional 0-4 fake devices
* * (2) - return empty promise
*
* The shuffling approach is inspired by the algorithms created by [Brave Software](https://brave.com)
* available [here](https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/mediastream/media_devices.cc).
*/
/*
/*
* Create private namespace
*/
(function() {
/**
* \brief change reurn value of enumerateDevices
*
* Depending on level chosen this function returns:
* * (0,1) - promise with modified device array
* * (2) - empty promise
*/
function farbleEnumerateDevices() {
// to emulate correctly we need a fresh Promise and a fresh Array (with same values) on every call
return cachedDevices.then(r => {
r = r.concat();
return r;
});
}
/**
* \brief create and return MediaDeviceInfo object by overlaying a native one with fake properties
*
* \param device Device is any native MediaInfoDevice object (https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo)
* \param fd_prng Initialized PRNG to be deterministically used by the function
*/
function fakeDevice(device, fd_prng){
var kinds = ["videoinput", "audioinput", "audiooutput"];
// browserEnum specifies the browser: 0 - Chrome 1 - Firefox
let browserEnum = device.groupId.length == 44 ? 1 : 0;
var deviceId = browserEnum == 1 ? randomString(43, browserEnum, fd_prng)+ "=" : "";
let fakeData = {
deviceId,
groupId: deviceRandomString(browserEnum),
kind: kinds[Math.floor(fd_prng() * 3)],
label: "",
};
let json = JSON.stringify(fakeData);
fakeData.toJSON = () => json;
let overlay = WrapHelper.overlay(device, fakeData);
return overlay;
}
/**
* \brief return random string for MediaDeviceInfo parameters
*
* \param browserEnum enum specifying browser 0 - Chrome 1 - Firefox
*/
function deviceRandomString(browserEnum) {
var ret = "";
var lengths = [64, 43];
var charSets = ["abcdefghijklmnopqrstuvwxyz0123456789","abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"];
var length = lengths[browserEnum];
var charSet = charSets[browserEnum];
for ( var i = 0; i < length; i++ ) {
ret += charSet.charAt(Math.floor(Math.random() * charSet.length));
}
if(browserEnum == 1)
ret += "=";
return ret;
}
var wrappers = [
{
parent_object: "MediaDevices.prototype",
parent_object_property: "enumerateDevices",
wrapped_objects: [{
original_name: "MediaDevices.prototype.enumerateDevices",
callable_name: "origEnumerateDevices",
}],
helping_code: farbleEnumerateDevices + shuffleArray + deviceRandomString + randomString + fakeDevice + `
var fd_prng = alea(domainHash, "S-MCS MediaDevices.prototype.enumerateDevices");
let [level] = args;
let cachedDevices = level < 2 ?
origEnumerateDevices.call(navigator.mediaDevices).then(result => {
try {
let shuffle = () => {
if (result.length > 1) shuffleArray(result);
return result;
};
if (level === 1 && result.length) {
var enumd_prng = alea(domainHash, "MediaDevices.prototype.enumerateDevices");
let additional = Math.floor(enumd_prng()*4);
if (additional > 0) {
let adding = [];
while (additional-- > 0) {
adding.push(origEnumerateDevices.call(navigator.mediaDevices).then(([device]) => {
let fake = fakeDevice(device, fd_prng);
result.push(fake);
}));
}
return Promise.all(adding).then(r => {
return shuffle();
});
}
}
return shuffle();
} catch (e) {
console.error("Error in farble promise callback", e);
throw e;
}
})
: Promise.resolve([]);
`,
wrapping_function_args: "",
/** \fn fake MediaDevices.prototype.enumerateDevices
* \brief Modifies return value
*
* Depending on level chosen this function returns:
* * (0) - promise with shuffled array
* * (1) - promise with shuffled array with additional 0-4 fake devices
* * (2) - empty promise
*/
wrapping_function_body: `
return farbleEnumerateDevices();
`,
},
]
add_wrappers(wrappers);
})()
|