mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 08:45:46 +00:00
9c614370ec
Bug 1034856 added support for DH algorithms to WebCrypto, however the final specification did not choose to include them, making Firefox the only browser with support. Bug 1539578 added telemetry to show usage, and it is extremely low (not appearing on the graphs), which could be expected as Firefox is the only supporting browser. Since DH is an ongoing maintenance burden -- and overall cryptanalysis of DH is progressing -- let's remove it. Notice to unship went to dev-platform on 29 March 2019 with no objections. [0] [0] https://groups.google.com/d/msg/mozilla.dev.platform/Ut3-eQmUdWg/O9w1et1aBgAJ Differential Revision: https://phabricator.services.mozilla.com/D50865 --HG-- extra : moz-landing-system : lando
3227 lines
102 KiB
C++
3227 lines
102 KiB
C++
/* -*- 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 "nsNSSComponent.h"
|
|
#include "nsProxyRelease.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/Utf8.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/WorkerRef.h"
|
|
#include "mozilla/dom/WorkerPrivate.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 {
|
|
|
|
// 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,
|
|
TA_DH = 24,
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
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_NOT_SUPPORTED_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 dispatch if we're already done. Otherwise launch a CryptoTask
|
|
if (mEarlyComplete) {
|
|
CallCallback(mEarlyRv);
|
|
return;
|
|
}
|
|
|
|
// Store calling thread
|
|
mOriginalEventTarget = GetCurrentThreadSerialEventTarget();
|
|
|
|
// 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()) {
|
|
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
|
MOZ_ASSERT(workerPrivate);
|
|
|
|
RefPtr<StrongWorkerRef> workerRef =
|
|
StrongWorkerRef::Create(workerPrivate, "WebCryptoTask");
|
|
if (NS_WARN_IF(!workerRef)) {
|
|
mEarlyRv = NS_BINDING_ABORTED;
|
|
} else {
|
|
mWorkerRef = new ThreadSafeWorkerRef(workerRef);
|
|
}
|
|
}
|
|
MAYBE_EARLY_FAIL(mEarlyRv);
|
|
|
|
// dispatch to thread pool
|
|
|
|
if (!EnsureNSSInitializedChromeOrContent()) {
|
|
mEarlyRv = NS_ERROR_FAILURE;
|
|
}
|
|
MAYBE_EARLY_FAIL(mEarlyRv);
|
|
|
|
mEarlyRv = NS_DispatchBackgroundTask(this);
|
|
MAYBE_EARLY_FAIL(mEarlyRv)
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebCryptoTask::Run() {
|
|
// Run heavy crypto operations on the thread pool, off the original thread.
|
|
if (!IsOnOriginalThread()) {
|
|
mRv = CalculateResult();
|
|
|
|
// Back to the original thread, i.e. continue below.
|
|
mOriginalEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
return NS_OK;
|
|
}
|
|
|
|
// We're now back on the calling thread.
|
|
CallCallback(mRv);
|
|
|
|
// Stop holding the worker thread alive now that the async work has
|
|
// been completed.
|
|
mWorkerRef = 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;
|
|
mWorkerRef = nullptr;
|
|
Cleanup();
|
|
}
|
|
|
|
nsresult WebCryptoTask::CalculateResult() {
|
|
MOZ_ASSERT(!IsOnOriginalThread());
|
|
|
|
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)
|
|
: mMechanism(CKM_INVALID_MECHANISM),
|
|
mSymKey(aKey.GetSymKey()),
|
|
mTagLength(0),
|
|
mCounterLength(0),
|
|
mEncrypt(aEncrypt) {
|
|
Init(aCx, aAlgorithm, aKey, aEncrypt);
|
|
}
|
|
|
|
AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
|
|
const CryptoOperationData& aData, bool aEncrypt)
|
|
: mMechanism(CKM_INVALID_MECHANISM),
|
|
mSymKey(aKey.GetSymKey()),
|
|
mTagLength(0),
|
|
mCounterLength(0),
|
|
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;
|
|
RootedDictionary<AesCbcParams> params(aCx);
|
|
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;
|
|
RootedDictionary<AesCtrParams> params(aCx);
|
|
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;
|
|
RootedDictionary<AesGcmParams> params(aCx);
|
|
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;
|
|
}
|
|
|
|
UniquePLArenaPool 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.get(), ¶m, 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.get(), &keyItem, mSymKey);
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
MOZ_ASSERT(slot.get());
|
|
UniquePK11SymKey symKey(PK11_ImportSymKey(slot.get(), mMechanism,
|
|
PK11_OriginUnwrap, CKA_ENCRYPT,
|
|
&keyItem, nullptr));
|
|
if (!symKey) {
|
|
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
|
}
|
|
|
|
// Check whether the integer addition would overflow.
|
|
if (std::numeric_limits<CryptoBuffer::size_type>::max() - 16 <
|
|
mData.Length()) {
|
|
return NS_ERROR_DOM_DATA_ERR;
|
|
}
|
|
|
|
// Initialize the output buffer (enough space for padding / a full tag)
|
|
if (!mResult.SetLength(mData.Length() + 16, fallible)) {
|
|
return NS_ERROR_DOM_UNKNOWN_ERR;
|
|
}
|
|
|
|
uint32_t outLen = 0;
|
|
|
|
// Perform the encryption/decryption
|
|
if (mEncrypt) {
|
|
rv = MapSECStatus(PK11_Encrypt(
|
|
symKey.get(), mMechanism, ¶m, mResult.Elements(), &outLen,
|
|
mResult.Length(), mData.Elements(), mData.Length()));
|
|
} else {
|
|
rv = MapSECStatus(PK11_Decrypt(
|
|
symKey.get(), mMechanism, ¶m, 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;
|
|
}
|
|
|
|
UniquePLArenaPool 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.get(), &keyItem, mSymKey);
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
MOZ_ASSERT(slot.get());
|
|
UniquePK11SymKey symKey(PK11_ImportSymKey(slot.get(), 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.get(), &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
|
|
UniquePK11SymKey keyToWrap(
|
|
PK11_ImportSymKey(slot.get(), 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;
|
|
UniquePK11SymKey unwrappedKey(
|
|
PK11_UnwrapSymKey(symKey.get(), 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.get()));
|
|
if (NS_FAILED(rv)) {
|
|
return NS_ERROR_DOM_UNKNOWN_ERR;
|
|
}
|
|
ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(unwrappedKey.get()));
|
|
}
|
|
|
|
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.get());
|
|
} else {
|
|
if (!mPrivKey) {
|
|
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
|
return;
|
|
}
|
|
mStrength = PK11_GetPrivateModulusLen(mPrivKey.get());
|
|
}
|
|
|
|
// 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;
|
|
UniqueSECKEYPrivateKey mPrivKey;
|
|
UniqueSECKEYPublicKey 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, ¶m, mResult.Elements(), &outLen,
|
|
mResult.Length(), mData.Elements(), mData.Length(), nullptr));
|
|
} else {
|
|
rv = MapSECStatus(PK11_PrivDecrypt(
|
|
mPrivKey.get(), CKM_RSA_PKCS_OAEP, ¶m, 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;
|
|
}
|
|
|
|
UniquePLArenaPool 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.get(), &keyItem, mSymKey);
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
MOZ_ASSERT(slot.get());
|
|
UniquePK11SymKey symKey(PK11_ImportSymKey(slot.get(), 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(), ¶m));
|
|
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;
|
|
UniqueSECKEYPrivateKey mPrivKey;
|
|
UniqueSECKEYPublicKey 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;
|
|
UniqueSECItem 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) {
|
|
UniqueSGNDigestInfo di(
|
|
SGN_CreateDigestInfo(mOidTag, hash->data, hash->len));
|
|
if (!di) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// Reuse |hash|.
|
|
SECITEM_FreeItem(hash.get(), false);
|
|
if (!SEC_ASN1EncodeItem(nullptr, hash.get(), di.get(),
|
|
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.get()) : 0;
|
|
UniqueSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
|
|
if (!sig) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
if (mSign) {
|
|
// Sign the hash.
|
|
rv = PK11_SignWithMechanism(mPrivKey.get(), mech, params, sig.get(),
|
|
hash.get());
|
|
NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
|
|
ATTEMPT_BUFFER_ASSIGN(mSignature, sig.get());
|
|
} else {
|
|
// Copy the given signature to the SECItem.
|
|
if (!mSignature.ToSECItem(nullptr, sig.get())) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// Verify the signature.
|
|
rv = PK11_VerifyWithMechanism(mPubKey.get(), mech, params, sig.get(),
|
|
hash.get(), 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
|
|
RootedSpiderMonkeyInterface<ArrayBuffer> ab(aCx);
|
|
if (ab.Init(aKeyData)) {
|
|
if (!mKeyData.Assign(ab)) {
|
|
mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Try ArrayBufferView
|
|
RootedSpiderMonkeyInterface<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 &&
|
|
!mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
|
|
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)
|
|
: mModulusLength(0) {
|
|
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)
|
|
: mModulusLength(0) {
|
|
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 {
|
|
// Import the key data itself
|
|
UniqueSECKEYPublicKey pubKey;
|
|
UniqueSECKEYPrivateKey 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);
|
|
} else {
|
|
pubKey = CryptoKey::PublicKeyFromJwk(mJwk);
|
|
}
|
|
|
|
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);
|
|
} else {
|
|
privKey = CryptoKey::PrivateKeyFromJwk(mJwk);
|
|
}
|
|
|
|
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 = UniqueSECKEYPublicKey(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
|
|
UniqueSECKEYPublicKey pubKey;
|
|
UniqueSECKEYPrivateKey privKey;
|
|
|
|
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
|
|
mJwk.mD.WasPassed()) {
|
|
// Private key import
|
|
privKey = CryptoKey::PrivateKeyFromJwk(mJwk);
|
|
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);
|
|
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
|
|
pubKey = CryptoKey::PublicKeyFromSpki(mKeyData);
|
|
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
|
|
pubKey = CryptoKey::PublicKeyFromJwk(mJwk);
|
|
} 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 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;
|
|
UniqueSECKEYPrivateKey mPrivateKey;
|
|
UniqueSECKEYPublicKey mPublicKey;
|
|
CryptoKey::KeyType mKeyType;
|
|
bool mExtractable;
|
|
nsString mAlg;
|
|
nsTArray<nsString> mKeyUsages;
|
|
CryptoBuffer mResult;
|
|
JsonWebKey mJwk;
|
|
|
|
private:
|
|
virtual nsresult DoCrypto() override {
|
|
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
|
|
if (mPublicKey && mPublicKey->keyType == dhKey) {
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
|
|
if (mPublicKey && mPublicKey->keyType == ecKey) {
|
|
nsresult rv = CryptoKey::PublicECKeyToRaw(mPublicKey.get(), mResult);
|
|
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);
|
|
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);
|
|
} 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.get(), mJwk);
|
|
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.get(), mJwk);
|
|
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)) {
|
|
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)) {
|
|
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 {
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
MOZ_ASSERT(slot.get());
|
|
|
|
UniquePK11SymKey symKey(
|
|
PK11_KeyGen(slot.get(), mMechanism, nullptr, mLength, nullptr));
|
|
if (!symKey) {
|
|
return NS_ERROR_DOM_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey.get()));
|
|
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.get()));
|
|
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()),
|
|
mMechanism(CKM_INVALID_MECHANISM),
|
|
mRsaParams(),
|
|
mDhParams() {
|
|
mArena = UniquePLArenaPool(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)) {
|
|
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)) {
|
|
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 {
|
|
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)) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
nsresult GenerateAsymmetricKeyTask::DoCrypto() {
|
|
MOZ_ASSERT(mKeyPair);
|
|
|
|
UniquePK11SlotInfo 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.get());
|
|
if (!param) {
|
|
return NS_ERROR_DOM_UNKNOWN_ERR;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
|
|
SECKEYPublicKey* pubKey = nullptr;
|
|
mPrivateKey = UniqueSECKEYPrivateKey(PK11_GenerateKeyPair(
|
|
slot.get(), mMechanism, param, &pubKey, PR_FALSE, PR_FALSE, nullptr));
|
|
mPublicKey = UniqueSECKEYPublicKey(pubKey);
|
|
pubKey = nullptr;
|
|
if (!mPrivateKey.get() || !mPublicKey.get()) {
|
|
return NS_ERROR_DOM_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsresult rv = mKeyPair->mPrivateKey.get()->SetPrivateKey(mPrivateKey.get());
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
|
|
rv = mKeyPair->mPublicKey.get()->SetPublicKey(mPublicKey.get());
|
|
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.get());
|
|
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()), mMechanism(CKM_INVALID_MECHANISM) {
|
|
Init(aCx, aAlgorithm, aKey, aLength);
|
|
}
|
|
|
|
DeriveHkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
|
|
CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
|
|
: mLengthInBits(0),
|
|
mLengthInBytes(0),
|
|
mSymKey(aKey.GetSymKey()),
|
|
mMechanism(CKM_INVALID_MECHANISM) {
|
|
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 {
|
|
UniquePLArenaPool 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.get(), &keyItem, mSymKey);
|
|
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot.get()) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
UniquePK11SymKey baseKey(PK11_ImportSymKey(slot.get(), 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.get(), &salt, mSalt);
|
|
ATTEMPT_BUFFER_TO_SECITEM(arena.get(), &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.
|
|
UniquePK11SymKey symKey(PK11_Derive(baseKey.get(), mMechanism, ¶ms,
|
|
CKM_SHA512_HMAC, CKA_SIGN,
|
|
mLengthInBytes));
|
|
|
|
if (!symKey.get()) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey.get()));
|
|
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.get()));
|
|
|
|
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()), mHashOidTag(SEC_OID_UNKNOWN) {
|
|
Init(aCx, aAlgorithm, aKey, aLength);
|
|
}
|
|
|
|
DerivePbkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
|
|
CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
|
|
: mLength(0),
|
|
mIterations(0),
|
|
mSymKey(aKey.GetSymKey()),
|
|
mHashOidTag(SEC_OID_UNKNOWN) {
|
|
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);
|
|
|
|
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_OPERATION_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 {
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
SECItem salt = {siBuffer, nullptr, 0};
|
|
ATTEMPT_BUFFER_TO_SECITEM(arena.get(), &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.get(), 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).
|
|
UniqueSECAlgorithmID algID(
|
|
PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA1,
|
|
mHashOidTag, mLength, mIterations, &salt));
|
|
|
|
if (!algID) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot.get()) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
SECItem keyItem = {siBuffer, nullptr, 0};
|
|
ATTEMPT_BUFFER_TO_SECITEM(arena.get(), &keyItem, mSymKey);
|
|
|
|
UniquePK11SymKey symKey(
|
|
PK11_PBEKeyGen(slot.get(), algID.get(), &keyItem, false, nullptr));
|
|
if (!symKey.get()) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey.get()));
|
|
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.get()));
|
|
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) {
|
|
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;
|
|
|
|
private:
|
|
virtual void Resolve() override {
|
|
mTask->SetRawKeyData(this->mResult);
|
|
mTask->DispatchWithPromise(this->mResultPromise);
|
|
}
|
|
|
|
virtual void Cleanup() override { 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;
|
|
UniqueSECKEYPrivateKey mPrivKey;
|
|
UniqueSECKEYPublicKey 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.
|
|
UniquePK11SymKey symKey(
|
|
PK11_PubDeriveWithKDF(mPrivKey.get(), mPubKey.get(), 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.get()));
|
|
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.get()));
|
|
|
|
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) {
|
|
if (NS_FAILED(mEarlyRv)) {
|
|
return;
|
|
}
|
|
|
|
mTask = new KeyEncryptTask(aCx, aWrapAlgorithm, aWrappingKey, true);
|
|
}
|
|
|
|
private:
|
|
RefPtr<KeyEncryptTask> mTask;
|
|
|
|
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);
|
|
}
|
|
|
|
virtual void Cleanup() override { 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) {}
|
|
|
|
private:
|
|
RefPtr<ImportKeyTask> mTask;
|
|
|
|
virtual void Resolve() override {
|
|
mTask->SetKeyDataMaybeParseJWK(KeyEncryptTask::mResult);
|
|
mTask->DispatchWithPromise(KeyEncryptTask::mResultPromise);
|
|
}
|
|
|
|
virtual void Cleanup() override { 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 {
|
|
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)) {
|
|
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)) {
|
|
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_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()
|
|
: CancelableRunnable("WebCryptoTask"),
|
|
mEarlyRv(NS_OK),
|
|
mEarlyComplete(false),
|
|
mOriginalEventTarget(nullptr),
|
|
mRv(NS_ERROR_NOT_INITIALIZED) {}
|
|
|
|
WebCryptoTask::~WebCryptoTask() = default;
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|