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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andreas Tolfsen 2019-11-02 18:08:56 +00:00
parent 4b22ebf93f
commit ea1f27965e
7 changed files with 236 additions and 0 deletions

View File

@ -17,5 +17,6 @@ XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
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",
});

View File

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

View File

@ -52,6 +52,7 @@ remote.jar:
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)

View File

@ -0,0 +1,8 @@
[DEFAULT]
tags = remote
subsuite = remote
prefs = remote.enabled=true
support-files =
head.js
[browser_setIgnoreCertificateErrors.js]

View File

@ -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");
});

View File

@ -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
);

View File

@ -9,6 +9,7 @@ BROWSER_CHROME_MANIFESTS += [
"browser/network/browser.ini",
"browser/page/browser.ini",
"browser/runtime/browser.ini",
"browser/security/browser.ini",
"browser/target/browser.ini",
]