Commit 530ec164 authored by sanketh's avatar sanketh Committed by Richard Pospesel
Browse files

Bug 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
parent a5b134cb
Loading
Loading
Loading
Loading
+87 −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/. */

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,
            });
          }
        });
      }
    }
  }
}
+142 −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/. */

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
      );
    }
  }
}
+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ FINAL_TARGET_FILES.actors += [
    "ContentSearchParent.jsm",
    "ContextMenuChild.jsm",
    "ContextMenuParent.jsm",
    "CryptoSafetyChild.jsm",
    "CryptoSafetyParent.jsm",
    "DecoderDoctorChild.jsm",
    "DecoderDoctorParent.jsm",
    "DOMFullscreenChild.jsm",
+14 −0
Original line number Diff line number Diff line
@@ -166,3 +166,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>
+18 −0
Original line number Diff line number Diff line
@@ -453,6 +453,24 @@ let JSWINDOWACTORS = {
    },

    messageManagerGroups: ["browsers"],

    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,
  },

Loading