diff options
Diffstat (limited to 'security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js')
-rw-r--r-- | security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js b/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js new file mode 100644 index 0000000000..22f552218c --- /dev/null +++ b/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js @@ -0,0 +1,317 @@ +// -*- 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"; + +// In which we connect to a number of domains (as faked by a server running +// locally) with OCSP stapling enabled to determine that good things happen +// and bad things don't, specifically with respect to various expired OCSP +// responses (stapled and otherwise). +// According to RFC 6066, if a stapled OCSP response can't be satisfactorilly +// verified, the client should terminate the connection. Unfortunately, due to +// some bugs where servers will staple any old garbage without verifying it, we +// can't be this strict in practice. Originally this caveat only applied to +// expired responses, but recent high-profile failures have caused us to expand +// this to "try later" responses and responses where the signing certificate +// doesn't verify successfully. + +var gCurrentOCSPResponse = null; +var gOCSPRequestCount = 0; + +function add_ocsp_test( + aHost, + aExpectedResult, + aOCSPResponseToServe, + aExpectedRequestCount +) { + add_connection_test( + aHost, + aExpectedResult, + function () { + clearOCSPCache(); + clearSessionCache(); + gCurrentOCSPResponse = aOCSPResponseToServe; + gOCSPRequestCount = 0; + }, + function () { + equal( + gOCSPRequestCount, + aExpectedRequestCount, + "Should have made " + + aExpectedRequestCount + + " fallback OCSP request" + + (aExpectedRequestCount == 1 ? "" : "s") + ); + } + ); +} + +do_get_profile(); +Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true); +Services.prefs.setIntPref("security.OCSP.enabled", 1); +// Sometimes this test will fail on android due to an OCSP request timing out. +// That aspect of OCSP requests is not what we're testing here, so we can just +// bump the timeout and hopefully avoid these failures. +Services.prefs.setIntPref("security.OCSP.timeoutMilliseconds.soft", 5000); +var args = [ + ["good", "default-ee", "unused", 0], + ["expiredresponse", "default-ee", "unused", 0], + ["oldvalidperiod", "default-ee", "unused", 0], + ["revoked", "default-ee", "unused", 0], + ["unknown", "default-ee", "unused", 0], + ["good", "must-staple-ee", "unused", 0], +]; +var ocspResponses = generateOCSPResponses(args, "ocsp_certs"); +// Fresh response, certificate is good. +var ocspResponseGood = ocspResponses[0]; +// Expired response, certificate is good. +var expiredOCSPResponseGood = ocspResponses[1]; +// Fresh signature, old validity period, certificate is good. +var oldValidityPeriodOCSPResponseGood = ocspResponses[2]; +// Fresh signature, certificate is revoked. +var ocspResponseRevoked = ocspResponses[3]; +// Fresh signature, certificate is unknown. +var ocspResponseUnknown = ocspResponses[4]; +var ocspResponseGoodMustStaple = ocspResponses[5]; + +// sometimes we expect a result without re-fetch +var willNotRetry = 1; +// but sometimes, since a bad response is in the cache, OCSP fetch will be +// attempted for each validation - in practice, for these test certs, this +// means 2 requests because various key sizes are tried. +var willRetry = 2; + +function run_test() { + let ocspResponder = new HttpServer(); + ocspResponder.registerPrefixHandler("/", function (request, response) { + if (gCurrentOCSPResponse) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/ocsp-response"); + response.write(gCurrentOCSPResponse); + } else { + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); + response.write("Internal Server Error"); + } + gOCSPRequestCount++; + }); + ocspResponder.start(8888); + add_tls_server_setup("OCSPStaplingServer", "ocsp_certs"); + + // In these tests, the OCSP stapling server gives us a stapled + // response based on the host name ("ocsp-stapling-expired" or + // "ocsp-stapling-expired-fresh-ca"). We then ensure that we're + // properly falling back to fetching revocation information. + // For ocsp-stapling-expired.example.com, the OCSP stapling server + // staples an expired OCSP response. The certificate has not expired. + // For ocsp-stapling-expired-fresh-ca.example.com, the OCSP stapling + // server staples an OCSP response with a recent signature but with an + // out-of-date validity period. The certificate has not expired. + add_ocsp_test( + "ocsp-stapling-expired.example.com", + PRErrorCodeSuccess, + ocspResponseGood, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-expired-fresh-ca.example.com", + PRErrorCodeSuccess, + ocspResponseGood, + willNotRetry + ); + // if we can't fetch a more recent response when + // given an expired stapled response, we terminate the connection. + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + expiredOCSPResponseGood, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-expired-fresh-ca.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + expiredOCSPResponseGood, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + oldValidityPeriodOCSPResponseGood, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-expired-fresh-ca.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + oldValidityPeriodOCSPResponseGood, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + null, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_OCSP_OLD_RESPONSE, + null, + willNotRetry + ); + // Of course, if the newer response indicates Revoked or Unknown, + // that status must be returned. + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_REVOKED_CERTIFICATE, + ocspResponseRevoked, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-expired-fresh-ca.example.com", + SEC_ERROR_REVOKED_CERTIFICATE, + ocspResponseRevoked, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-expired.example.com", + SEC_ERROR_OCSP_UNKNOWN_CERT, + ocspResponseUnknown, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-expired-fresh-ca.example.com", + SEC_ERROR_OCSP_UNKNOWN_CERT, + ocspResponseUnknown, + willRetry + ); + + // If the response is expired but indicates Revoked or Unknown and a + // newer status can't be fetched, the Revoked or Unknown status will + // be returned. + add_ocsp_test( + "ocsp-stapling-revoked-old.example.com", + SEC_ERROR_REVOKED_CERTIFICATE, + null, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-unknown-old.example.com", + SEC_ERROR_OCSP_UNKNOWN_CERT, + null, + willNotRetry + ); + // If the response is expired but indicates Revoked or Unknown and + // a newer status can be fetched and successfully verified, this + // should result in a successful certificate verification. + add_ocsp_test( + "ocsp-stapling-revoked-old.example.com", + PRErrorCodeSuccess, + ocspResponseGood, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-unknown-old.example.com", + PRErrorCodeSuccess, + ocspResponseGood, + willNotRetry + ); + // If a newer status can be fetched but it fails to verify, the + // Revoked or Unknown status of the expired stapled response + // should be returned. + add_ocsp_test( + "ocsp-stapling-revoked-old.example.com", + SEC_ERROR_REVOKED_CERTIFICATE, + expiredOCSPResponseGood, + willRetry + ); + add_ocsp_test( + "ocsp-stapling-unknown-old.example.com", + SEC_ERROR_OCSP_UNKNOWN_CERT, + expiredOCSPResponseGood, + willRetry + ); + + // These tests are verifying that an valid but very old response + // is rejected as a valid stapled response, requiring a fetch + // from the ocsp responder. + add_ocsp_test( + "ocsp-stapling-ancient-valid.example.com", + PRErrorCodeSuccess, + ocspResponseGood, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-ancient-valid.example.com", + SEC_ERROR_REVOKED_CERTIFICATE, + ocspResponseRevoked, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-ancient-valid.example.com", + SEC_ERROR_OCSP_UNKNOWN_CERT, + ocspResponseUnknown, + willRetry + ); + + // Test how OCSP-must-staple (i.e. TLS feature) interacts with stapled OCSP + // responses that don't successfully verify. + // A strict reading of the relevant RFCs might say that these connections + // should all fail because a satisfactory stapled OCSP response is not + // present, but for compatibility reasons we fall back to active OCSP fetching + // in these situations. If the fetch succeeds, then connection succeeds. + add_ocsp_test( + "ocsp-stapling-must-staple-expired.example.com", + PRErrorCodeSuccess, + ocspResponseGoodMustStaple, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-must-staple-try-later.example.com", + PRErrorCodeSuccess, + ocspResponseGoodMustStaple, + willNotRetry + ); + add_ocsp_test( + "ocsp-stapling-must-staple-invalid-signer.example.com", + PRErrorCodeSuccess, + ocspResponseGoodMustStaple, + willNotRetry + ); + + add_test(function () { + ocspResponder.stop(run_next_test); + }); + add_test(check_ocsp_stapling_telemetry); + run_next_test(); +} + +function check_ocsp_stapling_telemetry() { + let histogram = Services.telemetry + .getHistogramById("SSL_OCSP_STAPLING") + .snapshot(); + equal( + histogram.values[0] || 0, + 0, + "Should have 0 connections for unused histogram bucket 0" + ); + equal( + histogram.values[1] || 0, + 0, + "Actual and expected connections with a good response should match" + ); + equal( + histogram.values[2] || 0, + 0, + "Actual and expected connections with no stapled response should match" + ); + equal( + histogram.values[3], + 22, + "Actual and expected connections with an expired response should match" + ); + equal( + histogram.values[4], + 2, + "Actual and expected connections with bad responses should match" + ); + run_next_test(); +} |