From 8895ec2977a9a02f5f0f0d50a6f33500b76e7d67 Mon Sep 17 00:00:00 2001 From: Libor Polčák Date: Oct 13 2021 10:14:46 +0000 Subject: Merge source of new jShelter.org website Designed by Manufactura. Thanks. --- diff --git a/.gitignore b/.gitignore index 272c61a..e4bc0d6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ tests/system_tests/get_data/top_sites/*.csv tests/system_tests/get_data/selenium/*.jar tests/unit_tests/node_modules/* tests/unit_tests/tmp/* + +website/output diff --git a/Makefile b/Makefile index ba8512b..e9071c4 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,6 @@ submodules: @find $*_JSR/ -name '*.license' -delete @cd $*_JSR/ && zip -q -r ../$@ ./* --exclude \*.sw[pno] -docs: - PROJECT_NAME="${PROJECT_NAME}" doxygen < doxyfile - clean: rm -rf firefox_JSR.zip rm -rf firefox_JSR diff --git a/README.md b/README.md deleted file mode 120000 index e892330..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -docs/index.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..854c33a --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +> **Disclaimer**: This is a research project under development, see the [issue page](https://github.com/polcak/jShelterestrictor/issues) and the [webextension home page](https://polcak.github.io/jShelterestrictor/) for more details about the current status. + +A JS-enabled web page can access any of the APIs that a web browser provides. The user has only a limited control and some APIs cannot be restricted by the user easily. jShelter aims to improve the user control of the web browser. Similarly to a firewall that controls the network traffic, jShelter controls the APIs provided by the browser. The goal is to improve the privacy and security of the user running the extension. + +## Installation + +jShelter is a browser extension with support for multiple browsers: [Firefox](https://addons.mozilla.org/cs/firefox/addon/javascript-restrictor/), [Google Chrome](https://chrome.google.com/webstore/detail/javascript-restrictor/ammoloihpcbognfddfjcljgembpibcmb), and [Opera](https://addons.opera.com/en/extensions/details/javascript-restrictor/). The extension also works with Brave, Microsoft Edge, and most likely any Chromium-based browser. [Let us know](https://github.com/polcak/jShelterestrictor/issues) if you want to add the extension to additional store. + +## Goals + +Various websites collect information about users without their awareness. The collected information is used to track users. Malicious websites can fingerprint user browsers or computers. jShelter protects the user by restricting or modifying several web browser APIs used to create side-channels and identify the user, the browser or the computer. jShelter can block access to JavaScript objects, functions and properties or provide a less precise implementation of their functionality, for example, by modifying or spoofing values returned by the JS calls. The goal is to mislead websites by providing false data or no data at all. + +Another goal of the extension is not to break the visited websites. As the deployment of JavaScript only websites rise, it is necessary to fine-tune the API available to the websites to prevent unsolicited tracking and protect against data thefts. + +### Protected APIs + +jShelter currently supports modifying and restricting the following APIs (for more details visit [levels of protection page](https://polcak.github.io/jShelterestrictor/levels.html)): + +* **Network boundary shield** (NBS) prevents web pages to use the browser as a proxy between local network and the public Internet. See the [Force Point report](https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf) for an example of the attack. The protection encapsulates the WebRequest API, so it captures all outgoing requests including all elements created by JavaScript. +* **window.Date object**, **window.performance.now()** and **window.PerformanceEntry** provide high-resolution timestamps that can be used to [idenfity the user](http://www.jucs.org/jucs_21_9/clock_skew_based_computer) or can be used for microarchitectural attacks and [timing attacks](https://lirias.kuleuven.be/retrieve/389086). +* **HTMLCanvasElement**: Functions canvas.toDataURL(), canvas.toBlob(), CanvasRenderingContext2D.getImageData, OffscreenCanvas.convertToBlob() return either - modified image data based on session and domain keys, making canvas fingerprint unique, or white image. Canvas element provides access to HW acceleration which may reveal the card and consequently be used as a fingerprinting source. +* **AudioBuffer and AnalyserNode**: These API can be used to create fingerprint by analysing audio signal. jShelter modifies AudioBuffer.getChannelData(), AudioBuffer.copyFromChannel(), AnalyserNode.getByteTimeDomainData(), AnalyserNode.getFloatTimeDomainData(), AnalyserNode.getByteFrequencyData() and AnalyserNode.getFloatFrequencyData() to alter audio data based on domain key, or return white noise based on domain key, making audio fingerprint unique. +* **WebGLRenderingContext**: WebGL parameters and functions can expose hardware and software uniqueness. jShelter modifies function WebGLRenderingContext.getParameter() to return bottom values (null, 0, empty string, etc) or alter return values for certain arguments. WebGLRenderingContext.getActiveAttrib, WebGLRenderingContext.getActiveUniform, +WebGLRenderingContext.getAttribLocation, WebGLRenderingContext.getBufferParameter, WebGLRenderingContext.getFramebufferAttachmentParameter, +WebGLRenderingContext.getProgramParameter, WebGLRenderingContext.getRenderbufferParameter, WebGLRenderingContext.getShaderParameter, +WebGLRenderingContext.getShaderPrecisionFormat, WebGLRenderingContext.getTexParameter, WebGLRenderingContext.getUniformLocation, +WebGLRenderingContext.getVertexAttribOffset, WebGLRenderingContext.getSupportedExtensions, WebGLRenderingContext.getExtension are modified to return bottom values. WebGLRenderingContext.readPixels() is modified to return either empty image or modified image data based on session and domain keys. +* **MediaDevices.prototype.enumerateDevices** provides a unique strings identifying cameras and + microphones. This strings can be used to fingerprint the user (user session). +* **navigator.deviceMemory** or **navigator.hardwareConcurrency** can reveal hardware specification of the device. +* **XMLHttpRequest (XHR)** performs requests to the server after the page is displayed and gathered information available through other APIs. Such information might carry identification data or results of other attacks. +* **ArrayBuffer** can be exploited for microarchitectural attacks. + * Encapsulates window.DataView, window.Uint8Array, window.Int8Array, window.Uint8ClampedArray, window.Int16Array, window.Uint16Array, window.Int32Array, window.Uint32Array, window.Float32Array, window.Float64Array +* **SharedArrayBuffer (window.SharedArrayBuffer)** can be exploited for [timing attacks](https://graz.pure.elsevier.com/de/publications/fantastic-timers-and-where-to-find-them-high-resolution-microarch). +* **WebWorker (window.Worker)** can be exploited for [timing attacks](https://graz.pure.elsevier.com/de/publications/practical-keystroke-timing-attacks-in-sandboxed-javascript). +* **[Geolocation API](https://www.w3.org/TR/geolocation-API/) (navigator.geolocation)**: Although + the browser should request permission to access to the Geolocation API, the user can be unwilling + to share the exact position. jShelter allows the user to limit the precision of the API or disable the + API. jShelter also modifies the timestamps provided by Geolocation API in consistency with its time + precision settings. +* **window.name** provides a very simple cross-origin tracking method of the same tab, see https://github.com/polcak/jShelterestrictor/issues/72, https://developer.mozilla.org/en-US/docs/Web/API/Window/name, https://2019.www.torproject.org/projects/torbrowser/design/, https://bugzilla.mozilla.org/show_bug.cgi?id=444222, and https://html.spec.whatwg.org/#history-traversal. jShelter provides an option to remove any `window.name` content on each page load. +* **navigator.sendBeacon** is an API desinged for analytics. jShelter provides an option to disable the API. The call returns success but nothing is sent to any web server. + +Note that the spoofing and rounding actions performed by the extension can break the functionality of a website (e.g. Netflix). Please [report to us](https://github.com/polcak/jShelterestrictor/issues) any malfunction websites that do not track users. + +### Levels of Protection + +jShelter provides four in-built levels of protection: + +* 0 - the functionality of the extension is turned off. All web pages are displayed as intended without any interaction from jShelter. +* 1 - the minimal level of protection. Only changes that should not break most pages are enabled. + Note that timestamps are rounded so pages relying on precise time may be broken. +* 2 - intended to be used as a default level of protection, this level should not break any site + while maintaining strong protection. +* 3 - maximal level of protection: enable all functionality. + +For more accurate description of the restrictions see [levels of protection page](/levels). + +The default level of protection can be set by a popup (clicking on jShelter icon) or through options of the extension. Specific level of protection for specific domains can be set in options by adding them to the list of websites with specific level of protection. This can be done also by a popup during a visit of the website. + +## Contributing + +If you have any questions or you have spotted a bug, please [let us know](https://github.com/polcak/jShelterestrictor/issues). + +If you would like to give us [feedback](https://github.com/polcak/jShelterestrictor/issues), we would really appreciate it. + +Once you install the extension, see the [test page](test/test.html) for the working demo on how the +extension can help in restricting JS capabilities. + +## License Information + +This project is available as open source under the terms of the [GPL 3.0 or later](/license). However, some elements are being licensed under MIT license and MPL 2.0 license. For accurate information, please check individual files. As well as for accurate information regarding copyrights. diff --git a/common/wrappingS-H-C.js b/common/wrappingS-H-C.js index c4caa37..f9814dc 100644 --- a/common/wrappingS-H-C.js +++ b/common/wrappingS-H-C.js @@ -36,19 +36,21 @@ /** \file * \ingroup wrappers + * This file contains wrappers for calls related to the Canvas API, about which you can read more at MDN: + * * [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) + * * [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) + * * [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) * * The goal is to prevent fingerprinting by modifying the values that can be read from the canvas. * So the visual content of wrapped canvases as displayed on the screen is the same as intended. * * The modified content can be either an empty image or a fake image that is modified according to * session and domain keys to be different than the original albeit very similar (i.e. the approach - * inspired by the algorithms created by Brave Software - * available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/execution_context/execution_context.cc. + * 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/core/execution_context/execution_context.cc). * * Note that both approaches are detectable by a fingerprinter that checks if a predetermined image - * inserted to the canvas is the same as the read one, see for example, - * https://arkenfox.github.io/TZP/tests/canvasnoise.html. Nevertheless, the aim of the wrappers is - * to limit the finerprintability. + * inserted to the canvas is the same as the read one, see [here](https://arkenfox.github.io/TZP/tests/canvasnoise.html) for an example, + * Nevertheless, the aim of the wrappers is to limit the finerprintability. * * Also note that a determined fingerprinter can reveal the modifications and consequently uncover * the original image. This can be avoided with the approach that completely clears the data stored diff --git a/common/wrappingS-WEBGL.js b/common/wrappingS-WEBGL.js index c4a4693..abce544 100644 --- a/common/wrappingS-WEBGL.js +++ b/common/wrappingS-WEBGL.js @@ -43,9 +43,8 @@ * * Content is either modified according to domain and session keys to be different than the original albeit very similar * or replaced by bottom value which is consistent every time. - * Both approaches are inspired by the algorithms created by Brave Software - * available at [https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc, - * https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc] + * Both 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/webgl/webgl_rendering_context_base.cc) + * and [here](https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc). * * This wrapper operates with two levels of protection: * * (0) - return modified results, such as slightly changed image, slightly changed number or random string diff --git a/docs/build.md b/docs/build.md index 8af7427..7e33a71 100644 --- a/docs/build.md +++ b/docs/build.md @@ -1,29 +1,28 @@ -### Build JSR from scratch +Title: Building from scratch -#### GNU/Linux and Apple Mac OS: +### GNU/Linux and Mac OS 1. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). -1. Download the desired branch, e.g. as zip archive. -1. Unpack the zip archive. -1. Run `make`. +2. Download the desired branch, e.g. as zip archive. +3. Unpack the zip archive. +4. Run `make`. * You will need common software, such as `zip`, `wget`, `bash`, `awk`, `sed`. -1. Import the extension to the browser. +5. Import the extension to the browser. * Firefox: [https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/) * Use the file `firefox_JSR.zip` created by `make`. * Chromium-based browsers: 1. Open `chrome://extensions`. - 1. Enable developper mode. - 1. Click `Load unpacked`. - 1. Import the `chrome_JSR/` directory created by `make`. + 2. Enable developper mode. + 3. Click `Load unpacked`. + 4. Import the `chrome_JSR/` directory created by `make`. + +### Windows -#### Windows: 1. Install Windows Subsystem for Linux (WSL): [https://docs.microsoft.com/en-us/windows/wsl/install-win10](https://docs.microsoft.com/en-us/windows/wsl/install-win10). -1. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). -1. Download the desired branch, e.g. as zip archive. -1. Unpack the zip archive. -1. Open the JSR project folder in WSL, run `make`. +2. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). +3. Download the desired branch, e.g. as zip archive. +4. Unpack the zip archive. +5. Open the JSR project folder in WSL, run `make`. * Make sure that `zip` and all other necessary tools are installed. * Note that EOL in `fix_manifest.sh` must be set to `LF` (you can use the tool `dos2unix` in WSL to convert `CR LF` to `LF`). -1. On Windows, import the extension to the browser according to the instructions for Linux (above). - - +6. On Windows, import the extension to the browser according to the instructions for Linux (above). diff --git a/docs/credits.md b/docs/credits.md index c79f937..3271fdf 100644 --- a/docs/credits.md +++ b/docs/credits.md @@ -1,27 +1,26 @@ -# Developers +Title: Credits -[Libor Polčák](https://www.fit.vutbr.cz/~polcak) was behind an idea to implement a webextension that works as a firewall for JavaScript APIs. He is the current main maintainer. He received support for this project through the JavaScript Restrictor project of NGI0 PET Fund, a fund established by NLnet with financial support - from the European Commission's Next Generation Internet programme, under the aegis of DG - Communications Networks, Content and Technology under grant agreement No 825310. He supervised/supervises diploma theses that improves the web extension. +### Developers -Martin Bednář is working on developping and testing the extension as a part of his Ph.D. research. +**[Libor Polčák](https://www.fit.vutbr.cz/~polcak)** was behind an idea to implement a webextension that works as a firewall for JavaScript APIs. He is the current main maintainer. He received support for this project through the JavaScript Restrictor project of NGI0 PET Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. He supervised/supervises diploma theses that improve the web extension. -Giorgio Maone is working on the extension as a part of the [JS Shield project](https://nlnet.nl/project/JavascriptShield/), for example, he is working on cross-browser support, improvements on code injection and the compatibility between the global JS environment, Workers, and iframes. +**Martin Bednář** is working on developing and testing the extension as a part of his Ph.D. research. -Zbyněk Červinka developped a [proof-of-concept version](https://github.com/cervinka-zbynek/masters-thesis) of this extension as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21274/) (in Czech). +**Giorgio Maone** is working on the extension as a part of the [JS Shield project](https://nlnet.nl/project/JavascriptShield/), for example, he is working on cross-browser support, improvements on code injection and the compatibility between the global JS environment, Workers, and iframes. -Martin Timko developped first public versions upto [0.2.1](https://github.com/polcak/jsrestrictor/releases/tag/0.2.1) as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21824/). He also ported the extension to Chrome and Opera. +**Zbyněk Červinka** developed a [proof-of-concept version](https://github.com/cervinka-zbynek/masters-thesis) of this extension as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21274/) (in Czech). -Pavel Pohner developed the Network Boundary Scanner as a part of his master's thesis. +**Martin Timko** developed first public versions upto [0.2.1](https://github.com/polcak/jsrestrictor/releases/tag/0.2.1) as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21824/). He also ported the extension to Chrome and Opera. -Pater Horňák ported functionality from [Chrome Zero](https://github.com/IAIK/ChromeZero) as a part -of his bachelor thesis. He also provided several small fixes to the code base. +**Pavel Pohner** developed the Network Boundary Scanner as a part of his master's thesis. -Matúš Švancár ported Farbling anti-fingerprinting measures from the Brave browser as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/23310/). +**Pater Horňák** ported functionality from [Chrome Zero](https://github.com/IAIK/ChromeZero) as a part of his bachelor thesis. He also provided several small fixes to the code base. + +**Matúš Švancár** ported Farbling anti-fingerprinting measures from the Brave browser as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/23310/). We thank all other minor contributors of the project that are not listed in this section. -# Key ideas +### Key ideas The development of this extension is influenced by the paper [JavaScript Zero: Real JavaScript and Zero Side-Channel Attacks](https://graz.pure.elsevier.com/de/publications/javascript-zero-real-javascript-and-zero-side-channel-attacks). It appeared during the work of Zbyněk Červinka and provided basically the same approach to restrict APIs as was at the time developed by Zbyněk Červinka. @@ -29,9 +28,9 @@ The [Force Point report](https://www.forcepoint.com/sites/default/files/resource Some of the fingerprinting counter-measures are inspired by [Farbling of the Brave browser](blogarticles/farbling.md). -# Borrowed code +### Borrowed code -We borrowed code from other free software project: +We borrowed code from other free software projects: * [Chrome Zero](https://github.com/IAIK/ChromeZero) * [Brave Farbling](https://github.com/brave/brave-browser/issues/8787) diff --git a/docs/levels.md b/docs/levels.md index b0401f0..5e3b93f 100644 --- a/docs/levels.md +++ b/docs/levels.md @@ -1,4 +1,4 @@ -# The configuration of the extension +Title: Protection levels ## Network Boundary Shield (NBS) @@ -76,4 +76,3 @@ NBS is active independently on the levels defined below. If necessary, you can w * **navigator.sendBeacon --** *Do not send anything and return true* * **Disable Battery status API --** *ON* * **window.name --** *Clear with each page load* - diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..7d8b928 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,674 @@ +Title: GNU General Public License + +Version 3, 29 June 2007 + +Copyright (C) 2007 [Free Software Foundation, Inc.](https://fsf.org/) + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/docs/new-wrapper.md b/docs/new-wrapper.md new file mode 100644 index 0000000..de58230 --- /dev/null +++ b/docs/new-wrapper.md @@ -0,0 +1,286 @@ +Title: How to write a new wrapper + +The primary focus of jShelter is to provide security and privacy oriented wrappers of JavaScript APIs. As some of the code is very similar for each wrapper, we use a unified approach to describe wrappers. The approach is described below. This approach also helps to automatically modify `toString` conversions of the wrapped APIs, i.e. a correctly written wrapper creates a modified function but `wrapper.toString()` returns the original string. Finally, this approach also helps in generating code dealing with the [Firefox bug described in #25](https://github.com/polcak/jShelterestrictor/issues/25). + +## File structure + +* `wrapping.js` provides main facilities for interacting with specific wrappers: + * `add_wrappers()` has to be provided with all the wrappers. Hence, a typical module with wrappers of a web standard APIS. ends with a call of `add_wrappers(list_of_all_wrappers_defined_by_the_module)`. + * `build_wrapping_code` contains all the registered wrappers. The keys are referenced by the levels wrapper list. Do not modify the `build_wrapping_code` variable directly, use `add_wrappers` instead. + * The module also provides common functions used by the wrappers, e.g. `rounding_function` and `noise_function`. +* `wrappingS-XYZ` are files dealing with APIs introduced by specific web or ECMA standard. See the **naming conventions** for details on the XYZ part of the name. See the **wrapper specification** section for the properties of the wrapper objects. + +## Naming conventions + +jShelter adopted the naming conventions of the [Web API Manager](https://github.com/pes10k/web-api-manager/tree/master/sources/standards). See the paper: + +Peter Snyder, Cynthia Taylor, and Chris Kanich, “[Most Websites Don’t Need to Vibrate: A Cost–Benefit Approach to Improving Browser Security](https://arxiv.org/abs/1708.08510),” in Proceedings of the [2017 ACM Conference on Computer and Communications Security](https://www.sigsac.org/ccs/CCS2017/), 2017. + +## Wrapper specification + +Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is: + +```js +(function() { + // definition of common functions used by the wrappers bellow + var wrappers = [ + { + // wrapping object 1 + }, + { + // wrapping object 2 + }, +... + { + // wrapping object n + }, + ] + add_wrappers(wrappers); +})(); +``` + +The content of the module should be in an IIFE so that it does not polute the global namespace. The property values are strings that are generally interpreted either as identifiers or JS code. + +Each wrapping object must have the following mandatory properties: + +* `parent_object` and `parent_object_property` are used to define the name of the wrapping + (`parent_object.parent_object_property`) that is referenced by level wrappers. Additionally, it is +used if `wrapper_prototype` is defined to provide the object name to have the prototype changed. Finally, `Object.freeze` can be optionally called on `parent_object.parent_object_property`. +* `apply_if` optionally provides a condition that needs to be fullfilled to apply the wrapper. For + example if a wrapper should be applied only when an API already provides some information. For + example, `apply_if: "navigator.plugins.length > 0"`. +* `wrapped_objects` is a list of objects, each having the following properties (1 mandatory, 2 + optional, typically, wrappers use one of the optional names in the wrapper code to access the + original result of the call): + * `original_name` (_mandatory_) - the original name of the object to be wrapped. Do not mention `window` here! + * `wrapped_name` - the variable name that points to the actual original object. Wrappers might need it for identity comparisons or other instance-specific use cases. The variable name can be used by `wrapping_function_body`, `helping_code` + and other code fragments to reference the original object. Note that this name is not available + outside the code generated by this wrappper. + * `callable_name` - is similar to the `wrapped_name` but the variable refers to a proxy of the original object. The proxy intercepts calls and automatically marshals parameters and return values. Wrappers MUST define `callable_name` if the object is passed to other code like `Promise` objects or +callbacks. The variable is available only + in the code generate by this wrapper. `callable_name` is specifically **meant to be used for native methods and functions** + which the wrapper needs to call. This is especially important if it **accepts callback arguments or returns `Promise` objects**: + invoking them through their `callable_name` automates complex steps otherwise required for [sandboxed browser extensions to interact with web pages](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). + +Generally speaking, use `wrapped_name` whenever you need to access the original objects only inside +the wrapper and you do not need to pass the object to other code, such as `Promise` objects or +callbacks. Compared to `callable_name`, `wrapped_name` has less overhead and is the preferred way. + +Each wrapping object can have the following optional properties: + +* `wrapping_function_args` is a string that is used as an argument list for the wrapping function. + Typically this string reflects the parameters of the original method, it can be an empty string, a + list of parameters, such as `"source, target, color"` or `"...args"`. +* `wrapping_function_body` specified the behaviour of the wrapped function. You can provide a + completely different implementation but often, you want to refer to the original implementation + (available in the variable `wrapped_name`) and modify the original implementation. +* `wrapping_code_function_name` can be used to call the wrapping function from the other code inside + the wrapper. For example to create another wrapper similar to the original wrapper. +* `wrapper_prototype` - if defined, `parent_object.parent_object_property` prototype is set to the +prototype identifier provided by `wrapper_prototype`. +* `original_function` if not provided, it defaults to `parent_object.parent_object_property`. This + name is used for overloading the `toString` function. Instead of the wrapping code, `toString` + returns the content of the original function. +* `helping_code` provides you an option to define code available for both `replacement` function and + `post_wrapping_code` +* `replace_original_function` is used to control which function should be replaced +* `post_replacement_code` Allows to provide additional code with the access to the original function + and to the wrapped function +* `freeze` if set to `true` causes the API to be freezed. It should not be necessary if the wrapping is performed at the right level of the object's prototype chain (i.e. where the property to be modified was originally defined, i.e. usually in the object's prototype). Use this option with caution, only if strictly needed, because native APIs are configurable (otherwise our wrapping couldn't work) and therefore freezing them introduces a "weirdness", exploitable for fingerprinting. +* `post_wrapping_code` is a list of additional wrapping objects with a similar structure to the + wrappers, see the section bellow. + +### Post wrapping code + +Complex wrappers need to provide additional wrapping code. + +You can make the generation of the post wrapping code generation conditional by using `apply_if`, e.g.: +```js + { + ... + apply_if: "!enableGeolocation", + } +``` +(`enableGeolocation` is a Booloean variable) + +Currently jShelter supports additional wrapping of: + +* Definition of a function (used, for example, to reintroduce `Date.now()` function to the wrapped + `Date` object) +```js +[ + { + code_type: "function_define", + original_function: "originalDateConstructor.now", + parent_object: "window.Date", + parent_object_property: "now", + wrapping_function_args: "", + wrapping_function_body: "return func(originalDateConstructor.now.call(Date), precision);", + }, +] +``` +* Export a function from the wrapping namespace (currently not used, the following code exposes the + unwrapped, i.e. original, version of Date.now to page scripts) +```js +[ + { + code_type: "function_export", + parent_object: "window.Date", + parent_object_property: "now", + export_function_name: "originalDateConstructor.now", + }, +] +``` +* Redefine an object property (used to prevent leaking iframe properties to the unwrapped objects): +```js +{ + code_type: "object_properties", + parent_object: "HTMLIFrameElement.prototype", + parent_object_property: "contentWindow", + wrapped_objects: [ + { + original_name: "HTMLIFrameElement.prototype.__lookupGetter__('contentWindow')", + wrapped_name: "cw", + }, + ], + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + var parent=cw.call(this); + try { + parent.HTMLCanvasElement; + } + catch(d) { + return; // HTMLIFrameElement.contentWindow properties could not be accessed anyway + } + wrapping(parent); + return parent; + }`, + }, + ], +} +``` +* Deleting a property (used when you want to completely disable an API): +```js + { + code_type: "delete_properties", + parent_object: "navigator", + apply_if: "!enableGeolocation", + delete_properties: ["geolocation"], + } +``` +### The WrapHelper API + +A `WrapHelper` object is globally available to wrappers code, exposing some methods and properties which are mostly +used internally by the code builders to automate tasks such as handling [Firefox's content script sandbox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) or making replacement objects look as native as possible. +However a few of them may be useful to complex wrappers or in edge case not covered by `callable_name` and other declarative object replacement / property definition wrapper constructs: + +* `WrapHelper.shared: {}` - a "bare" JavaScript object which a wrapper can use to share information with other wrappers + (e.g. to coordinate behavior between related parts of the same API requiring multiple wrappings) + by attaching its own data objects as properties. __Warning__: namespacing is not enforced and up to the wrapper implementor, but obviously recommended. +* `WrapHelper.overlay(obj, data)` - Proxies the prototype of the `obj` object in order to return the properties of the `data` object + as if they were native properties (e.g. as if they were returned by getters on the prototype chain, + rather than defined on the instance). This allows spoofing some native objects data in a less detectable / fingerprintable way + than by using `Object.defineProperty()`. See `wrappingS-MCS.js` for an example. +* `WrapHelper.forPage(obj)` - it's mostly used internally and transparently by `code_builder.js`, + but may be useful to very complex proxies in edge cases needing to explicitly + prepare an object/function created in [Firefox's sandboxed content script environment](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) to be consumed/called from the page context, and to make replacements for native + objects and functions provided by the wrappers look as much native as possible (on Chromium, too). + In most cases, however, this gets automated by the code builders replacing + Object methods with their WrapHelper counterpart in the wrapper sources + and by proxying "callable_name" function references through `WrapHelper.pageAPI()` (see below). +* `WrapHelper.pageAPI(f)` - proxies the function/method f so that arguments and return values, and especially callbacks and + `Promise` objects, are recursively managed in order to transparently marshal objects back and forth + [Firefox's sandbox for extensions (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). + __Wrapper implementors should almost never need to use this API directly__, since any function referenced via its "callable_name" + goes automatically through it. + +### The generated wrapper structure + +To get a better idea how the code is generated see the following pseudo code. Please do not refer to +any name created by the code builders from your wrapper. Use your custom names. If it is not +available, please, open an issue where you explain what you are trying to achieve. It is probable +that we will introduce a new property that allows to provide your name to the code builders. The +code lives in an anonymous namespaces, so variables introduced here do not directly leak to page +scripts. + +```js +// Define wrapped_name(s) variables holding the original JS objects +helping_code // if present +function wrapping_code_function_name(param) { + // Store original function: either original_function which defaults to parent_object.parent_object_property + function replacement(wrapping_function_args) { + wrapping_function_body + } + if (replace_original_function) + original_function = replacement + else + parent_object.parent_object_property = replacement + // Modify toString + post_replacement_code +} +wrapping_code_function_name(window if wrapping_code_function_call_window) // The function is called even if the name is not explicitely provided +// wrappings generated by post wrapping code +``` + +## Compiling the wrappers + +Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See `code_builders.js`. + +## Registering a new wrapper + +`fix_manifest.sh` automatically adds all modules with file name of `wrapping*.js` to the manifest.json of the extension. There is no need for any additional action. + +## Register new wrapper to be used by the extension in a level or available in the GUI. + +See `levels.js` and its list `wrapping_groups.groups`. Once you add your wrapper to an existing group or create a new group, the wrapper becomes available in the built-in levels containing the group and in the GUI for custom levels. + +## Describe the wrapper for Doygen documentation + +Describe what the wrapper tries to accomplish and its approach: + +* Add Doxygen comment at the top of the file with the following structure: + +```js +/** \file + * \brief This file contains wrappers for the X API (link to the standard or MDN) + * \ingroup wrappers + * + * Put at least one paragraph describing the goal of the wrapper. Describe a possible attack vector. + * Link to further resources/readings. + * + * Describe the options of the wrapper. Mention examples when a user should want to use the + * available options. + * + * Describe any \note, \bug ór use other suitable Doxygen commands + * (https://www.doxygen.nl/manual/commands.html) + */ +``` + +* Describe helping functions and `wrapping_function_body` + * Use Doxygen command `\fn fake wrapped.object` for each `wrapping_function_body` + * Use the following structure for function comments: + +```js +/** + * \brief Short description + * + * \param X Descripton + * \param Y Descripton + * + * \returns Description + * + * Main description of the aim of the function, algorithm, etc. + * + * Describe any \note, \bug ór use other suitable Doxygen commands + * (https://www.doxygen.nl/manual/commands.html) + */ +``` + +## Write unit tests or integgration tests for the wrapper + +Follow instructions for [unit testing](md_tests_unit_tests_README.html) and +[integration testing](md_tests_integration_tests_README.html) (see the `tests` directory in the +repository). diff --git a/docs/new_wrapper.md b/docs/new_wrapper.md deleted file mode 100644 index dc72168..0000000 --- a/docs/new_wrapper.md +++ /dev/null @@ -1,286 +0,0 @@ -# How to write a new wrapper? - -The primary focus of JSR is to provide security and privacy oriented wrappers of JavaScript APIs. As some of the code is very similar for each wrapper, we use a unified approach to describe wrappers. The approach is described below. This approach also helps to automatically modify `toString` conversions of the wrapped APIs, i.e. a correctly written wrapper creates a modified function but `wrapper.toString()` returns the original string. Finally, this approach also helps in generating code dealing with the [Firefox bug described in #25](https://github.com/polcak/jsrestrictor/issues/25). - -## File structure - -* `wrapping.js` provides main facilities for interacting with specific wrappers: - * `add_wrappers()` has to be provided with all the wrappers. Hence, a typical module with wrappers of a web standard APIS. ends with a call of `add_wrappers(list_of_all_wrappers_defined_by_the_module)`. - * `build_wrapping_code` contains all the registered wrappers. The keys are referenced by the levels wrapper list. Do not modify the `build_wrapping_code` variable directly, use `add_wrappers` instead. - * The module also provides common functions used by the wrappers, e.g. `rounding_function` and `noise_function`. -* `wrappingS-XYZ` are files dealing with APIs introduced by specific web or ECMA standard. See the **naming conventions** for details on the XYZ part of the name. See the **wrapper specification** section for the properties of the wrapper objects. - -## Naming conventions - -JSR adopted the naming conventions of the [Web API Manager](https://github.com/pes10k/web-api-manager/tree/master/sources/standards). See the paper: - -Peter Snyder, Cynthia Taylor, and Chris Kanich, “[Most Websites Don’t Need to Vibrate: A Cost–Benefit Approach to Improving Browser Security](https://arxiv.org/abs/1708.08510),” in Proceedings of the [2017 ACM Conference on Computer and Communications Security](https://www.sigsac.org/ccs/CCS2017/), 2017. - -## Wrapper specification - -Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is: - -```js -(function() { - // definition of common functions used by the wrappers bellow - var wrappers = [ - { - // wrapping object 1 - }, - { - // wrapping object 2 - }, -... - { - // wrapping object n - }, - ] - add_wrappers(wrappers); -})(); -``` - -The content of the module should be in an IIFE so that it does not polute the global namespace. The property values are strings that are generally interpreted either as identifiers or JS code. - -Each wrapping object must have the following mandatory properties: - -* `parent_object` and `parent_object_property` are used to define the name of the wrapping - (`parent_object.parent_object_property`) that is referenced by level wrappers. Additionally, it is -used if `wrapper_prototype` is defined to provide the object name to have the prototype changed. Finally, `Object.freeze` can be optionally called on `parent_object.parent_object_property`. -* `apply_if` optionally provides a condition that needs to be fullfilled to apply the wrapper. For - example if a wrapper should be applied only when an API already provides some information. For - example, `apply_if: "navigator.plugins.length > 0"`. -* `wrapped_objects` is a list of objects, each having the following properties (1 mandatory, 2 - optional, typically, wrappers use one of the optional names in the wrapper code to access the - original result of the call): - * `original_name` (_mandatory_) - the original name of the object to be wrapped. Do not mention `window` here! - * `wrapped_name` - the variable name that points to the actual original object. Wrappers might need it for identity comparisons or other instance-specific use cases. The variable name can be used by `wrapping_function_body`, `helping_code` - and other code fragments to reference the original object. Note that this name is not available - outside the code generated by this wrappper. - * `callable_name` - is similar to the `wrapped_name` but the variable refers to a proxy of the original object. The proxy intercepts calls and automatically marshals parameters and return values. Wrappers MUST define `callable_name` if the object is passed to other code like `Promise` objects or -callbacks. The variable is available only - in the code generate by this wrapper. `callable_name` is specifically **meant to be used for native methods and functions** - which the wrapper needs to call. This is especially important if it **accepts callback arguments or returns `Promise` objects**: - invoking them through their `callable_name` automates complex steps otherwise required for [sandboxed browser extensions to interact with web pages](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). - -Generally speaking, use `wrapped_name` whenever you need to access the original objects only inside -the wrapper and you do not need to pass the object to other code, such as `Promise` objects or -callbacks. Compared to `callable_name`, `wrapped_name` has less overhead and is the preferred way. - -Each wrapping object can have the following optional properties: - -* `wrapping_function_args` is a string that is used as an argument list for the wrapping function. - Typically this string reflects the parameters of the original method, it can be an empty string, a - list of parameters, such as `"source, target, color"` or `"...args"`. -* `wrapping_function_body` specified the behaviour of the wrapped function. You can provide a - completely different implementation but often, you want to refer to the original implementation - (available in the variable `wrapped_name`) and modify the original implementation. -* `wrapping_code_function_name` can be used to call the wrapping function from the other code inside - the wrapper. For example to create another wrapper similar to the original wrapper. -* `wrapper_prototype` - if defined, `parent_object.parent_object_property` prototype is set to the -prototype identifier provided by `wrapper_prototype`. -* `original_function` if not provided, it defaults to `parent_object.parent_object_property`. This - name is used for overloading the `toString` function. Instead of the wrapping code, `toString` - returns the content of the original function. -* `helping_code` provides you an option to define code available for both `replacement` function and - `post_wrapping_code` -* `replace_original_function` is used to control which function should be replaced -* `post_replacement_code` Allows to provide additional code with the access to the original function - and to the wrapped function -* `freeze` if set to `true` causes the API to be freezed. It should not be necessary if the wrapping is performed at the right level of the object's prototype chain (i.e. where the property to be modified was originally defined, i.e. usually in the object's prototype). Use this option with caution, only if strictly needed, because native APIs are configurable (otherwise our wrapping couldn't work) and therefore freezing them introduces a "weirdness", exploitable for fingerprinting. -* `post_wrapping_code` is a list of additional wrapping objects with a similar structure to the - wrappers, see the section bellow. - -### Post wrapping code - -Complex wrappers need to provide additional wrapping code. - -You can make the generation of the post wrapping code generation conditional by using `apply_if`, e.g.: -```js - { - ... - apply_if: "!enableGeolocation", - } -``` -(`enableGeolocation` is a Booloean variable) - -Currently JSR supports additional wrapping of: - -* Definition of a function (used, for example, to reintroduce `Date.now()` function to the wrapped - `Date` object) -```js -[ - { - code_type: "function_define", - original_function: "originalDateConstructor.now", - parent_object: "window.Date", - parent_object_property: "now", - wrapping_function_args: "", - wrapping_function_body: "return func(originalDateConstructor.now.call(Date), precision);", - }, -] -``` -* Export a function from the wrapping namespace (currently not used, the following code exposes the - unwrapped, i.e. original, version of Date.now to page scripts) -```js -[ - { - code_type: "function_export", - parent_object: "window.Date", - parent_object_property: "now", - export_function_name: "originalDateConstructor.now", - }, -] -``` -* Redefine an object property (used to prevent leaking iframe properties to the unwrapped objects): -```js -{ - code_type: "object_properties", - parent_object: "HTMLIFrameElement.prototype", - parent_object_property: "contentWindow", - wrapped_objects: [ - { - original_name: "HTMLIFrameElement.prototype.__lookupGetter__('contentWindow')", - wrapped_name: "cw", - }, - ], - wrapped_properties: [ - { - property_name: "get", - property_value: ` - function() { - var parent=cw.call(this); - try { - parent.HTMLCanvasElement; - } - catch(d) { - return; // HTMLIFrameElement.contentWindow properties could not be accessed anyway - } - wrapping(parent); - return parent; - }`, - }, - ], -} -``` -* Deleting a property (used when you want to completely disable an API): -```js - { - code_type: "delete_properties", - parent_object: "navigator", - apply_if: "!enableGeolocation", - delete_properties: ["geolocation"], - } -``` -### The WrapHelper API - -A `WrapHelper` object is globally available to wrappers code, exposing some methods and properties which are mostly -used internally by the code builders to automate tasks such as handling [Firefox's content script sandbox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) or making replacement objects look as native as possible. -However a few of them may be useful to complex wrappers or in edge case not covered by `callable_name` and other declarative object replacement / property definition wrapper constructs: - -* `WrapHelper.shared: {}` - a "bare" JavaScript object which a wrapper can use to share information with other wrappers - (e.g. to coordinate behavior between related parts of the same API requiring multiple wrappings) - by attaching its own data objects as properties. __Warning__: namespacing is not enforced and up to the wrapper implementor, but obviously recommended. -* `WrapHelper.overlay(obj, data)` - Proxies the prototype of the `obj` object in order to return the properties of the `data` object - as if they were native properties (e.g. as if they were returned by getters on the prototype chain, - rather than defined on the instance). This allows spoofing some native objects data in a less detectable / fingerprintable way - than by using `Object.defineProperty()`. See `wrappingS-MCS.js` for an example. -* `WrapHelper.forPage(obj)` - it's mostly used internally and transparently by `code_builder.js`, - but may be useful to very complex proxies in edge cases needing to explicitly - prepare an object/function created in [Firefox's sandboxed content script environment](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) to be consumed/called from the page context, and to make replacements for native - objects and functions provided by the wrappers look as much native as possible (on Chromium, too). - In most cases, however, this gets automated by the code builders replacing - Object methods with their WrapHelper counterpart in the wrapper sources - and by proxying "callable_name" function references through `WrapHelper.pageAPI()` (see below). -* `WrapHelper.pageAPI(f)` - proxies the function/method f so that arguments and return values, and especially callbacks and - `Promise` objects, are recursively managed in order to transparently marshal objects back and forth - [Firefox's sandbox for extensions (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). - __Wrapper implementors should almost never need to use this API directly__, since any function referenced via its "callable_name" - goes automatically through it. - -### The generated wrapper structure - -To get a better idea how the code is generated see the following pseudo code. Please do not refer to -any name created by the code builders from your wrapper. Use your custom names. If it is not -available, please, open an issue where you explain what you are trying to achieve. It is probable -that we will introduce a new property that allows to provide your name to the code builders. The -code lives in an anonymous namespaces, so variables introduced here do not directly leak to page -scripts. - -```js -// Define wrapped_name(s) variables holding the original JS objects -helping_code // if present -function wrapping_code_function_name(param) { - // Store original function: either original_function which defaults to parent_object.parent_object_property - function replacement(wrapping_function_args) { - wrapping_function_body - } - if (replace_original_function) - original_function = replacement - else - parent_object.parent_object_property = replacement - // Modify toString - post_replacement_code -} -wrapping_code_function_name(window if wrapping_code_function_call_window) // The function is called even if the name is not explicitely provided -// wrappings generated by post wrapping code -``` - -## Compiling the wrappers - -Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See `code_builders.js`. - -## Registering a new wrapper - -`fix_manifest.sh` automatically adds all modules with file name of `wrapping*.js` to the manifest.json of the extension. There is no need for any additional action. - -## Register new wrapper to be used by the extension in a level or available in the GUI. - -See `levels.js` and its list `wrapping_groups.groups`. Once you add your wrapper to an existing group or create a new group, the wrapper becomes available in the built-in levels containing the group and in the GUI for custom levels. - -## Describe the wrapper for Doygen documentation - -Describe what the wrapper tries to accomplish and its approach: - -* Add Doxygen comment at the top of the file with the following structure: - -```js -/** \file - * \brief This file contains wrappers for the X API (link to the standard or MDN) - * \ingroup wrappers - * - * Put at least one paragraph describing the goal of the wrapper. Describe a possible attack vector. - * Link to further resources/readings. - * - * Describe the options of the wrapper. Mention examples when a user should want to use the - * available options. - * - * Describe any \note, \bug ór use other suitable Doxygen commands - * (https://www.doxygen.nl/manual/commands.html) - */ -``` - -* Describe helping functions and `wrapping_function_body` - * Use Doxygen command `\fn fake wrapped.object` for each `wrapping_function_body` - * Use the following structure for function comments: - -```js -/** - * \brief Short description - * - * \param X Descripton - * \param Y Descripton - * - * \returns Description - * - * Main description of the aim of the function, algorithm, etc. - * - * Describe any \note, \bug ór use other suitable Doxygen commands - * (https://www.doxygen.nl/manual/commands.html) - */ -``` - -## Write unit tests or integgration tests for the wrapper - -Follow instructions for [unit testing](md_tests_unit_tests_README.html) and -[integration testing](md_tests_integration_tests_README.html) (see the `tests` directory in the -repository). diff --git a/docs/permissions.md b/docs/permissions.md index 7b17c9e..beaf2fa 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -1,14 +1,11 @@ -# Permissions +Title: Permissions -*Javascript Restrictor* requires these permissions: - * **storage --** *used for storing extension configuration and user options* - * **tabs --** *used for updating icon badge of the extension on tab change* - * **webRequest, webRequestBlocking, and all_urls --** *needed for modyfing JavaScript objects and APIs on all pages and also used for capturing and blocking malicious HTTP requests (Network Boundary Shield)* - * **webNavigation** *needed for modyfing JavaScript objects and APIs as early as possible* - * **dns --** *used by Network Boundary Shield to determine if a domain belongs to local network or - not* - * **notifications--** *used for notifying user on blocked HTTP requests/hosts* +JShelter requires these permissions: -*JavaScript Resctictor* stores all configuration data in the browser or in the user account. It does -not upload any data to our servers. + * **storage**: for storing extension configuration and user options + * **tabs**: for updating the extension's icon badge on tab change + * **webRequest, webRequestBlocking, all_urls**: for modifying JavaScript objects and APIs on all pages, and for capturing and blocking malicious HTTP requests + * **dns**: for resolving DNS queries in Firefox version of HTTP request shield + * **notifications**: for notifying users on blocked HTTP requests/hosts +jShelter stores all configuration data in the browser or in the user account. It does not upload any data to our servers. diff --git a/docs/versions.md b/docs/versions.md index 2272ac4..85fe234 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -1,5 +1,4 @@ - -# Release history +Title: Release history ## 0.5.2 diff --git a/doxyfile b/doxyfile index 9ea4940..90a2a6f 100644 --- a/doxyfile +++ b/doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.16 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -158,7 +158,7 @@ INLINE_INHERITED_MEMB = NO # shortest path that makes the file name unique will be used # The default value is: YES. -FULL_PATH_NAMES = YES +FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand @@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -263,12 +271,6 @@ TAB_SIZE = 2 ALIASES = "license=@par License:^^" -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -309,21 +311,25 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = js=Javascript,no_extension=md +EXTENSION_MAPPING = js=Javascript \ + no_extension=md # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -455,6 +461,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -518,6 +537,13 @@ EXTRACT_LOCAL_METHODS = YES EXTRACT_ANON_NSPACES = YES +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -535,8 +561,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -555,11 +581,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -798,7 +831,10 @@ WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -829,13 +865,19 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = common firefox chrome docs tests CODING_STYLE LICENSE.md +INPUT = common \ + firefox \ + chrome \ + docs \ + tests \ + CODING_STYLE \ + LICENSE.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -848,15 +890,19 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.js \ *.json \ - *.md + *.md # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -911,7 +957,7 @@ EXAMPLE_PATH = # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -1068,6 +1114,44 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1079,13 +1163,6 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1224,9 +1301,9 @@ HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1256,10 +1333,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1301,8 +1379,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1332,7 +1410,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1377,7 +1455,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1385,8 +1464,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1394,16 +1473,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1415,9 +1494,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1494,6 +1573,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1514,8 +1604,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1527,7 +1623,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1543,7 +1639,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ @@ -1557,7 +1653,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1585,7 +1682,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1604,7 +1701,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1617,8 +1715,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1782,9 +1881,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2216,7 +2317,7 @@ HIDE_UNDOC_RELATIONS = NO # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = YES @@ -2295,10 +2396,32 @@ UML_LOOK = YES # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 20 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2372,7 +2495,9 @@ DIRECTORY_GRAPH = YES # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. @@ -2488,9 +2613,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/logos_images/icons_chrome/icon128.png b/logos_images/icons_chrome/icon128.png new file mode 100644 index 0000000..4943449 Binary files /dev/null and b/logos_images/icons_chrome/icon128.png differ diff --git a/logos_images/icons_chrome/icon128.svg b/logos_images/icons_chrome/icon128.svg new file mode 100644 index 0000000..7fa476e --- /dev/null +++ b/logos_images/icons_chrome/icon128.svg @@ -0,0 +1,163 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logos_images/icons_chrome/icon16.png b/logos_images/icons_chrome/icon16.png new file mode 100644 index 0000000..9756393 Binary files /dev/null and b/logos_images/icons_chrome/icon16.png differ diff --git a/logos_images/icons_chrome/icon16.svg b/logos_images/icons_chrome/icon16.svg new file mode 100644 index 0000000..3817976 --- /dev/null +++ b/logos_images/icons_chrome/icon16.svg @@ -0,0 +1,137 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/logos_images/icons_chrome/icon48.png b/logos_images/icons_chrome/icon48.png new file mode 100644 index 0000000..b9d8647 Binary files /dev/null and b/logos_images/icons_chrome/icon48.png differ diff --git a/logos_images/icons_chrome/icon48.svg b/logos_images/icons_chrome/icon48.svg new file mode 100644 index 0000000..bc36ea7 --- /dev/null +++ b/logos_images/icons_chrome/icon48.svg @@ -0,0 +1,168 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logos_images/icons_firefox/icon.png b/logos_images/icons_firefox/icon.png new file mode 100644 index 0000000..b9d8647 Binary files /dev/null and b/logos_images/icons_firefox/icon.png differ diff --git a/logos_images/icons_firefox/icon.svg b/logos_images/icons_firefox/icon.svg new file mode 100644 index 0000000..bc36ea7 --- /dev/null +++ b/logos_images/icons_firefox/icon.svg @@ -0,0 +1,168 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logos_images/icons_firefox/icon@2x.png b/logos_images/icons_firefox/icon@2x.png new file mode 100644 index 0000000..d4c8117 Binary files /dev/null and b/logos_images/icons_firefox/icon@2x.png differ diff --git a/logos_images/icons_firefox/icon@2x.svg b/logos_images/icons_firefox/icon@2x.svg new file mode 100644 index 0000000..ff21e3b --- /dev/null +++ b/logos_images/icons_firefox/icon@2x.svg @@ -0,0 +1,174 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/Makefile b/website/Makefile new file mode 100644 index 0000000..b76961a --- /dev/null +++ b/website/Makefile @@ -0,0 +1,91 @@ +PY?=python3 +ACTIVATE=. `pwd`/.env/bin/activate +PELICAN?=$(ACTIVATE); pelican +PELICANOPTS= + +BASEDIR=$(CURDIR) +INPUTDIR=$(BASEDIR)/content +OUTPUTDIR=$(BASEDIR)/output +CONFFILE=$(BASEDIR)/pelicanconf.py +PUBLISHCONF=$(BASEDIR)/publishconf.py + + +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + PELICANOPTS += -D +endif + +RELATIVE ?= 0 +ifeq ($(RELATIVE), 1) + PELICANOPTS += --relative-urls +endif + +SERVER ?= "0.0.0.0" + +PORT ?= 0 +ifneq ($(PORT), 0) + PELICANOPTS += -p $(PORT) +endif + + +help: + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' make regenerate regenerate files upon modification ' + @echo ' make publish generate using production settings ' + @echo ' make serve [PORT=8000] serve site at http://localhost:8000' + @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' + @echo ' make devserver [PORT=8000] serve and regenerate together ' + @echo ' make devserver-global regenerate and serve on 0.0.0.0 ' + @echo ' ' + @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' + @echo 'Set the RELATIVE variable to 1 to enable relative urls ' + @echo ' ' + +install: + virtualenv .env --python=/usr/bin/python3 + $(ACTIVATE); pip install -r requirements.txt + +extract: + $(ACTIVATE); python extract_comments.py + +html: + cd ../docs; cp -f build.md credits.md levels.md permissions.md versions.md license.md ../website/content/pages + cd ../docs; cp -f new-wrapper.md ../website/content/pages + cp -r favicon.ico output/ + $(ACTIVATE); python extract_comments.py + $(ACTIVATE); $(PELICAN) "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) + # fix translated indexes to address a Pelican bug + cd content/pages; for lang in */; do mv ../../output/$$lang/$$lang/index.html ../../output/$$lang; rm -f ../../output/$$lang/$$lang/index.html; done + +clean: + [ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)" + +regenerate: + $(ACTIVATE); $(PELICAN) -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) + +serve: + $(ACTIVATE); $(PELICAN) -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) + +serve-global: + $(ACTIVATE); $(PELICAN) -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER) + +devserver: + $(ACTIVATE); $(PELICAN) -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) + +devserver-global: + $(ACTIVATE); $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0 + +publish: + $(ACTIVATE); $(PELICAN) "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS) + +deploy: html + rsync -e "ssh -p 22" -P -rvzc output/ root@jshelter.org:/var/www/html/ --cvs-exclude + +dry-deploy: html + rsync -n -e "ssh -p 22" -P -rvzc output/ root@jshelter.org:/var/www/html/ --cvs-exclude + +.PHONY: html help clean regenerate serve serve-global devserver publish diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..f1cfd6c --- /dev/null +++ b/website/README.md @@ -0,0 +1,33 @@ +# jShelter website + +This site runs on [Pelican](https://getpelican.com). + +## Editing content + +Before starting, it's important to keep in mind that: + +- Wrapper pages (all content inside `content/wrappers`) are automatically + generated from the source code comments, so these shouldn't be edited + directly -- edit the original source comments instead (inside the `common/` + project directory) +- Other pages are copied from the project's `docs/` dir, so be sure to edit + those instead of the files inside `content/pages`. + +## Adding new pages + +## Translating content + +Translated content goes inside the subfolders of the `content/` directory using +the language code. For instance, pages translated to Spanish should be placed +inside `content/pages/es/`, taking care to keep the file names and the metadata +names (e.g. `Title:`) intact. The site generator will gather the files and put +them in their place inside the site structure. + +## Generating the site + +To make a local copy of the web site, follow these steps: + +1. Install the dependencies with `make install` +2. Generate the site with `make html` +3. Run the local webserver with `make serve` and go to `http://localhost:8000` + in your browser diff --git a/website/content/images/crawling-apis.png b/website/content/images/crawling-apis.png new file mode 100644 index 0000000..4cf5c6d Binary files /dev/null and b/website/content/images/crawling-apis.png differ diff --git a/website/content/images/crawling-architecture.png b/website/content/images/crawling-architecture.png new file mode 100644 index 0000000..f0c6d4f Binary files /dev/null and b/website/content/images/crawling-architecture.png differ diff --git a/website/content/images/portscan-1_captured_traffic.png b/website/content/images/portscan-1_captured_traffic.png new file mode 100644 index 0000000..2a925fd Binary files /dev/null and b/website/content/images/portscan-1_captured_traffic.png differ diff --git a/website/content/images/portscan-2_request_blocked.png b/website/content/images/portscan-2_request_blocked.png new file mode 100644 index 0000000..84fa60d Binary files /dev/null and b/website/content/images/portscan-2_request_blocked.png differ diff --git a/website/content/pages/build.md b/website/content/pages/build.md new file mode 100644 index 0000000..7e33a71 --- /dev/null +++ b/website/content/pages/build.md @@ -0,0 +1,28 @@ +Title: Building from scratch + +### GNU/Linux and Mac OS + +1. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). +2. Download the desired branch, e.g. as zip archive. +3. Unpack the zip archive. +4. Run `make`. + * You will need common software, such as `zip`, `wget`, `bash`, `awk`, `sed`. +5. Import the extension to the browser. + * Firefox: [https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/) + * Use the file `firefox_JSR.zip` created by `make`. + * Chromium-based browsers: + 1. Open `chrome://extensions`. + 2. Enable developper mode. + 3. Click `Load unpacked`. + 4. Import the `chrome_JSR/` directory created by `make`. + +### Windows + +1. Install Windows Subsystem for Linux (WSL): [https://docs.microsoft.com/en-us/windows/wsl/install-win10](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +2. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). +3. Download the desired branch, e.g. as zip archive. +4. Unpack the zip archive. +5. Open the JSR project folder in WSL, run `make`. + * Make sure that `zip` and all other necessary tools are installed. + * Note that EOL in `fix_manifest.sh` must be set to `LF` (you can use the tool `dos2unix` in WSL to convert `CR LF` to `LF`). +6. On Windows, import the extension to the browser according to the instructions for Linux (above). diff --git a/website/content/pages/coding-style.md b/website/content/pages/coding-style.md new file mode 100644 index 0000000..39013f1 --- /dev/null +++ b/website/content/pages/coding-style.md @@ -0,0 +1,67 @@ +Title: Coding style + +### General guidelines + +- Use **tabulators** for indentation, and avoid spaces/tabs at the end of the line. +- Functions, methods, classes, ifs, cycles etc. have opening braces at the same line. +- Text width preferred less than 80 characters, maximum 100 characters. It is better having readable code than pursuing this limit. +- Always enclose blocks of expressions into brackets. +- Names are lower case and preferably explain the purpose of the variable, class, etc. +- Comment classes, functions, etc. in Doxygen style. Use `make doc` to generate documentation. + +### Code examples + +Correct code example: + +```javascript +/** + * This is an example function created for the coding style manual. + * + * @param abc The number that will be ... + * @returns The Answer to the Ultimate Question of Life, The Universe, and Everything. + */ +function example(abc) { + var counter = 0; + for (let i = 0; i < 42; i++) { + counter++; + } + return counter; +}; +``` + +Bad code example: + +```javascript +function example(abc) +{ + function method() + { + return 42; + } + return method(); +}; +``` + +### Version control workflow + +- Do not provide commits dealing with bad coding style. The only exception is if + you want to improve code that does not follow the coding style rules. + Preferably provide one commit that fixes the issues and another (others) that + improve the code, add new functionality etc. +- Atomic commits are useful, see [this + page](https://www.freshconsulting.com/atomic-commits/) for help. +- A commit should not contain adding missing semicolons, changes in generated + code, a bugfix, and addition of a new functionality. Each of these changes + should go to a separate commit with a message explaining why is the change + necessary (if it is not obvious like in the case of missing semicolons). +- Provide meaningful commit messages. For help, see points 1, 2 and 7 of [this + guide](https://chris.beams.io/posts/git-commit/) +- Do not fear changing commits that are not public, yet. If you create a bug and + find it before merge, it is better to fix the bug in the original commit. + Available Git operations for this are `rebase (-i)`, `fixup`, `squash` and + `push --force`. +- This [pull request]() contains an example of big commits that needed to be refactored. +- Provide merge request more often rather than commiting big changes. If you fix + Makefile or other scripts, provide the change and do not wait. Create code + that is understandable and does not repeat itself. If possible, use variables + instead of copying the same code. diff --git a/website/content/pages/credits.md b/website/content/pages/credits.md new file mode 100644 index 0000000..3271fdf --- /dev/null +++ b/website/content/pages/credits.md @@ -0,0 +1,40 @@ +Title: Credits + +### Developers + +**[Libor Polčák](https://www.fit.vutbr.cz/~polcak)** was behind an idea to implement a webextension that works as a firewall for JavaScript APIs. He is the current main maintainer. He received support for this project through the JavaScript Restrictor project of NGI0 PET Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. He supervised/supervises diploma theses that improve the web extension. + +**Martin Bednář** is working on developing and testing the extension as a part of his Ph.D. research. + +**Giorgio Maone** is working on the extension as a part of the [JS Shield project](https://nlnet.nl/project/JavascriptShield/), for example, he is working on cross-browser support, improvements on code injection and the compatibility between the global JS environment, Workers, and iframes. + +**Zbyněk Červinka** developed a [proof-of-concept version](https://github.com/cervinka-zbynek/masters-thesis) of this extension as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21274/) (in Czech). + +**Martin Timko** developed first public versions upto [0.2.1](https://github.com/polcak/jsrestrictor/releases/tag/0.2.1) as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/21824/). He also ported the extension to Chrome and Opera. + +**Pavel Pohner** developed the Network Boundary Scanner as a part of his master's thesis. + +**Pater Horňák** ported functionality from [Chrome Zero](https://github.com/IAIK/ChromeZero) as a part of his bachelor thesis. He also provided several small fixes to the code base. + +**Matúš Švancár** ported Farbling anti-fingerprinting measures from the Brave browser as a part of his [master's thesis](https://www.fit.vut.cz/study/thesis/23310/). + +We thank all other minor contributors of the project that are not listed in this section. + +### Key ideas + +The development of this extension is influenced by the paper [JavaScript Zero: Real JavaScript and Zero Side-Channel Attacks](https://graz.pure.elsevier.com/de/publications/javascript-zero-real-javascript-and-zero-side-channel-attacks). It appeared during the work of Zbyněk Červinka and provided basically the same approach to restrict APIs as was at the time developed by Zbyněk Červinka. + +The [Force Point report](https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf) was a key inspiration for the development of the Network Boundary Shield. + +Some of the fingerprinting counter-measures are inspired by [Farbling of the Brave browser](blogarticles/farbling.md). + +### Borrowed code + +We borrowed code from other free software projects: + +* [Chrome Zero](https://github.com/IAIK/ChromeZero) +* [Brave Farbling](https://github.com/brave/brave-browser/issues/8787) +* [NoScript Common Library](https://github.com/hackademix/nscl/) +* [Typed array polyfill](https://github.com/inexorabletash/polyfill/blob/master/typedarray.js), + Copyright (c) 2010, Linden Research, Inc., Copyright (c) 2014, Joshua Bell +* [PRNG Alea](https://github.com/nquinlan/better-random-numbers-for-javascript-mirror) by (C) 2010 Johannes Baagøe diff --git a/website/content/pages/home.md b/website/content/pages/home.md new file mode 100644 index 0000000..8cb33e2 --- /dev/null +++ b/website/content/pages/home.md @@ -0,0 +1,120 @@ +Title: Home +Template: home +save_as: index.html +URL: + +
+
+ +

An anti-malware Web browser extension to mitigate potential + threats from JavaScript, including fingerprinting, tracking, and data + collection!

+
+
+ + +
+
+

About

+
+
+
+
+

What is JShelter?

+
+
+

JShelter is a browser extension to give back control over what your + browser is doing. A JavaScript-enabled web page can access much of the + browser's functionality, with little control over this process available + to the user: malicious websites can uniquely identify you through + fingerprinting and use other tactics for tracking your activity. + JShelter aims to improve the privacy and security of your web + browsing.

+
+
+
+
+

How does it work?

+
+
+

Like a firewall that controls network connections, JShelter controls + the APIs provided by the browser, restricting the data that they gather + and send out to websites. JShelter adds a safety layer that allows the + user to choose if a certain action should be forbidden on a site, or if + it should be allowed with restrictions, such as reducing the precision + of geolocation to the city area. This layer can also aid as a + countermeasure against attacks targeting the browser, operating system + or hardware.

+
+
+ +
+
+

Who's behind this project?

+
+
+

See the credits page.

+
+
+
+
+ +
+
+

Contribute

+
+
+
+
+

I found a bug!

+
+
+

If you have any questions or you spotted a bug, the project's issue tracker is the place for posting those. We especially appreciate feedback, so feel free to use the issue tracker for chiming in.

+ +
+
+ +
+
+

How can I help?

+
+
+

Using JShelter and reporting any problems you find in our issue tracker is a huge help. If you want to contribute to the project itself, post your ideas on the issue tracker or just go ahead and make a pull request.

+
+
+ + +
+
diff --git a/website/content/pages/install.md b/website/content/pages/install.md new file mode 100644 index 0000000..8018516 --- /dev/null +++ b/website/content/pages/install.md @@ -0,0 +1,10 @@ +Title: Installing + +JShelter can be installed directly through each browser's extension repository: + +- [Firefox](https://addons.mozilla.org/firefox/addon/javascript-restrictor/) +- [Chrome](https://chrome.google.com/webstore/detail/javascript-restrictor/ammoloihpcbognfddfjcljgembpibcmb) +- [Opera](https://addons.opera.com/extensions/details/javascript-restrictor/) + +To compile the extension from the source code, see the [building from +scratch](/build/) documentation. diff --git a/website/content/pages/levels.md b/website/content/pages/levels.md new file mode 100644 index 0000000..5e3b93f --- /dev/null +++ b/website/content/pages/levels.md @@ -0,0 +1,78 @@ +Title: Protection levels + +## Network Boundary Shield (NBS) + +NBS is active independently on the levels defined below. If necessary, you can whitelist websites for which the NBS should be turned off. Generally, you want NBS to be active, however, some pages can be broken, because they require interaction between public Internet and local network, for example, some Intranet information systems might be broken by the NBS. + +## Levels controlling JS-object wrapping + +### Level 0 +* *All functionality is disabled OFF* + +### Level 1 + +* **Manipulate the time precision provided by Date, performance, events, gamepads, virtual reality, and Geolocation API --** *ON* + * Round time to: *hundredths of a second (1.230 -- Date, 1230 -- performance, Geolocation API)* +* **Protect against canvas fingerprinting --** *OFF* +* **List of microphones and cameras: --** *original* +* **Spoof hardware information to the most popular HW --** *ON* + * JS navigator.deviceMemory: *4* (not applied if the browser does not support the property, e.g. + Firefox) + * JS navigator.hardwareConcurrency: *2* +* **Filter XMLHttpRequest requests --** *OFF* +* **Protect against ArrayBuffer exploitation --** *OFF* +* **Protect against SharedArrayBuffer exploitation --** *OFF* +* **Protect against WebWorker exploitation --** *OFF* +* **Limit Geolocation API --** *Use accuracy of hundreds of meters* +* **Gamepad API --** *List all attached gamepads* +* **Original virtual reality API --** *List all attached VR sets* +* **Mixed reality API --** *Enabled* +* **navigator.sendBeacon --** *Do not send anything and return true* +* **Disable Battery status API --** *ON* +* **window.name --** *Clear with each page load* + +### Level 2 +* **Manipulate the time precision provided by Date, performance, events, gamepads, virtual reality, and Geolocation API --** *ON* + * Round time to: *tenths of a second (1.200 -- Date, 1200 -- performance, Geolocation API)* +* **Protect against canvas fingerprinting: --** *ON* + * Reading from canvas returns white image. +* **List of microphones and cameras: --** *EMPTY* +* **Spoof hardware information to the most popular HW --** *ON* + * JS navigator.deviceMemory: *4* (not applied if the browser does not support the property) + * JS navigator.hardwareConcurrency: *2* +* **Filter XMLHttpRequest requests --** *OFF* +* **Protect against ArrayBuffer exploitation --** *OFF* +* **Protect against SharedArrayBuffer exploitation --** *OFF* +* **Protect against WebWorker exploitation --** *OFF* +* **Limit Geolocation API --** *Use accuracy of kilometers* +* **Gamepad API --** *Hide all attached gamepads* +* **Original virtual reality API --** *Hide all attached VR sets* +* **Mixed reality API --** *Disabled* +* **navigator.sendBeacon --** *Do not send anything and return true* +* **Disable Battery status API --** *ON* +* **window.name --** *Clear with each page load* + +### Level 3 +* **Manipulate the time precision provided by Date, performance, events, gamepads, virtual reality, and Geolocation API --** *ON* + * Round time to: *full seconds (1.000 -- Date, 1000 -- performance)* + * *Randomize time* +* **Protect against canvas fingerprinting: --** *ON* + * Reading from canvas returns white image. +* **List of microphones and cameras: --** *EMPTY* +* **Spoof hardware information to the most popular HW --** *ON* + * JS navigator.deviceMemory: *4* (not applied if the browser does not support the property) + * JS navigator.hardwareConcurrency: *2* +* **Filter XMLHttpRequest requests: --** *confirm requests but do not block* +* **Protect against ArrayBuffer exploitation --** *ON* + * *Use random mapping of array indexing to memory* +* **Protect against SharedArrayBuffer exploitation --** *ON* + * *Block SharedArrayBuffer* -- SharedArrayBuffer provided by the browser is not available to page scripts at all. +* **Protect against WebWorker exploitation --** *ON* + * *Remove real parallelism* -- Use Worker polyfill instead of the native Worker. +* **Limit Geolocation API --** *Disabled* +* **Gamepad API --** *Hide all attached gamepads* +* **Original virtual reality API --** *Hide all attached VR sets* +* **Mixed reality API --** *Disabled* +* **navigator.sendBeacon --** *Do not send anything and return true* +* **Disable Battery status API --** *ON* +* **window.name --** *Clear with each page load* diff --git a/website/content/pages/license.md b/website/content/pages/license.md new file mode 100644 index 0000000..7d8b928 --- /dev/null +++ b/website/content/pages/license.md @@ -0,0 +1,674 @@ +Title: GNU General Public License + +Version 3, 29 June 2007 + +Copyright (C) 2007 [Free Software Foundation, Inc.](https://fsf.org/) + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/website/content/pages/nbs.md b/website/content/pages/nbs.md new file mode 100644 index 0000000..ffe4fa0 --- /dev/null +++ b/website/content/pages/nbs.md @@ -0,0 +1,33 @@ +Title: Network Boundary Shield +Slug: nbs + +The Network Boundary Shield (NBS) is a protection against attacks from an +external network (the Internet) to an internal network - especially against a +reconnaissance attacks when a web browser is abused as a proxy. See, for +example, the [ForcePoint +report](https://www.forcepoint.com/blog/x-labs/attacking-internal-network-public-internet-using-browser-proxy). + +The NBS functionality is based on filtering HTTP requests. The Network Boundary +Shield uses blocking webRequest API to handle HTTP requests. This means that +processing of each HTTP request is paused before it is analyzed and allowed (if +it seems benign) or blocked (if it is suspicious). + +The main goal of NBS is to prevent attacks like a public website requests a +resource from the internal network (e.g. the logo of the manufacturer of the +local router); NBS will detect that a web page hosted on the public Internet +tries to connect to a local IP address. NBS blocks only HTTP requests from a web +page hosted on a public IP address to a private network resource. The user can +allow specific web pages to access local resources (e.g. when using Intranet +services). + +NBS uses [CSV files provided by +IANA](https://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xml) +to determine public and local IP address prefixes. Both IPv4 and IPv6 is +supported. The CSV files are downloaded during the JShelter building process. + +The NBS has only a small impact on the web browser performance. The impact +differs for each implementation. + +More information about the Network Boundary Shield can be obtained from the +[master thesis by Pavel +Pohner](https://www.vutbr.cz/studenti/zav-prace/detail/129272) (in Czech). diff --git a/website/content/pages/new-wrapper.md b/website/content/pages/new-wrapper.md new file mode 100644 index 0000000..de58230 --- /dev/null +++ b/website/content/pages/new-wrapper.md @@ -0,0 +1,286 @@ +Title: How to write a new wrapper + +The primary focus of jShelter is to provide security and privacy oriented wrappers of JavaScript APIs. As some of the code is very similar for each wrapper, we use a unified approach to describe wrappers. The approach is described below. This approach also helps to automatically modify `toString` conversions of the wrapped APIs, i.e. a correctly written wrapper creates a modified function but `wrapper.toString()` returns the original string. Finally, this approach also helps in generating code dealing with the [Firefox bug described in #25](https://github.com/polcak/jShelterestrictor/issues/25). + +## File structure + +* `wrapping.js` provides main facilities for interacting with specific wrappers: + * `add_wrappers()` has to be provided with all the wrappers. Hence, a typical module with wrappers of a web standard APIS. ends with a call of `add_wrappers(list_of_all_wrappers_defined_by_the_module)`. + * `build_wrapping_code` contains all the registered wrappers. The keys are referenced by the levels wrapper list. Do not modify the `build_wrapping_code` variable directly, use `add_wrappers` instead. + * The module also provides common functions used by the wrappers, e.g. `rounding_function` and `noise_function`. +* `wrappingS-XYZ` are files dealing with APIs introduced by specific web or ECMA standard. See the **naming conventions** for details on the XYZ part of the name. See the **wrapper specification** section for the properties of the wrapper objects. + +## Naming conventions + +jShelter adopted the naming conventions of the [Web API Manager](https://github.com/pes10k/web-api-manager/tree/master/sources/standards). See the paper: + +Peter Snyder, Cynthia Taylor, and Chris Kanich, “[Most Websites Don’t Need to Vibrate: A Cost–Benefit Approach to Improving Browser Security](https://arxiv.org/abs/1708.08510),” in Proceedings of the [2017 ACM Conference on Computer and Communications Security](https://www.sigsac.org/ccs/CCS2017/), 2017. + +## Wrapper specification + +Once you are set with the file name for your wrappers, you can start coding. The basic structure of the file is: + +```js +(function() { + // definition of common functions used by the wrappers bellow + var wrappers = [ + { + // wrapping object 1 + }, + { + // wrapping object 2 + }, +... + { + // wrapping object n + }, + ] + add_wrappers(wrappers); +})(); +``` + +The content of the module should be in an IIFE so that it does not polute the global namespace. The property values are strings that are generally interpreted either as identifiers or JS code. + +Each wrapping object must have the following mandatory properties: + +* `parent_object` and `parent_object_property` are used to define the name of the wrapping + (`parent_object.parent_object_property`) that is referenced by level wrappers. Additionally, it is +used if `wrapper_prototype` is defined to provide the object name to have the prototype changed. Finally, `Object.freeze` can be optionally called on `parent_object.parent_object_property`. +* `apply_if` optionally provides a condition that needs to be fullfilled to apply the wrapper. For + example if a wrapper should be applied only when an API already provides some information. For + example, `apply_if: "navigator.plugins.length > 0"`. +* `wrapped_objects` is a list of objects, each having the following properties (1 mandatory, 2 + optional, typically, wrappers use one of the optional names in the wrapper code to access the + original result of the call): + * `original_name` (_mandatory_) - the original name of the object to be wrapped. Do not mention `window` here! + * `wrapped_name` - the variable name that points to the actual original object. Wrappers might need it for identity comparisons or other instance-specific use cases. The variable name can be used by `wrapping_function_body`, `helping_code` + and other code fragments to reference the original object. Note that this name is not available + outside the code generated by this wrappper. + * `callable_name` - is similar to the `wrapped_name` but the variable refers to a proxy of the original object. The proxy intercepts calls and automatically marshals parameters and return values. Wrappers MUST define `callable_name` if the object is passed to other code like `Promise` objects or +callbacks. The variable is available only + in the code generate by this wrapper. `callable_name` is specifically **meant to be used for native methods and functions** + which the wrapper needs to call. This is especially important if it **accepts callback arguments or returns `Promise` objects**: + invoking them through their `callable_name` automates complex steps otherwise required for [sandboxed browser extensions to interact with web pages](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). + +Generally speaking, use `wrapped_name` whenever you need to access the original objects only inside +the wrapper and you do not need to pass the object to other code, such as `Promise` objects or +callbacks. Compared to `callable_name`, `wrapped_name` has less overhead and is the preferred way. + +Each wrapping object can have the following optional properties: + +* `wrapping_function_args` is a string that is used as an argument list for the wrapping function. + Typically this string reflects the parameters of the original method, it can be an empty string, a + list of parameters, such as `"source, target, color"` or `"...args"`. +* `wrapping_function_body` specified the behaviour of the wrapped function. You can provide a + completely different implementation but often, you want to refer to the original implementation + (available in the variable `wrapped_name`) and modify the original implementation. +* `wrapping_code_function_name` can be used to call the wrapping function from the other code inside + the wrapper. For example to create another wrapper similar to the original wrapper. +* `wrapper_prototype` - if defined, `parent_object.parent_object_property` prototype is set to the +prototype identifier provided by `wrapper_prototype`. +* `original_function` if not provided, it defaults to `parent_object.parent_object_property`. This + name is used for overloading the `toString` function. Instead of the wrapping code, `toString` + returns the content of the original function. +* `helping_code` provides you an option to define code available for both `replacement` function and + `post_wrapping_code` +* `replace_original_function` is used to control which function should be replaced +* `post_replacement_code` Allows to provide additional code with the access to the original function + and to the wrapped function +* `freeze` if set to `true` causes the API to be freezed. It should not be necessary if the wrapping is performed at the right level of the object's prototype chain (i.e. where the property to be modified was originally defined, i.e. usually in the object's prototype). Use this option with caution, only if strictly needed, because native APIs are configurable (otherwise our wrapping couldn't work) and therefore freezing them introduces a "weirdness", exploitable for fingerprinting. +* `post_wrapping_code` is a list of additional wrapping objects with a similar structure to the + wrappers, see the section bellow. + +### Post wrapping code + +Complex wrappers need to provide additional wrapping code. + +You can make the generation of the post wrapping code generation conditional by using `apply_if`, e.g.: +```js + { + ... + apply_if: "!enableGeolocation", + } +``` +(`enableGeolocation` is a Booloean variable) + +Currently jShelter supports additional wrapping of: + +* Definition of a function (used, for example, to reintroduce `Date.now()` function to the wrapped + `Date` object) +```js +[ + { + code_type: "function_define", + original_function: "originalDateConstructor.now", + parent_object: "window.Date", + parent_object_property: "now", + wrapping_function_args: "", + wrapping_function_body: "return func(originalDateConstructor.now.call(Date), precision);", + }, +] +``` +* Export a function from the wrapping namespace (currently not used, the following code exposes the + unwrapped, i.e. original, version of Date.now to page scripts) +```js +[ + { + code_type: "function_export", + parent_object: "window.Date", + parent_object_property: "now", + export_function_name: "originalDateConstructor.now", + }, +] +``` +* Redefine an object property (used to prevent leaking iframe properties to the unwrapped objects): +```js +{ + code_type: "object_properties", + parent_object: "HTMLIFrameElement.prototype", + parent_object_property: "contentWindow", + wrapped_objects: [ + { + original_name: "HTMLIFrameElement.prototype.__lookupGetter__('contentWindow')", + wrapped_name: "cw", + }, + ], + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + var parent=cw.call(this); + try { + parent.HTMLCanvasElement; + } + catch(d) { + return; // HTMLIFrameElement.contentWindow properties could not be accessed anyway + } + wrapping(parent); + return parent; + }`, + }, + ], +} +``` +* Deleting a property (used when you want to completely disable an API): +```js + { + code_type: "delete_properties", + parent_object: "navigator", + apply_if: "!enableGeolocation", + delete_properties: ["geolocation"], + } +``` +### The WrapHelper API + +A `WrapHelper` object is globally available to wrappers code, exposing some methods and properties which are mostly +used internally by the code builders to automate tasks such as handling [Firefox's content script sandbox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) or making replacement objects look as native as possible. +However a few of them may be useful to complex wrappers or in edge case not covered by `callable_name` and other declarative object replacement / property definition wrapper constructs: + +* `WrapHelper.shared: {}` - a "bare" JavaScript object which a wrapper can use to share information with other wrappers + (e.g. to coordinate behavior between related parts of the same API requiring multiple wrappings) + by attaching its own data objects as properties. __Warning__: namespacing is not enforced and up to the wrapper implementor, but obviously recommended. +* `WrapHelper.overlay(obj, data)` - Proxies the prototype of the `obj` object in order to return the properties of the `data` object + as if they were native properties (e.g. as if they were returned by getters on the prototype chain, + rather than defined on the instance). This allows spoofing some native objects data in a less detectable / fingerprintable way + than by using `Object.defineProperty()`. See `wrappingS-MCS.js` for an example. +* `WrapHelper.forPage(obj)` - it's mostly used internally and transparently by `code_builder.js`, + but may be useful to very complex proxies in edge cases needing to explicitly + prepare an object/function created in [Firefox's sandboxed content script environment](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) to be consumed/called from the page context, and to make replacements for native + objects and functions provided by the wrappers look as much native as possible (on Chromium, too). + In most cases, however, this gets automated by the code builders replacing + Object methods with their WrapHelper counterpart in the wrapper sources + and by proxying "callable_name" function references through `WrapHelper.pageAPI()` (see below). +* `WrapHelper.pageAPI(f)` - proxies the function/method f so that arguments and return values, and especially callbacks and + `Promise` objects, are recursively managed in order to transparently marshal objects back and forth + [Firefox's sandbox for extensions (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts). + __Wrapper implementors should almost never need to use this API directly__, since any function referenced via its "callable_name" + goes automatically through it. + +### The generated wrapper structure + +To get a better idea how the code is generated see the following pseudo code. Please do not refer to +any name created by the code builders from your wrapper. Use your custom names. If it is not +available, please, open an issue where you explain what you are trying to achieve. It is probable +that we will introduce a new property that allows to provide your name to the code builders. The +code lives in an anonymous namespaces, so variables introduced here do not directly leak to page +scripts. + +```js +// Define wrapped_name(s) variables holding the original JS objects +helping_code // if present +function wrapping_code_function_name(param) { + // Store original function: either original_function which defaults to parent_object.parent_object_property + function replacement(wrapping_function_args) { + wrapping_function_body + } + if (replace_original_function) + original_function = replacement + else + parent_object.parent_object_property = replacement + // Modify toString + post_replacement_code +} +wrapping_code_function_name(window if wrapping_code_function_call_window) // The function is called even if the name is not explicitely provided +// wrappings generated by post wrapping code +``` + +## Compiling the wrappers + +Of course the wrappers need to be compiled to JavaScript before inserting the code to page scripts. See `code_builders.js`. + +## Registering a new wrapper + +`fix_manifest.sh` automatically adds all modules with file name of `wrapping*.js` to the manifest.json of the extension. There is no need for any additional action. + +## Register new wrapper to be used by the extension in a level or available in the GUI. + +See `levels.js` and its list `wrapping_groups.groups`. Once you add your wrapper to an existing group or create a new group, the wrapper becomes available in the built-in levels containing the group and in the GUI for custom levels. + +## Describe the wrapper for Doygen documentation + +Describe what the wrapper tries to accomplish and its approach: + +* Add Doxygen comment at the top of the file with the following structure: + +```js +/** \file + * \brief This file contains wrappers for the X API (link to the standard or MDN) + * \ingroup wrappers + * + * Put at least one paragraph describing the goal of the wrapper. Describe a possible attack vector. + * Link to further resources/readings. + * + * Describe the options of the wrapper. Mention examples when a user should want to use the + * available options. + * + * Describe any \note, \bug ór use other suitable Doxygen commands + * (https://www.doxygen.nl/manual/commands.html) + */ +``` + +* Describe helping functions and `wrapping_function_body` + * Use Doxygen command `\fn fake wrapped.object` for each `wrapping_function_body` + * Use the following structure for function comments: + +```js +/** + * \brief Short description + * + * \param X Descripton + * \param Y Descripton + * + * \returns Description + * + * Main description of the aim of the function, algorithm, etc. + * + * Describe any \note, \bug ór use other suitable Doxygen commands + * (https://www.doxygen.nl/manual/commands.html) + */ +``` + +## Write unit tests or integgration tests for the wrapper + +Follow instructions for [unit testing](md_tests_unit_tests_README.html) and +[integration testing](md_tests_integration_tests_README.html) (see the `tests` directory in the +repository). diff --git a/website/content/pages/permissions.md b/website/content/pages/permissions.md new file mode 100644 index 0000000..beaf2fa --- /dev/null +++ b/website/content/pages/permissions.md @@ -0,0 +1,11 @@ +Title: Permissions + +JShelter requires these permissions: + + * **storage**: for storing extension configuration and user options + * **tabs**: for updating the extension's icon badge on tab change + * **webRequest, webRequestBlocking, all_urls**: for modifying JavaScript objects and APIs on all pages, and for capturing and blocking malicious HTTP requests + * **dns**: for resolving DNS queries in Firefox version of HTTP request shield + * **notifications**: for notifying users on blocked HTTP requests/hosts + +jShelter stores all configuration data in the browser or in the user account. It does not upload any data to our servers. diff --git a/website/content/pages/pt/build.md b/website/content/pages/pt/build.md new file mode 100644 index 0000000..8050463 --- /dev/null +++ b/website/content/pages/pt/build.md @@ -0,0 +1,32 @@ +Title: Compilar + +Esta é a versão em português! + +### GNU/Linux and Mac OS + +1. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). +1. Download the desired branch, e.g. as zip archive. +1. Unpack the zip archive. +1. Run `git submodule update` +1. Run `make`. + * You will need common software, such as `zip`, `wget`, `bash`, `awk`, `sed`. +1. Import the extension to the browser. + * Firefox: [https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/) + * Use the file `firefox_JSR.zip` created by `make`. + * Chromium-based browsers: + 1. Open `chrome://extensions`. + 1. Enable developper mode. + 1. Click `Load unpacked`. + 1. Import the `chrome_JSR/` directory created by `make`. + +### Windows + +1. Install Windows Subsystem for Linux (WSL): [https://docs.microsoft.com/en-us/windows/wsl/install-win10](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +2. Go to the project repository: [https://github.com/polcak/jsrestrictor](https://github.com/polcak/jsrestrictor). +3. Download the desired branch, e.g. as zip archive. +4. Unpack the zip archive. +5. Run `git submodule update` +6. Open the JSR project folder in WSL, run `make`. + * Make sure that `zip` and all other necessary tools are installed. + * Note that EOL in `fix_manifest.sh` must be set to `LF` (you can use the tool `dos2unix` in WSL to convert `CR LF` to `LF`). +7. On Windows, import the extension to the browser according to the instructions for Linux (above). diff --git a/website/content/pages/pt/home.md b/website/content/pages/pt/home.md new file mode 100644 index 0000000..1050e5c --- /dev/null +++ b/website/content/pages/pt/home.md @@ -0,0 +1,122 @@ +Title: Início +Template: home +URL: +save_as: pt/index.html + +
+
+

+ jShelter +

+ +

Uma extensão anti-malware para o seu browser, dedicada a mitigar potenciais ameaças via Javascript, como fingerprinting, tracking e armazenamento de dados pessoais!

+ +
+

Instalar já

+

+ + Firefox + + + Chrome + + + Opera + +

+
+ +
+
+ + +
+
+
+
+

O que é o jShelter?

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+
+
+

Como funciona?

+
+
+

JavaScript Shield adds a + safety layer that allows the user to choose if a certain action should + be forbidden on a site, or if it should be allowed with restrictions, + such as reducing the precision of geolocation to the city area. This + layer can also aid as a countermeasure against attacks targeting the + browser, operating system or hardware levels.

+
+
+
+
+

Qual é a diferença entre o jShelter e X, Y ou Z?

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+
+
+

Quem está por trás deste projeto?

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+
+
+ +
+
+
+
+

Como posso ajudar?

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+ +
+
+

Encontrei um bug!

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+ +
+
+

Quais são os termos da licença?

+
+
+

Accessing cookies, performing fingerprinting to track users across + multiple sites, revealing the local network address, or capturing the + user's input before they submit a form are some examples of JavaScript's + capabilities that can be used in harmful ways.

+
+
+
+
diff --git a/website/content/pages/versions.md b/website/content/pages/versions.md new file mode 100644 index 0000000..85fe234 --- /dev/null +++ b/website/content/pages/versions.md @@ -0,0 +1,136 @@ +Title: Release history + +## 0.5.2 + +* Bugfix: Do not modify JS environment on level 0. Regression appeared in 0.5. + +## 0.5.1 + +* Bugfix: Display correctly NBS status at the current page (Github issue #114) +* Rebranding step 1: change UI-facing icons +* Set minimal pop up width so that the pop up is usable in Chrome (Github issue #112, Pagure issue + #7) +* Chromium-based browsers: revise Battery API protection that should match the expectations of page + scripts (mimic Firefox behaviour). +* Fixed typos in settings. + +## 0.5 + +* Add fingerprinting defenses based on Farbling developed by the Brave browser (improved or added + wrappers for Canvas, Audio, Web GL, device memory, hardware concurrency, enumerateDevices). Most + wrappers support provisioning of white lies that differ between origins and sessions (the + fingeprint is different across origins and across sessions). + * We claimed to generate white image fake Canvas value but instead generated fully transparent black image. We now generate the white image as it is more common in other anti-canvas fingerprinting tools (level 3). + * toDataUrl() no longer destructs the original canvas. +* We use NoScript Commons Library to simplify some tasks like cross-browser support. + * More reliable early content script configuration. + * CSP headers no longer prevents the extension from wrapping JS APIs in Firefox (Github issue #25) + * Wrappers should be injected reliably before page scripts start to operate (Github issue #40) + * We use NSCL to wrap APIs in iframes and workers + * It is no longer possible to access unwrapped functions from iframes and workers (Pagure issue #2, Github issue #56) +* Ignore trailing '.' in domain names when selecting appropriate custom level. +* Do not freeze wrappers to prevent fingeprintability of the users of JSR. We wrap the correct function + in the prototype chain instead. +* navigator.getGamepads() wrapper added +* navigator.activeVRDisplays() and navigator.xr wrappers added +* Limit precision of high resolution timestamps in the Event, VRFrameData, and Gamepad interface to be consistent + with Date and Performance precision + +## 0.4.7 + +* Wrap Beacon API +* Bugfix: inject content scripts to all iframes +* Fix exception throwing in the code generator dealing with Firefox bug 1267027 + +## 0.4.6 + +* NBS improvements for Chromium-based browsers: block a host after detecting the first suspicious HTTP request from the public to the private network. + +## 0.4.5 + +* Add wrapper of MediaDevices.prototype.enumerateDevices +* Fix missing Date properties +* Fix Geolocation overflows appearing near poles +* Improve handling of domain names (URLs): + * Handle IPv4 addresses used as hostnames correctly + * Do not treat TLD specially and allow specifying wrapping levels for TLDs + * Fix handling of two-letters 2-nd level domains +* Fix exception throwing in the code generator dealing with Firefox bug 1267027 + +## 0.4.4 + +* Bugfix: Do not try to redefine undefined objects. The exceptions thrown in injected code used to + prevent application of all the wrapping code. + +## 0.4.3 + +* Add an option to clear `window.name` with each page reload. + +## 0.4.2 + +* Rewrite the NBS for Chromium-based browsers with custom DNS cache build with resolved data available in onResponseStartedListener() + +## 0.4.1 + +* Fix the amount of saved data through pop-up (for a specific domain), it is much harder to reach + the quoa + +## 0.4 + +* Re-introduced Geolocation API wrapping (several settings available). + +## 0.3.2 + +* Bugfix: Set up domain-specific levels from storage correctly +* Wrap PerformanceEntry instead of performance.getEntries\*() - prevents a known leak of precise + time stamps in Chromium-based browsers. +* Add note on the effectivity of time randomization +* Firefox fix background and content scripts synchronization, use correct naming (improves speed) +* Time wrappers in Firefox affected by the Fiefox CSP bug should work better. However, the precise timers are not wrapped, see also #25. +* NBS message for Chromium-based browsers reworded. + +## 0.3.1 + +* Improve compatibility with Chromium based browsers + +## 0.3 + +* Major code rewrite - make the code more modular, remove duplications +* Add wrappings inspired by [JavaScript Zero: Real JavaScript and Zero S +ide-Channel Attacks](https://misc0110.net/web/files/jszero.pdf) +* Network boundary shield prevents web pages to use the browser as a proxy between local network and the public Internet. See the [Force Point report](https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf) for an example of the attack. The protection encapsulates the WebRequest API, so it captures all outgoing requests. +* Allow multiple custom levels +* Do not modify DOM of displayed pages (the modifications were detectable by the page scripts and may + reveal that the user is running JSR) +* Canvas fingerprinting: originally, only `toDataURL` was blocked. The extension now blocks `CanvasRenderingContext2D.prototype.getImageData` and `HTMLCanvasElement.prototype.toBlob`. +* Block additionaly methods to get performance data. +* Unfortunately, we do not migrate old settings as the levels were redesigned and several features + were removed. We expect to migrate previous settings in the future. +* Initial attempt to deal with a bug [https://bugzilla.mozilla.org/show_bug.cgi?id=1267027](https://bugzilla.mozilla.org/show_bug.cgi?id=1267027) but it + does not work completely as expected, yet. +* Make sure that calling toString on the wrapped function does not leak the wrapping code. +* Fix original canvas method leaks through iframes +* Do not allow page scripts to delete wrappers +* GUI rewritten. +* Do not open the main page after browser or extension update as it is irritating and may send a + signal that the user is tracked. +* *Removed feature* Do not change request HTTP headers. See the paper + FP-Scanner: The privacy implications of browser + fingerprint inconsistencies and pages like + [https://ghacksuserjs.github.io/TorZillaPrint/TorZillaPrint.html](https://ghacksuserjs.github.io/TorZillaPrint/TorZillaPrint.html). +* *Removed feature* GPS/location is not blocked anymore, we expect to reintroduce this feature in the future. + +## 0.2.1 + +* Fix `Date` wrapping that used to break some pages; `Date` wrapping code improved +* Improve `XMLHttpRequest` wrapping + +## 0.2 + +* Additional APIs that can be wrapped: + * `navigator` properties: `userAgent`, `vendor`, `platform`, `appVersion`, `oscpu`, `language`, `languages` + * `document` properties: `referrer` + +## 0.1 + +* Initial public version diff --git a/website/content/posts/crawling.md b/website/content/posts/crawling.md new file mode 100644 index 0000000..08a74f5 --- /dev/null +++ b/website/content/posts/crawling.md @@ -0,0 +1,141 @@ +--- +title: Measurement of JavaScript API usage on the web +date: 2021-03-30 14:00 +--- + +The world wide web is a complex environment. Web pages can access many APIs ranging from text formatting to access to nearby Bluetooth devices. While many APIs are used for legitimate purposes, some are misused to track and identify their users without their knowledge. In this paper, we propose a methodology to measure the usage of JavaScript APIs on the public web. The methodology consists of an automated visit of several thousand websites and intercepting JavaScript calls performed by the pages. We also provide a design and architecture of a measurement platform that can be used for an automated visit of a list of websites. The proposed platform is based on OpenWPM. The browser is instrumented by OpenWPM and a customized Web API Manager extension is responsible for capturing JavaScript API calls. + +### Introduction + +Web browsers offer a wide range of possibilities. on the surface +they _just_ display web pages, but under the hood, web +browsers provide a bridge between a viewed page and the host +operating system. A web browser allows a web page to access information +like values from sensors, information about battery status, installed +fonts, and much more. The advertisement industry often takes advantage of +the wide range of information provided by web browsers to create a web browser +[fingerprint](https://amiunique.org/links). Most commonly, the fingeprinters misuse Web APIs (also +called JavaScript APIs). + +This blog post is mainly concerned +with user tracking and fingerprinting. +For example Battery Status API implementation on Mozilla Firefox +revealed very precise value allowing the trackers to identify the user +for a [period of time](https://petsymposium.org/2017/papers/hotpets/batterystatus-not-included.pdf). +As the Battery Status API was used heavily for fingerprinting, it +has been removed from Mozilla Firefox in 2017. Other examples of +JavaScript APIs that are used often for fingerprinting are [Canvas API](http://cseweb.ucsd.edu/~hovav/papers/ms12.html), [Audio API](https://senglehardt.com/papers/princeton_phd_dissertation_englehardt.pdf), +[Permissions API](https://arxiv.org/abs/2008.04480) or [APIs for device sensors](https://dl.acm.org/doi/10.1145/3243734.3243860). + +In our work, we aim to measure the JavaScript APIs usage by popular +websites. In this article, we present core technologies to accomplish +these measurements. The stack of technologies is based on [OpenWPM](https://github.com/mozilla/OpenWPM) +enriched by a browser extension, that allows us to intercept JavaScript +calls of different APIs. This browser extension is based on Proxy +objects. + +Our work is based on work of [Peter Snyders et al.](https://www3.cs.uic.edu/pub/Bits/PeterSnyder/Browser_Feature_Usage_on_the_Modern_Web.pdf) +carried in 2016. Since then many new +APIs were specified and implemented in web browsers, see the figure below +(based on data from [Can I Use? website](https://caniuse.com/)). + +![Progress of Web APIs amount implemented in distinct browsers in time.]({attach}/images/crawling-apis.png) + +### Methodology proposal + +This section describes the methodology that we plan to use. +This methodology is based on +As it is based on the work of Peter Snyders, it is already validated. +Moreover, using a methodology that is very close to the +original one should show us a difference in the usage of the JavaScript +APIs in 2016 and 2021. + +The main idea of the measurement is to visit several thousands of the +most [popular pages](https://tranco-list.eu/#aboutus) on the internet and intercept as many JavaScript calls +as possible. + +Visiting websites will be performed through Mozilla Firefox with +an extension that intercepts and logs the JavaScript calls. +We will visit not +only the landing page but also the subset of subpages of each website. +From the landing page, we will extract three links that point to +a subpage of a given page. From each of these three subpages, we will +get another three subpage links resulting in up to 13 pages of a given +website being visited. This amount of pages should be high enough to +catch the most of JavaScript calls. +We will wait and intercept JavaScript +calls for 30 seconds on each page to wait of API calls performed during the page load. + +Results of our measurements should also provide information about the +JavaScript APIs, that were probably used in a manner, that is not +necessary for a page to be working and is very likely used in a way, +that the user would not find useful. To achieve this, + +We will run our +measurements on every page in two different modes. Firstly, we will +visit the page using the a browser withou adblocker. Later, we will also employ an adblocker. +Hence, the study will show the difference API usage of regular pages and trackers. + +#### The original study + +The Snyders' study suggests that some of the JavaScript APIs are +extremely popular and they are used on more than 90% of measured pages +(e.g. a well known `Document.createElement` method from DOM API). On the +other hand, there are many APIs that are used by a minority of measured +pages. That being said, almost 50% of JavaScript APIs implemented in the +browser at the time were not used by any of the measured pages. + +The study also suggests that there is no direct connection between the +implementation date of a given JavaScript API in the browser (or by its +specification date by some of the specifications vendors) and its +popularity in using by websites. Concretely, there are some old +JavaScript APIs, such as `XMLHttpRequest` that are still very popular. +However, there are also quite new +JavaScript APIs, that are used very frequently (i.e., `Selectors API +Level 1`). + +The conducted study also measured the pages in two ways - with the ad +blocker and without any ad blocking extension. Results of measurements +showed that the blocking of different JavaScript APIs is not uniform and +some APIs are blocked more often than others. Specifically, 10% of +JavaScript APIs were blocked in 90% of cases resulting in a fact that +83% of APIs were used on less than 1% of websites when the page was +visited with active blocking extension. + +#### Web API Manager + +Web API Manager is a browser extension, that aims to block explicitly +defined JavaScript APIs. It has been developed by Snyders in 2016 and +used in several studies conducted by [Snyders et al.](https://www.peteresnyder.com/https://www.peteresnyder.com/). + +The original purpose of the Web API Manager is to block explicitly +defined JavaScript APIs. However, in our measurements, we just need to +intercept the API calls, log these calls and delegate the calls to +original receivers. + +The main principle of Web API Manager is based on Proxy objects. This +metaprogramming technique allows intercepting calls performed on +objects. While the main goal of the Web API Manager extension is to +block the calls performed on objects that belong to particular +JavaScript APIs, our goal is only to intercept these operations and +delegate them to their original receivers. We will use the Log Aggregator interface to log the API +calls. + +To provide a Web API Manager the list of JavaScript APIs members we need +a list of supported APIs. The APIs implemented in Mozilla Firefox are available as [IDL files](https://searchfox.org/mozilla-central/source/dom/webidl). + +#### Measurement tools + +The figure below shows a simplified +illustration of the measurement platform. There is OpenWPM in the middle of the +architecture. OpenWPM orchestrates +Selenium and Mozilla Firefox with the proxy-based intercepting Web API Manager. + +![image]({attach}/images/crawling-architecture.png) + +### The impact on JSR + +Once we have data from our crawling study, we will compare the data with [another recent study](https://github.com/uiowa-irl/FP-Inspector/blob/master/Data/potential_fingerprinting_APIs.md). As already mentioned, we want to develop a fingerprinting detection based on counting the number of different +APIs employed by a page, especially APIs that are not frequently used for benign purposes. When +a fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the +fingerprint to the server, (3) prevent storing the fingerprint for later usage. diff --git a/website/content/posts/farbling.md b/website/content/posts/farbling.md new file mode 100644 index 0000000..a7d356b --- /dev/null +++ b/website/content/posts/farbling.md @@ -0,0 +1,93 @@ +--- +title: Farbling-based wrappers to hinder browser fingerprinting +date: 2021-08-23 09:00 +--- + +[Browser fingerprinting](https://arxiv.org/pdf/1905.01051.pdf) is a more and more popular technique used to identify browsers. The fingerprint is computed based on the results of JavaScript calls, the content of HTTP headers, hardware characteristics, underlying operating system and other software information. Consequently, browser fingerprints are used for cross-domain tracking. However, users cannot clear their browser fingerprint as it is not stored on the client-side. It is also challenging to determine whether a browser is being fingerprinted. + +Another issue that hinders fingerprinting protection is the ever-changing variety of supported APIs. Browsers implement new APIs over time, and existing APIs change. Consequently, it is necessary to continuously monitor the APIs being used for fingerprinting purposes to block fingerprinting attempts. + +Due to fingerprinting scripts being [more prevalent](https://www.cs.princeton.edu/~arvindn/publications/OpenWPM_1_million_site_tracking_measurement.pdf), various web browsers - for example, Tor, Brave, and Firefox - started implementing fingerprinting protection to protect users and their privacy. + +## Brave fingerprinting protection + +Why is Brave's Farbling special? Until recently, [Tor browser](https://2019.www.torproject.org/projects/torbrowser/design/#fingerprinting-linkability) had the most robust defence against fingerprinting. It (1) implemented modifications in various APIs, (2) blocks some other APIs, (3) runs in a window of predefined size, etc. to ensure all users have the same fingerprint. This approach is very effective at producing uniform fingerprint for all users, which makes it difficult for fingerprinters to differentiate between browsers. Still, such fingerprint is also brittle -- minor changes like resizing the window could cause the browser to have a unique fingerprint. Hence, users need to follow inconvenient steps to keep their fingerprint uniform. + +With all this in [mind](https://brave.com/brave-fingerprinting-and-privacy-budgets/), Brave software decided to improve their fingerprinting protection. They [proposed](https://brave.com/privacy-updates-3/) new fingerprinting protection, Farbling, arguing that it is (almost) impossible to produce uniform fingerprint without compromising user experience. Their countermeasures involve randomising values based on previous research papers [PriVaricator](https://www.doc.ic.ac.uk/~livshits/papers/pdf/www15.pdf) and [FPRandom](https://hal.inria.fr/hal-01527580/document) Both papers have shown promising results, and Brave has perfected this approach, creating effective defence while retaining almost full user experience. Farbling is a comprehensive collection of modifications that aim at producing a unique fingerprint on every domain and in every session. + +### How does farbling work? + +Farbling uses generated session and [eTLD+1](https://web.dev/same-site-same-origin/) keys to deterministically change outputs of certain APIs commonly used for browser fingerprinting. These white lies result in different websites calculating different fingerprints. Moreover, a previously visited website calculates a different fingerprint in a new browsing session. + +Farbling implementation is publicly available on Github [issue](https://github.com/brave/brave-browser/issues/8787) with discussions on design decisions, future plans and possible changes in a separate [issue](https://github.com/brave/brave-browser/issues/11770). + +Farbling operates on three levels: + 1. **Off** - countermeasures are not active + 2. **Balanced** - various APIs have modified values based on domain/session keys + 3. **Maximum** - various APIs values replaced by randomised values based on domain/session keys + +Now, what changes did actually Brave implement to specific APIs? + +### Canvas + +Canvas modifications are tracked in a separate [issue](https://github.com/brave/brave-browser/issues/9186). +Both *balanced* and *maximum* approach modify API calls `CanvasRendering2dContext.getImageData`, +`HTMLCanvasElement.toDataURL`, +`HTMLCanvasElement.toBlob`, and +`OffscreenCanvas.convertToBlob`. A [Filter function](https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/execution_context/execution_context.cc) changes values of certain pixels chosen based on session/domain keys, resulting in a unique canvas fingerprint. +On *maximum* level, methods `CanvasRenderingContext2D.isPointInPath` and `CanvasRenderingContext2D.isPointInStroke` always return *false*. + +### WebGL + +Modifications for both WebGL and WebGL2 are described in issues [webgl](https://github.com/brave/brave-browser/issues/9188) , [webgl2](https://github.com/brave/brave-browser/issues/9189). +On *balanced* level +`WebGLRenderingContext.getParameter` and other methods return slightly modified values. +`WebGLRenderingContext.readPixels` is modified similarly to canvas methods. +On *maximum* level, `WebGLRenderingContext.getParameter` returns random strings for unmasked vendor and renderer, bottom values for other arguments. Other modified calls return bottom values. All modifications can be found in the issues mentioned above or directly in the [code](https://github.com/brave/brave-core/tree/master/chromium_src/third_party/blink/renderer/modules/webgl). + +### Web Audio + +The [issue](https://github.com/brave/brave-browser/issues/9187) modifies +several endpoints of `AnalyserNode` and `AudioBuffer` APIs used for audio data handling are modified. On the *balanced* level, the amplitude of returned audio data is slightly changed based on the domain key. However, data are replaced by white noise generated from domain hash on the maximum level, so there is no relation with original data. + +### Plugins + +Currently, +`navigator.plugins` and `navigator.mimeTypes` are modified on *balanced* level to return an array with altered plugins and two fake plugins. On *maximum* level, the returned array contains only two fake plugins. +See [issue1](https://github.com/brave/brave-browser/issues/9435) and [issue2](https://github.com/brave/brave-browser/issues/10597) for more details. + +### User agent + +Brave employs the default Chrome UA and the newest OS version as the user agent string. Also, a random number of blank spaces (up to 5) appended to the end of the user agent string. +For more details, see the [GitHub issue](https://github.com/brave/brave-browser/issues/9190). + +### EnumerateDevices + +This API is used to list I/O media devices like microphone or speakers. When fingerprinting protection is active, Brave returns a shuffled list of devices. For more details, see +[issue1](https://github.com/brave/brave-browser/issues/11271) and +[issue2](https://github.com/brave/brave-browser/issues/8666). + +### HardwareConcurrency + +The number of logical processors returned by this interface is modified as follows -- on *balanced* level, a valid value between 2 and the true value, on *maximum* level, a valid value between 2 and 8. +See the [GitHub issue](https://github.com/brave/brave-browser/issues/10808) for more details. + +# Porting Farbling to JSR + +Our goal was to extend JSR anti-fingerprinting protections with similar measures to those available in Brave's Farbling. +We decided to implement Brave Farbling with minor tweaks. As Brave is an open-source project based on [Chromium](https://www.chromium.org/Home), core changes are available in the public [repository](https://github.com/brave/brave-core). Furthermore, as Brave is licensed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/) license, its countermeasures can be ported to JSR. +Similarly to Brave, JSR utilises session and domain hashes (currently, we use a different domain hash based on origin, however, we consider switching to the eTLD+1 approach used by Brave). Nevertheless, we ported only those changes that an extension can reasonably apply. So we do not plan to change system fonts as the true set of fonts can leak in several ways (e.g., CSS, canvas). We will keep a close eye on anti-fingerprining techniquest applied by Brave in the future. + +Former JSR defences were left as an option so user can choose which protection they want. For example, for **Canvas API**, JSR retains the old defence that returns a white image, but it is also possible to use Farbling and slightly modify the image. + +`CanvasRenderingContext2D.isPointInPath` and `CanvasRenderingContext2D.isPointInStroke` are modified to return *false* with 5% probability, returning *false* to every call seems to be easily identifiable and it limits the usablity of the calls. + +**WebGL**, **Web audio**, **plugins**, **hardwareConcurrency** and **deviceMemory** have been changed accordingly to Brave. API **enumerateDevices** has the same functionality as in Brave. In addition, we add fake devices to the list. **User agent** wasn't modified because it can cause compatibility issues as we support multiple browsers. Adding empty spaces at the end of UAS seems to be quite a weak countermeasure. We will continue to watch changes in the user agent and may implement some defence in future, although it looks like a [better solution](https://datatracker.ietf.org/doc/html/rfc8942) is on the way. + +JSR 0.5 changes the default level -- **level 2** to apply the farbling-based defence for all covered APIs, and it will be very similar to the *balanced* level of *Brave*. **Level 3** is redesigned to partly apply new and partly old countermeasures to provide as little information as possible. Please report websites that does not work correctly with Farbling. + +During the examination of the ported code, we [identified and reported](https://github.com/brave/brave-browser/issues/15882) an issue in the original Brave implementation. The issue was acknowledged and fixed by Brave. This is the beauty of the free software: several projects can benefit from the same code-base and mutualy improve the quality. + +# Conclusion + +Farbling-based wrappers produce very similar outputs to Brave. So with JSR, Farbling-like capabilities are available in multiple browsers. Nevertheless, keep in mind that the best anti-fingerprinting techniques are still a research question, fingerprinting techniques are deployed for security reasons (and farbling-like anti-fingerprinting masking may complicate some log in processes), so it is not completely clear what defences are the best and the choice of the defences also depends on specific use cases. We will investigate fingerprinting scripts further during the future work on this project. diff --git a/website/content/posts/localportscanning.md b/website/content/posts/localportscanning.md new file mode 100644 index 0000000..208607b --- /dev/null +++ b/website/content/posts/localportscanning.md @@ -0,0 +1,85 @@ +--- +title: How JavaScript Restrictor prevents other parties from sniffing on your local applications? +date: 2021-06-15 09:00 +--- + +We recently found a [blog post](https://blog.nem.ec/2020/05/24/ebay-port-scanning/) about +ThreatMetrix Inc. (a part of LexisNexis) scanning locally open ports for about 30,000 web +sites, including eBay. The figure below shows that a browser tries to connect to ports commonly used for remote access to the computer (e.g., RDesktop, VNC, TeamViewer) and other applications. + +![A screenshot of the browser being used as a proxy to scan locally open ports](localportscanning/1_captured_traffic.png) + +The obvious question is, what is the reason for such behaviour? The simple answer is security. See +additional links to [Security Boulevard](https://securityboulevard.com/2020/05/is-ebay-port-scanning-your-pc-probably/), [Avast](https://blog.avast.com/why-is-ebay-port-scanning-my-computer-avast), and [The register](https://www.theregister.com/2020/05/26/ebay_port_scans_your_pc/). + +One possibility is that ThreatMetrix creates a [fingerprint](https://arxiv.org/pdf/1905.01051.pdf), and locally running +applications are a part of the fingerprint. Consequently, the authentication algorithm stores +attributes about your device(s) and compare them during each log in with the previous values. Seeing +that you are logging in using a previously seen device, the algorithm can let you in with just a +password without additional proves. However, should you use a new device, the algorithm might decide that +additional authentication steps are required and send you an SMS. + +Another option is that ThreatMetrix knows that many fraudulent activities occur on +devices with specific ports open. Recall that the ports being checked concern remote desktop +access. Having a remote desktop port open means that the computer may be used by an adversary that does not sit near the computer but is connected remotely. Consequently, the authentication algorithm might decide that additional proves about the user identity should be checked. + +We do not know what the real reason behind the scanning is. It might be one of the above, both, or a +similar reason. + +### Ethical and legal issues + +Although it could be that the underlying intentions are benign and users actually do benefit from +the scanning, the scanning raises some ethical issues. + +Very often, security and privacy are interconnected. But sometimes, one might increase security by +revealing something private. In this case, ThreatMetrix learns information about the running device +that is not obvious to the device owner (a user or a company). Typically, the owner of the device +does not even know that such information can leak. If the information +stays with ThreatMetrix, then the benefits could appear to be greater than the disadvantages. +However, adversaries could stole information from ThreatMetrix (see for example the [Ecquifax breach](https://en.wikipedia.org/wiki/2017_Equifax_data_breach)) or the company can start to [sell](https://www.vice.com/en/article/qjdkq7/avast-antivirus-sells-user-browsing-data-investigation) the [information](https://www.pcmag.com/news/the-cost-of-avasts-free-antivirus-companies-can-spy-on-your-clicks) or even [share with others](https://brave.com/rtb-evidence/). + +So is the scanning and data collecting legal? As we are based in the EU, we will dig into the EU perspective. You might want to +consult your local laws if you are outside the EU. Moreover, as we are not lawyers, you might want to +consult one even in the EU. + +[EU ePrivacy Directive](https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32002L0058) applies. However, as [WP29 clarified](https://ec.europa.eu/justice/article-29/documentation/opinion-recommendation/files/2014/wp224_en.pdf) (use case 7.5), user-centric security can be viewed as strictly necessary to provide +the service. So it seems likely that port scanning for security reasons would +trigger the ePrivacy exception and user consent is not necessary. + +As the port scanning is a part of the login mechanism, open ports are personal data +without doubts. So GDPR also applies. +GDPR also list security as a possible legitimate interest of a data controller (e.g. eBay), see +recital 49. Nevertheless, if such a scan is proportionate is an open question; it is possible that the legitimate interests of data controllers (such as eBay) are overriden by the interests or fundamental rights and freedoms of the data subject (you), see Article(6)(1)(f). +The Court of Justice of EU (CJEU) decided several issues that concerned legitimate interests and the necessity of processing, e.g. [C-13/16, point 30 that also points to other related cases](https://curia.europa.eu/juris/liste.jsf?num=C-13/16) or [C-708/18 points 40–45](https://curia.europa.eu/juris/liste.jsf?num=C-708/18). It might be possible that it is strictly necessary for eBay to perform local port scanning. + +Nevertheless, Article 12-14 of GDPR lists requirements on the information that a data controller should reveal +to each data subject before the data processing starts or in a reasonable time afterwards. Hence, each controller employing ThreatMetrix should reveal, for example, in the privacy policy, what categories of data it is using and +for which purposes. From the [linked](https://blog.avast.com/why-is-ebay-port-scanning-my-computer-avast) [articles](https://www.theregister.com/2020/05/26/ebay_port_scans_your_pc/), it seems that ThreatMetrix and eBay are secretive about data being collected. + +Another GDPR issue might be data transfers to third countries. Data transfers of open ports may not be +compatible with GDPR in the light of the [CJEU C-311/18](https://curia.europa.eu/juris/liste.jsf?num=C-311/18) decision if the information leaves EEA. + +### Why is not my browser protecting me from remote servers accessing local information? + +OK, so even though the scanning could be legal, one can disagree that others should be allowed to sniff on +local applications. So why does a browser leak the information? + +Well, the browser employs so called [same origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (SOP) that in abstract theory should prevent websites from the scans in question. As your local computer is of a different origin from the remote website, your computer should be protected by SOP. Nevertheless, SOP has its limitations. First of all, some [cross-origin resource sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) is beneficial, so the browser cannot block outgoing requests to other origins. Such behaviour opens possibilities for [side-channels](https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf) to be identified. So even though the web page cannot communicate with applications on your computer (or in your network) without the cooperation of these applications, it can observe the behaviour and make some conclusions based on the observed errors, timing, etc. + +An (ad) blocker can prevent you from the activity. As the blockers typically leverage blocklists, +such a port scanning script URL needs to match a rule in a block list. Once information about a +misbehaving script becomes public, a rule can be added to a block list. However, this could take some time. Additional techniques like [DNS de-cloaking](https://blog.lukaszolejnik.com/large-scale-analysis-of-dns-based-tracking-evasion-broad-data-leaks-included/) +need to be applied in this case. + +### Network Boundary Shield to the rescue + +JSR contains a Network Boundary Shield (NBS) that blocks outgoing browser requests based on the observed behaviour, i.e. a +page hosted on public internet tries to access local URLs. +NBS just works and cannot be fooled by changes in the URL path, DNS cloaking or other techniques. + +![JSR blocks the scan](localportscanning/2_request_blocked.png) + +Firefox contains DNS API, so NBS works flawlessly. In Chromium-based browsers, the exact blocking +behaviour depends on how quickly a scanning script can fire the requests and the precise +destination (IP address or a domain name). Depending on the interaction with DNS, NBS can be side-stepped on Chrome. In this case, ThreatMetrix does not try any evasion technique, so +NBS just works in the case of eBay and ThreatMetrix. diff --git a/website/content/posts/pt/crawling.md b/website/content/posts/pt/crawling.md new file mode 100644 index 0000000..061ad12 --- /dev/null +++ b/website/content/posts/pt/crawling.md @@ -0,0 +1,140 @@ +--- +title: Medição do uso de APIs JavaScript na web +--- + +The world wide web is a complex environment. Web pages can access many APIs ranging from text formatting to access to nearby Bluetooth devices. While many APIs are used for legitimate purposes, some are misused to track and identify their users without their knowledge. In this paper, we propose a methodology to measure the usage of JavaScript APIs on the public web. The methodology consists of an automated visit of several thousand websites and intercepting JavaScript calls performed by the pages. We also provide a design and architecture of a measurement platform that can be used for an automated visit of a list of websites. The proposed platform is based on OpenWPM. The browser is instrumented by OpenWPM and a customized Web API Manager extension is responsible for capturing JavaScript API calls. + +## Introduction + +Web browsers offer a wide range of possibilities. on the surface +they _just_ display web pages, but under the hood, web +browsers provide a bridge between a viewed page and the host +operating system. A web browser allows a web page to access information +like values from sensors, information about battery status, installed +fonts, and much more. The advertisement industry often takes advantage of +the wide range of information provided by web browsers to create a web browser +[fingerprint](https://amiunique.org/links). Most commonly, the fingeprinters misuse Web APIs (also +called JavaScript APIs). + +This blog post is mainly concerned +with user tracking and fingerprinting. +For example Battery Status API implementation on Mozilla Firefox +revealed very precise value allowing the trackers to identify the user +for a [period of time](https://petsymposium.org/2017/papers/hotpets/batterystatus-not-included.pdf). +As the Battery Status API was used heavily for fingerprinting, it +has been removed from Mozilla Firefox in 2017. Other examples of +JavaScript APIs that are used often for fingerprinting are [Canvas API](http://cseweb.ucsd.edu/~hovav/papers/ms12.html), [Audio API](https://senglehardt.com/papers/princeton_phd_dissertation_englehardt.pdf), +[Permissions API](https://arxiv.org/abs/2008.04480) or [APIs for device sensors](https://dl.acm.org/doi/10.1145/3243734.3243860). + +In our work, we aim to measure the JavaScript APIs usage by popular +websites. In this article, we present core technologies to accomplish +these measurements. The stack of technologies is based on [OpenWPM](https://github.com/mozilla/OpenWPM) +enriched by a browser extension, that allows us to intercept JavaScript +calls of different APIs. This browser extension is based on Proxy +objects. + +Our work is based on work of [Peter Snyders et al.](https://www3.cs.uic.edu/pub/Bits/PeterSnyder/Browser_Feature_Usage_on_the_Modern_Web.pdf) +carried in 2016. Since then many new +APIs were specified and implemented in web browsers, see the figure below +(based on data from [Can I Use? website](https://caniuse.com/)). + +![Progress of Web APIs amount implemented in distinct browsers in time.]({attach}/images/crawling-apis.png) + +## Methodology proposal + +This section describes the methodology that we plan to use. +This methodology is based on +As it is based on the work of Peter Snyders, it is already validated. +Moreover, using a methodology that is very close to the +original one should show us a difference in the usage of the JavaScript +APIs in 2016 and 2021. + +The main idea of the measurement is to visit several thousands of the +most [popular pages](https://tranco-list.eu/#aboutus) on the internet and intercept as many JavaScript calls +as possible. + +Visiting websites will be performed through Mozilla Firefox with +an extension that intercepts and logs the JavaScript calls. +We will visit not +only the landing page but also the subset of subpages of each website. +From the landing page, we will extract three links that point to +a subpage of a given page. From each of these three subpages, we will +get another three subpage links resulting in up to 13 pages of a given +website being visited. This amount of pages should be high enough to +catch the most of JavaScript calls. +We will wait and intercept JavaScript +calls for 30 seconds on each page to wait of API calls performed during the page load. + +Results of our measurements should also provide information about the +JavaScript APIs, that were probably used in a manner, that is not +necessary for a page to be working and is very likely used in a way, +that the user would not find useful. To achieve this, + +We will run our +measurements on every page in two different modes. Firstly, we will +visit the page using the a browser withou adblocker. Later, we will also employ an adblocker. +Hence, the study will show the difference API usage of regular pages and trackers. + +### The original study + +The Snyders' study suggests that some of the JavaScript APIs are +extremely popular and they are used on more than 90% of measured pages +(e.g. a well known `Document.createElement` method from DOM API). On the +other hand, there are many APIs that are used by a minority of measured +pages. That being said, almost 50% of JavaScript APIs implemented in the +browser at the time were not used by any of the measured pages. + +The study also suggests that there is no direct connection between the +implementation date of a given JavaScript API in the browser (or by its +specification date by some of the specifications vendors) and its +popularity in using by websites. Concretely, there are some old +JavaScript APIs, such as `XMLHttpRequest` that are still very popular. +However, there are also quite new +JavaScript APIs, that are used very frequently (i.e., `Selectors API +Level 1`). + +The conducted study also measured the pages in two ways - with the ad +blocker and without any ad blocking extension. Results of measurements +showed that the blocking of different JavaScript APIs is not uniform and +some APIs are blocked more often than others. Specifically, 10% of +JavaScript APIs were blocked in 90% of cases resulting in a fact that +83% of APIs were used on less than 1% of websites when the page was +visited with active blocking extension. + +### Web API Manager + +Web API Manager is a browser extension, that aims to block explicitly +defined JavaScript APIs. It has been developed by Snyders in 2016 and +used in several studies conducted by [Snyders et al.](https://www.peteresnyder.com/https://www.peteresnyder.com/). + +The original purpose of the Web API Manager is to block explicitly +defined JavaScript APIs. However, in our measurements, we just need to +intercept the API calls, log these calls and delegate the calls to +original receivers. + +The main principle of Web API Manager is based on Proxy objects. This +metaprogramming technique allows intercepting calls performed on +objects. While the main goal of the Web API Manager extension is to +block the calls performed on objects that belong to particular +JavaScript APIs, our goal is only to intercept these operations and +delegate them to their original receivers. We will use the Log Aggregator interface to log the API +calls. + +To provide a Web API Manager the list of JavaScript APIs members we need +a list of supported APIs. The APIs implemented in Mozilla Firefox are available as [IDL files](https://searchfox.org/mozilla-central/source/dom/webidl). + +### Measurement tools + +The figure below shows a simplified +illustration of the measurement platform. There is OpenWPM in the middle of the +architecture. OpenWPM orchestrates +Selenium and Mozilla Firefox with the proxy-based intercepting Web API Manager. + +![image]({attach}/images/crawling-architecture.png) + +## The impact on JSR + +Once we have data from our crawling study, we will compare the data with [another recent study](https://github.com/uiowa-irl/FP-Inspector/blob/master/Data/potential_fingerprinting_APIs.md). As already mentioned, we want to develop a fingerprinting detection based on counting the number of different +APIs employed by a page, especially APIs that are not frequently used for benign purposes. When +a fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the +fingerprint to the server, (3) prevent storing the fingerprint for later usage. diff --git a/website/content/posts/pt/localportscanning.md b/website/content/posts/pt/localportscanning.md new file mode 100644 index 0000000..b2bef57 --- /dev/null +++ b/website/content/posts/pt/localportscanning.md @@ -0,0 +1,84 @@ +--- +title: Como é que o jShelter impede terceiros de espiar as nossas aplicações locais? +--- + +We recently found a [blog post](https://blog.nem.ec/2020/05/24/ebay-port-scanning/) about +ThreatMetrix Inc. (a part of LexisNexis) scanning locally open ports for about 30,000 web +sites, including eBay. The figure below shows that a browser tries to connect to ports commonly used for remote access to the computer (e.g., RDesktop, VNC, TeamViewer) and other applications. + +![A screenshot of the browser being used as a proxy to scan locally open ports](localportscanning/1_captured_traffic.png) + +The obvious question is, what is the reason for such behaviour? The simple answer is security. See +additional links to [Security Boulevard](https://securityboulevard.com/2020/05/is-ebay-port-scanning-your-pc-probably/), [Avast](https://blog.avast.com/why-is-ebay-port-scanning-my-computer-avast), and [The register](https://www.theregister.com/2020/05/26/ebay_port_scans_your_pc/). + +One possibility is that ThreatMetrix creates a [fingerprint](https://arxiv.org/pdf/1905.01051.pdf), and locally running +applications are a part of the fingerprint. Consequently, the authentication algorithm stores +attributes about your device(s) and compare them during each log in with the previous values. Seeing +that you are logging in using a previously seen device, the algorithm can let you in with just a +password without additional proves. However, should you use a new device, the algorithm might decide that +additional authentication steps are required and send you an SMS. + +Another option is that ThreatMetrix knows that many fraudulent activities occur on +devices with specific ports open. Recall that the ports being checked concern remote desktop +access. Having a remote desktop port open means that the computer may be used by an adversary that does not sit near the computer but is connected remotely. Consequently, the authentication algorithm might decide that additional proves about the user identity should be checked. + +We do not know what the real reason behind the scanning is. It might be one of the above, both, or a +similar reason. + +## Ethical and legal issues + +Although it could be that the underlying intentions are benign and users actually do benefit from +the scanning, the scanning raises some ethical issues. + +Very often, security and privacy are interconnected. But sometimes, one might increase security by +revealing something private. In this case, ThreatMetrix learns information about the running device +that is not obvious to the device owner (a user or a company). Typically, the owner of the device +does not even know that such information can leak. If the information +stays with ThreatMetrix, then the benefits could appear to be greater than the disadvantages. +However, adversaries could stole information from ThreatMetrix (see for example the [Ecquifax breach](https://en.wikipedia.org/wiki/2017_Equifax_data_breach)) or the company can start to [sell](https://www.vice.com/en/article/qjdkq7/avast-antivirus-sells-user-browsing-data-investigation) the [information](https://www.pcmag.com/news/the-cost-of-avasts-free-antivirus-companies-can-spy-on-your-clicks) or even [share with others](https://brave.com/rtb-evidence/). + +So is the scanning and data collecting legal? As we are based in the EU, we will dig into the EU perspective. You might want to +consult your local laws if you are outside the EU. Moreover, as we are not lawyers, you might want to +consult one even in the EU. + +[EU ePrivacy Directive](https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32002L0058) applies. However, as [WP29 clarified](https://ec.europa.eu/justice/article-29/documentation/opinion-recommendation/files/2014/wp224_en.pdf) (use case 7.5), user-centric security can be viewed as strictly necessary to provide +the service. So it seems likely that port scanning for security reasons would +trigger the ePrivacy exception and user consent is not necessary. + +As the port scanning is a part of the login mechanism, open ports are personal data +without doubts. So GDPR also applies. +GDPR also list security as a possible legitimate interest of a data controller (e.g. eBay), see +recital 49. Nevertheless, if such a scan is proportionate is an open question; it is possible that the legitimate interests of data controllers (such as eBay) are overriden by the interests or fundamental rights and freedoms of the data subject (you), see Article(6)(1)(f). +The Court of Justice of EU (CJEU) decided several issues that concerned legitimate interests and the necessity of processing, e.g. [C-13/16, point 30 that also points to other related cases](https://curia.europa.eu/juris/liste.jsf?num=C-13/16) or [C-708/18 points 40–45](https://curia.europa.eu/juris/liste.jsf?num=C-708/18). It might be possible that it is strictly necessary for eBay to perform local port scanning. + +Nevertheless, Article 12-14 of GDPR lists requirements on the information that a data controller should reveal +to each data subject before the data processing starts or in a reasonable time afterwards. Hence, each controller employing ThreatMetrix should reveal, for example, in the privacy policy, what categories of data it is using and +for which purposes. From the [linked](https://blog.avast.com/why-is-ebay-port-scanning-my-computer-avast) [articles](https://www.theregister.com/2020/05/26/ebay_port_scans_your_pc/), it seems that ThreatMetrix and eBay are secretive about data being collected. + +Another GDPR issue might be data transfers to third countries. Data transfers of open ports may not be +compatible with GDPR in the light of the [CJEU C-311/18](https://curia.europa.eu/juris/liste.jsf?num=C-311/18) decision if the information leaves EEA. + +## Why is not my browser protecting me from remote servers accessing local information? + +OK, so even though the scanning could be legal, one can disagree that others should be allowed to sniff on +local applications. So why does a browser leak the information? + +Well, the browser employs so called [same origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (SOP) that in abstract theory should prevent websites from the scans in question. As your local computer is of a different origin from the remote website, your computer should be protected by SOP. Nevertheless, SOP has its limitations. First of all, some [cross-origin resource sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) is beneficial, so the browser cannot block outgoing requests to other origins. Such behaviour opens possibilities for [side-channels](https://www.forcepoint.com/sites/default/files/resources/files/report-attacking-internal-network-en_0.pdf) to be identified. So even though the web page cannot communicate with applications on your computer (or in your network) without the cooperation of these applications, it can observe the behaviour and make some conclusions based on the observed errors, timing, etc. + +An (ad) blocker can prevent you from the activity. As the blockers typically leverage blocklists, +such a port scanning script URL needs to match a rule in a block list. Once information about a +misbehaving script becomes public, a rule can be added to a block list. However, this could take some time. Additional techniques like [DNS de-cloaking](https://blog.lukaszolejnik.com/large-scale-analysis-of-dns-based-tracking-evasion-broad-data-leaks-included/) +need to be applied in this case. + +## Network Boundary Shield to the rescue + +JSR contains a Network Boundary Shield (NBS) that blocks outgoing browser requests based on the observed behaviour, i.e. a +page hosted on public internet tries to access local URLs. +NBS just works and cannot be fooled by changes in the URL path, DNS cloaking or other techniques. + +![JSR blocks the scan](localportscanning/2_request_blocked.png) + +Firefox contains DNS API, so NBS works flawlessly. In Chromium-based browsers, the exact blocking +behaviour depends on how quickly a scanning script can fire the requests and the precise +destination (IP address or a domain name). Depending on the interaction with DNS, NBS can be side-stepped on Chrome. In this case, ThreatMetrix does not try any evasion technique, so +NBS just works in the case of eBay and ThreatMetrix. diff --git a/website/content/posts/pt/support.md b/website/content/posts/pt/support.md new file mode 100644 index 0000000..d37d1e2 --- /dev/null +++ b/website/content/posts/pt/support.md @@ -0,0 +1,55 @@ +--- +title: Conseguimos o apoio do NGI0 PET Fund +--- + +We are very happy to announce that the JavaScript +Restrictor received support from NGI0 PET Fund, a fund established by NLnet with financial +support from the European Commission's Next Generation Internet programme, under the aegis of DG +Communications Networks, Content and Technology under grant agreement No 825310. + +We are very excited to improve the extension further. We will focus on the following main goals: + +### 1. Investigate fingerprinting scripts and prepare wrappers + +Review the previously identified APIs suitable for fingerprinting. Select APIs suitable for JSR and +add wrappers for these APIs. This work has already started, see issue #66. Additionally, we want to focus +on identification of methods used for fingeprinting such as those identified by Iqbal et al., see +https://uiowa-irl.github.io/FP-Inspector/ + +### 2. Prevent unique identification of a device + +It is hard, if not impossible, to both prevent fingerprinting and still provide customized environment +for the user. Hence, we want to identify fingerprinting attempts by counting the number of different +APIs employed by a page, especially APIs that are not frequently used for benign purposes. When +a fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the +fingerprint to the server, (3) prevent storing the fingerprint for later usage. + +### 3. Code ported from Chrome Zero + +In version 0.3, we integrated features of Chrome Zero 7 as it is no longer maintained. By +integrating the functionality to JSR, we want to keep the counter-meassures available in a +maintained extension. However, we do not have sufficient tests for the functionality. + +### 4. Evaluation and porting of code from Brave + +Brave browser currently implements anti-fingerprinting techniques that aim at providing white lies +about the browser environment. We want to evaluate the messures and select techniques that are +suitable for JSR. + +### 5. Fixing known bugs + +We want to focus on the proposed changes and found bugs that are reported in the GitHub bug tracker. + +* We already closed issues #53, #62, and #72 as a part of this project. The fixes are already available as + a part of the 0.4 subversions. +* We want to also deal with issues #56 and #71 that are crucial for the success of the extension. +* We will focus on other identified bugs in the wrappers or developped techniques. + +### 6. Cooperation with the Privacy Shield project + +We are also excited to announce that we found other partners that are willing to work on our code +base through the NGI0 PET Fund, Privacy Shield +project run by Free Software Foundation. Expect inclusion of code that will help to defend your +freedoms and provide anti-malware protections. This cooperation should also improve the GUI of the +extension and create explenatory web pages explaining the functionality and its risks. It is +possible that the project will be rebranded as a result of the cooperation. diff --git a/website/content/posts/support.md b/website/content/posts/support.md new file mode 100644 index 0000000..a78010f --- /dev/null +++ b/website/content/posts/support.md @@ -0,0 +1,56 @@ +--- +title: We received support from NGI0 PET Fund +date: 2021-03-30 09:00 +--- + +We are very happy to announce that the JavaScript +Restrictor received support from NGI0 PET Fund, a fund established by NLnet with financial +support from the European Commission's Next Generation Internet programme, under the aegis of DG +Communications Networks, Content and Technology under grant agreement No 825310. + +We are very excited to improve the extension further. We will focus on the following main goals: + +### 1. Investigate fingerprinting scripts and prepare wrappers + +Review the previously identified APIs suitable for fingerprinting. Select APIs suitable for JSR and +add wrappers for these APIs. This work has already started, see issue #66. Additionally, we want to focus +on identification of methods used for fingeprinting such as those identified by Iqbal et al., see +https://uiowa-irl.github.io/FP-Inspector/ + +### 2. Prevent unique identification of a device + +It is hard, if not impossible, to both prevent fingerprinting and still provide customized environment +for the user. Hence, we want to identify fingerprinting attempts by counting the number of different +APIs employed by a page, especially APIs that are not frequently used for benign purposes. When +a fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the +fingerprint to the server, (3) prevent storing the fingerprint for later usage. + +### 3. Code ported from Chrome Zero + +In version 0.3, we integrated features of Chrome Zero 7 as it is no longer maintained. By +integrating the functionality to JSR, we want to keep the counter-meassures available in a +maintained extension. However, we do not have sufficient tests for the functionality. + +### 4. Evaluation and porting of code from Brave + +Brave browser currently implements anti-fingerprinting techniques that aim at providing white lies +about the browser environment. We want to evaluate the messures and select techniques that are +suitable for JSR. + +### 5. Fixing known bugs + +We want to focus on the proposed changes and found bugs that are reported in the GitHub bug tracker. + +* We already closed issues #53, #62, and #72 as a part of this project. The fixes are already available as + a part of the 0.4 subversions. +* We want to also deal with issues #56 and #71 that are crucial for the success of the extension. +* We will focus on other identified bugs in the wrappers or developped techniques. + +### 6. Cooperation with the Privacy Shield project + +We are also excited to announce that we found other partners that are willing to work on our code +base through the NGI0 PET Fund, Privacy Shield +project run by Free Software Foundation. Expect inclusion of code that will help to defend your +freedoms and provide anti-malware protections. This cooperation should also improve the GUI of the +extension and create explenatory web pages explaining the functionality and its risks. It is +possible that the project will be rebranded as a result of the cooperation. diff --git a/website/content/wrappers/ajax.md b/website/content/wrappers/ajax.md new file mode 100644 index 0000000..67d62b2 --- /dev/null +++ b/website/content/wrappers/ajax.md @@ -0,0 +1,3 @@ +Title: AJAX +Filename: ../common/wrappingS-AJAX.js + diff --git a/website/content/wrappers/battery-cr.md b/website/content/wrappers/battery-cr.md new file mode 100644 index 0000000..4ab8e74 --- /dev/null +++ b/website/content/wrappers/battery-cr.md @@ -0,0 +1,3 @@ +Title: Battery level +Filename: ../common/wrappingS-BATTERY-CR.js + diff --git a/website/content/wrappers/be.md b/website/content/wrappers/be.md new file mode 100644 index 0000000..b5ce5d9 --- /dev/null +++ b/website/content/wrappers/be.md @@ -0,0 +1,13 @@ +Title: Beacon API +Filename: ../common/wrappingS-BE.js + + +The navigator.sendBeacon() method asynchronously sends a small +amount of data over HTTP to a web server. It is intended to be +used for sending analytics data to a web server. For more details +see https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API and +https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon + +`navigator.sendBeacon` is the only method currently defined for the +Beacon API. + diff --git a/website/content/wrappers/dm.md b/website/content/wrappers/dm.md new file mode 100644 index 0000000..8bccd72 --- /dev/null +++ b/website/content/wrappers/dm.md @@ -0,0 +1,16 @@ +Title: Device memory +Filename: ../common/wrappingS-DM.js + +This file contains wrapper for navigator.deviceMemory https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory + +The goal is to prevent fingerprinting by modifying return value of navigator.deviceMemory parameter. + +This wrapper operates with three levels of protection: +* (0) - return random valid value from range [0.25 - real value] +* (1) - return random valid value from range [0.25 - 8] +* (2) - return 4 + +These approaches are inspired by the algorithms created by Brave Software +available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/frame/navigator_device_memory.cc + + diff --git a/website/content/wrappers/ecma-array.md b/website/content/wrappers/ecma-array.md new file mode 100644 index 0000000..e1154b6 --- /dev/null +++ b/website/content/wrappers/ecma-array.md @@ -0,0 +1,3 @@ +Title: ECMAscript arrays +Filename: ../common/wrappingS-ECMA-ARRAY.js + diff --git a/website/content/wrappers/ecma-date.md b/website/content/wrappers/ecma-date.md new file mode 100644 index 0000000..289aeb0 --- /dev/null +++ b/website/content/wrappers/ecma-date.md @@ -0,0 +1,3 @@ +Title: ECMAscript date +Filename: ../common/wrappingS-ECMA-DATE.js + diff --git a/website/content/wrappers/ecma-shared.md b/website/content/wrappers/ecma-shared.md new file mode 100644 index 0000000..1150cf1 --- /dev/null +++ b/website/content/wrappers/ecma-shared.md @@ -0,0 +1,3 @@ +Title: ECMA shared buffers +Filename: ../common/wrappingS-ECMA-SHARED.js + diff --git a/website/content/wrappers/geo.md b/website/content/wrappers/geo.md new file mode 100644 index 0000000..12b2185 --- /dev/null +++ b/website/content/wrappers/geo.md @@ -0,0 +1,26 @@ +Title: Geolocation +Filename: ../common/wrappingS-GEO.js + + +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. diff --git a/website/content/wrappers/h-c.md b/website/content/wrappers/h-c.md new file mode 100644 index 0000000..4e535c3 --- /dev/null +++ b/website/content/wrappers/h-c.md @@ -0,0 +1,3 @@ +Title: HTML Canvas +Filename: ../common/wrappingS-H-C.js + diff --git a/website/content/wrappers/hrt.md b/website/content/wrappers/hrt.md new file mode 100644 index 0000000..fcbb111 --- /dev/null +++ b/website/content/wrappers/hrt.md @@ -0,0 +1,3 @@ +Title: HTML Performance +Filename: ../common/wrappingS-HRT.js + diff --git a/website/content/wrappers/html-ls.md b/website/content/wrappers/html-ls.md new file mode 100644 index 0000000..68b5867 --- /dev/null +++ b/website/content/wrappers/html-ls.md @@ -0,0 +1,3 @@ +Title: HTML Workers +Filename: ../common/wrappingS-HTML-LS.js + diff --git a/website/content/wrappers/html.md b/website/content/wrappers/html.md new file mode 100644 index 0000000..0f1f7ec --- /dev/null +++ b/website/content/wrappers/html.md @@ -0,0 +1,18 @@ +Title: HTML window name +Filename: ../common/wrappingS-HTML.js + + +`window.name` prvides a simple cross-origin tracking method of the same tab: + +```js +window.name = "8pdRoEaQCpsjtC8w07dOy7xwXjXrHDyxxmPWBUxQKrh7xfJ4SYFH8QClp6U9T+Ypa8IEa5AwFw3x" +``` + +Go to completely different web site and window.name stays the same. + +\see https://2019.www.torproject.org/projects/torbrowser/design/ provides a library build on +top of `window.name`: https://www.thomasfrank.se/sessionvars.html. + +\see https://html.spec.whatwg.org/#history-traversal; this feature should not be ncessary +for Firefox 86 or newer https://bugzilla.mozilla.org/show_bug.cgi?id=444222. + diff --git a/website/content/wrappers/mcs.md b/website/content/wrappers/mcs.md new file mode 100644 index 0000000..6e310e4 --- /dev/null +++ b/website/content/wrappers/mcs.md @@ -0,0 +1,17 @@ +Title: Media devices +Filename: ../common/wrappingS-MCS.js + +This file contains wrapper for MediaDevices.enumerateDevices https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices + +The goal is to prevent fingerprinting by modifying return value of enumerateDevices. + +This wrapper operates with three levels of protection: +* (0) - return promise with suffled array +* (1) - return promise with shuffled array with additional 0-4 fake devices +* (2) - return empty promise + + +Shuffling approach is inspired by the algorithms created by Brave Software +available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/mediastream/media_devices.cc + + diff --git a/website/content/wrappers/np.md b/website/content/wrappers/np.md new file mode 100644 index 0000000..24b08d2 --- /dev/null +++ b/website/content/wrappers/np.md @@ -0,0 +1,18 @@ +Title: np +Filename: ../common/wrappingS-NP.js + +This file contains wrappers for NavigatorPlugins +* https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins +* https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes + +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 +available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/plugins/dom_plugin_array.cc + + diff --git a/website/content/wrappers/pt2.md b/website/content/wrappers/pt2.md new file mode 100644 index 0000000..b26e8b5 --- /dev/null +++ b/website/content/wrappers/pt2.md @@ -0,0 +1,3 @@ +Title: PT2 +Filename: ../common/wrappingS-PT2.js + diff --git a/website/content/wrappers/weba.md b/website/content/wrappers/weba.md new file mode 100644 index 0000000..f1046a2 --- /dev/null +++ b/website/content/wrappers/weba.md @@ -0,0 +1,20 @@ +Title: WebAudio +Filename: ../common/wrappingS-WEBA.js + +This file contains wrappers for AudioBuffer and AnalyserNode related calls + * https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer + * https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode + +The goal is to prevent fingerprinting by modifying the values from functions which are reading/copying from AudioBuffer and AnalyserNode. +So the audio content of wrapped objects is the same as intended. + +The modified content can be either a white noise based on domain key or a fake audio data that is modified according to +domain key to be different than the original albeit very similar (i.e. the approach +inspired by the algorithms created by Brave Software +available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/execution_context/execution_context.cc.) + +Note: Both approaches are detectable by a fingerprinter that checks if a predetermined audio +is the same as the read one. Nevertheless, the aim of the wrappers is +to limit the finerprintability. + + diff --git a/website/content/wrappers/webgl.md b/website/content/wrappers/webgl.md new file mode 100644 index 0000000..0e04b38 --- /dev/null +++ b/website/content/wrappers/webgl.md @@ -0,0 +1,29 @@ +Title: WebGL +Filename: ../common/wrappingS-WEBGL.js + +This file contains wrappers for WebGL related calls + * https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext + * https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext + +The goal is to prevent fingerprinting by modifying the values from certain WebGLRenderingContext API functions. +This includes return values of various functions which can be hardware/software specific and image data reading. + +Content is either modified according to domain and session keys to be different than the original albeit very similar +or replaced by bottom value which is consistent every time. +Both 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/webgl/webgl_rendering_context_base.cc) +and [here](https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc). + +This wrapper operates with two levels of protection: +* (0) - return modified results, such as slightly changed image, slightly changed number or random string +* (1) - return bottom values - such as zero, empty string, empty image, null, etc. + +Level 0 is trying to force WebGL fingeprint to be unique on every domain and every session. This can be effective +when used with other wrappers with same options. This level causes breakage of websites using WebGL only rarely. +Level 1 is trying to return as little information as possible while being consistent across domains and sessions. +This level can cause breakage on majority of websites using WebGL. + +Note: Both approaches are detectable by a fingerprinter that checks if a predetermined image +is the same as the read one or if specific function returns expected value. +Nevertheless, the aim of the wrappers is to limit the finerprintability. + + diff --git a/website/content/wrappers/wrappingS-AJAX.js b/website/content/wrappers/wrappingS-AJAX.js new file mode 100644 index 0000000..30deed7 --- /dev/null +++ b/website/content/wrappers/wrappingS-AJAX.js @@ -0,0 +1,57 @@ +/** \file + * \brief Wrappers for XMLHttpRequest standard + * + * \see https://xhr.spec.whatwg.org/ + * + * \author Copyright (C) 2019 Libor Polcak + * + * \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 . +// + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "window", + parent_object_property: "XMLHttpRequest", + wrapped_objects: [ + { + original_name: "XMLHttpRequest", + wrapped_name: "originalXMLHttpRequest", + }, + ], + helping_code: "var blockEveryXMLHttpRequest = args[0]; var confirmEveryXMLHttpRequest = args[1];", + wrapping_function_args: "", + wrapping_function_body: ` + var currentXMLHttpRequestObject = new originalXMLHttpRequest(); + var originalXMLHttpRequestOpenFunction = currentXMLHttpRequestObject.open; + currentXMLHttpRequestObject.open = function(...args) { + if (blockEveryXMLHttpRequest || (confirmEveryXMLHttpRequest && !confirm('There is a XMLHttpRequest on URL ' + args[1] + '. Do you want to continue?'))) { + currentXMLHttpRequestObject.send = function () {}; // Prevents throwing an exception + return undefined; + } + else { + return originalXMLHttpRequestOpenFunction.call(this, ...args); + } + }; + return currentXMLHttpRequestObject; + `, + }, + ] + add_wrappers(wrappers); +})() diff --git a/website/content/wrappers/wrappingS-BATTERY-CR.js b/website/content/wrappers/wrappingS-BATTERY-CR.js new file mode 100644 index 0000000..51643a3 --- /dev/null +++ b/website/content/wrappers/wrappingS-BATTERY-CR.js @@ -0,0 +1,49 @@ +/** \file + * \brief Wrappers for Battery Status API + * + * \see https://www.w3.org/TR/battery-status/ + * + * \author Copyright (C) 2020 Peter Hornak + * + * \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 . +// + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "navigator", + parent_object_property: "getBattery", + wrapped_objects: [], + helping_code: ` + if (navigator.getBattery === undefined) { + return; + } + `, + original_function: "navigator.getBattery", + wrapping_function_body: ` + return undefined; + `, + post_replacement_code: ` + delete BatteryManager; + ` + }, + ]; + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-BE.js b/website/content/wrappers/wrappingS-BE.js new file mode 100644 index 0000000..9d3cdd4 --- /dev/null +++ b/website/content/wrappers/wrappingS-BE.js @@ -0,0 +1,55 @@ +/** \file + * \brief Wrappers for that disables the Beacon API. + * + * \see https://www.w3.org/TR/beacon/ + * + * \author Copyright (C) 2021 Libor Polcak + * + * \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 . +// + +/** \file + * \ingroup wrappers + * + * The navigator.sendBeacon() method asynchronously sends a small + * amount of data over HTTP to a web server. It is intended to be + * used for sending analytics data to a web server. For more details + * see https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API and + * https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon + * + * `navigator.sendBeacon` is the only method currently defined for the + * Beacon API. + */ + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "navigator", + parent_object_property: "sendBeacon", + wrapped_objects: [], + helping_code: "", + /// Although we should return false, we spoof returning true as ClearURL does + wrapping_function_body: ` + return true; + `, + }, + ] + add_wrappers(wrappers); +})() diff --git a/website/content/wrappers/wrappingS-DM.js b/website/content/wrappers/wrappingS-DM.js new file mode 100644 index 0000000..8ecbf80 --- /dev/null +++ b/website/content/wrappers/wrappingS-DM.js @@ -0,0 +1,105 @@ +/** \file + * \brief Wrappers for navigator.deviceMemory property + * + * \see https://xhr.spec.whatwg.org/ + * + * \author Copyright (C) 2019 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 . +// +// 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 navigator.deviceMemory https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory + * \ingroup wrappers + * + * The goal is to prevent fingerprinting by modifying return value of navigator.deviceMemory parameter. + * + * This wrapper operates with three levels of protection: + * * (0) - return random valid value from range [0.25 - real value] + * * (1) - return random valid value from range [0.25 - 8] + * * (2) - return 4 + * + * These approaches are inspired by the algorithms created by Brave Software + * available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/frame/navigator_device_memory.cc + * + */ + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "navigator", + parent_object_property: "deviceMemory", + wrapped_objects: [], + helping_code: ` + var validValues = [0.25, 0.5, 1.0, 2.0, 4.0, 8.0]; + var ret = 4; + var realValue = navigator.deviceMemory; + if(args[0]!=2 && realValue==0.25){ + ret = realValue; + } + else if(args[0]==0){ + var maxIndex = validValues.indexOf(realValue); + if(maxIndex == -1){ + maxIndex = validValues.length-1; + } + ret = validValues[Math.floor((prng()*(maxIndex+1)))]; + } + else if(args[0]==1){ + ret = validValues[Math.floor(prng()*(validValues.length))]; + } + `, + post_wrapping_code: [ + { + code_type: "object_properties", + parent_object: "navigator", + parent_object_property: "deviceMemory", + wrapped_objects: [], + /** \brief replaces navigator.deviceMemory getter + * + * Depending on level chosen this property returns: + * * (0) - random valid value from range [0.25 - real value] + * * (1) - random valid value from range [0.25 - 8] + * * (2) - 4 + */ + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + return ret; + }`, + }, + ], + } + ], + }, + ] + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-ECMA-ARRAY.js b/website/content/wrappers/wrappingS-ECMA-ARRAY.js new file mode 100644 index 0000000..6a29547 --- /dev/null +++ b/website/content/wrappers/wrappingS-ECMA-ARRAY.js @@ -0,0 +1,680 @@ +/** \file + * \brief Wrappers for arrays from the ECMA standard library + * + * \author Copyright (C) 2020 Peter Hornak + * + * \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 . +// + +/* + * Create private namespace + */ + + +/// \see This function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function packIEEE754(v, ebits, fbits) { + var bias = (1 << (ebits - 1)) - 1; + + function roundToEven(n) { + var w = Math.floor(n), f = n - w; + if (f < 0.5) + return w; + if (f > 0.5) + return w + 1; + return w % 2 ? w + 1 : w; + } + + // Compute sign, exponent, fraction + var s, e, f; + if (v !== v) { + // NaN + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + e = (1 << ebits) - 1; + f = Math.pow(2, fbits - 1); + s = 0; + } else if (v === Infinity || v === -Infinity) { + e = (1 << ebits) - 1; + f = 0; + s = (v < 0) ? 1 : 0; + } else if (v === 0) { + e = 0; + f = 0; + s = (1 / v === -Infinity) ? 1 : 0; + } else { + s = v < 0; + v = Math.abs(v); + + if (v >= Math.pow(2, 1 - bias)) { + // Normalized + e = Math.min(Math.floor(Math.log(v) / Math.LN2), 1023); + var significand = v / Math.pow(2, e); + if (significand < 1) { + e -= 1; + significand *= 2; + } + if (significand >= 2) { + e += 1; + significand /= 2; + } + var d = Math.pow(2, fbits); + f = roundToEven(significand * d) - d; + e += bias; + if (f / d >= 1) { + e += 1; + f = 0; + } + if (e > 2 * bias) { + // Overflow + e = (1 << ebits) - 1; + f = 0; + } + } else { + // Denormalized + e = 0; + f = roundToEven(v / Math.pow(2, 1 - bias - fbits)); + } + } + + // Pack sign, exponent, fraction + var bits = [], i; + for (i = fbits; i; i -= 1) { + bits.push(f % 2 ? 1 : 0); + f = Math.floor(f / 2); + } + for (i = ebits; i; i -= 1) { + bits.push(e % 2 ? 1 : 0); + e = Math.floor(e / 2); + } + bits.push(s ? 1 : 0); + bits.reverse(); + var str = bits.join(''); + + // Bits to bytes + var bytes = []; + while (str.length) { + bytes.unshift(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; +} + +/// \see This function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function unpackIEEE754(bytes, ebits, fbits) { + // Bytes to bits + var bits = [], i, j, b, str, + bias, s, e, f; + + for (i = 0; i < bytes.length; ++i) { + b = bytes[i]; + for (j = 8; j; j -= 1) { + bits.push(b % 2 ? 1 : 0); + b = b >> 1; + } + } + bits.reverse(); + str = bits.join(''); + + // Unpack sign, exponent, fraction + bias = (1 << (ebits - 1)) - 1; + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + e = parseInt(str.substring(1, 1 + ebits), 2); + f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } else if (e > 0) { + // Normalized + return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits)); + } else if (f !== 0) { + // Denormalized + return s * Math.pow(2, -(bias - 1)) * (f / Math.pow(2, fbits)); + } else { + return s < 0 ? -0 : 0; + } +} + +/// \see Function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function unpackF64(b) { + return unpackIEEE754(b, 11, 52); +} + +/// \see Function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function packF64(v) { + return packIEEE754(v, 11, 52); +} + +/// \see Function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function unpackF32(b) { + return unpackIEEE754(b, 8, 23); +} + +/// \see Function was adopted from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js under MIT licence. +function packF32(v) { + return packIEEE754(v, 8, 23); +} + +function constructDecorator(wrapped) { + return function () { + const res = wrapped.apply(originalF, arguments); + return replacementF(res); + } +} + +function offsetDecorator(wrapped, type, proxyRef, offsetF) { + return function () { + var ref = proxyRef; + let newArr = []; + // Create a copy of array + for (let i = 0; i < this.length; i++) { + newArr[i] = this[offsetF(i)] + } + // Shift to original + for (let i = 0; i < this.length; i++) { + this[i] = newArr[i]; + } + // Do func + let res; + if (type === 3) { + res = new this.__proto__.constructor(this)[wrapped.name.split(' ')[1]]() + } else { + res = wrapped.apply(this, arguments); + } + // Create copy of new arr + let secArr = []; + for (let i = 0; i < this.length; i++) { + secArr[i] = this[i]; + } + for (let i = 0; i < this.length; i++) { + this[offsetF(i)] = secArr[i]; + } + switch (type) { + case 0: + return ref; + case 1: + return replacementF(res); + case 2: + return res; + default: + return res + } + } +} + +function redefineNewArrayFunctions(target, offsetF) { + target['sort'] = offsetDecorator(target['sort'], 0, target, offsetF); + target['reverse'] = offsetDecorator(target['reverse'], 0, target, offsetF); + target['fill'] = offsetDecorator(target['fill'], 0, target, offsetF); + target['copyWithin'] = offsetDecorator(target['copyWithin'], 0, target, offsetF); + target['subarray'] = offsetDecorator(target['subarray'], 0, target, offsetF); + target['slice'] = offsetDecorator(target['slice'], 1, target, offsetF); + target['map'] = offsetDecorator(target['map'], 1, target, offsetF); + target['filter'] = offsetDecorator(target['filter'], 1, target, offsetF); + target['set'] = offsetDecorator(target['set'], 2, target, offsetF); + target['reduce'] = offsetDecorator(target['reduce'], 2, target, offsetF); + target['reduceRight'] = offsetDecorator(target['reduceRight'], 2, target, offsetF); + target['lastIndexOf'] = offsetDecorator(target['lastIndexOf'], 2, target, offsetF); + target['indexOf'] = offsetDecorator(target['indexOf'], 2, target, offsetF); + target['forEach'] = offsetDecorator(target['forEach'], 2, target, offsetF); + target['find'] = offsetDecorator(target['find'], 2, target, offsetF); + target['join'] = offsetDecorator(target['join'], 2, target, offsetF); + target['entries'] = offsetDecorator(target['entries'], 3, target, offsetF); + target['keys'] = offsetDecorator(target['keys'], 3, target, offsetF); + target['values'] = offsetDecorator(target['values'], 3, target, offsetF); +} + +function redefineNewArrayConstructors(target) { + target['from'] = constructDecorator(originalF['from']); + target['of'] = constructDecorator(originalF['of']); +} + +/// Default proxy handler for Typed Arrays +var proxyHandler = `{ + get(target, key, receiver) { + var random_idx = Math.floor(Math.random() * target['length']); + // Load random index from array + var rand_val = target[random_idx]; + let proto_keys = ['buffer', 'byteLength', 'byteOffset', 'length']; + if (proto_keys.indexOf(key) >= 0) { + return target[key]; + } + // offsetF argument needs to be in array range + if (typeof key !== 'symbol' && Number(key) >= 0 && Number(key) < target.length) { + key = offsetF(key) + } + let value = Reflect.get(...arguments); + return typeof value == 'function' ? value.bind(target) : value; + }, + set(target, key, value) { + var random_idx = Math.floor(Math.random() * (target['length'])); + // Load random index from array + var rand_val = target[random_idx]; + rand_val = rand_val; + if (typeof key !== 'symbol' && Number(key) >= 0 && Number(key) < target.length) { + key = offsetF(key) + } + return Reflect.set(...arguments); + } +}`; + +function getByteDecorator(wrapped, offsetF, name, doMapping) { + return function () { + const originalIdx = arguments[0]; + const endian = arguments[1]; + if (name === 'getUint8') { + // Random access + let ran = wrapped.apply(this, [Math.floor(Math.random() * (this.byteLength))]); + // Call original func + arguments[0] = offsetF(originalIdx); + return wrapped.apply(this, arguments); + } + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const byteNumber = (parseInt(name[name.length - 2] + name[name.length - 1]) || parseInt(name[name.length - 1])) / 8; + let res = 0; + let swapNumber = byteNumber - 1; + for (let i = 0; i < byteNumber; i++) { + if (endian) { + // Shift starting with 0,1,2 + swapNumber = i * 2; + } + res += this.getUint8(originalIdx + i) << ((swapNumber - i) * 8); + } + return res; + } +} + +function setByteDecorator(wrapped, offsetF, name, doMapping) { + function toNBitBin(n, bits) { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + function makeString(n) { + let s = ""; + for (let i = 0; i < n; i++) { + s += "0"; + } + return s; + } + return (makeString(bits) + parseInt(n, 10).toString(2)).substr(-bits); + } + + return function () { + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const originalIdx = arguments[0]; + const value = arguments[1]; + const endian = arguments[2]; + if (name === 'setUint8') { + // Random access + this.getUint8(0); + // Call original func + arguments[0] = offsetF(originalIdx); + return wrapped.apply(this, arguments); + } + const byteNumber = (parseInt(name[name.length - 2] + name[name.length - 1]) || parseInt(name[name.length - 1])) / 8; + const binNumber = toNBitBin(value, byteNumber * 8); + let numberPart; + for (let i = 0; i < byteNumber; i++) { + numberPart = binNumber.substr(i * 8, 8); + numberPart = parseInt(numberPart, 2); + if (endian) { + this.setUint8(originalIdx + byteNumber - i - 1, numberPart); + } else { + this.setUint8(originalIdx + i, numberPart); + } + } + return undefined; + } +} + +function getFloatDecorator(wrapped, name, doMapping) { + return function () { + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const originalIdx = arguments[0]; + if (originalIdx === undefined) { + wrapped.apply(this, arguments) + } + const endian = arguments[1]; + const byteNumber = (parseInt(name[name.length - 2] + name[name.length - 1]) || parseInt(name[name.length - 1])) / 8; + let binArray = []; + // Random access + this.getUint8(0); + for (let i = 0; i < byteNumber; i++) { + binArray[binArray.length] = this.getUint8(originalIdx + i); + } + if (endian) { + binArray = binArray.reverse() + } + if (byteNumber === 4) { + return unpackF32(binArray); + } else { + return unpackF64(binArray); + } + } +} + +function setFloatDecorator(wrapped, name, doMapping) { + return function () { + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const originalIdx = arguments[0]; + const value = arguments[1]; + if (originalIdx === undefined || value === undefined) { + wrapped.apply(this, arguments) + } + const endian = arguments[2]; + const byteNumber = (parseInt(name[name.length - 2] + name[name.length - 1]) || parseInt(name[name.length - 1])) / 8; + let binArray; + + // Random access + this.getUint8(0); + if (byteNumber === 4) { + binArray = packF32(value); + } else { + binArray = packF64(value); + } + for (let i = 0; i < binArray.length; i++) { + if (endian) { + this.setUint8(originalIdx + byteNumber - i - 1, binArray[i]); + } else { + this.setUint8(originalIdx + i, binArray[i]); + } + } + return undefined; + } +} + +function getBigIntDecorator(wrapped, doMapping) { + return function () { + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const originalIdx = arguments[0]; + if (originalIdx === undefined) { + wrapped.apply(this, arguments) + } + const endian = arguments[1]; + let hex = []; + let binArray = []; + for (let i = 0; i < 8; i++) { + binArray[binArray.length] = this.getUint8(originalIdx + i); + } + if (endian) { + binArray = binArray.reverse(); + } + for (let i of binArray) { + let h = i.toString(16); + if (h.length % 2) { + h = '0' + h; + } + hex.push(h); + } + let result = BigInt('0x' + hex.join('')); + if (binArray[0] >= 128) { + return result - 18446744073709551615n - 1n; + } + return result + } +} + +function setBigIntDecorator(wrapped, doMapping) { + return function () { + if (!doMapping){ + this.getUint8(0); + return wrapped.apply(this, arguments); + } + const originalIdx = arguments[0]; + let value = arguments[1]; + if (originalIdx === undefined || value === undefined || typeof value !== 'bigint') { + return wrapped.apply(this, arguments) + } + const endian = arguments[2]; + if (value < 0n) { + value = 18446744073709551615n + value + 1n; + } + let hex = BigInt(value).toString(16); + if (hex.length % 2) { + hex = '0' + hex; + } + + const len = hex.length / 2; + let binArray = []; + let j = 0; + // Random access + this.getUint8(0); + for (let i = 0; i < 8; i++) { + if (i < 8 - len) { + binArray[binArray.length] = 0; + } else { + binArray[binArray.length] = parseInt(hex.slice(j, j + 2), 16); + j += 2; + } + } + if (endian) { + binArray.reverse(); + } + for (let i in binArray) { + this.setUint8(originalIdx + parseInt(i), binArray[i]) + } + return undefined; + } +} + +function redefineDataViewFunctions(target, offsetF, doMapping) { + // Replace functions working with Ints + var dataViewTypes = ['getInt8', 'getInt16', 'getInt32', 'getUint8', 'getUint16', 'getUint32']; + for (type of dataViewTypes) { + target[type] = getByteDecorator(target[type], offsetF, type, doMapping); + type = 's' + type.substr(1); + target[type] = setByteDecorator(target[type], offsetF, type, doMapping); + } + + var dataViewTypes2 = ['getFloat32', 'getFloat64']; + for (type of dataViewTypes2) { + target[type] = getFloatDecorator(target[type], type, doMapping); + type = 's' + type.substr(1); + target[type] = setFloatDecorator(target[type], type, doMapping); + } + var dataViewTypes3 = ['getBigInt64', 'getBigUint64']; + for (type of dataViewTypes3) { + target[type] = getBigIntDecorator(target[type], doMapping); + type = 's' + type.substr(1); + target[type] = setBigIntDecorator(target[type], doMapping); + } + +}; + +(function () { + var common_function_body = ` + let _data; + if (typeof target === 'object' && target !== null) { + if (is_proxy in target){ + // If already Proxied array is passed as arg return it + return target; + } + } + _data = new originalF(...arguments); + // No offset + var offsetF = function(x) { + return x; + }; + if (doMapping) { + // Random numbers for offset function + let n = _data.length; + let a; + while (true){ + a = Math.floor(Math.random() * 4096); + if (gcd(a,n) === 1){ + break; + } + } + let b = Math.floor(Math.random() * 4096); + // Define function to calculate offset; + offsetF = function(x) { + if (x === undefined){ + return x; + } + x = x < 0 ? n + x : x; + return (a*x + b) % n ; + }; + let arr = [] + for (let i = 0; i < _data.length; i++) { + arr[i] = _data[i]; + } + + for (let i = 0; i < _data.length; i++) { + _data[offsetF(i)] = arr[i]; + } + } + let _target = target; + var proxy = new newProxy(_data, ${proxyHandler}); + // Proxy has to support all methods, original object supports. + ${offsetDecorator}; + ${redefineNewArrayFunctions}; + if (doMapping) { + // Methods have to work with offsets; + redefineNewArrayFunctions(proxy, offsetF); + } + // Preload array + let j; + for (let i = 0; i < _data['length']; i++) { + j = _data[i]; + } + return proxy; + `; + + var wrappers = [ + { + parent_object: 'window', + parent_object_property: 'DataView', + original_function: 'window.DataView', + wrapped_objects: [], + wrapping_function_args: 'buffer, byteOffset, byteLength', + helping_code: packIEEE754 + unpackIEEE754 + packF32 + unpackF32 + packF64 + unpackF64 + ` + function gcd(x, y) { + while(y) { + var t = y; + y = x % y; + x = t; + } + return x; + } + var doMapping = args[0]; + `, + wrapping_function_body: ` + let _data = new originalF(...arguments); + let n = _data.byteLength; + let a; + while (true){ + a = Math.floor(Math.random() * 4096); + if (gcd(a,n) === 1){ + break; + } + } + let b = Math.floor(Math.random() * 4096); + // Define function to calculate offset; + offsetF = function(x) { + if (x === undefined){ + return x; + } + x = x < 0 ? n + x : x; + return (a*x + b) % n ; + }; + ${getByteDecorator} + ${setByteDecorator} + ${getFloatDecorator} + ${setFloatDecorator} + ${getBigIntDecorator} + ${setBigIntDecorator} + ${redefineDataViewFunctions} + for (let i = 0; i < n; i++) { + let random = _data.getUint8(i); + } + if (!doMapping){ + offsetF = function(x) { + return x; + } + } + redefineDataViewFunctions(_data, offsetF, doMapping); + return _data; + `, + }, + ]; + + + let DEFAULT_TYPED_ARRAY_WRAPPER = { + parent_object: 'window', + parent_object_property: '_PROPERTY_', + original_function: 'window._PROPERTY_', + wrapped_objects: [], + helping_code:` + let doMapping = args[0]; + var proxyHandler = ${proxyHandler}; + function gcd(x, y) { + while(y) { + var t = y; + y = x % y; + x = t; + } + return x; + } + + const is_proxy = Symbol('is_proxy'); + const originalProxy = Proxy; + var proxyHandler = { + has (target, key) { + return (is_proxy === key) || (key in target); + } + }; + let newProxy = new Proxy(Proxy, { + construct(target, args) { + return new originalProxy(new target(...args), proxyHandler); + } + }); + `, + wrapping_function_args: `target`, + wrapping_function_body: common_function_body, + post_replacement_code: ` + ${constructDecorator} + ${redefineNewArrayConstructors} + redefineNewArrayConstructors(window._PROPERTY_); + ` + }; + + var typedTypes = ['Uint8Array', 'Int8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array']; + for (let p of typedTypes) { + let wrapper = {...DEFAULT_TYPED_ARRAY_WRAPPER}; + wrapper.parent_object_property = wrapper.parent_object_property.replace('_PROPERTY_', p); + wrapper.original_function = wrapper.original_function.replace('_PROPERTY_', p); + wrapper.post_replacement_code = wrapper.post_replacement_code.split('_PROPERTY_').join(p); + wrapper.wrapping_function_body += `// ${p};`; + wrappers.push(wrapper); + } + + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-ECMA-DATE.js b/website/content/wrappers/wrappingS-ECMA-DATE.js new file mode 100644 index 0000000..fffb5d6 --- /dev/null +++ b/website/content/wrappers/wrappingS-ECMA-DATE.js @@ -0,0 +1,101 @@ +/** \file + * \brief Wrappers for the Date object + * + * \author Copyright (C) 2019 Libor Polcak + * \author Copyright (C) 2020 Peter Hornak + * + * \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 . +// + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "window", + parent_object_property: "Date", + wrapped_objects: [ + { + original_name: "Date", + wrapped_name: "originalDateConstructor", + }, + ], + helping_code: rounding_function + noise_function + + ` + var precision = args[0]; + var doNoise = args[1]; + var func = rounding_function; + if (doNoise) { + func = noise_function; + } + `, + wrapping_function_args: "", + wrapping_function_body: ` + var wrapped = new originalDateConstructor(...arguments); + let cachedValue; + if (arguments[0] !== undefined) { + // Don't change lastValue if custom arguments are passed + cachedValue = lastValue; + } + var changedValue = func(wrapped.getTime(), precision); + if (cachedValue) { + // Don't change lastValue if custom arguments are passed + lastValue = cachedValue; + } + wrapped.setTime(changedValue); + return wrapped; + `, + wrapper_prototype: "originalDateConstructor", + post_wrapping_code: [ + { + code_type: "function_define", + original_function: "originalDateConstructor.now", + parent_object: "window.Date", + parent_object_property: "now", + wrapping_function_args: "", + wrapping_function_body: "return func(originalDateConstructor.now.call(Date), precision);", + }, + { + code_type: "function_export", + parent_object: "window.Date", + parent_object_property: "parse", + export_function_name: "originalDateConstructor.parse", + }, + { + code_type: "function_export", + parent_object: "window.Date", + parent_object_property: "UTC", + export_function_name: "originalDateConstructor.UTC", + }, + { + code_type: "assign", + parent_object: "window.Date", + parent_object_property: "prototype", + value: "originalDateConstructor.prototype", + }, + { + code_type: "assign", + parent_object: "window.Date.prototype", + parent_object_property: "constructor", + value: "window.Date", + }, + ] + }, + ] + add_wrappers(wrappers); +})() diff --git a/website/content/wrappers/wrappingS-ECMA-SHARED.js b/website/content/wrappers/wrappingS-ECMA-SHARED.js new file mode 100644 index 0000000..811f9f6 --- /dev/null +++ b/website/content/wrappers/wrappingS-ECMA-SHARED.js @@ -0,0 +1,80 @@ +/** \file + * \brief Wrappers for SharedArrayBuffer + * + * \author Copyright (C) 2020 Peter Hornak + * + * \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 . +// + +/* + * Create private namespace + */ + +var proxyHandler = `{ + get(target, key, receiver) { + let j; + let slow = Math.floor(Math.random() * 10000) + for (let i = 0; i < slow;) { + j = i; + i = j + 1; + } + let value = target[key]; + return typeof value == 'function' ? value.bind(target) : value; + }, + set(target, key, value) { + let j; + let slow = Math.floor(Math.random() * 10000); + for (let i = 0; i < slow;) { + j = i; + i = j + 1; + } + return Reflect.set(...arguments); + } +}`; + +(function () { + var wrappingFunctionBody = ` + let _target = target; + let _data = new originalF(target); + var proxy = new Proxy(_data, ${proxyHandler}); + return proxy; + `; + + var wrappers = [ + { + parent_object: "window", + parent_object_property: "SharedArrayBuffer", + original_function: "window.SharedArrayBuffer", + wrapped_objects: [], + helping_code: ` + if (window.SharedArrayBuffer === undefined) { + return; + } + let forbid = args[0]; + `, + wrapping_function_args: `target`, + wrapping_function_body: wrappingFunctionBody, + post_replacement_code: ` + if (forbid) { + delete(window.SharedArrayBuffer); + } + `, + } + ]; + + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-GEO.js b/website/content/wrappers/wrappingS-GEO.js new file mode 100644 index 0000000..3e17b7d --- /dev/null +++ b/website/content/wrappers/wrappingS-GEO.js @@ -0,0 +1,319 @@ +/** \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 + * + * \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 . +// + +/** \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 = gen_random32 + ` + /** + * Make sure that repeated calls shows the same position to reduce + * fingerprintablity. + */ + var previouslyReturnedCoords = undefined; + /** + * \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. + */ + var processOriginalGPSDataObject = ` + 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); + if (provideAccurateGeolocationData) { + var pos = { + coords: originalPositionObject.coords, + timestamp: geoTimestamp // Limit the timestamp accuracy + }; + successCallback(pos); + return; + } + if (previouslyReturnedCoords !== undefined) { + var pos = { + coords: previouslyReturnedCoords, + timestamp: geoTimestamp + }; + successCallback(pos); + return; + } + + 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 + gen_random32()/2**32 * 3 * DESIRED_ACCURACY_KM - DESIRED_ACCURACY_KM; + var newy = ymin + gen_random32()/2**32 * 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 + + const editedPositionObject = { + coords: { + latitude: newLatitude, + longitude: newLongitude, + altitude: null, + accuracy: newAccuracy, + altitudeAccuracy: null, + heading: null, + speed: null, + __proto__: originalPositionObject.coords.__proto__ + }, + timestamp: geoTimestamp, + __proto__: originalPositionObject.__proto__ + }; + Object.freeze(editedPositionObject.coords); + previouslyReturnedCoords = editedPositionObject.coords; + successCallback(editedPositionObject); + } + `; + /** + * \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", + parent_object_property: "geolocation", + wrapped_objects: [], + helping_code: setArgs, + post_wrapping_code: [ + { + code_type: "delete_properties", + parent_object: "navigator", + apply_if: "!enableGeolocation", + delete_properties: ["geolocation"], + } + ], + nofreeze: true, + }, + { + parent_object: "navigator.geolocation", + parent_object_property: "getCurrentPosition", + + wrapped_objects: [ + { + original_name: "navigator.geolocation.getCurrentPosition", + wrapped_name: "originalGetCurrentPosition", + }, + ], + helping_code: setArgs + processOriginalGPSDataObject_globals, + wrapping_function_args: "successCallback, errorCallback, origOptions", + /** \fn fake navigator.geolocation.getCurrentPosition + * \brief Provide a fake geolocation position + */ + wrapping_function_body: processOriginalGPSDataObject + ` + var options = { + enableHighAccuracy: false, + }; + 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: "navigator.geolocation", + parent_object_property: "watchPosition", + wrapped_objects: [ + { + original_name: "navigator.geolocation.watchPosition", + wrapped_name: "originalWatchPosition", + }, + ], + helping_code: setArgs + processOriginalGPSDataObject_globals + "let watchPositionCounter = 0;", + wrapping_function_args: "successCallback, errorCallback, origOptions", + /** \fn fake navigator.geolocation.watchPosition + * navigator.geolocation.watchPosition intended use concerns tracking user position changes. + * JSR 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 + var pos = { + coords: originalPositionObject.coords, + timestamp: geoTimestamp + }; + successCallback(pos); + } + originalWatchPosition.call(this, wrappedSuccessCallback, errorCallback, origOptions); + } + else { + // Re-use the wrapping of n.g.getCurrentPosition() + navigator.geolocation.getCurrentPosition(successCallback, errorCallback, origOptions); + watchPositionCounter++; + return watchPositionCounter; + } + `, + }, + { + parent_object: "navigator.geolocation", + parent_object_property: "clearWatch", + wrapped_objects: [ + { + original_name: "navigator.geolocation.clearWatch", + wrapped_name: "originalClearWatch", + }, + ], + helping_code: setArgs, + wrapping_function_args: "id", + /** \fn fake_or_original navigator.geolocation.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(navigator.geolocation, id); + } + `, + } + ] + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-H-C.js b/website/content/wrappers/wrappingS-H-C.js new file mode 100644 index 0000000..b81c5d3 --- /dev/null +++ b/website/content/wrappers/wrappingS-H-C.js @@ -0,0 +1,382 @@ +/** \file + * \brief This file contains wrappers for Canvas-related calls + * + * \see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API + * \see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D + * \see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas + * + * \author Copyright (C) 2019 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 . +// +// 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 calls related to the Canvas API, about which you can read more at MDN: + * * [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) + * * [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) + * * [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) + * + * The goal is to prevent fingerprinting by modifying the values that can be read from the canvas. + * So the visual content of wrapped canvases as displayed on the screen is the same as intended. + * + * The modified content can be either an empty image or a fake image that is modified according to + * session and domain keys to be different than the original albeit very similar (i.e. the approach + * 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/core/execution_context/execution_context.cc). + * + * Note that both approaches are detectable by a fingerprinter that checks if a predetermined image + * inserted to the canvas is the same as the read one, see [here](https://arkenfox.github.io/TZP/tests/canvasnoise.html) for an example, + * Nevertheless, the aim of the wrappers is to limit the finerprintability. + * + * Also note that a determined fingerprinter can reveal the modifications and consequently uncover + * the original image. This can be avoided with the approach that completely clears the data stored + * in the canvas. Use the modifications based on session and domain keys if you want to provide an + * image that is similar to the original or if you want to produce a fake image that is not + * obviously spoofed to a naked eye. Otherwise, use the clearing approach. + */ + +/* + * Create private namespace + */ +(function() { + /** \fn fake create_post_wrappers + * \brief This function is used to prevent access to unwrapped APIs through iframes. + * + * \param The object to wrap like HTMLIFrameElement.prototype + */ + function create_post_wrappers(parent_object) { + return [{ + code_type: "object_properties", + parent_object: parent_object, + parent_object_property: "contentWindow", + wrapped_objects: [{ + original_name: "Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow')['get'];", + wrapped_name: "cw", + }], + wrapped_properties: [{ + property_name: "get", + property_value: ` + function() { + var parent=cw.call(this); + try { + parent.HTMLCanvasElement; + } + catch(d) { + return; // HTMLIFrameElement.contentWindow properties could not be accessed anyway + } + wrapping(parent); + return parent; + }`, + }], + }, + { + code_type: "object_properties", + parent_object: parent_object, + parent_object_property: "contentDocument", + wrapped_objects: [{ + original_name: "Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentDocument')['get'];", + wrapped_name: "cd", + }, ], + wrapped_properties: [{ + property_name: "get", + property_value: ` + function() { + var parent=cw.call(this); + try{ + parent.HTMLCanvasElement; + } + catch(d) { + return; // HTMLIFrameElement.contentDocument properties could not be accessed anywaya + } + wrapping(parent); + return cd.call(this); + }`, + }, ], + }, + ]; + } + + /** @var String helping_code. + * Selects if the canvas should be cleared (1) or a fake image should be created based on session + * and domain keys (0). + */ + var helping_code = `var approach = args[0];`; + var wrappers = [{ + parent_object: "HTMLCanvasElement.prototype", + parent_object_property: "toDataURL", + wrapped_objects: [{ + original_name: "HTMLCanvasElement.prototype.toDataURL", + wrapped_name: "origToDataURL", + }], + helping_code: helping_code, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.HTMLCanvasElement.prototype.toDataURL", + replace_original_function: true, + wrapping_function_args: "...args", + /** \fn fake HTMLCanvasElement.prototype.toDataURL + * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details. + * + * Internally creates a fake canvas of the same height and width as the original and calls + * CanvasRenderingContext2D.getImageData() that determines the result. If canvas uses WebGLRenderingContext + * the content is copied to new canvas using CanvasRenderingContext2D and function toDataURL is called on it. + */ + wrapping_function_body: ` + var ctx = this.getContext("2d"); + if(ctx){ + var fake = document.createElement("canvas"); + fake.setAttribute("width", this.width); + fake.setAttribute("height", this.height); + var stx = fake.getContext("2d"); + var imageData = ctx.getImageData(0, 0, this.width, this.height); + stx.putImageData(imageData, 0, 0); + return origToDataURL.call(fake, ...args); + } + else { + var ctx = this.getContext("webgl2", {preserveDrawingBuffer: true}) || + this.getContext("experimental-webgl2", {preserveDrawingBuffer: true}) || + this.getContext("webgl", {preserveDrawingBuffer: true}) || + this.getContext("experimental-webgl", {preserveDrawingBuffer: true}) || + this.getContext("moz-webgl", {preserveDrawingBuffer: true}); + if(ctx){ + var fake = document.createElement("canvas"); + fake.setAttribute("width", this.width); + fake.setAttribute("height", this.height); + var stx = fake.getContext("2d"); + stx.drawImage(ctx.canvas, 0, 0); + return fake.toDataURL(); + } + } + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + { + parent_object: "CanvasRenderingContext2D.prototype", + parent_object_property: "getImageData", + wrapped_objects: [{ + original_name: "CanvasRenderingContext2D.prototype.getImageData", + wrapped_name: "origGetImageData", + }], + helping_code: helping_code + ` + function lfsr_next(v) { + return BigInt.asUintN(64, ((v >> 1n) | (((v << 62n) ^ (v << 61n)) & (~(~0n << 63n) << 62n)))); + } + var farble = function(context, fake) { + if(approach === 1){ + fake.fillStyle = "white"; + fake.fillRect(0, 0, context.canvas.width, context.canvas.height); + return; + } + else if(approach === 0){ + const width = context.canvas.width; + const height = context.canvas.height; + var imageData = origGetImageData.call(context, 0, 0, width, height); + fake.putImageData(imageData, 0, 0); + var fakeData = origGetImageData.call(fake, 0, 0, width, height); + var pixel_count = BigInt(width * height); + var channel = domainHash[0].charCodeAt(0) % 3; + var canvas_key = domainHash; + var v = BigInt(sessionHash); + + for (let i = 0; i < 32; i++) { + var bit = canvas_key[i]; + for (let j = 8; j >= 0; j--) { + var pixel_index = (4 * Number(v % pixel_count) + channel); + fakeData.data[pixel_index] = fakeData.data[pixel_index] ^ (bit & 0x1); + bit = bit >> 1; + v = lfsr_next(v); + } + } + fake.putImageData(fakeData, 0, 0); + } + };`, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.CanvasRenderingContext2D.prototype.getImageData", + replace_original_function: true, + wrapping_function_args: "sx, sy, sw, sh", + /** \fn fake CanvasRenderingContext2D.prototype.getImageData + * \brief Returns a fake image data of the same height and width as stored in the original canvas. + * + * Internally calls the farbling that select the output which can be either an empty image or + * a fake image that is modified according to session and domain keys to be different than the + * original albeit very similar. + */ + wrapping_function_body: ` + var fake = document.createElement("canvas"); + fake.setAttribute("width", this.canvas.width); + fake.setAttribute("height", this.canvas.height); + var stx = fake.getContext("2d"); + farble(this,stx); + return origGetImageData.call(stx, sx, sy, sw, sh); + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + { + parent_object: "HTMLCanvasElement.prototype", + parent_object_property: "toBlob", + wrapped_objects: [{ + original_name: "HTMLCanvasElement.prototype.toBlob", + wrapped_name: "origToBlob", + }], + helping_code: ``, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.HTMLCanvasElement.prototype.toBlob", + replace_original_function: true, + wrapping_function_args: "...args", + /** \fn fake HTMLCanvasElement.prototype.toBlob + * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details. + * + * Internally creates a fake canvas of the same height and width as the original and calls + * CanvasRenderingContext2D.getImageData() that detemines the result. + */ + wrapping_function_body: ` + var ctx = this.getContext("2d"); + var fake = document.createElement("canvas"); + fake.setAttribute("width", this.width); + fake.setAttribute("height", this.height); + var stx = fake.getContext("2d"); + var imageData = ctx.getImageData(0,0,this.width,this.height); + stx.putImageData(imageData, 0, 0); + return origToBlob.call(fake, ...args); + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + { + parent_object: "OffscreenCanvas.prototype", + parent_object_property: "convertToBlob", + wrapped_objects: [{ + original_name: "OffscreenCanvas.prototype.convertToBlob", + wrapped_name: "origConvertToBlob", + }], + helping_code: ``, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.OffscreenCanvas.prototype.convertToBlob", + replace_original_function: true, + wrapping_function_args: "...args", + /** \fn fake OffscreenCanvas.prototype.convertToBlob + * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details. + * + * Internally creates a fake canvas of the same height and width as the original and calls + * CanvasRenderingContext2D.getImageData() that detemines the result. + */ + wrapping_function_body: ` + var ctx = this.getContext("2d"); + var fake = document.createElement("canvas"); + fake.setAttribute("width", this.width); + fake.setAttribute("height", this.height); + var stx = fake.getContext("2d"); + var imageData = ctx.getImageData(0,0,this.width,this.height); + stx.putImageData(imageData, 0, 0); + return origConvertToBlob.call(fake, ...args); + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + { + parent_object: "CanvasRenderingContext2D.prototype", + parent_object_property: "isPointInPath", + wrapped_objects: [{ + original_name: "CanvasRenderingContext2D.prototype.isPointInPath", + wrapped_name: "origIsPointInPath", + }], + helping_code: helping_code + ` + function farbleIsPointInPath(ctx, ...args){ + if(approach === 0){ + var ret = origIsPointInPath.call(ctx, ...args); + return (ret && ((prng()*20) > 1)); + } + else if(approach === 1){ + return origIsPointInPath.call(ctx, ...args); + } + }; + `, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.CanvasRenderingContext2D.prototype.isPointInPath", + replace_original_function: true, + wrapping_function_args: "...args", + /** \fn fake CanvasRenderingContext2D.prototype.isPointInPath + * \brief Returns modified result + * + * Either returns false or original function return value which is changed to false with 1/20 probability + * + * \bug Changing value with probability has some issues: + * * multiple calls with the same pixel can return different values + * * inconsistencies among adjacent pixels + */ + wrapping_function_body: ` + return farbleIsPointInPath(this, ...args); + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + { + parent_object: "CanvasRenderingContext2D.prototype", + parent_object_property: "isPointInStroke", + wrapped_objects: [{ + original_name: "CanvasRenderingContext2D.prototype.isPointInStroke", + wrapped_name: "origIsPointInStroke", + }], + helping_code: helping_code + ` + function farbleIsPointInStroke(ctx, ...args){ + if(approach === 0){ + var ret = origIsPointInStroke.call(ctx, ...args); + return (ret && ((prng()*20) > 1)); + } + else if(approach === 1){ + return origIsPointInStroke.call(ctx, ...args); + } + }; + `, + wrapping_code_function_name: "wrapping", + wrapping_code_function_params: "parent", + wrapping_code_function_call_window: true, + original_function: "parent.CanvasRenderingContext2D.prototype.isPointInStroke", + replace_original_function: true, + wrapping_function_args: "...args", + /** \fn fake CanvasRenderingContext2D.prototype.isPointInStroke + * \brief Returns modified result + * + * Either returns false or original function return value which is changed to false with 1/20 probability + * + * \bug Changing value with probability has some issues: + * * multiple calls with the same pixel can return different values + * * inconsistencies among adjacent pixels + */ + wrapping_function_body: ` + return farbleIsPointInStroke(this, ...args); + `, + post_wrapping_code: create_post_wrappers("HTMLIFrameElement.prototype"), + }, + ] + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-HRT.js b/website/content/wrappers/wrappingS-HRT.js new file mode 100644 index 0000000..08e2ab4 --- /dev/null +++ b/website/content/wrappers/wrappingS-HRT.js @@ -0,0 +1,53 @@ +/** \file + * \brief Wrappers for High Resolution Time (Level 2) standard + * + * \see https://w3c.github.io/hr-time/ + * + * \author Copyright (C) 2019 Libor Polcak + * \author Copyright (C) 2020 Peter Hornak + * + * \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 . +// + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "Performance.prototype", + parent_object_property: "now", + wrapped_objects: [ + { + original_name: "Performance.prototype.now", + wrapped_name: "origNow", + } + ], + helping_code: rounding_function + noise_function + ` + let precision = args[0]; + let doNoise = args[1]; + `, + wrapping_function_args: "", + wrapping_function_body: ` + var originalPerformanceValue = origNow.call(window.performance); + var limit_precision = doNoise ? noise_function : rounding_function; + return limit_precision(originalPerformanceValue, precision); + `, + }, + ]; + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-HTML-LS.js b/website/content/wrappers/wrappingS-HTML-LS.js new file mode 100644 index 0000000..39d6589 --- /dev/null +++ b/website/content/wrappers/wrappingS-HTML-LS.js @@ -0,0 +1,280 @@ +/** \file + * \brief Wrappers for Workers + * + * \author Copyright (C) 2019 Libor Polcak + * \author Copyright (C) 2020 Peter Hornak + * \author Copyright (C) 2021 Matus Svancar + * + * \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 . +// + +/* + * Create private namespace + */ +(function() { + var polyfillBody = ` + /// This polyfill was adopted from https://github.com/nolanlawson/pseudo-worker under Apache License 2.0 and modified. + function doEval(self, __pseudoworker_script) { + /* jshint unused:false */ + (function () { + /* jshint evil:true */ + eval(__pseudoworker_script); + }).call(window); + } + + var messageListeners = []; + var errorListeners = []; + var workerMessageListeners = []; + var workerErrorListeners = []; + var postMessageListeners = []; + var terminated = false; + var script; + var workerSelf; + + var api = this; + + // custom each loop is for IE8 support + function executeEach(arr, fun) { + var i = -1; + while (++i < arr.length) { + if (arr[i]) { + fun(arr[i]); + } + } + } + + function callErrorListener(err) { + return function (listener) { + listener({ + type: 'error', + error: err, + message: err.message + }); + }; + } + + function addEventListener(type, fun) { + /* istanbul ignore else */ + if (type === 'message') { + messageListeners.push(fun); + } else if (type === 'error') { + errorListeners.push(fun); + } + } + + function removeEventListener(type, fun) { + var listeners; + /* istanbul ignore else */ + if (type === 'message') { + listeners = messageListeners; + } else if (type === 'error') { + listeners = errorListeners; + } else { + return; + } + var i = -1; + while (++i < listeners.length) { + var listener = listeners[i]; + if (listener === fun) { + delete listeners[i]; + break; + } + } + } + + function postError(err) { + var callFun = callErrorListener(err); + if (typeof api.onerror === 'function') { + callFun(api.onerror); + } + if (workerSelf && typeof workerSelf.onerror === 'function') { + callFun(workerSelf.onerror); + } + executeEach(errorListeners, callFun); + executeEach(workerErrorListeners, callFun); + } + + function runPostMessage(msg, transfer) { + function callFun(listener) { + try { + listener({data: msg, ports: transfer}); + } catch (err) { + postError(err); + } + } + + if (workerSelf && typeof workerSelf.onmessage === 'function') { + callFun(workerSelf.onmessage); + } + executeEach(workerMessageListeners, callFun); + } + + function postMessage(msg, transfer) { + if (typeof msg === 'undefined') { + throw new Error('postMessage() requires an argument'); + } + if (terminated) { + return; + } + if (!script) { + postMessageListeners.push({msg: msg, transfer: (transfer ? transfer : undefined)}); + return; + } + runPostMessage(msg, transfer); + } + + function terminate() { + terminated = true; + } + + function workerPostMessage(msg) { + if (terminated) { + return; + } + + function callFun(listener) { + listener({ + data: msg + }); + } + + if (typeof api.onmessage === 'function') { + callFun(api.onmessage); + } + executeEach(messageListeners, callFun); + } + + function workerAddEventListener(type, fun) { + /* istanbul ignore else */ + if (type === 'message') { + workerMessageListeners.push(fun); + } else if (type === 'error') { + workerErrorListeners.push(fun); + } + } + + var xhr = new XMLHttpRequest(); + + xhr.open('GET', path); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status >= 200 && xhr.status < 400) { + script = xhr.responseText; + workerSelf = { + postMessage: workerPostMessage, + addEventListener: workerAddEventListener, + close: terminate + }; + doEval(workerSelf, script); + var currentListeners = postMessageListeners; + postMessageListeners = []; + for (var i = 0; i < currentListeners.length; i++) { + runPostMessage(currentListeners[i].msg, currentListeners[i].transfer); + } + } else { + postError(new Error('cannot find script ' + path)); + } + } + }; + + xhr.send(); + + api.postMessage = postMessage; + api.addEventListener = addEventListener; + api.removeEventListener = removeEventListener; + api.terminate = terminate; + + return api; + `; + + var slowBody = ` + let _data = new originalF(path); + let _old = _data.postMessage; + _data.postMessage = function(message) { + let delay = Math.floor(Math.random() * 10**9) + let j; + for (let i = 0; i < delay;) { + j = i; + i = j + 1; + } + return _old.call(_data, message); + } + return _data; + `; + + var wrappers = [ + { + parent_object: "navigator", + parent_object_property: "hardwareConcurrency", + wrapped_objects: [], + helping_code: ` + var ret = 2; + if(args[0]==0){ + var realValue = navigator.hardwareConcurrency; + ret = Math.floor(2+prng()*(realValue-2)); + } + else if(args[0]==1){ + ret = Math.floor(2+(prng()*6)); + } + `, + post_wrapping_code: [ + { + code_type: "object_properties", + original_name: "navigator.hardwareConcurrency", + wrapped_name: "origConcurrency", + wrapped_objects: [], + parent_object: "navigator", + parent_object_property: "hardwareConcurrency", + /** \brief replaces navigator.hardwareConcurrency getter + * + * Depending on level chosen this property returns: + * * (0) - random valid value from range [2 - real value] + * * (1) - random valid value from range [2 - 8] + * * (2) - 2 + */ + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + return ret; + }`, + }, + ], + } + ], + }, + { + parent_object: "window", + parent_object_property: "Worker", + original_function: "window.Worker", + wrapped_objects: [], + helping_code: ` + let doPolyfill = args[0]; + `, + wrapping_function_args: `path`, + wrapping_function_body: ` + if (doPolyfill) { + ${polyfillBody} + } else { + ${slowBody} + } + `, + } + ] + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-HTML.js b/website/content/wrappers/wrappingS-HTML.js new file mode 100644 index 0000000..c91e993 --- /dev/null +++ b/website/content/wrappers/wrappingS-HTML.js @@ -0,0 +1,57 @@ +/** \file + * \brief This file contains wrapper that clears the window.name property + * + * \see https://developer.mozilla.org/en-US/docs/Web/API/Window/name + * + * \author Copyright (C) 2020 Martin Bednar, Libor Polcak + * + * \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 . +// + +/** \file + * \ingroup wrappers + * + * `window.name` prvides a simple cross-origin tracking method of the same tab: + * + * ```js + * window.name = "8pdRoEaQCpsjtC8w07dOy7xwXjXrHDyxxmPWBUxQKrh7xfJ4SYFH8QClp6U9T+Ypa8IEa5AwFw3x" + * ``` + * + * Go to completely different web site and window.name stays the same. + * + * \see https://2019.www.torproject.org/projects/torbrowser/design/ provides a library build on + * top of `window.name`: https://www.thomasfrank.se/sessionvars.html. + * + * \see https://html.spec.whatwg.org/#history-traversal; this feature should not be ncessary + * for Firefox 86 or newer https://bugzilla.mozilla.org/show_bug.cgi?id=444222. + */ + +/* + * Create private namespace + */ +(function() { + var wrappers = [ + { + parent_object: "window", + parent_object_property: "name", + wrapped_objects: [], + helping_code: "window.name = '';", + nofreeze: true, + }, + ] + add_wrappers(wrappers); +})() diff --git a/website/content/wrappers/wrappingS-MCS.js b/website/content/wrappers/wrappingS-MCS.js new file mode 100644 index 0000000..2c04267 --- /dev/null +++ b/website/content/wrappers/wrappingS-MCS.js @@ -0,0 +1,160 @@ +/** \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 . +// +// 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 suffled array + * * (1) - return promise with shuffled array with additional 0-4 fake devices + * * (2) - return empty promise + * + * + * Shuffling approach is inspired by the algorithms created by Brave Software + * available at 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(){ + if(args[0] == 0 || args[0] == 1){ + return devices; + } + else if(args[0] == 2){ + return new Promise((resolve) => resolve([])); + } + } + /** + * \brief create and return MediaDeviceInfo object + * + * \param browserEnum enum specifying browser 0 - Chrome 1 - Firefox + */ + function fakeDevice(browserEnum){ + var kinds = ["videoinput", "audioinput", "audiooutput"]; + var deviceId = browserEnum == 1 ? randomString(43, browserEnum)+ "=" : ""; + var ret = Object.create(MediaDeviceInfo.prototype); + Object.defineProperties(ret, { + deviceId:{ + value: deviceId + }, + groupId:{ + value: deviceRandomString(browserEnum) + }, + kind:{ + value: kinds[Math.floor(prng()*3)] + }, + label:{ + value: "" + } + }); + ret.__proto__.toJSON = JSON.stringify; + return ret; + } + /** + * \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", + wrapped_name: "origEnumerateDevices", + }], + helping_code: farbleEnumerateDevices+shuffleArray+deviceRandomString+randomString+fakeDevice+` + if(args[0]==0){ + var devices = origEnumerateDevices.call(navigator.mediaDevices); + devices.then(function(result) { + shuffleArray(result); + }); + } + if(args[0]==1){ + var until = Math.floor(prng()*4); + var devices = origEnumerateDevices.call(navigator.mediaDevices); + devices.then(function(result) { + var browserEnum = 0; + if(result[0].groupId.length == 44) + browserEnum = 1; + for(var i = 0; i < until; i++){ + result.push(fakeDevice(browserEnum)); + } + shuffleArray(result); + }); + } + `, + 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); +})() diff --git a/website/content/wrappers/wrappingS-NP.js b/website/content/wrappers/wrappingS-NP.js new file mode 100644 index 0000000..29babf2 --- /dev/null +++ b/website/content/wrappers/wrappingS-NP.js @@ -0,0 +1,335 @@ +/** \file + * \brief Wrappers for NavigatorPlugins + * + * \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 . +// +// 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 + * * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins + * * 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 + * available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/modules/plugins/dom_plugin_array.cc + * + */ + +/* + * 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) + }, + 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) + }, + filename:{ + value: randomString(filenameLength, 0) + }, + description:{ + value: randomString(descLength, 0) + }, + 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 fakePluginArray(plugins){ + var ret = Object.create(PluginArray.prototype); + var count = 0; + for(var i = 0; i=0;j++){ + if((typeof plugins[i][j] != 'undefined') && (ret.namedItem(plugins[i][j].name)==null) && (plugins[i][j].type != "")){ + ret[counter] = plugins[i][j]; + 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){ + var chrome = ["Chrome ", "Chromium ", "Web ", "Browser ", "OpenSource ", "Online ", "JavaScript ", ""]; + var pdf = ["PDF ", "Portable Document Format ", "portable-document-format ", "document ", "doc ", "PDF and PS ", "com.adobe.pdf "]; + var viewer = ["Viewer", "Renderer", "Display", "Plugin", "plug-in", "plug in", "extension", ""]; + var name = plugin.name; + var description = plugin.description; + if(plugin.name.includes("PDF")){ + name = chrome[Math.floor(prng() * (chrome.length))]+pdf[Math.floor(prng() * (pdf.length))]+viewer[Math.floor(prng() * (viewer.length))]; + description = pdf[Math.floor(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), + }, + 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 + fakePluginArray + fakeMimeTypeArray; + var wrappers = [ + { + parent_object: "navigator", + parent_object_property: "plugins", + wrapped_objects: [], + helping_code: + methods + farbles + fakes +` + var plugins = navigator.plugins; + var buffer = []; + if(args[0]==0){ + for(var i = 0;i. +// + +/* + * Create private namespace + */ +(function() { + var helping_code = `var precision = args[0]; + var doNoise = args[1]; + var pastValues = {}; + ${rounding_function} + ${noise_function} + var func = rounding_function; + if (doNoise === true){ + func = function(value, precision) { + let params = [value, precision]; + if (params in pastValues) { + return pastValues[params]; + } + let result = noise_function(...params); + pastValues[params] = result; + return result; + } + } + `; + + var wrappers = [ + { + parent_object: "PerformanceEntry", + parent_object_property: "prototype", + wrapped_objects: [], + helping_code: helping_code, + post_wrapping_code: [ + { + code_type: "object_properties", + parent_object: "PerformanceEntry.prototype", + parent_object_property: "startTime", + wrapped_objects: [ + { + original_name: "Object.getOwnPropertyDescriptor(PerformanceEntry.prototype, 'startTime')['get']", + wrapped_name: "originalST", + }, + ], + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + let originalVal = originalST.call(this, ...arguments); + return func(originalVal, precision); + }`, + }, + ], + }, + { + code_type: "object_properties", + parent_object: "PerformanceEntry.prototype", + parent_object_property: "duration", + wrapped_objects: [ + { + original_name: "Object.getOwnPropertyDescriptor(PerformanceEntry.prototype, 'duration')['get']", + wrapped_name: "originalD", + }, + ], + wrapped_properties: [ + { + property_name: "get", + property_value: ` + function() { + let originalVal = originalD.call(this, ...arguments); + return func(this.startTime + originalVal, precision) - this.startTime; + }`, + }, + ], + }, + { + code_type: "object_properties", + parent_object: "PerformanceEntry.prototype", + parent_object_property: "toJSON", + wrapped_objects: [], + wrapped_properties: [ + { + property_name: "value", + property_value: ` + function() { + let res = { + entryType: this.entryType, + name: this.name, + startTime: this.startTime, + duration: this.duration, + toJSON: function() {return this}, + }; + return res.toJSON(); + }`, + }, + ], + }, + ], + }, + ] + add_wrappers(wrappers); +})(); diff --git a/website/content/wrappers/wrappingS-WEBA.js b/website/content/wrappers/wrappingS-WEBA.js new file mode 100644 index 0000000..f2f2e7d --- /dev/null +++ b/website/content/wrappers/wrappingS-WEBA.js @@ -0,0 +1,260 @@ +/** \file + * \brief Wrappers for Web Audio API + * + * \see https://webaudio.github.io/web-audio-api + * + * \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 . +// +// 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 AudioBuffer and AnalyserNode related calls + * * https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer + * * https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode + * \ingroup wrappers + * + * The goal is to prevent fingerprinting by modifying the values from functions which are reading/copying from AudioBuffer and AnalyserNode. + * So the audio content of wrapped objects is the same as intended. + * + * The modified content can be either a white noise based on domain key or a fake audio data that is modified according to + * domain key to be different than the original albeit very similar (i.e. the approach + * inspired by the algorithms created by Brave Software + * available at https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/execution_context/execution_context.cc.) + * + * \note Both approaches are detectable by a fingerprinter that checks if a predetermined audio + * is the same as the read one. Nevertheless, the aim of the wrappers is + * to limit the finerprintability. + * + */ + +/* + * Create private namespace + */ +(function() { + /** + * \brief makes number from substring of given string - should work as reinterpret_cast + * \param str String + * \param length Number specifying substring length + */ + function strToUint(str, length){ + var sub = str.substring(0,length); + var ret = ""; + for (var i = sub.length-1; i >= 0; i--) { + ret += ((sub[i].charCodeAt(0)).toString(2).padStart(8, "0")); + } + return "0b"+ret; + }; + /** + * \brief shifts number bits to pick new number + * \param v number to shift + */ + function lfsr_next32(v) { + return ((v >>> 1) | (((v << 30) ^ (v << 29)) & (~(~0 << 31) << 30))); + }; + /** + * \brief seeded pseudo random sequence using lfsr_next32 + * \param seed Number used as seed at first call + */ + function pseudoRandomSequence(seed){ + if (typeof this.v == 'undefined'){ + this.v = seed; + } + const maxUInt32n = 4294967295; + this.v = lfsr_next32(this.v); + return ((this.v>>>0) / maxUInt32n) / 10; + }; + /** + * \brief Modifies audio data + * + * \param arr typed array with data - Uint8Array or Float32Array + * + * Depending on level chosen this function modifies arr content: + * * (0) - multiplies values by fudge factor based on domain key + * * (1) - replace values by white noise based on domain key + */ + function audioFarble(arr){ + if(args[0] == 0){ + var fudge = BigInt(strToUint(domainHash,8))*1000n; + var maxUInt64 = 18446744073709551615n; + var fudge_factor = 0.99 + (Number(fudge / maxUInt64) / 100000); + for (var i = 0; i < arr.length; i++) { + arr[i] = arr[i]*fudge_factor; + } + } + else if(args[0] == 1){ + var seed = Number(strToUint(domainHash,4)); + for (var i = 0; i < arr.length; i++) { + arr[i] = pseudoRandomSequence(seed); + } + } + }; + /** @var String audioFarbleBody. + * + * Contains functions for modyfing audio data according to chosen level of protection - + * (0) - replace by white noise (range <0,0.1>) based on domain key + * (1) - multiply array by fudge factor based on domain key + */ + var audioFarbleBody = strToUint + lfsr_next32 + pseudoRandomSequence + audioFarble; + var wrappers = [ + { + parent_object: "AudioBuffer.prototype", + parent_object_property: "getChannelData", + wrapped_objects: [ + { + original_name: "AudioBuffer.prototype.getChannelData", + wrapped_name: "origGetChannelData", + } + ], + helping_code: audioFarbleBody, + original_function: "parent.AudioBuffer.prototype.getChannelData", + wrapping_function_args: "channel", + /** \fn fake AudioBuffer.prototype.getChannelData + * \brief Returns modified channel data. + * + * Calls original function, which returns array with result, then calls function + * audioFarble with returned array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + var floatArr = origGetChannelData.call(this, channel); + audioFarble(floatArr); + return floatArr; + `, + }, + { + parent_object: "AudioBuffer.prototype", + parent_object_property: "copyFromChannel", + wrapped_objects: [ + { + original_name: "AudioBuffer.prototype.copyFromChannel", + wrapped_name: "origCopyFromChannel", + } + ], + helping_code: audioFarbleBody, + original_function: "parent.AudioBuffer.prototype.copyFromChannel", + wrapping_function_args: "destination, channel, start", + /** \fn fake AudioBuffer.prototype.copyFromChannel + * \brief Modifies destination array after calling original function. + * + * Calls original function, which writes data to destination array, then calls function + * audioFarble with destination array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + origCopyFromChannel.call(this, destination, channel, start); + audioFarble(destination); + `, + }, + { + parent_object: "AnalyserNode.prototype", + parent_object_property: "getByteTimeDomainData", + wrapped_objects: [ + { + original_name: "AnalyserNode.prototype.getByteTimeDomainData", + wrapped_name: "origGetByteTimeDomainData", + } + ], + helping_code:audioFarbleBody, + wrapping_function_args: "destination", + /** \fn fake AnalyserNode.prototype.getByteTimeDomainData + * \brief Modifies destination array after calling original function. + * + * Calls original function, which writes data to destination array, then calls function + * audioFarble with destination array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + origGetByteTimeDomainData.call(this, destination); + audioFarble(destination); + `, + }, + { + parent_object: "AnalyserNode.prototype", + parent_object_property: "getFloatTimeDomainData", + wrapped_objects: [ + { + original_name: "AnalyserNode.prototype.getFloatTimeDomainData", + wrapped_name: "origGetFloatTimeDomainData", + } + ], + helping_code:audioFarbleBody, + wrapping_function_args: "destination", + /** \fn fake AnalyserNode.prototype.getFloatTimeDomainData + * \brief Modifies destination array after calling original function. + * + * Calls original function, which writes data to destination array, then calls function + * audioFarble with destination array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + origGetFloatTimeDomainData.call(this, destination); + audioFarble(destination); + `, + }, + { + parent_object: "AnalyserNode.prototype", + parent_object_property: "getByteFrequencyData", + wrapped_objects: [ + { + original_name: "AnalyserNode.prototype.getByteFrequencyData", + wrapped_name: "origGetByteFrequencyData", + } + ], + helping_code:audioFarbleBody, + wrapping_function_args: "destination", + /** \fn fake AnalyserNode.prototype.getByteFrequencyData + * \brief Modifies destination array after calling original function. + * + * Calls original function, which writes data to destination array, then calls function + * audioFarble with destination array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + origGetByteFrequencyData.call(this, destination); + audioFarble(destination); + `, + }, + { + parent_object: "AnalyserNode.prototype", + parent_object_property: "getFloatFrequencyData", + wrapped_objects: [ + { + original_name: "AnalyserNode.prototype.getFloatFrequencyData", + wrapped_name: "origGetFloatFrequencyData", + } + ], + helping_code:audioFarbleBody, + wrapping_function_args: "destination", + /** \fn fake AnalyserNode.prototype.getFloatFrequencyData + * \brief Modifies destination array after calling original function. + * + * Calls original function, which writes data to destination array, then calls function + * audioFarble with destination array as argument - which changes array values according to chosen level. + */ + wrapping_function_body: ` + origGetFloatFrequencyData.call(this, destination); + audioFarble(destination); + `, + } + ]; + add_wrappers(wrappers); +})(); diff --git a/website/extract_comments.py b/website/extract_comments.py new file mode 100644 index 0000000..6a25e69 --- /dev/null +++ b/website/extract_comments.py @@ -0,0 +1,78 @@ +import jinja2 +from comment_parser import comment_parser +import glob + +TEMPLATES_PATH = "md-templates/" +filenames = glob.glob("../common/wrapping-*.js") +output_dir = "content/wrappers/" + +titles = { + "ajax": "AJAX", + "battery-cr": "Battery level", + "be": "Beacon API", + "dm": "Device memory", + "ecma-array": "ECMAscript arrays", + "ecma-date": "ECMAscript date", + "ecma-shared": "ECMA shared buffers", + "geo": "Geolocation", + "h-c": "HTML Canvas", + "hrt": "HTML Performance", + "html-ls": "HTML Workers", + "html": "HTML window name", + "mcs": "Media devices", + "pt2": "PT2", + "weba": "WebAudio", + "webgl": "WebGL", + "nbs" : "Network Boundary Shield", +} + + +def render_template_into_file(env, templatename, filename, context=None): + template = env.get_template(templatename) + if not context: + context = {} + html = template.render(**context) + with open(filename, "wb") as fh: + fh.write(html.encode("utf-8")) + + +def comment_to_md(text): + newtext = "" + for line in text.split("\n"): + line = line.strip() + line = line.replace("* ", "", 1) + if line in ("\\file", "\\ingroup wrappers"): + continue + elif line in ("* "): + line = "" + if line.startswith("\\note"): + line = line.replace("\\note", "Note: ") + newtext += line + "\n" + return newtext + + +env = jinja2.Environment( + loader=jinja2.FileSystemLoader([TEMPLATES_PATH]), + extensions=["jinja2.ext.with_"], + trim_blocks=True, + lstrip_blocks=True, +) + + +for fn in filenames: + slug = fn.split("/")[-1].replace("wrappingS-", "").replace(".js", "").lower() + comments = comment_parser.extract_comments(fn, mime="text/x-javascript") + + description = [c.text() for c in comments if "ingroup" in c.text()] + context = {} + context["filename"] = fn + context["title"] = titles.get(slug, slug) + + if description: + print("+ " + slug) + context["description"] = comment_to_md(description[0]) + else: + # meter título sem link no site + print(" " + slug) + outfn = output_dir + slug + ".md" + render_template_into_file(env, "wrapper.md", outfn, context=context) diff --git a/website/favicon.ico b/website/favicon.ico new file mode 100644 index 0000000..5de565d Binary files /dev/null and b/website/favicon.ico differ diff --git a/website/favicon.png b/website/favicon.png new file mode 100644 index 0000000..49d1706 Binary files /dev/null and b/website/favicon.png differ diff --git a/website/md-templates/wrapper.md b/website/md-templates/wrapper.md new file mode 100644 index 0000000..0a8d344 --- /dev/null +++ b/website/md-templates/wrapper.md @@ -0,0 +1,4 @@ +Title: {{ title }} +Filename: {{ filename }} + +{{ description }} diff --git a/website/pelicanconf.py b/website/pelicanconf.py new file mode 100644 index 0000000..0749bba --- /dev/null +++ b/website/pelicanconf.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # + +AUTHOR = "The JShelter team" +SITENAME = "JShelter" +DESCRIPTION = "Your browser extension to keep you safe" +LONGDESCRIPTION = "An anti-malware Web browser extension to mitigate potential threats from JavaScript, including fingerprinting, tracking, and data collection!" +SITEURL = "https://jshelter.org" +RELATIVE_URLS = True + +DEFAULT_DATE = "fs" # use a default date to stop Pelican complaints on pages +PAGE_PATHS = ["pages", "wrappers"] +THEME = "./theme/" +DIRECT_TEMPLATES = ["index"] + +# Use filenames as the base for slugs (default is post title) +SLUGIFY_SOURCE = "basename" + +DEFAULT_METADATA = {"lang": "en"} +PATH_METADATA = r"(pages|posts|wrappers)/(?P..)/.*" + +# Use static page as index (home.md) and move blog index to blog/ +INDEX_SAVE_AS = "blog/index.html" +# Settings for clean URLs +ARTICLE_URL = "{slug}/" +ARTICLE_SAVE_AS = "{slug}/index.html" +PAGE_URL = "{slug}/" +PAGE_SAVE_AS = "{slug}/index.html" +PAGE_LANG_URL = "{lang}/{slug}/" +PAGE_LANG_SAVE_AS = "{lang}/{slug}.html" +DRAFT_URL = "private/{slug}/" +DRAFT_SAVE_AS = "private/{slug}/index.html" +DRAFT_LANG_URL = "private/{slug}-{lang}/" +DRAFT_LANG_SAVE_AS = "private/{slug}-{lang}/index.html" + +PATH = "content" + +TIMEZONE = "Europe/Paris" + +DEFAULT_LANG = "en" + +DEFAULT_PAGINATION = 15 + +# Uncomment following line if you want document-relative URLs when developing +# RELATIVE_URLS = True + +PLUGIN_PATHS = ["plugins"] +PLUGINS = ["i18n_subsites"] +I18N_UNTRANSLATED_PAGES = "remove" # needed to avoid index overwrites +I18N_SUBSITES = { + "pt": { + "DESCRIPTION": "A extensão para navegar em segurança", + "LONGDESCRIPTION": "Uma extensão anti-malware para o teu navegador web que vai pôr sob controlo ameaças de JavaScript, incluindo a recolha de impressões digitais, rastreamento e recolha de dados", + } +} + +# custom Jinja2 filter +# https://siongui.github.io/2017/01/08/pelican-get-single-page-or-article-by-slug-metadata-in-theme/ +def get_by_slug(objs, slug): + for obj in objs: + if obj.slug == slug: + return obj + + +JINJA_FILTERS = { + "get_by_slug": get_by_slug, +} diff --git a/website/plugins/i18n_subsites/README.rst b/website/plugins/i18n_subsites/README.rst new file mode 100644 index 0000000..340109b --- /dev/null +++ b/website/plugins/i18n_subsites/README.rst @@ -0,0 +1,165 @@ +======================= + I18N Sub-sites Plugin +======================= + +This plugin extends the translations functionality by creating +internationalized sub-sites for the default site. + +This plugin is designed for Pelican 3.4 and later. + +What it does +============ + +1. When the content of the main site is being generated, the settings + are saved and the generation stops when content is ready to be + written. While reading source files and generating content objects, + the output queue is modified in certain ways: + + - translations that will appear as native in a different (sub-)site + will be removed + - untranslated articles will be transformed to drafts if + ``I18N_UNTRANSLATED_ARTICLES`` is ``'hide'`` (default), removed if + ``'remove'`` or kept as they are if ``'keep'``. + - untranslated pages will be transformed into hidden pages if + ``I18N_UNTRANSLATED_PAGES`` is ``'hide'`` (default), removed if + ``'remove'`` or kept as they are if ``'keep'``.'' + - additional content manipulation similar to articles and pages can + be specified for custom generators in the ``I18N_GENERATOR_INFO`` + setting. + +2. For each language specified in the ``I18N_SUBSITES`` dictionary the + settings overrides are applied to the settings from the main site + and a new sub-site is generated in the same way as with the main + site until content is ready to be written. +3. When all (sub-)sites are waiting for content writing, all removed + contents, translations and static files are interlinked across the + (sub-)sites. +4. Finally, all the output is written. + +Setting it up +============= + +For each extra used language code, a language-specific settings overrides +dictionary must be given (but can be empty) in the ``I18N_SUBSITES`` dictionary + +.. code-block:: python + + PLUGINS = ['i18n_subsites', ...] + + # mapping: language_code -> settings_overrides_dict + I18N_SUBSITES = { + 'cz': { + 'SITENAME': 'Hezkej blog', + } + } + +You must also have the following in your pelican configuration + +.. code-block:: python + JINJA_ENVIRONMENT = { + 'extensions': ['jinja2.ext.i18n'], + } + + + +Default and special overrides +----------------------------- +The settings overrides may contain arbitrary settings, however, there +are some that are handled in a special way: + +``SITEURL`` + Any overrides to this setting should ensure that there is some level + of hierarchy between all (sub-)sites, because Pelican makes all URLs + relative to ``SITEURL`` and the plugin can only cross-link between + the sites using this hierarchy. For instance, with the main site + ``http://example.com`` a sub-site ``http://example.com/de`` will + work, but ``http://de.example.com`` will not. If not overridden, the + language code (the language identifier used in the ``lang`` + metadata) is appended to the main ``SITEURL`` for each sub-site. +``OUTPUT_PATH``, ``CACHE_PATH`` + If not overridden, the language code is appended as with ``SITEURL``. + Separate cache paths are required as parser results depend on the locale. +``STATIC_PATHS``, ``THEME_STATIC_PATHS`` + If not overridden, they are set to ``[]`` and all links to static + files are cross-linked to the main site. +``THEME``, ``THEME_STATIC_DIR`` + If overridden, the logic with ``THEME_STATIC_PATHS`` does not apply. +``DEFAULT_LANG`` + This should not be overridden as the plugin changes it to the + language code of each sub-site to change what is perceived as translations. + +Localizing templates +-------------------- + +Most importantly, this plugin can use localized templates for each +sub-site. There are two approaches to having the templates localized: + +- You can set a different ``THEME`` override for each language in + ``I18N_SUBSITES``, e.g. by making a copy of a theme ``my_theme`` to + ``my_theme_lang`` and then editing the templates in the new + localized theme. This approach means you don't have to deal with + gettext ``*.po`` files, but it is harder to maintain over time. +- You use only one theme and localize the templates using the + `jinja2.ext.i18n Jinja2 extension + `_. For a kickstart + read this `guide <./localizing_using_jinja2.rst>`_. + +Additional context variables +............................ + +It may be convenient to add language buttons to your theme in addition +to the translation links of articles and pages. These buttons could, +for example, point to the ``SITEURL`` of each (sub-)site. For this +reason the plugin adds these variables to the template context: + +``main_lang`` + The language of the main site — the original ``DEFAULT_LANG`` +``main_siteurl`` + The ``SITEURL`` of the main site — the original ``SITEURL`` +``lang_siteurls`` + An ordered dictionary, mapping all used languages to their + ``SITEURL``. The ``main_lang`` is the first key with ``main_siteurl`` + as the value. This dictionary is useful for implementing global + language buttons that show the language of the currently viewed + (sub-)site too. +``extra_siteurls`` + An ordered dictionary, subset of ``lang_siteurls``, the current + ``DEFAULT_LANG`` of the rendered (sub-)site is not included, so for + each (sub-)site ``set(extra_siteurls) == set(lang_siteurls) - + set([DEFAULT_LANG])``. This dictionary is useful for implementing + global language buttons that do not show the current language. +``relpath_to_site`` + A function that returns a relative path from the first (sub-)site to + the second (sub-)site where the (sub-)sites are identified by the + language codes given as two arguments. + +If you don't like the default ordering of the ordered dictionaries, +use a Jinja2 filter to alter the ordering. + +All the siteurls above are always absolute even in the case of +``RELATIVE_URLS == True`` (it would be to complicated to replicate the +Pelican internals for local siteurls), so you may rather use something +like ``{{ SITEURL }}/{{ relpath_to_site(DEFAULT_LANG, main_lang }}`` +to link to the main site. + +This short `howto <./implementing_language_buttons.rst>`_ shows two +example implementations of language buttons. + +Usage notes +=========== +- It is **mandatory** to specify ``lang`` metadata for each article + and page as ``DEFAULT_LANG`` is later changed for each sub-site, so + content without ``lang`` metadata would be rendered in every + (sub-)site. +- As with the original translations functionality, ``slug`` metadata + is used to group translations. It is therefore often convenient to + compensate for this by overriding the content URL (which defaults to + slug) using the ``url`` and ``save_as`` metadata. You could also + give articles e.g. ``name`` metadata and use it in ``ARTICLE_URL = + '{name}.html'``. + +Development +=========== + +- A demo and a test site is in the ``gh-pages`` branch and can be seen + at http://smartass101.github.io/pelican-plugins/ diff --git a/website/plugins/i18n_subsites/__init__.py b/website/plugins/i18n_subsites/__init__.py new file mode 100644 index 0000000..7dfbde0 --- /dev/null +++ b/website/plugins/i18n_subsites/__init__.py @@ -0,0 +1 @@ +from .i18n_subsites import * diff --git a/website/plugins/i18n_subsites/i18n_subsites.py b/website/plugins/i18n_subsites/i18n_subsites.py new file mode 100644 index 0000000..dc27799 --- /dev/null +++ b/website/plugins/i18n_subsites/i18n_subsites.py @@ -0,0 +1,462 @@ +"""i18n_subsites plugin creates i18n-ized subsites of the default site + +This plugin is designed for Pelican 3.4 and later +""" + + +import os +import six +import logging +import posixpath + +from copy import copy +from itertools import chain +from operator import attrgetter +try: + from collections.abc import OrderedDict +except ImportError: + from collections import OrderedDict +from contextlib import contextmanager +from six.moves.urllib.parse import urlparse + +import gettext +import locale + +from pelican import signals +from pelican.generators import ArticlesGenerator, PagesGenerator +from pelican.settings import configure_settings +try: + from pelican.contents import Draft +except ImportError: + from pelican.contents import Article as Draft + + +# Global vars +_MAIN_SETTINGS = None # settings dict of the main Pelican instance +_MAIN_LANG = None # lang of the main Pelican instance +_MAIN_SITEURL = None # siteurl of the main Pelican instance +_MAIN_STATIC_FILES = None # list of Static instances the main Pelican instance +_SUBSITE_QUEUE = {} # map: lang -> settings overrides +_SITE_DB = OrderedDict() # OrderedDict: lang -> siteurl +_SITES_RELPATH_DB = {} # map: (lang, base_lang) -> relpath +# map: generator -> list of removed contents that need interlinking +_GENERATOR_DB = {} +_NATIVE_CONTENT_URL_DB = {} # map: source_path -> content in its native lang +_LOGGER = logging.getLogger(__name__) + + +@contextmanager +def temporary_locale(temp_locale=None): + '''Enable code to run in a context with a temporary locale + + Resets the locale back when exiting context. + Can set a temporary locale if provided + ''' + orig_locale = locale.setlocale(locale.LC_ALL) + if temp_locale is not None: + locale.setlocale(locale.LC_ALL, temp_locale) + yield + locale.setlocale(locale.LC_ALL, orig_locale) + + +def initialize_dbs(settings): + '''Initialize internal DBs using the Pelican settings dict + + This clears the DBs for e.g. autoreload mode to work + ''' + global _MAIN_SETTINGS, _MAIN_SITEURL, _MAIN_LANG, _SUBSITE_QUEUE + _MAIN_SETTINGS = settings + _MAIN_LANG = settings['DEFAULT_LANG'] + _MAIN_SITEURL = settings['SITEURL'] + _SUBSITE_QUEUE = settings.get('I18N_SUBSITES', {}).copy() + prepare_site_db_and_overrides() + # clear databases in case of autoreload mode + _SITES_RELPATH_DB.clear() + _NATIVE_CONTENT_URL_DB.clear() + _GENERATOR_DB.clear() + + +def prepare_site_db_and_overrides(): + '''Prepare overrides and create _SITE_DB + + _SITE_DB.keys() need to be ready for filter_translations + ''' + _SITE_DB.clear() + _SITE_DB[_MAIN_LANG] = _MAIN_SITEURL + # make sure it works for both root-relative and absolute + main_siteurl = '/' if _MAIN_SITEURL == '' else _MAIN_SITEURL + for lang, overrides in _SUBSITE_QUEUE.items(): + if 'SITEURL' not in overrides: + overrides['SITEURL'] = posixpath.join(main_siteurl, lang) + _SITE_DB[lang] = overrides['SITEURL'] + # default subsite hierarchy + if 'OUTPUT_PATH' not in overrides: + overrides['OUTPUT_PATH'] = os.path.join( + _MAIN_SETTINGS['OUTPUT_PATH'], lang) + if 'CACHE_PATH' not in overrides: + overrides['CACHE_PATH'] = os.path.join( + _MAIN_SETTINGS['CACHE_PATH'], lang) + if 'STATIC_PATHS' not in overrides: + overrides['STATIC_PATHS'] = [] + if ('THEME' not in overrides and 'THEME_STATIC_DIR' not in overrides and + 'THEME_STATIC_PATHS' not in overrides): + relpath = relpath_to_site(lang, _MAIN_LANG) + overrides['THEME_STATIC_DIR'] = posixpath.join( + relpath, _MAIN_SETTINGS['THEME_STATIC_DIR']) + overrides['THEME_STATIC_PATHS'] = [] + # to change what is perceived as translations + overrides['DEFAULT_LANG'] = lang + + +def subscribe_filter_to_signals(settings): + '''Subscribe content filter to requested signals''' + for sig in settings.get('I18N_FILTER_SIGNALS', []): + sig.connect(filter_contents_translations) + + +def initialize_plugin(pelican_obj): + '''Initialize plugin variables and Pelican settings''' + if _MAIN_SETTINGS is None: + initialize_dbs(pelican_obj.settings) + subscribe_filter_to_signals(pelican_obj.settings) + + +def get_site_path(url): + '''Get the path component of an url, excludes siteurl + + also normalizes '' to '/' for relpath to work, + otherwise it could be interpreted as a relative filesystem path + ''' + path = urlparse(url).path + if path == '': + path = '/' + return path + + +def relpath_to_site(lang, target_lang): + '''Get relative path from siteurl of lang to siteurl of base_lang + + the output is cached in _SITES_RELPATH_DB + ''' + path = _SITES_RELPATH_DB.get((lang, target_lang), None) + if path is None: + siteurl = _SITE_DB.get(lang, _MAIN_SITEURL) + target_siteurl = _SITE_DB.get(target_lang, _MAIN_SITEURL) + path = posixpath.relpath(get_site_path(target_siteurl), + get_site_path(siteurl)) + _SITES_RELPATH_DB[(lang, target_lang)] = path + return path + + +def save_generator(generator): + '''Save the generator for later use + + initialize the removed content list + ''' + _GENERATOR_DB[generator] = [] + + +def article2draft(article): + '''Transform an Article to Draft''' + draft = Draft(article._content, article.metadata, article.settings, + article.source_path, article._context) + draft.status = 'draft' + return draft + + +def page2hidden_page(page): + '''Transform a Page to a hidden Page''' + page.status = 'hidden' + return page + + +class GeneratorInspector(object): + '''Inspector of generator instances''' + + generators_info = { + ArticlesGenerator: { + 'translations_lists': ['translations', 'drafts_translations'], + 'contents_lists': [('articles', 'drafts')], + 'hiding_func': article2draft, + 'policy': 'I18N_UNTRANSLATED_ARTICLES', + }, + PagesGenerator: { + 'translations_lists': ['translations', 'hidden_translations'], + 'contents_lists': [('pages', 'hidden_pages')], + 'hiding_func': page2hidden_page, + 'policy': 'I18N_UNTRANSLATED_PAGES', + }, + } + + def __init__(self, generator): + '''Identify the best known class of the generator instance + + The class ''' + self.generator = generator + self.generators_info.update(generator.settings.get( + 'I18N_GENERATORS_INFO', {})) + for cls in generator.__class__.__mro__: + if cls in self.generators_info: + self.info = self.generators_info[cls] + break + else: + self.info = {} + + def translations_lists(self): + '''Iterator over lists of content translations''' + return (getattr(self.generator, name) for name in + self.info.get('translations_lists', [])) + + def contents_list_pairs(self): + '''Iterator over pairs of normal and hidden contents''' + return (tuple(getattr(self.generator, name) for name in names) + for names in self.info.get('contents_lists', [])) + + def hiding_function(self): + '''Function for transforming content to a hidden version''' + hiding_func = self.info.get('hiding_func', lambda x: x) + return hiding_func + + def untranslated_policy(self, default): + '''Get the policy for untranslated content''' + return self.generator.settings.get(self.info.get('policy', None), + default) + + def all_contents(self): + '''Iterator over all contents''' + translations_iterator = chain(*self.translations_lists()) + return chain(translations_iterator, + *(pair[i] for pair in self.contents_list_pairs() + for i in (0, 1))) + + +def filter_contents_translations(generator): + '''Filter the content and translations lists of a generator + + Filters out + 1) translations which will be generated in a different site + 2) content that is not in the language of the currently + generated site but in that of a different site, content in a + language which has no site is generated always. The filtering + method bay be modified by the respective untranslated policy + ''' + inspector = GeneratorInspector(generator) + current_lang = generator.settings['DEFAULT_LANG'] + langs_with_sites = _SITE_DB.keys() + removed_contents = _GENERATOR_DB[generator] + + for translations in inspector.translations_lists(): + for translation in translations[:]: # copy to be able to remove + if translation.lang in langs_with_sites: + translations.remove(translation) + removed_contents.append(translation) + + hiding_func = inspector.hiding_function() + untrans_policy = inspector.untranslated_policy(default='hide') + for (contents, other_contents) in inspector.contents_list_pairs(): + for content in other_contents: # save any hidden native content first + if content.lang == current_lang: # in native lang + # save the native URL attr formatted in the current locale + _NATIVE_CONTENT_URL_DB[content.source_path] = content.url + for content in contents[:]: # copy for removing in loop + if content.lang == current_lang: # in native lang + # save the native URL attr formatted in the current locale + _NATIVE_CONTENT_URL_DB[content.source_path] = content.url + elif content.lang in langs_with_sites and untrans_policy != 'keep': + contents.remove(content) + if untrans_policy == 'hide': + other_contents.append(hiding_func(content)) + elif untrans_policy == 'remove': + removed_contents.append(content) + + +def install_templates_translations(generator): + '''Install gettext translations in the jinja2.Environment + + Only if the 'jinja2.ext.i18n' jinja2 extension is enabled + the translations for the current DEFAULT_LANG are installed. + ''' + if 'JINJA_ENVIRONMENT' in generator.settings: # pelican 3.7+ + jinja_extensions = generator.settings['JINJA_ENVIRONMENT'].get( + 'extensions', []) + else: + jinja_extensions = generator.settings['JINJA_EXTENSIONS'] + + if 'jinja2.ext.i18n' in jinja_extensions: + domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages') + localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR') + if localedir is None: + localedir = os.path.join(generator.theme, 'translations') + current_lang = generator.settings['DEFAULT_LANG'] + if current_lang == generator.settings.get('I18N_TEMPLATES_LANG', + _MAIN_LANG): + translations = gettext.NullTranslations() + else: + langs = [current_lang] + try: + translations = gettext.translation(domain, localedir, langs) + except (IOError, OSError): + _LOGGER.error(( + "Cannot find translations for language '{}' in '{}' with " + "domain '{}'. Installing NullTranslations.").format( + langs[0], localedir, domain)) + translations = gettext.NullTranslations() + newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True) + generator.env.install_gettext_translations(translations, newstyle) + + +def add_variables_to_context(generator): + '''Adds useful iterable variables to template context''' + context = generator.context # minimize attr lookup + context['relpath_to_site'] = relpath_to_site + context['main_siteurl'] = _MAIN_SITEURL + context['main_lang'] = _MAIN_LANG + context['lang_siteurls'] = _SITE_DB + current_lang = generator.settings['DEFAULT_LANG'] + extra_siteurls = _SITE_DB.copy() + extra_siteurls.pop(current_lang) + context['extra_siteurls'] = extra_siteurls + + +def interlink_translations(content): + '''Link content to translations in their main language + + so the URL (including localized month names) of the different subsites + will be honored + ''' + lang = content.lang + # sort translations by lang + content.translations.sort(key=attrgetter('lang')) + for translation in content.translations: + relpath = relpath_to_site(lang, translation.lang) + url = _NATIVE_CONTENT_URL_DB[translation.source_path] + translation.override_url = posixpath.join(relpath, url) + + +def interlink_translated_content(generator): + '''Make translations link to the native locations + + for generators that may contain translated content + ''' + inspector = GeneratorInspector(generator) + for content in inspector.all_contents(): + interlink_translations(content) + + +def interlink_removed_content(generator): + '''For all contents removed from generation queue update interlinks + + link to the native location + ''' + current_lang = generator.settings['DEFAULT_LANG'] + for content in _GENERATOR_DB[generator]: + url = _NATIVE_CONTENT_URL_DB[content.source_path] + relpath = relpath_to_site(current_lang, content.lang) + content.override_url = posixpath.join(relpath, url) + + +def interlink_static_files(generator): + '''Add links to static files in the main site if necessary''' + if generator.settings['STATIC_PATHS'] != []: + return # customized STATIC_PATHS + try: # minimize attr lookup + static_content = generator.context['static_content'] + except KeyError: + static_content = generator.context['filenames'] + relpath = relpath_to_site(generator.settings['DEFAULT_LANG'], _MAIN_LANG) + for staticfile in _MAIN_STATIC_FILES: + if staticfile.get_relative_source_path() not in static_content: + staticfile = copy(staticfile) # prevent override in main site + staticfile.override_url = posixpath.join(relpath, staticfile.url) + try: + generator.add_source_path(staticfile, static=True) + except TypeError: + generator.add_source_path(staticfile) + + +def save_main_static_files(static_generator): + '''Save the static files generated for the main site''' + global _MAIN_STATIC_FILES + # test just for current lang as settings change in autoreload mode + if static_generator.settings['DEFAULT_LANG'] == _MAIN_LANG: + _MAIN_STATIC_FILES = static_generator.staticfiles + + +def update_generators(): + '''Update the context of all generators + + Ads useful variables and translations into the template context + and interlink translations + ''' + for generator in _GENERATOR_DB.keys(): + install_templates_translations(generator) + add_variables_to_context(generator) + interlink_static_files(generator) + interlink_removed_content(generator) + interlink_translated_content(generator) + + +def get_pelican_cls(settings): + '''Get the Pelican class requested in settings''' + cls = settings['PELICAN_CLASS'] + if isinstance(cls, six.string_types): + module, cls_name = cls.rsplit('.', 1) + module = __import__(module) + cls = getattr(module, cls_name) + return cls + + +def create_next_subsite(pelican_obj): + '''Create the next subsite using the lang-specific config + + If there are no more subsites in the generation queue, update all + the generators (interlink translations and removed content, add + variables and translations to template context). Otherwise get the + language and overrides for next the subsite in the queue and apply + overrides. Then generate the subsite using a PELICAN_CLASS + instance and its run method. Finally, restore the previous locale. + ''' + global _MAIN_SETTINGS + if len(_SUBSITE_QUEUE) == 0: + _LOGGER.debug( + 'i18n: Updating cross-site links and context of all generators.') + update_generators() + _MAIN_SETTINGS = None # to initialize next time + else: + with temporary_locale(): + settings = _MAIN_SETTINGS.copy() + lang, overrides = _SUBSITE_QUEUE.popitem() + settings.update(overrides) + settings = configure_settings(settings) # to set LOCALE, etc. + cls = get_pelican_cls(settings) + + new_pelican_obj = cls(settings) + _LOGGER.debug(("Generating i18n subsite for language '{}' " + "using class {}").format(lang, cls)) + new_pelican_obj.run() + + +# map: signal name -> function name +_SIGNAL_HANDLERS_DB = { + 'get_generators': initialize_plugin, + 'article_generator_pretaxonomy': filter_contents_translations, + 'page_generator_finalized': filter_contents_translations, + 'get_writer': create_next_subsite, + 'static_generator_finalized': save_main_static_files, + 'generator_init': save_generator, +} + + +def register(): + '''Register the plugin only if required signals are available''' + for sig_name in _SIGNAL_HANDLERS_DB.keys(): + if not hasattr(signals, sig_name): + _LOGGER.error(( + 'The i18n_subsites plugin requires the {} ' + 'signal available for sure in Pelican 3.4.0 and later, ' + 'plugin will not be used.').format(sig_name)) + return + + for sig_name, handler in _SIGNAL_HANDLERS_DB.items(): + sig = getattr(signals, sig_name) + sig.connect(handler) diff --git a/website/plugins/i18n_subsites/implementing_language_buttons.rst b/website/plugins/i18n_subsites/implementing_language_buttons.rst new file mode 100644 index 0000000..55b7bf3 --- /dev/null +++ b/website/plugins/i18n_subsites/implementing_language_buttons.rst @@ -0,0 +1,128 @@ +----------------------------- +Implementing language buttons +----------------------------- + +Each article with translations has translations links, but that's the +only way to switch between language subsites. + +For this reason it is convenient to add language buttons to the top +menu bar to make it simple to switch between the language subsites on +all pages. + +Example designs +--------------- + +Language buttons showing other available languages +.................................................. + +The ``extra_siteurls`` dictionary is a mapping of all other (not the +``DEFAULT_LANG`` of the current (sub-)site) languages to the +``SITEURL`` of the respective (sub-)sites + +.. code-block:: jinja + + +