summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/mochitest/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/ssl/tests/mochitest/browser
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/tests/mochitest/browser')
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser.ini37
-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.js91
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_certViewer.js107
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js144
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js297
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js340
-rw-r--r--security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js199
-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
66 files changed, 3205 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/mochitest/browser/browser.ini b/security/manager/ssl/tests/mochitest/browser/browser.ini
new file mode 100644
index 0000000000..7692072459
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+tags = psm
+support-files =
+ *.pem
+ head.js
+ hsts_headers.sjs
+ hsts_headers_framed.html
+ some_content.html
+ some_content_framed.html
+
+[browser_HSTS.js]
+https_first_disabled = true
+[browser_bug627234_perwindowpb.js]
+[browser_certificateManager.js]
+[browser_add_exception_dialog.js]
+[browser_certViewer.js]
+skip-if = verify
+[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
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_clientAuth_ui.js]
+[browser_clientAuthRememberService.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[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..a898e436c4
--- /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..d5ebe0b208
--- /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
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+});
+
+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.loadURI(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..fbb7b5e9f9
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
@@ -0,0 +1,91 @@
+/* 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.loadURI(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..489d7a4ef6
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
@@ -0,0 +1,107 @@
+/* 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.
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+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..a7422609e8
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certificateManager.js
@@ -0,0 +1,144 @@
+/* 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 certString = labels[i * 3 + 1].value || labels[i * 3 + 1].textContent;
+ let isTemporaryString =
+ labels[i * 3 + 2].value || labels[i * 3 + 2].textContent;
+
+ Assert.equal(
+ hostPort,
+ item.hostPort,
+ `Expected override to be ${item.hostPort} but got ${hostPort}`
+ );
+
+ Assert.equal(
+ certString,
+ item.certName,
+ `Expected override to have field ${item.certName}`
+ );
+
+ Assert.equal(
+ isTemporaryString,
+ item.isTemporary ? "Temporary" : "Permanent",
+ `Expected override to be ${item.isTemporary ? "Temporary" : "Permanent"}`
+ );
+ });
+}
+
+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."
+ );
+}
+
+async function testViewButton(win) {
+ win.document.getElementById("serverList").selectedIndex = 0;
+
+ Assert.ok(
+ !win.document.getElementById("websites_viewButton").disabled,
+ "View button should be enabled for override with cert"
+ );
+
+ let loaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+
+ win.document.getElementById("websites_viewButton").click();
+
+ let newTab = await loaded;
+ let spec = newTab.linkedBrowser.documentURI.spec;
+
+ Assert.ok(
+ spec.startsWith("about:certificate"),
+ "about:certificate should habe been opened"
+ );
+
+ let newUrl = new URL(spec);
+ let certEncoded = newUrl.searchParams.get("cert");
+ let certDecoded = decodeURIComponent(certEncoded);
+ Assert.ok(certDecoded, "should have some certificate as cert url param");
+
+ gBrowser.removeCurrentTab();
+}
+
+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",
+ certName: "md5-ee",
+ isTemporary: false,
+ },
+ ]);
+
+ await testViewButton(win);
+
+ 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..9f9ce6b28a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js
@@ -0,0 +1,297 @@
+// -*- 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.loadURI(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() {
+ gClientAuthDialogs.chooseCertificateCalled = false;
+ await testHelper(
+ "https://requireclientcert.example.com:443",
+ "https://requireclientcert.example.com/"
+ );
+}
+
+async function openRequireClientCert2() {
+ gClientAuthDialogs.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 gClientAuthDialogs = {
+ _chooseCertificateCalled: false,
+
+ get chooseCertificateCalled() {
+ return this._chooseCertificateCalled;
+ },
+
+ set chooseCertificateCalled(value) {
+ this._chooseCertificateCalled = value;
+ },
+
+ chooseCertificate(
+ hostname,
+ port,
+ organization,
+ issuerOrg,
+ certList,
+ selectedIndex,
+ rememberClientAuthCertificate
+ ) {
+ rememberClientAuthCertificate.value = true;
+ this.chooseCertificateCalled = true;
+ selectedIndex.value = 0;
+ return true;
+ },
+
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogs]),
+};
+
+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 clientAuthDialogsCID = MockRegistrar.register(
+ "@mozilla.org/nsClientAuthDialogs;1",
+ gClientAuthDialogs
+ );
+ let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
+ Ci.nsIClientAuthRememberService
+ );
+
+ await openRequireClientCert();
+ Assert.ok(
+ gClientAuthDialogs.chooseCertificateCalled,
+ "chooseCertificate should have been called if visiting 'requireclientcert.example.com' for the first time"
+ );
+
+ await openRequireClientCert();
+ Assert.ok(
+ !gClientAuthDialogs.chooseCertificateCalled,
+ "chooseCertificate should not have been called if visiting 'requireclientcert.example.com' for the second time"
+ );
+
+ await openRequireClientCert2();
+ Assert.ok(
+ gClientAuthDialogs.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(
+ gClientAuthDialogs.chooseCertificateCalled,
+ "chooseCertificate should have been called after removing all remembered decisions for 'requireclientcert.example.com'"
+ );
+
+ await openRequireClientCert2();
+ Assert.ok(
+ !gClientAuthDialogs.chooseCertificateCalled,
+ "chooseCertificate should not have been called if visiting 'requireclientcert-2.example.com' for the second time"
+ );
+
+ MockRegistrar.unregister(clientAuthDialogsCID);
+});
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..efb01e6165
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_connection.js
@@ -0,0 +1,340 @@
+// -*- 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 nsIClientAuthDialogs.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 nsIClientAuthDialogs.
+const gClientAuthDialogs = {
+ _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,
+ port,
+ organization,
+ issuerOrg,
+ certList,
+ selectedIndex,
+ rememberClientAuthCertificate
+ ) {
+ this.chooseCertificateCalled = true;
+ Assert.notEqual(
+ this.state,
+ DialogState.ASSERT_NOT_CALLED,
+ "chooseCertificate() should be called only when expected"
+ );
+
+ rememberClientAuthCertificate.value = this.rememberClientAuthCertificate;
+
+ Assert.equal(
+ hostname,
+ "requireclientcert.example.com",
+ "Hostname should be 'requireclientcert.example.com'"
+ );
+ Assert.equal(port, 443, "Port should be 443");
+ Assert.equal(
+ organization,
+ "",
+ "Server cert Organization should be empty/not present"
+ );
+ Assert.equal(
+ issuerOrg,
+ "Mozilla Testing",
+ "Server cert issuer Organization should be 'Mozilla Testing'"
+ );
+
+ // 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(certList, null, "Cert list should not be null");
+ Assert.equal(
+ certList.length,
+ gExpectedClientCertificateChoices,
+ `${gExpectedClientCertificateChoices} certificates should be available`
+ );
+
+ for (let cert of certList.enumerate(Ci.nsIX509Cert)) {
+ 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) {
+ selectedIndex.value = 0;
+ return true;
+ }
+ return false;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
+};
+
+add_setup(async function() {
+ let clientAuthDialogsCID = MockRegistrar.register(
+ "@mozilla.org/nsClientAuthDialogs;1",
+ gClientAuthDialogs
+ );
+ registerCleanupFunction(() => {
+ MockRegistrar.unregister(clientAuthDialogsCID);
+ });
+
+ // 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} 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.
+ */
+async function testHelper(
+ prefValue,
+ expectedURL,
+ expectCallingChooseCertificate,
+ options = undefined
+) {
+ gClientAuthDialogs.chooseCertificateCalled = false;
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.default_personal_cert", prefValue]],
+ });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow(options);
+
+ BrowserTestUtils.loadURI(
+ win.gBrowser.selectedBrowser,
+ "https://requireclientcert.example.com:443"
+ );
+
+ 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}')`
+ );
+ Assert.equal(
+ gClientAuthDialogs.chooseCertificateCalled,
+ expectCallingChooseCertificate,
+ "chooseCertificate should have been called if we were expecting it to be called"
+ );
+
+ 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 nsIClientAuthDialogs.chooseCertificate() is never called.
+add_task(async function testCertChosenAutomatically() {
+ gClientAuthDialogs.state = DialogState.ASSERT_NOT_CALLED;
+ await testHelper(
+ "Select Automatically",
+ "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() {
+ gClientAuthDialogs.state = DialogState.RETURN_CERT_NOT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "about:neterror?e=nssFailure2&u=https%3A//requireclientcert.example.com/",
+ true
+ );
+ cars.clearRememberedDecisions();
+});
+
+// Test that if the user chooses a certificate the connection suceeeds.
+add_task(async function testCertChosenByUser() {
+ gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ true
+ );
+ cars.clearRememberedDecisions();
+});
+
+// Test that the cancel decision is remembered correctly
+add_task(async function testEmptyCertChosenByUser() {
+ gClientAuthDialogs.state = DialogState.RETURN_CERT_NOT_SELECTED;
+ gClientAuthDialogs.rememberClientAuthCertificate = true;
+ await testHelper(
+ "Ask Every Time",
+ "about:neterror?e=nssFailure2&u=https%3A//requireclientcert.example.com/",
+ true
+ );
+ await testHelper(
+ "Ask Every Time",
+ "about:neterror?e=nssFailure2&u=https%3A//requireclientcert.example.com/",
+ 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() {
+ gClientAuthDialogs.rememberClientAuthCertificate = true;
+ gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ true,
+ {
+ private: true,
+ }
+ );
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ true,
+ {
+ private: true,
+ }
+ );
+ await testHelper(
+ "Ask Every Time",
+ "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;
+ gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
+ await testHelper(
+ "Ask Every Time",
+ "https://requireclientcert.example.com/",
+ true
+ );
+ cars.clearRememberedDecisions();
+ // This will reset the added intermediate.
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.enterprise_roots.enabled", true]],
+ });
+});
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..d28c61b7f2
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuth_ui.js
@@ -0,0 +1,199 @@
+// -*- 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 certList = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ certList.appendElement(cert);
+
+ let returnVals = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ let win = window.openDialog(
+ "chrome://pippki/content/clientauthask.xhtml",
+ "",
+ "",
+ TEST_HOSTNAME,
+ TEST_ORG,
+ TEST_ISSUER_ORG,
+ TEST_PORT,
+ certList,
+ returnVals
+ );
+ return TestUtils.topicObserved("cert-dialog-loaded").then(() => [
+ win,
+ returnVals,
+ ]);
+}
+
+/**
+ * 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.
+ */
+function checkDialogContents(win, notBefore, notAfter) {
+ is(
+ win.document.getElementById("hostname").textContent,
+ `${TEST_HOSTNAME}:${TEST_PORT}`,
+ "Actual and expected hostname and port should be equal"
+ );
+ is(
+ win.document.getElementById("organization").textContent,
+ `Organization: “${TEST_ORG}”`,
+ "Actual and expected organization should be equal"
+ );
+ is(
+ win.document.getElementById("issuer").textContent,
+ `Issued Under: “${TEST_ISSUER_ORG}”`,
+ "Actual and expected issuer organization should be equal"
+ );
+
+ is(
+ win.document.getElementById("nicknames").label,
+ "Mochitest client [03]",
+ "Actual and expected selected cert nickname and serial should be equal"
+ );
+ is(
+ win.document.getElementById("nicknames").itemCount,
+ 1,
+ "correct number of items"
+ );
+
+ let [
+ subject,
+ serialNum,
+ validity,
+ issuer,
+ tokenName,
+ ] = win.document.getElementById("details").value.split("\n");
+ is(
+ subject,
+ "Issued to: CN=Mochitest client",
+ "Actual and expected subject should be equal"
+ );
+ is(
+ serialNum,
+ "Serial number: 03",
+ "Actual and expected serial number should be equal"
+ );
+ is(
+ validity,
+ `Valid from ${notBefore} to ${notAfter}`,
+ "Actual and expected validity should be equal"
+ );
+ is(
+ issuer,
+ "Issued by: OU=Profile Guided Optimization,O=Mozilla Testing,CN=Temporary Certificate Authority",
+ "Actual and expected issuer should be equal"
+ );
+ is(
+ tokenName,
+ "Stored on: Software Security Device",
+ "Actual and expected token name should be equal"
+ );
+}
+
+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);
+ 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);
+
+ ok(
+ retVals.get("certChosen"),
+ "Return value should signal user chose a certificate"
+ );
+ is(
+ retVals.get("selectedIndex"),
+ 0,
+ "0 should be returned as the selected index"
+ );
+ ok(
+ retVals.get("rememberSelection"),
+ "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.get("certChosen"),
+ "Return value should signal user did not choose a certificate"
+ );
+ ok(
+ !retVals.get("rememberSelection"),
+ "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..a75a901b0f
--- /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..75011f6b99
--- /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..ac3abd44e6
--- /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..8adf1c8ccd
--- /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..035d12fde7
--- /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..91233a79dd
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyTCCAbGgAwIBAgIUW0tAkXslaf5AkE2ROwvnEvMo79MwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowDTELMAkGA1UEAwwCY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYD
+VR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQA/1uiRtJJ6gASZfaGdmjTgHNnG
+OX3VBb8CGbu7yrmlUU0/oiH9BHb9/gq+Gwwcjg++2AEmUWd4damMXKUtRzouP6dV
+Z02jnExdXqq3xLDP71/bY3eneqg/KVIv3wDaHhatmVqWB4oy4itNmvVbbtCjUOyX
+DF3RM5HPQEDjMJO0TjKZMXp5HbyggAvGTbyw6kz+6EVuxxhFqVq1bti1CU4tGBdk
+b6ZOVQhxgJljogn70syXX+CUuUtu9+ufklWQjMtITjBKTSP5cg/NMnKrAL7dF30n
+aUcXz8fpmzjseQjk6uzkDYSFW2HiQtMHKhpCOXAoZ0hGtQ4BXhyhScK2sSB8
+-----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..d5a039da41
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-via-intermediate.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDETCCAfmgAwIBAgIUAee5xOtLu81Y/SiLJ0STt50RMT8wDQYJKoZIhvcNAQEL
+BQAwQTEoMCYGA1UEAwwfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEV
+MBMGA1UECwwMSW50ZXJtZWRpYXRlMCIYDzIwMjExMTI3MDAwMDAwWhgPMjAyNDAy
+MDUwMDAwMDBaMCcxJTAjBgNVBAMMHGNsaWVudCBjZXJ0IHZpYSBpbnRlcm1lZGlh
+dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braI
+BjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVa
+p0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB
+7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4C
+kC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJv
+aeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgK
+Ne2NAgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUA
+A4IBAQCE6MA6Fdz6xzNIKqV043/urSVLkVeUAwcSeezuQ3/mTOxP1tj5wtNj+Uso
+hS30XJT3geaVlNnr64+tHthJ0MC23lwEwVNPr6xGXfaFPeaw/ummrTPjhZYXWNKm
+oTmavnj5izA/czrwE0drzPSEKM36yX9xwf8iGAiKFpzYZkjX/0+KuG0rPm7Qgvcg
+fWY17qK3aQKJea/J+i1S4pKjiMxqigXdskTcyvwruagfSRXPbVjVhsmdObWFCcsE
+ry7PbOg9yMkjX4UkUOs2PqYjvRD9MqEIMJelBhi1wdi/TKVCAcvHb7MxYH+LP1Qw
+Pi1oYACb5mW9qKKClpSHxNr04NWA
+-----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..78286e0d36
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/client-cert-with-ocsp-signing.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjCgAwIBAgIUU6sY262PVGY54q2FxUD1ZqmwIiMwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAwMDAw
+MFowKzEpMCcGA1UEAwwgY2xpZW50IGNlcnQgd2l0aCBPQ1NQU2lnbmluZyBla3Uw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQ
+PTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH
+9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw
+4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86
+exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0
+ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2N
+AgMBAAGjITAfMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDCTANBgkqhkiG
+9w0BAQsFAAOCAQEAE6gT6G/Ib4Pw2nTE++I4CpRBEkkAct39JNzsyBvT2ShM29V8
+iQkqQDszpvwc3ftnVwF4FI/darsiE1uaLs2POr2MuJjXKMkxfnlCCEaKMvOMBekK
+Ybm5zo01YYoybiPgPWX3p19nkNcHBSCmWWHt1t96JNtFJobBU0gszx5x1l7FFYOZ
+xq5F9nbczqeFhKU9YSYwk0wOpALCRy9VLiuWQtoqMFa8gfSbfpAPn2avIy989/dS
+Zn8joPmWmCNsplAwXlzHffZZgg1EcnajsjxMjSjDMIt3IbTM+uWA8qfTKdu4vDdc
+541L1ma1kZAe97/gRaNzpCZGaGGSJz9TfhHlaQ==
+-----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..cf541b540b
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/code-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyDCCAbCgAwIBAgIUH6zK9zay4frBqO9qURd3GUeWak8wDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowEjEQMA4GA1UEAwwHY29kZS1lZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMXMBUwEwYDVR0lBAwwCgYI
+KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBABr4gvRF8SN/dO2YGh6DchN50cLl
+Om6d82KA3LgBcLgFOcDMldtUf1maAJxh+Me1mipsJ3RlnWXoummcol61G+xO2Hjb
+uruw6+ttoNwOX80QvOYwRX5NABpGOxb7pReUFCiWO63Hve/pSjN+EP/2OGF5ndQb
+Qlo81qFePb1ylr0Ebmz6C1zasWiciS7+NPuyCxDu1UHdbBmeqrFJxi7hTvmYuLYB
+HRFJqQy4sogCOBHiBiH5iclkUmM/+c9CuyiCuqLFCLiay6qgBvYCn3hLPJVMIrl6
+/Y5Y+XEAOevaKjXsSCcyu4r89mChPYFKAhucqFN6QrNxHm9vIyfaL5E0dEg=
+-----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..326888f61d
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-expired-ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICwjCCAaqgAwIBAgIUZ0P6GYwMhwdeVdDdwsrrm9WDMpIwDQYJKoZIhvcNAQEL
+BQAwFTETMBEGA1UEAwwKZXhwaXJlZC1jYTAiGA8yMDIxMTEyNzAwMDAwMFoYDzIw
+MjQwMjA1MDAwMDAwWjAdMRswGQYDVQQDDBJlZS1mcm9tLWV4cGlyZWQtY2EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT
+2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzV
+JJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8N
+jf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCA
+BiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVh
+He4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMB
+AAEwDQYJKoZIhvcNAQELBQADggEBAGdIC3QGmFDHvbj90BWGTwtmOncA6bThMES6
+OPyPTSWcoDa5hnvqi3UsX3O6c+cA2FB80Bop479wgtcGKZq9p7zzXtZRdqHW6xQa
+BMO7I04hU0rJXgqRnbjgpf4v5JIoLWLDdzqOs+51VGANSBAuKMg39mXifskKTjcf
+E3wGCikRxKN7Z1NyB6roRBTIdMwG6tTbJ/ZyqZNkt6aqw8kEXZNqm5unzD8PyMmm
+AUiaXMZrplfWAO8RKnmWyYJzAFp1oVZHf3dTrQ8GUVbSBaKlI9+p1jdVmDkzgUNo
+Y/OOflG7uWEsdKUq3gbooFMusQpyjMZtKxe1HutK12jPnK9DEy0=
+-----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..0eda6fea83
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ee-from-untrusted-ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAa6gAwIBAgIUReDMx4rhamCnCZ1c4abV5C8OwPIwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMdW50cnVzdGVkLWNhMCIYDzIwMjExMTI3MDAwMDAwWhgP
+MjAyNDAyMDUwMDAwMDBaMB8xHTAbBgNVBAMMFGVlLWZyb20tdW50cnVzdGVkLWNh
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2
+ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF
+h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n
+cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv
+OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj
+tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt
+jQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA8nC2eyCl4TwO6oKmyBGxZ/nkin+RW
+aSa/iHz1qov/nfHisxzTeKCUPvRBfTWR1jCLvnii89CjcQCoTfM+P/lrmGe0VffJ
+/Czmi6nkV5IaK2l0lXnpjSuIwpreqTziC4U3VA1TYCF8afN5y66r87X3t8YEjvGD
+Ypn6pTYApHLKt7qJ+lfMkdSlgDvbgMgdBHRSayF0Be8icuoC0pQFZXHlA81Wvao9
+vDskHxrla2QEUJ3GnxYG2oaQTsNIVW5SjgqRpFJAYWTXjJpUKI8VR3U0h3vLbcXr
+esF+fzYlIKJFE13WCo6Tf1cvPU2wWZeB6hi3iC3VniEUtp+T94VhVLAG
+-----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..8b9448c6a7
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/email-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyTCCAbGgAwIBAgIUYa+qat2+K6UrReFkeL4gpZvziCkwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowEzERMA8GA1UEAwwIZW1haWwtZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg
+2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ
+5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQ
+PdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGj
+DJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8W
+iy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjFzAVMBMGA1UdJQQMMAoG
+CCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4IBAQChV2j3U91RsK4oxoBwFf562R9g
+CARt6olvETxlle+NMeN7JqiWOKo9ReTgPpwu1f6Eg4t1L8QljHXBAcv4rbtFa+Nt
+h2UjxEklRqp/A3FnIB5SMeU0PE5WyKVLe5k3+I4b1+k9J2HxOEUBdDLYjRQ5KWh5
+iDbOhlGacLnElGyzntmIy6asH9wLYmrF2ZvYcvIrvX/o/+MkTGMRwDsA/eeaQbK5
+sNwkAxfvMiSkXZlGdfDPeMNYTP0SwsTwJKtlLKBxmHkF+tzjZqWYBr+vM2eWiIRl
+WYWzVJWmvTlWI04Lpd+R6Oc9vvxFhxXO5VzXzJpAllmbA2FVs+x3sRRakGWd
+-----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..cccd8f92cc
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-cn.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIUNYIXTgcbtso82Swb2ciZEygN0ScwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowNzEMMAoGA1UEAwwDRm9vMQwwCgYDVQQLDANCYXIxDDAKBgNVBAoMA0Jh
+ejELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFqub/ejjAfj
+tWZuXDCKchVrxiN2PljQe5uKepmkFFvlOt3nkTTvZdvvUd5QTwRdrg6HcPx075SZ
+uj0SzzuBSJrz99zbbkspHnT2l2R47kYx8P3FNodUCYcnnpAJlPIaVOxGR1ul9ZZb
++qz1OMaysszwOPCRwx2OKi+cj3SuVvGFwzDBaLkJkljQyWyuOihJJXKoc/D1MUIc
+nyakQkwEawb63cYE5OfaBQe+hL2Inm2a5CmXR+N8+/Sac85Azd1I3TisYnfiaHZD
+j0yGMhIQ1d3qV8jZQDTgg1qImG35/paBDCZOlvuz+IlwpGmE/Cvvj2ujmpPP8mh/
+kDAob3uIchA=
+-----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..de3a247a5c
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICijCCAXKgAwIBAgIBCjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDDAJjYTAi
+GA8yMDIxMTEyNzAwMDAwMFoYDzIwMjQwMjA1MDAwMDAwWjAAMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1
+aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/we
+adA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSS
+pH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62W
+YVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauR
+CE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMA0GCSqG
+SIb3DQEBCwUAA4IBAQBxrxn5lQCA9h/kx9uR5rX0DtEpdOHyHHMQlJu33NZjC3mc
+PStwcsTDPCM6TCgu4rqmPrYQuw/YN6sUoSiD8jD5ywz/8UEeAlHpi7j/3zLL2mhw
+8f+gu96NSAMmbSg8gBXPMca7uZ4FbDP8h5Aa6d+cT0xSlbhyD5OXIdVN1ju4daR/
+Qab/ksmLvVH6l2yWN72ZaEUpn1bCbpW1CWCJMuGzFExQxP4HO7SO0yOCw+1DRfzU
+oRjXYlj51CTEWqUNa5lqLnRjIiyP+ahH73nbLCubggPQhrI6iMhB8IaWjew3SIgM
+UCaTOOQmM6tpObDSVyHtuiEDWPasyC8yVvMy5LRI
+-----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..af8b89c4b9
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICqjCCAZKgAwIBAgIUJ9bwAWFp2jt506L4XfpDxTkpK/swDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowDTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmf
+8XVFVrwdXfdlJsMkFh7dvT3I2Eh4MFFeETf8rKe9lVIFv0B3IjJzXE+wjc/BpEPY
+Yhh/1Bhi+SBSMUuxUwwmOmLhcUQNMVbWhy3n5JoOyt0tq9KsgjrUEFVcmz0eoWbd
+/NkiaDsKp7d8Nvuz1JEX0JLZf+JpyoMG8gT2Qxl2QZeY2z0BsnHnfTzsoARm9nZP
+cMhH9scqoVbPEEST5PlgB0Z8T2974fH9VHNFdbaml37p7aQMnt+g/NusKEXZlcTJ
+bIN7pQyp9rkCQV9Tiw5EvnZK0AWQF6ymL9WD7O5reyPzQ6kvRNJrmxKzs9mocj9J
+Y4fc7KGQ3CBwfGEHLdg=
+-----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..55353b8eb7
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-o.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuDCCAaCgAwIBAgIUaRRl+1wXs1a/UB82ZaCqoPtH0NswDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowGzEMMAoGA1UECgwDQmF6MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhX
+bCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQ
+OCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9
+uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFb
+t+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhO
+NsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEAoZ6x5m03rOaM4Z2H1ZCu0gLLdoPgYvbTYvDQBMzea84mT8Zz
+D7UKQGIA2o72dhQY7xzs1rhNZJlog/hP/Xtve9vN/do2QPOlTuK/bKV92CdW3B+J
+D0xn8FM8qoMkFnwAIojhuRHJ5/CrnSrbBj5DPTBOktYW2mLGvFvMvGkZe0IMcSKO
+LhOrP1ZBn0jWGGmuGdtFnyiV2IGZMoXIYHGYWLLvzMbesNY3P90tWnzc1m8TTWZg
++KTj479e2DvWsxvVu5rG+oMLI65m+YbMmz93R7BYfO3exkzVK6ZDmZvxr5ggivCw
+HsselmxKcSpJbJuY6DvRtu9TwS3NJwCQJMJwPQ==
+-----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..7fc4dc3a64
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-ou.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxjCCAa6gAwIBAgIUU4uHxO/2rTwK9zo7UktMNOPJfP0wDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowKTEMMAoGA1UECwwDQmFyMQwwCgYDVQQKDANCYXoxCzAJBgNVBAYTAlVT
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2
+ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF
+h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n
+cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv
+OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj
+tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt
+jQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAv92wXP+0GoQ1xAUFK2uijpyx+3dwb
+EaNQ10Ny8PQ7u/Vu7xPq3seYp4eVmJphQGzfS2oPMyqKrLI3E++2ohThqdE5NJQd
+voXGTrAvNgAVlW1l3E/KIUs/CChG9gO+iPBbWUJ7oklajESuJmHFefP7pFsmrkRI
+7yaB04F63ymPJIwBboVPFks0HPmf1veTXzi85Lm2rr8Y6+yk4REIkvF2RdWYhAmZ
+PoXSmfcBxuZlG0+tRu7ZzfyhLCQIeunJjyXeJiVEpd2/ISlP+tQUs9RNEc6eQnUO
+Uszfc7n1zKcWnykyEDaa9P8DW0RIUjqy6cfVaFDI4I5Vv8S5f/fU/HVM
+-----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..41593abcd3
--- /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..08d8d31c9f
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/intermediate.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIUFfVirSKu6HtZb2LYCmJb2aHMq0wwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAwMDAw
+MFowQTEoMCYGA1UEAwwfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEV
+MBMGA1UECwwMSW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGc
+BptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzC
+a2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8Xg
+uEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK
+9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGP
+mRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRMEBTADAQH/MAsG
+A1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAdKOLt5qF0rjrdg+yVRYeeFQM
+wF2w3aYSqjFLC0pxjYxta3TZVYA8JsErqIefsaU+lvURUFpAZxBhocySXOOkjhdo
+BtJDa5XpPmcRbLpj3YmL1TxBsgkyVJyWHg8RczxE2RckROAIrk/EoQU7yi4kjXxr
+g/uaxekzGYq2dysPR4+Pm1HfnpBRXVQ20P77AJmYrB6HFGHTXR7FsU2CdXQU5D4K
+UfmyBbJA2jfZZLl05rPkJNHCoI3WWdMDyGEMo+cuYJ3/lfPE7k7LGANrn1fUkC8u
+RgXoGKW/UBpLGsluYwMDiSGrsUe2G3Iv63K0Z+HYEMs/Ez03ueph5R2aanVeHQ==
+-----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..0fabf3e81c
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/invalid.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICwDCCAaigAwIBAgIUTcJUGOsO/pcSk2CcjS3L0FXun4swDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowEjEQMA4GA1UEAwwHaW52YWxpZDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMPMA0wCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBCwUAA4IBAQAHRQ4x0bpexE1zpa7fbvNqxmhR8ybYTMVEHveL
+NxAhaxpvN2wjIRbB5VsAk38UunB3J3RvqkmOHYvGwCJZNQhe0RUsg+yCh+yK3bSe
+30Q0uKXdqqtAsCyBhTBxp3hqFzaJgz9hzUUAqSHHCO8jZUugu+RNq2uv6bMYBY1S
+YW2QiJG1yur2PLtQsG+NCd+S/4KDi+7N1w+5rD/CtDD0H0vAVvXXNuIjUjFuADlV
+hghm2rF5N7i5T1mroDeQT2l89XEQghB+cGtdgbzOBucAI8v3rOV/Qg8xk1l61nYz
+8xh7zBXdrByqd1mtTnyOBtzNIjLLveqgQxo1QIKh19vYrW3k
+-----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..d199f88a01
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/longOID.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIESjCCAzKgAwIBAgIUbSwzdRwYtipF7m7s7eZIseaX64wwDQYJKoZIhvcNAQEL
+BQAwEzERMA8GA1UEAwwITG9uZyBPSUQwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0
+MDIwNTAwMDAwMFowEzERMA8GA1UEAwwITG9uZyBPSUQwggEiMA0GCSqGSIb3DQEB
+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/ATANBgkqhkiG9w0BAQsFAAOCAQEAbq2wf1RHXZAU9iD60DX8B52M
+zz0n2Bv3R4rmqJeoFlvgRhnInb0c5N5ceqbjzGE2QTEmjgDhyy5wiU70yLblkyNz
+IWxYxg3Qzjfcibt9AQYYoHXS8rEAKJ5oTWrVExPLCsSnmzL8TDwEb9v/noOwdilU
+98BD3H+5hQMiYZIVo66Jq5uexLV69DH0+pI1n68pKtFtABpX1grjhEVC670mb5uO
+YoMl4GGqFhkmFwpiOcFKcvESfHZdUhzTiPa86rU8LGfA5YyaQMStUWb6jFaCt+LK
+bKyf4De9/3XZpsRoPX7VscBtBLhhnvZERkITAt1ovbZmX4IY9Yh6Jo7Z9urzqg==
+-----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..b65bd1a9e9
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/md5-ee.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrjCCAZagAwIBAgIUU6LuY45SDmIYSOVybKlfPm4PkDMwDQYJKoZIhvcNAQEE
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowETEPMA0GA1UEAwwGbWQ1LWVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4Ngf
+vbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTb
+uUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3S
+O8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR
+3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv
+5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMA0GCSqGSIb3DQEBBAUAA4IB
+AQCmzCGveXJAR75YgRtydRG1y2yxonzm5bKb1sdk4y0Oma7cMeibR7QAz8kCxu3J
+jqc7B1f7G2RD0uWe7Am8diTfPiuDhTJY9kSFqiigrdFB7tpvWUYbfo+jSnfXK8+o
+jhaNGAqz42yrcwxyUHTzUG1jSe6bQYoQu3evJTb31g6VYCHG+QcF5BUMKu3MkCQ6
+L5dK4+4m6ocLycTBGXkcjf0PEhYkyUN2rSYPykNMkEdYx3VVNLbnMqVkC1ozqA4U
+AZuTAZmDeGPzOPtEm85x730nO1DH2MJClZcNHc5iEUiTD3B78EJAtPdgBRfvf6K/
+BCCj6SSNUceqcvU1DkB9tj4U
+-----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..67b1ac4bbb
--- /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.ini"]
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..8017659689
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-all-usages.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAmugAwIBAgIUIVidj/ZVgLtZWGWK7cZ8hRvkLCIwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAwMDAw
+MFowajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQD
+AgH+MA0GCSqGSIb3DQEBCwUAA4IBAQBbElkqm+Dtof/toa8F2jT0n4u+MSeVCV8v
+uGzZb34g8j/VPNKqaX3KYFqKhRjQvG/ScFn2xQRVEaX402qCPZkrSFNI0+sGERBo
+MydN0awvZEWn3AEMccbXH7ZzBXNAqu7iNtkUns0Gde9S362ejlEXhlEewFQhhlEs
+LBzUrjiW8AiKG8keFiajEgO8yDbtEjy8z9MPkK23oLBgtVQtg1GO3X2rUxHGZwkE
+Ck8ikXVoaw7olYchAhwFTYtbK7+Pwq1U6Rs/1CWSla++6SAh/uasDnnDziLJj3z+
+MBv2WVFfBpRTCr0IdF/fOqsC5utncdXZe8qwViS5/JzZbySctrbO
+-----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..502632f5a3
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/pgo-ca-regular-usages.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAmugAwIBAgIUU34Ahx5Y3OrxTZ8Zij+34BQHt4AwDQYJKoZIhvcNAQEL
+BQAwajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAwMDAw
+MFowajEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEY
+MBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSQwIgYDVQQLExtQcm9maWxlIEd1aWRl
+ZCBPcHRpbWl6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr
+4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP
+8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OI
+Q+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ
+77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5J
+I/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQD
+AgEGMA0GCSqGSIb3DQEBCwUAA4IBAQCqS4h6tvzNQUKlLt3gdUuOjviVTfOhc2qA
+w0TmWZUY2DDfIo1jggqH2yIikPGByOfkEJ3VICRk0N6Ivk6Nh3EZyp/mmebpGOIV
+Qz9itOzknvny33LwVFovwer4hnHXGKIrcGDIORNWt6BGJ0oREji6bZ8Py12ZuMJ3
+stXdKN3bOrVqrDbCBAmaXuqtngpob5TRCP5/6WJDaFE3JjKOpY+JIwXFSotA+/PO
+aNRF5kGRyBsuESb3l5UNMckwMZedxYCndT4ntGxoA/YJ/lo0boqaP4lVqpbabIqp
+G42FXocz+dpeWPZYFZEx/hNBQRqXNegIuzv7U4rdeHfbuyxDJhXo
+-----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..db8fca458a
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/revoked.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrzCCAZegAwIBAgIUOZGi7rHuV7JtJp8SD/Di7lbe3vkwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowEjEQMA4GA1UEAwwHcmV2b2tlZDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODY
+H72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk
+27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A9
+0jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMM
+kd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaL
+L+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkqhkiG9w0BAQsFAAOC
+AQEAYK8T1YdY7LQ42fkIm48FNnEduEul9ISTjgqpcGscFAEaUtlfb+LoYFXJJh1M
+rcw1WtNaMoA7UMjnj6+18RXi6wTuJiCK9RiLdBFDbZUf8mbavbf69WAwgW1ekT5P
+KNZ+OCQAbw2F41u3MTA4tEybPd9I/fkUiuHwVAFQh4seSOq5WGuu1INgEhHd27R9
+zLRVw37WZIo6DusMt+hmE0KyMNtpoBsGvtMBhnVTA/Hox7DyYoYTh2b8N7zDSda8
+nfon3Zw1ZvoHXw/0p0kfftdrychilcNjequSF1e0PvWC5TnGF7rRck0BHI0Mg00I
+Nej1od23fhK305jDXk6oJ3VqJg==
+-----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..0ea778c9c3
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/ssl-ee.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIUFRaHxC8q2vvr6gyB8AMvtVwPTeYwDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowETEPMA0GA1UEAwwGc3NsLWVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4Ngf
+vbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTb
+uUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3S
+O8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR
+3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv
+5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoyEwHzAdBgNVHSUEFjAUBggr
+BgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAB0FoJO4EuzTF5o0
+mfFcJV3ORxRDTKUqrcE1cl0S0wcFJ7T8A+XEFnAP/YqxFgFCjbpAtJwxv4P3IOPv
+b6dGhSG9rEHJUnVFoKcHnoajzFnn741J0/i6Lkux2tM/eF0sTnADVxmlklt7848h
+0DWNrRY/NbouJjQy8yxVMIrJLSWR9G0fzXLemieXnVKA7MGXH7cbECC8gWYgsuEP
+pTdJ0r9OsTfgLAoW34RxGFSl/l0gmFuinbbqLVGCUQ6TIeLfn9bmKgMxat+TdT6L
+w5k/S3w1bm69naK1YdTGQVFIO4wrNPHOAUXxrcziMd56oMnXmcuG/Ef9N3zqB5/X
+8Q5H9gE=
+-----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..5154c97ccd
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/unknown-issuer.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaOgAwIBAgIUBdsczlklj8pHAhNbKaxiu+leXzMwDQYJKoZIhvcNAQEL
+BQAwEjEQMA4GA1UEAwwHdW5rbm93bjAiGA8yMDIxMTEyNzAwMDAwMFoYDzIwMjQw
+MjA1MDAwMDAwWjAZMRcwFQYDVQQDDA51bmtub3duLWlzc3VlcjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs
+9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8
+HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7Ak
+kqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJet
+lmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2r
+kQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATANBgkq
+hkiG9w0BAQsFAAOCAQEAFUiE73r6gX+NDLhNvEOHBxSFYVCdvMSJ3WSojaF6JKht
+bXdx1/M/TqC0cKi/m4pNEW/2VVjOyibc3qRb/X+TRcHZz3PaSA5cYRfoJVPyH+k6
+LAkGmeGLlCNYFrtcfu9qQ+/ap2z5XzmrJa76KxEXW/RjR16sfCIfRP7HGJZ3fYsB
+JC91LSzGtIMrqA5K97tolp09cC2cIojNXSlaXwQqVn+2uMQdLMcecFcth1P5Sp02
+rBbzlzCZUl0XVhmAOeEefhak2dnwTG6vRZlbim927/8DzPIEgwmZbvjIrpmYQMZx
+GoVdCCgwSbC/uM8tI+LebrJCXqP0VRiZrc3PMbcUUw==
+-----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..4f09a837ae
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/untrusted-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0zCCAbugAwIBAgIUJ52B/yodNzE1jqzgJSkNIQMaj1swDQYJKoZIhvcNAQEL
+BQAwDTELMAkGA1UEAwwCY2EwIhgPMjAyMTExMjcwMDAwMDBaGA8yMDI0MDIwNTAw
+MDAwMFowFzEVMBMGA1UEAwwMdW50cnVzdGVkLWNhMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvB
+xyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmT
+qyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5
+kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYS
+wHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwk
+BCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRME
+BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAoZWuIsn8xdiC
+OyqqSA4hpY0wVq6xutUD2WlV8zDLf4by2C8Tno7NMMC1a/+UuQlmfLs1ztb3RPgj
+hJ/24fGuGnP/E71VwrZ/IR2mQBhRdo1ILZeHSDuTKqXGBds5SBHd77NIIMSmbU7w
+dhy6bh6Qbh9WYIoHA5h/QGn5x7+b90I0yzTLPkpEV7/v++VWt6hIU/Xc+TvJ1g8M
+uR9QKz9EQzbuVytTXGUzFaUattCtaPKxikf8xhHqcZypjU7r1FEa/JAqUlYWiEew
+V+7I4IsgpPbOUZoUKU1R/KzHObVKHcmsxBhQ5FYIW8tiXqkFAxp9I4hgAuu9yOB0
+3EV9PsnIrQ==
+-----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