Bug 804663: Create a CryptoTask API to simplify the creation of correct async crypto operations and add more utilities to ScopedNSSTypes.h, r=honzab
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