bug 982248 - NSSCertDBTrustDomain: specify timeout for OCSP requests r=briansmith
authorDavid Keeler <dkeeler@mozilla.com>
Thu, 01 May 2014 15:07:55 -0700
changeset 181310 daee17c1458115ef05b91c19a13a407052bf9b9e
parent 181309 1dfb9d3331ede5458af7b27a7a22c00f620a34e8
child 181311 e8eceeb7fa65b17d70bfe7cf630295067babadcd
push id26699
push usercbook@mozilla.com
push dateFri, 02 May 2014 12:30:59 +0000
treeherdermozilla-central@66ea09d0c951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbriansmith
bugs982248
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 982248 - NSSCertDBTrustDomain: specify timeout for OCSP requests r=briansmith
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/OCSPRequestor.cpp
security/certverifier/OCSPRequestor.h
security/certverifier/moz.build
security/manager/ssl/tests/unit/test_ocsp_timeout.js
security/manager/ssl/tests/unit/xpcshell.ini
--- 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;
 
@@ -132,16 +133,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)
 {
@@ -311,46 +332,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,25 +3,27 @@
 # 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/boot/src',
+    '../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