diff options
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/wrappingS-GEO.js')
-rw-r--r-- | data/extensions/jsr@javascriptrestrictor/wrappingS-GEO.js | 383 |
1 files changed, 0 insertions, 383 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/wrappingS-GEO.js b/data/extensions/jsr@javascriptrestrictor/wrappingS-GEO.js deleted file mode 100644 index a23ba47..0000000 --- a/data/extensions/jsr@javascriptrestrictor/wrappingS-GEO.js +++ /dev/null @@ -1,383 +0,0 @@ -/** \file - * \brief This file contains wrappers for the Geolocation API - * - * \see https://www.w3.org/TR/geolocation-API/ - * - * \author Copyright (C) 2019 Martin Timko - * \author Copyright (C) 2020 Libor Polcak - * \author Copyright (C) 2020 Peter Marko - * \author Copyright (C) 2021 Giorgio Maone - * - * \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/>. -// - -/** \file - * \ingroup wrappers - * - * The goal is to prevent leaks of user current position. The Geolocation API also provides access - * to high precision timestamps which can be used to various web attacks (see for example, - * http://www.jucs.org/jucs_21_9/clock_skew_based_computer, - * https://lirias.kuleuven.be/retrieve/389086). - * - * Although it is true that the user needs to specificaly approve access to location facilities, - * these wrappers aim on improving the control of the precision of the geolocation. - * - * The wrappers support the following controls: - * - * * Accurate data: the extension provides precise geolocation position but modifies the time - * precision in conformance with the Date and Performance wrappers. - * * Modified position: the extension modifies the time precision of the time stamps in - * conformance with the Date and Performance wrappers, and additionally, allows to limit the - * precision of the current position to hundered of meters, kilometers, tens, or hundereds of - * kilometers. - * - * When modifying position: - * - * * Repeated calls of `navigator.geolocation.getCurrentPosition()` return the same position - * without page load and typically return another position after page reload. - * * `navigator.geolocation.watchPosition()` does not change position. - */ - -(function() { - var processOriginalGPSDataObject_globals = ` - let geo_prng = alea(domainHash, "Geolocation"); - let randomx = geo_prng(); - let randomy = geo_prng(); - /** - * Make sure that repeated calls shows the same position (BUT different objects, via cloning) - * to reduce fingerprintablity. - */ - let previouslyReturnedCoords; - let clone = obj => Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); - - /** - * \brief Store the limit for the returned timestamps. - * - * This is used to avoid returning older readings compared to the previous readings. - * The timestamp needs to be the same as was last time or newser. - */ - var geoTimestamp = Date.now(); - `; - /** - * \brief Modifies the given PositionObject according to settings - * - * \param expectedMaxAge The maximal age of the returned time stamps as defined by the wrapped API - * (https://www.w3.org/TR/geolocation-API/#max-age) - * \param originalPositionObject the position object to be returned without this wrapper, see the - * Position interface (https://www.w3.org/TR/geolocation-API/#position) - * - * The function modifies the originalPositionObject and stores it for later readins. The returned - * position does not modify during the life time of a pages. The returned postion can be different - * after a page reload. - * - * The goal of the behavoiur is to prevent learning the current position so that different - * original postion can be mapped to the same position and the same position should generally - * yield a different outcome to prevent correlation of user activities. - * - * The algorithm works as follows: - * 1. The Earth surface is partitioned into squares (tiles) with the edge derived from the desired - * accuracy. - * 2. The position from the originalPositionObject is mapped to its tile and eight adjacent tiles. - * 3. A position in the current tile and the eight adjacent tiles is selected randomly. - * - * The returned timestamp is not older than 1 hour and it is the same as was during the last call - * or newer. Different calls to the function can yield different timestamps. - * - * \bug The tile-based approach does not work correctly near poles but: - * * The function returns fake locations near poles. - * * As there are not many people near poles, we do not believe this wrapping is useful near poles - * so we do not consider this bug as important. - */ - - function spoofCall(fakeData, originalPositionObject, successCallback) { - // proxying the original object lessens the fingerprintable weirdness - // (e.g. accessors on the instance rather than on the prototype) - fakeData = clone(fakeData); - let pos = new Proxy(originalPositionObject, { - get(target, key) { - return (key in fakeData) ? fakeData[key] : target[key]; - }, - getPrototypeOf(target) { - return Object.getPrototypeOf(target); - } - }); - successCallback(pos); - } - - function processOriginalGPSDataObject(expectedMaxAge, originalPositionObject) { - if (expectedMaxAge === undefined) { - expectedMaxAge = 0; // default value - } - // Set reasonable expectedMaxAge of 1 hour for later computation - expectedMaxAge = Math.min(3600000, expectedMaxAge); - geoTimestamp = Math.max(geoTimestamp, Date.now() - Math.random()*expectedMaxAge); - - let spoofPos = coords => { - let pos = { timestamp: geoTimestamp }; - if (coords) pos.coords = coords; - spoofCall(pos, originalPositionObject, successCallback); - }; - - if (provideAccurateGeolocationData) { - return spoofPos(); - } - if (previouslyReturnedCoords) { - return spoofPos(clone(previouslyReturnedCoords)); - } - - const EQUATOR_LEN = 40074; - const HALF_MERIDIAN = 10002; - const DESIRED_ACCURACY_KM = desiredAccuracy*2; - - var lat = originalPositionObject.coords.latitude; - var lon = originalPositionObject.coords.longitude; - // Compute (approximate) distance from 0 meridian [m] - var x = (lon * (EQUATOR_LEN * Math.cos((lat/90)*(Math.PI/2))) / 180); - // Compute (approximate) distance from equator [m] - var y = (lat / 90) * (HALF_MERIDIAN); - - // Compute the coordinates of the left bottom corner of the tile in which the orig position is - var xmin = Math.floor(x / DESIRED_ACCURACY_KM) * DESIRED_ACCURACY_KM; - var ymin = Math.floor(y / DESIRED_ACCURACY_KM) * DESIRED_ACCURACY_KM; - - // The position to be returned should be in the original tile and the 8 adjacent tiles: - // +----+----+----+ - // | | | | - // +----+----+----+ - // | |orig| | - // +----+----+----+ - // | | | | - // +----+----+----+ - var newx = xmin + randomx * 3 * DESIRED_ACCURACY_KM - DESIRED_ACCURACY_KM; - var newy = ymin + randomy * 3 * DESIRED_ACCURACY_KM - DESIRED_ACCURACY_KM; - - if (Math.abs(newy) > (HALF_MERIDIAN)) { - newy = (HALF_MERIDIAN + HALF_MERIDIAN - Math.abs(newy)) * (newy < 0 ? -1 : 1); - newx = -newx; - } - - var newLatitude = newy / HALF_MERIDIAN * 90; - var newLongitude = newx * 180 / (EQUATOR_LEN * Math.cos((newLatitude/90)*(Math.PI/2))); - - while (newLongitude < -180) { - newLongitude += 360; - } - while (newLongitude > 180) { - newLongitude -= 360; - } - - var newAccuracy = DESIRED_ACCURACY_KM * 1000 * 2.5; // in meters - - previouslyReturnedCoords = { - latitude: newLatitude, - longitude: newLongitude, - altitude: null, - accuracy: newAccuracy, - altitudeAccuracy: null, - heading: null, - speed: null, - __proto__: originalPositionObject.coords.__proto__ - }; - spoofPos(previouslyReturnedCoords); - }; - /** - * \brief process the parameters of the wrapping function - * - * Checks if the wrappers should be active, and the position modified. Transforms the desired - * precision into kilometers. - */ - var setArgs = ` - var enableGeolocation = (args[0] !== 0); - var provideAccurateGeolocationData = (args[0] === -1); - let desiredAccuracy = 0; - switch (args[0]) { - case 2: - desiredAccuracy = 0.1; - break; - case 3: - desiredAccuracy = 1; - break; - case 4: - desiredAccuracy = 10; - break; - case 5: - desiredAccuracy = 100; - break; - } - `; - - var wrappers = [ - { - parent_object: "Navigator.prototype", - parent_object_property: "geolocation", - wrapped_objects: [], - helping_code: setArgs, - post_wrapping_code: [ - { - code_type: "delete_properties", - parent_object: "Navigator.prototype", - apply_if: "!enableGeolocation", - delete_properties: ["geolocation"], - } - ], - }, - { - parent_object: "window", - parent_object_property: "Geolocation", - wrapped_objects: [], - helping_code: setArgs, - post_wrapping_code: [ - { - code_type: "delete_properties", - parent_object: "window", - apply_if: "!enableGeolocation", - delete_properties: ["Geolocation"], - } - ], - }, - { - parent_object: "window", - parent_object_property: "GeolocationCoordinates", - wrapped_objects: [], - helping_code: setArgs, - post_wrapping_code: [ - { - code_type: "delete_properties", - parent_object: "window", - apply_if: "!enableGeolocation", - delete_properties: ["GeolocationCoordinates"], - } - ], - }, - { - parent_object: "window", - parent_object_property: "GeolocationPosition", - wrapped_objects: [], - helping_code: setArgs, - post_wrapping_code: [ - { - code_type: "delete_properties", - parent_object: "window", - apply_if: "!enableGeolocation", - delete_properties: ["GeolocationPosition"], - } - ], - }, - { - parent_object: "window", - parent_object_property: "GeolocationPositionError", - wrapped_objects: [], - helping_code: setArgs, - post_wrapping_code: [ - { - code_type: "delete_properties", - parent_object: "window", - apply_if: "!enableGeolocation", - delete_properties: ["GeolocationPositionError"], - } - ], - }, - { - parent_object: "Geolocation.prototype", - parent_object_property: "getCurrentPosition", - - wrapped_objects: [ - { - original_name: "Geolocation.prototype.getCurrentPosition", - callable_name: "originalGetCurrentPosition", - }, - ], - helping_code: setArgs + processOriginalGPSDataObject_globals, - wrapping_function_args: "successCallback, errorCallback, origOptions", - /** \fn fake Geolocation.prototype.getCurrentPosition - * \brief Provide a fake geolocation position - */ - wrapping_function_body: ` - ${spoofCall} - ${processOriginalGPSDataObject} - var options = { - enableHighAccuracy: false, - }; - if (origOptions) try { - if ("timeout" in origOptions) { - options.timeout = origOptions.timeout; - } - if ("maximumAge" in origOptions) { - option.maximumAge = origOptions.maximumAge; - } - } - catch { /* Undefined or another error */} - originalGetCurrentPosition.call(this, processOriginalGPSDataObject.bind(null, options.maximumAge), errorCallback, options); - `, - }, - { - parent_object: "Geolocation.prototype", - parent_object_property: "watchPosition", - wrapped_objects: [ - { - original_name: "Geolocation.prototype.watchPosition", - wrapped_name: "originalWatchPosition", - }, - ], - helping_code: setArgs + processOriginalGPSDataObject_globals + "let watchPositionCounter = 0;", - wrapping_function_args: "successCallback, errorCallback, origOptions", - /** \fn fake Geolocation.prototype.watchPosition - * Geolocation.prototype.watchPosition intended use concerns tracking user position changes. - * JShelter provides four modes of operaion: - * * current position approximation: Always return the same data, the same as getCurrentPosition() - * * accurate data: Return exact position but fake timestamp - */ - wrapping_function_body: ` - if (provideAccurateGeolocationData) { - function wrappedSuccessCallback(originalPositionObject) { - geoTimestamp = Date.now(); // Limit the timestamp accuracy by calling possibly wrapped function - return spoofCall({ timestamp: geoTimestamp }, originalPositionObject, succesCallback); - } - originalWatchPosition.call(this, wrappedSuccessCallback, errorCallback, origOptions); - } - else { - // Re-use the wrapping of n.g.getCurrentPosition() - Geolocation.prototype.getCurrentPosition(successCallback, errorCallback, origOptions); - watchPositionCounter++; - return watchPositionCounter; - } - `, - }, - { - parent_object: "Geolocation.prototype", - parent_object_property: "clearWatch", - wrapped_objects: [ - { - original_name: "Geolocation.prototype.clearWatch", - wrapped_name: "originalClearWatch", - }, - ], - helping_code: setArgs, - wrapping_function_args: "id", - /** \fn fake_or_original Geolocation.prototype.clearWatch - * If the Geolocation API provides correct data, call the original implementation, - * otherwise do nothing as the watchPosition object was not created. - */ - wrapping_function_body: ` - if (provideAccurateGeolocationData) { - originalClearWatch.call(Geolocation.prototype, id); - } - `, - } - ] - add_wrappers(wrappers); -})(); |