Loading browser/components/DesktopActorRegistry.sys.mjs +19 −0 Original line number Diff line number Diff line Loading @@ -382,6 +382,25 @@ let JSWINDOWACTORS = { safeForUntrustedWebProcess: true, }, CryptoSafety: { parent: { esModuleURI: "moz-src:///browser/components/cryptosafety/CryptoSafetyParent.sys.mjs", }, child: { esModuleURI: "moz-src:///browser/components/cryptosafety/CryptoSafetyChild.sys.mjs", group: "browsers", events: { copy: { mozSystemGroup: true }, cut: { mozSystemGroup: true }, }, }, allFrames: true, }, /* Note: this uses the same JSMs as ClickHandler, but because it * relies on "normal" click events anywhere on the page (not just * links) and is expensive, and only does something for the Loading browser/components/cryptosafety/Bech32Decode.sys.mjs 0 → 100644 +99 −0 Original line number Diff line number Diff line // Adapted from the reference implementation of Bech32 // https://github.com/sipa/bech32 // Copyright (c) 2017 Pieter Wuille // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * JS module implementation of Bech32 decoding adapted from the reference * implementation https://github.com/sipa/bech32. */ var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; function polymod(values) { var chk = 1; for (var p = 0; p < values.length; ++p) { var top = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ values[p]; for (var i = 0; i < 5; ++i) { if ((top >> i) & 1) { chk ^= GENERATOR[i]; } } } return chk; } function hrpExpand(hrp) { var ret = []; var p; for (p = 0; p < hrp.length; ++p) { ret.push(hrp.charCodeAt(p) >> 5); } ret.push(0); for (p = 0; p < hrp.length; ++p) { ret.push(hrp.charCodeAt(p) & 31); } return ret; } function verifyChecksum(hrp, data) { return polymod(hrpExpand(hrp).concat(data)) === 1; } export function Bech32Decode(bechString) { var p; var has_lower = false; var has_upper = false; for (p = 0; p < bechString.length; ++p) { if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { return null; } if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { has_lower = true; } if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { has_upper = true; } } if (has_lower && has_upper) { return null; } bechString = bechString.toLowerCase(); var pos = bechString.lastIndexOf("1"); if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { return null; } var hrp = bechString.substring(0, pos); var data = []; for (p = pos + 1; p < bechString.length; ++p) { var d = CHARSET.indexOf(bechString.charAt(p)); if (d === -1) { return null; } data.push(d); } if (!verifyChecksum(hrp, data)) { return null; } return { hrp, data: data.slice(0, data.length - 6) }; } browser/components/cryptosafety/CryptoSafetyChild.sys.mjs 0 → 100644 +55 −0 Original line number Diff line number Diff line /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* Copyright (c) 2020, The Tor Project, Inc. * * 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { setTimeout: "resource://gre/modules/Timer.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( lazy, "isCryptoSafetyEnabled", "security.cryptoSafety", true // Defaults to true. ); /** * Child part of the crypto safety actor. * * It just signals the parent whenever the user copies some text in a non-secure * HTTP page. */ export class CryptoSafetyChild extends JSWindowActorChild { handleEvent(event) { if ( !lazy.isCryptoSafetyEnabled || // Ignore non-HTTP addresses. // We do this before reading the host property since this is not available // for about: pages. !this.document.documentURIObject.schemeIs("http") || // Ignore onion addresses. this.document.documentURIObject.host.endsWith(".onion") || (event.type !== "copy" && event.type !== "cut") ) { return; } // We send a message to the parent to inspect the clipboard content. // NOTE: We wait until next cycle to allow the event to propagate and fill // the clipboard before being read. // NOTE: Using navigator.clipboard.readText fails with Wayland. See // tor-browser#42702. lazy.setTimeout(() => { this.sendAsyncMessage("CryptoSafety:CopiedText", { host: this.document.documentURIObject.host, }); }); } } browser/components/cryptosafety/CryptoSafetyParent.sys.mjs 0 → 100644 +136 −0 Original line number Diff line number Diff line /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* Copyright (c) 2020, The Tor Project, Inc. * * 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorDomainIsolator: "moz-src:///toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs", Bech32Decode: "moz-src:///browser/components/cryptosafety/Bech32Decode.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "CryptoStrings", function () { return new Localization(["toolkit/global/tor-browser.ftl"]); }); XPCOMUtils.defineLazyPreferenceGetter( lazy, "isCryptoSafetyEnabled", "security.cryptoSafety", true // Defaults to true. ); function looksLikeCryptoAddress(s) { // P2PKH and P2SH addresses // https://stackoverflow.com/a/24205650 const bitcoinAddr = /^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$/; if (bitcoinAddr.test(s)) { return true; } // Bech32 addresses if (lazy.Bech32Decode(s) !== null) { return true; } // regular addresses const etherAddr = /^0x[a-fA-F0-9]{40}$/; if (etherAddr.test(s)) { return true; } // t-addresses // https://www.reddit.com/r/zec/comments/8mxj6x/simple_regex_to_validate_a_zcash_tz_address/dzr62p5/ const zcashAddr = /^t1[a-zA-Z0-9]{33}$/; if (zcashAddr.test(s)) { return true; } // Standard, Integrated, and 256-bit Integrated addresses // https://monero.stackexchange.com/a/10627 const moneroAddr = /^4(?:[0-9AB]|[1-9A-HJ-NP-Za-km-z]{12}(?:[1-9A-HJ-NP-Za-km-z]{30})?)[1-9A-HJ-NP-Za-km-z]{93}$/; if (moneroAddr.test(s)) { return true; } return false; } /** * This actor analyzes the copied text, and warns the user whenever that text * contains a string that resembles a crypto currency address. */ export class CryptoSafetyParent extends JSWindowActorParent { async receiveMessage(aMessage) { if ( !lazy.isCryptoSafetyEnabled || aMessage.name !== "CryptoSafety:CopiedText" ) { return; } // Read the global clipboard. We assume the contents come from the HTTP // page specified in `aMessage.data.host`. const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); trans.addDataFlavor("text/plain"); Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); let data = {}; trans.getTransferData("text/plain", data); data = data?.value.QueryInterface(Ci.nsISupportsString).data; let address = data?.replace(/\s+/g, ""); if (!address || !looksLikeCryptoAddress(address)) { return; } if (address.length > 32) { address = `${address.substring(0, 32)}…`; } const [titleText, bodyText, reloadText, dismissText] = await lazy.CryptoStrings.formatValues([ { id: "crypto-safety-prompt-title" }, { id: "crypto-safety-prompt-body", args: { address, host: aMessage.data.host }, }, { id: "crypto-safety-prompt-reload-button" }, { id: "crypto-safety-prompt-dismiss-button" }, ]); const buttonPressed = Services.prompt.confirmEx( this.browsingContext.topChromeWindow, titleText, bodyText, Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1, reloadText, dismissText, null, null, {} ); if (buttonPressed === 0) { const { browsingContext } = this.manager; const browser = browsingContext.embedderElement; if (browser) { lazy.TorDomainIsolator.newCircuitForBrowser( browser.documentGlobal.gBrowser.selectedBrowser ); } } } } browser/components/cryptosafety/moz.build 0 → 100644 +11 −0 Original line number Diff line number Diff line # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. MOZ_SRC_FILES += [ "Bech32Decode.sys.mjs", "CryptoSafetyChild.sys.mjs", "CryptoSafetyParent.sys.mjs", ] Loading
browser/components/DesktopActorRegistry.sys.mjs +19 −0 Original line number Diff line number Diff line Loading @@ -382,6 +382,25 @@ let JSWINDOWACTORS = { safeForUntrustedWebProcess: true, }, CryptoSafety: { parent: { esModuleURI: "moz-src:///browser/components/cryptosafety/CryptoSafetyParent.sys.mjs", }, child: { esModuleURI: "moz-src:///browser/components/cryptosafety/CryptoSafetyChild.sys.mjs", group: "browsers", events: { copy: { mozSystemGroup: true }, cut: { mozSystemGroup: true }, }, }, allFrames: true, }, /* Note: this uses the same JSMs as ClickHandler, but because it * relies on "normal" click events anywhere on the page (not just * links) and is expensive, and only does something for the Loading
browser/components/cryptosafety/Bech32Decode.sys.mjs 0 → 100644 +99 −0 Original line number Diff line number Diff line // Adapted from the reference implementation of Bech32 // https://github.com/sipa/bech32 // Copyright (c) 2017 Pieter Wuille // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * JS module implementation of Bech32 decoding adapted from the reference * implementation https://github.com/sipa/bech32. */ var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; function polymod(values) { var chk = 1; for (var p = 0; p < values.length; ++p) { var top = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ values[p]; for (var i = 0; i < 5; ++i) { if ((top >> i) & 1) { chk ^= GENERATOR[i]; } } } return chk; } function hrpExpand(hrp) { var ret = []; var p; for (p = 0; p < hrp.length; ++p) { ret.push(hrp.charCodeAt(p) >> 5); } ret.push(0); for (p = 0; p < hrp.length; ++p) { ret.push(hrp.charCodeAt(p) & 31); } return ret; } function verifyChecksum(hrp, data) { return polymod(hrpExpand(hrp).concat(data)) === 1; } export function Bech32Decode(bechString) { var p; var has_lower = false; var has_upper = false; for (p = 0; p < bechString.length; ++p) { if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { return null; } if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { has_lower = true; } if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { has_upper = true; } } if (has_lower && has_upper) { return null; } bechString = bechString.toLowerCase(); var pos = bechString.lastIndexOf("1"); if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { return null; } var hrp = bechString.substring(0, pos); var data = []; for (p = pos + 1; p < bechString.length; ++p) { var d = CHARSET.indexOf(bechString.charAt(p)); if (d === -1) { return null; } data.push(d); } if (!verifyChecksum(hrp, data)) { return null; } return { hrp, data: data.slice(0, data.length - 6) }; }
browser/components/cryptosafety/CryptoSafetyChild.sys.mjs 0 → 100644 +55 −0 Original line number Diff line number Diff line /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* Copyright (c) 2020, The Tor Project, Inc. * * 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { setTimeout: "resource://gre/modules/Timer.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( lazy, "isCryptoSafetyEnabled", "security.cryptoSafety", true // Defaults to true. ); /** * Child part of the crypto safety actor. * * It just signals the parent whenever the user copies some text in a non-secure * HTTP page. */ export class CryptoSafetyChild extends JSWindowActorChild { handleEvent(event) { if ( !lazy.isCryptoSafetyEnabled || // Ignore non-HTTP addresses. // We do this before reading the host property since this is not available // for about: pages. !this.document.documentURIObject.schemeIs("http") || // Ignore onion addresses. this.document.documentURIObject.host.endsWith(".onion") || (event.type !== "copy" && event.type !== "cut") ) { return; } // We send a message to the parent to inspect the clipboard content. // NOTE: We wait until next cycle to allow the event to propagate and fill // the clipboard before being read. // NOTE: Using navigator.clipboard.readText fails with Wayland. See // tor-browser#42702. lazy.setTimeout(() => { this.sendAsyncMessage("CryptoSafety:CopiedText", { host: this.document.documentURIObject.host, }); }); } }
browser/components/cryptosafety/CryptoSafetyParent.sys.mjs 0 → 100644 +136 −0 Original line number Diff line number Diff line /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* Copyright (c) 2020, The Tor Project, Inc. * * 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorDomainIsolator: "moz-src:///toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs", Bech32Decode: "moz-src:///browser/components/cryptosafety/Bech32Decode.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "CryptoStrings", function () { return new Localization(["toolkit/global/tor-browser.ftl"]); }); XPCOMUtils.defineLazyPreferenceGetter( lazy, "isCryptoSafetyEnabled", "security.cryptoSafety", true // Defaults to true. ); function looksLikeCryptoAddress(s) { // P2PKH and P2SH addresses // https://stackoverflow.com/a/24205650 const bitcoinAddr = /^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$/; if (bitcoinAddr.test(s)) { return true; } // Bech32 addresses if (lazy.Bech32Decode(s) !== null) { return true; } // regular addresses const etherAddr = /^0x[a-fA-F0-9]{40}$/; if (etherAddr.test(s)) { return true; } // t-addresses // https://www.reddit.com/r/zec/comments/8mxj6x/simple_regex_to_validate_a_zcash_tz_address/dzr62p5/ const zcashAddr = /^t1[a-zA-Z0-9]{33}$/; if (zcashAddr.test(s)) { return true; } // Standard, Integrated, and 256-bit Integrated addresses // https://monero.stackexchange.com/a/10627 const moneroAddr = /^4(?:[0-9AB]|[1-9A-HJ-NP-Za-km-z]{12}(?:[1-9A-HJ-NP-Za-km-z]{30})?)[1-9A-HJ-NP-Za-km-z]{93}$/; if (moneroAddr.test(s)) { return true; } return false; } /** * This actor analyzes the copied text, and warns the user whenever that text * contains a string that resembles a crypto currency address. */ export class CryptoSafetyParent extends JSWindowActorParent { async receiveMessage(aMessage) { if ( !lazy.isCryptoSafetyEnabled || aMessage.name !== "CryptoSafety:CopiedText" ) { return; } // Read the global clipboard. We assume the contents come from the HTTP // page specified in `aMessage.data.host`. const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); trans.addDataFlavor("text/plain"); Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); let data = {}; trans.getTransferData("text/plain", data); data = data?.value.QueryInterface(Ci.nsISupportsString).data; let address = data?.replace(/\s+/g, ""); if (!address || !looksLikeCryptoAddress(address)) { return; } if (address.length > 32) { address = `${address.substring(0, 32)}…`; } const [titleText, bodyText, reloadText, dismissText] = await lazy.CryptoStrings.formatValues([ { id: "crypto-safety-prompt-title" }, { id: "crypto-safety-prompt-body", args: { address, host: aMessage.data.host }, }, { id: "crypto-safety-prompt-reload-button" }, { id: "crypto-safety-prompt-dismiss-button" }, ]); const buttonPressed = Services.prompt.confirmEx( this.browsingContext.topChromeWindow, titleText, bodyText, Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1, reloadText, dismissText, null, null, {} ); if (buttonPressed === 0) { const { browsingContext } = this.manager; const browser = browsingContext.embedderElement; if (browser) { lazy.TorDomainIsolator.newCircuitForBrowser( browser.documentGlobal.gBrowser.selectedBrowser ); } } } }
browser/components/cryptosafety/moz.build 0 → 100644 +11 −0 Original line number Diff line number Diff line # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. MOZ_SRC_FILES += [ "Bech32Decode.sys.mjs", "CryptoSafetyChild.sys.mjs", "CryptoSafetyParent.sys.mjs", ]