Bug 982248 - NSSCertDBTrustDomain: Specify timeout for OCSP requests. r=briansmith, a=sledru
authorDavid Keeler <dkeeler@mozilla.com>
Thu, 01 May 2014 15:07:55 -0700
changeset 200312 af1dda114d87c492ff546728fa9eb7fe9511af69
parent 200311 0c8a23a933718d7216e09421f8ac5a22556acf81
child 200313 6b054afbbf6bd5cb2069760ba6027f9324ad00cc
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbriansmith, sledru
bugs982248
milestone31.0a2
Bug 982248 - NSSCertDBTrustDomain: Specify timeout for OCSP requests. r=briansmith, a=sledru
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;
 
@@ -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