Commit 7f366644 authored by sanketh's avatar sanketh Committed by Pier Angelo Vendrame
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 e0b35372
Loading
Loading
Loading
Loading
+90 −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 lazy = {};

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 (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 (
      !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;
    }

    this.contentWindow.navigator.clipboard.readText().then(clipText => {
      const selection = clipText.replace(/\s+/g, "");
      if (!looksLikeCryptoAddress(selection)) {
        return;
      }
      this.sendAsyncMessage("CryptoSafety:CopiedText", {
        selection,
        host: this.document.documentURIObject.host,
      });
    });
  }
}
+98 −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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);

const lazy = {};

ChromeUtils.defineModuleGetter(
  lazy,
  "TorDomainIsolator",
  "resource://gre/modules/TorDomainIsolator.jsm"
);

XPCOMUtils.defineLazyGetter(lazy, "cryptoSafetyBundle", () => {
  return Services.strings.createBundle(
    "chrome://browser/locale/cryptoSafetyPrompt.properties"
  );
});

// en-US fallback in case a locale is missing a string.
XPCOMUtils.defineLazyGetter(lazy, "fallbackCryptoSafetyBundle", () => {
  return Services.strings.createBundle(
    "resource:///chrome/en-US/locale/browser/cryptoSafetyPrompt.properties"
  );
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "isCryptoSafetyEnabled",
  "security.cryptoSafety",
  true // Defaults to true.
);

/**
 * Get a formatted string from the locale's bundle, or the en-US bundle if the
 * string is missing.
 *
 * @param {string} name - The string's name.
 * @param {string[]} [args] - Positional arguments to pass to the format string,
 *   or leave empty if none are needed.
 *
 * @returns {string} - The formatted string.
 */
function getString(name, args = []) {
  try {
    return lazy.cryptoSafetyBundle.formatStringFromName(name, args);
  } catch {
    return lazy.fallbackCryptoSafetyBundle.formatStringFromName(name, args);
  }
}

class CryptoSafetyParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    if (
      !lazy.isCryptoSafetyEnabled ||
      aMessage.name !== "CryptoSafety:CopiedText"
    ) {
      return;
    }

    let address = aMessage.data.selection;
    if (address.length > 32) {
      address = `${address.substring(0, 32)}…`;
    }

    const buttonPressed = Services.prompt.confirmEx(
      this.browsingContext.topChromeWindow,
      getString("cryptoSafetyPrompt.cryptoTitle"),
      getString("cryptoSafetyPrompt.cryptoBody", [address, aMessage.data.host]),
      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
        Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1,
      getString("cryptoSafetyPrompt.primaryAction"),
      getString("cryptoSafetyPrompt.secondaryAction"),
      null,
      null,
      {}
    );

    if (buttonPressed === 0) {
      const { browsingContext } = this.manager;
      const browser = browsingContext.embedderElement;
      if (browser) {
        lazy.TorDomainIsolator.newCircuitForBrowser(
          browser.ownerGlobal.gBrowser
        );
      }
    }
  }
}
+2 −0
Original line number Diff line number Diff line
@@ -56,6 +56,8 @@ FINAL_TARGET_FILES.actors += [
    "ContentSearchParent.sys.mjs",
    "ContextMenuChild.sys.mjs",
    "ContextMenuParent.sys.mjs",
    "CryptoSafetyChild.jsm",
    "CryptoSafetyParent.jsm",
    "DecoderDoctorChild.sys.mjs",
    "DecoderDoctorParent.sys.mjs",
    "DOMFullscreenChild.sys.mjs",
+18 −0
Original line number Diff line number Diff line
@@ -603,6 +603,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,
  },

+19 −0
Original line number Diff line number Diff line
# Copyright (c) 2022, 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/.

cryptoSafetyPrompt.cryptoTitle=Cryptocurrency address copied from an insecure website
# LOCALIZATION NOTE:
# %1$S is the copied cryptocurrency address.
# %2$S is the website host.
cryptoSafetyPrompt.cryptoBody=The copied text (%1$S) appears to be a cryptocurrency address. Since the connection to %2$S is not secure, the address may have been modified and should not be trusted. You can try establishing a secure connection by reconnecting with a new circuit.
# LOCALIZATION NOTE: %S will be replaced with the cryptocurrency address.
cryptoSafetyPrompt.cryptoWarning=A cryptocurrency address (%S) has been copied from an insecure website. It could have been modified.
cryptoSafetyPrompt.whatCanHeading=What can you do about it?
cryptoSafetyPrompt.whatCanBody=You can try reconnecting with a new circuit to establish a secure connection, or accept the risk and dismiss this warning.
cryptoSafetyPrompt.learnMore=Learn more
cryptoSafetyPrompt.primaryAction=Reload Tab with a New Circuit
cryptoSafetyPrompt.primaryActionAccessKey=R
cryptoSafetyPrompt.secondaryAction=Dismiss Warning
cryptoSafetyPrompt.secondaryActionAccessKey=B
Loading