Bug 804663: Create a CryptoTask API to simplify the creation of correct async crypto operations and add more utilities to ScopedNSSTypes.h, r=honzab
authorBrian Smith <bsmith@mozilla.com>
Fri, 13 Jul 2012 15:44:24 -0700
changeset 120705 9f24c48b21e3a7787a2cd11e760136c37fe1c824
parent 120704 5d514385c585f71d77e0bcd279e958bfc2d986d6
child 120706 57e047e6401989a7b04528f75ab095bb40b27b9f
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs804663
milestone20.0a1
Bug 804663: Create a CryptoTask API to simplify the creation of correct async crypto operations and add more utilities to ScopedNSSTypes.h, r=honzab
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,27 +2,82 @@
 /* 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)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertificateList,
@@ -42,38 +97,174 @@ 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
--- 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