summaryrefslogtreecommitdiffstats
path: root/dom/crypto/WebCryptoTask.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/crypto/WebCryptoTask.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/crypto/WebCryptoTask.cpp')
-rw-r--r--dom/crypto/WebCryptoTask.cpp3736
1 files changed, 3736 insertions, 0 deletions
diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp
new file mode 100644
index 000000000..57a7da186
--- /dev/null
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -0,0 +1,3736 @@
+/* -*- 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 "pk11pub.h"
+#include "cryptohi.h"
+#include "secerr.h"
+#include "ScopedNSSTypes.h"
+#include "nsNSSComponent.h"
+#include "nsProxyRelease.h"
+
+#include "jsapi.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/dom/KeyAlgorithmProxy.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/dom/WebCryptoTask.h"
+#include "mozilla/dom/WebCryptoThreadPool.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+
+// Template taken from security/nss/lib/util/templates.c
+// This (or SGN_EncodeDigestInfo) would ideally be exported
+// by NSS and until that happens we have to keep our own copy.
+const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(SGNDigestInfo) },
+ { SEC_ASN1_INLINE,
+ offsetof(SGNDigestInfo,digestAlgorithm),
+ SEC_ASN1_GET(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING,
+ offsetof(SGNDigestInfo,digest) },
+ { 0, }
+};
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::dom::workers::Canceling;
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::Status;
+using mozilla::dom::workers::WorkerHolder;
+using mozilla::dom::workers::WorkerPrivate;
+
+// Pre-defined identifiers for telemetry histograms
+
+enum TelemetryMethod {
+ TM_ENCRYPT = 0,
+ TM_DECRYPT = 1,
+ TM_SIGN = 2,
+ TM_VERIFY = 3,
+ TM_DIGEST = 4,
+ TM_GENERATEKEY = 5,
+ TM_DERIVEKEY = 6,
+ TM_DERIVEBITS = 7,
+ TM_IMPORTKEY = 8,
+ TM_EXPORTKEY = 9,
+ TM_WRAPKEY = 10,
+ TM_UNWRAPKEY = 11
+};
+
+enum TelemetryAlgorithm {
+ // Please make additions at the end of the list,
+ // to preserve comparability of histograms over time
+ TA_UNKNOWN = 0,
+ // encrypt / decrypt
+ TA_AES_CBC = 1,
+ TA_AES_CFB = 2,
+ TA_AES_CTR = 3,
+ TA_AES_GCM = 4,
+ TA_RSAES_PKCS1 = 5, // NB: This algorithm has been removed
+ TA_RSA_OAEP = 6,
+ // sign/verify
+ TA_RSASSA_PKCS1 = 7,
+ TA_RSA_PSS = 8,
+ TA_HMAC_SHA_1 = 9,
+ TA_HMAC_SHA_224 = 10,
+ TA_HMAC_SHA_256 = 11,
+ TA_HMAC_SHA_384 = 12,
+ TA_HMAC_SHA_512 = 13,
+ // digest
+ TA_SHA_1 = 14,
+ TA_SHA_224 = 15,
+ TA_SHA_256 = 16,
+ TA_SHA_384 = 17,
+ TA_SHA_512 = 18,
+ // Later additions
+ TA_AES_KW = 19,
+ TA_ECDH = 20,
+ TA_PBKDF2 = 21,
+ TA_ECDSA = 22,
+ TA_HKDF = 23,
+};
+
+// Convenience functions for extracting / converting information
+
+// OOM-safe CryptoBuffer initialization, suitable for constructors
+#define ATTEMPT_BUFFER_INIT(dst, src) \
+ if (!dst.Assign(src)) { \
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
+ return; \
+ }
+
+// OOM-safe CryptoBuffer-to-SECItem copy, suitable for DoCrypto
+#define ATTEMPT_BUFFER_TO_SECITEM(arena, dst, src) \
+ if (!src.ToSECItem(arena, dst)) { \
+ return NS_ERROR_DOM_UNKNOWN_ERR; \
+ }
+
+// OOM-safe CryptoBuffer copy, suitable for DoCrypto
+#define ATTEMPT_BUFFER_ASSIGN(dst, src) \
+ if (!dst.Assign(src)) { \
+ return NS_ERROR_DOM_UNKNOWN_ERR; \
+ }
+
+// Safety check for algorithms that use keys, suitable for constructors
+#define CHECK_KEY_ALGORITHM(keyAlg, algName) \
+ { \
+ if (!NORMALIZED_EQUALS(keyAlg.mName, algName)) { \
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; \
+ return; \
+ } \
+ }
+
+class ClearException
+{
+public:
+ explicit ClearException(JSContext* aCx)
+ : mCx(aCx)
+ {}
+
+ ~ClearException()
+ {
+ JS_ClearPendingException(mCx);
+ }
+
+private:
+ JSContext* mCx;
+};
+
+class WebCryptoTask::InternalWorkerHolder final : public WorkerHolder
+{
+ InternalWorkerHolder()
+ { }
+
+ ~InternalWorkerHolder()
+ {
+ NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+ // Nothing to do here since the parent destructor releases the
+ // worker automatically.
+ }
+
+public:
+ static already_AddRefed<InternalWorkerHolder>
+ Create()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ RefPtr<InternalWorkerHolder> ref = new InternalWorkerHolder();
+ if (NS_WARN_IF(!ref->HoldWorker(workerPrivate, Canceling))) {
+ return nullptr;
+ }
+ return ref.forget();
+ }
+
+ virtual bool
+ Notify(Status aStatus) override
+ {
+ NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+ // Do nothing here. Since WebCryptoTask dispatches back to
+ // the worker thread using nsThread::Dispatch() instead of
+ // WorkerRunnable it will always be able to execute its
+ // runnables.
+ return true;
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(WebCryptoTask::InternalWorkerHolder)
+};
+
+template<class OOS>
+static nsresult
+GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm, nsString& aName)
+{
+ ClearException ce(aCx);
+
+ if (aAlgorithm.IsString()) {
+ // If string, then treat as algorithm name
+ aName.Assign(aAlgorithm.GetAsString());
+ } else {
+ // Coerce to algorithm and extract name
+ JS::RootedValue value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
+ Algorithm alg;
+
+ if (!alg.Init(aCx, value)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ aName = alg.mName;
+ }
+
+ if (!NormalizeToken(aName, aName)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+template<class T, class OOS>
+static nsresult
+Coerce(JSContext* aCx, T& aTarget, const OOS& aAlgorithm)
+{
+ ClearException ce(aCx);
+
+ if (!aAlgorithm.IsObject()) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ JS::RootedValue value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
+ if (!aTarget.Init(aCx, value)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+inline size_t
+MapHashAlgorithmNameToBlockSize(const nsString& aName)
+{
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1) ||
+ aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return 512;
+ }
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384) ||
+ aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return 1024;
+ }
+
+ return 0;
+}
+
+inline nsresult
+GetKeyLengthForAlgorithm(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ size_t& aLength)
+{
+ aLength = 0;
+
+ // Extract algorithm name
+ nsString algName;
+ if (NS_FAILED(GetAlgorithmName(aCx, aAlgorithm, algName))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Read AES key length from given algorithm object.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ RootedDictionary<AesDerivedKeyParams> params(aCx);
+ if (NS_FAILED(Coerce(aCx, params, aAlgorithm))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (params.mLength != 128 &&
+ params.mLength != 192 &&
+ params.mLength != 256) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ aLength = params.mLength;
+ return NS_OK;
+ }
+
+ // Read HMAC key length from given algorithm object or
+ // determine key length as the block size of the given hash.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacDerivedKeyParams> params(aCx);
+ if (NS_FAILED(Coerce(aCx, params, aAlgorithm))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Return the passed length, if any.
+ if (params.mLength.WasPassed()) {
+ aLength = params.mLength.Value();
+ return NS_OK;
+ }
+
+ nsString hashName;
+ if (NS_FAILED(GetAlgorithmName(aCx, params.mHash, hashName))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Return the given hash algorithm's block size as the key length.
+ size_t length = MapHashAlgorithmNameToBlockSize(hashName);
+ if (length == 0) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ aLength = length;
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+inline bool
+MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult)
+{
+ switch (aOIDTag) {
+ case SEC_OID_SECG_EC_SECP256R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256);
+ break;
+ case SEC_OID_SECG_EC_SECP384R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384);
+ break;
+ case SEC_OID_SECG_EC_SECP521R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+inline SECOidTag
+MapHashAlgorithmNameToOID(const nsString& aName)
+{
+ SECOidTag hashOID(SEC_OID_UNKNOWN);
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ hashOID = SEC_OID_SHA1;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ hashOID = SEC_OID_SHA256;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ hashOID = SEC_OID_SHA384;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ hashOID = SEC_OID_SHA512;
+ }
+
+ return hashOID;
+}
+
+inline CK_MECHANISM_TYPE
+MapHashAlgorithmNameToMgfMechanism(const nsString& aName) {
+ CK_MECHANISM_TYPE mech(UNKNOWN_CK_MECHANISM);
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ mech = CKG_MGF1_SHA1;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ mech = CKG_MGF1_SHA256;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ mech = CKG_MGF1_SHA384;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ mech = CKG_MGF1_SHA512;
+ }
+
+ return mech;
+}
+
+// Implementation of WebCryptoTask methods
+
+void
+WebCryptoTask::DispatchWithPromise(Promise* aResultPromise)
+{
+ mResultPromise = aResultPromise;
+
+ // Fail if an error was set during the constructor
+ MAYBE_EARLY_FAIL(mEarlyRv)
+
+ // Perform pre-NSS operations, and fail if they fail
+ mEarlyRv = BeforeCrypto();
+ MAYBE_EARLY_FAIL(mEarlyRv)
+
+ // Skip NSS if we're already done, or launch a CryptoTask
+ if (mEarlyComplete) {
+ CallCallback(mEarlyRv);
+ Skip();
+ return;
+ }
+
+ // Store calling thread
+ mOriginalThread = NS_GetCurrentThread();
+
+ // If we are running on a worker thread we must hold the worker
+ // alive while we work on the thread pool. Otherwise the worker
+ // private may get torn down before we dispatch back to complete
+ // the transaction.
+ if (!NS_IsMainThread()) {
+ mWorkerHolder = InternalWorkerHolder::Create();
+ // If we can't register a holder then the worker is already
+ // shutting down. Don't start new work.
+ if (!mWorkerHolder) {
+ mEarlyRv = NS_BINDING_ABORTED;
+ }
+ }
+ MAYBE_EARLY_FAIL(mEarlyRv);
+
+ // dispatch to thread pool
+ mEarlyRv = WebCryptoThreadPool::Dispatch(this);
+ MAYBE_EARLY_FAIL(mEarlyRv)
+}
+
+NS_IMETHODIMP
+WebCryptoTask::Run()
+{
+ // Run heavy crypto operations on the thread pool, off the original thread.
+ if (!IsOnOriginalThread()) {
+ nsNSSShutDownPreventionLock locker;
+
+ if (isAlreadyShutDown()) {
+ mRv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ mRv = CalculateResult();
+ }
+
+ // Back to the original thread, i.e. continue below.
+ mOriginalThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ // We're now back on the calling thread.
+
+ // Release NSS resources now, before calling CallCallback, so that
+ // WebCryptoTasks have consistent behavior regardless of whether NSS is shut
+ // down between CalculateResult being called and CallCallback being called.
+ virtualDestroyNSSReference();
+
+ CallCallback(mRv);
+
+ // Stop holding the worker thread alive now that the async work has
+ // been completed.
+ mWorkerHolder = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+WebCryptoTask::Cancel()
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ FailWithError(NS_BINDING_ABORTED);
+ return NS_OK;
+}
+
+void
+WebCryptoTask::FailWithError(nsresult aRv)
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
+
+ // Blindly convert nsresult to DOMException
+ // Individual tasks must ensure they pass the right values
+ mResultPromise->MaybeReject(aRv);
+ // Manually release mResultPromise while we're on the main thread
+ mResultPromise = nullptr;
+ mWorkerHolder = nullptr;
+ Cleanup();
+}
+
+nsresult
+WebCryptoTask::CalculateResult()
+{
+ MOZ_ASSERT(!IsOnOriginalThread());
+
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return DoCrypto();
+}
+
+void
+WebCryptoTask::CallCallback(nsresult rv)
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ if (NS_FAILED(rv)) {
+ FailWithError(rv);
+ return;
+ }
+
+ nsresult rv2 = AfterCrypto();
+ if (NS_FAILED(rv2)) {
+ FailWithError(rv2);
+ return;
+ }
+
+ Resolve();
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, true);
+
+ // Manually release mResultPromise while we're on the main thread
+ mResultPromise = nullptr;
+ Cleanup();
+}
+
+// Some generic utility classes
+
+class FailureTask : public WebCryptoTask
+{
+public:
+ explicit FailureTask(nsresult aRv) {
+ mEarlyRv = aRv;
+ }
+};
+
+class ReturnArrayBufferViewTask : public WebCryptoTask
+{
+protected:
+ CryptoBuffer mResult;
+
+private:
+ // Returns mResult as an ArrayBufferView, or an error
+ virtual void Resolve() override
+ {
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ }
+};
+
+class DeferredData
+{
+public:
+ template<class T>
+ void SetData(const T& aData) {
+ mDataIsSet = mData.Assign(aData);
+ }
+
+protected:
+ DeferredData()
+ : mDataIsSet(false)
+ {}
+
+ CryptoBuffer mData;
+ bool mDataIsSet;
+};
+
+class AesTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check that we got a reasonable key
+ if ((mSymKey.Length() != 16) &&
+ (mSymKey.Length() != 24) &&
+ (mSymKey.Length() != 32))
+ {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Cache parameters depending on the specific algorithm
+ TelemetryAlgorithm telemetryAlg;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_CBC);
+
+ mMechanism = CKM_AES_CBC_PAD;
+ telemetryAlg = TA_AES_CBC;
+ AesCbcParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mIv)
+ if (mIv.Length() != 16) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_CTR);
+
+ mMechanism = CKM_AES_CTR;
+ telemetryAlg = TA_AES_CTR;
+ AesCtrParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mCounter)
+ if (mIv.Length() != 16) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mCounterLength = params.mLength;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_GCM);
+
+ mMechanism = CKM_AES_GCM;
+ telemetryAlg = TA_AES_GCM;
+ AesGcmParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mIv)
+
+ if (params.mAdditionalData.WasPassed()) {
+ ATTEMPT_BUFFER_INIT(mAad, params.mAdditionalData.Value())
+ }
+
+ // 32, 64, 96, 104, 112, 120 or 128
+ mTagLength = 128;
+ if (params.mTagLength.WasPassed()) {
+ mTagLength = params.mTagLength.Value();
+ if ((mTagLength > 128) ||
+ !(mTagLength == 32 || mTagLength == 64 ||
+ (mTagLength >= 96 && mTagLength % 8 == 0))) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ CryptoBuffer mIv; // Initialization vector
+ CryptoBuffer mAad; // Additional Authenticated Data
+ uint8_t mTagLength;
+ uint8_t mCounterLength;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Construct the parameters object depending on algorithm
+ SECItem param = { siBuffer, nullptr, 0 };
+ CK_AES_CTR_PARAMS ctrParams;
+ CK_GCM_PARAMS gcmParams;
+ switch (mMechanism) {
+ case CKM_AES_CBC_PAD:
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &param, mIv);
+ break;
+ case CKM_AES_CTR:
+ ctrParams.ulCounterBits = mCounterLength;
+ MOZ_ASSERT(mIv.Length() == 16);
+ memcpy(&ctrParams.cb, mIv.Elements(), 16);
+ param.type = siBuffer;
+ param.data = (unsigned char*) &ctrParams;
+ param.len = sizeof(ctrParams);
+ break;
+ case CKM_AES_GCM:
+ gcmParams.pIv = mIv.Elements();
+ gcmParams.ulIvLen = mIv.Length();
+ gcmParams.pAAD = mAad.Elements();
+ gcmParams.ulAADLen = mAad.Length();
+ gcmParams.ulTagBits = mTagLength;
+ param.type = siBuffer;
+ param.data = (unsigned char*) &gcmParams;
+ param.len = sizeof(gcmParams);
+ break;
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Initialize the output buffer (enough space for padding / a full tag)
+ uint32_t dataLen = mData.Length();
+ uint32_t maxLen = dataLen + 16;
+ if (!mResult.SetLength(maxLen, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ uint32_t outLen = 0;
+
+ // Perform the encryption/decryption
+ if (mEncrypt) {
+ rv = MapSECStatus(PK11_Encrypt(symKey.get(), mMechanism, &param,
+ mResult.Elements(), &outLen,
+ mResult.Length(), mData.Elements(),
+ mData.Length()));
+ } else {
+ rv = MapSECStatus(PK11_Decrypt(symKey.get(), mMechanism, &param,
+ mResult.Elements(), &outLen,
+ mResult.Length(), mData.Elements(),
+ mData.Length()));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return rv;
+ }
+};
+
+// This class looks like an encrypt/decrypt task, like AesTask,
+// but it is only exposed to wrapKey/unwrapKey, not encrypt/decrypt
+class AesKwTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mMechanism(CKM_NSS_AES_KEY_WRAP)
+ , mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mMechanism(CKM_NSS_AES_KEY_WRAP)
+ , mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_KW);
+
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check that we got a reasonable key
+ if ((mSymKey.Length() != 16) &&
+ (mSymKey.Length() != 24) &&
+ (mSymKey.Length() != 32))
+ {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_AES_KW);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Check that the input is a multiple of 64 bits long
+ if (mData.Length() == 0 || mData.Length() % 8 != 0) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_WRAP, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Import the data to a SECItem
+ SECItem dataItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &dataItem, mData);
+
+ // Parameters for the fake keys
+ CK_MECHANISM_TYPE fakeMechanism = CKM_SHA_1_HMAC;
+ CK_ATTRIBUTE_TYPE fakeOperation = CKA_SIGN;
+
+ if (mEncrypt) {
+ // Import the data into a fake PK11SymKey structure
+ ScopedPK11SymKey keyToWrap(PK11_ImportSymKey(slot, fakeMechanism,
+ PK11_OriginUnwrap, fakeOperation,
+ &dataItem, nullptr));
+ if (!keyToWrap) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Encrypt and return the wrapped key
+ // AES-KW encryption results in a wrapped key 64 bits longer
+ if (!mResult.SetLength(mData.Length() + 8, fallible)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ SECItem resultItem = {siBuffer, mResult.Elements(),
+ (unsigned int) mResult.Length()};
+ rv = MapSECStatus(PK11_WrapSymKey(mMechanism, nullptr, symKey.get(),
+ keyToWrap.get(), &resultItem));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ } else {
+ // Decrypt the ciphertext into a temporary PK11SymKey
+ // Unwrapped key should be 64 bits shorter
+ int keySize = mData.Length() - 8;
+ ScopedPK11SymKey unwrappedKey(PK11_UnwrapSymKey(symKey, mMechanism, nullptr,
+ &dataItem, fakeMechanism,
+ fakeOperation, keySize));
+ if (!unwrappedKey) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Export the key to get the cleartext
+ rv = MapSECStatus(PK11_ExtractKeyValue(unwrappedKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(unwrappedKey));
+ }
+
+ return rv;
+ }
+};
+
+class RsaOaepTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_OAEP);
+
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_OAEP);
+
+ if (mEncrypt) {
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ mStrength = SECKEY_PublicKeyStrength(mPubKey);
+ } else {
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ mStrength = PK11_GetPrivateModulusLen(mPrivKey);
+ }
+
+ // The algorithm could just be given as a string
+ // in which case there would be no label specified.
+ if (!aAlgorithm.IsString()) {
+ RootedDictionary<RsaOaepParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (params.mLabel.WasPassed()) {
+ ATTEMPT_BUFFER_INIT(mLabel, params.mLabel.Value());
+ }
+ }
+ // Otherwise mLabel remains the empty octet string, as intended
+
+ KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+ mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+ mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlg.mName);
+
+ // Check we found appropriate mechanisms.
+ if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+ mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+
+private:
+ CK_MECHANISM_TYPE mHashMechanism;
+ CK_MECHANISM_TYPE mMgfMechanism;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+ CryptoBuffer mLabel;
+ uint32_t mStrength;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Ciphertext is an integer mod the modulus, so it will be
+ // no longer than mStrength octets
+ if (!mResult.SetLength(mStrength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ CK_RSA_PKCS_OAEP_PARAMS oaepParams;
+ oaepParams.source = CKZ_DATA_SPECIFIED;
+
+ oaepParams.pSourceData = mLabel.Length() ? mLabel.Elements() : nullptr;
+ oaepParams.ulSourceDataLen = mLabel.Length();
+
+ oaepParams.mgf = mMgfMechanism;
+ oaepParams.hashAlg = mHashMechanism;
+
+ SECItem param;
+ param.type = siBuffer;
+ param.data = (unsigned char*) &oaepParams;
+ param.len = sizeof(oaepParams);
+
+ uint32_t outLen = 0;
+ if (mEncrypt) {
+ // PK11_PubEncrypt() checks the plaintext's length and fails if it is too
+ // long to encrypt, i.e. if it is longer than (k - 2hLen - 2) with 'k'
+ // being the length in octets of the RSA modulus n and 'hLen' being the
+ // output length in octets of the chosen hash function.
+ // <https://tools.ietf.org/html/rfc3447#section-7.1>
+ rv = MapSECStatus(PK11_PubEncrypt(
+ mPubKey.get(), CKM_RSA_PKCS_OAEP, &param,
+ mResult.Elements(), &outLen, mResult.Length(),
+ mData.Elements(), mData.Length(), nullptr));
+ } else {
+ rv = MapSECStatus(PK11_PrivDecrypt(
+ mPrivKey.get(), CKM_RSA_PKCS_OAEP, &param,
+ mResult.Elements(), &outLen, mResult.Length(),
+ mData.Elements(), mData.Length()));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return NS_OK;
+ }
+};
+
+class HmacTask : public WebCryptoTask
+{
+public:
+ HmacTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+ : mMechanism(aKey.Algorithm().Mechanism())
+ , mSymKey(aKey.GetSymKey())
+ , mSign(aSign)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HMAC);
+
+ ATTEMPT_BUFFER_INIT(mData, aData);
+ if (!aSign) {
+ ATTEMPT_BUFFER_INIT(mSignature, aSignature);
+ }
+
+ // Check that we got a symmetric key
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ TelemetryAlgorithm telemetryAlg;
+ switch (mMechanism) {
+ case CKM_SHA_1_HMAC: telemetryAlg = TA_HMAC_SHA_1; break;
+ case CKM_SHA224_HMAC: telemetryAlg = TA_HMAC_SHA_224; break;
+ case CKM_SHA256_HMAC: telemetryAlg = TA_HMAC_SHA_256; break;
+ case CKM_SHA384_HMAC: telemetryAlg = TA_HMAC_SHA_384; break;
+ case CKM_SHA512_HMAC: telemetryAlg = TA_HMAC_SHA_512; break;
+ default: telemetryAlg = TA_UNKNOWN;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ CryptoBuffer mData;
+ CryptoBuffer mSignature;
+ CryptoBuffer mResult;
+ bool mSign;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Initialize the output buffer
+ if (!mResult.SetLength(HASH_LENGTH_MAX, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ uint32_t outLen;
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_SIGN, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Compute the MAC
+ SECItem param = { siBuffer, nullptr, 0 };
+ UniquePK11Context ctx(PK11_CreateContextBySymKey(mMechanism, CKA_SIGN,
+ symKey.get(), &param));
+ if (!ctx.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ nsresult rv = MapSECStatus(PK11_DigestBegin(ctx.get()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = MapSECStatus(PK11_DigestOp(ctx.get(), mData.Elements(), mData.Length()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = MapSECStatus(PK11_DigestFinal(ctx.get(), mResult.Elements(),
+ &outLen, mResult.Length()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return rv;
+ }
+
+ // Returns mResult as an ArrayBufferView, or an error
+ virtual void Resolve() override
+ {
+ if (mSign) {
+ // Return the computed MAC
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ } else {
+ // Compare the MAC to the provided signature
+ // No truncation allowed
+ bool equal = (mResult.Length() == mSignature.Length());
+ if (equal) {
+ int cmp = NSS_SecureMemcmp(mSignature.Elements(),
+ mResult.Elements(),
+ mSignature.Length());
+ equal = (cmp == 0);
+ }
+ mResultPromise->MaybeResolve(equal);
+ }
+ }
+};
+
+class AsymmetricSignVerifyTask : public WebCryptoTask
+{
+public:
+ AsymmetricSignVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+ : mOidTag(SEC_OID_UNKNOWN)
+ , mHashMechanism(UNKNOWN_CK_MECHANISM)
+ , mMgfMechanism(UNKNOWN_CK_MECHANISM)
+ , mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mSaltLength(0)
+ , mSign(aSign)
+ , mVerified(false)
+ , mAlgorithm(Algorithm::UNKNOWN)
+ {
+ ATTEMPT_BUFFER_INIT(mData, aData);
+ if (!aSign) {
+ ATTEMPT_BUFFER_INIT(mSignature, aSignature);
+ }
+
+ nsString algName;
+ nsString hashAlgName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ mAlgorithm = Algorithm::RSA_PKCS1;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
+ hashAlgName = aKey.Algorithm().mRsa.mHash.mName;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ mAlgorithm = Algorithm::RSA_PSS;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS);
+
+ KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+ hashAlgName = hashAlg.mName;
+ mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+ mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName);
+
+ // Check we found appropriate mechanisms.
+ if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+ mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ RootedDictionary<RsaPssParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ mSaltLength = params.mSaltLength;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ mAlgorithm = Algorithm::ECDSA;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA);
+
+ // For ECDSA, the hash name comes from the algorithm parameter
+ RootedDictionary<EcdsaParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ } else {
+ // This shouldn't happen; CreateSignVerifyTask shouldn't create
+ // one of these unless it's for the above algorithms.
+ MOZ_ASSERT(false);
+ }
+
+ // Must have a valid algorithm by now.
+ MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN);
+
+ // Determine hash algorithm to use.
+ mOidTag = MapHashAlgorithmNameToOID(hashAlgName);
+ if (mOidTag == SEC_OID_UNKNOWN) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Check that we have the appropriate key
+ if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ }
+
+private:
+ SECOidTag mOidTag;
+ CK_MECHANISM_TYPE mHashMechanism;
+ CK_MECHANISM_TYPE mMgfMechanism;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+ CryptoBuffer mSignature;
+ CryptoBuffer mData;
+ uint32_t mSaltLength;
+ bool mSign;
+ bool mVerified;
+
+ // The signature algorithm to use.
+ enum class Algorithm: uint8_t {ECDSA, RSA_PKCS1, RSA_PSS, UNKNOWN};
+ Algorithm mAlgorithm;
+
+ virtual nsresult DoCrypto() override
+ {
+ SECStatus rv;
+ ScopedSECItem hash(::SECITEM_AllocItem(nullptr, nullptr,
+ HASH_ResultLenByOidTag(mOidTag)));
+ if (!hash) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Compute digest over given data.
+ rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(), mData.Length());
+ NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+
+ // Wrap hash in a digest info template (RSA-PKCS1 only).
+ if (mAlgorithm == Algorithm::RSA_PKCS1) {
+ ScopedSGNDigestInfo di(SGN_CreateDigestInfo(mOidTag, hash->data, hash->len));
+ if (!di) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Reuse |hash|.
+ SECITEM_FreeItem(hash, false);
+ if (!SEC_ASN1EncodeItem(nullptr, hash, di, SGN_DigestInfoTemplate)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ SECItem* params = nullptr;
+ CK_MECHANISM_TYPE mech = PK11_MapSignKeyType((mSign ? mPrivKey->keyType :
+ mPubKey->keyType));
+
+ CK_RSA_PKCS_PSS_PARAMS rsaPssParams;
+ SECItem rsaPssParamsItem = { siBuffer, };
+
+ // Set up parameters for RSA-PSS.
+ if (mAlgorithm == Algorithm::RSA_PSS) {
+ rsaPssParams.hashAlg = mHashMechanism;
+ rsaPssParams.mgf = mMgfMechanism;
+ rsaPssParams.sLen = mSaltLength;
+
+ rsaPssParamsItem.data = (unsigned char*)&rsaPssParams;
+ rsaPssParamsItem.len = sizeof(rsaPssParams);
+ params = &rsaPssParamsItem;
+
+ mech = CKM_RSA_PKCS_PSS;
+ }
+
+ // Allocate SECItem to hold the signature.
+ uint32_t len = mSign ? PK11_SignatureLen(mPrivKey) : 0;
+ ScopedSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
+ if (!sig) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (mSign) {
+ // Sign the hash.
+ rv = PK11_SignWithMechanism(mPrivKey, mech, params, sig, hash);
+ NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+ ATTEMPT_BUFFER_ASSIGN(mSignature, sig);
+ } else {
+ // Copy the given signature to the SECItem.
+ if (!mSignature.ToSECItem(nullptr, sig)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Verify the signature.
+ rv = PK11_VerifyWithMechanism(mPubKey, mech, params, sig, hash, nullptr);
+ mVerified = NS_SUCCEEDED(MapSECStatus(rv));
+ }
+
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ if (mSign) {
+ TypedArrayCreator<ArrayBuffer> ret(mSignature);
+ mResultPromise->MaybeResolve(ret);
+ } else {
+ mResultPromise->MaybeResolve(mVerified);
+ }
+ }
+};
+
+class DigestTask : public ReturnArrayBufferViewTask
+{
+public:
+ DigestTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData)
+ {
+ ATTEMPT_BUFFER_INIT(mData, aData);
+
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ TelemetryAlgorithm telemetryAlg;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ telemetryAlg = TA_SHA_1;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ telemetryAlg = TA_SHA_224;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ telemetryAlg = TA_SHA_256;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ telemetryAlg = TA_SHA_384;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ mOidTag = MapHashAlgorithmNameToOID(algName);
+ }
+
+private:
+ SECOidTag mOidTag;
+ CryptoBuffer mData;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Resize the result buffer
+ uint32_t hashLen = HASH_ResultLenByOidTag(mOidTag);
+ if (!mResult.SetLength(hashLen, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // Compute the hash
+ nsresult rv = MapSECStatus(PK11_HashBuf(mOidTag, mResult.Elements(),
+ mData.Elements(), mData.Length()));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return rv;
+ }
+};
+
+class ImportKeyTask : public WebCryptoTask
+{
+public:
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ mFormat = aFormat;
+ mDataIsSet = false;
+ mDataIsJwk = false;
+
+ // This stuff pretty much always happens, so we'll do it here
+ mKey = new CryptoKey(aGlobal);
+ mKey->SetExtractable(aExtractable);
+ mKey->ClearUsages();
+ for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKey->AddUsage(aKeyUsages[i]);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+ static bool JwkCompatible(const JsonWebKey& aJwk, const CryptoKey* aKey)
+ {
+ // Check 'ext'
+ if (aKey->Extractable() &&
+ aJwk.mExt.WasPassed() && !aJwk.mExt.Value()) {
+ return false;
+ }
+
+ // Check 'alg'
+ if (aJwk.mAlg.WasPassed() &&
+ aJwk.mAlg.Value() != aKey->Algorithm().JwkAlg()) {
+ return false;
+ }
+
+ // Check 'key_ops'
+ if (aJwk.mKey_ops.WasPassed()) {
+ nsTArray<nsString> usages;
+ aKey->GetUsages(usages);
+ for (size_t i = 0; i < usages.Length(); ++i) {
+ if (!aJwk.mKey_ops.Value().Contains(usages[i])) {
+ return false;
+ }
+ }
+ }
+
+ // Individual algorithms may still have to check 'use'
+ return true;
+ }
+
+ void SetKeyData(JSContext* aCx, JS::Handle<JSObject*> aKeyData)
+ {
+ mDataIsJwk = false;
+
+ // Try ArrayBuffer
+ RootedTypedArray<ArrayBuffer> ab(aCx);
+ if (ab.Init(aKeyData)) {
+ if (!mKeyData.Assign(ab)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return;
+ }
+
+ // Try ArrayBufferView
+ RootedTypedArray<ArrayBufferView> abv(aCx);
+ if (abv.Init(aKeyData)) {
+ if (!mKeyData.Assign(abv)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return;
+ }
+
+ // Try JWK
+ ClearException ce(aCx);
+ JS::RootedValue value(aCx, JS::ObjectValue(*aKeyData));
+ if (!mJwk.Init(aCx, value)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mDataIsJwk = true;
+ }
+
+ void SetKeyDataMaybeParseJWK(const CryptoBuffer& aKeyData)
+ {
+ if (!mKeyData.Assign(aKeyData)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ mDataIsJwk = false;
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ nsDependentCSubstring utf8((const char*) mKeyData.Elements(),
+ (const char*) (mKeyData.Elements() +
+ mKeyData.Length()));
+ if (!IsUTF8(utf8)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ nsString json = NS_ConvertUTF8toUTF16(utf8);
+ if (!mJwk.Init(json)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mDataIsJwk = true;
+ }
+ }
+
+ void SetRawKeyData(const CryptoBuffer& aKeyData)
+ {
+ if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ if (!mKeyData.Assign(aKeyData)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ mDataIsJwk = false;
+ }
+
+protected:
+ nsString mFormat;
+ RefPtr<CryptoKey> mKey;
+ CryptoBuffer mKeyData;
+ bool mDataIsSet;
+ bool mDataIsJwk;
+ JsonWebKey mJwk;
+ nsString mAlgName;
+
+private:
+ virtual void Resolve() override
+ {
+ mResultPromise->MaybeResolve(mKey);
+ }
+
+ virtual void Cleanup() override
+ {
+ mKey = nullptr;
+ }
+};
+
+
+class ImportSymmetricKeyTask : public ImportKeyTask
+{
+public:
+ ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // This task only supports raw and JWK format.
+ if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // If this is an HMAC key, import the hash name
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+ }
+
+ virtual nsresult BeforeCrypto() override
+ {
+ nsresult rv;
+
+ // If we're doing a JWK import, import the key data
+ if (mDataIsJwk) {
+ if (!mJwk.mK.WasPassed()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ // Import the key material
+ rv = mKeyData.FromJwkBase64(mJwk.mK.Value());
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ }
+
+ // Check that we have valid key data.
+ if (mKeyData.Length() == 0) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ // Construct an appropriate KeyAlorithm,
+ // and verify that usages are appropriate
+ uint32_t length = 8 * mKeyData.Length(); // bytes to bits
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::DECRYPT |
+ CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) &&
+ mKey->HasUsageOtherThan(CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if ( (length != 128) && (length != 192) && (length != 256) ) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ mKey->Algorithm().MakeAes(mAlgName, length);
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+ !mJwk.mUse.Value().EqualsLiteral(JWK_USE_ENC)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ mKey->Algorithm().MakeAes(mAlgName, length);
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed()) {
+ // There is not a 'use' value consistent with PBKDF or HKDF
+ return NS_ERROR_DOM_DATA_ERR;
+ };
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::SIGN | CryptoKey::VERIFY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mKey->Algorithm().MakeHmac(length, mHashName);
+
+ if (mKey->Algorithm().Mechanism() == UNKNOWN_CK_MECHANISM) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+ !mJwk.mUse.Value().EqualsLiteral(JWK_USE_SIG)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetSymKey(mKeyData))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::SECRET);
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mEarlyComplete = true;
+ return NS_OK;
+ }
+
+private:
+ nsString mHashName;
+};
+
+class ImportRsaKeyTask : public ImportKeyTask
+{
+public:
+ ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // If this is RSA with a hash, cache the hash name
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ RootedDictionary<RsaHashedImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+ // Check support for the algorithm and hash names
+ CK_MECHANISM_TYPE mech1 = MapAlgorithmNameToMechanism(mAlgName);
+ CK_MECHANISM_TYPE mech2 = MapAlgorithmNameToMechanism(mHashName);
+ if ((mech1 == UNKNOWN_CK_MECHANISM) || (mech2 == UNKNOWN_CK_MECHANISM)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+
+private:
+ nsString mHashName;
+ uint32_t mModulusLength;
+ CryptoBuffer mPublicExponent;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsNSSShutDownPreventionLock locker;
+
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+ ScopedSECKEYPrivateKey privKey;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mJwk.mD.WasPassed())) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else {
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ mJwk.mD.WasPassed())) {
+ // Private key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
+ privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker);
+ } else {
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
+ }
+
+ if (!privKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPrivateKey(privKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PRIVATE);
+ pubKey = SECKEY_ConvertToPublicKey(privKey.get());
+ if (!pubKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ } else {
+ // Invalid key format
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Extract relevant information from the public key
+ mModulusLength = 8 * pubKey->u.rsa.modulus.len;
+ if (!mPublicExponent.Assign(&pubKey->u.rsa.publicExponent)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ // Check permissions for the requested operation
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::WRAPKEY)) ||
+ (mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
+ (mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(CryptoKey::SIGN))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ }
+
+ // Set an appropriate KeyAlgorithm
+ if (!mKey->Algorithm().MakeRsa(mAlgName, mModulusLength,
+ mPublicExponent, mHashName)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class ImportEcKeyTask : public ImportKeyTask
+{
+public:
+ ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ RootedDictionary<EcKeyImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv) || !params.mNamedCurve.WasPassed()) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (!NormalizeToken(params.mNamedCurve.Value(), mNamedCurve)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+ }
+
+private:
+ nsString mNamedCurve;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+ ScopedSECKEYPrivateKey privKey;
+
+ nsNSSShutDownPreventionLock locker;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && mJwk.mD.WasPassed()) {
+ // Private key import
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
+ if (!privKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPrivateKey(privKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PRIVATE);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+ mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mJwk.mD.WasPassed())) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ pubKey = CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ if (!CheckEncodedECParameters(&pubKey->u.ec.DEREncodedParams)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Construct the OID tag.
+ SECItem oid = { siBuffer, nullptr, 0 };
+ oid.len = pubKey->u.ec.DEREncodedParams.data[1];
+ oid.data = pubKey->u.ec.DEREncodedParams.data + 2;
+
+ // Find a matching and supported named curve.
+ if (!MapOIDTagToNamedCurve(SECOID_FindOIDTag(&oid), mNamedCurve)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ // Extract 'crv' parameter from JWKs.
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ if (!NormalizeToken(mJwk.mCrv.Value(), mNamedCurve)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+ privateAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+ publicAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ privateAllowedUsages = CryptoKey::SIGN;
+ publicAllowedUsages = CryptoKey::VERIFY;
+ }
+
+ // Check permissions for the requested operation
+ if ((mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(privateAllowedUsages)) ||
+ (mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(publicAllowedUsages))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mKey->Algorithm().MakeEc(mAlgName, mNamedCurve);
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class ImportDhKeyTask : public ImportKeyTask
+{
+public:
+ ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ RootedDictionary<DhImportKeyParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoBuffer prime;
+ ATTEMPT_BUFFER_INIT(mPrime, params.mPrime);
+
+ CryptoBuffer generator;
+ ATTEMPT_BUFFER_INIT(mGenerator, params.mGenerator);
+ }
+ }
+
+private:
+ CryptoBuffer mPrime;
+ CryptoBuffer mGenerator;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+
+ nsNSSShutDownPreventionLock locker;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+ mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ ATTEMPT_BUFFER_ASSIGN(mPrime, &pubKey->u.dh.prime);
+ ATTEMPT_BUFFER_ASSIGN(mGenerator, &pubKey->u.dh.base);
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ // Check permissions for the requested operation
+ if (mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mKey->Algorithm().MakeDh(mAlgName, mPrime, mGenerator)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+};
+
+class ExportKeyTask : public WebCryptoTask
+{
+public:
+ ExportKeyTask(const nsAString& aFormat, CryptoKey& aKey)
+ : mFormat(aFormat)
+ , mSymKey(aKey.GetSymKey())
+ , mPrivateKey(aKey.GetPrivateKey())
+ , mPublicKey(aKey.GetPublicKey())
+ , mKeyType(aKey.GetKeyType())
+ , mExtractable(aKey.Extractable())
+ , mAlg(aKey.Algorithm().JwkAlg())
+ {
+ aKey.GetUsages(mKeyUsages);
+ }
+
+
+protected:
+ nsString mFormat;
+ CryptoBuffer mSymKey;
+ ScopedSECKEYPrivateKey mPrivateKey;
+ ScopedSECKEYPublicKey mPublicKey;
+ CryptoKey::KeyType mKeyType;
+ bool mExtractable;
+ nsString mAlg;
+ nsTArray<nsString> mKeyUsages;
+ CryptoBuffer mResult;
+ JsonWebKey mJwk;
+
+private:
+ virtual void ReleaseNSSResources() override
+ {
+ mPrivateKey.dispose();
+ mPublicKey.dispose();
+ }
+
+ virtual nsresult DoCrypto() override
+ {
+ nsNSSShutDownPreventionLock locker;
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ if (mPublicKey && mPublicKey->keyType == dhKey) {
+ nsresult rv = CryptoKey::PublicDhKeyToRaw(mPublicKey, mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+
+ if (mPublicKey && mPublicKey->keyType == ecKey) {
+ nsresult rv = CryptoKey::PublicECKeyToRaw(mPublicKey, mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+
+ mResult = mSymKey;
+ if (mResult.Length() == 0) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return NS_OK;
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
+ if (!mPrivateKey) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ switch (mPrivateKey->keyType) {
+ case rsaKey: {
+ nsresult rv = CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ if (!mPublicKey) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return CryptoKey::PublicKeyToSpki(mPublicKey.get(), mResult, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ if (mKeyType == CryptoKey::SECRET) {
+ nsString k;
+ nsresult rv = mSymKey.ToJwkBase64(k);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ mJwk.mK.Construct(k);
+ mJwk.mKty = NS_LITERAL_STRING(JWK_TYPE_SYMMETRIC);
+ } else if (mKeyType == CryptoKey::PUBLIC) {
+ if (!mPublicKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = CryptoKey::PublicKeyToJwk(mPublicKey, mJwk, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ } else if (mKeyType == CryptoKey::PRIVATE) {
+ if (!mPrivateKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, mJwk, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ if (!mAlg.IsEmpty()) {
+ mJwk.mAlg.Construct(mAlg);
+ }
+
+ mJwk.mExt.Construct(mExtractable);
+
+ mJwk.mKey_ops.Construct();
+ if (!mJwk.mKey_ops.Value().AppendElements(mKeyUsages, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Returns mResult as an ArrayBufferView or JWK, as appropriate
+ virtual void Resolve() override
+ {
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mResultPromise->MaybeResolve(mJwk);
+ return;
+ }
+
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ }
+};
+
+class GenerateSymmetricKeyTask : public WebCryptoTask
+{
+public:
+ GenerateSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ // Create an empty key and set easy attributes
+ mKey = new CryptoKey(aGlobal);
+ mKey->SetExtractable(aExtractable);
+ mKey->SetType(CryptoKey::SECRET);
+
+ // Extract algorithm name
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Construct an appropriate KeyAlorithm
+ uint32_t allowedUsages = 0;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aAlgorithm, mLength);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ mKey->Algorithm().MakeAes(algName, mLength);
+
+ allowedUsages = CryptoKey::ENCRYPT | CryptoKey::DECRYPT |
+ CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (params.mLength.WasPassed()) {
+ mLength = params.mLength.Value();
+ } else {
+ mLength = MapHashAlgorithmNameToBlockSize(hashName);
+ }
+
+ if (mLength == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mKey->Algorithm().MakeHmac(mLength, hashName);
+ allowedUsages = CryptoKey::SIGN | CryptoKey::VERIFY;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Add key usages
+ mKey->ClearUsages();
+ for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKey->AddUsageIntersecting(aKeyUsages[i], allowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+ mMechanism = mKey->Algorithm().Mechanism();
+ // SetSymKey done in Resolve, after we've done the keygen
+ }
+
+private:
+ RefPtr<CryptoKey> mKey;
+ size_t mLength;
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mKeyData;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ ScopedPK11SymKey symKey(PK11_KeyGen(slot.get(), mMechanism, nullptr,
+ mLength, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mKeyData manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mKeyData, PK11_GetKeyData(symKey));
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ if (NS_SUCCEEDED(mKey->SetSymKey(mKeyData))) {
+ mResultPromise->MaybeResolve(mKey);
+ } else {
+ mResultPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ }
+ }
+
+ virtual void Cleanup() override
+ {
+ mKey = nullptr;
+ }
+};
+
+GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
+ nsIGlobalObject* aGlobal, JSContext* aCx, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ : mKeyPair(new CryptoKeyPair())
+{
+ mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!mArena) {
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
+ return;
+ }
+
+ // Create an empty key pair and set easy attributes
+ mKeyPair->mPrivateKey = new CryptoKey(aGlobal);
+ mKeyPair->mPublicKey = new CryptoKey(aGlobal);
+
+ // Extract algorithm name
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Construct an appropriate KeyAlorithm
+ uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ RootedDictionary<RsaHashedKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Pull relevant info
+ uint32_t modulusLength = params.mModulusLength;
+ CryptoBuffer publicExponent;
+ ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent);
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Create algorithm
+ if (!mKeyPair->mPublicKey.get()->Algorithm().MakeRsa(mAlgName,
+ modulusLength,
+ publicExponent,
+ hashName)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ if (!mKeyPair->mPrivateKey.get()->Algorithm().MakeRsa(mAlgName,
+ modulusLength,
+ publicExponent,
+ hashName)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN;
+
+ // Set up params struct
+ mRsaParams.keySizeInBits = modulusLength;
+ bool converted = publicExponent.GetBigIntValue(mRsaParams.pe);
+ if (!converted) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ RootedDictionary<EcKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Create algorithm.
+ mKeyPair->mPublicKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve);
+ mKeyPair->mPrivateKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve);
+ mMechanism = CKM_EC_KEY_PAIR_GEN;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ RootedDictionary<DhKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoBuffer prime;
+ ATTEMPT_BUFFER_INIT(prime, params.mPrime);
+
+ CryptoBuffer generator;
+ ATTEMPT_BUFFER_INIT(generator, params.mGenerator);
+
+ // Set up params.
+ if (!prime.ToSECItem(mArena, &mDhParams.prime) ||
+ !generator.ToSECItem(mArena, &mDhParams.base)) {
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
+ return;
+ }
+
+ // Create algorithm.
+ if (!mKeyPair->mPublicKey.get()->Algorithm().MakeDh(mAlgName,
+ prime,
+ generator)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ if (!mKeyPair->mPrivateKey.get()->Algorithm().MakeDh(mAlgName,
+ prime,
+ generator)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Set key usages.
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ privateAllowedUsages = CryptoKey::SIGN;
+ publicAllowedUsages = CryptoKey::VERIFY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
+ publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS;
+ publicAllowedUsages = 0;
+ } else {
+ MOZ_ASSERT(false); // This shouldn't happen.
+ }
+
+ mKeyPair->mPrivateKey.get()->SetExtractable(aExtractable);
+ mKeyPair->mPrivateKey.get()->SetType(CryptoKey::PRIVATE);
+
+ mKeyPair->mPublicKey.get()->SetExtractable(true);
+ mKeyPair->mPublicKey.get()->SetType(CryptoKey::PUBLIC);
+
+ mKeyPair->mPrivateKey.get()->ClearUsages();
+ mKeyPair->mPublicKey.get()->ClearUsages();
+ for (uint32_t i=0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKeyPair->mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i],
+ privateAllowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ mEarlyRv = mKeyPair->mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i],
+ publicAllowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ // If no usages ended up being allowed, DataError
+ if (!mKeyPair->mPublicKey.get()->HasAnyUsage() &&
+ !mKeyPair->mPrivateKey.get()->HasAnyUsage()) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+}
+
+void
+GenerateAsymmetricKeyTask::ReleaseNSSResources()
+{
+ mPublicKey.dispose();
+ mPrivateKey.dispose();
+}
+
+nsresult
+GenerateAsymmetricKeyTask::DoCrypto()
+{
+ MOZ_ASSERT(mKeyPair);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ void* param;
+ switch (mMechanism) {
+ case CKM_RSA_PKCS_KEY_PAIR_GEN:
+ param = &mRsaParams;
+ break;
+ case CKM_DH_PKCS_KEY_PAIR_GEN:
+ param = &mDhParams;
+ break;
+ case CKM_EC_KEY_PAIR_GEN: {
+ param = CreateECParamsForCurve(mNamedCurve, mArena);
+ if (!param) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ break;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ SECKEYPublicKey* pubKey = nullptr;
+ mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey,
+ PR_FALSE, PR_FALSE, nullptr);
+ mPublicKey = pubKey;
+ if (!mPrivateKey.get() || !mPublicKey.get()) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = mKeyPair->mPrivateKey.get()->SetPrivateKey(mPrivateKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = mKeyPair->mPublicKey.get()->SetPublicKey(mPublicKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ // PK11_GenerateKeyPair() does not set a CKA_EC_POINT attribute on the
+ // private key, we need this later when exporting to PKCS8 and JWK though.
+ if (mMechanism == CKM_EC_KEY_PAIR_GEN) {
+ rv = mKeyPair->mPrivateKey->AddPublicKeyData(mPublicKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ }
+
+ return NS_OK;
+}
+
+void
+GenerateAsymmetricKeyTask::Resolve()
+{
+ mResultPromise->MaybeResolve(*mKeyPair);
+}
+
+void
+GenerateAsymmetricKeyTask::Cleanup()
+{
+ mKeyPair = nullptr;
+}
+
+class DeriveHkdfBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveHkdfBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mSymKey(aKey.GetSymKey())
+ {
+ Init(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ DeriveHkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mSymKey(aKey.GetSymKey())
+ {
+ size_t length;
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
+
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey, length);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
+ uint32_t aLength)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_HKDF);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HKDF);
+
+ // Check that we have a key.
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ RootedDictionary<HkdfParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // length must be greater than zero.
+ if (aLength == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Extract the hash algorithm.
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check the given hash algorithm.
+ switch (MapAlgorithmNameToMechanism(hashName)) {
+ case CKM_SHA_1: mMechanism = CKM_NSS_HKDF_SHA1; break;
+ case CKM_SHA256: mMechanism = CKM_NSS_HKDF_SHA256; break;
+ case CKM_SHA384: mMechanism = CKM_NSS_HKDF_SHA384; break;
+ case CKM_SHA512: mMechanism = CKM_NSS_HKDF_SHA512; break;
+ default:
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
+ ATTEMPT_BUFFER_INIT(mInfo, params.mInfo)
+ mLengthInBytes = ceil((double)aLength / 8);
+ mLengthInBits = aLength;
+ }
+
+private:
+ size_t mLengthInBits;
+ size_t mLengthInBytes;
+ CryptoBuffer mSalt;
+ CryptoBuffer mInfo;
+ CryptoBuffer mSymKey;
+ CK_MECHANISM_TYPE mMechanism;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPK11SymKey baseKey(PK11_ImportSymKey(slot, mMechanism,
+ PK11_OriginUnwrap, CKA_WRAP,
+ &keyItem, nullptr));
+ if (!baseKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ SECItem salt = { siBuffer, nullptr, 0 };
+ SECItem info = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &info, mInfo);
+
+ CK_NSS_HKDFParams hkdfParams = { true, salt.data, salt.len,
+ true, info.data, info.len };
+ SECItem params = { siBuffer, (unsigned char*)&hkdfParams,
+ sizeof(hkdfParams) };
+
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_Derive(baseKey, mMechanism, &params,
+ CKM_SHA512_HMAC, CKA_SIGN,
+ mLengthInBytes));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLengthInBytes > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLengthInBytes, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // If the number of bits to derive is not a multiple of 8 we need to
+ // zero out the remaining bits that were derived but not requested.
+ if (mLengthInBits % 8) {
+ mResult[mResult.Length() - 1] &= 0xff << (mLengthInBits % 8);
+ }
+
+ return NS_OK;
+ }
+};
+
+class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DerivePbkdfBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mSymKey(aKey.GetSymKey())
+ {
+ Init(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ DerivePbkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mSymKey(aKey.GetSymKey())
+ {
+ size_t length;
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
+
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey, length);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
+ uint32_t aLength)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_PBKDF2);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_PBKDF2);
+
+ // Check that we got a symmetric key
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ RootedDictionary<Pbkdf2Params> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // length must be a multiple of 8 bigger than zero.
+ if (aLength == 0 || aLength % 8) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Extract the hash algorithm.
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check the given hash algorithm.
+ switch (MapAlgorithmNameToMechanism(hashName)) {
+ case CKM_SHA_1: mHashOidTag = SEC_OID_HMAC_SHA1; break;
+ case CKM_SHA256: mHashOidTag = SEC_OID_HMAC_SHA256; break;
+ case CKM_SHA384: mHashOidTag = SEC_OID_HMAC_SHA384; break;
+ case CKM_SHA512: mHashOidTag = SEC_OID_HMAC_SHA512; break;
+ default:
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
+ mLength = aLength >> 3; // bits to bytes
+ mIterations = params.mIterations;
+ }
+
+private:
+ size_t mLength;
+ size_t mIterations;
+ CryptoBuffer mSalt;
+ CryptoBuffer mSymKey;
+ SECOidTag mHashOidTag;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem salt = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
+ // PK11_CreatePBEV2AlgorithmID will "helpfully" create PBKDF2 parameters
+ // with a random salt if given a SECItem* that is either null or has a null
+ // data pointer. This obviously isn't what we want, so we have to fake it
+ // out by passing in a SECItem* with a non-null data pointer but with zero
+ // length.
+ if (!salt.data) {
+ MOZ_ASSERT(salt.len == 0);
+ salt.data = reinterpret_cast<unsigned char*>(PORT_ArenaAlloc(arena, 1));
+ if (!salt.data) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ }
+
+ // Always pass in cipherAlg=SEC_OID_HMAC_SHA1 (i.e. PBMAC1) as this
+ // parameter is unused for key generation. It is currently only used
+ // for PBKDF2 authentication or key (un)wrapping when specifying an
+ // encryption algorithm (PBES2).
+ ScopedSECAlgorithmID alg_id(PK11_CreatePBEV2AlgorithmID(
+ SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA1, mHashOidTag,
+ mLength, mIterations, &salt));
+
+ if (!alg_id.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+
+ ScopedPK11SymKey symKey(PK11_PBEKeyGen(slot, alg_id, &keyItem, false, nullptr));
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+ return NS_OK;
+ }
+};
+
+template<class DeriveBitsTask>
+class DeriveKeyTask : public DeriveBitsTask
+{
+public:
+ DeriveKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aBaseKey,
+ const ObjectOrString& aDerivedKeyType, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ : DeriveBitsTask(aCx, aAlgorithm, aBaseKey, aDerivedKeyType)
+ , mResolved(false)
+ {
+ if (NS_FAILED(this->mEarlyRv)) {
+ return;
+ }
+
+ NS_NAMED_LITERAL_STRING(format, WEBCRYPTO_KEY_FORMAT_RAW);
+ mTask = new ImportSymmetricKeyTask(aGlobal, aCx, format, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+protected:
+ RefPtr<ImportSymmetricKeyTask> mTask;
+ bool mResolved;
+
+private:
+ virtual void Resolve() override {
+ mTask->SetRawKeyData(this->mResult);
+ mTask->DispatchWithPromise(this->mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+class DeriveEcdhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveEcdhBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mLength(aLength),
+ mPrivKey(aKey.GetPrivateKey())
+ {
+ Init(aCx, aAlgorithm, aKey);
+ }
+
+ DeriveEcdhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mPrivKey(aKey.GetPrivateKey())
+ {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDH);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDH);
+
+ // Check that we have a private key.
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ // Length must be a multiple of 8 bigger than zero.
+ if (mLength == 0 || mLength % 8) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+
+ // Retrieve the peer's public key.
+ RootedDictionary<EcdhKeyDeriveParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoKey* publicKey = params.mPublic;
+ mPubKey = publicKey->GetPublicKey();
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ CHECK_KEY_ALGORITHM(publicKey->Algorithm(), WEBCRYPTO_ALG_ECDH);
+
+ // Both keys must use the same named curve.
+ nsString curve1 = aKey.Algorithm().mEc.mNamedCurve;
+ nsString curve2 = publicKey->Algorithm().mEc.mNamedCurve;
+
+ if (!curve1.Equals(curve2)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+private:
+ size_t mLength;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+
+ virtual nsresult DoCrypto() override
+ {
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
+ mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_ECDH1_DERIVE,
+ CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLength > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class DeriveDhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveDhBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mLength(aLength),
+ mPrivKey(aKey.GetPrivateKey())
+ {
+ Init(aCx, aAlgorithm, aKey);
+ }
+
+ DeriveDhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mPrivKey(aKey.GetPrivateKey())
+ {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_DH);
+
+ // Check that we have a private key.
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+
+ // Retrieve the peer's public key.
+ RootedDictionary<DhKeyDeriveParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoKey* publicKey = params.mPublic;
+ mPubKey = publicKey->GetPublicKey();
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ KeyAlgorithmProxy alg1 = publicKey->Algorithm();
+ CHECK_KEY_ALGORITHM(alg1, WEBCRYPTO_ALG_DH);
+
+ // Both keys must use the same prime and generator.
+ KeyAlgorithmProxy alg2 = aKey.Algorithm();
+ if (alg1.mDh.mPrime != alg2.mDh.mPrime ||
+ alg1.mDh.mGenerator != alg2.mDh.mGenerator) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+private:
+ size_t mLength;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+
+ virtual nsresult DoCrypto() override
+ {
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
+ mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_DH_PKCS_DERIVE,
+ CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLength > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+template<class KeyEncryptTask>
+class WrapKeyTask : public ExportKeyTask
+{
+public:
+ WrapKeyTask(JSContext* aCx,
+ const nsAString& aFormat,
+ CryptoKey& aKey,
+ CryptoKey& aWrappingKey,
+ const ObjectOrString& aWrapAlgorithm)
+ : ExportKeyTask(aFormat, aKey)
+ , mResolved(false)
+ {
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ mTask = new KeyEncryptTask(aCx, aWrapAlgorithm, aWrappingKey, true);
+ }
+
+private:
+ RefPtr<KeyEncryptTask> mTask;
+ bool mResolved;
+
+ virtual nsresult AfterCrypto() override {
+ // If wrapping JWK, stringify the JSON
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ nsAutoString json;
+ if (!mJwk.ToJSON(json)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8(json);
+ if (!mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length())) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ mTask->SetData(mResult);
+ mTask->DispatchWithPromise(mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+template<class KeyEncryptTask>
+class UnwrapKeyTask : public KeyEncryptTask
+{
+public:
+ UnwrapKeyTask(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aWrappedKey,
+ CryptoKey& aUnwrappingKey,
+ const ObjectOrString& aUnwrapAlgorithm,
+ ImportKeyTask* aTask)
+ : KeyEncryptTask(aCx, aUnwrapAlgorithm, aUnwrappingKey, aWrappedKey, false)
+ , mTask(aTask)
+ , mResolved(false)
+ {}
+
+private:
+ RefPtr<ImportKeyTask> mTask;
+ bool mResolved;
+
+ virtual void Resolve() override
+ {
+ mTask->SetKeyDataMaybeParseJWK(KeyEncryptTask::mResult);
+ mTask->DispatchWithPromise(KeyEncryptTask::mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+// Task creation methods for WebCryptoTask
+
+// Note: We do not perform algorithm normalization as a monolithic process,
+// as described in the spec. Instead:
+// * Each method handles its slice of the supportedAlgorithms structure
+// * Task constructors take care of:
+// * Coercing the algorithm to the proper concrete type
+// * Cloning subordinate data items
+// * Cloning input data as needed
+//
+// Thus, support for different algorithms is determined by the if-statements
+// below, rather than a data structure.
+//
+// This results in algorithm normalization coming after some other checks,
+// and thus slightly more steps being done synchronously than the spec calls
+// for. But none of these steps is especially time-consuming.
+
+WebCryptoTask*
+WebCryptoTask::CreateEncryptDecryptTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData,
+ bool aEncrypt)
+{
+ TelemetryMethod method = (aEncrypt)? TM_ENCRYPT : TM_DECRYPT;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_ENC, aKey.Extractable());
+
+ // Ensure key is usable for this operation
+ if ((aEncrypt && !aKey.HasUsage(CryptoKey::ENCRYPT)) ||
+ (!aEncrypt && !aKey.HasUsage(CryptoKey::DECRYPT))) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new AesTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new RsaOaepTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateSignVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+{
+ TelemetryMethod method = (aSign)? TM_SIGN : TM_VERIFY;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_SIG, aKey.Extractable());
+
+ // Ensure key is usable for this operation
+ if ((aSign && !aKey.HasUsage(CryptoKey::SIGN)) ||
+ (!aSign && !aKey.HasUsage(CryptoKey::VERIFY))) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature,
+ aData, aSign);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDigestTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DIGEST);
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return new DigestTask(aCx, aAlgorithm, aData);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateImportKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const nsAString& aFormat,
+ JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ // SPEC-BUG: PBKDF2 is not supposed to be supported for this operation.
+ // However, the spec should be updated to allow it.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ return new ImportSymmetricKeyTask(aGlobal, aCx, aFormat, aKeyData,
+ aAlgorithm, aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ return new ImportRsaKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ return new ImportEcKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ return new ImportDhKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
+ CryptoKey& aKey)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_EXPORTKEY);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Verify that the key is extractable
+ if (!aKey.Extractable()) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that the algorithm supports export
+ // SPEC-BUG: PBKDF2 is not supposed to be supported for this operation.
+ // However, the spec should be updated to allow it.
+ nsString algName = aKey.Algorithm().mName;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ return new ExportKeyTask(aFormat, aKey);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateGenerateKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_GENERATEKEY);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_GENERATE, aExtractable);
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ // SPEC-BUG: Spec says that this should be InvalidAccessError, but that
+ // is inconsistent with other analogous points in the spec
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
+ return new GenerateSymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+ aKeyUsages);
+ } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
+ return new GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+ aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDeriveKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aBaseKey,
+ const ObjectOrString& aDerivedKeyType,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEKEY);
+
+ // Ensure baseKey is usable for this operation
+ if (!aBaseKey.HasUsage(CryptoKey::DERIVEKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+ return new DeriveKeyTask<DeriveHkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
+ return new DeriveKeyTask<DerivePbkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
+ return new DeriveKeyTask<DeriveEcdhBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ uint32_t aLength)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEBITS);
+
+ // Ensure baseKey is usable for this operation
+ if (!aKey.HasUsage(CryptoKey::DERIVEBITS)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
+ return new DerivePbkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
+ return new DeriveEcdhBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
+ return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+ return new DeriveHkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateWrapKeyTask(JSContext* aCx,
+ const nsAString& aFormat,
+ CryptoKey& aKey,
+ CryptoKey& aWrappingKey,
+ const ObjectOrString& aWrapAlgorithm)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_WRAPKEY);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Ensure wrappingKey is usable for this operation
+ if (!aWrappingKey.HasUsage(CryptoKey::WRAPKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Ensure key is extractable
+ if (!aKey.Extractable()) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString wrapAlgName;
+ nsresult rv = GetAlgorithmName(aCx, aWrapAlgorithm, wrapAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new WrapKeyTask<AesTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ return new WrapKeyTask<AesKwTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new WrapKeyTask<RsaOaepTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateUnwrapKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const nsAString& aFormat,
+ const ArrayBufferViewOrArrayBuffer& aWrappedKey,
+ CryptoKey& aUnwrappingKey,
+ const ObjectOrString& aUnwrapAlgorithm,
+ const ObjectOrString& aUnwrappedKeyAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_UNWRAPKEY);
+
+ // Ensure key is usable for this operation
+ if (!aUnwrappingKey.HasUsage(CryptoKey::UNWRAPKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString keyAlgName;
+ nsresult rv = GetAlgorithmName(aCx, aUnwrappedKeyAlgorithm, keyAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ CryptoOperationData dummy;
+ RefPtr<ImportKeyTask> importTask;
+ if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
+ importTask = new ImportSymmetricKeyTask(aGlobal, aCx, aFormat,
+ aUnwrappedKeyAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
+ importTask = new ImportRsaKeyTask(aGlobal, aCx, aFormat,
+ aUnwrappedKeyAlgorithm,
+ aExtractable, aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+
+ nsString unwrapAlgName;
+ rv = GetAlgorithmName(aCx, aUnwrapAlgorithm, unwrapAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+ if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new UnwrapKeyTask<AesTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ return new UnwrapKeyTask<AesKwTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new UnwrapKeyTask<RsaOaepTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask::WebCryptoTask()
+ : mEarlyRv(NS_OK)
+ , mEarlyComplete(false)
+ , mOriginalThread(nullptr)
+ , mReleasedNSSResources(false)
+ , mRv(NS_ERROR_NOT_INITIALIZED)
+{
+}
+
+WebCryptoTask::~WebCryptoTask()
+{
+ MOZ_ASSERT(mReleasedNSSResources);
+
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ shutdown(ShutdownCalledFrom::Object);
+ }
+
+ if (mWorkerHolder) {
+ NS_ProxyRelease(mOriginalThread, mWorkerHolder.forget());
+ }
+}
+
+} // namespace dom
+} // namespace mozilla