Bug 804663: Add a CryptoTask class for asynchronous crypto operations and add more utilities to ScopedNSSTypes.h
☠☠ backed out by 58b9ad5a8043 ☠ ☠
authorBrian Smith <bsmith@mozilla.com>
Fri, 13 Jul 2012 15:44:24 -0700
changeset 113744 ef6db4ced917a8f3052efff1f9c0901fb2386012
parent 113743 fb6df9f4a6a278a782e820a68a99925a86e43d71
child 113745 aae7ca541654887e809bdf8b70675ac65fadbf4a
push id18359
push userbsmith@mozilla.com
push dateTue, 20 Nov 2012 04:11:14 +0000
treeherdermozilla-inbound@ef6db4ced917 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs804663
milestone20.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 804663: Add a CryptoTask class for asynchronous crypto operations and add more utilities to ScopedNSSTypes.h
security/manager/ssl/src/CryptoTask.cpp
security/manager/ssl/src/CryptoTask.h
security/manager/ssl/src/Makefile.in
security/manager/ssl/src/ScopedNSSTypes.h
security/manager/ssl/src/nsNSSComponent.cpp
toolkit/identity/IdentityCryptoService.cpp
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/src/CryptoTask.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "CryptoTask.h"
+
+namespace mozilla {
+
+CryptoTask::~CryptoTask()
+{
+  MOZ_ASSERT(mReleasedNSSResources);
+
+  nsNSSShutDownPreventionLock lock;
+  if (!isAlreadyShutDown()) {
+    shutdown(calledFromObject);
+  }
+}
+
+nsresult
+CryptoTask::Dispatch(const nsACString & taskThreadName)
+{
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewThread(getter_AddRefs(thread), this);
+  if (thread) {
+    NS_SetThreadName(thread, taskThreadName);
+  }
+  return rv;
+}
+
+NS_IMETHODIMP
+CryptoTask::Run()
+{
+  if (!NS_IsMainThread()) {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown()) {
+      mRv = NS_ERROR_NOT_AVAILABLE;
+    } else {
+      mRv = CalculateResult();
+    }
+    NS_DispatchToMainThread(this);
+  } else {
+    // back on the main thread
+
+    // call ReleaseNSSResources now, before calling CallCallback, so that
+    // CryptoTasks have consistent behavior regardless of whether NSS is shut
+    // down between CalculateResult being called and CallCallback being called.
+    if (!mReleasedNSSResources) {
+      mReleasedNSSResources = true;
+      ReleaseNSSResources();
+    }
+
+    CallCallback(mRv);
+  }
+
+  return NS_OK;
+}
+
+void
+CryptoTask::virtualDestroyNSSReference()
+{
+  NS_ABORT_IF_FALSE(NS_IsMainThread(),
+                    "virtualDestroyNSSReference called off the main thread");
+  if (!mReleasedNSSResources) {
+    mReleasedNSSResources = true;
+    ReleaseNSSResources();
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/src/CryptoTask.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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__CryptoTask_h
+#define mozilla__CryptoTask_h
+
+#include "mozilla/Attributes.h"
+#include "nsThreadUtils.h"
+#include "nsNSSShutDown.h"
+
+namespace mozilla {
+
+/**
+ * Frequently we need to run a task on a background thread without blocking
+ * the main thread, and then call a callback on the main thread with the
+ * result. This class provides the framework for that. Subclasses must:
+ *
+ *   (1) Override CalculateResult for the off-the-main-thread computation.
+ *       NSS functionality may only be accessed within CalculateResult.
+ *   (2) Override ReleaseNSSResources to release references to all NSS
+ *       resources (that do implement nsNSSShutDownObject themselves).
+ *   (3) Override CallCallback() for the on-the-main-thread call of the
+ *       callback.
+ *
+ * CalculateResult, ReleaseNSSResources, and CallCallback are called in order,
+ * except CalculateResult might be skipped if NSS is shut down before it can
+ * be called; in that case ReleaseNSSResources will be called and then
+ * CallCallback will be called with an error code.
+ */
+class CryptoTask : public nsRunnable,
+                   public nsNSSShutDownObject
+{
+public:
+  template <size_t LEN>
+  nsresult Dispatch(const char (&taskThreadName)[LEN])
+  {
+    MOZ_STATIC_ASSERT(LEN <= 15,
+                      "Thread name must be no more than 15 characters");
+    return Dispatch(nsDependentCString(taskThreadName));
+  }
+
+protected:
+  CryptoTask()
+    : mRv(NS_ERROR_NOT_INITIALIZED),
+      mReleasedNSSResources(false)
+  {
+  }
+
+  virtual ~CryptoTask();
+
+  /**
+   * Called on a background thread (never the main thread). If CalculateResult
+   * is called, then its result will be passed to CallCallback on the main
+   * thread.
+   */
+  virtual nsresult CalculateResult() = 0;
+
+  /**
+   * Called on the main thread during NSS shutdown or just before CallCallback
+   * has been called. All NSS resources must be released. Usually, this just
+   * means assigning nullptr to the ScopedNSSType-based memory variables.
+   */
+  virtual void ReleaseNSSResources() = 0;
+
+  /**
+   * Called on the main thread with the result from CalculateResult() or
+   * with an error code if NSS was shut down before CalculateResult could
+   * be called.
+   */
+  virtual void CallCallback(nsresult rv) = 0;
+
+private:
+  NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL;
+  virtual void virtualDestroyNSSReference() MOZ_OVERRIDE MOZ_FINAL;
+
+  nsresult Dispatch(const nsACString & taskThreadName);
+
+  nsresult mRv;
+  bool mReleasedNSSResources;
+};
+
+} // namespace mozilla
+
+#endif // mozilla__CryptoTask_h
--- a/security/manager/ssl/src/Makefile.in
+++ b/security/manager/ssl/src/Makefile.in
@@ -15,16 +15,17 @@ MODULE		= pipnss
 LIBRARY_NAME	= pipnss
 IS_COMPONENT	= 1
 MODULE_NAME	= NSS
 EXPORT_LIBRARY	= 1
 GRE_MODULE	= 1
 LIBXUL_LIBRARY	= 1
 
 CPPSRCS = 				\
+	CryptoTask.cpp \
 	nsCERTValInParamWrapper.cpp     \
 	nsNSSCleaner.cpp                \
 	nsCertOverrideService.cpp   \
 	nsRecentBadCerts.cpp \
         nsClientAuthRemember.cpp        \
 	nsPSMBackgroundThread.cpp       \
 	nsCertVerificationThread.cpp    \
 	nsProtectedAuthThread.cpp \
@@ -85,14 +86,15 @@ EXTRA_DEPS = $(NSS_DEP_LIBS)
 
 DEFINES += \
   -DNSS_ENABLE_ECC \
   -DDLL_PREFIX=\"$(DLL_PREFIX)\" \
   -DDLL_SUFFIX=\"$(DLL_SUFFIX)\" \
   $(NULL)
 
 EXPORTS += \
+  CryptoTask.h \
   nsNSSShutDown.h \
   ScopedNSSTypes.h \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/security/manager/ssl/src/ScopedNSSTypes.h
+++ b/security/manager/ssl/src/ScopedNSSTypes.h
@@ -2,24 +2,81 @@
 /* vim: set ts=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_ScopedNSSTypes_h
 #define mozilla_ScopedNSSTypes_h
 
+#include "mozilla/Likely.h"
+#include "mozilla/mozalloc_oom.h"
 #include "mozilla/Scoped.h"
 
 #include "prio.h"
 #include "cert.h"
 #include "cms.h"
 #include "keyhi.h"
 #include "pk11pub.h"
 #include "sechash.h"
+#include "secpkcs7.h"
+#include "prerror.h"
+
+namespace mozilla {
+
+// It is very common to cast between char* and uint8_t* when doing crypto stuff.
+// Here, we provide more type-safe wrappers around reinterpret_cast so you don't
+// shoot yourself in the foot by reinterpret_casting completely unrelated types.
+
+inline char *
+char_ptr_cast(uint8_t * p) { return reinterpret_cast<char *>(p); }
+
+inline const char *
+char_ptr_cast(const uint8_t * p) { return reinterpret_cast<const char *>(p); }
+
+inline uint8_t *
+uint8_t_ptr_cast(char * p) { return reinterpret_cast<uint8_t*>(p); }
+
+inline const uint8_t *
+uint8_t_ptr_cast(const char * p) { return reinterpret_cast<const uint8_t*>(p); }
+
+// NSPR APIs use PRStatus/PR_GetError and NSS APIs use SECStatus/PR_GetError to
+// report success/failure. These funtions make it more convenient and *safer*
+// to translate NSPR/NSS results to nsresult. They are safer because they
+// refuse to traslate any bad PRStatus/SECStatus into an NS_OK, even when the
+// NSPR/NSS function forgot to call PR_SetError.
+
+// IMPORTANT: This must be called immediately after the function that set the
+// error code. Prefer using MapSECStatus to this.
+inline nsresult
+PRErrorCode_to_nsresult(PRErrorCode error)
+{
+  if (!error) {
+    MOZ_NOT_REACHED("Function failed without calling PR_GetError");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // From NSSErrorsService::GetXPCOMFromNSSError
+  // XXX Don't make up nsresults, it's supposed to be an enum (bug 778113)
+  return (nsresult)NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY,
+                                             -1 * error);
+}
+
+// IMPORTANT: This must be called immediately after the function returning the
+// SECStatus result. The recommended usage is:
+//    nsresult rv = MapSECStatus(f(x, y, z));
+inline nsresult
+MapSECStatus(SECStatus rv)
+{
+  if (rv == SECSuccess)
+    return NS_OK;
+
+  PRErrorCode error = PR_GetError();
+  return PRErrorCode_to_nsresult(error);
+}
 
 // Alphabetical order by NSS type
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
                                           PRFileDesc,
                                           PR_Close)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertificate,
                                           CERTCertificate,
                                           CERT_DestroyCertificate)
@@ -40,39 +97,176 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLAT
                                           CERT_FreeNicknames)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTSubjectPublicKeyInfo,
                                           CERTSubjectPublicKeyInfo,
                                           SECKEY_DestroySubjectPublicKeyInfo)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTValidity,
                                           CERTValidity,
                                           CERT_DestroyValidity)
 
-MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedHASHContext,
-                                          HASHContext,
-                                          HASH_Destroy)
-
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedNSSCMSMessage,
                                           NSSCMSMessage,
                                           NSS_CMSMessage_Destroy)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedNSSCMSSignedData,
                                           NSSCMSSignedData,
                                           NSS_CMSSignedData_Destroy)
 
+namespace psm {
+
+inline void
+PK11_DestroyContext_true(PK11Context * ctx) {
+  PK11_DestroyContext(ctx, true);
+}
+
+} // namespace mozilla::psm
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11Context,
+                                          PK11Context,
+                                          mozilla::psm::PK11_DestroyContext_true)
+
+/** A more convenient way of dealing with digests calculated into
+ *  stack-allocated buffers.
+ *
+ * Typical usage, for digesting a buffer in memory:
+ *
+ *   Digest digest;
+ *   nsresult rv = digest.DigestBuf(SEC_OID_SHA256, mybuffer, myBufferLen);
+ *   NS_ENSURE_SUCCESS(rv, rv);
+ *   rv = MapSECStatus(SomeNSSFunction(..., digest.get(), ...));
+ *
+ * Less typical usage, for digesting while doing streaming I/O and similar:
+ *
+ *   Digest digest;
+ *   ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
+ *   NS_ENSURE_TRUE(digestContext, NS_ERROR_OUT_OF_MEMORY);
+ *   rv = MapSECStatus(PK11_DigestBegin(digestContext));
+ *   NS_ENSURE_SUCCESS(rv, rv);
+ *   for (...) {
+ *      rv = MapSECStatus(PK11_DigestOp(digestContext, ...));
+ *      NS_ENSURE_SUCCESS(rv, rv);
+ *   }
+ *   rv = digestContext.End(SEC_OID_SHA1, digestContext);
+ *   NS_ENSURE_SUCCESS(rv, rv)
+ */
+class Digest
+{
+public:
+  Digest()
+  {
+    item.type = siBuffer;
+    item.data = buf;
+    item.len = 0;
+  }
+
+  nsresult DigestBuf(SECOidTag hashAlg, const uint8_t * buf, uint32_t len)
+  {
+    nsresult rv = SetLength(hashAlg);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return MapSECStatus(PK11_HashBuf(hashAlg, item.data, buf, len));
+  }
+
+  nsresult End(SECOidTag hashAlg, ScopedPK11Context & context)
+  {
+    nsresult rv = SetLength(hashAlg);
+    NS_ENSURE_SUCCESS(rv, rv);
+    uint32_t len;
+    rv = MapSECStatus(PK11_DigestFinal(context, item.data, &len, item.len));
+    NS_ENSURE_SUCCESS(rv, rv);
+    context = nullptr;
+    NS_ENSURE_TRUE(len == item.len, NS_ERROR_UNEXPECTED);
+    return NS_OK;
+  }
+
+  const SECItem & get() const { return item; }
+
+private:
+  nsresult SetLength(SECOidTag hashType)
+  {
+    switch (hashType)
+    {
+      case SEC_OID_SHA1:   item.len = SHA1_LENGTH;   break;
+      case SEC_OID_SHA256: item.len = SHA256_LENGTH; break;
+      case SEC_OID_SHA384: item.len = SHA384_LENGTH; break;
+      case SEC_OID_SHA512: item.len = SHA512_LENGTH; break;
+      default:
+        return NS_ERROR_INVALID_ARG;
+    }
+
+    return NS_OK;
+  }
+
+  uint8_t buf[HASH_LENGTH_MAX];
+  SECItem item;
+};
 
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SlotInfo,
                                           PK11SlotInfo,
                                           PK11_FreeSlot)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SlotList,
                                           PK11SlotList,
                                           PK11_FreeSlotList)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPK11SymKey,
                                           PK11SymKey,
                                           PK11_FreeSymKey)
 
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSEC_PKCS7ContentInfo,
+                                          SEC_PKCS7ContentInfo,
+                                          SEC_PKCS7DestroyContentInfo)
+
+// Wrapper around NSS's SECItem_AllocItem that handles OOM the same way as
+// other allocators.
+inline void
+SECITEM_AllocItem(SECItem & item, uint32_t len)
+{
+  if (MOZ_UNLIKELY(!SECITEM_AllocItem(nullptr, &item, len))) {
+    mozalloc_handle_oom(len);
+    if (MOZ_UNLIKELY(!SECITEM_AllocItem(nullptr, &item, len))) {
+      MOZ_CRASH();
+    }
+  }
+}
+
+class ScopedAutoSECItem MOZ_FINAL : public SECItem
+{
+public:
+  ScopedAutoSECItem(uint32_t initialAllocatedLen = 0)
+  {
+    data = NULL;
+    len = 0;
+    if (initialAllocatedLen > 0) {
+      SECITEM_AllocItem(*this, initialAllocatedLen);
+    }
+  }
+
+  void reset()
+  {
+    SECITEM_FreeItem(this, false);
+  }
+
+  ~ScopedAutoSECItem()
+  {
+    reset();
+  }
+};
+
+namespace psm {
+
+inline void SECITEM_FreeItem_true(SECItem * s)
+{
+  return SECITEM_FreeItem(s, true);
+}
+
+} // namespace impl
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSECItem,
+                                          ::SECItem,
+                                          ::mozilla::psm::SECITEM_FreeItem_true)
+
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSECKEYPrivateKey,
                                           SECKEYPrivateKey,
                                           SECKEY_DestroyPrivateKey)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSECKEYPublicKey,
                                           SECKEYPublicKey,
                                           SECKEY_DestroyPublicKey)
 
+} // namespace mozilla
 
 #endif // mozilla_ScopedNSSTypes_h
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -50,16 +50,17 @@
 #include "nsIBufEntropyCollector.h"
 #include "nsIServiceManager.h"
 #include "nsIFile.h"
 #include "nsITokenPasswordDialogs.h"
 #include "nsICRLManager.h"
 #include "nsNSSShutDown.h"
 #include "nsSmartCardEvent.h"
 #include "nsIKeyModule.h"
+#include "ScopedNSSTypes.h"
 
 #include "nss.h"
 #include "pk11func.h"
 #include "ssl.h"
 #include "sslproto.h"
 #include "secmod.h"
 #include "sechash.h"
 #include "secmime.h"
@@ -2023,17 +2024,17 @@ nsNSSComponent::VerifySignature(const ch
   if (!aPrincipal || !aErrorCode) {
     return NS_ERROR_NULL_POINTER;
   }
 
   *aErrorCode = 0;
   *aPrincipal = nullptr;
 
   nsNSSShutDownPreventionLock locker;
-  SEC_PKCS7ContentInfo * p7_info = nullptr; 
+  ScopedSEC_PKCS7ContentInfo p7_info; 
   unsigned char hash[SHA1_LENGTH]; 
 
   SECItem item;
   item.type = siEncodedCertBuffer;
   item.data = (unsigned char*)aRSABuf;
   item.len = aRSABufLen;
   p7_info = SEC_PKCS7DecodeItem(&item,
                                 ContentCallback, nullptr,
@@ -2122,18 +2123,16 @@ nsNSSComponent::VerifySignature(const ch
                                    NS_ConvertUTF16toUTF8(subjectName),
                                    NS_ConvertUTF16toUTF8(orgName),
                                    pCert);
 
       certPrincipal.swap(*aPrincipal);
     } while (0);
   }
 
-  SEC_PKCS7DestroyContentInfo(p7_info);
-
   return rv2;
 }
 
 NS_IMETHODIMP
 nsNSSComponent::RandomUpdate(void *entropy, int32_t bufLen)
 {
   nsNSSShutDownPreventionLock locker;
 
--- a/toolkit/identity/IdentityCryptoService.cpp
+++ b/toolkit/identity/IdentityCryptoService.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/ModuleUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsNSSShutDown.h"
 #include "nsIThread.h"
 #include "nsThreadUtils.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
 #include "mozilla/Base64.h"
+#include "ScopedNSSTypes.h"
 
 #include "nss.h"
 #include "pk11pub.h"
 #include "secmod.h"
 #include "secerr.h"
 #include "keyhi.h"
 #include "cryptohi.h"
 
@@ -55,44 +56,16 @@ Base64UrlEncodeImpl(const nsACString & u
     } else if (out[i] == '/') {
       out[i] = '_';
     }
   }
 
   return NS_OK;
 }
 
-
-nsresult
-PRErrorCode_to_nsresult(PRErrorCode error)
-{
-  if (!error) {
-    MOZ_NOT_REACHED("Function failed without calling PR_GetError");
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  // From NSSErrorsService::GetXPCOMFromNSSError
-  // XXX Don't make up nsresults, it's supposed to be an enum (bug 778113)
-  return (nsresult)NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY,
-                                             -1 * error);
-}
-
-// IMPORTANT: This must be called immediately after the function returning the
-// SECStatus result. The recommended usage is:
-//    nsresult rv = MapSECStatus(f(x, y, z));
-nsresult
-MapSECStatus(SECStatus rv)
-{
-  if (rv == SECSuccess)
-    return NS_OK;
-
-  PRErrorCode error = PR_GetError();
-  return PRErrorCode_to_nsresult(error);
-}
-
 #define DSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("DS160"))
 #define RSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("RS256"))
 
 class KeyPair : public nsIIdentityKeyPair, public nsNSSShutDownObject
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIIDENTITYKEYPAIR