bug 1591927: remote: implement Security.setIgnoreCertificateErrors; r=remote-protocol-reviewers,maja_zf
authorAndreas Tolfsen <ato@sny.no>
Sat, 02 Nov 2019 18:08:56 +0000
changeset 500322 24345627e6de238e5a666ff5f9a408e8edbdf371
parent 500321 89521f19577223ea653809b25d04e33bb336cc53
child 500323 611bf4a5e7a22d20f99afc5d56f6b4b8a683cd26
push id99518
push useratolfsen@mozilla.com
push dateSun, 03 Nov 2019 11:39:27 +0000
treeherderautoland@24345627e6de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, maja_zf
bugs1591927
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1591927: remote: implement Security.setIgnoreCertificateErrors; r=remote-protocol-reviewers,maja_zf This implements an all-or-nothing insecure sweeping override that bypasses security exceptions when loading documents with invalid or otherwise bad TLS certificates. Differential Revision: https://phabricator.services.mozilla.com/D50838
remote/domains/ParentProcessDomains.jsm
remote/domains/parent/Security.jsm
remote/jar.mn
remote/test/browser/security/browser.ini
remote/test/browser/security/browser_setIgnoreCertificateErrors.js
remote/test/browser/security/head.js
remote/test/moz.build
--- a/remote/domains/ParentProcessDomains.jsm
+++ b/remote/domains/ParentProcessDomains.jsm
@@ -12,10 +12,11 @@ const { XPCOMUtils } = ChromeUtils.impor
 
 const ParentProcessDomains = {};
 
 XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
   Browser: "chrome://remote/content/domains/parent/Browser.jsm",
   Input: "chrome://remote/content/domains/parent/Input.jsm",
   Network: "chrome://remote/content/domains/parent/Network.jsm",
   Page: "chrome://remote/content/domains/parent/Page.jsm",
+  Security: "chrome://remote/content/domains/parent/Security.jsm",
   Target: "chrome://remote/content/domains/parent/Target.jsm",
 });
new file mode 100644
--- /dev/null
+++ b/remote/domains/parent/Security.jsm
@@ -0,0 +1,55 @@
+/* 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["Security"];
+
+const { Domain } = ChromeUtils.import(
+  "chrome://remote/content/domains/Domain.jsm"
+);
+
+const { Preferences } = ChromeUtils.import(
+  "resource://gre/modules/Preferences.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyServiceGetters(this, {
+  sss: ["@mozilla.org/ssservice;1", "nsISiteSecurityService"],
+  certOverrideService: [
+    "@mozilla.org/security/certoverride;1",
+    "nsICertOverrideService",
+  ],
+});
+
+const CERT_PINNING_ENFORCEMENT_PREF = "security.cert_pinning.enforcement_level";
+const HSTS_PRELOAD_LIST_PREF = "network.stricttransportsecurity.preloadlist";
+
+class Security extends Domain {
+  destructor() {
+    this.setIgnoreCertificateErrors({ ignore: false });
+  }
+
+  setIgnoreCertificateErrors({ ignore }) {
+    if (ignore) {
+      // make it possible to register certificate overrides for domains
+      // that use HSTS or HPKP
+      Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
+      Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0);
+    } else {
+      Preferences.reset(HSTS_PRELOAD_LIST_PREF);
+      Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF);
+
+      // clear collected HSTS and HPKP state
+      sss.clearAll();
+      sss.clearPreloads();
+    }
+
+    certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+      ignore
+    );
+  }
+}
--- a/remote/jar.mn
+++ b/remote/jar.mn
@@ -47,16 +47,17 @@ remote.jar:
   content/domains/content/Security.jsm (domains/content/Security.jsm)
   content/domains/parent/Browser.jsm (domains/parent/Browser.jsm)
   content/domains/parent/Input.jsm (domains/parent/Input.jsm)
   content/domains/parent/Network.jsm (domains/parent/Network.jsm)
   content/domains/parent/network/ChannelEventSink.jsm (domains/parent/network/ChannelEventSink.jsm)
   content/domains/parent/network/NetworkObserver.jsm (domains/parent/network/NetworkObserver.jsm)
   content/domains/parent/Page.jsm (domains/parent/Page.jsm)
   content/domains/parent/page/DialogHandler.jsm (domains/parent/page/DialogHandler.jsm)
+  content/domains/parent/Security.jsm (domains/parent/Security.jsm)
   content/domains/parent/Target.jsm (domains/parent/Target.jsm)
   content/domains/parent/target/TabManager.jsm (domains/parent/target/TabManager.jsm)
 
   # transport layer
   content/server/HTTPD.jsm (../netwerk/test/httpserver/httpd.js)
   content/server/WebSocketHandshake.jsm (server/WebSocketHandshake.jsm)
   content/server/WebSocketTransport.jsm (server/WebSocketTransport.jsm)
 
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/security/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = remote
+subsuite = remote
+prefs = remote.enabled=true
+support-files =
+  head.js
+
+[browser_setIgnoreCertificateErrors.js]
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/security/browser_setIgnoreCertificateErrors.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+  STATE_IS_SECURE,
+  STATE_IS_BROKEN,
+  STATE_IS_INSECURE,
+} = Ci.nsIWebProgressListener;
+
+// from ../../../build/pgo/server-locations.txt
+const NO_CERT = "https://nocert.example.com:443";
+const SELF_SIGNED = "https://self-signed.example.com:443";
+const UNTRUSTED = "https://untrusted.example.com:443";
+const EXPIRED = "https://expired.example.com:443";
+const MISMATCH_EXPIRED = "https://mismatch.expired.example.com:443";
+const MISMATCH_UNTRUSTED = "https://mismatch.untrusted.example.com:443";
+const UNTRUSTED_EXPIRED = "https://untrusted-expired.example.com:443";
+const MISMATCH_UNTRUSTED_EXPIRED =
+  "https://mismatch.untrusted-expired.example.com:443";
+
+const BAD_CERTS = [
+  NO_CERT,
+  SELF_SIGNED,
+  UNTRUSTED,
+  EXPIRED,
+  MISMATCH_EXPIRED,
+  MISMATCH_UNTRUSTED,
+  UNTRUSTED_EXPIRED,
+  MISMATCH_UNTRUSTED_EXPIRED,
+];
+
+function getConnectionState() {
+  // prevents items that are being lazy loaded causing issues
+  document.getElementById("identity-box").click();
+  gIdentityHandler.refreshIdentityPopup();
+  return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+/**
+ * Compares the security state of the page with what is expected.
+ * Returns one of "secure", "broken", "insecure", or "unknown".
+ */
+function isSecurityState(browser, expectedState) {
+  const ui = browser.securityUI;
+  if (!ui) {
+    ok(false, "No security UI to get the security state");
+    return;
+  }
+
+  const isSecure = ui.state & STATE_IS_SECURE;
+  const isBroken = ui.state & STATE_IS_BROKEN;
+  const isInsecure = ui.state & STATE_IS_INSECURE;
+
+  let actualState;
+  if (isSecure && !(isBroken || isInsecure)) {
+    actualState = "secure";
+  } else if (isBroken && !(isSecure || isInsecure)) {
+    actualState = "broken";
+  } else if (isInsecure && !(isSecure || isBroken)) {
+    actualState = "insecure";
+  } else {
+    actualState = "unknown";
+  }
+
+  is(
+    expectedState,
+    actualState,
+    `Expected state is ${expectedState} and actual state is ${actualState}`
+  );
+}
+
+add_task(async function testDefault({ Security }) {
+  for (const url of BAD_CERTS) {
+    info(`Navigating to ${url}`);
+    const loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+    await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+    await loaded;
+
+    is(
+      getConnectionState(),
+      "cert-error-page",
+      "Security error page is present"
+    );
+    isSecurityState(gBrowser, "insecure");
+  }
+});
+
+add_task(async function testIgnore({ Security }) {
+  info("Enable security certificate override");
+  await Security.setIgnoreCertificateErrors({ ignore: true });
+
+  for (const url of BAD_CERTS) {
+    info(`Navigating to ${url}`);
+    await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+    await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+    is(
+      getConnectionState(),
+      "secure-cert-user-overridden",
+      "Security certificate was overridden by user"
+    );
+    isSecurityState(gBrowser, "secure");
+  }
+});
+
+add_task(async function testUnignore({ Security }) {
+  info("Disable security certificate override");
+  await Security.setIgnoreCertificateErrors({ ignore: false });
+
+  for (const url of BAD_CERTS) {
+    info(`Navigating to ${url}`);
+    const loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+    await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+    await loaded;
+
+    is(
+      getConnectionState(),
+      "cert-error-page",
+      "Security error page is present"
+    );
+    isSecurityState(gBrowser, "insecure");
+  }
+});
+
+// smoke test for unignored -> ignored -> unignored
+add_task(async function testToggle({ Security }) {
+  let loaded;
+
+  info("Enable security certificate override");
+  await Security.setIgnoreCertificateErrors({ ignore: true });
+
+  info(`Navigating to ${UNTRUSTED} having set the override`);
+  await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, UNTRUSTED);
+  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  is(
+    getConnectionState(),
+    "secure-cert-user-overridden",
+    "Security certificate was overridden by user"
+  );
+  isSecurityState(gBrowser, "secure");
+
+  info("Disable security certificate override");
+  await Security.setIgnoreCertificateErrors({ ignore: false });
+
+  info(`Navigating to ${UNTRUSTED} having unset the override`);
+  loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+  await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, UNTRUSTED);
+  await loaded;
+
+  is(
+    getConnectionState(),
+    "cert-error-page",
+    "Security error page is present by default"
+  );
+  isSecurityState(gBrowser, "insecure");
+});
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/security/head.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../head.js */
+
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/remote/test/browser/head.js",
+  this
+);
--- a/remote/test/moz.build
+++ b/remote/test/moz.build
@@ -4,13 +4,14 @@
 
 XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
 BROWSER_CHROME_MANIFESTS += [
     "browser/browser.ini",
     "browser/input/browser.ini",
     "browser/network/browser.ini",
     "browser/page/browser.ini",
     "browser/runtime/browser.ini",
+    "browser/security/browser.ini",
     "browser/target/browser.ini",
 ]
 
 with Files("puppeteer/test/**/*.spec.js"):
     IMPACTED_TESTS.flavors += ["puppeteer"]