diff options
Diffstat (limited to 'security/manager/ssl/tests/unit/test_ev_certs.js')
-rw-r--r-- | security/manager/ssl/tests/unit/test_ev_certs.js | 356 |
1 files changed, 356 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 000000000..d4aabd888 --- /dev/null +++ b/security/manager/ssl/tests/unit/test_ev_certs.js @@ -0,0 +1,356 @@ +// -*- 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); + +do_register_cleanup(() => { + 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); +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(function* plainExpectSuccessEVTests() { + yield ensureVerifiesAsEV("anyPolicy-int-path"); + yield ensureVerifiesAsEV("test-oid-path"); + yield ensureVerifiesAsEV("cabforum-oid-path"); + yield ensureVerifiesAsEV("cabforum-and-test-oid-ee-path"); + yield ensureVerifiesAsEV("test-and-cabforum-oid-ee-path"); + yield 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. + yield 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(function* expectDVFallbackTests() { + yield ensureVerifiesAsDV("anyPolicy-ee-path"); + yield ensureVerifiesAsDV("non-ev-root-path"); + yield ensureVerifiesAsDV("no-ocsp-ee-path", + gEVExpected ? [ "no-ocsp-ee-path-int" ] : []); + yield 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. + yield 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. + yield 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(function* evRootTrustTests() { + clearOCSPCache(); + let evroot = certdb.findCertByNickname("evroot"); + do_print("untrusting evroot"); + certdb.setCertTrust(evroot, Ci.nsIX509Cert.CA_CERT, + Ci.nsIX509CertDB.UNTRUSTED); + yield ensureVerificationFails("test-oid-path", SEC_ERROR_UNKNOWN_ISSUER); + do_print("re-trusting evroot"); + certdb.setCertTrust(evroot, Ci.nsIX509Cert.CA_CERT, + Ci.nsIX509CertDB.TRUSTED_SSL); + yield 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(function* localOnlyMustBeEVTests() { + clearOCSPCache(); + yield ensureNoOCSPMeansNoEV("anyPolicy-ee-path"); + yield ensureNoOCSPMeansNoEV("anyPolicy-int-path"); + yield ensureNoOCSPMeansNoEV("non-ev-root-path"); + yield ensureNoOCSPMeansNoEV("no-ocsp-ee-path"); + yield ensureNoOCSPMeansNoEV("no-ocsp-int-path"); + yield ensureNoOCSPMeansNoEV("test-oid-path"); +}); + + +// Under certain conditions, OneCRL allows us to skip OCSP requests for +// intermediates. +add_task(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.blocklist.onecrl.checked", + Math.floor(Date.now() / 1000) - 1); + Services.prefs.setIntPref( + "app.update.lastUpdateTime.blocklist-background-update-timer", + Math.floor(Date.now() / 1000) - 1); + + yield ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path"); + yield ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path"); + yield ensureOneCRLSkipsOCSPForIntermediates("test-oid-path"); + + clearOCSPCache(); + // disable OneCRL OCSP Skipping (no staleness allowed) + Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 0); + yield 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. + yield ensureVerifiesAsDV("no-ocsp-int-path"); + yield 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.blocklist.onecrl.checked", + Math.floor(Date.now() / 1000) - 108080); + Services.prefs.setIntPref( + "app.update.lastUpdateTime.blocklist-background-update-timer", + Math.floor(Date.now() / 1000) - 108080); + yield ensureVerifiesAsEV("anyPolicy-int-path"); + yield ensureVerifiesAsDV("no-ocsp-int-path"); + yield ensureVerifiesAsEV("test-oid-path"); + + clearOCSPCache(); + // test that setting "security.onecrl.via.amo" results in the correct + // OCSP behavior when services.blocklist.onecrl.checked is in the distant past + // and blacklist-background-update-timer is recent + Services.prefs.setBoolPref("security.onecrl.via.amo", false); + // 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.blocklist.onecrl.checked defaults to 0) + Services.prefs.setIntPref( + "app.update.lastUpdateTime.blocklist-background-update-timer", + Math.floor(Date.now() / 1000) - 1); + + yield ensureVerifiesAsEV("anyPolicy-int-path"); + yield ensureVerifiesAsDV("no-ocsp-int-path"); + yield ensureVerifiesAsEV("test-oid-path"); + + clearOCSPCache(); + // test that setting "security.onecrl.via.amo" results in the correct + // OCSP behavior when services.blocklist.onecrl.checked is recent + Services.prefs.setBoolPref("security.onecrl.via.amo", false); + // enable OneCRL OCSP skipping - allow staleness of up to 30 hours + Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", + 108000); + // now set services.blocklist.onecrl.checked to a recent value + Services.prefs.setIntPref("services.blocklist.onecrl.checked", + Math.floor(Date.now() / 1000) - 1); + yield ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path"); + yield ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path"); + yield ensureOneCRLSkipsOCSPForIntermediates("test-oid-path"); + + Services.prefs.clearUserPref("security.onecrl.via.amo"); + Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds"); + Services.prefs.clearUserPref("services.blocklist.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(function* ocspCachingTests() { + clearOCSPCache(); + + yield ensureVerifiesAsEV("anyPolicy-int-path"); + yield ensureVerifiesAsEV("test-oid-path"); + + yield ensureVerifiesAsEVWithNoOCSPRequests("anyPolicy-int-path"); + yield ensureVerifiesAsEVWithNoOCSPRequests("test-oid-path"); + + yield ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("anyPolicy-int-path"); + yield 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(function* oldOCSPResponseTests() { + clearOCSPCache(); + + yield ensureVerifiesAsEVWithOldIntermediateOCSPResponse("anyPolicy-int-path"); + yield ensureVerifiesAsEVWithOldIntermediateOCSPResponse("test-oid-path"); + + clearOCSPCache(); + yield ensureVerifiesAsDVWithOldEndEntityOCSPResponse("anyPolicy-int-path"); + yield ensureVerifiesAsDVWithOldEndEntityOCSPResponse("test-oid-path"); + + clearOCSPCache(); + yield ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse( + "anyPolicy-int-path"); + yield ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse("test-oid-path"); +}); |