[tor-commits] [tor-browser/tor-browser-78.11.0esr-10.5-1] 40209: Implement Basic Crypto Safety

sysrqb at torproject.org sysrqb at torproject.org
Wed Jun 2 15:22:48 UTC 2021


commit 363e84c859fa76ce15e80bfc4eeee52b94022b6b
Author: sanketh <me at snkth.com>
Date:   Mon Feb 8 20:12:44 2021 -0500

    40209: Implement Basic Crypto Safety
    
    Adds a CryptoSafety actor which detects when you've copied a crypto
    address from a HTTP webpage and shows a warning.
    
    Closes #40209.
    
    Bug 40428: Fix string attribute names
---
 browser/actors/CryptoSafetyChild.jsm         |  87 ++++++++++++++++
 browser/actors/CryptoSafetyParent.jsm        | 142 +++++++++++++++++++++++++++
 browser/actors/moz.build                     |   2 +
 browser/base/content/popup-notifications.inc |  14 +++
 browser/components/BrowserGlue.jsm           |  17 ++++
 browser/modules/TorStrings.jsm               |  48 +++++++++
 browser/themes/shared/browser.inc.css        |   5 +
 toolkit/content/license.html                 |  32 ++++++
 toolkit/modules/Bech32Decode.jsm             | 103 +++++++++++++++++++
 toolkit/modules/moz.build                    |   1 +
 10 files changed, 451 insertions(+)

diff --git a/browser/actors/CryptoSafetyChild.jsm b/browser/actors/CryptoSafetyChild.jsm
new file mode 100644
index 000000000000..87ff261d4915
--- /dev/null
+++ b/browser/actors/CryptoSafetyChild.jsm
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyChild"];
+
+const { Bech32Decode } = ChromeUtils.import(
+  "resource://gre/modules/Bech32Decode.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "isCryptoSafetyEnabled",
+  kPrefCryptoSafety,
+  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;
+}
+
+class CryptoSafetyChild extends JSWindowActorChild {
+  handleEvent(event) {
+    if (isCryptoSafetyEnabled) {
+      // Ignore non-HTTP addresses
+      if (!this.document.documentURIObject.schemeIs("http")) {
+        return;
+      }
+      // Ignore onion addresses
+      if (this.document.documentURIObject.host.endsWith(".onion")) {
+        return;
+      }
+
+      if (event.type == "copy" || event.type == "cut") {
+        this.contentWindow.navigator.clipboard.readText().then(clipText => {
+          const selection = clipText.trim();
+          if (looksLikeCryptoAddress(selection)) {
+            this.sendAsyncMessage("CryptoSafety:CopiedText", {
+              selection,
+            });
+          }
+        });
+      }
+    }
+  }
+}
diff --git a/browser/actors/CryptoSafetyParent.jsm b/browser/actors/CryptoSafetyParent.jsm
new file mode 100644
index 000000000000..bac151df5511
--- /dev/null
+++ b/browser/actors/CryptoSafetyParent.jsm
@@ -0,0 +1,142 @@
+/* -*- 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/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyParent"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  TorStrings: "resource:///modules/TorStrings.jsm",
+});
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "isCryptoSafetyEnabled",
+  kPrefCryptoSafety,
+  true /* defaults to true */
+);
+
+class CryptoSafetyParent extends JSWindowActorParent {
+  getBrowser() {
+    return this.browsingContext.top.embedderElement;
+  }
+
+  receiveMessage(aMessage) {
+    if (isCryptoSafetyEnabled) {
+      if (aMessage.name == "CryptoSafety:CopiedText") {
+        showPopup(this.getBrowser(), aMessage.data.selection);
+      }
+    }
+  }
+}
+
+function trimAddress(cryptoAddr) {
+  if (cryptoAddr.length <= 32) {
+    return cryptoAddr;
+  }
+  return cryptoAddr.substring(0, 32) + "...";
+}
+
+function showPopup(aBrowser, cryptoAddr) {
+  const chromeDoc = aBrowser.ownerDocument;
+  if (chromeDoc) {
+    const win = chromeDoc.defaultView;
+    const cryptoSafetyPrompt = new CryptoSafetyPrompt(
+      aBrowser,
+      win,
+      cryptoAddr
+    );
+    cryptoSafetyPrompt.show();
+  }
+}
+
+class CryptoSafetyPrompt {
+  constructor(aBrowser, aWin, cryptoAddr) {
+    this._browser = aBrowser;
+    this._win = aWin;
+    this._cryptoAddr = cryptoAddr;
+  }
+
+  show() {
+    const primaryAction = {
+      label: TorStrings.cryptoSafetyPrompt.primaryAction,
+      accessKey: TorStrings.cryptoSafetyPrompt.primaryActionAccessKey,
+      callback: () => {
+        this._win.torbutton_new_circuit();
+      },
+    };
+
+    const secondaryAction = {
+      label: TorStrings.cryptoSafetyPrompt.secondaryAction,
+      accessKey: TorStrings.cryptoSafetyPrompt.secondaryActionAccessKey,
+      callback: () => {},
+    };
+
+    let _this = this;
+    const options = {
+      popupIconURL: "chrome://browser/skin/cert-error.svg",
+      eventCallback(aTopic) {
+        if (aTopic === "showing") {
+          _this._onPromptShowing();
+        }
+      },
+    };
+
+    const cryptoWarningText = TorStrings.cryptoSafetyPrompt.cryptoWarning.replace(
+      "%S",
+      trimAddress(this._cryptoAddr)
+    );
+
+    if (this._win.PopupNotifications) {
+      this._prompt = this._win.PopupNotifications.show(
+        this._browser,
+        "crypto-safety-warning",
+        cryptoWarningText,
+        null /* anchor ID */,
+        primaryAction,
+        [secondaryAction],
+        options
+      );
+    }
+  }
+
+  _onPromptShowing() {
+    let xulDoc = this._browser.ownerDocument;
+
+    let whatCanHeading = xulDoc.getElementById(
+      "crypto-safety-warning-notification-what-can-heading"
+    );
+    if (whatCanHeading) {
+      whatCanHeading.textContent = TorStrings.cryptoSafetyPrompt.whatCanHeading;
+    }
+
+    let whatCanBody = xulDoc.getElementById(
+      "crypto-safety-warning-notification-what-can-body"
+    );
+    if (whatCanBody) {
+      whatCanBody.textContent = TorStrings.cryptoSafetyPrompt.whatCanBody;
+    }
+
+    let learnMoreElem = xulDoc.getElementById(
+      "crypto-safety-warning-notification-learnmore"
+    );
+    if (learnMoreElem) {
+      learnMoreElem.setAttribute(
+        "value",
+        TorStrings.cryptoSafetyPrompt.learnMore
+      );
+      learnMoreElem.setAttribute(
+        "href",
+        TorStrings.cryptoSafetyPrompt.learnMoreURL
+      );
+    }
+  }
+}
diff --git a/browser/actors/moz.build b/browser/actors/moz.build
index e70f0f09fe3a..9eb5ca397060 100644
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -50,6 +50,8 @@ FINAL_TARGET_FILES.actors += [
     'ContentSearchParent.jsm',
     'ContextMenuChild.jsm',
     'ContextMenuParent.jsm',
+    'CryptoSafetyChild.jsm',
+    'CryptoSafetyParent.jsm',
     'DOMFullscreenChild.jsm',
     'DOMFullscreenParent.jsm',
     'FormValidationChild.jsm',
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
index 42e17e90c648..ff6f8cdeca80 100644
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -114,3 +114,17 @@
         </vbox>
       </popupnotificationfooter>
     </popupnotification>
+
+    <popupnotification id="crypto-safety-warning-notification" hidden="true">
+      <popupnotificationcontent orient="vertical">
+        <description id="crypto-safety-warning-notification-desc"/>
+        <html:div id="crypto-safety-warning-notification-what-can">
+        <html:strong id="crypto-safety-warning-notification-what-can-heading" />
+        <html:br/>
+        <html:span id="crypto-safety-warning-notification-what-can-body" />
+        </html:div>
+        <label id="crypto-safety-warning-notification-learnmore"
+               class="popup-notification-learnmore-link"
+               is="text-link"/>
+      </popupnotificationcontent>
+    </popupnotification>
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 3750230a250b..5f708fca3d5c 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -297,6 +297,23 @@ let JSWINDOWACTORS = {
     allFrames: true,
   },
 
+  CryptoSafety: {
+    parent: {
+      moduleURI: "resource:///actors/CryptoSafetyParent.jsm",
+    },
+
+    child: {
+      moduleURI: "resource:///actors/CryptoSafetyChild.jsm",
+      group: "browsers",
+      events: {
+        copy: { mozSystemGroup: true },
+        cut: { mozSystemGroup: true },
+      },
+    },
+
+    allFrames: true,
+  },
+
   DOMFullscreen: {
     parent: {
       moduleURI: "resource:///actors/DOMFullscreenParent.jsm",
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index e8a8d37ae373..1e08b168e4af 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -101,6 +101,54 @@ class TorPropertyStringBundle {
   Security Level Strings
 */
 var TorStrings = {
+  /*
+    CryptoSafetyPrompt Strings
+  */
+  cryptoSafetyPrompt: (function() {
+    let tsb = new TorPropertyStringBundle(
+      "chrome://torbutton/locale/torbutton.properties",
+      "cryptoSafetyPrompt."
+    );
+    let getString = function(key, fallback) {
+      return tsb.getString(key, fallback);
+    };
+
+    let retval = {
+      cryptoWarning: getString(
+        "cryptoWarning",
+        "A cryptocurrency address (%S) has been copied from an insecure website. It could have been modified."
+      ),
+      whatCanHeading: getString(
+        "whatCanHeading",
+        "What can you do about it?"
+      ),
+      whatCanBody: getString(
+        "whatCanBody",
+        "You can try reconnecting with a new circuit to establish a secure connection, or accept the risk and dismiss this warning."
+      ),
+      learnMore: getString("learnMore", "Learn more"),
+      learnMoreURL: `https://support.torproject.org/${getLocale()}/`,
+      primaryAction: getString(
+        "primaryAction",
+        "Reload Tab with a New Circuit"
+      ),
+      primaryActionAccessKey: getString(
+        "primaryActionAccessKey",
+        "R"
+      ),
+      secondaryAction: getString(
+        "secondaryAction",
+        "Dismiss Warning"
+      ),
+      secondaryActionAccessKey: getString(
+        "secondaryActionAccessKey",
+        "D"
+      ),
+    };
+
+    return retval;
+  })() /* CryptoSafetyPrompt Strings */,
+
   /*
     Tor Browser Security Level Strings
   */
diff --git a/browser/themes/shared/browser.inc.css b/browser/themes/shared/browser.inc.css
index 0113466e8e56..4ef27d880754 100644
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -620,3 +620,8 @@ menupopup::part(drop-indicator) {
 #sharing-warning-proceed-to-tab:hover {
   background-color: rgb(0,62,170);
 }
+
+#crypto-safety-warning-notification-what-can {
+  display: block;
+  margin: 5px;
+}
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
index e44c31ec6d4e..90995236b41b 100644
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -72,6 +72,7 @@
       <li><a href="about:license#arm">ARM License</a></li>
       <li><a href="about:license#babel">Babel License</a></li>
       <li><a href="about:license#babylon">Babylon License</a></li>
+      <li><a href="about:license#bech32">Bech32 License</a></li>
       <li><a href="about:license#bincode">bincode License</a></li>
       <li><a href="about:license#bsd2clause">BSD 2-Clause License</a></li>
       <li><a href="about:license#bsd3clause">BSD 3-Clause License</a></li>
@@ -2795,6 +2796,37 @@ 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.
+</pre>
+
+
+    <hr>
+
+    <h1><a id="bech32"></a>Bech32 License</h1>
+
+    <p>This license applies to the file
+      <code>toolkit/modules/Bech32Decode.jsm</code>.
+    </p>
+
+<pre>
+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
diff --git a/toolkit/modules/Bech32Decode.jsm b/toolkit/modules/Bech32Decode.jsm
new file mode 100644
index 000000000000..3a2bc7ae0a10
--- /dev/null
+++ b/toolkit/modules/Bech32Decode.jsm
@@ -0,0 +1,103 @@
+// 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.
+
+"use strict";
+
+/**
+ * JS module implementation of Bech32 decoding adapted from the reference
+ * implementation https://github.com/sipa/bech32.
+ */
+
+var EXPORTED_SYMBOLS = ["Bech32Decode"];
+
+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;
+}
+
+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: hrp, data: data.slice(0, data.length - 6) };
+}
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index e1f1eb5759c5..698d2773a7ed 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -160,6 +160,7 @@ EXTRA_JS_MODULES += [
     'ActorManagerParent.jsm',
     'AppMenuNotifications.jsm',
     'AsyncPrefs.jsm',
+    'Bech32Decode.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',





More information about the tor-commits mailing list