// -*- 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/. // // For all cases, the acceptable pinset includes only certificates pinned to // Test End Entity Cert (signed by issuer testCA). Other certificates // are issued by otherCA, which is never in the pinset but is a user-specified // trust anchor. This test covers multiple cases: // // Pinned domain include-subdomains.pinning.example.com includes subdomains // - PASS: include-subdomains.pinning.example.com serves a correct cert // - PASS: good.include-subdomains.pinning.example.com serves a correct cert // - FAIL (strict): bad.include-subdomains.pinning.example.com serves a cert // not in the pinset // - PASS (mitm): bad.include-subdomains.pinning.example.com serves a cert not // in the pinset, but issued by a user-specified trust domain // // Pinned domain exclude-subdomains.pinning.example.com excludes subdomains // - PASS: exclude-subdomains.pinning.example.com serves a correct cert // - FAIL: exclude-subdomains.pinning.example.com serves an incorrect cert // (TODO: test using verifyCertNow) // - PASS: sub.exclude-subdomains.pinning.example.com serves an incorrect cert "use strict"; do_get_profile(); // must be called before getting nsIX509CertDB const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( Ci.nsIX509CertDB ); function add_clear_override(host) { add_test(function() { let certOverrideService = Cc[ "@mozilla.org/security/certoverride;1" ].getService(Ci.nsICertOverrideService); certOverrideService.clearValidityOverride(host, 8443, {}); run_next_test(); }); } function test_strict() { // In strict mode, we always evaluate pinning data, regardless of whether the // issuer is a built-in trust anchor. We only enforce pins that are not in // test mode. add_test(function() { Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2); run_next_test(); }); // Normally this is overridable. But, since we have pinning information for // this host, we don't allow overrides. add_prevented_cert_override_test( "unknownissuer.include-subdomains.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.include-subdomains.pinning.example.com"); // Issued by otherCA, which is not in the pinset for pinning.example.com. add_connection_test( "bad.include-subdomains.pinning.example.com", MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ); // Check that using a FQDN doesn't bypass pinning. add_connection_test( "bad.include-subdomains.pinning.example.com.", MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ); // For some reason this is also navigable (see bug 1118522). add_connection_test( "bad.include-subdomains.pinning.example.com..", MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ); // These domains serve certs that match the pinset. add_connection_test( "include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "good.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); // This domain serves a cert that doesn't match the pinset, but subdomains // are excluded. add_connection_test( "sub.exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); // This domain's pinset is exactly the same as // include-subdomains.pinning.example.com, serves the same cert as // bad.include-subdomains.pinning.example.com, but it should pass because // it's in test_mode. add_connection_test("test-mode.pinning.example.com", PRErrorCodeSuccess); // Similarly, this pin is in test-mode, so it should be overridable. add_cert_override_test( "unknownissuer.test-mode.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.test-mode.pinning.example.com"); } function test_mitm() { // In MITM mode, we allow pinning to pass if the chain resolves to any // user-specified trust anchor, even if it is not in the pinset. add_test(function() { Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1); run_next_test(); }); add_connection_test( "include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "good.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); // Normally this is overridable. But, since we have pinning information for // this host, we don't allow overrides (since building a trusted chain fails, // we have no reason to believe this was issued by a user-added trust // anchor, so we can't allow overrides for it). add_prevented_cert_override_test( "unknownissuer.include-subdomains.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.include-subdomains.pinning.example.com"); // In this case, even though otherCA is not in the pinset, it is a // user-specified trust anchor and the pinning check succeeds. add_connection_test( "bad.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "sub.exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test("test-mode.pinning.example.com", PRErrorCodeSuccess); add_cert_override_test( "unknownissuer.test-mode.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.test-mode.pinning.example.com"); } function test_disabled() { // Disable pinning. add_test(function() { Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 0); run_next_test(); }); add_connection_test( "include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "good.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "bad.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "sub.exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test("test-mode.pinning.example.com", PRErrorCodeSuccess); add_cert_override_test( "unknownissuer.include-subdomains.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.include-subdomains.pinning.example.com"); add_cert_override_test( "unknownissuer.test-mode.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.test-mode.pinning.example.com"); } function test_enforce_test_mode() { // In enforce test mode, we always enforce all pins, even test pins. add_test(function() { Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 3); run_next_test(); }); // Normally this is overridable. But, since we have pinning information for // this host, we don't allow overrides. add_prevented_cert_override_test( "unknownissuer.include-subdomains.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.include-subdomains.pinning.example.com"); // Issued by otherCA, which is not in the pinset for pinning.example.com. add_connection_test( "bad.include-subdomains.pinning.example.com", MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ); // These domains serve certs that match the pinset. add_connection_test( "include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "good.include-subdomains.pinning.example.com", PRErrorCodeSuccess ); add_connection_test( "exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); // This domain serves a cert that doesn't match the pinset, but subdomains // are excluded. add_connection_test( "sub.exclude-subdomains.pinning.example.com", PRErrorCodeSuccess ); // This domain's pinset is exactly the same as // include-subdomains.pinning.example.com, serves the same cert as // bad.include-subdomains.pinning.example.com, is in test-mode, but we are // enforcing test mode pins. add_connection_test( "test-mode.pinning.example.com", MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ); // Normally this is overridable. But, since we have pinning information for // this host (and since we're enforcing test mode), we don't allow overrides. add_prevented_cert_override_test( "unknownissuer.test-mode.pinning.example.com", SEC_ERROR_UNKNOWN_ISSUER ); add_clear_override("unknownissuer.test-mode.pinning.example.com"); } function check_pinning_telemetry() { let prod_histogram = Services.telemetry .getHistogramById("CERT_PINNING_RESULTS") .snapshot(); let test_histogram = Services.telemetry .getHistogramById("CERT_PINNING_TEST_RESULTS") .snapshot(); // Because all of our test domains are pinned to user-specified trust // anchors, effectively only strict mode and enforce test-mode get evaluated equal( prod_histogram.values[0], 4, "Actual and expected prod (non-Mozilla) failure count should match" ); equal( prod_histogram.values[1], 6, "Actual and expected prod (non-Mozilla) success count should match" ); equal( test_histogram.values[0], 2, "Actual and expected test (non-Mozilla) failure count should match" ); equal( test_histogram.values[1] || 0, 0, "Actual and expected test (non-Mozilla) success count should match" ); run_next_test(); } function run_test() { // Ensure that static pinning works when HPKP is disabled. Services.prefs.setBoolPref("security.cert_pinning.hpkp.enabled", false); add_tls_server_setup("BadCertAndPinningServer", "bad_certs"); // Add a user-specified trust anchor. addCertFromFile(certdb, "bad_certs/other-test-ca.pem", "CTu,u,u"); test_strict(); test_mitm(); test_disabled(); test_enforce_test_mode(); add_test(function() { check_pinning_telemetry(); }); run_next_test(); }