summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_ev_certs.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/tests/unit/test_ev_certs.js')
-rw-r--r--security/manager/ssl/tests/unit/test_ev_certs.js437
1 files changed, 437 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/test_ev_certs.js b/security/manager/ssl/tests/unit/test_ev_certs.js
new file mode 100644
index 0000000000..937e4509f3
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_ev_certs.js
@@ -0,0 +1,437 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"use strict";
+
+// Tests that end-entity certificates that should successfully verify as EV
+// (Extended Validation) do so and that end-entity certificates that should not
+// successfully verify as EV do not. Also tests related situations (e.g. that
+// failure to fetch an OCSP response results in no EV treatment).
+//
+// A quick note about the certificates in these tests: generally, an EV
+// certificate chain will have an end-entity with a specific policy OID followed
+// by an intermediate with the anyPolicy OID chaining to a root with no policy
+// OID (since it's a trust anchor, it can be omitted). In these tests, the
+// specific policy OID is 1.3.6.1.4.1.13769.666.666.666.1.500.9.1 and is
+// referred to as the test OID. In order to reflect what will commonly be
+// encountered, the end-entity of any given test path will have the test OID
+// unless otherwise specified in the name of the test path. Similarly, the
+// intermediate will have the anyPolicy OID, again unless otherwise specified.
+// For example, for the path where the end-entity does not have an OCSP URI
+// (referred to as "no-ocsp-ee-path-{ee,int}", the end-entity has the test OID
+// whereas the intermediate has the anyPolicy OID.
+// For another example, for the test OID path ("test-oid-path-{ee,int}"), both
+// the end-entity and the intermediate have the test OID.
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("security.OCSP.enabled");
+});
+
+Services.prefs.setCharPref("network.dns.localDomains", "www.example.com");
+Services.prefs.setIntPref("security.OCSP.enabled", 1);
+const evroot = addCertFromFile(certdb, "test_ev_certs/evroot.pem", "CTu,,");
+addCertFromFile(certdb, "test_ev_certs/non-evroot-ca.pem", "CTu,,");
+
+const SERVER_PORT = 8888;
+
+function failingOCSPResponder() {
+ return getFailingHttpServer(SERVER_PORT, ["www.example.com"]);
+}
+
+class EVCertVerificationResult {
+ constructor(
+ testcase,
+ expectedPRErrorCode,
+ expectedEV,
+ resolve,
+ ocspResponder
+ ) {
+ this.testcase = testcase;
+ this.expectedPRErrorCode = expectedPRErrorCode;
+ this.expectedEV = expectedEV;
+ this.resolve = resolve;
+ this.ocspResponder = ocspResponder;
+ }
+
+ verifyCertFinished(prErrorCode, verifiedChain, hasEVPolicy) {
+ equal(
+ prErrorCode,
+ this.expectedPRErrorCode,
+ `${this.testcase} should have expected error code`
+ );
+ equal(
+ hasEVPolicy,
+ this.expectedEV,
+ `${this.testcase} should result in expected EV status`
+ );
+ this.ocspResponder.stop(this.resolve);
+ }
+}
+
+function asyncTestEV(
+ cert,
+ expectedPRErrorCode,
+ expectedEV,
+ expectedOCSPRequestPaths,
+ ocspResponseTypes = undefined
+) {
+ let now = Date.now() / 1000;
+ return new Promise((resolve, reject) => {
+ let ocspResponder =
+ expectedOCSPRequestPaths.length > 0
+ ? startOCSPResponder(
+ SERVER_PORT,
+ "www.example.com",
+ "test_ev_certs",
+ expectedOCSPRequestPaths,
+ expectedOCSPRequestPaths.slice(),
+ null,
+ ocspResponseTypes
+ )
+ : failingOCSPResponder();
+ let result = new EVCertVerificationResult(
+ cert.subjectName,
+ expectedPRErrorCode,
+ expectedEV,
+ resolve,
+ ocspResponder
+ );
+ certdb.asyncVerifyCertAtTime(
+ cert,
+ certificateUsageSSLServer,
+ 0,
+ "ev-test.example.com",
+ now,
+ result
+ );
+ });
+}
+
+function ensureVerifiesAsEV(testcase) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ let expectedOCSPRequestPaths = gEVExpected
+ ? [`${testcase}-int`, `${testcase}-ee`]
+ : [`${testcase}-ee`];
+ return asyncTestEV(
+ cert,
+ PRErrorCodeSuccess,
+ gEVExpected,
+ expectedOCSPRequestPaths
+ );
+}
+
+function ensureVerifiesAsEVWithNoOCSPRequests(testcase) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ return asyncTestEV(cert, PRErrorCodeSuccess, gEVExpected, []);
+}
+
+function ensureVerifiesAsDV(testcase, expectedOCSPRequestPaths = undefined) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ return asyncTestEV(
+ cert,
+ PRErrorCodeSuccess,
+ false,
+ expectedOCSPRequestPaths ? expectedOCSPRequestPaths : [`${testcase}-ee`]
+ );
+}
+
+function ensureVerificationFails(testcase, expectedPRErrorCode) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ return asyncTestEV(cert, expectedPRErrorCode, false, []);
+}
+
+function verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, expectSuccess) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ let now = Date.now() / 1000;
+ let expectedErrorCode = SEC_ERROR_POLICY_VALIDATION_FAILED;
+ if (expectSuccess && gEVExpected) {
+ expectedErrorCode = PRErrorCodeSuccess;
+ }
+ return new Promise((resolve, reject) => {
+ let ocspResponder = failingOCSPResponder();
+ let result = new EVCertVerificationResult(
+ cert.subjectName,
+ expectedErrorCode,
+ expectSuccess && gEVExpected,
+ resolve,
+ ocspResponder
+ );
+ let flags =
+ Ci.nsIX509CertDB.FLAG_LOCAL_ONLY | Ci.nsIX509CertDB.FLAG_MUST_BE_EV;
+ certdb.asyncVerifyCertAtTime(
+ cert,
+ certificateUsageSSLServer,
+ flags,
+ "ev-test.example.com",
+ now,
+ result
+ );
+ });
+}
+
+function ensureNoOCSPMeansNoEV(testcase) {
+ return verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, false);
+}
+
+function ensureVerifiesAsEVWithFLAG_LOCAL_ONLY(testcase) {
+ return verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, true);
+}
+
+function ensureOneCRLSkipsOCSPForIntermediates(testcase) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ return asyncTestEV(cert, PRErrorCodeSuccess, gEVExpected, [`${testcase}-ee`]);
+}
+
+function verifyWithDifferentOCSPResponseTypes(testcase, responses, expectEV) {
+ let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
+ addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
+ let expectedOCSPRequestPaths = gEVExpected
+ ? [`${testcase}-int`, `${testcase}-ee`]
+ : [`${testcase}-ee`];
+ let ocspResponseTypes = gEVExpected ? responses : responses.slice(1);
+ return asyncTestEV(
+ cert,
+ PRErrorCodeSuccess,
+ gEVExpected && expectEV,
+ expectedOCSPRequestPaths,
+ ocspResponseTypes
+ );
+}
+
+function ensureVerifiesAsEVWithOldIntermediateOCSPResponse(testcase) {
+ return verifyWithDifferentOCSPResponseTypes(
+ testcase,
+ ["longvalidityalmostold", "good"],
+ true
+ );
+}
+
+function ensureVerifiesAsDVWithOldEndEntityOCSPResponse(testcase) {
+ return verifyWithDifferentOCSPResponseTypes(
+ testcase,
+ ["good", "longvalidityalmostold"],
+ false
+ );
+}
+
+function ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(testcase) {
+ return verifyWithDifferentOCSPResponseTypes(
+ testcase,
+ ["good", "ancientstillvalid"],
+ false
+ );
+}
+
+// These should all verify as EV.
+add_task(async function plainExpectSuccessEVTests() {
+ await ensureVerifiesAsEV("anyPolicy-int-path");
+ await ensureVerifiesAsEV("test-oid-path");
+ await ensureVerifiesAsEV("cabforum-oid-path");
+ await ensureVerifiesAsEV("cabforum-and-test-oid-ee-path");
+ await ensureVerifiesAsEV("test-and-cabforum-oid-ee-path");
+ await ensureVerifiesAsEV("reverse-order-oids-path");
+ // In this case, the end-entity has both the CA/B Forum OID and the test OID
+ // (in that order). The intermediate has the CA/B Forum OID. Since the
+ // implementation uses the first EV policy it encounters in the end-entity as
+ // the required one, this successfully verifies as EV.
+ await ensureVerifiesAsEV("cabforum-and-test-oid-ee-cabforum-oid-int-path");
+});
+
+// These fail for various reasons to verify as EV, but fallback to DV should
+// succeed.
+add_task(async function expectDVFallbackTests() {
+ await ensureVerifiesAsDV("anyPolicy-ee-path");
+ await ensureVerifiesAsDV("non-ev-root-path");
+ await ensureVerifiesAsDV(
+ "no-ocsp-ee-path",
+ gEVExpected ? ["no-ocsp-ee-path-int"] : []
+ );
+ await ensureVerifiesAsDV("no-ocsp-int-path");
+ // In this case, the end-entity has the test OID and the intermediate has the
+ // CA/B Forum OID. Since the CA/B Forum OID is not treated the same as the
+ // anyPolicy OID, this will not verify as EV.
+ await ensureVerifiesAsDV("test-oid-ee-cabforum-oid-int-path");
+ // In this case, the end-entity has both the test OID and the CA/B Forum OID
+ // (in that order). The intermediate has only the CA/B Forum OID. Since the
+ // implementation uses the first EV policy it encounters in the end-entity as
+ // the required one, this fails to verify as EV.
+ await ensureVerifiesAsDV("test-and-cabforum-oid-ee-cabforum-oid-int-path");
+});
+
+// Test that removing the trust bits from an EV root causes verifications
+// relying on that root to fail (and then test that adding back the trust bits
+// causes the verifications to succeed again).
+add_task(async function evRootTrustTests() {
+ clearOCSPCache();
+ info("untrusting evroot");
+ certdb.setCertTrust(
+ evroot,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.UNTRUSTED
+ );
+ await ensureVerificationFails("test-oid-path", SEC_ERROR_UNKNOWN_ISSUER);
+ info("re-trusting evroot");
+ certdb.setCertTrust(
+ evroot,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ );
+ await ensureVerifiesAsEV("test-oid-path");
+});
+
+// Test that if FLAG_LOCAL_ONLY and FLAG_MUST_BE_EV are specified, that no OCSP
+// requests are made (this also means that nothing will verify as EV).
+add_task(async function localOnlyMustBeEVTests() {
+ clearOCSPCache();
+ await ensureNoOCSPMeansNoEV("anyPolicy-ee-path");
+ await ensureNoOCSPMeansNoEV("anyPolicy-int-path");
+ await ensureNoOCSPMeansNoEV("non-ev-root-path");
+ await ensureNoOCSPMeansNoEV("no-ocsp-ee-path");
+ await ensureNoOCSPMeansNoEV("no-ocsp-int-path");
+ await ensureNoOCSPMeansNoEV("test-oid-path");
+});
+
+// Under certain conditions, OneCRL allows us to skip OCSP requests for
+// intermediates.
+add_task(async function oneCRLTests() {
+ clearOCSPCache();
+
+ // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
+ Services.prefs.setIntPref(
+ "security.onecrl.maximum_staleness_in_seconds",
+ 108000
+ );
+ // set the blocklist-background-update-timer value to the recent past
+ Services.prefs.setIntPref(
+ "services.settings.security.onecrl.checked",
+ Math.floor(Date.now() / 1000) - 1
+ );
+ Services.prefs.setIntPref(
+ "app.update.lastUpdateTime.blocklist-background-update-timer",
+ Math.floor(Date.now() / 1000) - 1
+ );
+
+ await ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
+ await ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
+ await ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");
+
+ clearOCSPCache();
+ // disable OneCRL OCSP Skipping (no staleness allowed)
+ Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 0);
+ await ensureVerifiesAsEV("anyPolicy-int-path");
+ // Because the intermediate in this case is missing an OCSP URI, it will not
+ // validate as EV, but it should fall back to DV.
+ await ensureVerifiesAsDV("no-ocsp-int-path");
+ await ensureVerifiesAsEV("test-oid-path");
+
+ clearOCSPCache();
+ // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
+ Services.prefs.setIntPref(
+ "security.onecrl.maximum_staleness_in_seconds",
+ 108000
+ );
+ // set the blocklist-background-update-timer value to the more distant past
+ Services.prefs.setIntPref(
+ "services.settings.security.onecrl.checked",
+ Math.floor(Date.now() / 1000) - 108080
+ );
+ Services.prefs.setIntPref(
+ "app.update.lastUpdateTime.blocklist-background-update-timer",
+ Math.floor(Date.now() / 1000) - 108080
+ );
+ await ensureVerifiesAsEV("anyPolicy-int-path");
+ await ensureVerifiesAsDV("no-ocsp-int-path");
+ await ensureVerifiesAsEV("test-oid-path");
+
+ clearOCSPCache();
+ // test the OCSP behavior when services.settings.security.onecrl.checked is in the
+ // distant past and blacklist-background-update-timer is recent
+ // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
+ Services.prefs.setIntPref(
+ "security.onecrl.maximum_staleness_in_seconds",
+ 108000
+ );
+ // set the blocklist-background-update-timer value to the recent past
+ // (services.settings.security.onecrl.checked defaults to 0)
+ Services.prefs.setIntPref(
+ "app.update.lastUpdateTime.blocklist-background-update-timer",
+ Math.floor(Date.now() / 1000) - 1
+ );
+
+ await ensureVerifiesAsEV("anyPolicy-int-path");
+ await ensureVerifiesAsDV("no-ocsp-int-path");
+ await ensureVerifiesAsEV("test-oid-path");
+
+ clearOCSPCache();
+ // test the OCSP behavior when services.settings.security.onecrl.checked is recent
+ // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
+ Services.prefs.setIntPref(
+ "security.onecrl.maximum_staleness_in_seconds",
+ 108000
+ );
+ // now set services.settings.security.onecrl.checked to a recent value
+ Services.prefs.setIntPref(
+ "services.settings.security.onecrl.checked",
+ Math.floor(Date.now() / 1000) - 1
+ );
+ await ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
+ await ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
+ await ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");
+
+ Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds");
+ Services.prefs.clearUserPref("services.settings.security.onecrl.checked");
+ Services.prefs.clearUserPref(
+ "app.update.lastUpdateTime.blocklist-background-update-timer"
+ );
+});
+
+// Prime the OCSP cache and then ensure that we can validate certificates as EV
+// without hitting the network. There's two cases here: one where we simply
+// validate like normal and then check that the network was never accessed and
+// another where we use flags to mandate that the network not be used.
+add_task(async function ocspCachingTests() {
+ clearOCSPCache();
+
+ await ensureVerifiesAsEV("anyPolicy-int-path");
+ await ensureVerifiesAsEV("test-oid-path");
+
+ await ensureVerifiesAsEVWithNoOCSPRequests("anyPolicy-int-path");
+ await ensureVerifiesAsEVWithNoOCSPRequests("test-oid-path");
+
+ await ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("anyPolicy-int-path");
+ await ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("test-oid-path");
+});
+
+// Old-but-still-valid OCSP responses are accepted for intermediates but not
+// end-entity certificates (because of OCSP soft-fail this results in DV
+// fallback).
+add_task(async function oldOCSPResponseTests() {
+ clearOCSPCache();
+
+ await ensureVerifiesAsEVWithOldIntermediateOCSPResponse("anyPolicy-int-path");
+ await ensureVerifiesAsEVWithOldIntermediateOCSPResponse("test-oid-path");
+
+ clearOCSPCache();
+ await ensureVerifiesAsDVWithOldEndEntityOCSPResponse("anyPolicy-int-path");
+ await ensureVerifiesAsDVWithOldEndEntityOCSPResponse("test-oid-path");
+
+ clearOCSPCache();
+ await ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(
+ "anyPolicy-int-path"
+ );
+ await ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse("test-oid-path");
+});