Commit edfb0780 authored by sanketh's avatar sanketh Committed by Pier Angelo Vendrame
Browse files

TB 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 551640fe
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -382,6 +382,25 @@ let JSWINDOWACTORS = {
    safeForUntrustedWebProcess: true,
  },

  CryptoSafety: {
    parent: {
      esModuleURI:
        "moz-src:///browser/components/cryptosafety/CryptoSafetyParent.sys.mjs",
    },

    child: {
      esModuleURI:
        "moz-src:///browser/components/cryptosafety/CryptoSafetyChild.sys.mjs",
      group: "browsers",
      events: {
        copy: { mozSystemGroup: true },
        cut: { mozSystemGroup: true },
      },
    },

    allFrames: true,
  },

  /* Note: this uses the same JSMs as ClickHandler, but because it
   * relies on "normal" click events anywhere on the page (not just
   * links) and is expensive, and only does something for the
+99 −0
Original line number Diff line number Diff line
// 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.

/**
 * JS module implementation of Bech32 decoding adapted from the reference
 * implementation https://github.com/sipa/bech32.
 */

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

export 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, data: data.slice(0, data.length - 6) };
}
+55 −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.
);

/**
 * Child part of the crypto safety actor.
 *
 * It just signals the parent whenever the user copies some text in a non-secure
 * HTTP page.
 */
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,
      });
    });
  }
}
+136 −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:
    "moz-src:///toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs",
  Bech32Decode:
    "moz-src:///browser/components/cryptosafety/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;
}

/**
 * This actor analyzes the copied text, and warns the user whenever that text
 * contains a string that resembles a crypto currency address.
 */
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.documentGlobal.gBrowser.selectedBrowser
        );
      }
    }
  }
}
+11 −0
Original line number Diff line number Diff line
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.

MOZ_SRC_FILES += [
    "Bech32Decode.sys.mjs",
    "CryptoSafetyChild.sys.mjs",
    "CryptoSafetyParent.sys.mjs",
]
Loading