From 46c8b420d37b49b29cfc3c42d4a4f71d9c17c3bc Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 13 Jul 2012 15:44:24 -0700 Subject: [PATCH] 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 | 71 +++++++ security/manager/ssl/src/CryptoTask.h | 87 +++++++++ security/manager/ssl/src/Makefile.in | 2 + security/manager/ssl/src/ScopedNSSTypes.h | 199 +++++++++++++++++++- security/manager/ssl/src/nsNSSComponent.cpp | 5 +- toolkit/identity/IdentityCryptoService.cpp | 29 +-- 6 files changed, 358 insertions(+), 35 deletions(-) create mode 100644 security/manager/ssl/src/CryptoTask.cpp create mode 100644 security/manager/ssl/src/CryptoTask.h diff --git a/security/manager/ssl/src/CryptoTask.cpp b/security/manager/ssl/src/CryptoTask.cpp new file mode 100644 index 00000000000..02000f0fffb --- /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 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 diff --git a/security/manager/ssl/src/CryptoTask.h b/security/manager/ssl/src/CryptoTask.h new file mode 100644 index 00000000000..465b88f7cdd --- /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 + 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 diff --git a/security/manager/ssl/src/Makefile.in b/security/manager/ssl/src/Makefile.in index 99e9acf381b..8bc4a34c17f 100644 --- a/security/manager/ssl/src/Makefile.in +++ b/security/manager/ssl/src/Makefile.in @@ -20,6 +20,7 @@ GRE_MODULE = 1 LIBXUL_LIBRARY = 1 CPPSRCS = \ + CryptoTask.cpp \ nsCERTValInParamWrapper.cpp \ nsNSSCleaner.cpp \ nsCertOverrideService.cpp \ @@ -90,6 +91,7 @@ DEFINES += \ $(NULL) EXPORTS += \ + CryptoTask.h \ nsNSSShutDown.h \ ScopedNSSTypes.h \ $(NULL) diff --git a/security/manager/ssl/src/ScopedNSSTypes.h b/security/manager/ssl/src/ScopedNSSTypes.h index d5077b28378..4a560f0e4a4 100644 --- a/security/manager/ssl/src/ScopedNSSTypes.h +++ b/security/manager/ssl/src/ScopedNSSTypes.h @@ -7,6 +7,8 @@ #ifndef mozilla_ScopedNSSTypes_h #define mozilla_ScopedNSSTypes_h +#include "mozilla/Likely.h" +#include "mozilla/mozalloc_oom.h" #include "mozilla/Scoped.h" #include "prio.h" @@ -15,9 +17,62 @@ #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(p); } + +inline const char * +char_ptr_cast(const uint8_t * p) { return reinterpret_cast(p); } + +inline uint8_t * +uint8_t_ptr_cast(char * p) { return reinterpret_cast(p); } + +inline const uint8_t * +uint8_t_ptr_cast(const char * p) { return reinterpret_cast(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, @@ -47,10 +102,6 @@ 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) @@ -58,6 +109,93 @@ 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, @@ -69,6 +207,59 @@ 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) diff --git a/security/manager/ssl/src/nsNSSComponent.cpp b/security/manager/ssl/src/nsNSSComponent.cpp index a9d703f95ee..3a93e8c482a 100644 --- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -55,6 +55,7 @@ #include "nsNSSShutDown.h" #include "nsSmartCardEvent.h" #include "nsIKeyModule.h" +#include "ScopedNSSTypes.h" #include "nss.h" #include "pk11func.h" @@ -2028,7 +2029,7 @@ nsNSSComponent::VerifySignature(const char* aRSABuf, uint32_t aRSABufLen, *aPrincipal = nullptr; nsNSSShutDownPreventionLock locker; - SEC_PKCS7ContentInfo * p7_info = nullptr; + ScopedSEC_PKCS7ContentInfo p7_info; unsigned char hash[SHA1_LENGTH]; SECItem item; @@ -2127,8 +2128,6 @@ nsNSSComponent::VerifySignature(const char* aRSABuf, uint32_t aRSABufLen, } while (0); } - SEC_PKCS7DestroyContentInfo(p7_info); - return rv2; } diff --git a/toolkit/identity/IdentityCryptoService.cpp b/toolkit/identity/IdentityCryptoService.cpp index 707636ced19..d476103ce65 100644 --- a/toolkit/identity/IdentityCryptoService.cpp +++ b/toolkit/identity/IdentityCryptoService.cpp @@ -13,6 +13,7 @@ #include "nsCOMPtr.h" #include "nsStringGlue.h" #include "mozilla/Base64.h" +#include "ScopedNSSTypes.h" #include "nss.h" #include "pk11pub.h" @@ -60,34 +61,6 @@ Base64UrlEncodeImpl(const nsACString & utf8Input, nsACString & result) 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"))