// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- // Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ "use strict"; // Tests that PSM makes the correct determination of the security status of // loads involving session resumption (i.e. when a TLS handshake bypasses the // AuthCertificate callback). do_get_profile(); const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( Ci.nsIX509CertDB ); registerCleanupFunction(() => { Services.prefs.clearUserPref("security.OCSP.enabled"); }); Services.prefs.setIntPref("security.OCSP.enabled", 1); addCertFromFile(certdb, "bad_certs/evroot.pem", "CTu,,"); addCertFromFile(certdb, "bad_certs/ev-test-intermediate.pem", ",,"); // For expired.example.com, the platform will make a connection that will fail. // Using information gathered at that point, an override will be added and // another connection will be made. This connection will succeed. At that point, // as long as the session cache isn't cleared, subsequent new connections should // use session resumption, thereby bypassing the AuthCertificate hook. We need // to ensure that the correct security state is propagated to the new connection // information object. function add_resume_non_ev_with_override_test() { // This adds the override and makes one successful connection. add_cert_override_test("expired.example.com", SEC_ERROR_EXPIRED_CERTIFICATE); // This connects again, using session resumption. Note that we don't clear // the TLS session cache between these operations (that would defeat the // purpose). add_connection_test( "expired.example.com", PRErrorCodeSuccess, null, transportSecurityInfo => { ok(transportSecurityInfo.resumed, "connection should be resumed"); ok( transportSecurityInfo.securityState & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN, "expired.example.com should have STATE_CERT_USER_OVERRIDDEN flag" ); equal( transportSecurityInfo.succeededCertChain.length, 0, "expired.example.com should not have succeededCertChain set" ); equal( transportSecurityInfo.failedCertChain.length, 2, "expired.example.com should have failedCertChain set" ); equal( transportSecurityInfo.overridableErrorCategory, Ci.nsITransportSecurityInfo.ERROR_TIME, "expired.example.com should have time overridable error category" ); ok( !transportSecurityInfo.isExtendedValidation, "expired.example.com should not have isExtendedValidation set" ); let certOverrideService = Cc[ "@mozilla.org/security/certoverride;1" ].getService(Ci.nsICertOverrideService); certOverrideService.clearValidityOverride( "expired.example.com", 8443, {} ); } ); } // Helper function that adds a test that connects to ev-test.example.com and // verifies that it validates as EV (or not, if we're running a non-debug // build). This assumes that an appropriate OCSP responder is running or that // good responses are cached. function add_one_ev_test(resumed) { add_connection_test( "ev-test.example.com", PRErrorCodeSuccess, null, transportSecurityInfo => { equal( transportSecurityInfo.resumed, resumed, "connection should be resumed or not resumed as expected" ); ok( !( transportSecurityInfo.securityState & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN ), "ev-test.example.com should not have STATE_CERT_USER_OVERRIDDEN flag" ); equal( transportSecurityInfo.succeededCertChain.length, 3, "ev-test.example.com should have succeededCertChain set" ); equal( transportSecurityInfo.failedCertChain.length, 0, "ev-test.example.com should not have failedCertChain set" ); equal( transportSecurityInfo.overridableErrorCategory, Ci.nsITransportSecurityInfo.ERROR_UNSET, "ev-test.example.com should not have an overridable error category" ); ok( !gEVExpected || transportSecurityInfo.isExtendedValidation, "ev-test.example.com should have isExtendedValidation set " + "(or this is a non-debug build)" ); } ); } // This test is similar, except with extended validation. We should connect // successfully, and the certificate should be EV in debug builds. Without // clearing the session cache, we should connect successfully again, this time // with session resumption. The certificate should again be EV in debug builds. function add_resume_ev_test() { const SERVER_PORT = 8888; let expectedRequestPaths = ["ev-test"]; let responseTypes = ["good"]; // Since we cache OCSP responses, we only ever actually serve one set. let ocspResponder; // If we don't wrap this in an `add_test`, the OCSP responder will be running // while we are actually running unrelated testcases, which can disrupt them. add_test(() => { ocspResponder = startOCSPResponder( SERVER_PORT, "localhost", "bad_certs", expectedRequestPaths, expectedRequestPaths.slice(), null, responseTypes ); run_next_test(); }); // We should be able to connect and verify the certificate as EV (in debug // builds). add_one_ev_test(false); // We should be able to connect again (using session resumption). In debug // builds, the certificate should be noted as EV. Again, it's important that // nothing clears the TLS cache in between these two operations. add_one_ev_test(true); add_test(() => { ocspResponder.stop(run_next_test); }); } const GOOD_DOMAIN = "good.include-subdomains.pinning.example.com"; // Helper function that adds a test that connects to a domain that should // succeed (but isn't EV) and verifies that its succeededCertChain gets set // appropriately. function add_one_non_ev_test() { add_connection_test( GOOD_DOMAIN, PRErrorCodeSuccess, null, transportSecurityInfo => { ok( !( transportSecurityInfo.securityState & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN ), `${GOOD_DOMAIN} should not have STATE_CERT_USER_OVERRIDDEN flag` ); ok( transportSecurityInfo.succeededCertChain, `${GOOD_DOMAIN} should have succeededCertChain set` ); equal( transportSecurityInfo.overridableErrorCategory, 0, `${GOOD_DOMAIN} should not have an overridable error category set` ); ok( !transportSecurityInfo.isExtendedValidation, `${GOOD_DOMAIN} should not have isExtendedValidation set` ); } ); } // This test is similar, except with non-extended validation. We should connect // successfully, and the certificate should not be EV. Without clearing the // session cache, we should connect successfully again, this time with session // resumption. In this case, though, we want to ensure the succeededCertChain is // set. function add_resume_non_ev_test() { add_one_non_ev_test(); add_one_non_ev_test(); } const statsPtr = getSSLStatistics(); const toInt32 = ctypes.Int64.lo; // Connect to the same domain with two origin attributes and check if any ssl // session is resumed. function add_origin_attributes_test( originAttributes1, originAttributes2, resumeExpected ) { add_connection_test( GOOD_DOMAIN, PRErrorCodeSuccess, clearSessionCache, null, null, originAttributes1 ); let hitsBeforeConnect; let missesBeforeConnect; let expectedHits = resumeExpected ? 1 : 0; let expectedMisses = 1 - expectedHits; add_connection_test( GOOD_DOMAIN, PRErrorCodeSuccess, function () { // Add the hits and misses before connection. let stats = statsPtr.contents; hitsBeforeConnect = toInt32(stats.sch_sid_cache_hits); missesBeforeConnect = toInt32(stats.sch_sid_cache_misses); }, function () { let stats = statsPtr.contents; equal( toInt32(stats.sch_sid_cache_hits), hitsBeforeConnect + expectedHits, "Unexpected cache hits" ); equal( toInt32(stats.sch_sid_cache_misses), missesBeforeConnect + expectedMisses, "Unexpected cache misses" ); }, null, originAttributes2 ); } function add_resumption_tests() { add_resume_ev_test(); add_resume_non_ev_test(); add_resume_non_ev_with_override_test(); add_origin_attributes_test({}, {}, true); add_origin_attributes_test({ userContextId: 1 }, { userContextId: 2 }, false); add_origin_attributes_test({ userContextId: 3 }, { userContextId: 3 }, true); add_origin_attributes_test( { firstPartyDomain: "foo.com" }, { firstPartyDomain: "bar.com" }, false ); add_origin_attributes_test( { firstPartyDomain: "baz.com" }, { firstPartyDomain: "baz.com" }, true ); } function run_test() { add_tls_server_setup("BadCertAndPinningServer", "bad_certs"); add_resumption_tests(); // Enable external session cache and reset the status. add_test(function () { Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true); certdb.clearOCSPCache(); run_next_test(); }); // Do tests again. add_resumption_tests(); run_next_test(); }