Bug 982248 - NSSCertDBTrustDomain: Specify timeout for OCSP requests. r=briansmith, a=sledru
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -4,22 +4,23 @@
* 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/. */
#include "NSSCertDBTrustDomain.h"
#include <stdint.h>
#include "ExtendedValidation.h"
+#include "OCSPRequestor.h"
#include "certdb.h"
-#include "pkix/pkix.h"
#include "mozilla/Telemetry.h"
#include "nss.h"
#include "ocsp.h"
#include "pk11pub.h"
+#include "pkix/pkix.h"
#include "prerror.h"
#include "prmem.h"
#include "prprf.h"
#include "secerr.h"
#include "secmod.h"
using namespace mozilla::pkix;
@@ -129,16 +130,36 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
SECStatus
NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert)
{
return ::mozilla::pkix::VerifySignedData(signedData, cert, mPinArg);
}
+static PRIntervalTime
+OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching)
+{
+ switch (ocspFetching) {
+ case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail:
+ return PR_SecondsToInterval(2);
+ case NSSCertDBTrustDomain::FetchOCSPForEV:
+ case NSSCertDBTrustDomain::FetchOCSPForDVHardFail:
+ return PR_SecondsToInterval(10);
+ // The rest of these are error cases. Assert in debug builds, but return
+ // the default value corresponding to 2 seconds in release builds.
+ case NSSCertDBTrustDomain::NeverFetchOCSP:
+ case NSSCertDBTrustDomain::LocalOnlyOCSPForEV:
+ PR_NOT_REACHED("we should never see this OCSPFetching type here");
+ default:
+ PR_NOT_REACHED("we're not handling every OCSPFetching type");
+ }
+ return PR_SecondsToInterval(2);
+}
+
SECStatus
NSSCertDBTrustDomain::CheckRevocation(
mozilla::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
/*const*/ CERTCertificate* issuerCert,
PRTime time,
/*optional*/ const SECItem* stapledOCSPResponse)
{
@@ -307,46 +328,47 @@ NSSCertDBTrustDomain::CheckRevocation(
cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT ||
cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert,
issuerCert));
if (!request) {
return SECFailure;
}
- response = CERT_PostOCSPRequest(arena.get(), url.get(), request);
+ response = DoOCSPRequest(arena.get(), url.get(), request,
+ OCSPFetchingTypeToTimeoutTime(mOCSPFetching));
}
if (!response) {
PRErrorCode error = PR_GetError();
if (error == 0) {
error = cachedResponseErrorCode;
}
PRTime timeout = time + ServerFailureDelay;
if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) {
return SECFailure;
}
PR_SetError(error, 0);
if (mOCSPFetching != FetchOCSPForDVSoftFail) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: returning SECFailure after "
- "CERT_PostOCSPRequest failure"));
+ "OCSP request failure"));
return SECFailure;
}
if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: returning SECFailure from cached "
- "response after CERT_PostOCSPRequest failure"));
+ "response after OCSP request failure"));
PR_SetError(cachedResponseErrorCode, 0);
return SECFailure;
}
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: returning SECSuccess after "
- "CERT_PostOCSPRequest failure"));
+ "OCSP request failure"));
return SECSuccess; // Soft fail -> success :(
}
SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
response,
ResponseIsFromNetwork);
if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
new file mode 100644
--- /dev/null
+++ b/security/certverifier/OCSPRequestor.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "OCSPRequestor.h"
+
+#include "nsIURLParser.h"
+#include "nsNSSCallbacks.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "pkix/ScopedPtr.h"
+#include "secerr.h"
+
+namespace mozilla { namespace psm {
+
+using mozilla::pkix::ScopedPtr;
+
+void
+ReleaseHttpServerSession(nsNSSHttpServerSession* httpServerSession)
+{
+ delete httpServerSession;
+}
+typedef ScopedPtr<nsNSSHttpServerSession, ReleaseHttpServerSession>
+ ScopedHTTPServerSession;
+
+void
+ReleaseHttpRequestSession(nsNSSHttpRequestSession* httpRequestSession)
+{
+ httpRequestSession->Release();
+}
+typedef ScopedPtr<nsNSSHttpRequestSession, ReleaseHttpRequestSession>
+ ScopedHTTPRequestSession;
+
+SECItem* DoOCSPRequest(PLArenaPool* arena, const char* url,
+ const SECItem* encodedRequest, PRIntervalTime timeout)
+{
+ nsCOMPtr<nsIURLParser> urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ if (!urlParser) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return nullptr;
+ }
+
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ int32_t pathLen;
+ nsresult rv = urlParser->ParseURL(url, PL_strlen(url),
+ &schemePos, &schemeLen,
+ &authorityPos, &authorityLen,
+ &pathPos, &pathLen);
+ if (NS_FAILED(rv)) {
+ PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0);
+ return nullptr;
+ }
+ uint32_t hostnamePos;
+ int32_t hostnameLen;
+ int32_t port;
+ rv = urlParser->ParseAuthority(url + authorityPos, authorityLen,
+ nullptr, nullptr, nullptr, nullptr,
+ &hostnamePos, &hostnameLen, &port);
+ if (NS_FAILED(rv)) {
+ PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0);
+ return nullptr;
+ }
+ if (port == -1) {
+ port = 80;
+ }
+
+ nsAutoCString hostname(url + authorityPos + hostnamePos, hostnameLen);
+ SEC_HTTP_SERVER_SESSION serverSessionPtr = nullptr;
+ if (nsNSSHttpInterface::createSessionFcn(hostname.BeginReading(), port,
+ &serverSessionPtr) != SECSuccess) {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return nullptr;
+ }
+
+ ScopedHTTPServerSession serverSession(
+ reinterpret_cast<nsNSSHttpServerSession*>(serverSessionPtr));
+ nsAutoCString path(url + pathPos, pathLen);
+ SEC_HTTP_REQUEST_SESSION requestSessionPtr;
+ if (nsNSSHttpInterface::createFcn(serverSession.get(), "http",
+ path.BeginReading(), "POST",
+ timeout, &requestSessionPtr)
+ != SECSuccess) {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return nullptr;
+ }
+
+ ScopedHTTPRequestSession requestSession(
+ reinterpret_cast<nsNSSHttpRequestSession*>(requestSessionPtr));
+ if (nsNSSHttpInterface::setPostDataFcn(requestSession.get(),
+ reinterpret_cast<char*>(encodedRequest->data), encodedRequest->len,
+ "application/ocsp-request") != SECSuccess) {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return nullptr;
+ }
+
+ uint16_t httpResponseCode;
+ const char* httpResponseData;
+ uint32_t httpResponseDataLen = 0; // 0 means any response size is acceptable
+ if (nsNSSHttpInterface::trySendAndReceiveFcn(requestSession.get(), nullptr,
+ &httpResponseCode, nullptr,
+ nullptr, &httpResponseData,
+ &httpResponseDataLen)
+ != SECSuccess) {
+ PR_SetError(SEC_ERROR_OCSP_SERVER_ERROR, 0);
+ return nullptr;
+ }
+
+ if (httpResponseCode != 200) {
+ PR_SetError(SEC_ERROR_OCSP_SERVER_ERROR, 0);
+ return nullptr;
+ }
+
+ SECItem* encodedResponse = SECITEM_AllocItem(arena, nullptr,
+ httpResponseDataLen);
+ if (!encodedResponse) {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return nullptr;
+ }
+
+ memcpy(encodedResponse->data, httpResponseData, httpResponseDataLen);
+ return encodedResponse;
+}
+
+} } // namespace mozilla::psm
new file mode 100644
--- /dev/null
+++ b/security/certverifier/OCSPRequestor.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_psm_OCSPRequestor_h
+#define mozilla_psm_OCSPRequestor_h
+
+#include "secmodt.h"
+
+namespace mozilla { namespace psm {
+
+// The memory returned is owned by the given arena.
+SECItem* DoOCSPRequest(PLArenaPool* arena, const char* url,
+ const SECItem* encodedRequest, PRIntervalTime timeout);
+
+} } // namespace mozilla::psm
+
+#endif // mozilla_psm_OCSPRequestor_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -3,24 +3,26 @@
# 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/.
UNIFIED_SOURCES += [
'CertVerifier.cpp',
'NSSCertDBTrustDomain.cpp',
'OCSPCache.cpp',
+ 'OCSPRequestor.cpp',
]
if not CONFIG['NSS_NO_EV_CERTS']:
UNIFIED_SOURCES += [
'ExtendedValidation.cpp',
]
LOCAL_INCLUDES += [
+ '../manager/ssl/src',
'../pkix/include',
]
DIRS += [
'../pkix',
]
FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_ocsp_timeout.js
@@ -0,0 +1,58 @@
+// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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";
+
+function run_test() {
+ do_get_profile();
+
+ add_tls_server_setup("OCSPStaplingServer");
+
+ let socket = Cc["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
+ socket.init(8080, true, -1);
+
+
+ add_tests_in_mode(true, true);
+ add_tests_in_mode(false, true);
+ add_tests_in_mode(true, false);
+ add_tests_in_mode(false, false);
+
+ add_test(function() { socket.close(); run_next_test(); });
+ run_next_test();
+}
+
+function add_tests_in_mode(useMozillaPKIX, useHardFail) {
+ let startTime;
+ add_test(function () {
+ Services.prefs.setBoolPref("security.use_mozillapkix_verification",
+ useMozillaPKIX);
+ Services.prefs.setBoolPref("security.OCSP.require", useHardFail);
+ startTime = new Date();
+ run_next_test();
+ });
+
+ add_connection_test("ocsp-stapling-none.example.com", useHardFail
+ ? getXPCOMStatusFromNSS(SEC_ERROR_OCSP_SERVER_ERROR)
+ : Cr.NS_OK, clearSessionCache);
+
+ // Reset state
+ add_test(function() {
+ let endTime = new Date();
+ // With OCSP hard-fail on, we timeout after 10 seconds.
+ // With OCSP soft-fail, we timeout after 2 seconds.
+ if (useHardFail) {
+ do_check_true((endTime - startTime) > 10000);
+ } else {
+ do_check_true((endTime - startTime) > 2000);
+ }
+ // Make sure we didn't wait too long.
+ // (Unfortunately, we probably can't have a tight upper bound on
+ // how long is too long for this test, because we might be running
+ // on slow hardware.)
+ do_check_true((endTime - startTime) < 60000);
+ clearOCSPCache();
+ run_next_test();
+ });
+}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -42,16 +42,20 @@ fail-if = os == "android" || buildapp ==
[test_ocsp_caching.js]
run-sequentially = hardcoded ports
# Bug 676972: test fails consistently on Android and B2G
fail-if = os == "android" || buildapp == "b2g"
[test_ocsp_required.js]
run-sequentially = hardcoded ports
# Bug 676972: test fails consistently on Android and B2G
fail-if = os == "android" || buildapp == "b2g"
+[test_ocsp_timeout.js]
+run-sequentially = hardcoded ports
+# Bug 676972: test fails consistently on Android and B2G
+fail-if = os == "android" || buildapp == "b2g"
[test_cert_signatures.js]
[test_ev_certs.js]
# Bug 676972: test fails consistently on Android and B2G
fail-if = os == "android" || buildapp == "b2g"
[test_getchain.js]
[test_cert_overrides.js]
run-sequentially = hardcoded ports
# Bug 676972: test fails consistently on Android and B2G