Loading browser/actors/CryptoSafetyChild.sys.mjs +10 −45 Original line number Diff line number Diff line Loading @@ -5,12 +5,14 @@ * 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 { Bech32Decode } from "resource://gre/modules/Bech32Decode.sys.mjs"; 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", Loading @@ -18,43 +20,6 @@ XPCOMUtils.defineLazyPreferenceGetter( 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 (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; } export class CryptoSafetyChild extends JSWindowActorChild { handleEvent(event) { if ( Loading @@ -70,13 +35,13 @@ export class CryptoSafetyChild extends JSWindowActorChild { return; } this.contentWindow.navigator.clipboard.readText().then(clipText => { const selection = clipText.replace(/\s+/g, ""); if (!looksLikeCryptoAddress(selection)) { 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", { selection, host: this.document.documentURIObject.host, }); }); Loading browser/actors/CryptoSafetyParent.sys.mjs +56 −1 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs", Bech32Decode: "resource://gre/modules/Bech32Decode.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "CryptoStrings", function () { Loading @@ -24,6 +25,43 @@ XPCOMUtils.defineLazyPreferenceGetter( 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; } export class CryptoSafetyParent extends JSWindowActorParent { async receiveMessage(aMessage) { if ( Loading @@ -33,7 +71,24 @@ export class CryptoSafetyParent extends JSWindowActorParent { return; } let address = aMessage.data.selection; // 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)}…`; } Loading Loading
browser/actors/CryptoSafetyChild.sys.mjs +10 −45 Original line number Diff line number Diff line Loading @@ -5,12 +5,14 @@ * 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 { Bech32Decode } from "resource://gre/modules/Bech32Decode.sys.mjs"; 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", Loading @@ -18,43 +20,6 @@ XPCOMUtils.defineLazyPreferenceGetter( 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 (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; } export class CryptoSafetyChild extends JSWindowActorChild { handleEvent(event) { if ( Loading @@ -70,13 +35,13 @@ export class CryptoSafetyChild extends JSWindowActorChild { return; } this.contentWindow.navigator.clipboard.readText().then(clipText => { const selection = clipText.replace(/\s+/g, ""); if (!looksLikeCryptoAddress(selection)) { 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", { selection, host: this.document.documentURIObject.host, }); }); Loading
browser/actors/CryptoSafetyParent.sys.mjs +56 −1 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs", Bech32Decode: "resource://gre/modules/Bech32Decode.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "CryptoStrings", function () { Loading @@ -24,6 +25,43 @@ XPCOMUtils.defineLazyPreferenceGetter( 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; } export class CryptoSafetyParent extends JSWindowActorParent { async receiveMessage(aMessage) { if ( Loading @@ -33,7 +71,24 @@ export class CryptoSafetyParent extends JSWindowActorParent { return; } let address = aMessage.data.selection; // 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)}…`; } Loading