/** \file
* \brief Wrappers for NavigatorPlugins
*
* \author Copyright (C) 2021 Matus Svancar
* \author Copyright (C) 2022 Martin Bednar
*
* \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 .
//
// 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 wrappers for NavigatorPlugins. See the MDN docs on the [plugins](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins)
* and [MIME types](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes).
*
* \ingroup wrappers
*
* The goal is to prevent fingerprinting by modifying value returned by getters navigator.plugins and navigator.mimeTypes
*
* This wrapper operates with three levels of protection:
*
* * (0) - replace by shuffled edited PluginArray with two added fake plugins, edited MimeTypeArray
* * (1) - replace by shuffled PluginArray with two fake plugins, empty MimeTypeArray
* * (2) - replace by empty PluginArray and MimeTypeArray
*
* These approaches are 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/plugins/dom_plugin_array.cc).
*
* The purpose of the wrappers is solely to prevent fingerprinting. However,
* browsers modyfing the array stand out of the crowd, which makes them more
* fingerprintable. Consequently, JShelter does not modify an empty list. So
* no matter the configuration, an empty list is not populated by the wrappers.
*
* If PDF inline viewing is supported, Firefox returns 5 plugins:
* * "PDF Viewer"
* * "Chrome PDF Viewer"
* * "Chromium PDF Viewer"
* * "Microsoft Edge PDF Viewer"
* * "WebKit built-in PDF"
*
* A list containing exactly these plugins is treated as the empty list and it is not modified.
*/
/*
* Create private namespace
*/
(function() {
/**
* \brief create and return fake MimeType object
*
*/
function fakeMime(){
var ret = Object.create(MimeType.prototype);
Object.defineProperties(ret, {
type:{
value: ""
},
suffixes:{
value: ""
},
description:{
value: randomString(32, 0, fp_prng)
},
enabledPlugin:{
value: null
}
});
return ret;
}
/**
* \brief create and return fake MimeType object created from given mime and plugin
*
* \param mime original MimeType object https://developer.mozilla.org/en-US/docs/Web/API/MimeType
* \param plugin original Plugin object https://developer.mozilla.org/en-US/docs/Web/API/Plugin
*/
function farbleMime(mime, plugin){
var ret = Object.create(MimeType.prototype);
Object.defineProperties(ret, {
type:{
value: mime.type
},
suffixes:{
value: mime.suffixes
},
description:{
value: mime.description
},
enabledPlugin:{
value: plugin
}
});
return ret;
}
/**
* \brief create and return fake Plugin object
*
* \param descLength enum specifying browser 0 - Chrome 1 - Firefox
* \param filenameLength enum specifying browser 0 - Chrome 1 - Firefox
* \param nameLength enum specifying browser 0 - Chrome 1 - Firefox
*/
function fakePlugin(descLength, filenameLength, nameLength){
var ret = Object.create(Plugin.prototype);
var mime = fakeMime();
Object.defineProperties(ret, {
0:{
value: mime
},
"":{
value: mime
},
name:{
value: randomString(nameLength, 0, fp_prng)
},
filename:{
value: randomString(filenameLength, 0, fp_prng)
},
description:{
value: randomString(descLength, 0, fp_prng)
},
version:{
value: null
},
length:{
value: 1
}
});
ret.__proto__.item = item;
ret.__proto__.namedItem = namedItem;
return ret;
}
/**
* \brief create and return fake PluginArray object containing given plugins
*
* \param plugins array of Plugin objects https://developer.mozilla.org/en-US/docs/Web/API/Plugin
*/
function fakePluginArrayF(plugins){
var ret = Object.create(PluginArray.prototype);
var count = 0;
for(var i = 0; i=0;j++){
// (plugins[i][j].type != "") <- Exclude fake plugins. Fake plugin has type == "".
if((typeof plugins[i][j] != 'undefined') && (ret.namedItem(plugins[i][j].name)==null) && (plugins[i][j].type != "")){
ret[counter] = farbleMime(plugins[i][j], plugins[i]);
ret[plugins[i][j].type] = plugins[i][j];
counter++;
}
else{
break;
}
}
}
Object.defineProperty(ret, 'length', {
value: counter
});
return ret;
}
function item(arg){
if(typeof arg != 'undefined' && Number.isInteger(Number(arg)))
return this[arg];
else return null;
}
function namedItem(arg){
if(typeof arg != 'undefined' && this[arg])
return this[arg];
else return null;
}
function refresh(){
return undefined;
}
/**
* \brief create modified Plugin object from given plugin
*
* \param plugin original Plugin object https://developer.mozilla.org/en-US/docs/Web/API/Plugin
*
* Replaces words in name and description parameters in PDF plugins (default plugins in most browsers)
*/
function farblePlugin(plugin, fp_prng){
var name = plugin.name;
var description = plugin.description;
if(plugin.name.includes("PDF")){
let chrome = ["Chrome ", "Chromium ", "Web ", "Browser ", "OpenSource ", "Online ", "JavaScript ", ""];
let pdf = ["PDF ", "Portable Document Format ", "portable-document-format ", "document ", "doc ", "PDF and PS ", "com.adobe.pdf "];
let viewer = ["Viewer", "Renderer", "Display", "Plugin", "plug-in", "plug in", "extension", ""];
name = chrome[Math.floor(fp_prng() * (chrome.length))]+pdf[Math.floor(fp_prng() * (pdf.length))]+viewer[Math.floor(fp_prng() * (viewer.length))];
description = pdf[Math.floor(fp_prng() * (pdf.length))];
}
var ret = Object.create(Plugin.prototype);
var counter = 0;
while(1){
if(typeof plugin[counter] != 'undefined'){
Object.defineProperties(ret, {
[counter]:{
value: farbleMime(plugin[counter],ret)
},
[plugin[counter].type]:{
value: farbleMime(plugin[counter],ret)
}
});
}
else {
break;
}
counter++;
}
Object.defineProperties(ret, {
name:{
value: name
},
filename:{
value: randomString(32, 0, fp_prng),
},
description:{
value: description
},
version:{
value: null
},
length:{
value: 1
}
});
ret.__proto__.item = item;
ret.__proto__.namedItem = namedItem;
ret.__proto__.refresh = refresh;
return ret;
}
var methods = item + namedItem + refresh + shuffleArray + randomString;
var farbles = farblePlugin + farbleMime;
var fakes = fakeMime + fakePlugin + fakePluginArrayF + fakeMimeTypeArrayF;
var wrappers = [
{
parent_object: "Navigator.prototype",
parent_object_property: "plugins",
apply_if: "applyWrapper",
wrapped_objects: [],
helping_code:
methods + farbles + fakes +`
var applyWrapper = true; // Possibly overridden below
if (navigator.plugins.length === 0) {
applyWrapper = false;
}
if (navigator.plugins.length === 5) {
let plugins = Array.prototype.reduce.call(
navigator.plugins,
function (acc, o) {acc.push(o.name); return acc;}, []);
if (plugins.includes("PDF Viewer") &&
plugins.includes("Chrome PDF Viewer") &&
plugins.includes("Chromium PDF Viewer") &&
plugins.includes("Microsoft Edge PDF Viewer") &&
plugins.includes( "WebKit built-in PDF")) {
applyWrapper = false; // See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/plugins
}
}
if (applyWrapper) {
var fp_prng = alea(domainHash, "S-NP");
var plugins = navigator.plugins;
var buffer = [];
if(args[0]==0){
for(var i = 0;i