summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_cert_blocklist.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/tests/unit/test_cert_blocklist.js')
-rw-r--r--security/manager/ssl/tests/unit/test_cert_blocklist.js362
1 files changed, 362 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/test_cert_blocklist.js b/security/manager/ssl/tests/unit/test_cert_blocklist.js
new file mode 100644
index 000000000..75688b58a
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_blocklist.js
@@ -0,0 +1,362 @@
+/* -*- 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";
+
+// This test checks a number of things:
+// * it ensures that data loaded from revocations.txt on startup is present
+// * it ensures that certItems in blocklist.xml are persisted correctly
+// * it ensures that items in the CertBlocklist are seen as revoked by the
+// cert verifier
+// * it does a sanity check to ensure other cert verifier behavior is
+// unmodified
+
+// First, we need to setup appInfo for the blocklist service to work
+var id = "xpcshell@tests.mozilla.org";
+var appName = "XPCShell";
+var version = "1";
+var platformVersion = "1.9.2";
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+/*global updateAppInfo:false*/ // Imported via AppInfo.jsm.
+updateAppInfo({
+ name: appName,
+ ID: id,
+ version: version,
+ platformVersion: platformVersion ? platformVersion : "1.0",
+ crashReporter: true,
+});
+
+// we need to ensure we setup revocation data before certDB, or we'll start with
+// no revocation.txt in the profile
+var gProfile = do_get_profile();
+
+// Write out an empty blocklist.xml file to the profile to ensure nothing
+// is blocklisted by default
+var blockFile = gProfile.clone();
+blockFile.append("blocklist.xml");
+var stream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+stream.init(blockFile,
+ FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
+ FileUtils.PERMS_FILE, 0);
+
+var data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">\n" +
+ "</blocklist>\n";
+stream.write(data, data.length);
+stream.close();
+
+const PREF_BLOCKLIST_UPDATE_ENABLED = "services.blocklist.update_enabled";
+const PREF_ONECRL_VIA_AMO = "security.onecrl.via.amo";
+
+var gRevocations = gProfile.clone();
+gRevocations.append("revocations.txt");
+if (!gRevocations.exists()) {
+ let existing = do_get_file("test_onecrl/sample_revocations.txt", false);
+ existing.copyTo(gProfile, "revocations.txt");
+}
+
+var certDB = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+
+// set up a test server to serve the blocklist.xml
+var testserver = new HttpServer();
+
+var initialBlocklist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+ "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
+ // test with some bad data ...
+ "<certItems><certItem issuerName='Some nonsense in issuer'>" +
+ "<serialNumber>AkHVNA==</serialNumber>" +
+ "</certItem><certItem issuerName='MA0xCzAJBgNVBAMMAmNh'>" +
+ "<serialNumber>some nonsense in serial</serialNumber>" +
+ "</certItem><certItem issuerName='some nonsense in both issuer'>" +
+ "<serialNumber>and serial</serialNumber></certItem>" +
+ // some mixed
+ // In this case, the issuer name and the valid serialNumber correspond
+ // to test-int.pem in bad_certs/
+ "<certItem issuerName='MBIxEDAOBgNVBAMMB1Rlc3QgQ0E='>" +
+ "<serialNumber>oops! more nonsense.</serialNumber>" +
+ "<serialNumber>BVio/iQ21GCi2iUven8oJ/gae74=</serialNumber></certItem>" +
+ // ... and some good
+ // In this case, the issuer name and the valid serialNumber correspond
+ // to other-test-ca.pem in bad_certs/ (for testing root revocation)
+ "<certItem issuerName='MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E='>" +
+ "<serialNumber>exJUIJpq50jgqOwQluhVrAzTF74=</serialNumber></certItem>" +
+ // This item corresponds to an entry in sample_revocations.txt where:
+ // isser name is "another imaginary issuer" base-64 encoded, and
+ // serialNumbers are:
+ // "serial2." base-64 encoded, and
+ // "another serial." base-64 encoded
+ // We need this to ensure that existing items are retained if they're
+ // also in the blocklist
+ "<certItem issuerName='YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy'>" +
+ "<serialNumber>c2VyaWFsMi4=</serialNumber>" +
+ "<serialNumber>YW5vdGhlciBzZXJpYWwu</serialNumber></certItem>" +
+ // This item revokes same-issuer-ee.pem by subject and pubKeyHash.
+ "<certItem subject='MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5'" +
+ " pubKeyHash='VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8='>" +
+ "</certItem></certItems></blocklist>";
+
+var updatedBlocklist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+ "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
+ "<certItems>" +
+ "<certItem issuerName='something new in both the issuer'>" +
+ "<serialNumber>and the serial number</serialNumber></certItem>" +
+ "</certItems></blocklist>";
+
+
+var blocklists = {
+ "/initialBlocklist/": initialBlocklist,
+ "/updatedBlocklist/": updatedBlocklist
+};
+
+function serveResponse(request, response) {
+ do_print("Serving for path " + request.path + "\n");
+ response.write(blocklists[request.path]);
+}
+
+for (var path in blocklists) {
+ testserver.registerPathHandler(path, serveResponse);
+}
+
+// start the test server
+testserver.start(-1);
+var port = testserver.identity.primaryPort;
+
+// Setup the addonManager
+var addonManager = Cc["@mozilla.org/addons/integration;1"]
+ .getService(Ci.nsIObserver)
+ .QueryInterface(Ci.nsITimerCallback);
+addonManager.observe(null, "addons-startup", null);
+
+var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+converter.charset = "UTF-8";
+
+function verify_cert(file, expectedError) {
+ let ee = constructCertFromFile(file);
+ checkCertErrorGeneric(certDB, ee, expectedError, certificateUsageSSLServer);
+}
+
+// The certificate blocklist currently only applies to TLS server certificates.
+function verify_non_tls_usage_succeeds(file) {
+ let ee = constructCertFromFile(file);
+ checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+ certificateUsageSSLClient);
+ checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+ certificateUsageEmailSigner);
+ checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+ certificateUsageEmailRecipient);
+}
+
+function load_cert(cert, trust) {
+ let file = "bad_certs/" + cert + ".pem";
+ addCertFromFile(certDB, file, trust);
+}
+
+function test_is_revoked(certList, issuerString, serialString, subjectString,
+ pubKeyString) {
+ let issuer = converter.convertToByteArray(issuerString ? issuerString : '',
+ {});
+
+ let serial = converter.convertToByteArray(serialString ? serialString : '',
+ {});
+
+ let subject = converter.convertToByteArray(subjectString ? subjectString : '',
+ {});
+
+ let pubKey = converter.convertToByteArray(pubKeyString ? pubKeyString : '',
+ {});
+
+ return certList.isCertRevoked(issuer,
+ issuerString ? issuerString.length : 0,
+ serial,
+ serialString ? serialString.length : 0,
+ subject,
+ subjectString ? subjectString.length : 0,
+ pubKey,
+ pubKeyString ? pubKeyString.length : 0);
+}
+
+function fetch_blocklist(blocklistPath) {
+ do_print("path is " + blocklistPath + "\n");
+ let certblockObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(this, "blocklist-updated");
+ run_next_test();
+ }
+ };
+
+ Services.obs.addObserver(certblockObserver, "blocklist-updated", false);
+ Services.prefs.setCharPref("extensions.blocklist.url",
+ `http://localhost:${port}/${blocklistPath}`);
+ let blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ blocklist.notify(null);
+}
+
+function check_revocations_txt_contents(expected) {
+ let profile = do_get_profile();
+ let revocations = profile.clone();
+ revocations.append("revocations.txt");
+ ok(revocations.exists(), "the revocations file should exist");
+ let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ inputStream.init(revocations, -1, -1, 0);
+ inputStream.QueryInterface(Ci.nsILineInputStream);
+ let contents = "";
+ let hasmore = false;
+ do {
+ let line = {};
+ hasmore = inputStream.readLine(line);
+ contents += (contents.length == 0 ? "" : "\n") + line.value;
+ } while (hasmore);
+ equal(contents, expected, "revocations.txt should be as expected");
+}
+
+function run_test() {
+ // import the certificates we need
+ load_cert("test-ca", "CTu,CTu,CTu");
+ load_cert("test-int", ",,");
+ load_cert("other-test-ca", "CTu,CTu,CTu");
+
+ let certList = Cc["@mozilla.org/security/certblocklist;1"]
+ .getService(Ci.nsICertBlocklist);
+
+ let expected = "# Auto generated contents. Do not edit.\n" +
+ "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5\n" +
+ "\tVCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=\n" +
+ "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=\n" +
+ " BVio/iQ21GCi2iUven8oJ/gae74=\n" +
+ "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=\n" +
+ " exJUIJpq50jgqOwQluhVrAzTF74=\n" +
+ "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy\n" +
+ " YW5vdGhlciBzZXJpYWwu\n" +
+ " c2VyaWFsMi4=";
+
+ // This test assumes OneCRL updates via AMO
+ Services.prefs.setBoolPref(PREF_BLOCKLIST_UPDATE_ENABLED, false);
+ Services.prefs.setBoolPref(PREF_ONECRL_VIA_AMO, true);
+
+ add_test(function () {
+ // check some existing items in revocations.txt are blocked. Since the
+ // CertBlocklistItems don't know about the data they contain, we can use
+ // arbitrary data (not necessarily DER) to test if items are revoked or not.
+ // This test corresponds to:
+ // issuer: c29tZSBpbWFnaW5hcnkgaXNzdWVy
+ // serial: c2VyaWFsLg==
+ ok(test_is_revoked(certList, "some imaginary issuer", "serial."),
+ "issuer / serial pair should be blocked");
+
+ // This test corresponds to:
+ // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
+ // serial: c2VyaWFsLg==
+ ok(test_is_revoked(certList, "another imaginary issuer", "serial."),
+ "issuer / serial pair should be blocked");
+
+ // And this test corresponds to:
+ // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
+ // serial: c2VyaWFsMi4=
+ // (we test this issuer twice to ensure we can read multiple serials)
+ ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+ "issuer / serial pair should be blocked");
+
+ // Soon we'll load a blocklist which revokes test-int.pem, which issued
+ // test-int-ee.pem.
+ // Check the cert validates before we load the blocklist
+ let file = "test_onecrl/test-int-ee.pem";
+ verify_cert(file, PRErrorCodeSuccess);
+
+ // The blocklist also revokes other-test-ca.pem, which issued
+ // other-ca-ee.pem. Check the cert validates before we load the blocklist
+ file = "bad_certs/other-issuer-ee.pem";
+ verify_cert(file, PRErrorCodeSuccess);
+
+ // The blocklist will revoke same-issuer-ee.pem via subject / pubKeyHash.
+ // Check the cert validates before we load the blocklist
+ file = "test_onecrl/same-issuer-ee.pem";
+ verify_cert(file, PRErrorCodeSuccess);
+
+ run_next_test();
+ });
+
+ // blocklist load is async so we must use add_test from here
+ add_test(function() {
+ fetch_blocklist("initialBlocklist/");
+ });
+
+ add_test(function() {
+ // The blocklist will be loaded now. Let's check the data is sane.
+ // In particular, we should still have the revoked issuer / serial pair
+ // that was in both revocations.txt and the blocklist.xml
+ ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+ "issuer / serial pair should be blocked");
+
+ // Check that both serials in the certItem with multiple serials were read
+ // properly
+ ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+ "issuer / serial pair should be blocked");
+ ok(test_is_revoked(certList, "another imaginary issuer", "another serial."),
+ "issuer / serial pair should be blocked");
+
+ // test a subject / pubKey revocation
+ ok(test_is_revoked(certList, "nonsense", "more nonsense",
+ "some imaginary subject", "some imaginary pubkey"),
+ "issuer / serial pair should be blocked");
+
+ // Check the blocklist entry has been persisted properly to the backing
+ // file
+ check_revocations_txt_contents(expected);
+
+ // Check the blocklisted intermediate now causes a failure
+ let file = "test_onecrl/test-int-ee.pem";
+ verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
+ verify_non_tls_usage_succeeds(file);
+
+ // Check the ee with the blocklisted root also causes a failure
+ file = "bad_certs/other-issuer-ee.pem";
+ verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
+ verify_non_tls_usage_succeeds(file);
+
+ // Check the ee blocked by subject / pubKey causes a failure
+ file = "test_onecrl/same-issuer-ee.pem";
+ verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
+ verify_non_tls_usage_succeeds(file);
+
+ // Check a non-blocklisted chain still validates OK
+ file = "bad_certs/default-ee.pem";
+ verify_cert(file, PRErrorCodeSuccess);
+
+ // Check a bad cert is still bad (unknown issuer)
+ file = "bad_certs/unknownissuer.pem";
+ verify_cert(file, SEC_ERROR_UNKNOWN_ISSUER);
+
+ // check that save with no further update is a no-op
+ let lastModified = gRevocations.lastModifiedTime;
+ // add an already existing entry
+ certList.revokeCertByIssuerAndSerial("YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
+ "c2VyaWFsMi4=");
+ certList.saveEntries();
+ let newModified = gRevocations.lastModifiedTime;
+ equal(lastModified, newModified,
+ "saveEntries with no modifications should not update the backing file");
+
+ run_next_test();
+ });
+
+ // disable AMO cert blocklist - and check blocklist.xml changes do not
+ // affect the data stored.
+ add_test(function() {
+ Services.prefs.setBoolPref("security.onecrl.via.amo", false);
+ fetch_blocklist("updatedBlocklist/");
+ });
+
+ add_test(function() {
+ // Check the blocklist entry has not changed
+ check_revocations_txt_contents(expected);
+ run_next_test();
+ });
+
+ run_next_test();
+}