Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  WebCryptoTask.cpp   Sprache: 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/glean/DomCryptoMetrics.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"
#include "mozilla/dom/RootedDictionary.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.
MOZ_GLOBINIT 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::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,
  TA_ED25519 = 25,
  TA_X25519 = 26,
};

// 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::Rooted<JS::Value> value(aCx,
                                JS::ObjectValue(*aAlgorithm.GetAsObject()));
    Algorithm alg;

    if (!alg.Init(aCx, value)) {
      return NS_ERROR_DOM_TYPE_MISMATCH_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::Rooted<JS::Value> value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
  if (!aTarget.Init(aCx, value)) {
    return NS_ERROR_DOM_TYPE_MISMATCH_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 GetKeyLengthForAlgorithmIfSpecified(
    JSContext* aCx, const ObjectOrString& aAlgorithm, Maybe<size_t>& aLength) {
  // 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_OPERATION_ERR;
    }

    aLength.emplace(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.emplace(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 blockSize = MapHashAlgorithmNameToBlockSize(hashName);
    if (blockSize == 0) {
      return NS_ERROR_DOM_SYNTAX_ERR;
    }

    aLength.emplace(blockSize);
    return NS_OK;
  }

  return NS_OK;
}

inline nsresult GetKeyLengthForAlgorithm(JSContext* aCx,
                                         const ObjectOrString& aAlgorithm,
                                         size_t& aLength) {
  Maybe<size_t> length;
  nsresult rv = GetKeyLengthForAlgorithmIfSpecified(aCx, aAlgorithm, length);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (length.isNothing()) {
    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
  }
  aLength = *length;
  return NS_OK;
}

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;
    case SEC_OID_ED25519_PUBLIC_KEY:
      aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_ED25519);
      break;
    case SEC_OID_X25519:
      aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_CURVE25519);
      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 = GetCurrentSerialEventTarget();

  // 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());
  glean::webcrypto::resolved.EnumGet(glean::webcrypto::ResolvedLabel::eFalse)
      .Add();

  if (aRv == NS_ERROR_DOM_TYPE_MISMATCH_ERR) {
    mResultPromise->MaybeRejectWithTypeError(
        "The operation could not be performed.");
  } else {
    // 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();
  glean::webcrypto::resolved.EnumGet(glean::webcrypto::ResolvedLabel::eTrue)
      .Add();

  // 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),
        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),
        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;
    }

    if (!mSymKey.Assign(aKey.GetSymKey())) {
      mEarlyRv = NS_ERROR_OUT_OF_MEMORY;
      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_OPERATION_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_OPERATION_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_OPERATION_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_OPERATION_ERR;
          return;
        }
      }
    } else {
      mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
      return;
    }
    glean::webcrypto::alg.AccumulateSingleSample(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.ulIvBits = gcmParams.ulIvLen * 8;
        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), 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), 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;
    }

    if (!mSymKey.Assign(aKey.GetSymKey())) {
      mEarlyRv = NS_ERROR_OUT_OF_MEMORY;
      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;
    }

    glean::webcrypto::alg.AccumulateSingleSample(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) {
    glean::webcrypto::alg.AccumulateSingleSample(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()), mSign(aSign) {
    CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HMAC);

    ATTEMPT_BUFFER_INIT(mData, aData);
    if (!aSign) {
      ATTEMPT_BUFFER_INIT(mSignature, aSignature);
    }

    if (!mSymKey.Assign(aKey.GetSymKey())) {
      mEarlyRv = NS_ERROR_OUT_OF_MEMORY;
      return;
    }

    // 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;
    }
    glean::webcrypto::alg.AccumulateSingleSample(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;
      glean::webcrypto::alg.AccumulateSingleSample(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;
      glean::webcrypto::alg.AccumulateSingleSample(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;
      glean::webcrypto::alg.AccumulateSingleSample(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_NOT_SUPPORTED_ERR;
        return;
      }
    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ED25519)) {
      mAlgorithm = Algorithm::ED25519;
      glean::webcrypto::alg.AccumulateSingleSample(TA_ED25519);
      CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ED25519);
    } 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 && AlgorithmRequiresHashing(mAlgorithm)) {
      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,
    ED25519,
    UNKNOWN
  };
  Algorithm mAlgorithm;

  bool AlgorithmRequiresHashing(Algorithm aAlgorithm) {
    MOZ_ASSERT(aAlgorithm != Algorithm::UNKNOWN);
    /* Currently, only ED25519 does not require hashing.*/
    switch (aAlgorithm) {
      case Algorithm::ED25519:
        return false;
      case Algorithm::ECDSA:
      case Algorithm::RSA_PKCS1:
      case Algorithm::RSA_PSS:
      // Impossible
      case Algorithm::UNKNOWN:
        return true;
    }
    /*Also impossible, as all the algorithm options should be managed in the
     * switch. */

    return true;
  }

  virtual nsresult DoCrypto() override {
    SECStatus rv;
    UniqueSECItem hash;

    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;
    }

    if (AlgorithmRequiresHashing(mAlgorithm)) {
      // Compute digest over given data.
      hash.reset(::SECITEM_AllocItem(nullptr, nullptr,
                                     HASH_ResultLenByOidTag(mOidTag)));

      if (!hash || !hash->data || hash->len > PR_INT32_MAX) {
        return NS_ERROR_DOM_OPERATION_ERR;
      }

      rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(),
                        static_cast<PRInt32>(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) {
      if (!hash) {
        return NS_ERROR_DOM_OPERATION_ERR;
      }

      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;
      }
    }

    // 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;
    }

    // Buffer for signature/verification input.
    SECItem dataToOperateOn;
    if (mSign) {
      if (AlgorithmRequiresHashing(mAlgorithm)) {
        dataToOperateOn = {siBuffer, hash->data, hash->len};
      } else {
        dataToOperateOn = {siBuffer, mData.Elements(),
                           static_cast<unsigned int>(mData.Length())};
      }

      // Sign the hash.
      rv = PK11_SignWithMechanism(mPrivKey.get(), mech, params, sig.get(),
                                  &dataToOperateOn);
      NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
      ATTEMPT_BUFFER_ASSIGN(mSignature, sig.get());
    } else {
      if (AlgorithmRequiresHashing(mAlgorithm)) {
        dataToOperateOn = {siBuffer, hash->data, hash->len};
      } else {
        dataToOperateOn = {siBuffer, mData.Elements(),
                           static_cast<unsigned int>(mData.Length())};
      }

      // 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(),
                                    &dataToOperateOn, 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;
    }
    glean::webcrypto::alg.AccumulateSingleSample(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 'alg'
    if (!aJwk.mKty.EqualsLiteral(JWK_TYPE_OKP) &&
        !(aJwk.mKty.EqualsLiteral(JWK_TYPE_EC) &&
          aKey->Algorithm().Mechanism() == CKM_ECDH1_DERIVE) &&
        aJwk.mAlg.WasPassed() &&
        aJwk.mAlg.Value() != aKey->Algorithm().JwkAlg()) {
      return false;
    }

    // Check 'ext'
    if (aKey->Extractable() && aJwk.mExt.WasPassed() && !aJwk.mExt.Value()) {
      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::Rooted<JS::Value> 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) &&
         !mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF))) {
      return NS_ERROR_DOM_DATA_ERR;
    }

    // Construct an appropriate KeyAlgorithm,
    // and verify that usages are appropriate
    if (mKeyData.Length() > UINT32_MAX / 8) {
      return NS_ERROR_DOM_DATA_ERR;
    }
    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_SYNTAX_ERR;
      }

      if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) &&
          mKey->HasUsageOtherThan(CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) {
        return NS_ERROR_DOM_SYNTAX_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_SYNTAX_ERR;
      }
      mKey->Algorithm().MakeKDF(mAlgName);

      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_SYNTAX_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 (!mKey->HasAnyUsage()) {
      return NS_ERROR_DOM_SYNTAX_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;
    }

    if (pubKey->keyType != rsaKey) {
      return NS_ERROR_DOM_DATA_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_SYNTAX_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_SYNTAX_ERR;
      }
    }

    if (mKey->GetKeyType() == CryptoKey::PRIVATE && !mKey->HasAnyUsage()) {
      return NS_ERROR_DOM_SYNTAX_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) ||
        mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
      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()) ||
        mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
      // Private key import
      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
        privKey = CryptoKey::PrivateKeyFromJwk(mJwk);
        if (!privKey) {
          return NS_ERROR_DOM_DATA_ERR;
        }
      } else {
        privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData);
        if (!privKey) {
          return NS_ERROR_DOM_DATA_ERR;
        }

        ScopedAutoSECItem ecParams;
        if (PK11_ReadRawAttribute(PK11_TypePrivKey, privKey.get(),
                                  CKA_EC_PARAMS, &ecParams) != SECSuccess) {
          return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
        }

        SECOidTag tag;
        if (!FindOIDTagForEncodedParameters(&ecParams, &tag)) {
          return NS_ERROR_DOM_DATA_ERR;
        }

        // Find a matching and supported named curve.
        if (!MapOIDTagToNamedCurve(tag, mNamedCurve)) {
          return NS_ERROR_DOM_NOT_SUPPORTED_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 (pubKey->keyType != ecKey) {
          return NS_ERROR_DOM_DATA_ERR;
        }
        if (!CheckEncodedParameters(&pubKey->u.ec.DEREncodedParams)) {
          return NS_ERROR_DOM_OPERATION_ERR;
        }

        SECOidTag tag;
        if (!FindOIDTagForEncodedParameters(&pubKey->u.ec.DEREncodedParams,
                                            &tag)) {
          return NS_ERROR_DOM_DATA_ERR;
        }

        // Find a matching and supported named curve.
        if (!MapOIDTagToNamedCurve(tag, 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;
    }

    // Checking the 'crv' consistency
    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
      // the curve stated in 'crv field'
      nsString namedCurveFromCrv;
      if (!NormalizeToken(mJwk.mCrv.Value(), namedCurveFromCrv)) {
        return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
      }

      // https://w3c.github.io/webcrypto/#ecdh-operations
      // https://w3c.github.io/webcrypto/#ecdsa-operations
      // If namedCurve is not equal to the namedCurve member of
      // normalizedAlgorithm (mNamedCurve in our case), throw a DataError.
      if (!mNamedCurve.Equals(namedCurveFromCrv)) {
        return NS_ERROR_DOM_DATA_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 = 0;
    } 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_SYNTAX_ERR;
    }

    if (mKey->GetKeyType() == CryptoKey::PRIVATE && !mKey->HasAnyUsage()) {
      return NS_ERROR_DOM_SYNTAX_ERR;
    }

    mKey->Algorithm().MakeEc(mAlgName, mNamedCurve);

    if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
      return NS_ERROR_DOM_DATA_ERR;
    }

    return NS_OK;
  }
};

class ImportOKPKeyTask : public ImportKeyTask {
 public:
  ImportOKPKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
                   const nsAString& aFormat, const ObjectOrString& aAlgorithm,
                   bool aExtractable, const Sequence<nsString>& aKeyUsages) {
    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
  }

  ImportOKPKeyTask(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)) {
      nsString paramsAlgName;
      mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, paramsAlgName);
      if (NS_FAILED(mEarlyRv)) {
        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
        return;
      }

      nsString algName;
      if (!NormalizeToken(paramsAlgName, algName)) {
        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
        return;
      }

      // Construct an appropriate KeyAlgorithm
      if (algName.EqualsLiteral(WEBCRYPTO_ALG_ED25519)) {
        mNamedCurve.AssignLiteral(WEBCRYPTO_NAMED_CURVE_ED25519);
      } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_X25519)) {
        mNamedCurve.AssignLiteral(WEBCRYPTO_NAMED_CURVE_CURVE25519);
      } else {
        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()) ||
        mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
      // Private key import
      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
        privKey = CryptoKey::PrivateKeyFromJwk(mJwk);
        if (!privKey) {
          return NS_ERROR_DOM_DATA_ERR;
        }
      } else {
        privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData);
        if (!privKey) {
--> --------------------

--> maximum size reached

--> --------------------

100%


¤ Dauer der Verarbeitung: 0.36 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge