summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/mochitest/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /security/manager/ssl/tests/mochitest/browser
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser.toml52
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_HSTS.js277
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_add_exception_dialog.js69
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js94
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_certViewer.js112
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js105
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js290
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js385
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.html6
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.js84
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js161
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js259
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_downloadCert_ui.js134
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js141
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_exportP12_passwordUI.js164
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_loadPKCS11Module_ui.js312
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ca.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ca.pem.certspec4
-rw-r--r--security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem19
-rw-r--r--security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem20
-rw-r--r--security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/code-ee.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/code-ee.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/email-ee.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/email-ee.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/expired-ca.pem18
-rw-r--r--security/manager/ssl/tests/mochitest/browser/expired-ca.pem.certspec5
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-cn.pem18
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem16
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-o.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-ou.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/head.js82
-rw-r--r--security/manager/ssl/tests/mochitest/browser/hsts_headers.sjs16
-rw-r--r--security/manager/ssl/tests/mochitest/browser/hsts_headers_framed.html22
-rw-r--r--security/manager/ssl/tests/mochitest/browser/intermediate.pem20
-rw-r--r--security/manager/ssl/tests/mochitest/browser/intermediate.pem.certspec4
-rw-r--r--security/manager/ssl/tests/mochitest/browser/invalid.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/invalid.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/longOID.pem25
-rw-r--r--security/manager/ssl/tests/mochitest/browser/longOID.pem.certspec4
-rw-r--r--security/manager/ssl/tests/mochitest/browser/md5-ee.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/md5-ee.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/moz.build7
-rw-r--r--security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem21
-rw-r--r--security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem.certspec4
-rw-r--r--security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem21
-rw-r--r--security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem.certspec4
-rw-r--r--security/manager/ssl/tests/mochitest/browser/revoked.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/revoked.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/some_content.html6
-rw-r--r--security/manager/ssl/tests/mochitest/browser/some_content_framed.html14
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ssl-ee.pem18
-rw-r--r--security/manager/ssl/tests/mochitest/browser/ssl-ee.pem.certspec3
-rw-r--r--security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem17
-rw-r--r--security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem.certspec2
-rw-r--r--security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem18
-rw-r--r--security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem.certspec4
68 files changed, 3279 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/mochitest/browser/browser.toml b/security/manager/ssl/tests/mochitest/browser/browser.toml
new file mode 100644
index 0000000000..433fffa4ac
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser.toml
@@ -0,0 +1,52 @@
+[DEFAULT]
+tags = "psm"
+support-files = [
+ "*.pem",
+ "head.js",
+ "hsts_headers.sjs",
+ "hsts_headers_framed.html",
+ "some_content.html",
+ "some_content_framed.html",
+ "browser_clientAuth_speculative_connection.html"
+]
+
+["browser_HSTS.js"]
+https_first_disabled = true
+
+["browser_add_exception_dialog.js"]
+
+["browser_bug627234_perwindowpb.js"]
+
+["browser_certViewer.js"]
+skip-if = ["verify"]
+
+["browser_certificateManager.js"]
+
+["browser_clientAuthRememberService.js"]
+
+["browser_clientAuth_connection.js"]
+# Any test that has to delete certificates (e.g. as part of cleanup) is
+# fundamentally incompatible with verify due to how NSS handles deleting
+# certificates.
+skip-if = [
+ "verify",
+ "socketprocess_networking",
+]
+
+["browser_clientAuth_speculative_connection.js"]
+skip-if = ["socketprocess_networking"]
+
+["browser_clientAuth_ui.js"]
+
+["browser_deleteCert_ui.js"]
+
+["browser_downloadCert_ui.js"]
+
+["browser_editCACertTrust.js"]
+# An earlier attempt at landing this test resulted in frequent intermittent
+# failures, almost entirely on Linux. See Bug 1309519.
+skip-if = ["os == 'linux'"]
+
+["browser_exportP12_passwordUI.js"]
+
+["browser_loadPKCS11Module_ui.js"]
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_HSTS.js b/security/manager/ssl/tests/mochitest/browser/browser_HSTS.js
new file mode 100644
index 0000000000..f578ac7c4f
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_HSTS.js
@@ -0,0 +1,277 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that HTTP Strict Transport Security (HSTS) headers are noted as appropriate.
+
+// Register a cleanup function to clear all accumulated HSTS state when this
+// test is done.
+add_task(async function register_cleanup() {
+ registerCleanupFunction(() => {
+ let sss = Cc["@mozilla.org/ssservice;1"].getService(
+ Ci.nsISiteSecurityService
+ );
+ sss.clearAll();
+ });
+});
+
+// In the absense of HSTS information, no upgrade should happen.
+add_task(async function test_no_hsts_information_no_upgrade() {
+ let httpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, httpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+});
+
+// Visit a secure site that sends an HSTS header to set up the rest of the
+// test.
+add_task(async function see_hsts_header() {
+ let setHstsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers.sjs";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, setHstsUrl);
+ gBrowser.removeCurrentTab();
+});
+
+// Given a known HSTS host, future http navigations to that domain will be
+// upgraded.
+add_task(async function test_http_upgrade() {
+ let httpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, httpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "https");
+ gBrowser.removeCurrentTab();
+});
+
+// http navigations to unrelated hosts should not be upgraded.
+add_task(async function test_unrelated_domain_no_upgrade() {
+ let differentHttpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, differentHttpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+});
+
+// http navigations in private contexts shouldn't use information from
+// non-private contexts, so no upgrade should occur.
+add_task(async function test_private_window_no_upgrade() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first_pbm", false]],
+ });
+ let privateWindow = OpenBrowserWindow({ private: true });
+ await BrowserTestUtils.firstBrowserLoaded(privateWindow, false);
+ let url =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url);
+ Assert.equal(
+ privateWindow.gBrowser.selectedBrowser.currentURI.scheme,
+ "http"
+ );
+ privateWindow.gBrowser.removeCurrentTab();
+ privateWindow.close();
+});
+
+// Since the header didn't specify "includeSubdomains", visiting a subdomain
+// should not result in an upgrade.
+add_task(async function test_subdomain_no_upgrade() {
+ let subdomainHttpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://test1.example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, subdomainHttpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+});
+
+// Now visit a secure site that sends an HSTS header that also includes subdomains.
+add_task(async function see_hsts_header_include_subdomains() {
+ let setHstsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers.sjs?includeSubdomains";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, setHstsUrl);
+ gBrowser.removeCurrentTab();
+});
+
+// Now visiting a subdomain should result in an upgrade.
+add_task(async function test_subdomain_upgrade() {
+ let subdomainHttpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://test1.example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, subdomainHttpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "https");
+ gBrowser.removeCurrentTab();
+});
+
+// Visiting a subdomain with https should result in an https URL (this isn't an
+// upgrade - this test is essentially a consistency check).
+add_task(async function test_already_https() {
+ let subdomainHttpsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://test2.example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, subdomainHttpsUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "https");
+ gBrowser.removeCurrentTab();
+});
+
+// Test that subresources are upgraded.
+add_task(async function test_iframe_upgrade() {
+ let framedUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "some_content_framed.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, framedUrl);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let frame = content.document.getElementById("frame");
+ if (frame) {
+ return frame.baseURI.startsWith("https://");
+ }
+ return false;
+ });
+ });
+ gBrowser.removeCurrentTab();
+});
+
+// Clear state.
+add_task(async function clear_hsts_state() {
+ let sss = Cc["@mozilla.org/ssservice;1"].getService(
+ Ci.nsISiteSecurityService
+ );
+ sss.clearAll();
+});
+
+// Make sure this test is valid.
+add_task(async function test_no_hsts_information_no_upgrade_again() {
+ let httpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, httpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+});
+
+// Visit a site with an iframe that loads first-party content that sends an
+// HSTS header. The header should be heeded because it's first-party.
+add_task(async function see_hsts_header_in_framed_first_party_context() {
+ let framedUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers_framed.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, framedUrl);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("done");
+ });
+ });
+ gBrowser.removeCurrentTab();
+});
+
+// Check that the framed, first-party header was heeded.
+add_task(async function test_http_upgrade_after_framed_first_party_header() {
+ let httpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, httpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "https");
+ gBrowser.removeCurrentTab();
+});
+
+// Visit a site with an iframe that loads third-party content that sends an
+// HSTS header. The header should be ignored because it's third-party.
+add_task(async function see_hsts_header_in_third_party_context() {
+ let framedUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers_framed.html?third-party";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, framedUrl);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document.getElementById("done");
+ });
+ });
+ gBrowser.removeCurrentTab();
+});
+
+// Since the HSTS header was not received in a first-party context, no upgrade
+// should occur.
+add_task(async function test_no_upgrade_for_third_party_header() {
+ let url =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+});
+
+// Clear state again.
+add_task(async function clear_hsts_state_again() {
+ let sss = Cc["@mozilla.org/ssservice;1"].getService(
+ Ci.nsISiteSecurityService
+ );
+ sss.clearAll();
+});
+
+// HSTS information encountered in private contexts should not be used in
+// non-private contexts.
+add_task(
+ async function test_no_upgrade_for_HSTS_information_from_private_window() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first_pbm", false]],
+ });
+ let privateWindow = OpenBrowserWindow({ private: true });
+ await BrowserTestUtils.firstBrowserLoaded(privateWindow, false);
+ let setHstsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers.sjs";
+ await BrowserTestUtils.openNewForegroundTab(
+ privateWindow.gBrowser,
+ setHstsUrl
+ );
+ privateWindow.gBrowser.removeCurrentTab();
+
+ let httpUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "some_content.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, httpUrl);
+ Assert.equal(gBrowser.selectedBrowser.currentURI.scheme, "http");
+ gBrowser.removeCurrentTab();
+
+ privateWindow.close();
+ }
+);
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_add_exception_dialog.js b/security/manager/ssl/tests/mochitest/browser/browser_add_exception_dialog.js
new file mode 100644
index 0000000000..0916ac5ce4
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_add_exception_dialog.js
@@ -0,0 +1,69 @@
+/* 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";
+
+// This test makes sure that adding certificate exceptions behaves correctly
+// when done from the prefs window
+
+ChromeUtils.defineESModuleGetters(this, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+});
+
+function test() {
+ const EXCEPTIONS_DLG_URL = "chrome://pippki/content/exceptionDialog.xhtml";
+ const EXCEPTIONS_DLG_FEATURES = "chrome,centerscreen";
+ const INVALID_CERT_DOMAIN = "self-signed.example.com";
+ const INVALID_CERT_LOCATION = "https://" + INVALID_CERT_DOMAIN + "/";
+ waitForExplicitFinish();
+
+ function testAddCertificate() {
+ win.removeEventListener("load", testAddCertificate);
+ Services.obs.addObserver(async function onCertUI(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(onCertUI, "cert-exception-ui-ready");
+ ok(win.gCert, "The certificate information should be available now");
+
+ let dialog = win.document.getElementById("exceptiondialog");
+ let confirmButton = dialog.getButton("extra1");
+ confirmButton.click();
+ ok(
+ params.exceptionAdded,
+ "The certificate exception should have been added"
+ );
+
+ registerCleanupFunction(() => {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride(INVALID_CERT_DOMAIN, -1, {});
+ });
+
+ BrowserTestUtils.startLoadingURIString(gBrowser, INVALID_CERT_LOCATION);
+ let loaded = await BrowserTestUtils.browserLoaded(
+ gBrowser,
+ false,
+ INVALID_CERT_LOCATION,
+ true
+ );
+ ok(loaded, "The certificate exception should allow the page to load");
+
+ finish();
+ }, "cert-exception-ui-ready");
+ }
+
+ let bWin = BrowserWindowTracker.getTopWindow();
+ let params = {
+ exceptionAdded: false,
+ location: INVALID_CERT_LOCATION,
+ prefetchCert: true,
+ };
+
+ let win = bWin.openDialog(
+ EXCEPTIONS_DLG_URL,
+ "",
+ EXCEPTIONS_DLG_FEATURES,
+ params
+ );
+ win.addEventListener("load", testAddCertificate);
+}
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
new file mode 100644
index 0000000000..79e7ad9b12
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
@@ -0,0 +1,94 @@
+/* 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";
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ win.addEventListener(
+ "load",
+ function () {
+ aCallback(win);
+ },
+ { once: true }
+ );
+}
+
+// This is a template to help porting global private browsing tests
+// to per-window private browsing tests
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let windowsToClose = [];
+ let testURI = "about:blank";
+ let uri;
+ let gSSService = Cc["@mozilla.org/ssservice;1"].getService(
+ Ci.nsISiteSecurityService
+ );
+
+ function originAttributes(aIsPrivateMode) {
+ return aIsPrivateMode ? { privateBrowsingId: 1 } : {};
+ }
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(
+ () => {
+ uri = aWindow.Services.io.newURI("https://localhost/img.png");
+ gSSService.processHeader(
+ uri,
+ "max-age=1000",
+ originAttributes(aIsPrivateMode)
+ );
+ ok(
+ gSSService.isSecureURI(uri, originAttributes(aIsPrivateMode)),
+ "checking sts host"
+ );
+
+ aCallback();
+ }
+ );
+
+ BrowserTestUtils.startLoadingURIString(
+ aWindow.gBrowser.selectedBrowser,
+ testURI
+ );
+ }
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function (aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(function () {
+ aCallback(aWin);
+ });
+ });
+ }
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function () {
+ windowsToClose.forEach(function (aWin) {
+ aWin.close();
+ });
+ uri = Services.io.newURI("http://localhost");
+ gSSService.resetState(uri);
+ });
+
+ // test first when on private mode
+ testOnWindow({ private: true }, function (aWin) {
+ doTest(true, aWin, function () {
+ // test when not on private mode
+ testOnWindow({}, function (aWin) {
+ doTest(false, aWin, function () {
+ // test again when on private mode
+ testOnWindow({ private: true }, function (aWin) {
+ doTest(true, aWin, function () {
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
new file mode 100644
index 0000000000..7f0b8888c1
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
@@ -0,0 +1,112 @@
+/* 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";
+
+// Repeatedly opens the certificate viewer dialog with various certificates and
+// determines that the viewer correctly identifies either what usages those
+// certificates are valid for or what errors prevented the certificates from
+// being verified.
+
+add_task(async function testCAandTitle() {
+ let cert = await readCertificate("ca.pem", "CTu,CTu,CTu");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "ca");
+});
+
+add_task(async function testSSLEndEntity() {
+ let cert = await readCertificate("ssl-ee.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "ssl-ee");
+});
+
+add_task(async function testEmailEndEntity() {
+ let cert = await readCertificate("email-ee.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "email-ee");
+});
+
+add_task(async function testCodeSignEndEntity() {
+ let cert = await readCertificate("code-ee.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "code-ee");
+});
+
+add_task(async function testExpired() {
+ let cert = await readCertificate("expired-ca.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "expired-ca");
+});
+
+add_task(async function testUntrusted() {
+ let cert = await readCertificate("untrusted-ca.pem", "p,p,p");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "untrusted-ca");
+});
+
+add_task(async function testInvalid() {
+ // This certificate has a keyUsage extension asserting cRLSign and
+ // keyCertSign, but it doesn't have a basicConstraints extension. This
+ // shouldn't be valid for any usage. Sadly, we give a pretty bad error
+ // message in this case.
+ let cert = await readCertificate("invalid.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "invalid");
+});
+
+add_task(async function testLongOID() {
+ // This certificate has a certificatePolicies extension with a policy with a
+ // very long OID. This tests that we don't crash when looking at it.
+ let cert = await readCertificate("longOID.pem", ",,");
+ let url = getURL(cert);
+ await openCertViewerAndCheckTabName(url, "Long OID");
+});
+
+/**
+ * Given a certificate, returns its PEMs (each one of the certificate chain) string in a url.
+ *
+ * @param {object} cert
+ * A certificate object
+ * @returns {string} an URL for opening the certificate viewer
+ */
+function getURL(cert) {
+ // Note that we don't get the certificate chain as in e.g browser/base/content/browser.js,
+ // because all the .pem files when opened with CS (https://github.com/april/certainly-something)
+ // shows only one certificate
+ let derb64 = encodeURIComponent(cert.getBase64DERString());
+ return `about:certificate?cert=${derb64}`;
+}
+
+/**
+ * Given an certificate URL, opens the new certificate viewer and check
+ * if a certain element exists, with its expected result.
+ *
+ * @param {string} url
+ * The URL with the certificate info
+ * @param {string} expectedTabName
+ * The expected name of the tab in the certificate viewer
+ */
+async function openCertViewerAndCheckTabName(url, expectedTabName) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url },
+ async function (browser) {
+ await SpecialPowers.spawn(
+ browser,
+ [expectedTabName],
+ async function (expectedTabName) {
+ let certificateSection = await ContentTaskUtils.waitForCondition(
+ () => {
+ return content.document.querySelector("certificate-section");
+ },
+ "Certificate section found"
+ );
+ let tabName =
+ certificateSection.shadowRoot.querySelector(
+ ".tab[idnumber='0']"
+ ).textContent;
+ Assert.equal(tabName, expectedTabName);
+ }
+ );
+ }
+ );
+}
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js b/security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js
new file mode 100644
index 0000000000..c6619909d0
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js
@@ -0,0 +1,105 @@
+/* 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";
+
+async function checkServerCertificates(win, expectedValues = []) {
+ await TestUtils.waitForCondition(() => {
+ return (
+ win.document.getElementById("serverList").itemChildren.length ==
+ expectedValues.length
+ );
+ }, `Expected to have ${expectedValues.length} but got ${win.document.getElementById("serverList").itemChildren.length}`);
+ await new Promise(win.requestAnimationFrame);
+
+ let labels = win.document
+ .getElementById("serverList")
+ .querySelectorAll("label");
+
+ // The strings we will get from the DOM are localized with Fluent.
+ // This will wait until the translation is applied.
+ if (expectedValues.length) {
+ await BrowserTestUtils.waitForCondition(
+ () => labels[1].value || !!labels[1].textContent.length,
+ "At least one label is populated"
+ );
+ }
+
+ expectedValues.forEach((item, i) => {
+ let hostPort = labels[i * 3].value;
+ let fingerprint = labels[i * 3 + 1].value || labels[i * 3 + 1].textContent;
+
+ Assert.equal(
+ hostPort,
+ item.hostPort,
+ `Expected override to be ${item.hostPort} but got ${hostPort}`
+ );
+
+ Assert.equal(
+ fingerprint,
+ item.fingerprint,
+ `Expected override to have field ${item.fingerprint}`
+ );
+ });
+}
+
+async function deleteOverride(win, expectedLength) {
+ win.document.getElementById("serverList").selectedIndex = 0;
+ await TestUtils.waitForCondition(() => {
+ return (
+ win.document.getElementById("serverList").itemChildren.length ==
+ expectedLength
+ );
+ });
+ let newWinPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
+ // Since the .click() blocks we need to dispatch it to the main thread avoid that.
+ Services.tm.dispatchToMainThread(() =>
+ win.document.getElementById("websites_deleteButton").click()
+ );
+ let newWin = await newWinPromise;
+ newWin.document.getElementById("deleteCertificate").acceptDialog();
+ Assert.equal(
+ win.document.getElementById("serverList").selectedIndex,
+ 0,
+ "After deletion we expect the selectedItem to be reset."
+ );
+}
+
+add_task(async function test_cert_manager_server_tab() {
+ let win = await openCertManager();
+
+ await checkServerCertificates(win);
+
+ win.document.getElementById("certmanager").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ let cert = await readCertificate("md5-ee.pem", ",,");
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.rememberValidityOverride(
+ "example.com",
+ 443,
+ {},
+ cert,
+ false
+ );
+
+ win = await openCertManager();
+
+ await checkServerCertificates(win, [
+ {
+ hostPort: "example.com:443",
+ fingerprint: cert.sha256Fingerprint,
+ },
+ ]);
+
+ await deleteOverride(win, 1);
+
+ await checkServerCertificates(win, []);
+
+ win.document.getElementById("certmanager").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ certOverrideService.clearAllOverrides();
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js b/security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js
new file mode 100644
index 0000000000..87b476e012
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js
@@ -0,0 +1,290 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+/**
+ * Test certificate (i.e. build/pgo/certs/mochitest.client).
+ *
+ * @type {nsIX509Cert}
+ */
+var cert;
+var cert2;
+var cert3;
+
+var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
+var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+var deleted = false;
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+function findCertByCommonName(commonName) {
+ for (let cert of certDB.getCerts()) {
+ if (cert.commonName == commonName) {
+ return cert;
+ }
+ }
+ return null;
+}
+
+async function testHelper(connectURL, expectedURL) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.default_personal_cert", "Ask Every Time"]],
+ });
+
+ BrowserTestUtils.startLoadingURIString(
+ win.gBrowser.selectedBrowser,
+ connectURL
+ );
+
+ await BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ expectedURL,
+ true
+ );
+ let loadedURL = win.gBrowser.selectedBrowser.documentURI.spec;
+ Assert.ok(
+ loadedURL.startsWith(expectedURL),
+ `Expected and actual URLs should match (got '${loadedURL}', expected '${expectedURL}')`
+ );
+
+ await win.close();
+
+ // This clears the TLS session cache so we don't use a previously-established
+ // ticket to connect and bypass selecting a client auth certificate in
+ // subsequent tests.
+ sdr.logout();
+}
+
+async function openRequireClientCert() {
+ gClientAuthDialogService.chooseCertificateCalled = false;
+ await testHelper(
+ "https://requireclientcert.example.com:443",
+ "https://requireclientcert.example.com/"
+ );
+}
+
+async function openRequireClientCert2() {
+ gClientAuthDialogService.chooseCertificateCalled = false;
+ await testHelper(
+ "https://requireclientcert-2.example.com:443",
+ "https://requireclientcert-2.example.com/"
+ );
+}
+
+// Mock implementation of nsIClientAuthRememberService
+const gClientAuthRememberService = {
+ forgetRememberedDecision(key) {
+ deleted = true;
+ Assert.equal(
+ key,
+ "exampleKey2",
+ "Expected to get the same key that was passed in getDecisions()"
+ );
+ },
+
+ getDecisions() {
+ return [
+ {
+ asciiHost: "example.com",
+ dbKey: cert.dbKey,
+ entryKey: "exampleKey1",
+ },
+ {
+ asciiHost: "example.org",
+ dbKey: cert2.dbKey,
+ entryKey: "exampleKey2",
+ },
+ {
+ asciiHost: "example.test",
+ dbKey: cert3.dbKey,
+ entryKey: "exampleKey3",
+ },
+ {
+ asciiHost: "unavailable.example.com",
+ // This dbKey should not correspond to any real certificate. The first
+ // 8 bytes have to be 0, followed by the lengths of the serial number
+ // and issuer distinguished name, respectively, and then followed by
+ // the bytes of the serial number and finally the encoded issuer
+ // distinguished name. In this case, the serial number is a single 0
+ // byte and the issuer distinguished name is a DER SEQUENCE of length 0
+ // (the bytes 0x30 and 0).
+ // See also the documentation in nsNSSCertificateDB::FindCertByDBKey.
+ dbKey: "AAAAAAAAAAAAAAABAAAAAgAeAA==",
+ entryKey: "exampleKey4",
+ },
+ ];
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthRememberService"]),
+};
+
+const gClientAuthDialogService = {
+ _chooseCertificateCalled: false,
+
+ get chooseCertificateCalled() {
+ return this._chooseCertificateCalled;
+ },
+
+ set chooseCertificateCalled(value) {
+ this._chooseCertificateCalled = value;
+ },
+
+ chooseCertificate(hostname, certArray, loadContext, callback) {
+ this.chooseCertificateCalled = true;
+ callback.certificateChosen(certArray[0], true);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]),
+};
+
+add_task(async function testRememberedDecisionsUI() {
+ cert = findCertByCommonName("Mochitest client");
+ cert2 = await readCertificate("pgo-ca-all-usages.pem", ",,");
+ cert3 = await readCertificate("client-cert-via-intermediate.pem", ",,");
+ isnot(cert, null, "Should be able to find the test client cert");
+ isnot(cert2, null, "Should be able to find pgo-ca-all-usages.pem");
+ isnot(cert3, null, "Should be able to find client-cert-via-intermediate.pem");
+
+ let clientAuthRememberServiceCID = MockRegistrar.register(
+ "@mozilla.org/security/clientAuthRememberService;1",
+ gClientAuthRememberService
+ );
+
+ let win = await openCertManager();
+
+ let listItems = win.document
+ .getElementById("rememberedList")
+ .querySelectorAll("richlistitem");
+
+ Assert.equal(
+ listItems.length,
+ 4,
+ "rememberedList has expected number of items"
+ );
+
+ let labels = win.document
+ .getElementById("rememberedList")
+ .querySelectorAll("label");
+
+ Assert.equal(
+ labels.length,
+ 12,
+ "rememberedList has expected number of labels"
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () => !!labels[10].textContent.length,
+ "Localized label is populated"
+ );
+
+ let expectedHosts = [
+ "example.com",
+ "example.org",
+ "example.test",
+ "unavailable.example.com",
+ ];
+ let hosts = [
+ labels[0].value,
+ labels[3].value,
+ labels[6].value,
+ labels[9].value,
+ ];
+ let expectedNames = [
+ cert.commonName,
+ cert2.commonName,
+ cert3.commonName,
+ "(Unavailable)",
+ ];
+ let names = [
+ labels[1].value,
+ labels[4].value,
+ labels[7].value,
+ labels[10].textContent,
+ ];
+ let expectedSerialNumbers = [
+ cert.serialNumber,
+ cert2.serialNumber,
+ cert3.serialNumber,
+ "(Unavailable)",
+ ];
+ let serialNumbers = [
+ labels[2].value,
+ labels[5].value,
+ labels[8].value,
+ labels[11].textContent,
+ ];
+
+ for (let i = 0; i < listItems.length; i++) {
+ Assert.equal(hosts[i], expectedHosts[i], "got expected asciiHost");
+ Assert.equal(names[i], expectedNames[i], "got expected commonName");
+ Assert.equal(
+ serialNumbers[i],
+ expectedSerialNumbers[i],
+ "got expected serialNumber"
+ );
+ }
+
+ win.document.getElementById("rememberedList").selectedIndex = 1;
+ win.document.getElementById("remembered_deleteButton").click();
+
+ Assert.ok(deleted, "Expected forgetRememberedDecision() to get called");
+
+ win.document.getElementById("certmanager").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ MockRegistrar.unregister(clientAuthRememberServiceCID);
+});
+
+add_task(async function testDeletingRememberedDecisions() {
+ let clientAuthDialogServiceCID = MockRegistrar.register(
+ "@mozilla.org/security/ClientAuthDialogService;1",
+ gClientAuthDialogService
+ );
+ let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
+ Ci.nsIClientAuthRememberService
+ );
+
+ await openRequireClientCert();
+ Assert.ok(
+ gClientAuthDialogService.chooseCertificateCalled,
+ "chooseCertificate should have been called if visiting 'requireclientcert.example.com' for the first time"
+ );
+
+ await openRequireClientCert();
+ Assert.ok(
+ !gClientAuthDialogService.chooseCertificateCalled,
+ "chooseCertificate should not have been called if visiting 'requireclientcert.example.com' for the second time"
+ );
+
+ await openRequireClientCert2();
+ Assert.ok(
+ gClientAuthDialogService.chooseCertificateCalled,
+ "chooseCertificate should have been called if visiting 'requireclientcert-2.example.com' for the first time"
+ );
+
+ let originAttributes = { privateBrowsingId: 0 };
+ cars.deleteDecisionsByHost("requireclientcert.example.com", originAttributes);
+
+ await openRequireClientCert();
+ Assert.ok(
+ gClientAuthDialogService.chooseCertificateCalled,
+ "chooseCertificate should have been called after removing all remembered decisions for 'requireclientcert.example.com'"
+ );
+
+ await openRequireClientCert2();
+ Assert.ok(
+ !gClientAuthDialogService.chooseCertificateCalled,
+ "chooseCertificate should not have been called if visiting 'requireclientcert-2.example.com' for the second time"
+ );
+
+ MockRegistrar.unregister(clientAuthDialogServiceCID);
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js
new file mode 100644
index 0000000000..2eed2b620a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js
@@ -0,0 +1,385 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests various scenarios connecting to a server that requires client cert
+// authentication. Also tests that nsIClientAuthDialogService.chooseCertificate
+// is called at the appropriate times and with the correct arguments.
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const DialogState = {
+ // Assert that chooseCertificate() is never called.
+ ASSERT_NOT_CALLED: "ASSERT_NOT_CALLED",
+ // Return that the user selected the first given cert.
+ RETURN_CERT_SELECTED: "RETURN_CERT_SELECTED",
+ // Return that the user canceled.
+ RETURN_CERT_NOT_SELECTED: "RETURN_CERT_NOT_SELECTED",
+};
+
+var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
+let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
+ Ci.nsIClientAuthRememberService
+);
+
+var gExpectedClientCertificateChoices;
+
+// Mock implementation of nsIClientAuthDialogService.
+const gClientAuthDialogService = {
+ _state: DialogState.ASSERT_NOT_CALLED,
+ _rememberClientAuthCertificate: false,
+ _chooseCertificateCalled: false,
+
+ set state(newState) {
+ info(`old state: ${this._state}`);
+ this._state = newState;
+ info(`new state: ${this._state}`);
+ },
+
+ get state() {
+ return this._state;
+ },
+
+ set rememberClientAuthCertificate(value) {
+ this._rememberClientAuthCertificate = value;
+ },
+
+ get rememberClientAuthCertificate() {
+ return this._rememberClientAuthCertificate;
+ },
+
+ get chooseCertificateCalled() {
+ return this._chooseCertificateCalled;
+ },
+
+ set chooseCertificateCalled(value) {
+ this._chooseCertificateCalled = value;
+ },
+
+ chooseCertificate(hostname, certArray, loadContext, callback) {
+ this.chooseCertificateCalled = true;
+ Assert.notEqual(
+ this.state,
+ DialogState.ASSERT_NOT_CALLED,
+ "chooseCertificate() should be called only when expected"
+ );
+ Assert.equal(
+ hostname,
+ "requireclientcert.example.com",
+ "Hostname should be 'requireclientcert.example.com'"
+ );
+
+ // For mochitests, the cert at build/pgo/certs/mochitest.client should be
+ // selectable as well as one of the PGO certs we loaded in `setup`, so we do
+ // some brief checks to confirm this.
+ Assert.notEqual(certArray, null, "Cert list should not be null");
+ Assert.equal(
+ certArray.length,
+ gExpectedClientCertificateChoices,
+ `${gExpectedClientCertificateChoices} certificates should be available`
+ );
+
+ for (let cert of certArray) {
+ Assert.notEqual(cert, null, "Cert list should contain nsIX509Certs");
+ Assert.equal(
+ cert.issuerCommonName,
+ "Temporary Certificate Authority",
+ "cert should have expected issuer CN"
+ );
+ }
+
+ if (this.state == DialogState.RETURN_CERT_SELECTED) {
+ callback.certificateChosen(
+ certArray[0],
+ this.rememberClientAuthCertificate
+ );
+ } else {
+ callback.certificateChosen(null, this.rememberClientAuthCertificate);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
+};
+
+add_setup(async function () {
+ let clientAuthDialogServiceCID = MockRegistrar.register(
+ "@mozilla.org/security/ClientAuthDialogService;1",
+ gClientAuthDialogService
+ );
+ registerCleanupFunction(() => {
+ MockRegistrar.unregister(clientAuthDialogServiceCID);
+ });
+
+ // This CA has the expected keyCertSign and cRLSign usages. It should not be
+ // presented for use as a client certificate.
+ await readCertificate("pgo-ca-regular-usages.pem", "CTu,CTu,CTu");
+ // This CA has all keyUsages. For compatibility with preexisting behavior, it
+ // will be presented for use as a client certificate.
+ await readCertificate("pgo-ca-all-usages.pem", "CTu,CTu,CTu");
+ // This client certificate was issued by an intermediate that was issued by
+ // the test CA. The server only lists the test CA's subject distinguished name
+ // as an acceptible issuer name for client certificates. If the implementation
+ // can determine that the test CA is a root CA for the client certificate and
+ // thus is acceptible to use, it should be included in the chooseCertificate
+ // callback. At the beginning of this test (speaking of this file as a whole),
+ // the client is not aware of the intermediate, and so it is not available in
+ // the callback.
+ await readCertificate("client-cert-via-intermediate.pem", ",,");
+ // This certificate has an id-kp-OCSPSigning EKU. Client certificates
+ // shouldn't have this EKU, but there is at least one private PKI where they
+ // do. For interoperability, such certificates will be presented for use.
+ await readCertificate("client-cert-with-ocsp-signing.pem", ",,");
+ gExpectedClientCertificateChoices = 3;
+});
+
+/**
+ * Test helper for the tests below.
+ *
+ * @param {string} prefValue
+ * Value to set the "security.default_personal_cert" pref to.
+ * @param {string} urlToNavigate
+ * The URL to navigate to.
+ * @param {string} expectedURL
+ * If the connection is expected to load successfully, the URL that
+ * should load. If the connection is expected to fail and result in an
+ * error page, |undefined|.
+ * @param {boolean} expectCallingChooseCertificate
+ * Determines whether we expect chooseCertificate to be called.
+ * @param {object} options
+ * Optional options object to pass on to the window that gets opened.
+ * @param {string} expectStringInPage
+ * Optional string that is expected to be in the content of the page
+ * once it loads.
+ */
+async function testHelper(
+ prefValue,
+ urlToNavigate,
+ expectedURL,
+ expectCallingChooseCertificate,
+ options = undefined,
+ expectStringInPage = undefined
+) {
+ gClientAuthDialogService.chooseCertificateCalled = false;
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.default_personal_cert", prefValue]],
+ });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow(options);
+
+ BrowserTestUtils.startLoadingURIString(
+ win.gBrowser.selectedBrowser,
+ urlToNavigate
+ );
+ if (expectedURL) {
+ await BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ "https://requireclientcert.example.com/",
+ true
+ );
+ let loadedURL = win.gBrowser.selectedBrowser.documentURI.spec;
+ Assert.ok(
+ loadedURL.startsWith(expectedURL),
+ `Expected and actual URLs should match (got '${loadedURL}', expected '${expectedURL}')`
+ );
+ } else {
+ await new Promise(resolve => {
+ let removeEventListener = BrowserTestUtils.addContentEventListener(
+ win.gBrowser.selectedBrowser,
+ "AboutNetErrorLoad",
+ () => {
+ removeEventListener();
+ resolve();
+ },
+ { capture: false, wantUntrusted: true }
+ );
+ });
+ }
+
+ Assert.equal(
+ gClientAuthDialogService.chooseCertificateCalled,
+ expectCallingChooseCertificate,
+ "chooseCertificate should have been called if we were expecting it to be called"
+ );
+
+ if (expectStringInPage) {
+ let pageContent = await SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ return content.document.body.textContent;
+ }
+ );
+ Assert.ok(
+ pageContent.includes(expectStringInPage),
+ `page should contain the string '${expectStringInPage}' (was '${pageContent}')`
+ );
+ }
+
+ await win.close();
+
+ // This clears the TLS session cache so we don't use a previously-established
+ // ticket to connect and bypass selecting a client auth certificate in
+ // subsequent tests.
+ sdr.logout();
+}
+
+// Test that if a certificate is chosen automatically the connection succeeds,
+// and that nsIClientAuthDialogService.chooseCertificate() is never called.
+add_task(async function testCertChosenAutomatically() {
+ gClientAuthDialogService.state = DialogState.ASSERT_NOT_CALLED;
+ await testHelper(
+ "Select Automatically",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ false
+ );
+ // This clears all saved client auth certificate state so we don't influence
+ // subsequent tests.
+ cars.clearRememberedDecisions();
+});
+
+// Test that if the user doesn't choose a certificate, the connection fails and
+// an error page is displayed.
+add_task(async function testCertNotChosenByUser() {
+ gClientAuthDialogService.state = DialogState.RETURN_CERT_NOT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ undefined,
+ true,
+ undefined,
+ // bug 1818556: ssltunnel doesn't behave as expected here on Windows
+ AppConstants.platform != "win"
+ ? "SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT"
+ : undefined
+ );
+ cars.clearRememberedDecisions();
+});
+
+// Test that if the user chooses a certificate the connection suceeeds.
+add_task(async function testCertChosenByUser() {
+ gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ true
+ );
+ cars.clearRememberedDecisions();
+});
+
+// Test that the cancel decision is remembered correctly
+add_task(async function testEmptyCertChosenByUser() {
+ gClientAuthDialogService.state = DialogState.RETURN_CERT_NOT_SELECTED;
+ gClientAuthDialogService.rememberClientAuthCertificate = true;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ undefined,
+ true
+ );
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ undefined,
+ false
+ );
+ cars.clearRememberedDecisions();
+});
+
+// Test that if the user chooses a certificate in a private browsing window,
+// configures Firefox to remember this certificate for the duration of the
+// session, closes that window (and thus all private windows), reopens a private
+// window, and visits that site again, they are re-asked for a certificate (i.e.
+// any state from the previous private session should be gone). Similarly, after
+// closing that private window, if the user opens a non-private window, they
+// again should be asked to choose a certificate (i.e. private state should not
+// be remembered/used in non-private contexts).
+add_task(async function testClearPrivateBrowsingState() {
+ gClientAuthDialogService.rememberClientAuthCertificate = true;
+ gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ true,
+ {
+ private: true,
+ }
+ );
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ true,
+ {
+ private: true,
+ }
+ );
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ true
+ );
+ // NB: we don't `cars.clearRememberedDecisions()` in between the two calls to
+ // `testHelper` because that would clear all client auth certificate state and
+ // obscure what we're testing (that Firefox properly clears the relevant state
+ // when the last private window closes).
+ cars.clearRememberedDecisions();
+});
+
+// Test that 3rd party certificates are taken into account when filtering client
+// certificates based on the acceptible CA list sent by the server.
+add_task(async function testCertFilteringWithIntermediate() {
+ let intermediateBytes = await IOUtils.readUTF8(
+ getTestFilePath("intermediate.pem")
+ ).then(
+ pem => {
+ let base64 = pemToBase64(pem);
+ let bin = atob(base64);
+ let bytes = [];
+ for (let i = 0; i < bin.length; i++) {
+ bytes.push(bin.charCodeAt(i));
+ }
+ return bytes;
+ },
+ error => {
+ throw error;
+ }
+ );
+ let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
+ nssComponent.addEnterpriseIntermediate(intermediateBytes);
+ gExpectedClientCertificateChoices = 4;
+ gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ "https://requireclientcert.example.com/",
+ true
+ );
+ cars.clearRememberedDecisions();
+ // This will reset the added intermediate.
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.enterprise_roots.enabled", true]],
+ });
+});
+
+// Test that if the server certificate does not validate successfully,
+// nsIClientAuthDialogService.chooseCertificate() is never called.
+add_task(async function testNoDialogForUntrustedServerCertificate() {
+ gClientAuthDialogService.state = DialogState.ASSERT_NOT_CALLED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert-untrusted.example.com/",
+ undefined,
+ false
+ );
+ // This clears all saved client auth certificate state so we don't influence
+ // subsequent tests.
+ cars.clearRememberedDecisions();
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.html b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.html
new file mode 100644
index 0000000000..82aac47b2a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<a href="https://requireclientcert.example.com" id="link">Click Me</a>
+</body>
+</html>
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.js b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.js
new file mode 100644
index 0000000000..e68568ba86
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_speculative_connection.js
@@ -0,0 +1,84 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* 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";
+
+// Tests that with speculative connections enabled, connections to servers that
+// request a client authentication certificate succeed (the specific bug that
+// was addressed with this patch involved navigation hanging because the
+// connection to the server couldn't make progress without asking for a client
+// authentication certificate, but it also wouldn't ask for a client
+// authentication certificate until the connection had been claimed, which
+// required that it make progress first).
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+let chooseCertificateCalled = false;
+
+const clientAuthDialogService = {
+ chooseCertificate(hostname, certArray, loadContext, callback) {
+ is(
+ certArray.length,
+ 1,
+ "should have only one client certificate available"
+ );
+ ok(
+ !chooseCertificateCalled,
+ "chooseCertificate should only be called once"
+ );
+ chooseCertificateCalled = true;
+ callback.certificateChosen(certArray[0], false);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enable speculative connections.
+ ["network.http.speculative-parallel-limit", 6],
+ // Always ask to select a client authentication certificate.
+ ["security.default_personal_cert", "Ask Every Time"],
+ ],
+ });
+ let clientAuthDialogServiceCID = MockRegistrar.register(
+ "@mozilla.org/security/ClientAuthDialogService;1",
+ clientAuthDialogService
+ );
+ registerCleanupFunction(async function () {
+ MockRegistrar.unregister(clientAuthDialogServiceCID);
+ });
+});
+
+add_task(
+ async function test_no_client_auth_selection_dialog_for_speculative_connections() {
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}browser_clientAuth_speculative_connection.html`,
+ async browser => {
+ // Click the link to navigate to a page that requests a client
+ // authentication certificate. Necko will make a speculative
+ // connection, but unfortunately there's no event or notification to
+ // observe. This test ensures that the navigation succeeds and that a
+ // client authentication certificate was requested.
+ let loaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "https://requireclientcert.example.com/"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter("#link", {}, browser);
+ await loaded;
+ ok(chooseCertificateCalled, "chooseCertificate must have been called");
+ }
+ );
+ }
+);
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js
new file mode 100644
index 0000000000..9bf961250a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js
@@ -0,0 +1,161 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the client authentication certificate chooser correctly displays
+// provided information and correctly returns user input.
+
+const TEST_HOSTNAME = "Test Hostname";
+const TEST_ORG = "Test Org";
+const TEST_ISSUER_ORG = "Test Issuer Org";
+const TEST_PORT = 123;
+
+var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+/**
+ * Test certificate (i.e. build/pgo/certs/mochitest.client).
+ *
+ * @type {nsIX509Cert}
+ */
+var cert;
+
+/**
+ * Opens the client auth cert chooser dialog.
+ *
+ * @param {nsIX509Cert} cert The cert to pass to the dialog for display.
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading, with
+ * an array consisting of:
+ * 1. The window of the opened dialog.
+ * 2. The return value nsIWritablePropertyBag2 passed to the dialog.
+ */
+function openClientAuthDialog(cert) {
+ let certArray = [cert];
+ let retVals = { cert: undefined, rememberDecision: undefined };
+ let win = window.openDialog(
+ "chrome://pippki/content/clientauthask.xhtml",
+ "",
+ "",
+ { hostname: TEST_HOSTNAME, certArray, retVals }
+ );
+ return TestUtils.topicObserved("cert-dialog-loaded").then(() => {
+ return { win, retVals };
+ });
+}
+
+/**
+ * Checks that the contents of the given cert chooser dialog match the details
+ * of build/pgo/certs/mochitest.client.
+ *
+ * @param {window} win The cert chooser window.
+ * @param {string} notBefore
+ * The formatted notBefore date of mochitest.client.
+ * @param {string} notAfter
+ * The formatted notAfter date of mochitest.client.
+ */
+async function checkDialogContents(win, notBefore, notAfter) {
+ await TestUtils.waitForCondition(() => {
+ return win.document
+ .getElementById("clientAuthSiteIdentification")
+ .textContent.includes(`${TEST_HOSTNAME}`);
+ });
+ let nicknames = win.document.getElementById("nicknames");
+ await TestUtils.waitForCondition(() => {
+ return nicknames.label == "Mochitest client [03]";
+ });
+ await TestUtils.waitForCondition(() => {
+ return nicknames.itemCount == 1;
+ });
+ let subject = win.document.getElementById("clientAuthCertDetailsIssuedTo");
+ await TestUtils.waitForCondition(() => {
+ return subject.textContent == "Issued to: CN=Mochitest client";
+ });
+ let serialNum = win.document.getElementById(
+ "clientAuthCertDetailsSerialNumber"
+ );
+ await TestUtils.waitForCondition(() => {
+ return serialNum.textContent == "Serial number: 03";
+ });
+ let validity = win.document.getElementById(
+ "clientAuthCertDetailsValidityPeriod"
+ );
+ await TestUtils.waitForCondition(() => {
+ return validity.textContent == `Valid from ${notBefore} to ${notAfter}`;
+ });
+ let issuer = win.document.getElementById("clientAuthCertDetailsIssuedBy");
+ await TestUtils.waitForCondition(() => {
+ return (
+ issuer.textContent ==
+ "Issued by: OU=Profile Guided Optimization,O=Mozilla Testing,CN=Temporary Certificate Authority"
+ );
+ });
+ let tokenName = win.document.getElementById("clientAuthCertDetailsStoredOn");
+ await TestUtils.waitForCondition(() => {
+ return tokenName.textContent == "Stored on: Software Security Device";
+ });
+}
+
+function findCertByCommonName(commonName) {
+ for (let cert of certDB.getCerts()) {
+ if (cert.commonName == commonName) {
+ return cert;
+ }
+ }
+ return null;
+}
+
+add_setup(async function () {
+ cert = findCertByCommonName("Mochitest client");
+ isnot(cert, null, "Should be able to find the test client cert");
+});
+
+// Test that the contents of the dialog correspond to the details of the
+// provided cert.
+add_task(async function testContents() {
+ const formatter = new Intl.DateTimeFormat(undefined, {
+ dateStyle: "medium",
+ timeStyle: "long",
+ });
+ let { win } = await openClientAuthDialog(cert);
+ await checkDialogContents(
+ win,
+ formatter.format(new Date(cert.validity.notBefore / 1000)),
+ formatter.format(new Date(cert.validity.notAfter / 1000))
+ );
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(async function testAcceptDialogReturnValues() {
+ let { win, retVals } = await openClientAuthDialog(cert);
+ win.document.getElementById("rememberBox").checked = true;
+ info("Accepting dialog");
+ win.document.getElementById("certAuthAsk").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ is(retVals.cert, cert, "cert should be returned as chosen cert");
+ ok(
+ retVals.rememberDecision,
+ "Return value should signal 'Remember this decision' checkbox was checked"
+ );
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(async function testCancelDialogReturnValues() {
+ let { win, retVals } = await openClientAuthDialog(cert);
+ win.document.getElementById("rememberBox").checked = false;
+ info("Canceling dialog");
+ win.document.getElementById("certAuthAsk").cancelDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ ok(
+ !retVals.cert,
+ "Return value should signal user did not choose a certificate"
+ );
+ ok(
+ !retVals.rememberDecision,
+ "Return value should signal 'Remember this decision' checkbox was unchecked"
+ );
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js b/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
new file mode 100644
index 0000000000..a8ff7cc8fb
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
@@ -0,0 +1,259 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests various aspects of the cert delete confirmation dialog.
+// Among other things, tests that for each type of cert that can be deleted:
+// 1. The various lines of explanation text are correctly set.
+// 2. The implementation correctly falls back through multiple cert attributes
+// to determine what to display to represent a cert.
+
+/**
+ * An array of tree items corresponding to TEST_CASES.
+ *
+ * @type {nsICertTreeItem[]}
+ */
+var gCertArray = [];
+
+const FAKE_HOST_PORT = "Fake host and port";
+
+/**
+ * @typedef TestCase
+ * @type {object}
+ * @property {string} certFilename
+ * Filename of the cert, or null if we don't want to import a cert for
+ * this test case (i.e. we expect the hostPort attribute of
+ * nsICertTreeItem to be used).
+ * @property {string} expectedDisplayString
+ * The string we expect the UI to display to represent the given cert.
+ * @property {string} expectedSerialNumber
+ * The serial number we expect the UI to display if it exists.
+ */
+
+/**
+ * A list of test cases representing certs that get "deleted".
+ *
+ * @type {TestCase[]}
+ */
+const TEST_CASES = [
+ {
+ certFilename: null,
+ expectedDisplayString: FAKE_HOST_PORT,
+ expectedSerialNumber: null,
+ },
+ {
+ certFilename: "has-cn.pem",
+ expectedDisplayString: "Foo",
+ expectedSerialNumber: null,
+ },
+ {
+ certFilename: "has-ou.pem",
+ expectedDisplayString: "Bar",
+ expectedSerialNumber: null,
+ },
+ {
+ certFilename: "has-o.pem",
+ expectedDisplayString: "Baz",
+ expectedSerialNumber: null,
+ },
+ {
+ certFilename: "has-non-empty-subject.pem",
+ expectedDisplayString: "C=US",
+ expectedSerialNumber: null,
+ },
+ {
+ certFilename: "has-empty-subject.pem",
+ expectedDisplayString: "Certificate with serial number: 0A",
+ expectedSerialNumber: "0A",
+ },
+];
+
+/**
+ * Opens the cert delete confirmation dialog.
+ *
+ * @param {string} tabID
+ * The ID of the cert category tab the certs to delete belong to.
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading, with
+ * an array consisting of:
+ * 1. The window of the opened dialog.
+ * 2. The return value object passed to the dialog.
+ */
+function openDeleteCertConfirmDialog(tabID) {
+ let retVals = {
+ deleteConfirmed: false,
+ };
+ let win = window.openDialog(
+ "chrome://pippki/content/deletecert.xhtml",
+ "",
+ "",
+ tabID,
+ gCertArray,
+ retVals
+ );
+ return new Promise((resolve, reject) => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve([win, retVals]));
+ },
+ { once: true }
+ );
+ });
+}
+
+add_setup(async function () {
+ for (let testCase of TEST_CASES) {
+ let cert = null;
+ if (testCase.certFilename) {
+ cert = await readCertificate(testCase.certFilename, ",,");
+ }
+ let certTreeItem = {
+ hostPort: FAKE_HOST_PORT,
+ cert,
+ QueryInterface: ChromeUtils.generateQI(["nsICertTreeItem"]),
+ };
+ gCertArray.push(certTreeItem);
+ }
+});
+
+/**
+ * Test helper for the below test cases.
+ *
+ * @param {string} tabID
+ * ID of the cert category tab the certs to delete belong to.
+ * @param {string} expectedTitleL10nId
+ * The L10nId of title the dialog is expected to have.
+ * @param {string} expectedConfirmL10nId
+ * The l10n id of confirmation message the dialog expected to show.
+ * @param {string} expectedImpactL10nId
+ * The l10n id of impact the dialog expected to show.
+ */
+async function testHelper(
+ tabID,
+ expectedTitleL10nId,
+ expectedConfirmL10nId,
+ expectedImpactL10nId
+) {
+ let [win] = await openDeleteCertConfirmDialog(tabID);
+ let certList = win.document.getElementById("certlist");
+
+ Assert.deepEqual(
+ win.document.l10n.getAttributes(win.document.documentElement),
+ expectedTitleL10nId,
+ `Actual and expected titles should match for ${tabID}`
+ );
+ let confirm = win.document.getElementById("confirm");
+ Assert.deepEqual(
+ win.document.l10n.getAttributes(confirm),
+ expectedConfirmL10nId,
+ `Actual and expected confirm message should match for ${tabID}`
+ );
+ let impact = win.document.getElementById("impact");
+ Assert.deepEqual(
+ win.document.l10n.getAttributes(impact),
+ expectedImpactL10nId,
+ `Actual and expected impact should match for ${tabID}`
+ );
+
+ Assert.equal(
+ certList.itemCount,
+ TEST_CASES.length,
+ `No. of certs displayed should match for ${tabID}`
+ );
+ for (let i = 0; i < certList.itemCount; i++) {
+ let item = certList.getItemAtIndex(i);
+ if (TEST_CASES[i].expectedSerialNumber == null) {
+ Assert.equal(
+ item.label,
+ TEST_CASES[i].expectedDisplayString,
+ "Actual and expected display string should match for " +
+ `index ${i} for ${tabID}`
+ );
+ } else {
+ Assert.deepEqual(
+ win.document.l10n.getAttributes(item.children[0]),
+ {
+ id: "cert-with-serial",
+ args: { serialNumber: TEST_CASES[i].expectedSerialNumber },
+ },
+ "Actual and expected display string should match for " +
+ `index ${i} for ${tabID}`
+ );
+ }
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+}
+
+// Test deleting certs from the "Your Certificates" tab.
+add_task(async function testDeletePersonalCerts() {
+ const expectedTitleL10nId = { id: "delete-user-cert-title", args: null };
+ const expectedConfirmL10nId = { id: "delete-user-cert-confirm", args: null };
+ const expectedImpactL10nId = { id: "delete-user-cert-impact", args: null };
+ await testHelper(
+ "mine_tab",
+ expectedTitleL10nId,
+ expectedConfirmL10nId,
+ expectedImpactL10nId
+ );
+});
+
+// Test deleting certs from the "People" tab.
+add_task(async function testDeleteOtherPeopleCerts() {
+ const expectedTitleL10nId = { id: "delete-email-cert-title", args: null };
+ // ’ doesn't seem to work when embedded in the following literals, which is
+ // why escape codes are used instead.
+ const expectedConfirmL10nId = { id: "delete-email-cert-confirm", args: null };
+ const expectedImpactL10nId = { id: "delete-email-cert-impact", args: null };
+ await testHelper(
+ "others_tab",
+ expectedTitleL10nId,
+ expectedConfirmL10nId,
+ expectedImpactL10nId
+ );
+});
+
+// Test deleting certs from the "Authorities" tab.
+add_task(async function testDeleteCACerts() {
+ const expectedTitleL10nId = { id: "delete-ca-cert-title", args: null };
+ const expectedConfirmL10nId = { id: "delete-ca-cert-confirm", args: null };
+ const expectedImpactL10nId = { id: "delete-ca-cert-impact", args: null };
+ await testHelper(
+ "ca_tab",
+ expectedTitleL10nId,
+ expectedConfirmL10nId,
+ expectedImpactL10nId
+ );
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(async function testAcceptDialogReturnValues() {
+ let [win, retVals] = await openDeleteCertConfirmDialog(
+ "ca_tab" /* arbitrary */
+ );
+ info("Accepting dialog");
+ win.document.getElementById("deleteCertificate").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ retVals.deleteConfirmed,
+ "Return value should signal user accepted"
+ );
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(async function testCancelDialogReturnValues() {
+ let [win, retVals] = await openDeleteCertConfirmDialog(
+ "ca_tab" /* arbitrary */
+ );
+ info("Canceling dialog");
+ win.document.getElementById("deleteCertificate").cancelDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ !retVals.deleteConfirmed,
+ "Return value should signal user did not accept"
+ );
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_downloadCert_ui.js b/security/manager/ssl/tests/mochitest/browser/browser_downloadCert_ui.js
new file mode 100644
index 0000000000..51715b1352
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_downloadCert_ui.js
@@ -0,0 +1,134 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the cert download/import UI correctly identifies the cert being
+// downloaded, and allows the trust of the cert to be specified.
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+/**
+ * @typedef TestCase
+ * @type {object}
+ * @property {string} certFilename
+ * Filename of the cert for this test case.
+ * @property {string} expectedDisplayString
+ * The string we expect the UI to display to represent the given cert.
+ * @property {nsIX509Cert} cert
+ * Handle to the cert once read in setup().
+ */
+
+/**
+ * A list of test cases representing certs that get "downloaded".
+ *
+ * @type {TestCase[]}
+ */
+const TEST_CASES = [
+ { certFilename: "has-cn.pem", expectedDisplayString: "Foo", cert: null },
+ {
+ certFilename: "has-empty-subject.pem",
+ expectedDisplayString: "Certificate Authority (unnamed)",
+ cert: null,
+ },
+];
+
+/**
+ * Opens the cert download dialog.
+ *
+ * @param {nsIX509Cert} cert
+ * The cert to pass to the dialog for display.
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading, with
+ * an array consisting of:
+ * 1. The window of the opened dialog.
+ * 2. The return value nsIWritablePropertyBag2 passed to the dialog.
+ */
+function openCertDownloadDialog(cert) {
+ let returnVals = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ let win = window.openDialog(
+ "chrome://pippki/content/downloadcert.xhtml",
+ "",
+ "",
+ cert,
+ returnVals
+ );
+ return new Promise((resolve, reject) => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve([win, returnVals]));
+ },
+ { once: true }
+ );
+ });
+}
+
+add_setup(async function () {
+ for (let testCase of TEST_CASES) {
+ testCase.cert = await readCertificate(testCase.certFilename, ",,");
+ Assert.notEqual(
+ testCase.cert,
+ null,
+ `'${testCase.certFilename}' should have been read`
+ );
+ }
+});
+
+// Test that the trust header message corresponds to the provided cert, and that
+// the View Cert button launches the cert viewer for the provided cert.
+add_task(async function testTrustHeaderAndViewCertButton() {
+ for (let testCase of TEST_CASES) {
+ let [win] = await openCertDownloadDialog(testCase.cert);
+ let expectedTrustHeaderString =
+ `Do you want to trust \u201C${testCase.expectedDisplayString}\u201D ` +
+ "for the following purposes?";
+ Assert.equal(
+ win.document.getElementById("trustHeader").textContent,
+ expectedTrustHeaderString,
+ "Actual and expected trust header text should match for " +
+ `${testCase.certFilename}`
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(async function testAcceptDialogReturnValues() {
+ let [win, retVals] = await openCertDownloadDialog(TEST_CASES[0].cert);
+ win.document.getElementById("trustSSL").checked = true;
+ win.document.getElementById("trustEmail").checked = false;
+ info("Accepting dialog");
+ win.document.getElementById("download_cert").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ retVals.get("importConfirmed"),
+ "Return value should signal user chose to import the cert"
+ );
+ Assert.ok(
+ retVals.get("trustForSSL"),
+ "Return value should signal SSL trust checkbox was checked"
+ );
+ Assert.ok(
+ !retVals.get("trustForEmail"),
+ "Return value should signal E-mail trust checkbox was unchecked"
+ );
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(async function testCancelDialogReturnValues() {
+ let [win, retVals] = await openCertDownloadDialog(TEST_CASES[0].cert);
+ info("Canceling dialog");
+ win.document.getElementById("download_cert").cancelDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ !retVals.get("importConfirmed"),
+ "Return value should signal user chose not to import the cert"
+ );
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js b/security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js
new file mode 100644
index 0000000000..9a36eca7bf
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js
@@ -0,0 +1,141 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the UI for editing the trust of a CA certificate correctly
+// reflects trust in the cert DB, and correctly updates trust in the cert DB
+// when requested.
+
+var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+/**
+ * The cert we're editing the trust of.
+ *
+ * @type {nsIX509Cert}
+ */
+var gCert;
+
+/**
+ * Opens the cert trust editing dialog.
+ *
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading with
+ * the window of the opened dialog.
+ */
+function openEditCertTrustDialog() {
+ let win = window.openDialog(
+ "chrome://pippki/content/editcacert.xhtml",
+ "",
+ "",
+ gCert
+ );
+ return new Promise((resolve, reject) => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve(win));
+ },
+ { once: true }
+ );
+ });
+}
+
+add_setup(async function () {
+ // Initially trust ca.pem for SSL but not e-mail.
+ gCert = await readCertificate("ca.pem", "CT,,");
+ Assert.ok(
+ gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ ),
+ "Sanity check: ca.pem should be trusted for SSL"
+ );
+ Assert.ok(
+ !gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_EMAIL
+ ),
+ "Sanity check: ca.pem should not be trusted for e-mail"
+ );
+});
+
+// Tests the following:
+// 1. The checkboxes correctly reflect the trust set in setup().
+// 2. Accepting the dialog after flipping some of the checkboxes results in the
+// correct trust being set in the cert DB.
+add_task(async function testAcceptDialog() {
+ let win = await openEditCertTrustDialog();
+
+ let sslCheckbox = win.document.getElementById("trustSSL");
+ let emailCheckbox = win.document.getElementById("trustEmail");
+ Assert.ok(sslCheckbox.checked, "Cert should be trusted for SSL in UI");
+ Assert.ok(
+ !emailCheckbox.checked,
+ "Cert should not be trusted for e-mail in UI"
+ );
+
+ sslCheckbox.checked = false;
+ emailCheckbox.checked = true;
+
+ info("Accepting dialog");
+ win.document.getElementById("editCaCert").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ !gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ ),
+ "Cert should no longer be trusted for SSL"
+ );
+ Assert.ok(
+ gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_EMAIL
+ ),
+ "Cert should now be trusted for e-mail"
+ );
+});
+
+// Tests the following:
+// 1. The checkboxes correctly reflect the trust set in testAcceptDialog().
+// 2. Canceling the dialog even after flipping the checkboxes doesn't result in
+// a change of trust in the cert DB.
+add_task(async function testCancelDialog() {
+ let win = await openEditCertTrustDialog();
+
+ let sslCheckbox = win.document.getElementById("trustSSL");
+ let emailCheckbox = win.document.getElementById("trustEmail");
+ Assert.ok(!sslCheckbox.checked, "Cert should not be trusted for SSL in UI");
+ Assert.ok(emailCheckbox.checked, "Cert should be trusted for e-mail in UI");
+
+ sslCheckbox.checked = true;
+ emailCheckbox.checked = false;
+
+ info("Canceling dialog");
+ win.document.getElementById("editCaCert").cancelDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ !gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ ),
+ "Cert should still not be trusted for SSL"
+ );
+ Assert.ok(
+ gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_EMAIL
+ ),
+ "Cert should still be trusted for e-mail"
+ );
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_exportP12_passwordUI.js b/security/manager/ssl/tests/mochitest/browser/browser_exportP12_passwordUI.js
new file mode 100644
index 0000000000..8e6af27cbb
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_exportP12_passwordUI.js
@@ -0,0 +1,164 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the UI for setting the password on a to be exported PKCS #12 file:
+// 1. Correctly requires the password to be typed in twice as confirmation.
+// 2. Calculates and displays the strength of said password.
+
+/**
+ * @typedef TestCase
+ * @type {object}
+ * @property {string} name
+ * The name of the test case for display purposes.
+ * @property {string} password1
+ * The password to enter into the first password textbox.
+ * @property {string} password2
+ * The password to enter into the second password textbox.
+ * @property {string} strength
+ * The expected strength of the password in the range [0, 100].
+ */
+
+/**
+ * A list of test cases representing various inputs to the password textboxes.
+ *
+ * @type {TestCase[]}
+ */
+const TEST_CASES = [
+ { name: "empty", password1: "", password2: "", strength: "0" },
+ { name: "match-weak", password1: "foo", password2: "foo", strength: "10" },
+ {
+ name: "match-medium",
+ password1: "foo123",
+ password2: "foo123",
+ strength: "60",
+ },
+ {
+ name: "match-strong",
+ password1: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+ password2: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+ strength: "100",
+ },
+ { name: "mismatch-weak", password1: "foo", password2: "bar", strength: "10" },
+ {
+ name: "mismatch-medium",
+ password1: "foo123",
+ password2: "bar",
+ strength: "60",
+ },
+ {
+ name: "mismatch-strong",
+ password1: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+ password2: "bar",
+ strength: "100",
+ },
+];
+
+/**
+ * Opens the dialog shown to set the password on a PKCS #12 file being exported.
+ *
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading, with
+ * an array consisting of:
+ * 1. The window of the opened dialog.
+ * 2. The return value nsIWritablePropertyBag2 passed to the dialog.
+ */
+function openSetP12PasswordDialog() {
+ let returnVals = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ let win = window.openDialog(
+ "chrome://pippki/content/setp12password.xhtml",
+ "",
+ "",
+ returnVals
+ );
+ return new Promise((resolve, reject) => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve([win, returnVals]));
+ },
+ { once: true }
+ );
+ });
+}
+
+// Tests that the first password textbox is the element that is initially
+// focused.
+add_task(async function testFocus() {
+ let [win] = await openSetP12PasswordDialog();
+ Assert.equal(
+ win.document.activeElement,
+ win.document.getElementById("pw1"),
+ "First password textbox should have focus"
+ );
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Tests that the password strength algorithm used is reasonable, and that the
+// Accept button is only enabled if the two passwords match.
+add_task(async function testPasswordStrengthAndEquality() {
+ let [win] = await openSetP12PasswordDialog();
+ let password1Textbox = win.document.getElementById("pw1");
+ let password2Textbox = win.document.getElementById("pw2");
+ let strengthProgressBar = win.document.getElementById("pwmeter");
+
+ for (let testCase of TEST_CASES) {
+ password1Textbox.value = testCase.password1;
+ password2Textbox.value = testCase.password2;
+ // Setting the value of the password textboxes via |.value| apparently
+ // doesn't cause the oninput handlers to be called, so we do it here.
+ password1Textbox.oninput();
+ password2Textbox.oninput();
+
+ Assert.equal(
+ win.document.getElementById("setp12password").getButton("accept")
+ .disabled,
+ password1Textbox.value != password2Textbox.value,
+ "Actual and expected accept button disable state should " +
+ `match for ${testCase.name}`
+ );
+ Assert.equal(
+ strengthProgressBar.value,
+ testCase.strength,
+ `Actual and expected strength value should match for ${testCase.name}`
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(async function testAcceptDialogReturnValues() {
+ let [win, retVals] = await openSetP12PasswordDialog();
+ const password = "fooBAR 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三";
+ win.document.getElementById("pw1").value = password;
+ win.document.getElementById("pw2").value = password;
+ info("Accepting dialog");
+ win.document.getElementById("setp12password").acceptDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ retVals.get("confirmedPassword"),
+ "Return value should signal user confirmed a password"
+ );
+ Assert.equal(
+ retVals.get("password"),
+ password,
+ "Actual and expected password should match"
+ );
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(async function testCancelDialogReturnValues() {
+ let [win, retVals] = await openSetP12PasswordDialog();
+ info("Canceling dialog");
+ win.document.getElementById("setp12password").cancelDialog();
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.ok(
+ !retVals.get("confirmedPassword"),
+ "Return value should signal user didn't confirm a password"
+ );
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/browser_loadPKCS11Module_ui.js b/security/manager/ssl/tests/mochitest/browser/browser_loadPKCS11Module_ui.js
new file mode 100644
index 0000000000..9e4e244123
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_loadPKCS11Module_ui.js
@@ -0,0 +1,312 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests the dialog used for loading PKCS #11 modules.
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const gMockPKCS11ModuleDB = {
+ addModuleCallCount: 0,
+ expectedLibPath: "",
+ expectedModuleName: "",
+ throwOnAddModule: false,
+
+ addModule(moduleName, libraryFullPath, cryptoMechanismFlags, cipherFlags) {
+ this.addModuleCallCount++;
+ Assert.equal(
+ moduleName,
+ this.expectedModuleName,
+ "addModule: Name given should be what's in the name textbox"
+ );
+ Assert.equal(
+ libraryFullPath,
+ this.expectedLibPath,
+ "addModule: Path given should be what's in the path textbox"
+ );
+ Assert.equal(
+ cryptoMechanismFlags,
+ 0,
+ "addModule: No crypto mechanism flags should be passed"
+ );
+ Assert.equal(cipherFlags, 0, "addModule: No cipher flags should be passed");
+
+ if (this.throwOnAddModule) {
+ throw new Error(`addModule: Throwing exception`);
+ }
+ },
+
+ deleteModule(moduleName) {
+ Assert.ok(false, `deleteModule: should not be called`);
+ },
+
+ getInternal() {
+ throw new Error("not expecting getInternal() to be called");
+ },
+
+ getInternalFIPS() {
+ throw new Error("not expecting getInternalFIPS() to be called");
+ },
+
+ listModules() {
+ throw new Error("not expecting listModules() to be called");
+ },
+
+ get canToggleFIPS() {
+ throw new Error("not expecting get canToggleFIPS() to be called");
+ },
+
+ toggleFIPSMode() {
+ throw new Error("not expecting toggleFIPSMode() to be called");
+ },
+
+ get isFIPSEnabled() {
+ throw new Error("not expecting get isFIPSEnabled() to be called");
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIPKCS11ModuleDB"]),
+};
+
+const gMockPromptService = {
+ alertCallCount: 0,
+ expectedText: "",
+ expectedWindow: null,
+
+ alert(parent, dialogTitle, text) {
+ this.alertCallCount++;
+ Assert.equal(
+ parent,
+ this.expectedWindow,
+ "alert: Parent should be expected window"
+ );
+ Assert.equal(dialogTitle, null, "alert: Title should be null");
+ Assert.equal(
+ text,
+ this.expectedText,
+ "alert: Actual and expected text should match"
+ );
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+};
+
+var gMockPKCS11CID = MockRegistrar.register(
+ "@mozilla.org/security/pkcs11moduledb;1",
+ gMockPKCS11ModuleDB
+);
+var gMockPromptServiceCID = MockRegistrar.register(
+ "@mozilla.org/prompter;1",
+ gMockPromptService
+);
+
+var gMockFilePicker = SpecialPowers.MockFilePicker;
+gMockFilePicker.init(window);
+
+var gTempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+gTempFile.append("browser_loadPKCS11Module_ui-fakeModule");
+
+registerCleanupFunction(() => {
+ gMockFilePicker.cleanup();
+ MockRegistrar.unregister(gMockPKCS11CID);
+ MockRegistrar.unregister(gMockPromptServiceCID);
+});
+
+function resetCallCounts() {
+ gMockPKCS11ModuleDB.addModuleCallCount = 0;
+ gMockPromptService.alertCallCount = 0;
+}
+
+/**
+ * Opens the dialog shown to load a PKCS #11 module.
+ *
+ * @returns {Promise}
+ * A promise that resolves when the dialog has finished loading, with
+ * the window of the opened dialog.
+ */
+function openLoadModuleDialog() {
+ let win = window.openDialog(
+ "chrome://pippki/content/load_device.xhtml",
+ "",
+ ""
+ );
+ return new Promise(resolve => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve(win));
+ },
+ { once: true }
+ );
+ });
+}
+
+/**
+ * Presses the browse button and simulates interacting with the file picker that
+ * should be triggered.
+ *
+ * @param {window} win
+ * The dialog window.
+ * @param {boolean} cancel
+ * If true, the file picker is canceled. If false, gTempFile is chosen in
+ * the file picker and the file picker is accepted.
+ */
+async function browseToTempFile(win, cancel) {
+ gMockFilePicker.showCallback = () => {
+ gMockFilePicker.setFiles([gTempFile]);
+
+ if (cancel) {
+ info("MockFilePicker returning cancel");
+ return Ci.nsIFilePicker.returnCancel;
+ }
+
+ info("MockFilePicker returning OK");
+ return Ci.nsIFilePicker.returnOK;
+ };
+
+ info("Pressing browse button");
+ win.document.getElementById("browse").doCommand();
+ await TestUtils.topicObserved("LoadPKCS11Module:FilePickHandled");
+}
+
+add_task(async function testBrowseButton() {
+ let win = await openLoadModuleDialog();
+ let pathBox = win.document.getElementById("device_path");
+ let originalPathBoxValue = "expected path if picker is canceled";
+ pathBox.value = originalPathBoxValue;
+
+ // Test what happens if the file picker is canceled.
+ await browseToTempFile(win, true);
+ Assert.equal(
+ pathBox.value,
+ originalPathBoxValue,
+ "Path shown should be unchanged due to canceled picker"
+ );
+
+ // Test what happens if the file picker is not canceled.
+ await browseToTempFile(win, false);
+ Assert.equal(
+ pathBox.value,
+ gTempFile.path,
+ "Path shown should be same as the one chosen in the file picker"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+function testAddModuleHelper(win, throwOnAddModule) {
+ resetCallCounts();
+ gMockPKCS11ModuleDB.expectedLibPath = gTempFile.path;
+ gMockPKCS11ModuleDB.expectedModuleName = "test module";
+ gMockPKCS11ModuleDB.throwOnAddModule = throwOnAddModule;
+
+ win.document.getElementById("device_name").value =
+ gMockPKCS11ModuleDB.expectedModuleName;
+ win.document.getElementById("device_path").value =
+ gMockPKCS11ModuleDB.expectedLibPath;
+
+ info("Accepting dialog");
+ win.document.getElementById("loaddevice").acceptDialog();
+}
+
+add_task(async function testAddModuleSuccess() {
+ let win = await openLoadModuleDialog();
+
+ testAddModuleHelper(win, false);
+ await BrowserTestUtils.windowClosed(win);
+
+ Assert.equal(
+ gMockPKCS11ModuleDB.addModuleCallCount,
+ 1,
+ "addModule() should have been called once"
+ );
+ Assert.equal(
+ gMockPromptService.alertCallCount,
+ 0,
+ "alert() should never have been called"
+ );
+});
+
+add_task(async function testAddModuleFailure() {
+ let win = await openLoadModuleDialog();
+ gMockPromptService.expectedText = "Unable to add module";
+ gMockPromptService.expectedWindow = win;
+
+ // The exception we throw in addModule is first reported as an uncaught
+ // exception by XPConnect before an exception is propagated to the actual
+ // caller.
+ expectUncaughtException(true);
+
+ testAddModuleHelper(win, true);
+ expectUncaughtException(false);
+ // If adding a module fails, the dialog will not close. As such, we have to
+ // close the window ourselves.
+ await BrowserTestUtils.closeWindow(win);
+
+ Assert.equal(
+ gMockPKCS11ModuleDB.addModuleCallCount,
+ 1,
+ "addModule() should have been called once"
+ );
+ Assert.equal(
+ gMockPromptService.alertCallCount,
+ 1,
+ "alert() should have been called once"
+ );
+});
+
+add_task(async function testCancel() {
+ let win = await openLoadModuleDialog();
+ resetCallCounts();
+
+ info("Canceling dialog");
+ win.document.getElementById("loaddevice").cancelDialog();
+
+ Assert.equal(
+ gMockPKCS11ModuleDB.addModuleCallCount,
+ 0,
+ "addModule() should never have been called"
+ );
+ Assert.equal(
+ gMockPromptService.alertCallCount,
+ 0,
+ "alert() should never have been called"
+ );
+
+ await BrowserTestUtils.windowClosed(win);
+});
+
+async function testModuleNameHelper(moduleName, acceptButtonShouldBeDisabled) {
+ let win = await openLoadModuleDialog();
+ resetCallCounts();
+
+ info(`Setting Module Name to '${moduleName}'`);
+ let moduleNameBox = win.document.getElementById("device_name");
+ moduleNameBox.value = moduleName;
+ // this makes this not a great test, but it's the easiest way to simulate this
+ moduleNameBox.onchange();
+
+ let dialogNode = win.document.querySelector("dialog");
+ Assert.equal(
+ dialogNode.getAttribute("buttondisabledaccept"),
+ acceptButtonShouldBeDisabled ? "true" : "", // it's a string
+ `dialog accept button should ${
+ acceptButtonShouldBeDisabled ? "" : "not "
+ }be disabled`
+ );
+
+ return BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function testEmptyModuleName() {
+ await testModuleNameHelper("", true);
+});
+
+add_task(async function testReservedModuleName() {
+ await testModuleNameHelper("Root Certs", true);
+});
+
+add_task(async function testAcceptableModuleName() {
+ await testModuleNameHelper("Some Module Name", false);
+});
diff --git a/security/manager/ssl/tests/mochitest/browser/ca.pem b/security/manager/ssl/tests/mochitest/browser/ca.pem
new file mode 100644
index 0000000000..90b269209e
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyTCCAbGgAwIBAgIUCytXeIVSOQ622rYL1uaLSms7TrcwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowDTELMAkGA1UEAwwCY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYD
+VR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBEpBaH+earFBTSrKZUsUmxH5q1
+9Ln/OCzi1hB5IHo3haTTKl8xrTe5sI4A7knfwbz9AwbLRW0L3zIAJGPjxhMDxYjn
+t5YTQLQwZEbru2A9wCOELiDbXH1kJl0yI2JdGwGMwZ4Y7ifTG5EUEQeVFnDTc2xA
+4W/RZBld/6Iqb2ECMc20tjvBSo9YCJ7OEz+gva4OBx+BtK7LHRVLEMBGYet64wi4
+5Y8cdzMwsV69tlLffrwLV32TCt1a4dNLmq9g/vgaONx1B9ltxq8fc8ErzYvYTLsh
+0FY0VD/EabvGDnLuIHfTnuD5bbKhRFD8vOEoW+NKEVn3JveM8z6z0LQqt8CB
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/ca.pem.certspec b/security/manager/ssl/tests/mochitest/browser/ca.pem.certspec
new file mode 100644
index 0000000000..6660f5d478
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:ca
+extension:basicConstraints:cA,
+extension:keyUsage:cRLSign,keyCertSign
diff --git a/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem b/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem
new file mode 100644
index 0000000000..0639b2a7b0
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDETCCAfmgAwIBAgIUazXMVwZmjxSa95+Jhrdt0+mMZ3AwDQYJKoZIhvcNAQEL
+BQAwQTEoMCYGA1UEAwwfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEV
+MBMGA1UECwwMSW50ZXJtZWRpYXRlMCIYDzIwMjIxMTI3MDAwMDAwWhgPMjAyNTAy
+MDQwMDAwMDBaMCcxJTAjBgNVBAMMHGNsaWVudCBjZXJ0IHZpYSBpbnRlcm1lZGlh
+dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braI
+BjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVa
+p0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB
+7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4C
+kC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJv
+aeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgK
+Ne2NAgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUA
+A4IBAQAE8C6ApsLBOltZNrOLFWPb+hKGKJrbUDuvLh8BIXF8GhLz261zEj3IgZMI
+yRgVuEmAcjgkqSnuARq8zqGkr2mFT6g2GXix4QrBAuN8kitOki/Ds7yrTsRzk/iO
+AzJLa2Uvqa0Ai2cs7XepIAv114sSAIp1kP1+e0R1xi7smoDLFJmzisc7XhFmH4qI
+z37aeqU8QdaxJnWF08X+S0Gk5m7qC1ueWgcHEWDq5xenqQYW6IhrEhHEwNLzxs3V
+Q+YXIb8TXTNMfcbYr7j2MicoUD+emYGW+Tb/sB4xq1aH3QocJP/6kwpE6iqhjjr9
+HMJwx81SgJXoGs63k+Tf2ih4OPvG
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem.certspec b/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem.certspec
new file mode 100644
index 0000000000..cab2448889
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem.certspec
@@ -0,0 +1,3 @@
+issuer:/CN=Temporary Certificate Authority/OU=Intermediate
+subject:client cert via intermediate
+extension:extKeyUsage:clientAuth
diff --git a/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem b/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem
new file mode 100644
index 0000000000..b3321ade96
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjCgAwIBAgIUESnNHJJUmr0N5OMLVtpjwidqEu4wDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAwMDAw
+MFowKzEpMCcGA1UEAwwgY2xpZW50IGNlcnQgd2l0aCBPQ1NQU2lnbmluZyBla3Uw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQ
+PTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH
+9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw
+4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86
+exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0
+ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2N
+AgMBAAGjITAfMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDCTANBgkqhkiG
+9w0BAQsFAAOCAQEAQsoHHdWUfZ+DVMAsLGqRs7wUuK+JUT1ZGBUejm/jHUu53n44
+bd9dROahgeXvTAHvwZqvFNL3qYjJPhbtIYzNYa/OQwQCC9dPv9pPE5npRf64m4qc
+sMO1rWkSAnljIfJv+NPugtzfJPTNQ6nimx1CEijKxyv3/5hy2pYMAzMMDMufynID
+PlU8QXp6kHq+xYBggX54iHdAyObvD4O6YrFAOo/xXN6iqH4pNimE6m/+gPbWTerf
+YCHAWXYfZ4Mq3AnE+Dzkl1XxMCrmS9LFguWSV1Zz8YbzAWgiZ4M5qxP7eaA0hPSY
+bNEGLMr+tb3vn7AHGA9LySBZnZG2ZrMOgjdTnA==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem.certspec b/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem.certspec
new file mode 100644
index 0000000000..5cbd5af8f0
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem.certspec
@@ -0,0 +1,3 @@
+issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+subject:client cert with OCSPSigning eku
+extension:extKeyUsage:clientAuth,OCSPSigning
diff --git a/security/manager/ssl/tests/mochitest/browser/code-ee.pem b/security/manager/ssl/tests/mochitest/browser/code-ee.pem
new file mode 100644
index 0000000000..00d89a7880
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/code-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyDCCAbCgAwIBAgIUcSZpO8heK76gtjUinR9ZHfSXvHIwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowEjEQMA4GA1UEAwwHY29kZS1lZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMXMBUwEwYDVR0lBAwwCgYI
+KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAGJiZ7XrXvVd8x8x+Aq9uPgDAvLU
+MBJ2KTGGRcsu3RtTIy4856Ro7dgjuCyX38nX3AqI+geKWCerXe2sbjZ+NVC29Ppx
+BvQwq80s6wy0dSReOr/8hFDHFCqJ/jTHCafNFhX77Db+Tt+lWlkf/tGRiO4cqE/r
+6ejfJM7ZgNAdXHtY0v2H3DkSa19DUcY+kW45gYfnKkSrwAZFGbF//rb4uJy2i01q
+8fvimkpKSIwM6hL6nZdAwzO37xetaH7AhGbjtK9YTiXISfH34zQVjqMH1xddSCU/
+2LBeTxIBj7Pqt2n3diM7cL02Ip3scABoIDdIJkL6I/QcGDg6mUUVBv7cypQ=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/code-ee.pem.certspec b/security/manager/ssl/tests/mochitest/browser/code-ee.pem.certspec
new file mode 100644
index 0000000000..93f9a84265
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/code-ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:code-ee
+extension:extKeyUsage:codeSigning
diff --git a/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem b/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem
new file mode 100644
index 0000000000..18d4717a55
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICwjCCAaqgAwIBAgIUDwpGZ9TkQ1znfituEv+3wAUjlzcwDQYJKoZIhvcNAQEL
+BQAwFTETMBEGA1UEAwwKZXhwaXJlZC1jYTAiGA8yMDIyMTEyNzAwMDAwMFoYDzIw
+MjUwMjA0MDAwMDAwWjAdMRswGQYDVQQDDBJlZS1mcm9tLWV4cGlyZWQtY2EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT
+2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzV
+JJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8N
+jf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCA
+BiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVh
+He4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMB
+AAEwDQYJKoZIhvcNAQELBQADggEBAE6mku7tzWcfYfAA+8fQAPY5P84NmIXa9aRm
+nrcVE0i8w01KmCVA+1PvEosius/Ub6wiaCaze/WUNZig682wCWlbWS4fe+YPyaay
+9UqRNSrLfs8PtKa7iMXvrdU22RuM2XVAYysS/gqYCBxbeCzHDUeCB/08Re41XMOt
+5Vk0McSwOaZ5XELSWlBeFnSGSyYXKTSKaXtPz1hmRdF7oeAMj8oJb6VCRFTDCZSf
+eJN9n4s/TQa5qawlmxiwZIYI8SEir3hhQXF1G/Xf9DQf4EBpm6J5b23SJAUUfDKF
+YYr2uDbkzXOiALGvDjJ2HIbNAPbxhJwNqG1gheHcTpLbhmN6KGc=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem.certspec b/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem.certspec
new file mode 100644
index 0000000000..3e280fc4fc
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem.certspec
@@ -0,0 +1,2 @@
+issuer:expired-ca
+subject:ee-from-expired-ca
diff --git a/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem b/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem
new file mode 100644
index 0000000000..3a029db49e
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAa6gAwIBAgIUJajqZUcpJWGn9b/Eqqf0KVSXdqMwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMdW50cnVzdGVkLWNhMCIYDzIwMjIxMTI3MDAwMDAwWhgP
+MjAyNTAyMDQwMDAwMDBaMB8xHTAbBgNVBAMMFGVlLWZyb20tdW50cnVzdGVkLWNh
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2
+ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF
+h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n
+cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv
+OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj
+tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt
+jQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCEH6bV29DmFXA+Vg1pRbhKaxJ6DosL
+puDtx8eWeLz1LZnhDxg7WoxSzg8hSINoht+KeE3mEM1wDlj+CHUaHlHpxXA2jnw0
+qTMOw8/uuR9GegtjP8fxWgDRPE8FdObtqgTwgz3PcFGHHDIbdmBjzldjp+mr9rLA
+jLGUaMf2xLHHbIb9tCo06CGKcXs38sxnJLWC1XDe3RK36JD/Ba/6MvjEg7VM9a3T
+uQsKNRj64yy+/fTgJ/1VKlXmVHYdwWAYTs/5zYR388M7xOvUHFp2zrDFpnLkdUdh
+RPa5v7DKHa504V6dFSkMFkHsk0dNHgTznR1YNMFD7VmMQklMuvvgfQ3L
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem.certspec b/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem.certspec
new file mode 100644
index 0000000000..833e1a23a6
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem.certspec
@@ -0,0 +1,2 @@
+issuer:untrusted-ca
+subject:ee-from-untrusted-ca
diff --git a/security/manager/ssl/tests/mochitest/browser/email-ee.pem b/security/manager/ssl/tests/mochitest/browser/email-ee.pem
new file mode 100644
index 0000000000..d856aa6aa3
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/email-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyTCCAbGgAwIBAgIUVZwQ1Of+nj4eG0+TjbqZffyD1rEwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowEzERMA8GA1UEAwwIZW1haWwtZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg
+2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ
+5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQ
+PdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGj
+DJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8W
+iy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjFzAVMBMGA1UdJQQMMAoG
+CCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4IBAQCYbnocbOa5AGp4lEMq/ckjdp8J
+8WElrTbZmzFbUSdg4t8a3PCNzxpKQbGaQc4q1OtSGxhXNEVGYzxiOWIXIeYkuQWS
+Ej2SEk5krcOHgxu3JAucdidSaWNSUlhTpMgN2XekukSbSIE+MHBYgZqIM4yoQe59
+T2ns6fyqErRYpx828YrD2gDYiQAqyJQRA3DaGLRi1kjr8MWnalEgfxUkH7l8Qk09
+TGBmsOVLZaXtbXH3gNWW6275/Ea+zHyON3XrSVPNgXGPK6ZF7fb3sRE+SRaPjqgB
+8w2fPZ6y/jw8MklVKiR2zY6GdaDCiX1IxmrsOFy5ANlqTvmOAglCg11pdcTt
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/email-ee.pem.certspec b/security/manager/ssl/tests/mochitest/browser/email-ee.pem.certspec
new file mode 100644
index 0000000000..82e3296706
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/email-ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:email-ee
+extension:extKeyUsage:emailProtection
diff --git a/security/manager/ssl/tests/mochitest/browser/expired-ca.pem b/security/manager/ssl/tests/mochitest/browser/expired-ca.pem
new file mode 100644
index 0000000000..e019b37127
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/expired-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIUS6xUkMzG2REizII2g+VecO/KqX8wDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAxMDAxMDEwMDAwMDBaGA8yMDExMDEwMTAw
+MDAwMFowFTETMBEGA1UEAwwKZXhwaXJlZC1jYTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wccl
+qODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sg
+w0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCx
+V5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1
+MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQs
+vxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMdMBswDAYDVR0TBAUw
+AwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBABJdjrt25wocw4aP
+eR1kZuu3WS0bKfuvhQQPFkAG+HYSC5eu0OriQCRlxn+qHY7du9dePcD6DTMDIVDW
+r+oBJ9BwCEREyTcV8AEaHdcTAakXOMhq6OOltl6HUu3lSlqRslzAhtl1chM0P8m1
+Aj+ceOkCFHvnsDd+zcSP75u8zzJKypSWQwAg/i5S0BNLOWYarPiczuYi4HAOpwtX
+QqlmDNMYySqPFfH72BuQdCLuviBXmMP8/kOouBNP4ti06RR88XgqfoL/jV4gkIM7
+92hXt0WpS/QffjWzLaej39YhW4pMZ+hF4bk9nUCtN/MHtg8WDj1CgfSJZegrl28W
+3riMotA=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/expired-ca.pem.certspec b/security/manager/ssl/tests/mochitest/browser/expired-ca.pem.certspec
new file mode 100644
index 0000000000..15bdcd7d73
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/expired-ca.pem.certspec
@@ -0,0 +1,5 @@
+issuer:ca
+subject:expired-ca
+extension:basicConstraints:cA,
+extension:keyUsage:cRLSign,keyCertSign
+validity:20100101-20110101
diff --git a/security/manager/ssl/tests/mochitest/browser/has-cn.pem b/security/manager/ssl/tests/mochitest/browser/has-cn.pem
new file mode 100644
index 0000000000..d71fa7af3e
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-cn.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIUcRho0IgxDpQ9mLwrKXdUlGx+17EwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowNzEMMAoGA1UEAwwDRm9vMQwwCgYDVQQLDANCYXIxDDAKBgNVBAoMA0Jh
+ejELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAw5KXaTIVAa
+aC5Wtey/YCgRodJWV5FBtBDmMBMVS6p9nKltnrLkns1f+nVPwhngoQ6BCbN0omJO
+SCziNVK0RA3S1SfVOnz37sD0UodgGURZH2WoyLF5CLiplvkFmtR1E/NLGNSVthBk
+lO2U8n4azTjD474/MJfeaafavzp/FfKl/qn9Df+D0GTRuVO/cwTeZgV5Xq+5NQHl
+9TaCBWnJT0nCjXD5LY88MkS5gMfKYhg/Ukr+bXIONpoizc8oEbJ/y+zz40YTlyLS
+nKd3AGFihWamXUNQoRNrqj0LJjkp4UOHse1NjUR/ELBOFoQ+isc4IlqMs6EJkkrV
+nQn1tv3mTZQ=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec b/security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec
new file mode 100644
index 0000000000..a4a0fcb5fa
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/CN=Foo/OU=Bar/O=Baz/C=US
diff --git a/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem
new file mode 100644
index 0000000000..df14041f69
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICijCCAXKgAwIBAgIBCjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDDAJjYTAi
+GA8yMDIyMTEyNzAwMDAwMFoYDzIwMjUwMjA0MDAwMDAwWjAAMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1
+aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/we
+adA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSS
+pH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62W
+YVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauR
+CE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMA0GCSqG
+SIb3DQEBCwUAA4IBAQBu3MN1OSmzWjcY64BjiW16f8z/YzdgGljNnpiq9LBy3TwB
+Upd0lgIF6y25W6pAYASM9A+5Wk9T3jHz7cYtvgYsxEvbYF9bNSLTc4EQJpMTQCuc
+AcRTuZlGJJmBWAXG+FNgyuSGAHlW5fgv42k5av3Q7irzuDmKTp5nVNwbZ4a8gEyE
+xIdECAsN7OZAcafZKHkAFXE+7x5p1HppDUT6Cnud26bRdw3PzacyvgzbEE/E7SAW
+fi21fSsY84ybygiV6XXEDGlYMKiXPXNqKOKKQiOaNdjZHjDjs0WzMb3FitM6BJfT
+w6yQin/tsf2UbnP/s2hZrCg92fNbCtG3P6MAGdYS
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec
new file mode 100644
index 0000000000..6346f7b83a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:
+serialNumber:10
diff --git a/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem
new file mode 100644
index 0000000000..6fe0c66e96
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICqjCCAZKgAwIBAgIUcRygY3MPPtD72llMhdZrkA34zIQwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowDTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACYn
+2YYASS0cJRJNbLsF1u0Fst/4JcPQ/U9qzDh+jc5nhStUD4QUS20/dDQaOLX2LWan
+cDRxY1gXhN4xCbxMhnh6jzkpj6kFqu4Mf6j5J/3V0l2G3jnyRbd+IY3GYRnj7oDk
+1zllpA39hGRo8cdt1KNDwhc1BBfiFIu1M0iUIOEvpK5npKBXuR6z93OUhhtL1Fmf
+k73cAm2HGXsUxlPwLV8jlRRyflF7ndT6+CBN9rHdA2enM2J6WgJraWEiISwDvBgs
+sbWU/LoyrzsKFhH/TX3lN37VlqKeAmJVu1gC020Wu9G/yfzoaG38CQHwsiiwvwX/
+WDLOLH7F1yB7D+wlFCM=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec
new file mode 100644
index 0000000000..cc1b668a63
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/C=US
diff --git a/security/manager/ssl/tests/mochitest/browser/has-o.pem b/security/manager/ssl/tests/mochitest/browser/has-o.pem
new file mode 100644
index 0000000000..28080a17a7
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-o.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuDCCAaCgAwIBAgIUAS43zKpKvEqNftR+iGFQNyxunOEwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowGzEMMAoGA1UECgwDQmF6MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhX
+bCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQ
+OCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9
+uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFb
+t+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhO
+NsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEAl6kVxr3IJXmjnJ+SPVLG7b/UiRyPhZ1sEBBT2PLGeOU1bPNn
+N4vDiLno2UUNPiZOJi3zkG/ljVAz9mQCVvoxwdtqSjduUk3+rZFWSG4VFuGxc66d
+E7R0rTdlaFSdTp0Cf9zSHPIJ+bJzljB6plMzuRxy/PQ2JWii0qmK6pitTNpPrx6K
+VspDcdDdLYsrdZ8OeH1NGvqeYCPaweU5Jmt1tmCv3XLB8Zdk091UAuqmZIlGbH5C
+rXiDLxqnCr15dQYw/hHKNyRL2kSA8ZpVu/psmznGQB9wePYnjPnMUtJS/CF9pW0Y
+2icCxZDUzguu2/1tFT/MVArWlGCR3FUr3XjgJQ==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec b/security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec
new file mode 100644
index 0000000000..f7cc3ffc73
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/O=Baz/C=US
diff --git a/security/manager/ssl/tests/mochitest/browser/has-ou.pem b/security/manager/ssl/tests/mochitest/browser/has-ou.pem
new file mode 100644
index 0000000000..c5590b5779
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-ou.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAa6gAwIBAgIUa9qwFC5BxyhkqYu+ori020sfdFwwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowKTEMMAoGA1UECwwDQmFyMQwwCgYDVQQKDANCYXoxCzAJBgNVBAYTAlVT
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2
+ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF
+h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n
+cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv
+OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj
+tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt
+jQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAJwpYG0a3/hstrP1duw/KaRaQ//NbS
+QrlC0EBDjxZghjXjB+s5OTM78MO0Ao7052WgVCA289JWy9v5Pg9ht6KRBjarHSEU
+H7IsZ/EkK0sV9aHgM0Lw8mZsG1ZCzkMMcySVzYvxUpKbc056pEuZAkATUNj0SZjx
+tQMsqCXW0JvRQLUd7EDiFeGAx5UsRgQ33IUoEXLdJmsj+7RgEYPhX0bTNm18YpZV
+MlDmaYXv2gXCYjOJAMcYXlVruHNuuq/cTHCgwIUObqYtEpkIAPqXb0KaoqdTiHn7
+rdWCnOk7BrTHfnoChc5jJvhlKWS7f2UEUw9nJCO1CN13bRQ7AXhZsGrC
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec b/security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec
new file mode 100644
index 0000000000..8879dabf51
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/OU=Bar/O=Baz/C=US
diff --git a/security/manager/ssl/tests/mochitest/browser/head.js b/security/manager/ssl/tests/mochitest/browser/head.js
new file mode 100644
index 0000000000..1ae951d7a5
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/head.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+/**
+ * List of certs imported via readCertificate(). Certs in this list are
+ * automatically deleted from the cert DB when a test including this head file
+ * finishes.
+ *
+ * @type {nsIX509Cert[]}
+ */
+var gImportedCerts = [];
+
+registerCleanupFunction(() => {
+ for (let cert of gImportedCerts) {
+ gCertDB.deleteCertificate(cert);
+ }
+});
+
+// This function serves the same purpose as the one defined in head_psm.js.
+function pemToBase64(pem) {
+ return pem
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+}
+
+/**
+ * Given the filename of a certificate, returns a promise that will resolve with
+ * a handle to the certificate when that certificate has been read and imported
+ * with the given trust settings.
+ *
+ * Certs imported via this function will automatically be deleted from the cert
+ * DB once the calling test finishes.
+ *
+ * @param {string} filename
+ * The filename of the certificate (assumed to be in the same directory).
+ * @param {string} trustString
+ * A string describing how the certificate should be trusted (see
+ * `certutil -A --help`).
+ * @returns {Promise}
+ * A promise that will resolve with a handle to the certificate.
+ */
+function readCertificate(filename, trustString) {
+ return IOUtils.readUTF8(getTestFilePath(filename)).then(
+ pem => {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let base64 = pemToBase64(pem);
+ certdb.addCertFromBase64(base64, trustString);
+ let cert = certdb.constructX509FromBase64(base64);
+ gImportedCerts.push(cert);
+ return cert;
+ },
+ error => {
+ throw error;
+ }
+ );
+}
+
+/**
+ * Asynchronously opens the certificate manager.
+ *
+ * @returns {Window} a handle on the opened certificate manager window
+ */
+async function openCertManager() {
+ let win = window.openDialog("chrome://pippki/content/certManager.xhtml");
+ return new Promise((resolve, reject) => {
+ win.addEventListener(
+ "load",
+ function () {
+ executeSoon(() => resolve(win));
+ },
+ { once: true }
+ );
+ });
+}
diff --git a/security/manager/ssl/tests/mochitest/browser/hsts_headers.sjs b/security/manager/ssl/tests/mochitest/browser/hsts_headers.sjs
new file mode 100644
index 0000000000..95eede25f0
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/hsts_headers.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function handleRequest(request, response) {
+ let hstsHeader = "max-age=300";
+ if (request.queryString == "includeSubdomains") {
+ hstsHeader += "; includeSubdomains";
+ }
+ response.setHeader("Strict-Transport-Security", hstsHeader);
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, 200);
+ response.write("<!DOCTYPE html><html><body><h1>Ok!</h1></body></html>");
+}
diff --git a/security/manager/ssl/tests/mochitest/browser/hsts_headers_framed.html b/security/manager/ssl/tests/mochitest/browser/hsts_headers_framed.html
new file mode 100644
index 0000000000..5a0791557b
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/hsts_headers_framed.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ "use strict";
+
+ let src = document.location.href.replace("hsts_headers_framed.html", "hsts_headers.sjs");
+ if (document.location.search == "?third-party") {
+ src = src.replace("example.com", "example.org");
+ }
+ let frame = document.createElement("iframe");
+ frame.setAttribute("src", src);
+ frame.onload = () => {
+ let done = document.createElement("h1");
+ done.textContent = "done";
+ done.setAttribute("id", "done");
+ document.body.appendChild(done);
+ };
+ document.body.appendChild(frame);
+</script>
+</body>
+</html>
diff --git a/security/manager/ssl/tests/mochitest/browser/intermediate.pem b/security/manager/ssl/tests/mochitest/browser/intermediate.pem
new file mode 100644
index 0000000000..2480febf91
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/intermediate.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIUQ98nHYCeqigGsr++R4IpE/QtJTwwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAwMDAw
+MFowQTEoMCYGA1UEAwwfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEV
+MBMGA1UECwwMSW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGc
+BptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzC
+a2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8Xg
+uEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK
+9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGP
+mRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRMEBTADAQH/MAsG
+A1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAWSVWMKE1GS9pXzdJ2pgu501C
+H/JSsxDaex9LfN9GGXN4NVDnbgPglf+q1vzY+8XjCGeGVUhum82Ui7s+fjCkDBcO
+cY3iss94021rVHhNsdvuMq7BNE/Y7YtKfEQMNKtjoWWiF4OgU5M7NfNsU/oj3ycp
+mefk+hNA+blPX9yfACKCeO/6RK5QFakDxZl5ls6KJIgqM2RvJHMBedHqTsfvBCbp
+xMojtCZgCtFPGFgAIk2TGzRx5njiZeyseWH/drGdY/YKxJRCu0lLy30zVYJP1Ti/
+s4PSUczBu+6Hrx6PejewWtXcSyeK7E1+Fb7BX1OG9xPzVclUHrO8yuyS12ei7Q==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/intermediate.pem.certspec b/security/manager/ssl/tests/mochitest/browser/intermediate.pem.certspec
new file mode 100644
index 0000000000..a562814041
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/intermediate.pem.certspec
@@ -0,0 +1,4 @@
+issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+subject:/CN=Temporary Certificate Authority/OU=Intermediate
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
diff --git a/security/manager/ssl/tests/mochitest/browser/invalid.pem b/security/manager/ssl/tests/mochitest/browser/invalid.pem
new file mode 100644
index 0000000000..0063e0c39d
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/invalid.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICwDCCAaigAwIBAgIUfZyrU2miUlovoBEId6Sq0W2+GN4wDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowEjEQMA4GA1UEAwwHaW52YWxpZDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMPMA0wCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBCwUAA4IBAQB+WgW4wZiskIjyu+7JNHvivXSO3N6F6etrjgKS
+0KfvdOYpriS5J4yVsYjiTdlAK+PNgx3V9RDCrYD/lWQKZmMB5eWy5xvPfQvke+PQ
+VSo/IXvCWE3jvmNGH/1x0ludyFa08TgbcGGbfkJQhyg00IvdlyFypRzxkSFx4IiC
+h5UcZEhR8DhXeIS4Jcy9whcRfqC/rGLOWPS75rXDaSI+qolQlFEvmL0c89yYudtL
+g1vQz8YHo3OAqht60gkxMV/dtHwONltxDuO55PXl65Q1OYZGghdUC9q750Hi2U5Y
+lpz5GdxqPH1SlC4TFUXcTw+wTywV+OTR3B/lYxzwh6eZgUPW
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/invalid.pem.certspec b/security/manager/ssl/tests/mochitest/browser/invalid.pem.certspec
new file mode 100644
index 0000000000..71a1707c35
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/invalid.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:invalid
+extension:keyUsage:cRLSign,keyCertSign
diff --git a/security/manager/ssl/tests/mochitest/browser/longOID.pem b/security/manager/ssl/tests/mochitest/browser/longOID.pem
new file mode 100644
index 0000000000..edbe739c9f
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/longOID.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIESjCCAzKgAwIBAgIUH/LZJJV0+HAiFpg1LFyimJNN2/0wDQYJKoZIhvcNAQEL
+BQAwEzERMA8GA1UEAwwITG9uZyBPSUQwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1
+MDIwNDAwMDAwMFowEzERMA8GA1UEAwwITG9uZyBPSUQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk
+e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg
+KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI
+YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi
+lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL
+HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjggGQMIIBjDAM
+BgNVHRMEBTADAQH/MIIBegYDVR0gBIIBcTCCAW0wggFpBoIBZSqD3OuTf4Pc65N/
+g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zr
+k3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D
+3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuT
+f4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc
+65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/
+g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zr
+k3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D3OuTf4Pc65N/g9zrk3+D
+3OuTf4Pc65N/ATANBgkqhkiG9w0BAQsFAAOCAQEASReXmbBObPQQC3qN9cFj7txY
+ArJ/gW14Os+qqn03/ArWcORIoQF/vD6X8tdV1bKcKzLxqvZ0bdMyaRg2CMUX1LiF
+/jfVooot21ZJwh07IKYi8RBm54BAYlAqUB82mMutUc+6Ut/9MqxsAtKoBjjnoV78
+94cf3K/lKoTwdVz9F1L91RC6ARbnU69xcYLGU4Tazt1Zf3VVY4Y5iOxFYLuFcyyW
+dwHRaobTjN1OA70e5emPQARbVt+nUbcJPGTUL6kQFxNzRJ2GStqBV2QmxXES/cgw
++itB72hrTgIFFAsi9oYLPXlNIgJ+1T0uq7t8vqenpIZUTmch6ZLomFvRYEBOEQ==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/longOID.pem.certspec b/security/manager/ssl/tests/mochitest/browser/longOID.pem.certspec
new file mode 100644
index 0000000000..c3c08ac84b
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/longOID.pem.certspec
@@ -0,0 +1,4 @@
+issuer:Long OID
+subject:Long OID
+extension:basicConstraints:cA,
+extension:certificatePolicies:1.2.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.999999999.1
diff --git a/security/manager/ssl/tests/mochitest/browser/md5-ee.pem b/security/manager/ssl/tests/mochitest/browser/md5-ee.pem
new file mode 100644
index 0000000000..5c562cf0b4
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/md5-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrjCCAZagAwIBAgIURRrxes60EYjDGrpfM/azxcVXALQwDQYJKoZIhvcNAQEE
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowETEPMA0GA1UEAwwGbWQ1LWVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4Ngf
+vbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTb
+uUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3S
+O8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR
+3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv
+5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMA0GCSqGSIb3DQEBBAUAA4IB
+AQBvnuXiImrqTkusX+19BoKO2bQlP5sQLnfTvyOzyHOK5MoDkC7up7bFSnFgOdxH
+L2LllEFbS9O26poAMA+EY2sQ6Rn3WiFZUkmF5pKhVp6I90SwIExa13YGyLTqWMsu
+ttbwrAXwNIp31mGmt4UNArLoAyyK+nn+juVaPm+C8VORqzCpJVzJt9+35XAnpwt1
+1Q8lYNNulzi+qbZH85LGrUvhPe2tIstUz9SoGfg0ljP/fvinDRoVPOs7i3+x+RM5
+uVeaQ8LZTmAVCfBdD3e18ehYoHFyWPKZClQTR6X2TeLp4/gepissuwWa1igYA9Hi
+1tEhEobBwd17/mjr3J/fKnSd
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/md5-ee.pem.certspec b/security/manager/ssl/tests/mochitest/browser/md5-ee.pem.certspec
new file mode 100644
index 0000000000..279c158026
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/md5-ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:md5-ee
+signature:md5WithRSAEncryption
diff --git a/security/manager/ssl/tests/mochitest/browser/moz.build b/security/manager/ssl/tests/mochitest/browser/moz.build
new file mode 100644
index 0000000000..cfd5452a0e
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+BROWSER_CHROME_MANIFESTS += ["browser.toml"]
diff --git a/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem b/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem
new file mode 100644
index 0000000000..9d21112e3c
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAmugAwIBAgIUefe9aVUlLbfhs5MiySqADkIcdJ8wDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAwMDAw
+MFowajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQD
+AgH+MA0GCSqGSIb3DQEBCwUAA4IBAQClgVd2rdbSo0TCmrgszyl7TQ465L+YMlqD
+Q/KXpO8H7aKIxptW+7CwfhxnpLd6RLbjsK1K/cFF2a5cVJxsh4t2HoVybjivuOp1
+Yws4/3xVFqu4NsCz2eLHKt3bIZf8TASeJpuvJvNEmqnEHHoRQnfxiQMa0ztcJss7
+Wvb/Nck5iUHTFlkOghgUE3yDZ9yPlofIrCrZiWBGINslHYRZCue0irfv7T40hPyv
+hu+agWCdauh/p77+Z6Nq5b7rGNsF8zN0O3W/f4RbGTjzhtPMZcg4fYCLqqv7ML3z
+C314G9wQIBPwMjVAEyaOf7G6V48WrRz7q6V83noKmf0vsPGHvH9U
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem.certspec b/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem.certspec
new file mode 100644
index 0000000000..4def496f67
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem.certspec
@@ -0,0 +1,4 @@
+issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+subject:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+extension:basicConstraints:cA,
+extension:keyUsage:digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign
diff --git a/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem b/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem
new file mode 100644
index 0000000000..1fda92bb93
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAmugAwIBAgIUfy7mnEW2lfad+ZR8vPZUtd+l8KEwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAwMDAw
+MFowajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQD
+AgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAiC+uvMvWWWs2WYG/Hf3Q18unxl4jV7QhZ
+TGpxLCM63AUo0vhCZQia8L0qvvwLJ7RUOnDBQrT6mrGDBi8sWQyzna8X/qA7jD/K
+cM2z8QkIs/y8BV1u8KZ68fXqMz6toptWkJK55IFk85GHEDlgPX0lh4SPd4BCuY+X
++v534nMMm8xBtzXZbrvxe2manCFfPIq0yr0Vl+psnAYgVW96JivxqcfiYn59y1n6
+8YZrGgCZ35B5LgLIBzZi5sJtBC/VLRrVGSci8nNGQzqQ5TXDgGns7eO5mOGIy+ZV
+l8A03fNLP5lB6QFNvPMg5ux4gKN2VGjPcU+RxdrucDgJU+RgzuGs
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem.certspec b/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem.certspec
new file mode 100644
index 0000000000..448e167bd0
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem.certspec
@@ -0,0 +1,4 @@
+issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+subject:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
diff --git a/security/manager/ssl/tests/mochitest/browser/revoked.pem b/security/manager/ssl/tests/mochitest/browser/revoked.pem
new file mode 100644
index 0000000000..dfdea6aab8
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/revoked.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrzCCAZegAwIBAgIUY6Ozs/15FHnCV6XP+oKphcqua4IwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowEjEQMA4GA1UEAwwHcmV2b2tlZDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkqhkiG9w0BAQsFAAOC
+AQEAiygONmY4DwqqR/CexFcHaXXE+L+6O4hjvSjC+JfoAaQjULUYMyILyJLgwy5W
+sjO7zZBm9lpfOq2APz64rWqAWuxISbUkHTAD+Juqq08ehgCbO+qUqDPdN+8gbTy0
+IhJa5MjRg5eO7ggFLiMlnETI2ZkvQYe/LhGMUzel7sfsWi1eTEsB+BZSHQjUrjn4
+AJ7vBEOmI4c67DbZzhMCr32U6Zkv2J8mcH6H12U+WkyCbPDkx69UK+AqaGeEX+ka
+Lmn4Yi5FIP44Vv3IoSy9DMEsjuT+9GHrH9HFgNugThhmNis4DM2wjdbUXdjR/T9e
+yhz9WTXANM5omANrP/zH6dNk1g==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/revoked.pem.certspec b/security/manager/ssl/tests/mochitest/browser/revoked.pem.certspec
new file mode 100644
index 0000000000..daf75c670f
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/revoked.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:revoked
diff --git a/security/manager/ssl/tests/mochitest/browser/some_content.html b/security/manager/ssl/tests/mochitest/browser/some_content.html
new file mode 100644
index 0000000000..f591f32d3d
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/some_content.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<h1>Some Content!</h1>
+</body>
+</html>
diff --git a/security/manager/ssl/tests/mochitest/browser/some_content_framed.html b/security/manager/ssl/tests/mochitest/browser/some_content_framed.html
new file mode 100644
index 0000000000..8f8194f9e7
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/some_content_framed.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ "use strict";
+
+ let src = document.location.href.replace("https://", "http://");
+ let frame = document.createElement("iframe");
+ frame.setAttribute("id", "frame");
+ frame.setAttribute("src", src);
+ document.body.appendChild(frame);
+</script>
+</body>
+</html>
diff --git a/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem b/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem
new file mode 100644
index 0000000000..272be45a76
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIUH3+Xdp/O5Rd6jutipltQkifl9ycwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowETEPMA0GA1UEAwwGc3NsLWVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4Ngf
+vbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTb
+uUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3S
+O8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR
+3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv
+5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoyEwHzAdBgNVHSUEFjAUBggr
+BgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAJTxcdoif/XVwjYR
+hPRVCNmy20LErwphK4xK6+X4Zi1DJ/HvAWdjUPOkL/XbnddcoR56E11IgAD2UhLJ
+vFNItTVs8NV7kTTf2Jsg4Fn4n3vfOcivdzlFOPIW5UMHUeJ7PNA9emJnK8YXjbrs
+KZ0NVZ10H4Fuj+BgscdhvZ2FaHwyUhaJ9RgaORxGo+0pJDey/R9ruSn5CoqkHAFI
+bwZ0z22cxjo6hWuewfblsAe8a5Ssbd90q1pXDadcFhQ7Aq+6SJkSCQPiM+Sz/iDN
+xz1qCwdO0VjRRmVzweeOj3Ep8ebuUIGmnIdA08xAUztSHTkyXdAprN6EygHpibah
+vRfsQrY=
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem.certspec b/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem.certspec
new file mode 100644
index 0000000000..c4037675f1
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:ssl-ee
+extension:extKeyUsage:serverAuth,clientAuth
diff --git a/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem b/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem
new file mode 100644
index 0000000000..a6d802883b
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaOgAwIBAgIUb7DcOwSWh31LKC+TIu6um7tDdIUwDQYJKoZIhvcNAQEL
+BQAwEjEQMA4GA1UEAwwHdW5rbm93bjAiGA8yMDIyMTEyNzAwMDAwMFoYDzIwMjUw
+MjA0MDAwMDAwWjAZMRcwFQYDVQQDDA51bmtub3duLWlzc3VlcjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs
+9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8
+HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7Ak
+kqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJet
+lmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2r
+kQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkq
+hkiG9w0BAQsFAAOCAQEASkzl98adoA7+9SxqkkPzL1cXKOMaWCiDsRUElri/B5B9
+UvIRhPIN1MA5NnkM7F2y+md0jF7fQQ0Ui4VaOpGo6iICFYq4g5SwX16HvIM95Uxy
+1MK4TfbtaG7aoOvbV8fW8WDXnks2YyY34rd3AMU4xi2a+z7p1tNhU7K6gC5RgH+u
+uP/xU0rb+yIyTDApt25QEJBNcLUMLpJN8Zcg5+RKcP4q9YAFkh3tSYhpiZhSgB2q
+CTbFMRKpeXNpp9TgvMcAP1kM1UckNoQIyhBwgdtvPjEa0fbz/Wf7fIArCb6sk/jV
++xANql2CVcT07+Juka15opxAsDgwuYnR5eVaCB/DeA==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem.certspec b/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem.certspec
new file mode 100644
index 0000000000..c76a4e2c7b
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem.certspec
@@ -0,0 +1,2 @@
+issuer:unknown
+subject:unknown-issuer
diff --git a/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem b/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem
new file mode 100644
index 0000000000..fe91a2849c
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0zCCAbugAwIBAgIUN9RlKkRxZsQXbeuVuTiQV/eq/wUwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMjExMjcwMDAwMDBaGA8yMDI1MDIwNDAw
+MDAwMFowFzEVMBMGA1UEAwwMdW50cnVzdGVkLWNhMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvB
+xyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmT
+qyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5
+kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYS
+wHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwk
+BCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRME
+BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAPivTuANdnSMY
+9vSbWY1LUYMjcXfeKNYPIB6t9tPjNFOFst2QnRG8qUWUQlj9FaB4uTVagHD6r6Gk
+XEbuj3O/IgFfCLPxHebGLw1XlLHfWG9iQiR0bOkmLnlNJdHVJ4uI8aaVU9B80T3x
+AzvPAfc4sv/7Fqu9XXHCUx3g6nqyKgcxWoXUe5sX/Wcvtjf3a5HcRUPJ6CYxM36X
+RFeoELH79QMnJ4cYLbUWrOO8+n2RH0BnJAnyxBd8bNVknnROzbjq10wi60ei8Eon
+8EIPNjveVtlnrAePm4EyTvFTYB8YtUPRTnkfJlRlVRDkRtQlscxdbmPZI/+xeXFu
+5zD9Q/ez+Q==
+-----END CERTIFICATE-----
diff --git a/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem.certspec b/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem.certspec
new file mode 100644
index 0000000000..04f4430574
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:untrusted-ca
+extension:basicConstraints:cA,
+extension:keyUsage:cRLSign,keyCertSign