/** \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