Verified Commit deeb2ed3 authored by sanketh's avatar sanketh Committed by ma1
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 25e97fa3
Loading
Loading
Loading
Loading
+49 −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.
);

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,
      });
    });
  }
}
+130 −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: "resource://gre/modules/TorDomainIsolator.sys.mjs",
  Bech32Decode: "resource://gre/modules/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;
}

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.ownerGlobal.gBrowser.selectedBrowser
        );
      }
    }
  }
}
+2 −0
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ FINAL_TARGET_FILES.actors += [
    "ContentSearchParent.sys.mjs",
    "ContextMenuChild.sys.mjs",
    "ContextMenuParent.sys.mjs",
    "CryptoSafetyChild.sys.mjs",
    "CryptoSafetyParent.sys.mjs",
    "DecoderDoctorChild.sys.mjs",
    "DecoderDoctorParent.sys.mjs",
    "DOMFullscreenChild.sys.mjs",
+18 −0
Original line number Diff line number Diff line
@@ -650,6 +650,24 @@ let JSWINDOWACTORS = {
    },

    messageManagerGroups: ["browsers"],

    allFrames: true,
  },

  CryptoSafety: {
    parent: {
      esModuleURI: "resource:///actors/CryptoSafetyParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource:///actors/CryptoSafetyChild.sys.mjs",
      group: "browsers",
      events: {
        copy: { mozSystemGroup: true },
        cut: { mozSystemGroup: true },
      },
    },

    allFrames: true,
  },

+1 −0
Original line number Diff line number Diff line
@@ -3630,6 +3630,7 @@ SOFTWARE.
        <li><code>js/src/vm/Float16.h</code>(the code contained in the half namespace)</li>
        <li><code>toolkit/components/resistfingerprinting/content/gl-matrix.js</code></li>
        <li><code>toolkit/components/resistfingerprinting/content/ssdeep.js</code></li>
        <li><code>toolkit/modules/Bech32Decode.sys.mjs</code></li>
    </ul>
    See the individual LICENSE files or headers for copyright owners.</p>

Loading