/* -*- 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 = "\n" + "\n" + "\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 = "" + "" + // test with some bad data ... "" + "AkHVNA==" + "" + "some nonsense in serial" + "" + "and serial" + // some mixed // In this case, the issuer name and the valid serialNumber correspond // to test-int.pem in bad_certs/ "" + "oops! more nonsense." + "BVio/iQ21GCi2iUven8oJ/gae74=" + // ... 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) "" + "exJUIJpq50jgqOwQluhVrAzTF74=" + // 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 "" + "c2VyaWFsMi4=" + "YW5vdGhlciBzZXJpYWwu" + // This item revokes same-issuer-ee.pem by subject and pubKeyHash. "" + ""; var updatedBlocklist = "" + "" + "" + "" + "and the serial number" + ""; 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(); }