diff options
author | Ruben Rodriguez <ruben@trisquel.info> | 2022-09-08 20:18:54 -0400 |
---|---|---|
committer | Ruben Rodriguez <ruben@trisquel.info> | 2022-09-08 20:18:54 -0400 |
commit | 5da28b0f8771834ae208d61431d632875e9f8e7d (patch) | |
tree | 688ecaff26197bad8abde617b4947b11d617309e /data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js | |
parent | 4a87716686104266a9cccc2d83cc249e312f3673 (diff) |
Updated extensions:
* Upgraded Privacy Redirect to 1.1.49 and configured to use the 10 most reliable invidious instances
* Removed ViewTube
* Added torproxy@icecat.gnu based on 'Proxy toggle' extension
* Added jShelter 0.11.1
* Upgraded LibreJS to 7.21.0
* Upgraded HTTPS Everywhere to 2021.7.13
* Upgraded SubmitMe to 1.9
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js')
-rw-r--r-- | data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js b/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js new file mode 100644 index 0000000..c713834 --- /dev/null +++ b/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-MAGNET.js @@ -0,0 +1,552 @@ +/** \file + * \brief Wrappers for the Magnetometer Sensor + * + * \see https://www.w3.org/TR/magnetometer/ + * + * \author Copyright (C) 2021 Radek Hranicky + * + * \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 + * + * MOTIVATION + * + * Magnetometer is a platform sensor available under the Generic Sensor API. + * Magnetometer measures strength and direction of the magnetic field at device's + * location. The interface offers sensor readings using three properties: x, y, and z. + * Each returns a number that describes the magnetic field aroud the particular axis. + * The numbers have a double precision and can be positive or negative, depending + * on the orientation of the field. The total strength of the magnetic field (M) + * can be calculated as M = sqrt(x^2 + z^2 + y^2). The unit is in microtesla (µT). + * + * The Earth's magnetic field ranges between approximately 25 and 65 µT. Concrete + * values depend on location, altitude, weather, interference made by other electric. + * devices, etc. While we consider it is unlikely that someone determines the precise + * location of the device from the Mangetometer values, its data can be used for + * fingerprinting. For instance, it can be determined wheter the device is moving or not. + * In case of a stationary device, we can make a fingerprint from the device's orientation. + * Another fingerprintable value is the average total strength of the field, which + * should remain stable if the device is at the same position and in the same environment. + * + * + * WRAPPING + * + * To protect the device, we are wrapping the x, y, z getters of the + * `Magnetometer.prototype` object. Instead of using the original data, we use + * artificially generated values that look like actual sensor readings. + * + * At every moment, our wrapper stores information about the previous reading. Each + * rewrapped getter first checks the `timestamp` value of the sensor object. If there + * is no difference from the previous reading's timestamp, the wrapper returns the + * last measured value. Otherwise, it provides a new fake reading. + * + * We designed our fake field generator to fulfill the following properties: + * + * - The randomness of the generator should be high enough to prevent attackers from + * deducing the sensor values. + * - Multiple scripts from the same website that access readings with the same + * timestamp must get the same results. And thus: + * - The readings are deterministic - e.g., for a given website and time, we must + * be able to say what values to return. + * + * For every "random" draw, we use the Mulberry32 sen_prng that is seeded with a value + * generated from the `domainHash` which ensures deterministic behavior for the given + * website. First, we choose the desired total strength `M` of the magnetic field at + * our simulated location. This is a pseudo-random number from 25 to 60 uT, like on + * the Earth. + * + * We support two variants of settings the initial axes orientaton: + * - A pseudorandom draw (RANDOM_AXES_ORIENTATION = true) - the original implementation + * - Calculation from the faked device rotation (shared by other wrappers) - improved version + * + * For both methods, the orientation is defined by a number from -1 to 1 for each axis: + * `baseX`, `baseY`, and `baseZ`. By modifying the above-shown formula, we calculate + * the `multiplier` that needs to be applied to the base values to get the desired field. + * The calculation is done as follows: + * - mult = (M * sqrt(baseX^2 + baseY^2 + baseZ^2) / (baseX^2 + baseY^2 + baseZ^2)) + * Now, we know that for axis `x`, the value should fluctuate around `baseX * mult`, etc. + * + * How much the field changes over time is specified by the fluctuation factor (0;1] + * that can also be configured. For instance, 0.2 means that the magnetic field on + * the axis may change from the base value by 20% in both positive and negative way. + * + * The fluctuation is simulated by using a series of **sine** functions for each axis. + * Each sine has a unique amplitude, phase shift, and period. The number of sines per + * axis is chosen pseudorandomly based on the wrapper settings. For initial experiments, + * we used around 20 to 30 sines for each axis. The optimal configuration is in question. + * More sines give less predictable results, but also increase the computing complexity + * that could have a negative impact on the browser's performance. + * + * For the given timestamp `t`, we make the sum of all sine values at the point `x=t`. + * The result is then shifted over the y-axis by adding `base[X|Y|Z] * multiplier` to + * the sum. The initial configuration of the fake field generator was chosen intuitively + * to resemble the results of the real measurements. Currently, the generator uses at + * least one sine with the period around 100 us (with 10% tolerance), which seems to be + * the minimum sampling rate obtainable using the API on mobile devices. Then, at least + * one sine around 1 s, around 10 s, 1 minute, and 1 hour. When more than 5 sines are + * used, the cycle repeats using `modulo 5` and creates a new sine with the period around + * 100 us, but this time the tolerance is 20%. The same follows for seconds, tens of + * seconds, minutes, hours. The tolerance grows every 5 sines. For 11+ sines, the tolerance + * is 30% up to the maximum (currently 50%). The amplitude of each sine is chosen pseudo- + * randomly based on the **fluctuation factor** described above. The phase shift of each + * sine is also pseudo-random number from [0;2PI). + * + * Based on the results, this heuristic returns belivable values that look like actual + * sensor readings. Nevertheless, the generator uses a series of constants, whose optimal + * values should be a subject of future research and improvements. Perphaps, a correlation + * analysis with real mesurements could help in the future. + * + * + * POSSIBLE IMPROVEMENTS + * Non-stationary devices can be supported if the baseX,Y,Z is updated with each movement. + * Do more experiments in real environments and possibly update the reference magnetic field + * vector, or the sine generator, e.g. by simulating temporary pseudorandom electromagnetic + * interferences, etc. + */ + + /* + * Create private namespace + */ +(function() { + /* + * \brief Initialization of data for storing sensor readings + */ + var init_data = ` + var currentReading = currentReading || {orig_x: null, orig_y: null, orig_z: null, timestamp: null, + fake_x: null, fake_y: null, fake_z: null}; + var previousReading = previousReading || {orig_x: null, orig_y: null, orig_z: null, timestamp: null, + fake_x: null, fake_y: null, fake_z: null}; + var emulateStationaryDevice = (typeof args === 'undefined') ? true : args[0]; + var debugMode = false; + + const TWOPI = 2 * Math.PI; + `; + + /* + * \brief Property getters of the original sensor object + */ + var orig_getters = ` + var origGetX = Object.getOwnPropertyDescriptor(Magnetometer.prototype, "x").get; + var origGetY = Object.getOwnPropertyDescriptor(Magnetometer.prototype, "y").get; + var origGetZ = Object.getOwnPropertyDescriptor(Magnetometer.prototype, "z").get; + var origGetTimestamp = Object.getOwnPropertyDescriptor(Sensor.prototype, "timestamp").get; + `; + + /* + * \brief Constructor of the sine configuration object + */ + function SineCfg() { + this.center = 0; + this.amplitude = 1; + this.shift = 0; + this.period = 1; + } + + /* + * \brief Creates sine configurations based on the given settings + * + * \param Minimum number of sines + * \param Maximum number of sines + * \param Center 'y' value that the sine should spin around + * \param Minimal fluctuation factor of a sine + * \param Maximal fluctuation factor of a sine + * \param Minimal period of a sine + * \param Maximal period of a sine + */ + function configureSines(cntMin, cntMax, center, flucMin, fluctMax, periodMin, periodMax) { + // This is helping function for the field generator + // Configures an array of sines for the given settings + + // How many sines we have? + var cnt = Math.floor(sen_prng() * (cntMax - cntMin + 1) + cntMin); + + // max difference from base period + const TOLERANCE_MAX = 0.5; + + // What is the typical amplitude for these sines? + var sineAmplitude = center / cnt; + + var fluctMinMax = flucMin - fluctMax; + let sines = []; + let iteration = 0; + let tolerance = 0.1; + + for (let i = 0; i < cnt; i++) { + let s = new SineCfg(); + let fluctuationFactor = sen_prng() * (fluctMinMax) + fluctMax; + + s.center = center; + s.amplitude = sineAmplitude * fluctuationFactor; + s.shift = sen_prng() * TWOPI; + + let series = i % 5; + + switch(series) { + + case 0: + iteration += 1; + + // increase tolerance for new iterations + if (iteration > 1 && tolerance < TOLERANCE_MAX) { + tolerance += 0.1; + } + + // Minimal sampling rate (default: 100 miliseconds) + s.period = sen_generateAround(periodMin, tolerance); + break; + case 1: // Seconds + s.period = sen_generateAround(1000, tolerance); + break; + case 2: // Tens of seconds + s.period = sen_generateAround(10000, tolerance); + break; + case 3: // Minutes + s.period = sen_generateAround(60000, tolerance); + break; + case 4: // Hours + s.period = sen_generateAround(3600000, tolerance); + break; + } + sines.push(s); + } + return sines; + } + + /* + * \brief Fake magnetic field generator class + * (Modify the constants below to change the generator's behavior.) + */ + class FieldGenerator { + constructor() { + // Specifies, how much the values may (pseudorandomly) oscillate, + // i.e., how much the may relatively differ from the chosen center value + // in both positiva and negative way + this.FLUCTUATION_MIN = 0.20; + this.FLUCTUATION_MAX = 0.45; + this.AXES_OSCILLATE_DIFFERENTLY = true; + + this.NUMBER_OF_SINES_MIN = 25; + this.NUMBER_OF_SINES_MAX = 30; + + // Shifts the phase of each axis randomly [0, 2*PI) + this.RANDOM_PHASE_SHIFT = true; + + // Minimum sampling rate of the device(s) + // Motivation: It does not have sense to waste computing resources + // by oscillating in periods smaller than this value + this.MIN_SAMPLING_RATE = 100; // [ms] + + // Period configuration + this.PERIOD_MIN = this.MIN_SAMPLING_RATE; + this.PERIOD_MAX = 60000 // 1 minute + + // Defines whether the axes orientation is generated pseudorandomly + // true = A PRNG is used to draw the orientation of x/y/z axes + // false = orientation is calculated from the Earth's reference + // coordinate system and the (faked) orientation of the + // phone defined by the global rotation matrix (orient.rotMat) + this.RANDOM_AXES_ORIENTATION = false; + + let m = generateBaseField(); + + // Base of each axis + var baseX = 0; + var baseY = 0; + var baseZ = 0; + + // Calculate the axes base + if (this.RANDOM_AXES_ORIENTATION) { + /* + * Pseudorandom axes orientation + * + * The generateRandomAxisBase() is used to draw a number between + * -1 and 1 for each axis base. + */ + baseX = generateRandomAxisBase(); + baseY = generateRandomAxisBase(); + baseZ = generateRandomAxisBase(); + } else { + /* + * Calculation of axes orientation from the device's rotation + * + * The magnetic field vector is oriented towards the Earth's magnetic + * north and towards the center of the earth. + */ + let referenceMagVec = [0, 0.4, -0.6]; + + /* + * Actual field's strengths in all directions, based on the orientation: + * (Tested on Samsung Galaxy S21 Ultra [And12] and Xiaomi Redmi 9 [And11]) + * + * Legend: + * -- ... highly negative + * - ... negative + * 0 ... zero + * + ... positive + * ++ ... highly positive + * + * +-------+-------+------+---+---+---+ + * | yaw | pitch | roll | x | y | z | + * +-------+-------+------+---+---+---+ + * | 0 0 0 0 + -- | + * | PI 0 0 0 - -- | + * | PI/2 0 0 - 0 -- | + * | -PI/2 0 0 + 0 -- | + * +----------------------------------+ + */ + + // The vector is rotated using the device's fake rotation matrix + var deviceMagVec = multVectRot(referenceMagVec, orient.rotMat); + + if (debugMode) { + } + + // The orientation is taken from the elements of the vector + baseX = deviceMagVec[0]; + baseY = deviceMagVec[1]; + baseZ = deviceMagVec[2]; + } + + var baseX2 = Math.pow(baseX,2) + var baseY2 = Math.pow(baseY,2) + var baseZ2 = Math.pow(baseZ,2) + + // The total magnetic field strength is calculated as: + // m = sqrt(x^2, y^2, z^2) + // where x,y,z are strengs in individual directions (axes). + // + // For x,y,z, the algorithm generates a sine-based fluctuation around + // a center value for each axis. For axis x, it is calculated as: + // x = baseX * multiplier + // + // At this moment, we have calculate the basis (-1,1) for each axis. + // Now, we calculate the multiplier: + // + // m + sqrt(baseX^2 + baseY^2 + baseZ^2) + // multiplier = +/- ------------------------------------- + // baseX^2 + baseY^2 + baseZ^2 + // + // Values at axis X will oscillate around: baseX * multiplier, etc. + + let mult = (m * Math.sqrt(baseX2 + baseY2 + baseZ2)) + / (baseX2 + baseY2 + baseZ2); + + this.baseField = m, + this.multiplier = mult, + this.x = { + base: baseX, + center: baseX * mult, + sines: [], + value: null + }; + this.y = { + base: baseY, + center: baseY * mult, + sines: [], + value: null + }; + this.z = { + base: baseZ, + center: baseZ * mult, + sines: [], + value: null + }; + + this.x.sines = configureSines(this.NUMBER_OF_SINES_MIN, this.NUMBER_OF_SINES_MAX, this.x.center, + this.FLUCTUATION_MIN, this.FLUCTUATION_MAX, this.PERIOD_MIN, this.PERIOD_MAX); + this.y.sines = configureSines(this.NUMBER_OF_SINES_MIN, this.NUMBER_OF_SINES_MAX, this.y.center, + this.FLUCTUATION_MIN, this.FLUCTUATION_MAX, this.PERIOD_MIN, this.PERIOD_MAX); + this.z.sines = configureSines(this.NUMBER_OF_SINES_MIN, this.NUMBER_OF_SINES_MAX, this.z.center, + this.FLUCTUATION_MIN, this.FLUCTUATION_MAX, this.PERIOD_MIN, this.PERIOD_MAX); + } + + // Updates the x/y/z values based on timestamp + update(t) { + // Simulate the magnetic field fluctuation based on settings + // Center is added only once - we want to y-shift the result, not individial sines + this.x.value = this.x.center + this.x.sines.reduce(function (val, s) { + return val + (Math.sin(t * (TWOPI/s.period) + s.shift) * s.amplitude); + }, 0); + this.y.value = this.y.center + this.y.sines.reduce(function (val, s) { + return val + (Math.sin(t * (TWOPI/s.period) + s.shift) * s.amplitude); + }, 0); + this.z.value = this.z.center + this.z.sines.reduce(function (val, s) { + return val + (Math.sin(t * (TWOPI/s.period) + s.shift) * s.amplitude); + }, 0); + } + } + + /* + * \brief Pseudorandomly draws the desired total magnetic field around the device + */ + function generateBaseField() { + const FIELD_MIN = 25; + const FIELD_MAX = 60; + return sen_prng() * (FIELD_MIN - FIELD_MAX) + FIELD_MAX; + } + + /* + * \brief Pseudorandomly draws the orientation of X, Y, Z axes + */ + function generateRandomAxisBase() { + // Returns a number in (-1,1) + var v = sen_prng(); // Random in [0,1) + v *= Math.round(sen_prng()) ? 1 : -1; // 50% change for positive / negative + return v; + } + + /* + * \brief Updates the stored (both real and fake) sensor readings + * according to the data from the sensor object. + * + * \param The sensor object + */ + function updateReadings(sensorObject) { + // We need the original reading's timestamp to see if it differs + // from the previous sample. If so, we need to update the faked x,y,z + let previousTimestamp = previousReading.timestamp; + let currentTimestamp = origGetTimestamp.call(sensorObject); + + if (debugMode) { + // [!] Debug mode: overriding timestamp + // This allows test suites to set a custom timestamp externally + // by modifying the property of the Magnetometer object directly. + currentTimestamp = sensorObject.timestamp; + } + + if (currentTimestamp === previousTimestamp) { + // No new reading, nothing to update + return; + } + + // Rotate the readings: previous <- current + previousReading = JSON.parse(JSON.stringify(currentReading)); + + // Update current reading + // NOTE: Original values are also stored for possible future use + // in improvements of the magnetic field generator + currentReading.orig_x = origGetX.call(sensorObject); + currentReading.orig_y = origGetY.call(sensorObject); + currentReading.orig_z = origGetZ.call(sensorObject); + currentReading.timestamp = currentTimestamp; + + fieldGenerator.update(currentTimestamp); + currentReading.fake_x = fieldGenerator.x.value; + currentReading.fake_y = fieldGenerator.y.value; + currentReading.fake_z = fieldGenerator.z.value; + + if (debugMode) { + } + } + + /* + * \brief Initializes the related generators + */ + var generators = ` + // Initialize the field generator, if not initialized before + var fieldGenerator = fieldGenerator || new FieldGenerator(); + `; + + var helping_functions = sensorapi_prng_functions + device_orientation_functions + + SineCfg + configureSines + FieldGenerator + + generateBaseField + generateRandomAxisBase + updateReadings; + var hc = init_data + orig_getters + helping_functions + generators; + + var wrappers = [ + { + parent_object: "Magnetometer.prototype", + parent_object_property: "x", + wrapped_objects: [], + helping_code: hc, + post_wrapping_code: [ + { + code_type: "object_properties", + parent_object: "Magnetometer.prototype", + parent_object_property: "x", + wrapped_objects: [], + /** \brief replaces Sensor.prototype.x getter to return a faked value + */ + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + updateReadings(this); + return currentReading.fake_x; + }`, + }, + ], + } + ], + }, + { + parent_object: "Magnetometer.prototype", + parent_object_property: "y", + wrapped_objects: [], + helping_code: hc, + post_wrapping_code: [ + { + code_type: "object_properties", + parent_object: "Magnetometer.prototype", + parent_object_property: "y", + wrapped_objects: [], + /** \brief replaces Sensor.prototype.y getter to return a faked value + */ + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + updateReadings(this); + return currentReading.fake_y; + }`, + }, + ], + } + ], + }, + { + parent_object: "Magnetometer.prototype", + parent_object_property: "z", + wrapped_objects: [], + helping_code: hc, + post_wrapping_code: [ + { + code_type: "object_properties", + parent_object: "Magnetometer.prototype", + parent_object_property: "z", + wrapped_objects: [], + /** \brief replaces Sensor.prototype.z getter to return a faked value + */ + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + updateReadings(this); + return currentReading.fake_z; + }`, + }, + ], + } + ], + }, + ] + add_wrappers(wrappers); +})() |