summaryrefslogtreecommitdiffstats
path: root/dom/crypto
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/crypto
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/crypto')
-rw-r--r--dom/crypto/CryptoBuffer.cpp200
-rw-r--r--dom/crypto/CryptoBuffer.h58
-rw-r--r--dom/crypto/CryptoKey.cpp1342
-rw-r--r--dom/crypto/CryptoKey.h212
-rw-r--r--dom/crypto/KeyAlgorithmProxy.cpp241
-rw-r--r--dom/crypto/KeyAlgorithmProxy.h175
-rw-r--r--dom/crypto/WebCryptoCommon.h352
-rw-r--r--dom/crypto/WebCryptoTask.cpp3736
-rw-r--r--dom/crypto/WebCryptoTask.h249
-rw-r--r--dom/crypto/WebCryptoThreadPool.cpp119
-rw-r--r--dom/crypto/WebCryptoThreadPool.h54
-rw-r--r--dom/crypto/moz.build35
-rw-r--r--dom/crypto/test/file_indexedDB.html82
-rw-r--r--dom/crypto/test/mochitest.ini26
-rw-r--r--dom/crypto/test/test-array.js242
-rw-r--r--dom/crypto/test/test-vectors.js1052
-rw-r--r--dom/crypto/test/test-worker.js44
-rw-r--r--dom/crypto/test/test_WebCrypto.css86
-rw-r--r--dom/crypto/test/test_WebCrypto.html1077
-rw-r--r--dom/crypto/test/test_WebCrypto_DH.html284
-rw-r--r--dom/crypto/test/test_WebCrypto_ECDH.html583
-rw-r--r--dom/crypto/test/test_WebCrypto_ECDSA.html215
-rw-r--r--dom/crypto/test/test_WebCrypto_HKDF.html351
-rw-r--r--dom/crypto/test/test_WebCrypto_Import_Multiple_Identical_Keys.html119
-rw-r--r--dom/crypto/test/test_WebCrypto_JWK.html369
-rw-r--r--dom/crypto/test/test_WebCrypto_Normalize.html93
-rw-r--r--dom/crypto/test/test_WebCrypto_PBKDF2.html298
-rw-r--r--dom/crypto/test/test_WebCrypto_RSA_OAEP.html214
-rw-r--r--dom/crypto/test/test_WebCrypto_RSA_PSS.html404
-rw-r--r--dom/crypto/test/test_WebCrypto_Reject_Generating_Keys_Without_Usages.html86
-rw-r--r--dom/crypto/test/test_WebCrypto_Structured_Cloning.html305
-rw-r--r--dom/crypto/test/test_WebCrypto_Workers.html159
-rw-r--r--dom/crypto/test/test_WebCrypto_Wrap_Unwrap.html376
-rw-r--r--dom/crypto/test/test_indexedDB.html61
-rw-r--r--dom/crypto/test/util.js115
35 files changed, 13414 insertions, 0 deletions
diff --git a/dom/crypto/CryptoBuffer.cpp b/dom/crypto/CryptoBuffer.cpp
new file mode 100644
index 000000000..cefdd4287
--- /dev/null
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -0,0 +1,200 @@
+/* -*- 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 "CryptoBuffer.h"
+#include "secitem.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+uint8_t*
+CryptoBuffer::Assign(const CryptoBuffer& aData)
+{
+ // Same as in nsTArray_Impl::operator=, but return the value
+ // returned from ReplaceElementsAt to enable OOM detection
+ return ReplaceElementsAt(0, Length(), aData.Elements(), aData.Length(),
+ fallible);
+}
+
+uint8_t*
+CryptoBuffer::Assign(const uint8_t* aData, uint32_t aLength)
+{
+ return ReplaceElementsAt(0, Length(), aData, aLength, fallible);
+}
+
+uint8_t*
+CryptoBuffer::Assign(const nsACString& aString)
+{
+ return Assign(reinterpret_cast<uint8_t const*>(aString.BeginReading()),
+ aString.Length());
+}
+
+uint8_t*
+CryptoBuffer::Assign(const SECItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+ return Assign(aItem->data, aItem->len);
+}
+
+uint8_t*
+CryptoBuffer::Assign(const InfallibleTArray<uint8_t>& aData)
+{
+ return ReplaceElementsAt(0, Length(), aData.Elements(), aData.Length(),
+ fallible);
+}
+
+uint8_t*
+CryptoBuffer::Assign(const ArrayBuffer& aData)
+{
+ aData.ComputeLengthAndData();
+ return Assign(aData.Data(), aData.Length());
+}
+
+uint8_t*
+CryptoBuffer::Assign(const ArrayBufferView& aData)
+{
+ aData.ComputeLengthAndData();
+ return Assign(aData.Data(), aData.Length());
+}
+
+uint8_t*
+CryptoBuffer::Assign(const ArrayBufferViewOrArrayBuffer& aData)
+{
+ if (aData.IsArrayBufferView()) {
+ return Assign(aData.GetAsArrayBufferView());
+ } else if (aData.IsArrayBuffer()) {
+ return Assign(aData.GetAsArrayBuffer());
+ }
+
+ // If your union is uninitialized, something's wrong
+ MOZ_ASSERT(false);
+ Clear();
+ return nullptr;
+}
+
+uint8_t*
+CryptoBuffer::Assign(const OwningArrayBufferViewOrArrayBuffer& aData)
+{
+ if (aData.IsArrayBufferView()) {
+ return Assign(aData.GetAsArrayBufferView());
+ } else if (aData.IsArrayBuffer()) {
+ return Assign(aData.GetAsArrayBuffer());
+ }
+
+ // If your union is uninitialized, something's wrong
+ MOZ_ASSERT(false);
+ Clear();
+ return nullptr;
+}
+
+uint8_t*
+CryptoBuffer::AppendSECItem(const SECItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+ return AppendElements(aItem->data, aItem->len, fallible);
+}
+
+uint8_t*
+CryptoBuffer::AppendSECItem(const SECItem& aItem)
+{
+ return AppendElements(aItem.data, aItem.len, fallible);
+}
+
+// Helpers to encode/decode JWK's special flavor of Base64
+// * No whitespace
+// * No padding
+// * URL-safe character set
+nsresult
+CryptoBuffer::FromJwkBase64(const nsString& aBase64)
+{
+ NS_ConvertUTF16toUTF8 temp(aBase64);
+ temp.StripWhitespace();
+
+ // JWK prohibits padding per RFC 7515, section 2.
+ nsresult rv = Base64URLDecode(temp, Base64URLDecodePaddingPolicy::Reject,
+ *this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+CryptoBuffer::ToJwkBase64(nsString& aBase64)
+{
+ // Shortcut for the empty octet string
+ if (Length() == 0) {
+ aBase64.Truncate();
+ return NS_OK;
+ }
+
+ nsAutoCString base64;
+ nsresult rv = Base64URLEncode(Length(), Elements(),
+ Base64URLEncodePaddingPolicy::Omit, base64);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyASCIItoUTF16(base64, aBase64);
+ return NS_OK;
+}
+
+bool
+CryptoBuffer::ToSECItem(PLArenaPool *aArena, SECItem* aItem) const
+{
+ aItem->type = siBuffer;
+ aItem->data = nullptr;
+
+ if (!::SECITEM_AllocItem(aArena, aItem, Length())) {
+ return false;
+ }
+
+ memcpy(aItem->data, Elements(), Length());
+ return true;
+}
+
+JSObject*
+CryptoBuffer::ToUint8Array(JSContext* aCx) const
+{
+ return Uint8Array::Create(aCx, Length(), Elements());
+}
+
+bool
+CryptoBuffer::ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const
+{
+ MOZ_ASSERT(aBuf);
+ MOZ_ASSERT(aBufLen);
+
+ uint32_t dataLen = Length();
+ uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
+ if (NS_WARN_IF(!tmp)) {
+ return false;
+ }
+
+ memcpy(tmp, Elements(), dataLen);
+ *aBuf = tmp;
+ *aBufLen = dataLen;
+ return true;
+}
+
+// "BigInt" comes from the WebCrypto spec
+// ("unsigned long" isn't very "big", of course)
+// Likewise, the spec calls for big-endian ints
+bool
+CryptoBuffer::GetBigIntValue(unsigned long& aRetVal)
+{
+ if (Length() > sizeof(aRetVal)) {
+ return false;
+ }
+
+ aRetVal = 0;
+ for (size_t i=0; i < Length(); ++i) {
+ aRetVal = (aRetVal << 8) + ElementAt(i);
+ }
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/crypto/CryptoBuffer.h b/dom/crypto/CryptoBuffer.h
new file mode 100644
index 000000000..615916e58
--- /dev/null
+++ b/dom/crypto/CryptoBuffer.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_CryptoBuffer_h
+#define mozilla_dom_CryptoBuffer_h
+
+#include "nsTArray.h"
+#include "seccomon.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class OwningArrayBufferViewOrArrayBuffer;
+
+class CryptoBuffer : public FallibleTArray<uint8_t>
+{
+public:
+ uint8_t* Assign(const CryptoBuffer& aData);
+ uint8_t* Assign(const uint8_t* aData, uint32_t aLength);
+ uint8_t* Assign(const nsACString& aString);
+ uint8_t* Assign(const SECItem* aItem);
+ uint8_t* Assign(const InfallibleTArray<uint8_t>& aData);
+ uint8_t* Assign(const ArrayBuffer& aData);
+ uint8_t* Assign(const ArrayBufferView& aData);
+ uint8_t* Assign(const ArrayBufferViewOrArrayBuffer& aData);
+ uint8_t* Assign(const OwningArrayBufferViewOrArrayBuffer& aData);
+
+ uint8_t* AppendSECItem(const SECItem* aItem);
+ uint8_t* AppendSECItem(const SECItem& aItem);
+
+ template<typename T,
+ JSObject* UnwrapArray(JSObject*),
+ void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)>
+ uint8_t* Assign(const TypedArray_base<T, UnwrapArray,
+ GetLengthAndDataAndSharedness>& aArray)
+ {
+ aArray.ComputeLengthAndData();
+ return Assign(aArray.Data(), aArray.Length());
+ }
+
+ nsresult FromJwkBase64(const nsString& aBase64);
+ nsresult ToJwkBase64(nsString& aBase64);
+ bool ToSECItem(PLArenaPool* aArena, SECItem* aItem) const;
+ JSObject* ToUint8Array(JSContext* aCx) const;
+ bool ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const;
+
+ bool GetBigIntValue(unsigned long& aRetVal);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CryptoBuffer_h
diff --git a/dom/crypto/CryptoKey.cpp b/dom/crypto/CryptoKey.cpp
new file mode 100644
index 000000000..8763835bf
--- /dev/null
+++ b/dom/crypto/CryptoKey.cpp
@@ -0,0 +1,1342 @@
+/* -*- 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 "CryptoKey.h"
+
+#include "ScopedNSSTypes.h"
+#include "cryptohi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsNSSComponent.h"
+#include "pk11pub.h"
+
+// Templates taken from security/nss/lib/cryptohi/seckey.c
+// These would ideally be exported by NSS and until that
+// happens we have to keep our own copies.
+const SEC_ASN1Template SECKEY_DHPublicKeyTemplate[] = {
+ { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.publicValue), },
+ { 0, }
+};
+const SEC_ASN1Template SECKEY_DHParamKeyTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPublicKey) },
+ { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.prime), },
+ { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.base), },
+ { SEC_ASN1_SKIP_REST },
+ { 0, }
+};
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsresult
+StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut)
+{
+ if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_ENCRYPT)) {
+ aUsageOut = CryptoKey::ENCRYPT;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DECRYPT)) {
+ aUsageOut = CryptoKey::DECRYPT;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_SIGN)) {
+ aUsageOut = CryptoKey::SIGN;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_VERIFY)) {
+ aUsageOut = CryptoKey::VERIFY;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEKEY)) {
+ aUsageOut = CryptoKey::DERIVEKEY;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEBITS)) {
+ aUsageOut = CryptoKey::DERIVEBITS;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_WRAPKEY)) {
+ aUsageOut = CryptoKey::WRAPKEY;
+ } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)) {
+ aUsageOut = CryptoKey::UNWRAPKEY;
+ } else {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+ return NS_OK;
+}
+
+// This helper function will release the memory backing a SECKEYPrivateKey and
+// any resources acquired in its creation. It will leave the backing PKCS#11
+// object untouched, however. This should only be called from
+// PrivateKeyFromPrivateKeyTemplate.
+static void
+DestroyPrivateKeyWithoutDestroyingPKCS11Object(SECKEYPrivateKey* key)
+{
+ PK11_FreeSlot(key->pkcs11Slot);
+ PORT_FreeArena(key->arena, PR_TRUE);
+}
+
+// To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
+// generates a random ID for each key. The given template must contain an
+// attribute slot for a key ID, but it must consist of a null pointer and have a
+// length of 0.
+SECKEYPrivateKey*
+PrivateKeyFromPrivateKeyTemplate(CK_ATTRIBUTE* aTemplate,
+ CK_ULONG aTemplateSize)
+{
+ // Create a generic object with the contents of the key
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ // Generate a random 160-bit object ID. This ID must be unique.
+ ScopedSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20));
+ SECStatus rv = PK11_GenerateRandomOnSlot(slot, objID->data, objID->len);
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+ // Check if something is already using this ID.
+ SECKEYPrivateKey* preexistingKey = PK11_FindKeyByKeyID(slot, objID, nullptr);
+ if (preexistingKey) {
+ // Note that we can't just call SECKEY_DestroyPrivateKey here because that
+ // will destroy the PKCS#11 object that is backing a preexisting key (that
+ // we still have a handle on somewhere else in memory). If that object were
+ // destroyed, cryptographic operations performed by that other key would
+ // fail.
+ DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
+ // Try again with a new ID (but only once - collisions are very unlikely).
+ rv = PK11_GenerateRandomOnSlot(slot, objID->data, objID->len);
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+ preexistingKey = PK11_FindKeyByKeyID(slot, objID, nullptr);
+ if (preexistingKey) {
+ DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
+ return nullptr;
+ }
+ }
+
+ CK_ATTRIBUTE* idAttributeSlot = nullptr;
+ for (CK_ULONG i = 0; i < aTemplateSize; i++) {
+ if (aTemplate[i].type == CKA_ID) {
+ if (aTemplate[i].pValue != nullptr || aTemplate[i].ulValueLen != 0) {
+ return nullptr;
+ }
+ idAttributeSlot = aTemplate + i;
+ break;
+ }
+ }
+ if (!idAttributeSlot) {
+ return nullptr;
+ }
+
+ idAttributeSlot->pValue = objID->data;
+ idAttributeSlot->ulValueLen = objID->len;
+ ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot,
+ aTemplate,
+ aTemplateSize,
+ PR_FALSE));
+ // Unset the ID attribute slot's pointer and length so that data that only
+ // lives for the scope of this function doesn't escape.
+ idAttributeSlot->pValue = nullptr;
+ idAttributeSlot->ulValueLen = 0;
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Have NSS translate the object to a private key.
+ return PK11_FindKeyByKeyID(slot, objID, nullptr);
+}
+
+CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal)
+ , mAttributes(0)
+ , mSymKey()
+ , mPrivateKey(nullptr)
+ , mPublicKey(nullptr)
+{
+}
+
+CryptoKey::~CryptoKey()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+JSObject*
+CryptoKey::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CryptoKeyBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+CryptoKey::GetType(nsString& aRetVal) const
+{
+ uint32_t type = mAttributes & TYPE_MASK;
+ switch (type) {
+ case PUBLIC: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC); break;
+ case PRIVATE: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE); break;
+ case SECRET: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_SECRET); break;
+ }
+}
+
+bool
+CryptoKey::Extractable() const
+{
+ return (mAttributes & EXTRACTABLE);
+}
+
+void
+CryptoKey::GetAlgorithm(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) const
+{
+ bool converted = false;
+ JS::RootedValue val(cx);
+ switch (mAlgorithm.mType) {
+ case KeyAlgorithmProxy::AES:
+ converted = ToJSValue(cx, mAlgorithm.mAes, &val);
+ break;
+ case KeyAlgorithmProxy::HMAC:
+ converted = ToJSValue(cx, mAlgorithm.mHmac, &val);
+ break;
+ case KeyAlgorithmProxy::RSA: {
+ RootedDictionary<RsaHashedKeyAlgorithm> rsa(cx);
+ converted = mAlgorithm.mRsa.ToKeyAlgorithm(cx, rsa);
+ if (converted) {
+ converted = ToJSValue(cx, rsa, &val);
+ }
+ break;
+ }
+ case KeyAlgorithmProxy::EC:
+ converted = ToJSValue(cx, mAlgorithm.mEc, &val);
+ break;
+ case KeyAlgorithmProxy::DH: {
+ RootedDictionary<DhKeyAlgorithm> dh(cx);
+ converted = mAlgorithm.mDh.ToKeyAlgorithm(cx, dh);
+ if (converted) {
+ converted = ToJSValue(cx, dh, &val);
+ }
+ break;
+ }
+ }
+ if (!converted) {
+ aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ aRetVal.set(&val.toObject());
+}
+
+void
+CryptoKey::GetUsages(nsTArray<nsString>& aRetVal) const
+{
+ if (mAttributes & ENCRYPT) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_ENCRYPT));
+ }
+ if (mAttributes & DECRYPT) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DECRYPT));
+ }
+ if (mAttributes & SIGN) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_SIGN));
+ }
+ if (mAttributes & VERIFY) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_VERIFY));
+ }
+ if (mAttributes & DERIVEKEY) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY));
+ }
+ if (mAttributes & DERIVEBITS) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS));
+ }
+ if (mAttributes & WRAPKEY) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_WRAPKEY));
+ }
+ if (mAttributes & UNWRAPKEY) {
+ aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_UNWRAPKEY));
+ }
+}
+
+KeyAlgorithmProxy&
+CryptoKey::Algorithm()
+{
+ return mAlgorithm;
+}
+
+const KeyAlgorithmProxy&
+CryptoKey::Algorithm() const
+{
+ return mAlgorithm;
+}
+
+CryptoKey::KeyType
+CryptoKey::GetKeyType() const
+{
+ return static_cast<CryptoKey::KeyType>(mAttributes & TYPE_MASK);
+}
+
+nsresult
+CryptoKey::SetType(const nsString& aType)
+{
+ mAttributes &= CLEAR_TYPE;
+ if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_SECRET)) {
+ mAttributes |= SECRET;
+ } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC)) {
+ mAttributes |= PUBLIC;
+ } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE)) {
+ mAttributes |= PRIVATE;
+ } else {
+ mAttributes |= UNKNOWN;
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+void
+CryptoKey::SetType(CryptoKey::KeyType aType)
+{
+ mAttributes &= CLEAR_TYPE;
+ mAttributes |= aType;
+}
+
+void
+CryptoKey::SetExtractable(bool aExtractable)
+{
+ mAttributes &= CLEAR_EXTRACTABLE;
+ if (aExtractable) {
+ mAttributes |= EXTRACTABLE;
+ }
+}
+
+// NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the
+// public value. To properly export the private key to JWK or PKCS #8 we need
+// the public key data though and so we use this method to augment a private
+// key with data from the given public key.
+nsresult
+CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey)
+{
+ // This should be a private key.
+ MOZ_ASSERT(GetKeyType() == PRIVATE);
+ // There should be a private NSS key with type 'EC'.
+ MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey);
+ // The given public key should have the same key type.
+ MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType);
+
+ nsNSSShutDownPreventionLock locker;
+
+ // Read EC params.
+ ScopedAutoSECItem params;
+ SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey,
+ CKA_EC_PARAMS, &params);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Read private value.
+ ScopedAutoSECItem value;
+ rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey, CKA_VALUE, &value);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem* point = &aPublicKey->u.ec.publicValue;
+ CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
+ CK_BBOOL falseValue = CK_FALSE;
+ CK_KEY_TYPE ecValue = CKK_EC;
+
+ CK_ATTRIBUTE keyTemplate[9] = {
+ { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
+ { CKA_KEY_TYPE, &ecValue, sizeof(ecValue) },
+ { CKA_TOKEN, &falseValue, sizeof(falseValue) },
+ { CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
+ { CKA_PRIVATE, &falseValue, sizeof(falseValue) },
+ // PrivateKeyFromPrivateKeyTemplate sets the ID.
+ { CKA_ID, nullptr, 0 },
+ { CKA_EC_PARAMS, params.data, params.len },
+ { CKA_EC_POINT, point->data, point->len },
+ { CKA_VALUE, value.data, value.len },
+ };
+
+ mPrivateKey = PrivateKeyFromPrivateKeyTemplate(keyTemplate,
+ ArrayLength(keyTemplate));
+ NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
+
+ return NS_OK;
+}
+
+void
+CryptoKey::ClearUsages()
+{
+ mAttributes &= CLEAR_USAGES;
+}
+
+nsresult
+CryptoKey::AddUsage(const nsString& aUsage)
+{
+ return AddUsageIntersecting(aUsage, USAGES_MASK);
+}
+
+nsresult
+CryptoKey::AddUsageIntersecting(const nsString& aUsage, uint32_t aUsageMask)
+{
+ KeyUsage usage;
+ if (NS_FAILED(StringToUsage(aUsage, usage))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (usage & aUsageMask) {
+ AddUsage(usage);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+void
+CryptoKey::AddUsage(CryptoKey::KeyUsage aUsage)
+{
+ mAttributes |= aUsage;
+}
+
+bool
+CryptoKey::HasAnyUsage()
+{
+ return !!(mAttributes & USAGES_MASK);
+}
+
+bool
+CryptoKey::HasUsage(CryptoKey::KeyUsage aUsage)
+{
+ return !!(mAttributes & aUsage);
+}
+
+bool
+CryptoKey::HasUsageOtherThan(uint32_t aUsages)
+{
+ return !!(mAttributes & USAGES_MASK & ~aUsages);
+}
+
+bool
+CryptoKey::IsRecognizedUsage(const nsString& aUsage)
+{
+ KeyUsage dummy;
+ nsresult rv = StringToUsage(aUsage, dummy);
+ return NS_SUCCEEDED(rv);
+}
+
+bool
+CryptoKey::AllUsagesRecognized(const Sequence<nsString>& aUsages)
+{
+ for (uint32_t i = 0; i < aUsages.Length(); ++i) {
+ if (!IsRecognizedUsage(aUsages[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey)
+{
+ if (!mSymKey.Assign(aSymKey)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey)
+{
+ nsNSSShutDownPreventionLock locker;
+
+ if (!aPrivateKey || isAlreadyShutDown()) {
+ mPrivateKey = nullptr;
+ return NS_OK;
+ }
+
+ mPrivateKey = SECKEY_CopyPrivateKey(aPrivateKey);
+ return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult
+CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey)
+{
+ nsNSSShutDownPreventionLock locker;
+
+ if (!aPublicKey || isAlreadyShutDown()) {
+ mPublicKey = nullptr;
+ return NS_OK;
+ }
+
+ mPublicKey = SECKEY_CopyPublicKey(aPublicKey);
+ return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+const CryptoBuffer&
+CryptoKey::GetSymKey() const
+{
+ return mSymKey;
+}
+
+SECKEYPrivateKey*
+CryptoKey::GetPrivateKey() const
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!mPrivateKey || isAlreadyShutDown()) {
+ return nullptr;
+ }
+ return SECKEY_CopyPrivateKey(mPrivateKey.get());
+}
+
+SECKEYPublicKey*
+CryptoKey::GetPublicKey() const
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!mPublicKey || isAlreadyShutDown()) {
+ return nullptr;
+ }
+ return SECKEY_CopyPublicKey(mPublicKey.get());
+}
+
+void CryptoKey::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void CryptoKey::destructorSafeDestroyNSSReference()
+{
+ mPrivateKey.dispose();
+ mPublicKey.dispose();
+}
+
+
+// Serialization and deserialization convenience methods
+
+SECKEYPrivateKey*
+CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ SECKEYPrivateKey* privKey;
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ SECItem pkcs8Item = { siBuffer, nullptr, 0 };
+ if (!aKeyData.ToSECItem(arena, &pkcs8Item)) {
+ return nullptr;
+ }
+
+ // Allow everything, we enforce usage ourselves
+ unsigned int usage = KU_ALL;
+
+ SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ slot.get(), &pkcs8Item, nullptr, nullptr, false, false,
+ usage, &privKey, nullptr);
+
+ if (rv == SECFailure) {
+ return nullptr;
+ }
+ return privKey;
+}
+
+SECKEYPublicKey*
+CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ SECItem spkiItem = { siBuffer, nullptr, 0 };
+ if (!aKeyData.ToSECItem(arena, &spkiItem)) {
+ return nullptr;
+ }
+
+ ScopedCERTSubjectPublicKeyInfo spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
+ if (!spki) {
+ return nullptr;
+ }
+
+ bool isECDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH,
+ &spki->algorithm.algorithm);
+ bool isDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_DH_KEY_AGREEMENT,
+ &spki->algorithm.algorithm);
+
+ // Check for |id-ecDH| and |dhKeyAgreement|. Per the WebCrypto spec we must
+ // support these OIDs but NSS does unfortunately not know about them. Let's
+ // change the algorithm to |id-ecPublicKey| or |dhPublicKey| to make NSS happy.
+ if (isECDHAlgorithm || isDHAlgorithm) {
+ SECOidTag oid = SEC_OID_UNKNOWN;
+ if (isECDHAlgorithm) {
+ oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
+ } else if (isDHAlgorithm) {
+ oid = SEC_OID_X942_DIFFIE_HELMAN_KEY;
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ SECOidData* oidData = SECOID_FindOIDByTag(oid);
+ if (!oidData) {
+ return nullptr;
+ }
+
+ SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
+ &oidData->oid);
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+ }
+
+ ScopedSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
+ if (!tmp.get() || !PublicKeyValid(tmp.get())) {
+ return nullptr;
+ }
+
+ return SECKEY_CopyPublicKey(tmp);
+}
+
+nsresult
+CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ ScopedSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr));
+ if (!pkcs8Item.get()) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+ if (!aRetVal.Assign(pkcs8Item.get())) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+}
+
+nsresult
+PublicDhKeyToSpki(SECKEYPublicKey* aPubKey,
+ CERTSubjectPublicKeyInfo* aSpki)
+{
+ SECItem* params = ::SECITEM_AllocItem(aSpki->arena, nullptr, 0);
+ if (!params) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem* rvItem = SEC_ASN1EncodeItem(aSpki->arena, params, aPubKey,
+ SECKEY_DHParamKeyTemplate);
+ if (!rvItem) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECStatus rv = SECOID_SetAlgorithmID(aSpki->arena, &aSpki->algorithm,
+ SEC_OID_X942_DIFFIE_HELMAN_KEY, params);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ rvItem = SEC_ASN1EncodeItem(aSpki->arena, &aSpki->subjectPublicKey, aPubKey,
+ SECKEY_DHPublicKeyTemplate);
+ if (!rvItem) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // The public value is a BIT_STRING encoded as an INTEGER. After encoding
+ // an INT we need to adjust the length to reflect the number of bits.
+ aSpki->subjectPublicKey.len <<= 3;
+
+ return NS_OK;
+}
+
+nsresult
+CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ ScopedCERTSubjectPublicKeyInfo spki;
+
+ // NSS doesn't support exporting DH public keys.
+ if (aPubKey->keyType == dhKey) {
+ // Mimic the behavior of SECKEY_CreateSubjectPublicKeyInfo() and create
+ // a new arena for the SPKI object.
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ spki = PORT_ArenaZNew(arena, CERTSubjectPublicKeyInfo);
+ if (!spki) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Assign |arena| to |spki| and null the variable afterwards so that the
+ // arena created above that holds the SPKI object is free'd when |spki|
+ // goes out of scope, not when |arena| does.
+ spki->arena = arena.forget();
+
+ nsresult rv = PublicDhKeyToSpki(aPubKey, spki);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ spki = SECKEY_CreateSubjectPublicKeyInfo(aPubKey);
+ if (!spki) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ // Per WebCrypto spec we must export ECDH SPKIs with the algorithm OID
+ // id-ecDH (1.3.132.112) and DH SPKIs with OID dhKeyAgreement
+ // (1.2.840.113549.1.3.1). NSS doesn't know about these OIDs and there is
+ // no way to specify the algorithm to use when exporting a public key.
+ if (aPubKey->keyType == ecKey || aPubKey->keyType == dhKey) {
+ const SECItem* oidData = nullptr;
+ if (aPubKey->keyType == ecKey) {
+ oidData = &SEC_OID_DATA_EC_DH;
+ } else if (aPubKey->keyType == dhKey) {
+ oidData = &SEC_OID_DATA_DH_KEY_AGREEMENT;
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
+ oidData);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
+ ScopedSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki, tpl));
+
+ if (!aRetVal.Assign(spkiItem.get())) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+}
+
+SECItem*
+CreateECPointForCoordinates(const CryptoBuffer& aX,
+ const CryptoBuffer& aY,
+ PLArenaPool* aArena)
+{
+ // Check that both points have the same length.
+ if (aX.Length() != aY.Length()) {
+ return nullptr;
+ }
+
+ // Create point.
+ SECItem* point = ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
+ if (!point) {
+ return nullptr;
+ }
+
+ // Set point data.
+ point->data[0] = EC_POINT_FORM_UNCOMPRESSED;
+ memcpy(point->data + 1, aX.Elements(), aX.Length());
+ memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length());
+
+ return point;
+}
+
+SECKEYPrivateKey*
+CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
+ CK_BBOOL falseValue = CK_FALSE;
+
+ if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
+ // Verify that all of the required parameters are present
+ CryptoBuffer x, y, d;
+ if (!aJwk.mCrv.WasPassed() ||
+ !aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
+ !aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) ||
+ !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) {
+ return nullptr;
+ }
+
+ nsString namedCurve;
+ if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
+ return nullptr;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ // Create parameters.
+ SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
+ if (!params) {
+ return nullptr;
+ }
+
+ SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get());
+ if (!ecPoint) {
+ return nullptr;
+ }
+
+ // Populate template from parameters
+ CK_KEY_TYPE ecValue = CKK_EC;
+ CK_ATTRIBUTE keyTemplate[9] = {
+ { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
+ { CKA_KEY_TYPE, &ecValue, sizeof(ecValue) },
+ { CKA_TOKEN, &falseValue, sizeof(falseValue) },
+ { CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
+ { CKA_PRIVATE, &falseValue, sizeof(falseValue) },
+ // PrivateKeyFromPrivateKeyTemplate sets the ID.
+ { CKA_ID, nullptr, 0 },
+ { CKA_EC_PARAMS, params->data, params->len },
+ { CKA_EC_POINT, ecPoint->data, ecPoint->len },
+ { CKA_VALUE, (void*) d.Elements(), (CK_ULONG) d.Length() },
+ };
+
+ return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
+ ArrayLength(keyTemplate));
+ }
+
+ if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
+ // Verify that all of the required parameters are present
+ CryptoBuffer n, e, d, p, q, dp, dq, qi;
+ if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
+ !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
+ !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
+ !aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
+ !aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
+ !aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
+ !aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
+ !aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
+ return nullptr;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ // Populate template from parameters
+ CK_KEY_TYPE rsaValue = CKK_RSA;
+ CK_ATTRIBUTE keyTemplate[14] = {
+ { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
+ { CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue) },
+ { CKA_TOKEN, &falseValue, sizeof(falseValue) },
+ { CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
+ { CKA_PRIVATE, &falseValue, sizeof(falseValue) },
+ // PrivateKeyFromPrivateKeyTemplate sets the ID.
+ { CKA_ID, nullptr, 0 },
+ { CKA_MODULUS, (void*) n.Elements(), (CK_ULONG) n.Length() },
+ { CKA_PUBLIC_EXPONENT, (void*) e.Elements(), (CK_ULONG) e.Length() },
+ { CKA_PRIVATE_EXPONENT, (void*) d.Elements(), (CK_ULONG) d.Length() },
+ { CKA_PRIME_1, (void*) p.Elements(), (CK_ULONG) p.Length() },
+ { CKA_PRIME_2, (void*) q.Elements(), (CK_ULONG) q.Length() },
+ { CKA_EXPONENT_1, (void*) dp.Elements(), (CK_ULONG) dp.Length() },
+ { CKA_EXPONENT_2, (void*) dq.Elements(), (CK_ULONG) dq.Length() },
+ { CKA_COEFFICIENT, (void*) qi.Elements(), (CK_ULONG) qi.Length() },
+ };
+
+ return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
+ ArrayLength(keyTemplate));
+ }
+
+ return nullptr;
+}
+
+bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
+ CK_ATTRIBUTE_TYPE aAttribute,
+ Optional<nsString>& aDst)
+{
+ ScopedAutoSECItem item;
+ if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, &item)
+ != SECSuccess) {
+ return false;
+ }
+
+ CryptoBuffer buffer;
+ if (!buffer.Assign(&item)) {
+ return false;
+ }
+
+ if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey, const SECItem* aEcParams,
+ const SECItem* aPublicValue, JsonWebKey& aRetVal)
+{
+ aRetVal.mX.Construct();
+ aRetVal.mY.Construct();
+
+ // Check that the given EC parameters are valid.
+ if (!CheckEncodedECParameters(aEcParams)) {
+ return false;
+ }
+
+ // Construct the OID tag.
+ SECItem oid = { siBuffer, nullptr, 0 };
+ oid.len = aEcParams->data[1];
+ oid.data = aEcParams->data + 2;
+
+ uint32_t flen;
+ switch (SECOID_FindOIDTag(&oid)) {
+ case SEC_OID_SECG_EC_SECP256R1:
+ flen = 32; // bytes
+ aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P256));
+ break;
+ case SEC_OID_SECG_EC_SECP384R1:
+ flen = 48; // bytes
+ aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P384));
+ break;
+ case SEC_OID_SECG_EC_SECP521R1:
+ flen = 66; // bytes
+ aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P521));
+ break;
+ default:
+ return false;
+ }
+
+ // No support for compressed points.
+ if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) {
+ return false;
+ }
+
+ // Check length of uncompressed point coordinates.
+ if (aPublicValue->len != (2 * flen + 1)) {
+ return false;
+ }
+
+ ScopedSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
+ ScopedSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen));
+ if (!ecPointX || !ecPointY) {
+ return false;
+ }
+
+ // Extract point data.
+ memcpy(ecPointX->data, aPublicValue->data + 1, flen);
+ memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen);
+
+ CryptoBuffer x, y;
+ if (!x.Assign(ecPointX) || NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
+ !y.Assign(ecPointY) || NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
+ return false;
+ }
+
+ aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_EC);
+ return true;
+}
+
+nsresult
+CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
+ JsonWebKey& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ switch (aPrivKey->keyType) {
+ case rsaKey: {
+ aRetVal.mN.Construct();
+ aRetVal.mE.Construct();
+ aRetVal.mD.Construct();
+ aRetVal.mP.Construct();
+ aRetVal.mQ.Construct();
+ aRetVal.mDp.Construct();
+ aRetVal.mDq.Construct();
+ aRetVal.mQi.Construct();
+
+ if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
+ !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA);
+ return NS_OK;
+ }
+ case ecKey: {
+ // Read EC params.
+ ScopedAutoSECItem params;
+ SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
+ CKA_EC_PARAMS, &params);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Read public point Q.
+ ScopedAutoSECItem ecPoint;
+ rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
+ &ecPoint);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, &params, &ecPoint, aRetVal)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ aRetVal.mD.Construct();
+
+ // Read private value.
+ if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+}
+
+SECKEYPublicKey*
+CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve)
+{
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ // It's important that this be a ScopedSECKEYPublicKey, as this ensures that
+ // SECKEY_DestroyPublicKey will be called on it. If this doesn't happen, when
+ // CryptoKey::PublicKeyValid is called on it and it gets moved to the internal
+ // PKCS#11 slot, it will leak a reference to the slot.
+ ScopedSECKEYPublicKey key(PORT_ArenaZNew(arena, SECKEYPublicKey));
+ if (!key) {
+ return nullptr;
+ }
+
+ key->arena = nullptr; // key doesn't own the arena; it won't get double-freed
+ key->keyType = ecKey;
+ key->pkcs11Slot = nullptr;
+ key->pkcs11ID = CK_INVALID_HANDLE;
+
+ // Create curve parameters.
+ SECItem* params = CreateECParamsForCurve(aNamedCurve, arena);
+ if (!params) {
+ return nullptr;
+ }
+ key->u.ec.DEREncodedParams = *params;
+
+ // Set public point.
+ key->u.ec.publicValue = *aKeyData;
+
+ // Ensure the given point is on the curve.
+ if (!CryptoKey::PublicKeyValid(key)) {
+ return nullptr;
+ }
+
+ return SECKEY_CopyPublicKey(key);
+}
+
+SECKEYPublicKey*
+CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
+ // Verify that all of the required parameters are present
+ CryptoBuffer n, e;
+ if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
+ !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
+ return nullptr;
+ }
+
+ // Transcode to a DER RSAPublicKey structure
+ struct RSAPublicKeyData {
+ SECItem n;
+ SECItem e;
+ };
+ const RSAPublicKeyData input = {
+ { siUnsignedInteger, n.Elements(), (unsigned int) n.Length() },
+ { siUnsignedInteger, e.Elements(), (unsigned int) e.Length() }
+ };
+ const SEC_ASN1Template rsaPublicKeyTemplate[] = {
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
+ {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),},
+ {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),},
+ {0,}
+ };
+
+ ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
+ rsaPublicKeyTemplate));
+ if (!pkDer.get()) {
+ return nullptr;
+ }
+
+ return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
+ }
+
+ if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
+ // Verify that all of the required parameters are present
+ CryptoBuffer x, y;
+ if (!aJwk.mCrv.WasPassed() ||
+ !aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
+ !aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) {
+ return nullptr;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ // Create point.
+ SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
+ if (!point) {
+ return nullptr;
+ }
+
+ nsString namedCurve;
+ if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
+ return nullptr;
+ }
+
+ return CreateECPublicKey(point, namedCurve);
+ }
+
+ return nullptr;
+}
+
+nsresult
+CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
+ JsonWebKey& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ switch (aPubKey->keyType) {
+ case rsaKey: {
+ CryptoBuffer n, e;
+ aRetVal.mN.Construct();
+ aRetVal.mE.Construct();
+
+ if (!n.Assign(&aPubKey->u.rsa.modulus) ||
+ !e.Assign(&aPubKey->u.rsa.publicExponent) ||
+ NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
+ NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA);
+ return NS_OK;
+ }
+ case ecKey:
+ if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams,
+ &aPubKey->u.ec.publicValue, aRetVal)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+}
+
+SECKEYPublicKey*
+CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
+ const CryptoBuffer& aPrime,
+ const CryptoBuffer& aGenerator,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey);
+ if (!key) {
+ return nullptr;
+ }
+
+ key->keyType = dhKey;
+ key->pkcs11Slot = nullptr;
+ key->pkcs11ID = CK_INVALID_HANDLE;
+
+ // Set DH public key params.
+ if (!aPrime.ToSECItem(arena, &key->u.dh.prime) ||
+ !aGenerator.ToSECItem(arena, &key->u.dh.base) ||
+ !aKeyData.ToSECItem(arena, &key->u.dh.publicValue)) {
+ return nullptr;
+ }
+
+ key->u.dh.prime.type = siUnsignedInteger;
+ key->u.dh.base.type = siUnsignedInteger;
+ key->u.dh.publicValue.type = siUnsignedInteger;
+
+ return SECKEY_CopyPublicKey(key);
+}
+
+nsresult
+CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ if (!aRetVal.Assign(&aPubKey->u.dh.publicValue)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+}
+
+SECKEYPublicKey*
+CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData,
+ const nsString& aNamedCurve,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return nullptr;
+ }
+
+ SECItem rawItem = { siBuffer, nullptr, 0 };
+ if (!aKeyData.ToSECItem(arena, &rawItem)) {
+ return nullptr;
+ }
+
+ uint32_t flen;
+ if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) {
+ flen = 32; // bytes
+ } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) {
+ flen = 48; // bytes
+ } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) {
+ flen = 66; // bytes
+ } else {
+ return nullptr;
+ }
+
+ // Check length of uncompressed point coordinates. There are 2 field elements
+ // and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED).
+ if (rawItem.len != (2 * flen + 1)) {
+ return nullptr;
+ }
+
+ // No support for compressed points.
+ if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
+ return nullptr;
+ }
+
+ return CreateECPublicKey(&rawItem, aNamedCurve);
+}
+
+nsresult
+CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+}
+
+bool
+CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey)
+{
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot.get()) {
+ return false;
+ }
+
+ // This assumes that NSS checks the validity of a public key when
+ // it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE
+ // if it is invalid.
+ CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot, aPubKey, PR_FALSE);
+ if (id == CK_INVALID_HANDLE) {
+ return false;
+ }
+
+ SECStatus rv = PK11_DestroyObject(slot, id);
+ return (rv == SECSuccess);
+}
+
+bool
+CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return false;
+ }
+
+ // Write in five pieces
+ // 1. Attributes
+ // 2. Symmetric key as raw (if present)
+ // 3. Private key as pkcs8 (if present)
+ // 4. Public key as spki (if present)
+ // 5. Algorithm in whatever form it chooses
+ CryptoBuffer priv, pub;
+
+ if (mPrivateKey) {
+ if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey, priv, locker))) {
+ return false;
+ }
+ }
+
+ if (mPublicKey) {
+ if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey, pub, locker))) {
+ return false;
+ }
+ }
+
+ return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) &&
+ WriteBuffer(aWriter, mSymKey) &&
+ WriteBuffer(aWriter, priv) &&
+ WriteBuffer(aWriter, pub) &&
+ mAlgorithm.WriteStructuredClone(aWriter);
+}
+
+bool
+CryptoKey::ReadStructuredClone(JSStructuredCloneReader* aReader)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return false;
+ }
+
+ // Ensure that NSS is initialized.
+ if (!EnsureNSSInitializedChromeOrContent()) {
+ return false;
+ }
+
+ uint32_t version;
+ CryptoBuffer sym, priv, pub;
+
+ bool read = JS_ReadUint32Pair(aReader, &mAttributes, &version) &&
+ (version == CRYPTOKEY_SC_VERSION) &&
+ ReadBuffer(aReader, sym) &&
+ ReadBuffer(aReader, priv) &&
+ ReadBuffer(aReader, pub) &&
+ mAlgorithm.ReadStructuredClone(aReader);
+ if (!read) {
+ return false;
+ }
+
+ if (sym.Length() > 0 && !mSymKey.Assign(sym)) {
+ return false;
+ }
+ if (priv.Length() > 0) {
+ mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv, locker);
+ }
+ if (pub.Length() > 0) {
+ mPublicKey = CryptoKey::PublicKeyFromSpki(pub, locker);
+ }
+
+ // Ensure that what we've read is consistent
+ // If the attributes indicate a key type, should have a key of that type
+ if (!((GetKeyType() == SECRET && mSymKey.Length() > 0) ||
+ (GetKeyType() == PRIVATE && mPrivateKey) ||
+ (GetKeyType() == PUBLIC && mPublicKey))) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/crypto/CryptoKey.h b/dom/crypto/CryptoKey.h
new file mode 100644
index 000000000..31f7a84aa
--- /dev/null
+++ b/dom/crypto/CryptoKey.h
@@ -0,0 +1,212 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_CryptoKey_h
+#define mozilla_dom_CryptoKey_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+#include "nsNSSShutDown.h"
+#include "pk11pub.h"
+#include "keyhi.h"
+#include "ScopedNSSTypes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/KeyAlgorithmProxy.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+
+#define CRYPTOKEY_SC_VERSION 0x00000001
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+/*
+
+The internal structure of keys is dictated by the need for cloning.
+We store everything besides the key data itself in a 32-bit bitmask,
+with the following structure (byte-aligned for simplicity, in order
+from least to most significant):
+
+Bits Usage
+0 Extractable
+1-7 [reserved]
+8-15 KeyType
+16-23 KeyUsage
+24-31 [reserved]
+
+In the order of a hex value for a uint32_t
+
+ 3 2 1 0
+ 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|~~~~~~~~~~~~~~~| Usage | Type |~~~~~~~~~~~~~|E|
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+Thus, internally, a key has the following fields:
+* uint32_t - flags for extractable, usage, type
+* KeyAlgorithm - the algorithm (which must serialize/deserialize itself)
+* The actual keys (which the CryptoKey must serialize)
+
+*/
+
+struct JsonWebKey;
+
+class CryptoKey final : public nsISupports,
+ public nsWrapperCache,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CryptoKey)
+
+ static const uint32_t CLEAR_EXTRACTABLE = 0xFFFFFFE;
+ static const uint32_t EXTRACTABLE = 0x00000001;
+
+ static const uint32_t CLEAR_TYPE = 0xFFFF00FF;
+ static const uint32_t TYPE_MASK = 0x0000FF00;
+ enum KeyType {
+ UNKNOWN = 0x00000000,
+ SECRET = 0x00000100,
+ PUBLIC = 0x00000200,
+ PRIVATE = 0x00000300
+ };
+
+ static const uint32_t CLEAR_USAGES = 0xFF00FFFF;
+ static const uint32_t USAGES_MASK = 0x00FF0000;
+ enum KeyUsage {
+ ENCRYPT = 0x00010000,
+ DECRYPT = 0x00020000,
+ SIGN = 0x00040000,
+ VERIFY = 0x00080000,
+ DERIVEKEY = 0x00100000,
+ DERIVEBITS = 0x00200000,
+ WRAPKEY = 0x00400000,
+ UNWRAPKEY = 0x00800000
+ };
+
+ explicit CryptoKey(nsIGlobalObject* aWindow);
+
+ nsIGlobalObject* GetParentObject() const
+ {
+ return mGlobal;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ void GetType(nsString& aRetVal) const;
+ bool Extractable() const;
+ void GetAlgorithm(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) const;
+ void GetUsages(nsTArray<nsString>& aRetVal) const;
+
+ // The below methods are not exposed to JS, but C++ can use
+ // them to manipulate the object
+
+ KeyAlgorithmProxy& Algorithm();
+ const KeyAlgorithmProxy& Algorithm() const;
+ KeyType GetKeyType() const;
+ nsresult SetType(const nsString& aType);
+ void SetType(KeyType aType);
+ void SetExtractable(bool aExtractable);
+ nsresult AddPublicKeyData(SECKEYPublicKey* point);
+ void ClearUsages();
+ nsresult AddUsage(const nsString& aUsage);
+ nsresult AddUsageIntersecting(const nsString& aUsage, uint32_t aUsageMask);
+ void AddUsage(KeyUsage aUsage);
+ bool HasAnyUsage();
+ bool HasUsage(KeyUsage aUsage);
+ bool HasUsageOtherThan(uint32_t aUsages);
+ static bool IsRecognizedUsage(const nsString& aUsage);
+ static bool AllUsagesRecognized(const Sequence<nsString>& aUsages);
+
+ nsresult SetSymKey(const CryptoBuffer& aSymKey);
+ nsresult SetPrivateKey(SECKEYPrivateKey* aPrivateKey);
+ nsresult SetPublicKey(SECKEYPublicKey* aPublicKey);
+
+ // Accessors for the keys themselves
+ // Note: GetPrivateKey and GetPublicKey return copies of the internal
+ // key handles, which the caller must free with SECKEY_DestroyPrivateKey
+ // or SECKEY_DestroyPublicKey.
+ const CryptoBuffer& GetSymKey() const;
+ SECKEYPrivateKey* GetPrivateKey() const;
+ SECKEYPublicKey* GetPublicKey() const;
+
+ // For nsNSSShutDownObject
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+
+ // Serialization and deserialization convenience methods
+ // Note:
+ // 1. The inputs aKeyData are non-const only because the NSS import
+ // functions lack the const modifier. They should not be modified.
+ // 2. All of the NSS key objects returned need to be freed by the caller.
+ static SECKEYPrivateKey* PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static SECKEYPublicKey* PublicKeyFromSpki(CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PublicKeyToSpki(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static SECKEYPrivateKey* PrivateKeyFromJwk(const JsonWebKey& aJwk,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
+ JsonWebKey& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static SECKEYPublicKey* PublicKeyFromJwk(const JsonWebKey& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PublicKeyToJwk(SECKEYPublicKey* aPubKey,
+ JsonWebKey& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static SECKEYPublicKey* PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
+ const CryptoBuffer& aPrime,
+ const CryptoBuffer& aGenerator,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static SECKEYPublicKey* PublicECKeyFromRaw(CryptoBuffer& aKeyData,
+ const nsString& aNamedCurve,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static nsresult PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
+ CryptoBuffer& aRetVal,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
+ static bool PublicKeyValid(SECKEYPublicKey* aPubKey);
+
+ // Structured clone methods use these to clone keys
+ bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
+ bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+private:
+ ~CryptoKey();
+
+ RefPtr<nsIGlobalObject> mGlobal;
+ uint32_t mAttributes; // see above
+ KeyAlgorithmProxy mAlgorithm;
+
+ // Only one key handle should be set, according to the KeyType
+ CryptoBuffer mSymKey;
+ ScopedSECKEYPrivateKey mPrivateKey;
+ ScopedSECKEYPublicKey mPublicKey;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CryptoKey_h
diff --git a/dom/crypto/KeyAlgorithmProxy.cpp b/dom/crypto/KeyAlgorithmProxy.cpp
new file mode 100644
index 000000000..22e12cfc8
--- /dev/null
+++ b/dom/crypto/KeyAlgorithmProxy.cpp
@@ -0,0 +1,241 @@
+/* -*- 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 "mozilla/dom/KeyAlgorithmProxy.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+
+namespace mozilla {
+namespace dom {
+
+bool
+KeyAlgorithmProxy::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
+{
+ if (!WriteString(aWriter, mName) ||
+ !JS_WriteUint32Pair(aWriter, mType, KEY_ALGORITHM_SC_VERSION)) {
+ return false;
+ }
+
+ switch (mType) {
+ case AES:
+ return JS_WriteUint32Pair(aWriter, mAes.mLength, 0);
+ case HMAC:
+ return JS_WriteUint32Pair(aWriter, mHmac.mLength, 0) &&
+ WriteString(aWriter, mHmac.mHash.mName);
+ case RSA: {
+ return JS_WriteUint32Pair(aWriter, mRsa.mModulusLength, 0) &&
+ WriteBuffer(aWriter, mRsa.mPublicExponent) &&
+ WriteString(aWriter, mRsa.mHash.mName);
+ }
+ case EC:
+ return WriteString(aWriter, mEc.mNamedCurve);
+ case DH: {
+ return WriteBuffer(aWriter, mDh.mPrime) &&
+ WriteBuffer(aWriter, mDh.mGenerator);
+ }
+ }
+
+ return false;
+}
+
+bool
+KeyAlgorithmProxy::ReadStructuredClone(JSStructuredCloneReader* aReader)
+{
+ uint32_t type, version, dummy;
+ if (!ReadString(aReader, mName) ||
+ !JS_ReadUint32Pair(aReader, &type, &version)) {
+ return false;
+ }
+
+ if (version != KEY_ALGORITHM_SC_VERSION) {
+ return false;
+ }
+
+ mType = (KeyAlgorithmType) type;
+ switch (mType) {
+ case AES: {
+ uint32_t length;
+ if (!JS_ReadUint32Pair(aReader, &length, &dummy)) {
+ return false;
+ }
+
+ mAes.mLength = length;
+ mAes.mName = mName;
+ return true;
+ }
+ case HMAC: {
+ if (!JS_ReadUint32Pair(aReader, &mHmac.mLength, &dummy) ||
+ !ReadString(aReader, mHmac.mHash.mName)) {
+ return false;
+ }
+
+ mHmac.mName = mName;
+ return true;
+ }
+ case RSA: {
+ uint32_t modulusLength;
+ nsString hashName;
+ if (!JS_ReadUint32Pair(aReader, &modulusLength, &dummy) ||
+ !ReadBuffer(aReader, mRsa.mPublicExponent) ||
+ !ReadString(aReader, mRsa.mHash.mName)) {
+ return false;
+ }
+
+ mRsa.mModulusLength = modulusLength;
+ mRsa.mName = mName;
+ return true;
+ }
+ case EC: {
+ nsString namedCurve;
+ if (!ReadString(aReader, mEc.mNamedCurve)) {
+ return false;
+ }
+
+ mEc.mName = mName;
+ return true;
+ }
+ case DH: {
+ if (!ReadBuffer(aReader, mDh.mPrime) ||
+ !ReadBuffer(aReader, mDh.mGenerator)) {
+ return false;
+ }
+
+ mDh.mName = mName;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+CK_MECHANISM_TYPE
+KeyAlgorithmProxy::Mechanism() const
+{
+ if (mType == HMAC) {
+ return GetMechanism(mHmac);
+ }
+ return MapAlgorithmNameToMechanism(mName);
+}
+
+nsString
+KeyAlgorithmProxy::JwkAlg() const
+{
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
+ switch (mAes.mLength) {
+ case 128: return NS_LITERAL_STRING(JWK_ALG_A128CBC);
+ case 192: return NS_LITERAL_STRING(JWK_ALG_A192CBC);
+ case 256: return NS_LITERAL_STRING(JWK_ALG_A256CBC);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
+ switch (mAes.mLength) {
+ case 128: return NS_LITERAL_STRING(JWK_ALG_A128CTR);
+ case 192: return NS_LITERAL_STRING(JWK_ALG_A192CTR);
+ case 256: return NS_LITERAL_STRING(JWK_ALG_A256CTR);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ switch (mAes.mLength) {
+ case 128: return NS_LITERAL_STRING(JWK_ALG_A128GCM);
+ case 192: return NS_LITERAL_STRING(JWK_ALG_A192GCM);
+ case 256: return NS_LITERAL_STRING(JWK_ALG_A256GCM);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ switch (mAes.mLength) {
+ case 128: return NS_LITERAL_STRING(JWK_ALG_A128KW);
+ case 192: return NS_LITERAL_STRING(JWK_ALG_A192KW);
+ case 256: return NS_LITERAL_STRING(JWK_ALG_A256KW);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ nsString hashName = mHmac.mHash.mName;
+ if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ return NS_LITERAL_STRING(JWK_ALG_HS1);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return NS_LITERAL_STRING(JWK_ALG_HS256);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ return NS_LITERAL_STRING(JWK_ALG_HS384);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return NS_LITERAL_STRING(JWK_ALG_HS512);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ nsString hashName = mRsa.mHash.mName;
+ if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ return NS_LITERAL_STRING(JWK_ALG_RS1);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return NS_LITERAL_STRING(JWK_ALG_RS256);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ return NS_LITERAL_STRING(JWK_ALG_RS384);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return NS_LITERAL_STRING(JWK_ALG_RS512);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ nsString hashName = mRsa.mHash.mName;
+ if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_256);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_384);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_512);
+ }
+ }
+
+ if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ nsString hashName = mRsa.mHash.mName;
+ if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ return NS_LITERAL_STRING(JWK_ALG_PS1);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return NS_LITERAL_STRING(JWK_ALG_PS256);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ return NS_LITERAL_STRING(JWK_ALG_PS384);
+ } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return NS_LITERAL_STRING(JWK_ALG_PS512);
+ }
+ }
+
+ return nsString();
+}
+
+CK_MECHANISM_TYPE
+KeyAlgorithmProxy::GetMechanism(const KeyAlgorithm& aAlgorithm)
+{
+ // For everything but HMAC, the name determines the mechanism
+ // HMAC is handled by the specialization below
+ return MapAlgorithmNameToMechanism(aAlgorithm.mName);
+}
+
+CK_MECHANISM_TYPE
+KeyAlgorithmProxy::GetMechanism(const HmacKeyAlgorithm& aAlgorithm)
+{
+ // The use of HmacKeyAlgorithm doesn't completely prevent this
+ // method from being called with dictionaries that don't really
+ // represent HMAC key algorithms.
+ MOZ_ASSERT(aAlgorithm.mName.EqualsLiteral(WEBCRYPTO_ALG_HMAC));
+
+ CK_MECHANISM_TYPE hashMech;
+ hashMech = MapAlgorithmNameToMechanism(aAlgorithm.mHash.mName);
+
+ switch (hashMech) {
+ case CKM_SHA_1: return CKM_SHA_1_HMAC;
+ case CKM_SHA256: return CKM_SHA256_HMAC;
+ case CKM_SHA384: return CKM_SHA384_HMAC;
+ case CKM_SHA512: return CKM_SHA512_HMAC;
+ }
+ return UNKNOWN_CK_MECHANISM;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/crypto/KeyAlgorithmProxy.h b/dom/crypto/KeyAlgorithmProxy.h
new file mode 100644
index 000000000..2c4c302b4
--- /dev/null
+++ b/dom/crypto/KeyAlgorithmProxy.h
@@ -0,0 +1,175 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_KeyAlgorithmProxy_h
+#define mozilla_dom_KeyAlgorithmProxy_h
+
+#include "pk11pub.h"
+#include "js/StructuredClone.h"
+#include "mozilla/dom/KeyAlgorithmBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+
+#define KEY_ALGORITHM_SC_VERSION 0x00000001
+
+namespace mozilla {
+namespace dom {
+
+// A heap-safe variant of RsaHashedKeyAlgorithm
+// The only difference is that it uses CryptoBuffer instead of Uint8Array
+struct RsaHashedKeyAlgorithmStorage {
+ nsString mName;
+ KeyAlgorithm mHash;
+ uint16_t mModulusLength;
+ CryptoBuffer mPublicExponent;
+
+ bool
+ ToKeyAlgorithm(JSContext* aCx, RsaHashedKeyAlgorithm& aRsa) const
+ {
+ JS::Rooted<JSObject*> exponent(aCx, mPublicExponent.ToUint8Array(aCx));
+ if (!exponent) {
+ return false;
+ }
+
+ aRsa.mName = mName;
+ aRsa.mModulusLength = mModulusLength;
+ aRsa.mHash.mName = mHash.mName;
+ aRsa.mPublicExponent.Init(exponent);
+ aRsa.mPublicExponent.ComputeLengthAndData();
+
+ return true;
+ }
+};
+
+// A heap-safe variant of DhKeyAlgorithm
+// The only difference is that it uses CryptoBuffers instead of Uint8Arrays
+struct DhKeyAlgorithmStorage {
+ nsString mName;
+ CryptoBuffer mPrime;
+ CryptoBuffer mGenerator;
+
+ bool
+ ToKeyAlgorithm(JSContext* aCx, DhKeyAlgorithm& aDh) const
+ {
+ JS::Rooted<JSObject*> prime(aCx, mPrime.ToUint8Array(aCx));
+ if (!prime) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> generator(aCx, mGenerator.ToUint8Array(aCx));
+ if (!generator) {
+ return false;
+ }
+
+ aDh.mName = mName;
+ aDh.mPrime.Init(prime);
+ aDh.mPrime.ComputeLengthAndData();
+ aDh.mGenerator.Init(generator);
+ aDh.mGenerator.ComputeLengthAndData();
+
+ return true;
+ }
+};
+
+// This class encapuslates a KeyAlgorithm object, and adds several
+// methods that make WebCrypto operations simpler.
+struct KeyAlgorithmProxy
+{
+ enum KeyAlgorithmType {
+ AES,
+ HMAC,
+ RSA,
+ EC,
+ DH,
+ };
+ KeyAlgorithmType mType;
+
+ // Plain is always populated with the algorithm name
+ // Others are only populated for the corresponding key type
+ nsString mName;
+ AesKeyAlgorithm mAes;
+ HmacKeyAlgorithm mHmac;
+ RsaHashedKeyAlgorithmStorage mRsa;
+ EcKeyAlgorithm mEc;
+ DhKeyAlgorithmStorage mDh;
+
+ // Structured clone
+ bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
+ bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+ // Extract various forms of derived information
+ CK_MECHANISM_TYPE Mechanism() const;
+ nsString JwkAlg() const;
+
+ // And in static form for calling on raw KeyAlgorithm dictionaries
+ static CK_MECHANISM_TYPE GetMechanism(const KeyAlgorithm& aAlgorithm);
+ static CK_MECHANISM_TYPE GetMechanism(const HmacKeyAlgorithm& aAlgorithm);
+ static nsString GetJwkAlg(const KeyAlgorithm& aAlgorithm);
+
+ // Construction of the various algorithm types
+ void
+ MakeAes(const nsString& aName, uint32_t aLength)
+ {
+ mType = AES;
+ mName = aName;
+ mAes.mName = aName;
+ mAes.mLength = aLength;
+ }
+
+ void
+ MakeHmac(uint32_t aLength, const nsString& aHashName)
+ {
+ mType = HMAC;
+ mName = NS_LITERAL_STRING(WEBCRYPTO_ALG_HMAC);
+ mHmac.mName = NS_LITERAL_STRING(WEBCRYPTO_ALG_HMAC);
+ mHmac.mLength = aLength;
+ mHmac.mHash.mName = aHashName;
+ }
+
+ bool
+ MakeRsa(const nsString& aName, uint32_t aModulusLength,
+ const CryptoBuffer& aPublicExponent, const nsString& aHashName)
+ {
+ mType = RSA;
+ mName = aName;
+ mRsa.mName = aName;
+ mRsa.mModulusLength = aModulusLength;
+ mRsa.mHash.mName = aHashName;
+ if (!mRsa.mPublicExponent.Assign(aPublicExponent)) {
+ return false;
+ }
+ return true;
+ }
+
+ void
+ MakeEc(const nsString& aName, const nsString& aNamedCurve)
+ {
+ mType = EC;
+ mName = aName;
+ mEc.mName = aName;
+ mEc.mNamedCurve = aNamedCurve;
+ }
+
+ bool
+ MakeDh(const nsString& aName, const CryptoBuffer& aPrime,
+ const CryptoBuffer& aGenerator)
+ {
+ mType = DH;
+ mName = aName;
+ mDh.mName = aName;
+ if (!mDh.mPrime.Assign(aPrime)) {
+ return false;
+ }
+ if (!mDh.mGenerator.Assign(aGenerator)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_KeyAlgorithmProxy_h
diff --git a/dom/crypto/WebCryptoCommon.h b/dom/crypto/WebCryptoCommon.h
new file mode 100644
index 000000000..8eb5c20e1
--- /dev/null
+++ b/dom/crypto/WebCryptoCommon.h
@@ -0,0 +1,352 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebCryptoCommon_h
+#define mozilla_dom_WebCryptoCommon_h
+
+#include "js/StructuredClone.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "pk11pub.h"
+
+// WebCrypto algorithm names
+#define WEBCRYPTO_ALG_AES_CBC "AES-CBC"
+#define WEBCRYPTO_ALG_AES_CTR "AES-CTR"
+#define WEBCRYPTO_ALG_AES_GCM "AES-GCM"
+#define WEBCRYPTO_ALG_AES_KW "AES-KW"
+#define WEBCRYPTO_ALG_SHA1 "SHA-1"
+#define WEBCRYPTO_ALG_SHA256 "SHA-256"
+#define WEBCRYPTO_ALG_SHA384 "SHA-384"
+#define WEBCRYPTO_ALG_SHA512 "SHA-512"
+#define WEBCRYPTO_ALG_HMAC "HMAC"
+#define WEBCRYPTO_ALG_HKDF "HKDF"
+#define WEBCRYPTO_ALG_PBKDF2 "PBKDF2"
+#define WEBCRYPTO_ALG_RSASSA_PKCS1 "RSASSA-PKCS1-v1_5"
+#define WEBCRYPTO_ALG_RSA_OAEP "RSA-OAEP"
+#define WEBCRYPTO_ALG_RSA_PSS "RSA-PSS"
+#define WEBCRYPTO_ALG_ECDH "ECDH"
+#define WEBCRYPTO_ALG_ECDSA "ECDSA"
+#define WEBCRYPTO_ALG_DH "DH"
+
+// WebCrypto key formats
+#define WEBCRYPTO_KEY_FORMAT_RAW "raw"
+#define WEBCRYPTO_KEY_FORMAT_PKCS8 "pkcs8"
+#define WEBCRYPTO_KEY_FORMAT_SPKI "spki"
+#define WEBCRYPTO_KEY_FORMAT_JWK "jwk"
+
+// WebCrypto key types
+#define WEBCRYPTO_KEY_TYPE_PUBLIC "public"
+#define WEBCRYPTO_KEY_TYPE_PRIVATE "private"
+#define WEBCRYPTO_KEY_TYPE_SECRET "secret"
+
+// WebCrypto key usages
+#define WEBCRYPTO_KEY_USAGE_ENCRYPT "encrypt"
+#define WEBCRYPTO_KEY_USAGE_DECRYPT "decrypt"
+#define WEBCRYPTO_KEY_USAGE_SIGN "sign"
+#define WEBCRYPTO_KEY_USAGE_VERIFY "verify"
+#define WEBCRYPTO_KEY_USAGE_DERIVEKEY "deriveKey"
+#define WEBCRYPTO_KEY_USAGE_DERIVEBITS "deriveBits"
+#define WEBCRYPTO_KEY_USAGE_WRAPKEY "wrapKey"
+#define WEBCRYPTO_KEY_USAGE_UNWRAPKEY "unwrapKey"
+
+// WebCrypto named curves
+#define WEBCRYPTO_NAMED_CURVE_P256 "P-256"
+#define WEBCRYPTO_NAMED_CURVE_P384 "P-384"
+#define WEBCRYPTO_NAMED_CURVE_P521 "P-521"
+
+// JWK key types
+#define JWK_TYPE_SYMMETRIC "oct"
+#define JWK_TYPE_RSA "RSA"
+#define JWK_TYPE_EC "EC"
+
+// JWK algorithms
+#define JWK_ALG_A128CBC "A128CBC" // CBC
+#define JWK_ALG_A192CBC "A192CBC"
+#define JWK_ALG_A256CBC "A256CBC"
+#define JWK_ALG_A128CTR "A128CTR" // CTR
+#define JWK_ALG_A192CTR "A192CTR"
+#define JWK_ALG_A256CTR "A256CTR"
+#define JWK_ALG_A128GCM "A128GCM" // GCM
+#define JWK_ALG_A192GCM "A192GCM"
+#define JWK_ALG_A256GCM "A256GCM"
+#define JWK_ALG_A128KW "A128KW" // KW
+#define JWK_ALG_A192KW "A192KW"
+#define JWK_ALG_A256KW "A256KW"
+#define JWK_ALG_HS1 "HS1" // HMAC
+#define JWK_ALG_HS256 "HS256"
+#define JWK_ALG_HS384 "HS384"
+#define JWK_ALG_HS512 "HS512"
+#define JWK_ALG_RS1 "RS1" // RSASSA-PKCS1
+#define JWK_ALG_RS256 "RS256"
+#define JWK_ALG_RS384 "RS384"
+#define JWK_ALG_RS512 "RS512"
+#define JWK_ALG_RSA_OAEP "RSA-OAEP" // RSA-OAEP
+#define JWK_ALG_RSA_OAEP_256 "RSA-OAEP-256"
+#define JWK_ALG_RSA_OAEP_384 "RSA-OAEP-384"
+#define JWK_ALG_RSA_OAEP_512 "RSA-OAEP-512"
+#define JWK_ALG_PS1 "PS1" // RSA-PSS
+#define JWK_ALG_PS256 "PS256"
+#define JWK_ALG_PS384 "PS384"
+#define JWK_ALG_PS512 "PS512"
+#define JWK_ALG_ECDSA_P_256 "ES256"
+#define JWK_ALG_ECDSA_P_384 "ES384"
+#define JWK_ALG_ECDSA_P_521 "ES521"
+
+// JWK usages
+#define JWK_USE_ENC "enc"
+#define JWK_USE_SIG "sig"
+
+// Define an unknown mechanism type
+#define UNKNOWN_CK_MECHANISM CKM_VENDOR_DEFINED+1
+
+// python security/pkix/tools/DottedOIDToCode.py id-ecDH 1.3.132.112
+static const uint8_t id_ecDH[] = { 0x2b, 0x81, 0x04, 0x70 };
+const SECItem SEC_OID_DATA_EC_DH = { siBuffer, (unsigned char*)id_ecDH,
+ static_cast<unsigned int>(
+ mozilla::ArrayLength(id_ecDH)) };
+
+// python security/pkix/tools/DottedOIDToCode.py dhKeyAgreement 1.2.840.113549.1.3.1
+static const uint8_t dhKeyAgreement[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x03, 0x01
+};
+const SECItem SEC_OID_DATA_DH_KEY_AGREEMENT = { siBuffer,
+ (unsigned char*)dhKeyAgreement,
+ static_cast<unsigned int>(
+ mozilla::ArrayLength(dhKeyAgreement)) };
+
+namespace mozilla {
+namespace dom {
+
+// Helper functions for structured cloning
+inline bool
+ReadString(JSStructuredCloneReader* aReader, nsString& aString)
+{
+ bool read;
+ uint32_t nameLength, zero;
+ read = JS_ReadUint32Pair(aReader, &nameLength, &zero);
+ if (!read) {
+ return false;
+ }
+
+ aString.SetLength(nameLength);
+ size_t charSize = sizeof(nsString::char_type);
+ read = JS_ReadBytes(aReader, (void*) aString.BeginWriting(), nameLength * charSize);
+ if (!read) {
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+WriteString(JSStructuredCloneWriter* aWriter, const nsString& aString)
+{
+ size_t charSize = sizeof(nsString::char_type);
+ return JS_WriteUint32Pair(aWriter, aString.Length(), 0) &&
+ JS_WriteBytes(aWriter, aString.get(), aString.Length() * charSize);
+}
+
+inline bool
+ReadBuffer(JSStructuredCloneReader* aReader, CryptoBuffer& aBuffer)
+{
+ uint32_t length, zero;
+ bool ret = JS_ReadUint32Pair(aReader, &length, &zero);
+ if (!ret) {
+ return false;
+ }
+
+ if (length > 0) {
+ if (!aBuffer.SetLength(length, fallible)) {
+ return false;
+ }
+ ret = JS_ReadBytes(aReader, aBuffer.Elements(), aBuffer.Length());
+ }
+ return ret;
+}
+
+inline bool
+WriteBuffer(JSStructuredCloneWriter* aWriter, const uint8_t* aBuffer, size_t aLength)
+{
+ bool ret = JS_WriteUint32Pair(aWriter, aLength, 0);
+ if (ret && aLength > 0) {
+ ret = JS_WriteBytes(aWriter, aBuffer, aLength);
+ }
+ return ret;
+}
+
+inline bool
+WriteBuffer(JSStructuredCloneWriter* aWriter, const CryptoBuffer& aBuffer)
+{
+ return WriteBuffer(aWriter, aBuffer.Elements(), aBuffer.Length());
+}
+
+inline CK_MECHANISM_TYPE
+MapAlgorithmNameToMechanism(const nsString& aName)
+{
+ CK_MECHANISM_TYPE mechanism(UNKNOWN_CK_MECHANISM);
+
+ // Set mechanism based on algorithm name
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
+ mechanism = CKM_AES_CBC_PAD;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
+ mechanism = CKM_AES_CTR;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ mechanism = CKM_AES_GCM;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ mechanism = CKM_NSS_AES_KEY_WRAP;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ mechanism = CKM_SHA_1;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ mechanism = CKM_SHA256;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ mechanism = CKM_SHA384;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ mechanism = CKM_SHA512;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
+ mechanism = CKM_PKCS5_PBKD2;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ mechanism = CKM_RSA_PKCS;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ mechanism = CKM_RSA_PKCS_OAEP;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ mechanism = CKM_RSA_PKCS_PSS;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+ mechanism = CKM_ECDH1_DERIVE;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ mechanism = CKM_DH_PKCS_DERIVE;
+ }
+
+ return mechanism;
+}
+
+#define NORMALIZED_EQUALS(aTest, aConst) \
+ nsContentUtils::EqualsIgnoreASCIICase(aTest, NS_LITERAL_STRING(aConst))
+
+inline bool
+NormalizeToken(const nsString& aName, nsString& aDest)
+{
+ // Algorithm names
+ if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_AES_CBC)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_AES_CBC);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_AES_CTR)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_AES_CTR);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_AES_GCM)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_AES_GCM);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_AES_KW)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_AES_KW);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA1)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_SHA1);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA256)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_SHA256);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA384)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_SHA384);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA512)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_SHA512);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HMAC)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_HMAC);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HKDF)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_HKDF);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_PBKDF2)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_PBKDF2);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_OAEP)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_OAEP);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_PSS)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_PSS);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDH)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_ECDH);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDSA)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_ECDSA);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_DH)) {
+ aDest.AssignLiteral(WEBCRYPTO_ALG_DH);
+ // Named curve values
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P256)) {
+ aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P384)) {
+ aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384);
+ } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P521)) {
+ aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+CheckEncodedECParameters(const SECItem* aEcParams)
+{
+ // Need at least two bytes for a valid ASN.1 encoding.
+ if (aEcParams->len < 2) {
+ return false;
+ }
+
+ // Check the ASN.1 tag.
+ if (aEcParams->data[0] != SEC_ASN1_OBJECT_ID) {
+ return false;
+ }
+
+ // OID tags are short, we never need more than one length byte.
+ if (aEcParams->data[1] >= 128) {
+ return false;
+ }
+
+ // Check that the SECItem's length is correct.
+ if (aEcParams->len != (unsigned)aEcParams->data[1] + 2) {
+ return false;
+ }
+
+ return true;
+}
+
+inline SECItem*
+CreateECParamsForCurve(const nsString& aNamedCurve, PLArenaPool* aArena)
+{
+ MOZ_ASSERT(aArena);
+ SECOidTag curveOIDTag;
+
+ if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) {
+ curveOIDTag = SEC_OID_SECG_EC_SECP256R1;
+ } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) {
+ curveOIDTag = SEC_OID_SECG_EC_SECP384R1;
+ } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) {
+ curveOIDTag = SEC_OID_SECG_EC_SECP521R1;
+ } else {
+ return nullptr;
+ }
+
+ // Retrieve curve data by OID tag.
+ SECOidData* oidData = SECOID_FindOIDByTag(curveOIDTag);
+ if (!oidData) {
+ return nullptr;
+ }
+
+ // Create parameters.
+ SECItem* params = ::SECITEM_AllocItem(aArena, nullptr, 2 + oidData->oid.len);
+ if (!params) {
+ return nullptr;
+ }
+
+ // Set parameters.
+ params->data[0] = SEC_ASN1_OBJECT_ID;
+ params->data[1] = oidData->oid.len;
+ memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);
+
+ // Sanity check the params we just created.
+ if (!CheckEncodedECParameters(params)) {
+ return nullptr;
+ }
+
+ return params;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebCryptoCommon_h
diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp
new file mode 100644
index 000000000..57a7da186
--- /dev/null
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -0,0 +1,3736 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "pk11pub.h"
+#include "cryptohi.h"
+#include "secerr.h"
+#include "ScopedNSSTypes.h"
+#include "nsNSSComponent.h"
+#include "nsProxyRelease.h"
+
+#include "jsapi.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/dom/KeyAlgorithmProxy.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/dom/WebCryptoTask.h"
+#include "mozilla/dom/WebCryptoThreadPool.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+
+// Template taken from security/nss/lib/util/templates.c
+// This (or SGN_EncodeDigestInfo) would ideally be exported
+// by NSS and until that happens we have to keep our own copy.
+const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(SGNDigestInfo) },
+ { SEC_ASN1_INLINE,
+ offsetof(SGNDigestInfo,digestAlgorithm),
+ SEC_ASN1_GET(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING,
+ offsetof(SGNDigestInfo,digest) },
+ { 0, }
+};
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::dom::workers::Canceling;
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::Status;
+using mozilla::dom::workers::WorkerHolder;
+using mozilla::dom::workers::WorkerPrivate;
+
+// Pre-defined identifiers for telemetry histograms
+
+enum TelemetryMethod {
+ TM_ENCRYPT = 0,
+ TM_DECRYPT = 1,
+ TM_SIGN = 2,
+ TM_VERIFY = 3,
+ TM_DIGEST = 4,
+ TM_GENERATEKEY = 5,
+ TM_DERIVEKEY = 6,
+ TM_DERIVEBITS = 7,
+ TM_IMPORTKEY = 8,
+ TM_EXPORTKEY = 9,
+ TM_WRAPKEY = 10,
+ TM_UNWRAPKEY = 11
+};
+
+enum TelemetryAlgorithm {
+ // Please make additions at the end of the list,
+ // to preserve comparability of histograms over time
+ TA_UNKNOWN = 0,
+ // encrypt / decrypt
+ TA_AES_CBC = 1,
+ TA_AES_CFB = 2,
+ TA_AES_CTR = 3,
+ TA_AES_GCM = 4,
+ TA_RSAES_PKCS1 = 5, // NB: This algorithm has been removed
+ TA_RSA_OAEP = 6,
+ // sign/verify
+ TA_RSASSA_PKCS1 = 7,
+ TA_RSA_PSS = 8,
+ TA_HMAC_SHA_1 = 9,
+ TA_HMAC_SHA_224 = 10,
+ TA_HMAC_SHA_256 = 11,
+ TA_HMAC_SHA_384 = 12,
+ TA_HMAC_SHA_512 = 13,
+ // digest
+ TA_SHA_1 = 14,
+ TA_SHA_224 = 15,
+ TA_SHA_256 = 16,
+ TA_SHA_384 = 17,
+ TA_SHA_512 = 18,
+ // Later additions
+ TA_AES_KW = 19,
+ TA_ECDH = 20,
+ TA_PBKDF2 = 21,
+ TA_ECDSA = 22,
+ TA_HKDF = 23,
+};
+
+// Convenience functions for extracting / converting information
+
+// OOM-safe CryptoBuffer initialization, suitable for constructors
+#define ATTEMPT_BUFFER_INIT(dst, src) \
+ if (!dst.Assign(src)) { \
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
+ return; \
+ }
+
+// OOM-safe CryptoBuffer-to-SECItem copy, suitable for DoCrypto
+#define ATTEMPT_BUFFER_TO_SECITEM(arena, dst, src) \
+ if (!src.ToSECItem(arena, dst)) { \
+ return NS_ERROR_DOM_UNKNOWN_ERR; \
+ }
+
+// OOM-safe CryptoBuffer copy, suitable for DoCrypto
+#define ATTEMPT_BUFFER_ASSIGN(dst, src) \
+ if (!dst.Assign(src)) { \
+ return NS_ERROR_DOM_UNKNOWN_ERR; \
+ }
+
+// Safety check for algorithms that use keys, suitable for constructors
+#define CHECK_KEY_ALGORITHM(keyAlg, algName) \
+ { \
+ if (!NORMALIZED_EQUALS(keyAlg.mName, algName)) { \
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; \
+ return; \
+ } \
+ }
+
+class ClearException
+{
+public:
+ explicit ClearException(JSContext* aCx)
+ : mCx(aCx)
+ {}
+
+ ~ClearException()
+ {
+ JS_ClearPendingException(mCx);
+ }
+
+private:
+ JSContext* mCx;
+};
+
+class WebCryptoTask::InternalWorkerHolder final : public WorkerHolder
+{
+ InternalWorkerHolder()
+ { }
+
+ ~InternalWorkerHolder()
+ {
+ NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+ // Nothing to do here since the parent destructor releases the
+ // worker automatically.
+ }
+
+public:
+ static already_AddRefed<InternalWorkerHolder>
+ Create()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ RefPtr<InternalWorkerHolder> ref = new InternalWorkerHolder();
+ if (NS_WARN_IF(!ref->HoldWorker(workerPrivate, Canceling))) {
+ return nullptr;
+ }
+ return ref.forget();
+ }
+
+ virtual bool
+ Notify(Status aStatus) override
+ {
+ NS_ASSERT_OWNINGTHREAD(InternalWorkerHolder);
+ // Do nothing here. Since WebCryptoTask dispatches back to
+ // the worker thread using nsThread::Dispatch() instead of
+ // WorkerRunnable it will always be able to execute its
+ // runnables.
+ return true;
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(WebCryptoTask::InternalWorkerHolder)
+};
+
+template<class OOS>
+static nsresult
+GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm, nsString& aName)
+{
+ ClearException ce(aCx);
+
+ if (aAlgorithm.IsString()) {
+ // If string, then treat as algorithm name
+ aName.Assign(aAlgorithm.GetAsString());
+ } else {
+ // Coerce to algorithm and extract name
+ JS::RootedValue value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
+ Algorithm alg;
+
+ if (!alg.Init(aCx, value)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ aName = alg.mName;
+ }
+
+ if (!NormalizeToken(aName, aName)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+template<class T, class OOS>
+static nsresult
+Coerce(JSContext* aCx, T& aTarget, const OOS& aAlgorithm)
+{
+ ClearException ce(aCx);
+
+ if (!aAlgorithm.IsObject()) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ JS::RootedValue value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
+ if (!aTarget.Init(aCx, value)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+inline size_t
+MapHashAlgorithmNameToBlockSize(const nsString& aName)
+{
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1) ||
+ aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return 512;
+ }
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384) ||
+ aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return 1024;
+ }
+
+ return 0;
+}
+
+inline nsresult
+GetKeyLengthForAlgorithm(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ size_t& aLength)
+{
+ aLength = 0;
+
+ // Extract algorithm name
+ nsString algName;
+ if (NS_FAILED(GetAlgorithmName(aCx, aAlgorithm, algName))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Read AES key length from given algorithm object.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ RootedDictionary<AesDerivedKeyParams> params(aCx);
+ if (NS_FAILED(Coerce(aCx, params, aAlgorithm))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (params.mLength != 128 &&
+ params.mLength != 192 &&
+ params.mLength != 256) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ aLength = params.mLength;
+ return NS_OK;
+ }
+
+ // Read HMAC key length from given algorithm object or
+ // determine key length as the block size of the given hash.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacDerivedKeyParams> params(aCx);
+ if (NS_FAILED(Coerce(aCx, params, aAlgorithm))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Return the passed length, if any.
+ if (params.mLength.WasPassed()) {
+ aLength = params.mLength.Value();
+ return NS_OK;
+ }
+
+ nsString hashName;
+ if (NS_FAILED(GetAlgorithmName(aCx, params.mHash, hashName))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Return the given hash algorithm's block size as the key length.
+ size_t length = MapHashAlgorithmNameToBlockSize(hashName);
+ if (length == 0) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ aLength = length;
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+inline bool
+MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult)
+{
+ switch (aOIDTag) {
+ case SEC_OID_SECG_EC_SECP256R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256);
+ break;
+ case SEC_OID_SECG_EC_SECP384R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384);
+ break;
+ case SEC_OID_SECG_EC_SECP521R1:
+ aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+inline SECOidTag
+MapHashAlgorithmNameToOID(const nsString& aName)
+{
+ SECOidTag hashOID(SEC_OID_UNKNOWN);
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ hashOID = SEC_OID_SHA1;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ hashOID = SEC_OID_SHA256;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ hashOID = SEC_OID_SHA384;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ hashOID = SEC_OID_SHA512;
+ }
+
+ return hashOID;
+}
+
+inline CK_MECHANISM_TYPE
+MapHashAlgorithmNameToMgfMechanism(const nsString& aName) {
+ CK_MECHANISM_TYPE mech(UNKNOWN_CK_MECHANISM);
+
+ if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ mech = CKG_MGF1_SHA1;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ mech = CKG_MGF1_SHA256;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ mech = CKG_MGF1_SHA384;
+ } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ mech = CKG_MGF1_SHA512;
+ }
+
+ return mech;
+}
+
+// Implementation of WebCryptoTask methods
+
+void
+WebCryptoTask::DispatchWithPromise(Promise* aResultPromise)
+{
+ mResultPromise = aResultPromise;
+
+ // Fail if an error was set during the constructor
+ MAYBE_EARLY_FAIL(mEarlyRv)
+
+ // Perform pre-NSS operations, and fail if they fail
+ mEarlyRv = BeforeCrypto();
+ MAYBE_EARLY_FAIL(mEarlyRv)
+
+ // Skip NSS if we're already done, or launch a CryptoTask
+ if (mEarlyComplete) {
+ CallCallback(mEarlyRv);
+ Skip();
+ return;
+ }
+
+ // Store calling thread
+ mOriginalThread = NS_GetCurrentThread();
+
+ // If we are running on a worker thread we must hold the worker
+ // alive while we work on the thread pool. Otherwise the worker
+ // private may get torn down before we dispatch back to complete
+ // the transaction.
+ if (!NS_IsMainThread()) {
+ mWorkerHolder = InternalWorkerHolder::Create();
+ // If we can't register a holder then the worker is already
+ // shutting down. Don't start new work.
+ if (!mWorkerHolder) {
+ mEarlyRv = NS_BINDING_ABORTED;
+ }
+ }
+ MAYBE_EARLY_FAIL(mEarlyRv);
+
+ // dispatch to thread pool
+ mEarlyRv = WebCryptoThreadPool::Dispatch(this);
+ MAYBE_EARLY_FAIL(mEarlyRv)
+}
+
+NS_IMETHODIMP
+WebCryptoTask::Run()
+{
+ // Run heavy crypto operations on the thread pool, off the original thread.
+ if (!IsOnOriginalThread()) {
+ nsNSSShutDownPreventionLock locker;
+
+ if (isAlreadyShutDown()) {
+ mRv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ mRv = CalculateResult();
+ }
+
+ // Back to the original thread, i.e. continue below.
+ mOriginalThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ // We're now back on the calling thread.
+
+ // Release NSS resources now, before calling CallCallback, so that
+ // WebCryptoTasks have consistent behavior regardless of whether NSS is shut
+ // down between CalculateResult being called and CallCallback being called.
+ virtualDestroyNSSReference();
+
+ CallCallback(mRv);
+
+ // Stop holding the worker thread alive now that the async work has
+ // been completed.
+ mWorkerHolder = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+WebCryptoTask::Cancel()
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ FailWithError(NS_BINDING_ABORTED);
+ return NS_OK;
+}
+
+void
+WebCryptoTask::FailWithError(nsresult aRv)
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
+
+ // Blindly convert nsresult to DOMException
+ // Individual tasks must ensure they pass the right values
+ mResultPromise->MaybeReject(aRv);
+ // Manually release mResultPromise while we're on the main thread
+ mResultPromise = nullptr;
+ mWorkerHolder = nullptr;
+ Cleanup();
+}
+
+nsresult
+WebCryptoTask::CalculateResult()
+{
+ MOZ_ASSERT(!IsOnOriginalThread());
+
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return DoCrypto();
+}
+
+void
+WebCryptoTask::CallCallback(nsresult rv)
+{
+ MOZ_ASSERT(IsOnOriginalThread());
+ if (NS_FAILED(rv)) {
+ FailWithError(rv);
+ return;
+ }
+
+ nsresult rv2 = AfterCrypto();
+ if (NS_FAILED(rv2)) {
+ FailWithError(rv2);
+ return;
+ }
+
+ Resolve();
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, true);
+
+ // Manually release mResultPromise while we're on the main thread
+ mResultPromise = nullptr;
+ Cleanup();
+}
+
+// Some generic utility classes
+
+class FailureTask : public WebCryptoTask
+{
+public:
+ explicit FailureTask(nsresult aRv) {
+ mEarlyRv = aRv;
+ }
+};
+
+class ReturnArrayBufferViewTask : public WebCryptoTask
+{
+protected:
+ CryptoBuffer mResult;
+
+private:
+ // Returns mResult as an ArrayBufferView, or an error
+ virtual void Resolve() override
+ {
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ }
+};
+
+class DeferredData
+{
+public:
+ template<class T>
+ void SetData(const T& aData) {
+ mDataIsSet = mData.Assign(aData);
+ }
+
+protected:
+ DeferredData()
+ : mDataIsSet(false)
+ {}
+
+ CryptoBuffer mData;
+ bool mDataIsSet;
+};
+
+class AesTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check that we got a reasonable key
+ if ((mSymKey.Length() != 16) &&
+ (mSymKey.Length() != 24) &&
+ (mSymKey.Length() != 32))
+ {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Cache parameters depending on the specific algorithm
+ TelemetryAlgorithm telemetryAlg;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_CBC);
+
+ mMechanism = CKM_AES_CBC_PAD;
+ telemetryAlg = TA_AES_CBC;
+ AesCbcParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mIv)
+ if (mIv.Length() != 16) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_CTR);
+
+ mMechanism = CKM_AES_CTR;
+ telemetryAlg = TA_AES_CTR;
+ AesCtrParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mCounter)
+ if (mIv.Length() != 16) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mCounterLength = params.mLength;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_GCM);
+
+ mMechanism = CKM_AES_GCM;
+ telemetryAlg = TA_AES_GCM;
+ AesGcmParams params;
+ nsresult rv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(rv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mIv, params.mIv)
+
+ if (params.mAdditionalData.WasPassed()) {
+ ATTEMPT_BUFFER_INIT(mAad, params.mAdditionalData.Value())
+ }
+
+ // 32, 64, 96, 104, 112, 120 or 128
+ mTagLength = 128;
+ if (params.mTagLength.WasPassed()) {
+ mTagLength = params.mTagLength.Value();
+ if ((mTagLength > 128) ||
+ !(mTagLength == 32 || mTagLength == 64 ||
+ (mTagLength >= 96 && mTagLength % 8 == 0))) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ CryptoBuffer mIv; // Initialization vector
+ CryptoBuffer mAad; // Additional Authenticated Data
+ uint8_t mTagLength;
+ uint8_t mCounterLength;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Construct the parameters object depending on algorithm
+ SECItem param = { siBuffer, nullptr, 0 };
+ CK_AES_CTR_PARAMS ctrParams;
+ CK_GCM_PARAMS gcmParams;
+ switch (mMechanism) {
+ case CKM_AES_CBC_PAD:
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &param, mIv);
+ break;
+ case CKM_AES_CTR:
+ ctrParams.ulCounterBits = mCounterLength;
+ MOZ_ASSERT(mIv.Length() == 16);
+ memcpy(&ctrParams.cb, mIv.Elements(), 16);
+ param.type = siBuffer;
+ param.data = (unsigned char*) &ctrParams;
+ param.len = sizeof(ctrParams);
+ break;
+ case CKM_AES_GCM:
+ gcmParams.pIv = mIv.Elements();
+ gcmParams.ulIvLen = mIv.Length();
+ gcmParams.pAAD = mAad.Elements();
+ gcmParams.ulAADLen = mAad.Length();
+ gcmParams.ulTagBits = mTagLength;
+ param.type = siBuffer;
+ param.data = (unsigned char*) &gcmParams;
+ param.len = sizeof(gcmParams);
+ break;
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Initialize the output buffer (enough space for padding / a full tag)
+ uint32_t dataLen = mData.Length();
+ uint32_t maxLen = dataLen + 16;
+ if (!mResult.SetLength(maxLen, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ uint32_t outLen = 0;
+
+ // Perform the encryption/decryption
+ if (mEncrypt) {
+ rv = MapSECStatus(PK11_Encrypt(symKey.get(), mMechanism, &param,
+ mResult.Elements(), &outLen,
+ mResult.Length(), mData.Elements(),
+ mData.Length()));
+ } else {
+ rv = MapSECStatus(PK11_Decrypt(symKey.get(), mMechanism, &param,
+ mResult.Elements(), &outLen,
+ mResult.Length(), mData.Elements(),
+ mData.Length()));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return rv;
+ }
+};
+
+// This class looks like an encrypt/decrypt task, like AesTask,
+// but it is only exposed to wrapKey/unwrapKey, not encrypt/decrypt
+class AesKwTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mMechanism(CKM_NSS_AES_KEY_WRAP)
+ , mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mMechanism(CKM_NSS_AES_KEY_WRAP)
+ , mSymKey(aKey.GetSymKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_KW);
+
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check that we got a reasonable key
+ if ((mSymKey.Length() != 16) &&
+ (mSymKey.Length() != 24) &&
+ (mSymKey.Length() != 32))
+ {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_AES_KW);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Check that the input is a multiple of 64 bits long
+ if (mData.Length() == 0 || mData.Length() % 8 != 0) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_WRAP, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Import the data to a SECItem
+ SECItem dataItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &dataItem, mData);
+
+ // Parameters for the fake keys
+ CK_MECHANISM_TYPE fakeMechanism = CKM_SHA_1_HMAC;
+ CK_ATTRIBUTE_TYPE fakeOperation = CKA_SIGN;
+
+ if (mEncrypt) {
+ // Import the data into a fake PK11SymKey structure
+ ScopedPK11SymKey keyToWrap(PK11_ImportSymKey(slot, fakeMechanism,
+ PK11_OriginUnwrap, fakeOperation,
+ &dataItem, nullptr));
+ if (!keyToWrap) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Encrypt and return the wrapped key
+ // AES-KW encryption results in a wrapped key 64 bits longer
+ if (!mResult.SetLength(mData.Length() + 8, fallible)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ SECItem resultItem = {siBuffer, mResult.Elements(),
+ (unsigned int) mResult.Length()};
+ rv = MapSECStatus(PK11_WrapSymKey(mMechanism, nullptr, symKey.get(),
+ keyToWrap.get(), &resultItem));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ } else {
+ // Decrypt the ciphertext into a temporary PK11SymKey
+ // Unwrapped key should be 64 bits shorter
+ int keySize = mData.Length() - 8;
+ ScopedPK11SymKey unwrappedKey(PK11_UnwrapSymKey(symKey, mMechanism, nullptr,
+ &dataItem, fakeMechanism,
+ fakeOperation, keySize));
+ if (!unwrappedKey) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Export the key to get the cleartext
+ rv = MapSECStatus(PK11_ExtractKeyValue(unwrappedKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(unwrappedKey));
+ }
+
+ return rv;
+ }
+};
+
+class RsaOaepTask : public ReturnArrayBufferViewTask,
+ public DeferredData
+{
+public:
+ RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ : mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ }
+
+ RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const CryptoOperationData& aData,
+ bool aEncrypt)
+ : mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mEncrypt(aEncrypt)
+ {
+ Init(aCx, aAlgorithm, aKey, aEncrypt);
+ SetData(aData);
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, bool aEncrypt)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_OAEP);
+
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_OAEP);
+
+ if (mEncrypt) {
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ mStrength = SECKEY_PublicKeyStrength(mPubKey);
+ } else {
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ mStrength = PK11_GetPrivateModulusLen(mPrivKey);
+ }
+
+ // The algorithm could just be given as a string
+ // in which case there would be no label specified.
+ if (!aAlgorithm.IsString()) {
+ RootedDictionary<RsaOaepParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (params.mLabel.WasPassed()) {
+ ATTEMPT_BUFFER_INIT(mLabel, params.mLabel.Value());
+ }
+ }
+ // Otherwise mLabel remains the empty octet string, as intended
+
+ KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+ mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+ mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlg.mName);
+
+ // Check we found appropriate mechanisms.
+ if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+ mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+
+private:
+ CK_MECHANISM_TYPE mHashMechanism;
+ CK_MECHANISM_TYPE mMgfMechanism;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+ CryptoBuffer mLabel;
+ uint32_t mStrength;
+ bool mEncrypt;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsresult rv;
+
+ if (!mDataIsSet) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Ciphertext is an integer mod the modulus, so it will be
+ // no longer than mStrength octets
+ if (!mResult.SetLength(mStrength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ CK_RSA_PKCS_OAEP_PARAMS oaepParams;
+ oaepParams.source = CKZ_DATA_SPECIFIED;
+
+ oaepParams.pSourceData = mLabel.Length() ? mLabel.Elements() : nullptr;
+ oaepParams.ulSourceDataLen = mLabel.Length();
+
+ oaepParams.mgf = mMgfMechanism;
+ oaepParams.hashAlg = mHashMechanism;
+
+ SECItem param;
+ param.type = siBuffer;
+ param.data = (unsigned char*) &oaepParams;
+ param.len = sizeof(oaepParams);
+
+ uint32_t outLen = 0;
+ if (mEncrypt) {
+ // PK11_PubEncrypt() checks the plaintext's length and fails if it is too
+ // long to encrypt, i.e. if it is longer than (k - 2hLen - 2) with 'k'
+ // being the length in octets of the RSA modulus n and 'hLen' being the
+ // output length in octets of the chosen hash function.
+ // <https://tools.ietf.org/html/rfc3447#section-7.1>
+ rv = MapSECStatus(PK11_PubEncrypt(
+ mPubKey.get(), CKM_RSA_PKCS_OAEP, &param,
+ mResult.Elements(), &outLen, mResult.Length(),
+ mData.Elements(), mData.Length(), nullptr));
+ } else {
+ rv = MapSECStatus(PK11_PrivDecrypt(
+ mPrivKey.get(), CKM_RSA_PKCS_OAEP, &param,
+ mResult.Elements(), &outLen, mResult.Length(),
+ mData.Elements(), mData.Length()));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return NS_OK;
+ }
+};
+
+class HmacTask : public WebCryptoTask
+{
+public:
+ HmacTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+ : mMechanism(aKey.Algorithm().Mechanism())
+ , mSymKey(aKey.GetSymKey())
+ , mSign(aSign)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HMAC);
+
+ ATTEMPT_BUFFER_INIT(mData, aData);
+ if (!aSign) {
+ ATTEMPT_BUFFER_INIT(mSignature, aSignature);
+ }
+
+ // Check that we got a symmetric key
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ TelemetryAlgorithm telemetryAlg;
+ switch (mMechanism) {
+ case CKM_SHA_1_HMAC: telemetryAlg = TA_HMAC_SHA_1; break;
+ case CKM_SHA224_HMAC: telemetryAlg = TA_HMAC_SHA_224; break;
+ case CKM_SHA256_HMAC: telemetryAlg = TA_HMAC_SHA_256; break;
+ case CKM_SHA384_HMAC: telemetryAlg = TA_HMAC_SHA_384; break;
+ case CKM_SHA512_HMAC: telemetryAlg = TA_HMAC_SHA_512; break;
+ default: telemetryAlg = TA_UNKNOWN;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ }
+
+private:
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mSymKey;
+ CryptoBuffer mData;
+ CryptoBuffer mSignature;
+ CryptoBuffer mResult;
+ bool mSign;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Initialize the output buffer
+ if (!mResult.SetLength(HASH_LENGTH_MAX, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ uint32_t outLen;
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ ScopedPK11SymKey symKey(PK11_ImportSymKey(slot, mMechanism, PK11_OriginUnwrap,
+ CKA_SIGN, &keyItem, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ // Compute the MAC
+ SECItem param = { siBuffer, nullptr, 0 };
+ UniquePK11Context ctx(PK11_CreateContextBySymKey(mMechanism, CKA_SIGN,
+ symKey.get(), &param));
+ if (!ctx.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ nsresult rv = MapSECStatus(PK11_DigestBegin(ctx.get()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = MapSECStatus(PK11_DigestOp(ctx.get(), mData.Elements(), mData.Length()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = MapSECStatus(PK11_DigestFinal(ctx.get(), mResult.Elements(),
+ &outLen, mResult.Length()));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ mResult.TruncateLength(outLen);
+ return rv;
+ }
+
+ // Returns mResult as an ArrayBufferView, or an error
+ virtual void Resolve() override
+ {
+ if (mSign) {
+ // Return the computed MAC
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ } else {
+ // Compare the MAC to the provided signature
+ // No truncation allowed
+ bool equal = (mResult.Length() == mSignature.Length());
+ if (equal) {
+ int cmp = NSS_SecureMemcmp(mSignature.Elements(),
+ mResult.Elements(),
+ mSignature.Length());
+ equal = (cmp == 0);
+ }
+ mResultPromise->MaybeResolve(equal);
+ }
+ }
+};
+
+class AsymmetricSignVerifyTask : public WebCryptoTask
+{
+public:
+ AsymmetricSignVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+ : mOidTag(SEC_OID_UNKNOWN)
+ , mHashMechanism(UNKNOWN_CK_MECHANISM)
+ , mMgfMechanism(UNKNOWN_CK_MECHANISM)
+ , mPrivKey(aKey.GetPrivateKey())
+ , mPubKey(aKey.GetPublicKey())
+ , mSaltLength(0)
+ , mSign(aSign)
+ , mVerified(false)
+ , mAlgorithm(Algorithm::UNKNOWN)
+ {
+ ATTEMPT_BUFFER_INIT(mData, aData);
+ if (!aSign) {
+ ATTEMPT_BUFFER_INIT(mSignature, aSignature);
+ }
+
+ nsString algName;
+ nsString hashAlgName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ mAlgorithm = Algorithm::RSA_PKCS1;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
+ hashAlgName = aKey.Algorithm().mRsa.mHash.mName;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ mAlgorithm = Algorithm::RSA_PSS;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS);
+
+ KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+ hashAlgName = hashAlg.mName;
+ mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+ mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName);
+
+ // Check we found appropriate mechanisms.
+ if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+ mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ RootedDictionary<RsaPssParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ mSaltLength = params.mSaltLength;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ mAlgorithm = Algorithm::ECDSA;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA);
+
+ // For ECDSA, the hash name comes from the algorithm parameter
+ RootedDictionary<EcdsaParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ } else {
+ // This shouldn't happen; CreateSignVerifyTask shouldn't create
+ // one of these unless it's for the above algorithms.
+ MOZ_ASSERT(false);
+ }
+
+ // Must have a valid algorithm by now.
+ MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN);
+
+ // Determine hash algorithm to use.
+ mOidTag = MapHashAlgorithmNameToOID(hashAlgName);
+ if (mOidTag == SEC_OID_UNKNOWN) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Check that we have the appropriate key
+ if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ }
+
+private:
+ SECOidTag mOidTag;
+ CK_MECHANISM_TYPE mHashMechanism;
+ CK_MECHANISM_TYPE mMgfMechanism;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+ CryptoBuffer mSignature;
+ CryptoBuffer mData;
+ uint32_t mSaltLength;
+ bool mSign;
+ bool mVerified;
+
+ // The signature algorithm to use.
+ enum class Algorithm: uint8_t {ECDSA, RSA_PKCS1, RSA_PSS, UNKNOWN};
+ Algorithm mAlgorithm;
+
+ virtual nsresult DoCrypto() override
+ {
+ SECStatus rv;
+ ScopedSECItem hash(::SECITEM_AllocItem(nullptr, nullptr,
+ HASH_ResultLenByOidTag(mOidTag)));
+ if (!hash) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Compute digest over given data.
+ rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(), mData.Length());
+ NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+
+ // Wrap hash in a digest info template (RSA-PKCS1 only).
+ if (mAlgorithm == Algorithm::RSA_PKCS1) {
+ ScopedSGNDigestInfo di(SGN_CreateDigestInfo(mOidTag, hash->data, hash->len));
+ if (!di) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Reuse |hash|.
+ SECITEM_FreeItem(hash, false);
+ if (!SEC_ASN1EncodeItem(nullptr, hash, di, SGN_DigestInfoTemplate)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ SECItem* params = nullptr;
+ CK_MECHANISM_TYPE mech = PK11_MapSignKeyType((mSign ? mPrivKey->keyType :
+ mPubKey->keyType));
+
+ CK_RSA_PKCS_PSS_PARAMS rsaPssParams;
+ SECItem rsaPssParamsItem = { siBuffer, };
+
+ // Set up parameters for RSA-PSS.
+ if (mAlgorithm == Algorithm::RSA_PSS) {
+ rsaPssParams.hashAlg = mHashMechanism;
+ rsaPssParams.mgf = mMgfMechanism;
+ rsaPssParams.sLen = mSaltLength;
+
+ rsaPssParamsItem.data = (unsigned char*)&rsaPssParams;
+ rsaPssParamsItem.len = sizeof(rsaPssParams);
+ params = &rsaPssParamsItem;
+
+ mech = CKM_RSA_PKCS_PSS;
+ }
+
+ // Allocate SECItem to hold the signature.
+ uint32_t len = mSign ? PK11_SignatureLen(mPrivKey) : 0;
+ ScopedSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
+ if (!sig) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (mSign) {
+ // Sign the hash.
+ rv = PK11_SignWithMechanism(mPrivKey, mech, params, sig, hash);
+ NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+ ATTEMPT_BUFFER_ASSIGN(mSignature, sig);
+ } else {
+ // Copy the given signature to the SECItem.
+ if (!mSignature.ToSECItem(nullptr, sig)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Verify the signature.
+ rv = PK11_VerifyWithMechanism(mPubKey, mech, params, sig, hash, nullptr);
+ mVerified = NS_SUCCEEDED(MapSECStatus(rv));
+ }
+
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ if (mSign) {
+ TypedArrayCreator<ArrayBuffer> ret(mSignature);
+ mResultPromise->MaybeResolve(ret);
+ } else {
+ mResultPromise->MaybeResolve(mVerified);
+ }
+ }
+};
+
+class DigestTask : public ReturnArrayBufferViewTask
+{
+public:
+ DigestTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData)
+ {
+ ATTEMPT_BUFFER_INIT(mData, aData);
+
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ TelemetryAlgorithm telemetryAlg;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+ telemetryAlg = TA_SHA_1;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ telemetryAlg = TA_SHA_224;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+ telemetryAlg = TA_SHA_256;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ telemetryAlg = TA_SHA_384;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+ mOidTag = MapHashAlgorithmNameToOID(algName);
+ }
+
+private:
+ SECOidTag mOidTag;
+ CryptoBuffer mData;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Resize the result buffer
+ uint32_t hashLen = HASH_ResultLenByOidTag(mOidTag);
+ if (!mResult.SetLength(hashLen, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // Compute the hash
+ nsresult rv = MapSECStatus(PK11_HashBuf(mOidTag, mResult.Elements(),
+ mData.Elements(), mData.Length()));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return rv;
+ }
+};
+
+class ImportKeyTask : public WebCryptoTask
+{
+public:
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ mFormat = aFormat;
+ mDataIsSet = false;
+ mDataIsJwk = false;
+
+ // This stuff pretty much always happens, so we'll do it here
+ mKey = new CryptoKey(aGlobal);
+ mKey->SetExtractable(aExtractable);
+ mKey->ClearUsages();
+ for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKey->AddUsage(aKeyUsages[i]);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+ static bool JwkCompatible(const JsonWebKey& aJwk, const CryptoKey* aKey)
+ {
+ // Check 'ext'
+ if (aKey->Extractable() &&
+ aJwk.mExt.WasPassed() && !aJwk.mExt.Value()) {
+ return false;
+ }
+
+ // Check 'alg'
+ if (aJwk.mAlg.WasPassed() &&
+ aJwk.mAlg.Value() != aKey->Algorithm().JwkAlg()) {
+ return false;
+ }
+
+ // Check 'key_ops'
+ if (aJwk.mKey_ops.WasPassed()) {
+ nsTArray<nsString> usages;
+ aKey->GetUsages(usages);
+ for (size_t i = 0; i < usages.Length(); ++i) {
+ if (!aJwk.mKey_ops.Value().Contains(usages[i])) {
+ return false;
+ }
+ }
+ }
+
+ // Individual algorithms may still have to check 'use'
+ return true;
+ }
+
+ void SetKeyData(JSContext* aCx, JS::Handle<JSObject*> aKeyData)
+ {
+ mDataIsJwk = false;
+
+ // Try ArrayBuffer
+ RootedTypedArray<ArrayBuffer> ab(aCx);
+ if (ab.Init(aKeyData)) {
+ if (!mKeyData.Assign(ab)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return;
+ }
+
+ // Try ArrayBufferView
+ RootedTypedArray<ArrayBufferView> abv(aCx);
+ if (abv.Init(aKeyData)) {
+ if (!mKeyData.Assign(abv)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return;
+ }
+
+ // Try JWK
+ ClearException ce(aCx);
+ JS::RootedValue value(aCx, JS::ObjectValue(*aKeyData));
+ if (!mJwk.Init(aCx, value)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mDataIsJwk = true;
+ }
+
+ void SetKeyDataMaybeParseJWK(const CryptoBuffer& aKeyData)
+ {
+ if (!mKeyData.Assign(aKeyData)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ mDataIsJwk = false;
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ nsDependentCSubstring utf8((const char*) mKeyData.Elements(),
+ (const char*) (mKeyData.Elements() +
+ mKeyData.Length()));
+ if (!IsUTF8(utf8)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ nsString json = NS_ConvertUTF8toUTF16(utf8);
+ if (!mJwk.Init(json)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mDataIsJwk = true;
+ }
+ }
+
+ void SetRawKeyData(const CryptoBuffer& aKeyData)
+ {
+ if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ if (!mKeyData.Assign(aKeyData)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+
+ mDataIsJwk = false;
+ }
+
+protected:
+ nsString mFormat;
+ RefPtr<CryptoKey> mKey;
+ CryptoBuffer mKeyData;
+ bool mDataIsSet;
+ bool mDataIsJwk;
+ JsonWebKey mJwk;
+ nsString mAlgName;
+
+private:
+ virtual void Resolve() override
+ {
+ mResultPromise->MaybeResolve(mKey);
+ }
+
+ virtual void Cleanup() override
+ {
+ mKey = nullptr;
+ }
+};
+
+
+class ImportSymmetricKeyTask : public ImportKeyTask
+{
+public:
+ ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // This task only supports raw and JWK format.
+ if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // If this is an HMAC key, import the hash name
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+ }
+
+ virtual nsresult BeforeCrypto() override
+ {
+ nsresult rv;
+
+ // If we're doing a JWK import, import the key data
+ if (mDataIsJwk) {
+ if (!mJwk.mK.WasPassed()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ // Import the key material
+ rv = mKeyData.FromJwkBase64(mJwk.mK.Value());
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ }
+
+ // Check that we have valid key data.
+ if (mKeyData.Length() == 0) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ // Construct an appropriate KeyAlorithm,
+ // and verify that usages are appropriate
+ uint32_t length = 8 * mKeyData.Length(); // bytes to bits
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::DECRYPT |
+ CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) &&
+ mKey->HasUsageOtherThan(CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if ( (length != 128) && (length != 192) && (length != 256) ) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ mKey->Algorithm().MakeAes(mAlgName, length);
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+ !mJwk.mUse.Value().EqualsLiteral(JWK_USE_ENC)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ mKey->Algorithm().MakeAes(mAlgName, length);
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed()) {
+ // There is not a 'use' value consistent with PBKDF or HKDF
+ return NS_ERROR_DOM_DATA_ERR;
+ };
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ if (mKey->HasUsageOtherThan(CryptoKey::SIGN | CryptoKey::VERIFY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mKey->Algorithm().MakeHmac(length, mHashName);
+
+ if (mKey->Algorithm().Mechanism() == UNKNOWN_CK_MECHANISM) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (mDataIsJwk && mJwk.mUse.WasPassed() &&
+ !mJwk.mUse.Value().EqualsLiteral(JWK_USE_SIG)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetSymKey(mKeyData))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::SECRET);
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mEarlyComplete = true;
+ return NS_OK;
+ }
+
+private:
+ nsString mHashName;
+};
+
+class ImportRsaKeyTask : public ImportKeyTask
+{
+public:
+ ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // If this is RSA with a hash, cache the hash name
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ RootedDictionary<RsaHashedImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+ // Check support for the algorithm and hash names
+ CK_MECHANISM_TYPE mech1 = MapAlgorithmNameToMechanism(mAlgName);
+ CK_MECHANISM_TYPE mech2 = MapAlgorithmNameToMechanism(mHashName);
+ if ((mech1 == UNKNOWN_CK_MECHANISM) || (mech2 == UNKNOWN_CK_MECHANISM)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+
+private:
+ nsString mHashName;
+ uint32_t mModulusLength;
+ CryptoBuffer mPublicExponent;
+
+ virtual nsresult DoCrypto() override
+ {
+ nsNSSShutDownPreventionLock locker;
+
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+ ScopedSECKEYPrivateKey privKey;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mJwk.mD.WasPassed())) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else {
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ mJwk.mD.WasPassed())) {
+ // Private key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
+ privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker);
+ } else {
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
+ }
+
+ if (!privKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPrivateKey(privKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PRIVATE);
+ pubKey = SECKEY_ConvertToPublicKey(privKey.get());
+ if (!pubKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ } else {
+ // Invalid key format
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Extract relevant information from the public key
+ mModulusLength = 8 * pubKey->u.rsa.modulus.len;
+ if (!mPublicExponent.Assign(&pubKey->u.rsa.publicExponent)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ // Check permissions for the requested operation
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::WRAPKEY)) ||
+ (mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
+ (mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(CryptoKey::SIGN))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+ }
+
+ // Set an appropriate KeyAlgorithm
+ if (!mKey->Algorithm().MakeRsa(mAlgName, mModulusLength,
+ mPublicExponent, mHashName)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class ImportEcKeyTask : public ImportKeyTask
+{
+public:
+ ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ RootedDictionary<EcKeyImportParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv) || !params.mNamedCurve.WasPassed()) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (!NormalizeToken(params.mNamedCurve.Value(), mNamedCurve)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+ }
+ }
+
+private:
+ nsString mNamedCurve;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+ ScopedSECKEYPrivateKey privKey;
+
+ nsNSSShutDownPreventionLock locker;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && mJwk.mD.WasPassed()) {
+ // Private key import
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
+ if (!privKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (NS_FAILED(mKey->SetPrivateKey(privKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PRIVATE);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+ mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+ (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+ !mJwk.mD.WasPassed())) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ pubKey = CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ if (!CheckEncodedECParameters(&pubKey->u.ec.DEREncodedParams)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Construct the OID tag.
+ SECItem oid = { siBuffer, nullptr, 0 };
+ oid.len = pubKey->u.ec.DEREncodedParams.data[1];
+ oid.data = pubKey->u.ec.DEREncodedParams.data + 2;
+
+ // Find a matching and supported named curve.
+ if (!MapOIDTagToNamedCurve(SECOID_FindOIDTag(&oid), mNamedCurve)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ // Extract 'crv' parameter from JWKs.
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ if (!NormalizeToken(mJwk.mCrv.Value(), mNamedCurve)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+ privateAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+ publicAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ privateAllowedUsages = CryptoKey::SIGN;
+ publicAllowedUsages = CryptoKey::VERIFY;
+ }
+
+ // Check permissions for the requested operation
+ if ((mKey->GetKeyType() == CryptoKey::PRIVATE &&
+ mKey->HasUsageOtherThan(privateAllowedUsages)) ||
+ (mKey->GetKeyType() == CryptoKey::PUBLIC &&
+ mKey->HasUsageOtherThan(publicAllowedUsages))) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ mKey->Algorithm().MakeEc(mAlgName, mNamedCurve);
+
+ if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class ImportDhKeyTask : public ImportKeyTask
+{
+public:
+ ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ }
+
+ ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ SetKeyData(aCx, aKeyData);
+ NS_ENSURE_SUCCESS_VOID(mEarlyRv);
+ }
+ }
+
+ void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ RootedDictionary<DhImportKeyParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoBuffer prime;
+ ATTEMPT_BUFFER_INIT(mPrime, params.mPrime);
+
+ CryptoBuffer generator;
+ ATTEMPT_BUFFER_INIT(mGenerator, params.mGenerator);
+ }
+ }
+
+private:
+ CryptoBuffer mPrime;
+ CryptoBuffer mGenerator;
+
+ virtual nsresult DoCrypto() override
+ {
+ // Import the key data itself
+ ScopedSECKEYPublicKey pubKey;
+
+ nsNSSShutDownPreventionLock locker;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+ mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ // Public key import
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else {
+ MOZ_ASSERT(false);
+ }
+
+ if (!pubKey) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ ATTEMPT_BUFFER_ASSIGN(mPrime, &pubKey->u.dh.prime);
+ ATTEMPT_BUFFER_ASSIGN(mGenerator, &pubKey->u.dh.base);
+ }
+
+ if (NS_FAILED(mKey->SetPublicKey(pubKey.get()))) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ mKey->SetType(CryptoKey::PUBLIC);
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return NS_OK;
+ }
+
+ virtual nsresult AfterCrypto() override
+ {
+ // Check permissions for the requested operation
+ if (mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mKey->Algorithm().MakeDh(mAlgName, mPrime, mGenerator)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+};
+
+class ExportKeyTask : public WebCryptoTask
+{
+public:
+ ExportKeyTask(const nsAString& aFormat, CryptoKey& aKey)
+ : mFormat(aFormat)
+ , mSymKey(aKey.GetSymKey())
+ , mPrivateKey(aKey.GetPrivateKey())
+ , mPublicKey(aKey.GetPublicKey())
+ , mKeyType(aKey.GetKeyType())
+ , mExtractable(aKey.Extractable())
+ , mAlg(aKey.Algorithm().JwkAlg())
+ {
+ aKey.GetUsages(mKeyUsages);
+ }
+
+
+protected:
+ nsString mFormat;
+ CryptoBuffer mSymKey;
+ ScopedSECKEYPrivateKey mPrivateKey;
+ ScopedSECKEYPublicKey mPublicKey;
+ CryptoKey::KeyType mKeyType;
+ bool mExtractable;
+ nsString mAlg;
+ nsTArray<nsString> mKeyUsages;
+ CryptoBuffer mResult;
+ JsonWebKey mJwk;
+
+private:
+ virtual void ReleaseNSSResources() override
+ {
+ mPrivateKey.dispose();
+ mPublicKey.dispose();
+ }
+
+ virtual nsresult DoCrypto() override
+ {
+ nsNSSShutDownPreventionLock locker;
+
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ if (mPublicKey && mPublicKey->keyType == dhKey) {
+ nsresult rv = CryptoKey::PublicDhKeyToRaw(mPublicKey, mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+
+ if (mPublicKey && mPublicKey->keyType == ecKey) {
+ nsresult rv = CryptoKey::PublicECKeyToRaw(mPublicKey, mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+
+ mResult = mSymKey;
+ if (mResult.Length() == 0) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return NS_OK;
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
+ if (!mPrivateKey) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ switch (mPrivateKey->keyType) {
+ case rsaKey: {
+ nsresult rv = CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), mResult, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ if (!mPublicKey) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return CryptoKey::PublicKeyToSpki(mPublicKey.get(), mResult, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ if (mKeyType == CryptoKey::SECRET) {
+ nsString k;
+ nsresult rv = mSymKey.ToJwkBase64(k);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ mJwk.mK.Construct(k);
+ mJwk.mKty = NS_LITERAL_STRING(JWK_TYPE_SYMMETRIC);
+ } else if (mKeyType == CryptoKey::PUBLIC) {
+ if (!mPublicKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = CryptoKey::PublicKeyToJwk(mPublicKey, mJwk, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ } else if (mKeyType == CryptoKey::PRIVATE) {
+ if (!mPrivateKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, mJwk, locker);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ if (!mAlg.IsEmpty()) {
+ mJwk.mAlg.Construct(mAlg);
+ }
+
+ mJwk.mExt.Construct(mExtractable);
+
+ mJwk.mKey_ops.Construct();
+ if (!mJwk.mKey_ops.Value().AppendElements(mKeyUsages, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // Returns mResult as an ArrayBufferView or JWK, as appropriate
+ virtual void Resolve() override
+ {
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ mResultPromise->MaybeResolve(mJwk);
+ return;
+ }
+
+ TypedArrayCreator<ArrayBuffer> ret(mResult);
+ mResultPromise->MaybeResolve(ret);
+ }
+};
+
+class GenerateSymmetricKeyTask : public WebCryptoTask
+{
+public:
+ GenerateSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ {
+ // Create an empty key and set easy attributes
+ mKey = new CryptoKey(aGlobal);
+ mKey->SetExtractable(aExtractable);
+ mKey->SetType(CryptoKey::SECRET);
+
+ // Extract algorithm name
+ nsString algName;
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Construct an appropriate KeyAlorithm
+ uint32_t allowedUsages = 0;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aAlgorithm, mLength);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ mKey->Algorithm().MakeAes(algName, mLength);
+
+ allowedUsages = CryptoKey::ENCRYPT | CryptoKey::DECRYPT |
+ CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY;
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ RootedDictionary<HmacKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (params.mLength.WasPassed()) {
+ mLength = params.mLength.Value();
+ } else {
+ mLength = MapHashAlgorithmNameToBlockSize(hashName);
+ }
+
+ if (mLength == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mKey->Algorithm().MakeHmac(mLength, hashName);
+ allowedUsages = CryptoKey::SIGN | CryptoKey::VERIFY;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Add key usages
+ mKey->ClearUsages();
+ for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKey->AddUsageIntersecting(aKeyUsages[i], allowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+ mMechanism = mKey->Algorithm().Mechanism();
+ // SetSymKey done in Resolve, after we've done the keygen
+ }
+
+private:
+ RefPtr<CryptoKey> mKey;
+ size_t mLength;
+ CK_MECHANISM_TYPE mMechanism;
+ CryptoBuffer mKeyData;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ ScopedPK11SymKey symKey(PK11_KeyGen(slot.get(), mMechanism, nullptr,
+ mLength, nullptr));
+ if (!symKey) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mKeyData manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mKeyData, PK11_GetKeyData(symKey));
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ if (NS_SUCCEEDED(mKey->SetSymKey(mKeyData))) {
+ mResultPromise->MaybeResolve(mKey);
+ } else {
+ mResultPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ }
+ }
+
+ virtual void Cleanup() override
+ {
+ mKey = nullptr;
+ }
+};
+
+GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
+ nsIGlobalObject* aGlobal, JSContext* aCx, const ObjectOrString& aAlgorithm,
+ bool aExtractable, const Sequence<nsString>& aKeyUsages)
+ : mKeyPair(new CryptoKeyPair())
+{
+ mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!mArena) {
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
+ return;
+ }
+
+ // Create an empty key pair and set easy attributes
+ mKeyPair->mPrivateKey = new CryptoKey(aGlobal);
+ mKeyPair->mPublicKey = new CryptoKey(aGlobal);
+
+ // Extract algorithm name
+ mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Construct an appropriate KeyAlorithm
+ uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ RootedDictionary<RsaHashedKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Pull relevant info
+ uint32_t modulusLength = params.mModulusLength;
+ CryptoBuffer publicExponent;
+ ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent);
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // Create algorithm
+ if (!mKeyPair->mPublicKey.get()->Algorithm().MakeRsa(mAlgName,
+ modulusLength,
+ publicExponent,
+ hashName)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ if (!mKeyPair->mPrivateKey.get()->Algorithm().MakeRsa(mAlgName,
+ modulusLength,
+ publicExponent,
+ hashName)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN;
+
+ // Set up params struct
+ mRsaParams.keySizeInBits = modulusLength;
+ bool converted = publicExponent.GetBigIntValue(mRsaParams.pe);
+ if (!converted) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ RootedDictionary<EcKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Create algorithm.
+ mKeyPair->mPublicKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve);
+ mKeyPair->mPrivateKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve);
+ mMechanism = CKM_EC_KEY_PAIR_GEN;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ RootedDictionary<DhKeyGenParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoBuffer prime;
+ ATTEMPT_BUFFER_INIT(prime, params.mPrime);
+
+ CryptoBuffer generator;
+ ATTEMPT_BUFFER_INIT(generator, params.mGenerator);
+
+ // Set up params.
+ if (!prime.ToSECItem(mArena, &mDhParams.prime) ||
+ !generator.ToSECItem(mArena, &mDhParams.base)) {
+ mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
+ return;
+ }
+
+ // Create algorithm.
+ if (!mKeyPair->mPublicKey.get()->Algorithm().MakeDh(mAlgName,
+ prime,
+ generator)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ if (!mKeyPair->mPrivateKey.get()->Algorithm().MakeDh(mAlgName,
+ prime,
+ generator)) {
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
+ return;
+ }
+ mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN;
+ } else {
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ // Set key usages.
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ privateAllowedUsages = CryptoKey::SIGN;
+ publicAllowedUsages = CryptoKey::VERIFY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
+ publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS;
+ publicAllowedUsages = 0;
+ } else {
+ MOZ_ASSERT(false); // This shouldn't happen.
+ }
+
+ mKeyPair->mPrivateKey.get()->SetExtractable(aExtractable);
+ mKeyPair->mPrivateKey.get()->SetType(CryptoKey::PRIVATE);
+
+ mKeyPair->mPublicKey.get()->SetExtractable(true);
+ mKeyPair->mPublicKey.get()->SetType(CryptoKey::PUBLIC);
+
+ mKeyPair->mPrivateKey.get()->ClearUsages();
+ mKeyPair->mPublicKey.get()->ClearUsages();
+ for (uint32_t i=0; i < aKeyUsages.Length(); ++i) {
+ mEarlyRv = mKeyPair->mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i],
+ privateAllowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ mEarlyRv = mKeyPair->mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i],
+ publicAllowedUsages);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+ }
+
+ // If no usages ended up being allowed, DataError
+ if (!mKeyPair->mPublicKey.get()->HasAnyUsage() &&
+ !mKeyPair->mPrivateKey.get()->HasAnyUsage()) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+}
+
+void
+GenerateAsymmetricKeyTask::ReleaseNSSResources()
+{
+ mPublicKey.dispose();
+ mPrivateKey.dispose();
+}
+
+nsresult
+GenerateAsymmetricKeyTask::DoCrypto()
+{
+ MOZ_ASSERT(mKeyPair);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ void* param;
+ switch (mMechanism) {
+ case CKM_RSA_PKCS_KEY_PAIR_GEN:
+ param = &mRsaParams;
+ break;
+ case CKM_DH_PKCS_KEY_PAIR_GEN:
+ param = &mDhParams;
+ break;
+ case CKM_EC_KEY_PAIR_GEN: {
+ param = CreateECParamsForCurve(mNamedCurve, mArena);
+ if (!param) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ break;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ SECKEYPublicKey* pubKey = nullptr;
+ mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey,
+ PR_FALSE, PR_FALSE, nullptr);
+ mPublicKey = pubKey;
+ if (!mPrivateKey.get() || !mPublicKey.get()) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = mKeyPair->mPrivateKey.get()->SetPrivateKey(mPrivateKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ rv = mKeyPair->mPublicKey.get()->SetPublicKey(mPublicKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
+ // PK11_GenerateKeyPair() does not set a CKA_EC_POINT attribute on the
+ // private key, we need this later when exporting to PKCS8 and JWK though.
+ if (mMechanism == CKM_EC_KEY_PAIR_GEN) {
+ rv = mKeyPair->mPrivateKey->AddPublicKeyData(mPublicKey);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+ }
+
+ return NS_OK;
+}
+
+void
+GenerateAsymmetricKeyTask::Resolve()
+{
+ mResultPromise->MaybeResolve(*mKeyPair);
+}
+
+void
+GenerateAsymmetricKeyTask::Cleanup()
+{
+ mKeyPair = nullptr;
+}
+
+class DeriveHkdfBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveHkdfBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mSymKey(aKey.GetSymKey())
+ {
+ Init(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ DeriveHkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mSymKey(aKey.GetSymKey())
+ {
+ size_t length;
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
+
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey, length);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
+ uint32_t aLength)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_HKDF);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HKDF);
+
+ // Check that we have a key.
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ RootedDictionary<HkdfParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // length must be greater than zero.
+ if (aLength == 0) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Extract the hash algorithm.
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check the given hash algorithm.
+ switch (MapAlgorithmNameToMechanism(hashName)) {
+ case CKM_SHA_1: mMechanism = CKM_NSS_HKDF_SHA1; break;
+ case CKM_SHA256: mMechanism = CKM_NSS_HKDF_SHA256; break;
+ case CKM_SHA384: mMechanism = CKM_NSS_HKDF_SHA384; break;
+ case CKM_SHA512: mMechanism = CKM_NSS_HKDF_SHA512; break;
+ default:
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
+ ATTEMPT_BUFFER_INIT(mInfo, params.mInfo)
+ mLengthInBytes = ceil((double)aLength / 8);
+ mLengthInBits = aLength;
+ }
+
+private:
+ size_t mLengthInBits;
+ size_t mLengthInBytes;
+ CryptoBuffer mSalt;
+ CryptoBuffer mInfo;
+ CryptoBuffer mSymKey;
+ CK_MECHANISM_TYPE mMechanism;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Import the key
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPK11SymKey baseKey(PK11_ImportSymKey(slot, mMechanism,
+ PK11_OriginUnwrap, CKA_WRAP,
+ &keyItem, nullptr));
+ if (!baseKey) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ SECItem salt = { siBuffer, nullptr, 0 };
+ SECItem info = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &info, mInfo);
+
+ CK_NSS_HKDFParams hkdfParams = { true, salt.data, salt.len,
+ true, info.data, info.len };
+ SECItem params = { siBuffer, (unsigned char*)&hkdfParams,
+ sizeof(hkdfParams) };
+
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_Derive(baseKey, mMechanism, &params,
+ CKM_SHA512_HMAC, CKA_SIGN,
+ mLengthInBytes));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLengthInBytes > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLengthInBytes, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // If the number of bits to derive is not a multiple of 8 we need to
+ // zero out the remaining bits that were derived but not requested.
+ if (mLengthInBits % 8) {
+ mResult[mResult.Length() - 1] &= 0xff << (mLengthInBits % 8);
+ }
+
+ return NS_OK;
+ }
+};
+
+class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DerivePbkdfBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mSymKey(aKey.GetSymKey())
+ {
+ Init(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ DerivePbkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mSymKey(aKey.GetSymKey())
+ {
+ size_t length;
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
+
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey, length);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
+ uint32_t aLength)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_PBKDF2);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_PBKDF2);
+
+ // Check that we got a symmetric key
+ if (mSymKey.Length() == 0) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ RootedDictionary<Pbkdf2Params> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ // length must be a multiple of 8 bigger than zero.
+ if (aLength == 0 || aLength % 8) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ // Extract the hash algorithm.
+ nsString hashName;
+ mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ // Check the given hash algorithm.
+ switch (MapAlgorithmNameToMechanism(hashName)) {
+ case CKM_SHA_1: mHashOidTag = SEC_OID_HMAC_SHA1; break;
+ case CKM_SHA256: mHashOidTag = SEC_OID_HMAC_SHA256; break;
+ case CKM_SHA384: mHashOidTag = SEC_OID_HMAC_SHA384; break;
+ case CKM_SHA512: mHashOidTag = SEC_OID_HMAC_SHA512; break;
+ default:
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ return;
+ }
+
+ ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
+ mLength = aLength >> 3; // bits to bytes
+ mIterations = params.mIterations;
+ }
+
+private:
+ size_t mLength;
+ size_t mIterations;
+ CryptoBuffer mSalt;
+ CryptoBuffer mSymKey;
+ SECOidTag mHashOidTag;
+
+ virtual nsresult DoCrypto() override
+ {
+ ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem salt = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
+ // PK11_CreatePBEV2AlgorithmID will "helpfully" create PBKDF2 parameters
+ // with a random salt if given a SECItem* that is either null or has a null
+ // data pointer. This obviously isn't what we want, so we have to fake it
+ // out by passing in a SECItem* with a non-null data pointer but with zero
+ // length.
+ if (!salt.data) {
+ MOZ_ASSERT(salt.len == 0);
+ salt.data = reinterpret_cast<unsigned char*>(PORT_ArenaAlloc(arena, 1));
+ if (!salt.data) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ }
+
+ // Always pass in cipherAlg=SEC_OID_HMAC_SHA1 (i.e. PBMAC1) as this
+ // parameter is unused for key generation. It is currently only used
+ // for PBKDF2 authentication or key (un)wrapping when specifying an
+ // encryption algorithm (PBES2).
+ ScopedSECAlgorithmID alg_id(PK11_CreatePBEV2AlgorithmID(
+ SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA1, mHashOidTag,
+ mLength, mIterations, &salt));
+
+ if (!alg_id.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+
+ ScopedPK11SymKey symKey(PK11_PBEKeyGen(slot, alg_id, &keyItem, false, nullptr));
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+ return NS_OK;
+ }
+};
+
+template<class DeriveBitsTask>
+class DeriveKeyTask : public DeriveBitsTask
+{
+public:
+ DeriveKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aBaseKey,
+ const ObjectOrString& aDerivedKeyType, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+ : DeriveBitsTask(aCx, aAlgorithm, aBaseKey, aDerivedKeyType)
+ , mResolved(false)
+ {
+ if (NS_FAILED(this->mEarlyRv)) {
+ return;
+ }
+
+ NS_NAMED_LITERAL_STRING(format, WEBCRYPTO_KEY_FORMAT_RAW);
+ mTask = new ImportSymmetricKeyTask(aGlobal, aCx, format, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+protected:
+ RefPtr<ImportSymmetricKeyTask> mTask;
+ bool mResolved;
+
+private:
+ virtual void Resolve() override {
+ mTask->SetRawKeyData(this->mResult);
+ mTask->DispatchWithPromise(this->mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+class DeriveEcdhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveEcdhBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mLength(aLength),
+ mPrivKey(aKey.GetPrivateKey())
+ {
+ Init(aCx, aAlgorithm, aKey);
+ }
+
+ DeriveEcdhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mPrivKey(aKey.GetPrivateKey())
+ {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+ {
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDH);
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDH);
+
+ // Check that we have a private key.
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ // Length must be a multiple of 8 bigger than zero.
+ if (mLength == 0 || mLength % 8) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+
+ // Retrieve the peer's public key.
+ RootedDictionary<EcdhKeyDeriveParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoKey* publicKey = params.mPublic;
+ mPubKey = publicKey->GetPublicKey();
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ CHECK_KEY_ALGORITHM(publicKey->Algorithm(), WEBCRYPTO_ALG_ECDH);
+
+ // Both keys must use the same named curve.
+ nsString curve1 = aKey.Algorithm().mEc.mNamedCurve;
+ nsString curve2 = publicKey->Algorithm().mEc.mNamedCurve;
+
+ if (!curve1.Equals(curve2)) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+private:
+ size_t mLength;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+
+ virtual nsresult DoCrypto() override
+ {
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
+ mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_ECDH1_DERIVE,
+ CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLength > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+class DeriveDhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+ DeriveDhBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+ : mLength(aLength),
+ mPrivKey(aKey.GetPrivateKey())
+ {
+ Init(aCx, aAlgorithm, aKey);
+ }
+
+ DeriveDhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+ : mPrivKey(aKey.GetPrivateKey())
+ {
+ mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
+ if (NS_SUCCEEDED(mEarlyRv)) {
+ Init(aCx, aAlgorithm, aKey);
+ }
+ }
+
+ void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+ {
+ CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_DH);
+
+ // Check that we have a private key.
+ if (!mPrivKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ mLength = mLength >> 3; // bits to bytes
+
+ // Retrieve the peer's public key.
+ RootedDictionary<DhKeyDeriveParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoKey* publicKey = params.mPublic;
+ mPubKey = publicKey->GetPublicKey();
+ if (!mPubKey) {
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ return;
+ }
+
+ KeyAlgorithmProxy alg1 = publicKey->Algorithm();
+ CHECK_KEY_ALGORITHM(alg1, WEBCRYPTO_ALG_DH);
+
+ // Both keys must use the same prime and generator.
+ KeyAlgorithmProxy alg2 = aKey.Algorithm();
+ if (alg1.mDh.mPrime != alg2.mDh.mPrime ||
+ alg1.mDh.mGenerator != alg2.mDh.mGenerator) {
+ mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ return;
+ }
+ }
+
+private:
+ size_t mLength;
+ ScopedSECKEYPrivateKey mPrivKey;
+ ScopedSECKEYPublicKey mPubKey;
+
+ virtual nsresult DoCrypto() override
+ {
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
+ mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_DH_PKCS_DERIVE,
+ CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
+
+ if (!symKey.get()) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by symKey. The assignment copies the
+ // data, so mResult manages one copy, while symKey manages another.
+ ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+ if (mLength > mResult.Length()) {
+ return NS_ERROR_DOM_DATA_ERR;
+ }
+
+ if (!mResult.SetLength(mLength, fallible)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+ }
+};
+
+template<class KeyEncryptTask>
+class WrapKeyTask : public ExportKeyTask
+{
+public:
+ WrapKeyTask(JSContext* aCx,
+ const nsAString& aFormat,
+ CryptoKey& aKey,
+ CryptoKey& aWrappingKey,
+ const ObjectOrString& aWrapAlgorithm)
+ : ExportKeyTask(aFormat, aKey)
+ , mResolved(false)
+ {
+ if (NS_FAILED(mEarlyRv)) {
+ return;
+ }
+
+ mTask = new KeyEncryptTask(aCx, aWrapAlgorithm, aWrappingKey, true);
+ }
+
+private:
+ RefPtr<KeyEncryptTask> mTask;
+ bool mResolved;
+
+ virtual nsresult AfterCrypto() override {
+ // If wrapping JWK, stringify the JSON
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ nsAutoString json;
+ if (!mJwk.ToJSON(json)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8(json);
+ if (!mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length())) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ virtual void Resolve() override
+ {
+ mTask->SetData(mResult);
+ mTask->DispatchWithPromise(mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+template<class KeyEncryptTask>
+class UnwrapKeyTask : public KeyEncryptTask
+{
+public:
+ UnwrapKeyTask(JSContext* aCx,
+ const ArrayBufferViewOrArrayBuffer& aWrappedKey,
+ CryptoKey& aUnwrappingKey,
+ const ObjectOrString& aUnwrapAlgorithm,
+ ImportKeyTask* aTask)
+ : KeyEncryptTask(aCx, aUnwrapAlgorithm, aUnwrappingKey, aWrappedKey, false)
+ , mTask(aTask)
+ , mResolved(false)
+ {}
+
+private:
+ RefPtr<ImportKeyTask> mTask;
+ bool mResolved;
+
+ virtual void Resolve() override
+ {
+ mTask->SetKeyDataMaybeParseJWK(KeyEncryptTask::mResult);
+ mTask->DispatchWithPromise(KeyEncryptTask::mResultPromise);
+ mResolved = true;
+ }
+
+ virtual void Cleanup() override
+ {
+ if (mTask && !mResolved) {
+ mTask->Skip();
+ }
+ mTask = nullptr;
+ }
+};
+
+// Task creation methods for WebCryptoTask
+
+// Note: We do not perform algorithm normalization as a monolithic process,
+// as described in the spec. Instead:
+// * Each method handles its slice of the supportedAlgorithms structure
+// * Task constructors take care of:
+// * Coercing the algorithm to the proper concrete type
+// * Cloning subordinate data items
+// * Cloning input data as needed
+//
+// Thus, support for different algorithms is determined by the if-statements
+// below, rather than a data structure.
+//
+// This results in algorithm normalization coming after some other checks,
+// and thus slightly more steps being done synchronously than the spec calls
+// for. But none of these steps is especially time-consuming.
+
+WebCryptoTask*
+WebCryptoTask::CreateEncryptDecryptTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData,
+ bool aEncrypt)
+{
+ TelemetryMethod method = (aEncrypt)? TM_ENCRYPT : TM_DECRYPT;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_ENC, aKey.Extractable());
+
+ // Ensure key is usable for this operation
+ if ((aEncrypt && !aKey.HasUsage(CryptoKey::ENCRYPT)) ||
+ (!aEncrypt && !aKey.HasUsage(CryptoKey::DECRYPT))) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new AesTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new RsaOaepTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateSignVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign)
+{
+ TelemetryMethod method = (aSign)? TM_SIGN : TM_VERIFY;
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, method);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_SIG, aKey.Extractable());
+
+ // Ensure key is usable for this operation
+ if ((aSign && !aKey.HasUsage(CryptoKey::SIGN)) ||
+ (!aSign && !aKey.HasUsage(CryptoKey::VERIFY))) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature,
+ aData, aSign);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDigestTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DIGEST);
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+ return new DigestTask(aCx, aAlgorithm, aData);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateImportKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const nsAString& aFormat,
+ JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ // SPEC-BUG: PBKDF2 is not supposed to be supported for this operation.
+ // However, the spec should be updated to allow it.
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
+ return new ImportSymmetricKeyTask(aGlobal, aCx, aFormat, aKeyData,
+ aAlgorithm, aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+ return new ImportRsaKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ return new ImportEcKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ return new ImportDhKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
+ aExtractable, aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
+ CryptoKey& aKey)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_EXPORTKEY);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Verify that the key is extractable
+ if (!aKey.Extractable()) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that the algorithm supports export
+ // SPEC-BUG: PBKDF2 is not supposed to be supported for this operation.
+ // However, the spec should be updated to allow it.
+ nsString algName = aKey.Algorithm().mName;
+ if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+ return new ExportKeyTask(aFormat, aKey);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateGenerateKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_GENERATEKEY);
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_GENERATE, aExtractable);
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ // SPEC-BUG: Spec says that this should be InvalidAccessError, but that
+ // is inconsistent with other analogous points in the spec
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_AES_KW) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
+ return new GenerateSymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+ aKeyUsages);
+ } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA) ||
+ algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
+ return new GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+ aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDeriveKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aBaseKey,
+ const ObjectOrString& aDerivedKeyType,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEKEY);
+
+ // Ensure baseKey is usable for this operation
+ if (!aBaseKey.HasUsage(CryptoKey::DERIVEKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+ return new DeriveKeyTask<DeriveHkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
+ return new DeriveKeyTask<DerivePbkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
+ return new DeriveKeyTask<DeriveEcdhBitsTask>(aGlobal, aCx, aAlgorithm,
+ aBaseKey, aDerivedKeyType,
+ aExtractable, aKeyUsages);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ uint32_t aLength)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEBITS);
+
+ // Ensure baseKey is usable for this operation
+ if (!aKey.HasUsage(CryptoKey::DERIVEBITS)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString algName;
+ nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
+ return new DerivePbkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
+ return new DeriveEcdhBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
+ return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+ return new DeriveHkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateWrapKeyTask(JSContext* aCx,
+ const nsAString& aFormat,
+ CryptoKey& aKey,
+ CryptoKey& aWrappingKey,
+ const ObjectOrString& aWrapAlgorithm)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_WRAPKEY);
+
+ // Verify that the format is recognized
+ if (!aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8) &&
+ !aFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ // Ensure wrappingKey is usable for this operation
+ if (!aWrappingKey.HasUsage(CryptoKey::WRAPKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Ensure key is extractable
+ if (!aKey.Extractable()) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ nsString wrapAlgName;
+ nsresult rv = GetAlgorithmName(aCx, aWrapAlgorithm, wrapAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new WrapKeyTask<AesTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ return new WrapKeyTask<AesKwTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ } else if (wrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new WrapKeyTask<RsaOaepTask>(aCx, aFormat, aKey,
+ aWrappingKey, aWrapAlgorithm);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask*
+WebCryptoTask::CreateUnwrapKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const nsAString& aFormat,
+ const ArrayBufferViewOrArrayBuffer& aWrappedKey,
+ CryptoKey& aUnwrappingKey,
+ const ObjectOrString& aUnwrapAlgorithm,
+ const ObjectOrString& aUnwrappedKeyAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages)
+{
+ Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_UNWRAPKEY);
+
+ // Ensure key is usable for this operation
+ if (!aUnwrappingKey.HasUsage(CryptoKey::UNWRAPKEY)) {
+ return new FailureTask(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ // Verify that aKeyUsages does not contain an unrecognized value
+ if (!CryptoKey::AllUsagesRecognized(aKeyUsages)) {
+ return new FailureTask(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ nsString keyAlgName;
+ nsresult rv = GetAlgorithmName(aCx, aUnwrappedKeyAlgorithm, keyAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+
+ CryptoOperationData dummy;
+ RefPtr<ImportKeyTask> importTask;
+ if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
+ importTask = new ImportSymmetricKeyTask(aGlobal, aCx, aFormat,
+ aUnwrappedKeyAlgorithm,
+ aExtractable, aKeyUsages);
+ } else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+ keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
+ importTask = new ImportRsaKeyTask(aGlobal, aCx, aFormat,
+ aUnwrappedKeyAlgorithm,
+ aExtractable, aKeyUsages);
+ } else {
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ }
+
+ nsString unwrapAlgName;
+ rv = GetAlgorithmName(aCx, aUnwrapAlgorithm, unwrapAlgName);
+ if (NS_FAILED(rv)) {
+ return new FailureTask(rv);
+ }
+ if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
+ unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
+ unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
+ return new UnwrapKeyTask<AesTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) {
+ return new UnwrapKeyTask<AesKwTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ } else if (unwrapAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+ return new UnwrapKeyTask<RsaOaepTask>(aCx, aWrappedKey,
+ aUnwrappingKey, aUnwrapAlgorithm,
+ importTask);
+ }
+
+ return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+}
+
+WebCryptoTask::WebCryptoTask()
+ : mEarlyRv(NS_OK)
+ , mEarlyComplete(false)
+ , mOriginalThread(nullptr)
+ , mReleasedNSSResources(false)
+ , mRv(NS_ERROR_NOT_INITIALIZED)
+{
+}
+
+WebCryptoTask::~WebCryptoTask()
+{
+ MOZ_ASSERT(mReleasedNSSResources);
+
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ shutdown(ShutdownCalledFrom::Object);
+ }
+
+ if (mWorkerHolder) {
+ NS_ProxyRelease(mOriginalThread, mWorkerHolder.forget());
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/crypto/WebCryptoTask.h b/dom/crypto/WebCryptoTask.h
new file mode 100644
index 000000000..169321970
--- /dev/null
+++ b/dom/crypto/WebCryptoTask.h
@@ -0,0 +1,249 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebCryptoTask_h
+#define mozilla_dom_WebCryptoTask_h
+
+#include "nsNSSShutDown.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/CryptoKey.h"
+
+namespace mozilla {
+namespace dom {
+
+typedef ArrayBufferViewOrArrayBuffer CryptoOperationData;
+typedef ArrayBufferViewOrArrayBuffer KeyData;
+
+/*
+
+The execution of a WebCryptoTask happens in several phases
+
+1. Constructor
+2. BeforeCrypto
+3. CalculateResult -> DoCrypto
+4. AfterCrypto
+5. Resolve or FailWithError
+6. Cleanup
+
+If any of these steps produces an error (setting mEarlyRv), then
+subsequent steps will not proceed. If the constructor or BeforeCrypto
+sets mEarlyComplete to true, then we will skip step 3, saving the
+thread overhead.
+
+In general, the constructor should handle any parsing steps that
+require JS context, and otherwise just cache information for later
+steps to use.
+
+All steps besides step 3 occur on the main thread, so they should
+avoid blocking operations.
+
+Only step 3 is guarded to ensure that NSS has not been shutdown,
+so all NSS interactions should occur in DoCrypto
+
+Cleanup should execute regardless of what else happens.
+
+*/
+
+#define MAYBE_EARLY_FAIL(rv) \
+if (NS_FAILED(rv)) { \
+ FailWithError(rv); \
+ Skip(); \
+ return; \
+}
+
+class WebCryptoTask : public CancelableRunnable,
+ public nsNSSShutDownObject
+{
+public:
+ virtual void DispatchWithPromise(Promise* aResultPromise);
+
+ void Skip()
+ {
+ virtualDestroyNSSReference();
+ }
+
+protected:
+ static WebCryptoTask* CreateEncryptDecryptTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData,
+ bool aEncrypt);
+
+ static WebCryptoTask* CreateSignVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData,
+ bool aSign);
+
+public:
+ static WebCryptoTask* CreateEncryptTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData)
+ {
+ return CreateEncryptDecryptTask(aCx, aAlgorithm, aKey, aData, true);
+ }
+
+ static WebCryptoTask* CreateDecryptTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData)
+ {
+ return CreateEncryptDecryptTask(aCx, aAlgorithm, aKey, aData, false);
+ }
+
+ static WebCryptoTask* CreateSignTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aData)
+ {
+ CryptoOperationData dummy;
+ dummy.SetAsArrayBuffer(aCx);
+ return CreateSignVerifyTask(aCx, aAlgorithm, aKey, dummy, aData, true);
+ }
+
+ static WebCryptoTask* CreateVerifyTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ const CryptoOperationData& aSignature,
+ const CryptoOperationData& aData)
+ {
+ return CreateSignVerifyTask(aCx, aAlgorithm, aKey, aSignature, aData, false);
+ }
+
+ static WebCryptoTask* CreateDigestTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const CryptoOperationData& aData);
+
+ static WebCryptoTask* CreateImportKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const nsAString& aFormat,
+ JS::Handle<JSObject*> aKeyData,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages);
+ static WebCryptoTask* CreateExportKeyTask(const nsAString& aFormat,
+ CryptoKey& aKey);
+ static WebCryptoTask* CreateGenerateKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ bool aExtractable,
+ const Sequence<nsString>& aKeyUsages);
+
+ static WebCryptoTask* CreateDeriveKeyTask(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aBaseKey,
+ const ObjectOrString& aDerivedKeyType,
+ bool extractable,
+ const Sequence<nsString>& aKeyUsages);
+ static WebCryptoTask* CreateDeriveBitsTask(JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ CryptoKey& aKey,
+ uint32_t aLength);
+
+ static WebCryptoTask* CreateWrapKeyTask(JSContext* aCx,
+ const nsAString& aFormat,
+ CryptoKey& aKey,
+ CryptoKey& aWrappingKey,
+ const ObjectOrString& aWrapAlgorithm);
+ static 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);
+
+protected:
+ RefPtr<Promise> mResultPromise;
+ nsresult mEarlyRv;
+ bool mEarlyComplete;
+
+ WebCryptoTask();
+ virtual ~WebCryptoTask();
+
+ bool IsOnOriginalThread() {
+ return !mOriginalThread || NS_GetCurrentThread() == mOriginalThread;
+ }
+
+ // For things that need to happen on the main thread
+ // either before or after CalculateResult
+ virtual nsresult BeforeCrypto() { return NS_OK; }
+ virtual nsresult DoCrypto() { return NS_OK; }
+ virtual nsresult AfterCrypto() { return NS_OK; }
+ virtual void Resolve() {}
+ virtual void Cleanup() {}
+
+ void FailWithError(nsresult aRv);
+
+ // Subclasses should override this method if they keep references to
+ // any NSS objects, e.g., SECKEYPrivateKey or PK11SymKey.
+ virtual void ReleaseNSSResources() {}
+
+ virtual nsresult CalculateResult() final;
+
+ virtual void CallCallback(nsresult rv) final;
+
+private:
+ NS_IMETHOD Run() override final;
+ nsresult Cancel() override final;
+
+ virtual void
+ virtualDestroyNSSReference() override final
+ {
+ MOZ_ASSERT(IsOnOriginalThread());
+
+ if (!mReleasedNSSResources) {
+ mReleasedNSSResources = true;
+ ReleaseNSSResources();
+ }
+ }
+
+ class InternalWorkerHolder;
+
+ nsCOMPtr<nsIThread> mOriginalThread;
+ RefPtr<InternalWorkerHolder> mWorkerHolder;
+ bool mReleasedNSSResources;
+ nsresult mRv;
+};
+
+// XXX This class is declared here (unlike others) to enable reuse by WebRTC.
+class GenerateAsymmetricKeyTask : public WebCryptoTask
+{
+public:
+ GenerateAsymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm, bool aExtractable,
+ const Sequence<nsString>& aKeyUsages);
+protected:
+ ScopedPLArenaPool mArena;
+ UniquePtr<CryptoKeyPair> mKeyPair;
+ nsString mAlgName;
+ CK_MECHANISM_TYPE mMechanism;
+ PK11RSAGenParams mRsaParams;
+ SECKEYDHParams mDhParams;
+ nsString mNamedCurve;
+
+ virtual void ReleaseNSSResources() override;
+ virtual nsresult DoCrypto() override;
+ virtual void Resolve() override;
+ virtual void Cleanup() override;
+
+private:
+ ScopedSECKEYPublicKey mPublicKey;
+ ScopedSECKEYPrivateKey mPrivateKey;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebCryptoTask_h
diff --git a/dom/crypto/WebCryptoThreadPool.cpp b/dom/crypto/WebCryptoThreadPool.cpp
new file mode 100644
index 000000000..6a1e78608
--- /dev/null
+++ b/dom/crypto/WebCryptoThreadPool.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/WebCryptoThreadPool.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsXPCOMPrivate.h"
+#include "nsIObserverService.h"
+#include "nsIThreadPool.h"
+
+namespace mozilla {
+namespace dom {
+
+StaticRefPtr<WebCryptoThreadPool> gInstance;
+
+NS_IMPL_ISUPPORTS(WebCryptoThreadPool, nsIObserver)
+
+/* static */ void
+WebCryptoThreadPool::Initialize()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ MOZ_ASSERT(!gInstance, "More than one instance!");
+
+ gInstance = new WebCryptoThreadPool();
+ NS_WARNING_ASSERTION(gInstance, "Failed create thread pool!");
+
+ if (gInstance && NS_FAILED(gInstance->Init())) {
+ NS_WARNING("Failed to initialize thread pool!");
+ gInstance = nullptr;
+ }
+}
+
+/* static */ nsresult
+WebCryptoThreadPool::Dispatch(nsIRunnable* aRunnable)
+{
+ if (gInstance) {
+ return gInstance->DispatchInternal(aRunnable);
+ }
+
+ // Fail if called on shutdown.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+WebCryptoThreadPool::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ // Need this observer to know when to shut down the thread pool.
+ return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
+}
+
+nsresult
+WebCryptoThreadPool::DispatchInternal(nsIRunnable* aRunnable)
+{
+ MutexAutoLock lock(mMutex);
+
+ if (!mPool) {
+ NS_ENSURE_TRUE(EnsureNSSInitializedChromeOrContent(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIThreadPool> pool(do_CreateInstance(NS_THREADPOOL_CONTRACTID));
+ NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
+
+ nsresult rv = pool->SetName(NS_LITERAL_CSTRING("SubtleCrypto"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pool.swap(mPool);
+ }
+
+ return mPool->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
+void
+WebCryptoThreadPool::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ MutexAutoLock lock(mMutex);
+
+ if (mPool) {
+ mPool->Shutdown();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "Failed to retrieve observer service!");
+
+ if (obs) {
+ if (NS_FAILED(obs->RemoveObserver(this,
+ NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID))) {
+ NS_WARNING("Failed to remove shutdown observer!");
+ }
+ }
+}
+
+NS_IMETHODIMP
+WebCryptoThreadPool::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+ if (gInstance) {
+ gInstance->Shutdown();
+ gInstance = nullptr;
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/crypto/WebCryptoThreadPool.h b/dom/crypto/WebCryptoThreadPool.h
new file mode 100644
index 000000000..77c4afb24
--- /dev/null
+++ b/dom/crypto/WebCryptoThreadPool.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebCryptoThreadPool_h
+#define mozilla_dom_WebCryptoThreadPool_h
+
+#include "nsIObserverService.h"
+#include "nsIThreadPool.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebCryptoThreadPool final : nsIObserver {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static void
+ Initialize();
+
+ static nsresult
+ Dispatch(nsIRunnable* aRunnable);
+
+private:
+ WebCryptoThreadPool()
+ : mMutex("WebCryptoThreadPool::mMutex")
+ , mPool(nullptr)
+ { }
+ virtual ~WebCryptoThreadPool()
+ { }
+
+ nsresult
+ Init();
+
+ nsresult
+ DispatchInternal(nsIRunnable* aRunnable);
+
+ void
+ Shutdown();
+
+ NS_IMETHOD Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) override;
+
+ mozilla::Mutex mMutex;
+ nsCOMPtr<nsIThreadPool> mPool;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebCryptoThreadPool_h
diff --git a/dom/crypto/moz.build b/dom/crypto/moz.build
new file mode 100644
index 000000000..ba0b5f2cd
--- /dev/null
+++ b/dom/crypto/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'CryptoBuffer.h',
+ 'CryptoKey.h',
+ 'KeyAlgorithmProxy.h',
+ 'WebCryptoCommon.h',
+ 'WebCryptoTask.h',
+ 'WebCryptoThreadPool.h'
+]
+
+UNIFIED_SOURCES += [
+ 'CryptoBuffer.cpp',
+ 'CryptoKey.cpp',
+ 'KeyAlgorithmProxy.cpp',
+ 'WebCryptoTask.cpp',
+ 'WebCryptoThreadPool.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/workers',
+ '/security/manager/ssl',
+ '/security/pkix/include',
+ '/xpcom/build',
+]
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
diff --git a/dom/crypto/test/file_indexedDB.html b/dom/crypto/test/file_indexedDB.html
new file mode 100644
index 000000000..09e95b18f
--- /dev/null
+++ b/dom/crypto/test/file_indexedDB.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1188750 - WebCrypto must ensure NSS is initialized before deserializing</title>
+</head>
+<body>
+ <script type="application/javascript;version=1.8">
+ let db;
+
+ function err(resolve) {
+ return e => resolve(e.target.error.message);
+ }
+
+ function openDatabase() {
+ return new Promise((resolve, reject) => {
+ let request = indexedDB.open("keystore", 1);
+
+ request.onerror = err(reject);
+ request.onsuccess = function (event) {
+ db = event.target.result;
+ resolve();
+ };
+
+ request.onupgradeneeded = function(event) {
+ db = event.target.result;
+ let objectStore = db.createObjectStore("keys", {autoIncrement: true});
+ objectStore.transaction.oncomplete = resolve;
+ };
+ });
+ }
+
+ function storeKey(key) {
+ return new Promise((resolve, reject) => {
+ let transaction = db.transaction("keys", "readwrite");
+ transaction.objectStore("keys").put(key, key.type);
+
+ transaction.onabort = err(reject);
+ transaction.onerror = err(reject);
+
+ transaction.oncomplete = function () {
+ resolve(key);
+ };
+ });
+ };
+
+ function retrieveKey() {
+ return new Promise((resolve, reject) => {
+ let transaction = db.transaction("keys", "readonly");
+ let cursor = transaction.objectStore("keys").openCursor();
+
+ cursor.onerror = err(reject);
+ cursor.onabort = err(reject);
+
+ cursor.onsuccess = function (event) {
+ try {
+ let result = event.target.result;
+ resolve(result && result.value);
+ } catch (e) {
+ reject(e.message);
+ }
+ };
+ });
+ }
+
+ function generateKey() {
+ let algorithm = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 1024,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ return crypto.subtle.generateKey(algorithm, true, ["sign", "verify"]);
+ }
+
+ openDatabase()
+ .then(retrieveKey).then(generateKey).then(storeKey)
+ .then(() => alert("ok")).catch(alert);
+ </script>
+</body>
+</html>
diff --git a/dom/crypto/test/mochitest.ini b/dom/crypto/test/mochitest.ini
new file mode 100644
index 000000000..a30fe4fcc
--- /dev/null
+++ b/dom/crypto/test/mochitest.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+support-files =
+ file_indexedDB.html
+ test-array.js
+ test-vectors.js
+ test-worker.js
+ test_WebCrypto.css
+ util.js
+
+[test_indexedDB.html]
+skip-if = toolkit == 'android' # bug 1200570
+[test_WebCrypto.html]
+[test_WebCrypto_DH.html]
+[test_WebCrypto_ECDH.html]
+[test_WebCrypto_ECDSA.html]
+[test_WebCrypto_HKDF.html]
+[test_WebCrypto_Import_Multiple_Identical_Keys.html]
+[test_WebCrypto_JWK.html]
+[test_WebCrypto_Normalize.html]
+[test_WebCrypto_PBKDF2.html]
+[test_WebCrypto_Reject_Generating_Keys_Without_Usages.html]
+[test_WebCrypto_RSA_OAEP.html]
+[test_WebCrypto_RSA_PSS.html]
+[test_WebCrypto_Structured_Cloning.html]
+[test_WebCrypto_Workers.html]
+[test_WebCrypto_Wrap_Unwrap.html]
diff --git a/dom/crypto/test/test-array.js b/dom/crypto/test/test-array.js
new file mode 100644
index 000000000..1297f8eb8
--- /dev/null
+++ b/dom/crypto/test/test-array.js
@@ -0,0 +1,242 @@
+/* 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/. */
+
+var MOCHITEST = false;
+
+function Test(name, test) {
+ this.name = name;
+ this.startTime = null;
+ this.endTime = null;
+ this.result = null;
+ this.row = null;
+
+ this.run = function() {
+ // Note the start time
+ this.startTime = new Date();
+ // Run the test
+ try {
+ test.call(this);
+ } catch (e) {
+ console.log(e);
+ console.log(e.stack);
+ this.complete(false);
+ }
+ };
+
+ this.memcmp_complete = function(x, y) {
+ var passfail = util.memcmp(x, y);
+ if (!passfail) {
+ console.log("expected: " + util.abv2hex(x));
+ console.log(" got: " + util.abv2hex(y));
+ }
+ this.complete(passfail);
+ };
+
+ this.complete = function(result) {
+ if (MOCHITEST) { ok(result, this.name); }
+
+ // Note the end time
+ this.endTime = new Date();
+ // Set result
+ this.result = result;
+ // Re-draw the row
+ this.draw();
+ this.next();
+ };
+
+ this.next = function() {
+ if (this.oncomplete) {
+ this.oncomplete();
+ }
+ };
+
+ this.setRow = function(id) {
+ this.row = document.getElementById(id).getElementsByTagName("td");
+ };
+
+ this.draw = function() {
+ if (!this.row) return;
+
+ // Print the name of the test
+ if (this.name) {
+ this.row[0].innerHTML = this.name;
+ var that = this;
+ this.row[0].onclick = function() { that.run(); }
+ } else {
+ this.row[0] = "";
+ }
+
+ // Print the result of the test
+ if (this.result == true) {
+ this.row[1].className = "pass";
+ this.row[1].innerHTML = "PASS";
+ } else if (this.result == false) {
+ this.row[1].className = "fail";
+ this.row[1].innerHTML = "FAIL";
+ } else {
+ //this.row[1].innerHTML = "";
+ this.row[1].innerHTML = this.result;
+ }
+
+ // Print the elapsed time, if known
+ if (this.startTime && this.endTime) {
+ this.row[2].innerHTML = (this.endTime - this.startTime) + " ms";
+ } else {
+ this.row[2].innerHTML = "";
+ }
+ };
+}
+
+function WorkerTest(worker, name, test) {
+ this.name = `${name} (Worker)`;
+ this.startTime = null;
+ this.endTime = null;
+ this.result = null;
+ this.row = null;
+
+ this.run = function() {
+ // Note the start time
+ this.startTime = new Date();
+
+ // Send the test code to the worker.
+ worker.postMessage(test.toSource());
+
+ // We expect only boolean responses from the worker script.
+ worker.onmessage = e => this.complete(e.data);
+ worker.onerror = e => this.complete(false);
+ };
+
+ var base = new Test(name, test);
+
+ // Inherit what we need from the |Test| class. We can't simply use its
+ // prototype as Test is just a function that can be used like a constructor.
+ for (var method of ["draw", "setRow", "next", "complete"]) {
+ this[method] = base[method].bind(this);
+ }
+}
+
+var TestArray = {
+ tests: [],
+ table: null,
+ passSpan: null,
+ failSpan: null,
+ pendingSpan: null,
+ pass: 0,
+ fail: 0,
+ pending: 0,
+ currTest: 0,
+ worker: new Worker("test-worker.js"),
+
+ addTest: function(name, testFn) {
+ // Give it a reference to the array
+ var test = new Test(name, testFn);
+ test.ta = this;
+
+ // Add test to tests
+ this.tests.push(test);
+
+ // Run the test in a worker too.
+ this.tests.push(new WorkerTest(this.worker, name, testFn));
+ },
+
+ updateSummary: function() {
+ this.pass = this.fail = this.pending = 0;
+ for (var i=0; i<this.tests.length; ++i) {
+ if (this.tests[i].result == true) this.pass++;
+ if (this.tests[i].result == false) this.fail++;
+ if (this.tests[i].result == null) this.pending++;
+ }
+ this.passSpan.innerHTML = this.pass;
+ this.failSpan.innerHTML = this.fail;
+ this.pendingSpan.innerHTML = this.pending;
+ },
+
+ load: function() {
+ // Grab reference to table and summary numbers
+ this.table = document.getElementById("results");
+ this.passSpan = document.getElementById("passN");
+ this.failSpan = document.getElementById("failN");
+ this.pendingSpan = document.getElementById("pendingN");
+
+ // Populate everything initially
+ this.updateSummary();
+ for (var i=0; i<this.tests.length; ++i) {
+ var tr = document.createElement("tr");
+ tr.id = "test" + i;
+ tr.appendChild(document.createElement("td"));
+ tr.appendChild(document.createElement("td"));
+ tr.appendChild(document.createElement("td"));
+ this.table.appendChild(tr);
+ this.tests[i].setRow(tr.id);
+ this.tests[i].draw();
+ }
+ },
+
+ run: function() {
+ this.currTest = 0;
+ this.runNextTest();
+ },
+
+ runNextTest: function() {
+ this.updateSummary();
+ var i = this.currTest++;
+ if (i >= this.tests.length) {
+ if (MOCHITEST) { SimpleTest.finish(); }
+ return;
+ }
+
+ var self = this;
+ this.tests[i].oncomplete = function() {
+ self.runNextTest();
+ }
+ this.tests[i].run();
+ }
+}
+
+if (window.addEventListener) {
+ window.addEventListener("load", function() { TestArray.load(); } );
+} else {
+ window.attachEvent("onload", function() { TestArray.load(); } );
+}
+
+function start() {
+ TestArray.run();
+}
+
+MOCHITEST = ("SimpleTest" in window);
+if (MOCHITEST) {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(2);
+ window.addEventListener("load", function() {
+ SimpleTest.waitForFocus(start);
+ });
+}
+
+function error(test) {
+ return function(x) {
+ console.log("ERROR :: " + x);
+ test.complete(false);
+ throw x;
+ }
+}
+
+function complete(test, valid) {
+ return function(x) {
+ console.log("COMPLETE")
+ console.log(x);
+ if (valid) {
+ test.complete(valid(x));
+ } else {
+ test.complete(true);
+ }
+ }
+}
+
+function memcmp_complete(test, value) {
+ return function(x) {
+ console.log("COMPLETE")
+ console.log(x);
+ test.memcmp_complete(value, x);
+ }
+}
diff --git a/dom/crypto/test/test-vectors.js b/dom/crypto/test/test-vectors.js
new file mode 100644
index 000000000..dd6d2ba33
--- /dev/null
+++ b/dom/crypto/test/test-vectors.js
@@ -0,0 +1,1052 @@
+/* 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/. */
+
+tv = {
+ raw: util.hex2abv("f3095c4fe5e299477643c2310b44f0aa"),
+
+ // this key had an inappropriate length (18 octets)
+ negative_raw: util.hex2abv("f3095c4fe5e299477643c2310b44f0aabbcc"),
+
+ // openssl genrsa 512 | openssl pkcs8 -topk8 -nocrypt
+ pkcs8: util.hex2abv(
+ "30820154020100300d06092a864886f70d01010105000482013e3082013a0201" +
+ "00024100a240ceb54e70dc14825b587d2f5dfd463c4b8250b696004a1acaafe4" +
+ "9bcf384a46aa9fb4d9c7ee88e9ef0a315f53868f63680b58347249baedd93415" +
+ "16c4cab70203010001024034e6dc7ed0ec8b55448b73f69d1310196e5f5045f0" +
+ "c247a5e1c664432d6a0af7e7da40b83af047dd01f5e0a90e47c224d7b5133a35" +
+ "4d11aa5003b3e8546c9901022100cdb2d7a7435bcb45e50e86f6c14e97ed781f" +
+ "0956cd26e6f75ed9fc88125f8407022100c9ee30af6cb95ac9c1149ed84b3338" +
+ "481741359409f369c497be177d950fb7d10221008b0ef98d611320639b0b6c20" +
+ "4ae4a7fee8f30a6c3cfaacafd4d6c74af228d26702206b0e1dbf935bbd774327" +
+ "2483b572a53f0b1d2643a2f6eab7305fb6627cf9855102203d2263156b324146" +
+ "4478b713eb854c4f6b3ef052f0463b65d8217daec0099834"
+ ),
+
+ // Truncated form of the PKCS#8 stucture above
+ negative_pkcs8: util.hex2abv("30820154020100300d06092a864886f70d010"),
+
+ // Extracted from a cert via http://lapo.it/asn1js/
+ spki: util.hex2abv(
+ "30819F300D06092A864886F70D010101050003818D0030818902818100895309" +
+ "7086EE6147C5F4D5FFAF1B498A3D11EC5518E964DC52126B2614F743883F64CA" +
+ "51377ABB530DFD20464A48BD67CD27E7B29AEC685C5D10825E605C056E4AB8EE" +
+ "A460FA27E55AA62C498B02D7247A249838A12ECDF37C6011CF4F0EDEA9CEE687" +
+ "C1CB4A51C6AE62B2EFDB000723A01C99D6C23F834880BA8B42D5414E6F020301" +
+ "0001"
+ ),
+
+ // Truncated form of the PKCS#8 stucture above
+ negative_spki: util.hex2abv("30819F300D06092A864886F70D010101050003"),
+
+ // From the NESSIE project
+ // https://www.cosic.esat.kuleuven.be/nessie/testvectors/hash
+ // /sha/Sha-2-256.unverified.test-vectors
+ // Set 1, vector# 5
+ sha256: {
+ data: util.hex2abv("616263646263646563646566646566676566676866676" +
+ "8696768696a68696a6b696a6b6c6a6b6c6d6b6c6d6e6c" +
+ "6d6e6f6d6e6f706e6f7071"),
+ result: util.hex2abv("248D6A61D20638B8E5C026930C3E6039A33CE45964F" +
+ "F2167F6ECEDD419DB06C1"),
+ },
+
+ // Test vector 2 from:
+ // <https://github.com/geertj/bluepass/blob/master/tests/vectors/aes-cbc-pkcs7.txt>
+ aes_cbc_enc: {
+ /*
+ key: util.hex2abv("893123f2d57b6e2c39e2f10d3ff818d1"),
+ iv: util.hex2abv("64be1b06ea7453ed2df9a79319d5edc5"),
+ data: util.hex2abv("44afb9a64ac896c2"),
+ result: util.hex2abv("7067c4cb6dfc69df949c2f39903c9310"),
+ */
+ key: util.hex2abv("893123f2d57b6e2c39e2f10d3ff818d1"),
+ iv: util.hex2abv("64be1b06ea7453ed2df9a79319d5edc5"),
+ data: util.hex2abv("44afb9a64ac896c2"),
+ result: util.hex2abv("7067c4cb6dfc69df949c2f39903c9310"),
+ },
+
+ // Test vector 11 from:
+ // <https://github.com/geertj/bluepass/blob/master/tests/vectors/aes-cbc-pkcs7.txt>
+ aes_cbc_dec: {
+ key: util.hex2abv("04952c3fcf497a4d449c41e8730c5d9a"),
+ iv: util.hex2abv("53549bf7d5553b727458c1abaf0ba167"),
+ data: util.hex2abv("7fa290322ca7a1a04b61a1147ff20fe6" +
+ "6fde58510a1d0289d11c0ddf6f4decfd"),
+ result: util.hex2abv("c9a44f6f75e98ddbca7332167f5c45e3"),
+ },
+
+ // Test vector 2 from:
+ // <http://tools.ietf.org/html/rfc3686#section-6>
+ aes_ctr_enc: {
+ key: util.hex2abv("7E24067817FAE0D743D6CE1F32539163"),
+ iv: util.hex2abv("006CB6DBC0543B59DA48D90B00000001"),
+ data: util.hex2abv("000102030405060708090A0B0C0D0E0F" +
+ "101112131415161718191A1B1C1D1E1F"),
+ result: util.hex2abv("5104A106168A72D9790D41EE8EDAD3" +
+ "88EB2E1EFC46DA57C8FCE630DF9141BE28"),
+ },
+
+ // Test vector 3 from:
+ // <http://tools.ietf.org/html/rfc3686#section-6>
+ aes_ctr_dec: {
+ key: util.hex2abv("7691BE035E5020A8AC6E618529F9A0DC"),
+ iv: util.hex2abv("00E0017B27777F3F4A1786F000000001"),
+ data: util.hex2abv("000102030405060708090A0B0C0D0E0F" +
+ "101112131415161718191A1B1C1D1E1F20212223"),
+ result: util.hex2abv("C1CF48A89F2FFDD9CF4652E9EFDB72D7" +
+ "4540A42BDE6D7836D59A5CEAAEF3105325B2072F"),
+ },
+
+ // Test case #18 from McGrew and Viega
+ // <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf>
+ aes_gcm_enc: {
+ key: util.hex2abv("feffe9928665731c6d6a8f9467308308" +
+ "feffe9928665731c6d6a8f9467308308"),
+ key_jwk: {
+ kty: "oct",
+ k: "_v_pkoZlcxxtao-UZzCDCP7_6ZKGZXMcbWqPlGcwgwg"
+ },
+ iv: util.hex2abv("9313225df88406e555909c5aff5269aa" +
+ "6a7a9538534f7da1e4c303d2a318a728" +
+ "c3c0c95156809539fcf0e2429a6b5254" +
+ "16aedbf5a0de6a57a637b39b"),
+ adata: util.hex2abv("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
+ data: util.hex2abv("d9313225f88406e5a55909c5aff5269a" +
+ "86a7a9531534f7da2e4c303d8a318a72" +
+ "1c3c0c95956809532fcf0e2449a6b525" +
+ "b16aedf5aa0de657ba637b39"),
+ result: util.hex2abv("5a8def2f0c9e53f1f75d7853659e2a20" +
+ "eeb2b22aafde6419a058ab4f6f746bf4" +
+ "0fc0c3b780f244452da3ebf1c5d82cde" +
+ "a2418997200ef82e44ae7e3f" +
+ "a44a8266ee1c8eb0c8b5d4cf5ae9f19a"),
+ },
+
+
+ // Test case #17 from McGrew and Viega
+ // <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf>
+ aes_gcm_dec: {
+ key: util.hex2abv("feffe9928665731c6d6a8f9467308308" +
+ "feffe9928665731c6d6a8f9467308308"),
+ iv: util.hex2abv("cafebabefacedbad"),
+ adata: util.hex2abv("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
+ data: util.hex2abv("c3762df1ca787d32ae47c13bf19844cb" +
+ "af1ae14d0b976afac52ff7d79bba9de0" +
+ "feb582d33934a4f0954cc2363bc73f78" +
+ "62ac430e64abe499f47c9b1f" +
+ "3a337dbf46a792c45e454913fe2ea8f2"),
+ result: util.hex2abv("d9313225f88406e5a55909c5aff5269a" +
+ "86a7a9531534f7da2e4c303d8a318a72" +
+ "1c3c0c95956809532fcf0e2449a6b525" +
+ "b16aedf5aa0de657ba637b39"),
+ },
+
+ // Test case #17 from McGrew and Viega
+ // ... but with part of the authentication tag zeroed
+ // <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf>
+ aes_gcm_dec_fail: {
+ key: util.hex2abv("feffe9928665731c6d6a8f9467308308" +
+ "feffe9928665731c6d6a8f9467308308"),
+ iv: util.hex2abv("cafebabefacedbad"),
+ adata: util.hex2abv("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
+ data: util.hex2abv("c3762df1ca787d32ae47c13bf19844cb" +
+ "af1ae14d0b976afac52ff7d79bba9de0" +
+ "feb582d33934a4f0954cc2363bc73f78" +
+ "62ac430e64abe499f47c9b1f" +
+ "00000000000000005e454913fe2ea8f2"),
+ result: util.hex2abv("d9313225f88406e5a55909c5aff5269a" +
+ "86a7a9531534f7da2e4c303d8a318a72" +
+ "1c3c0c95956809532fcf0e2449a6b525" +
+ "b16aedf5aa0de657ba637b39"),
+ },
+
+ // RFC 4231 <http://tools.ietf.org/html/rfc4231>, Test Case 7
+ hmac_sign: {
+ key: util.hex2abv("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaa"),
+ data: util.hex2abv("54686973206973206120746573742075" +
+ "73696e672061206c6172676572207468" +
+ "616e20626c6f636b2d73697a65206b65" +
+ "7920616e642061206c61726765722074" +
+ "68616e20626c6f636b2d73697a652064" +
+ "6174612e20546865206b6579206e6565" +
+ "647320746f2062652068617368656420" +
+ "6265666f7265206265696e6720757365" +
+ "642062792074686520484d414320616c" +
+ "676f726974686d2e"),
+ result: util.hex2abv("9b09ffa71b942fcb27635fbcd5b0e944" +
+ "bfdc63644f0713938a7f51535c3a35e2"),
+ },
+
+ // RFC 4231 <http://tools.ietf.org/html/rfc4231>, Test Case 6
+ hmac_verify: {
+ key: util.hex2abv("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaa"),
+ data: util.hex2abv("54657374205573696e67204c61726765" +
+ "72205468616e20426c6f636b2d53697a" +
+ "65204b6579202d2048617368204b6579" +
+ "204669727374"),
+ sig: util.hex2abv("60e431591ee0b67f0d8a26aacbf5b77f" +
+ "8e0bc6213728c5140546040f0ee37f54"),
+ sig_fail: util.hex2abv("000000001ee0b67f0d8a26aacbf5b77f" +
+ "8e0bc6213728c5140546040f0ee37f54"),
+ },
+
+ // RSA test vectors, Example 1.3
+ // <ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15crypt-vectors.txt>
+ rsaes: {
+ pkcs8: util.hex2abv(
+ "30820276020100300d06092a864886f70d0101010500048202603082025c0201" +
+ "0002818100a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae" +
+ "4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6c630" +
+ "f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fa" +
+ "b9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de" +
+ "88d39f16fb020301000102818053339cfdb79fc8466a655c7316aca85c55fd8f" +
+ "6dd898fdaf119517ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a55" +
+ "3d4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c4831" +
+ "16ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d0c" +
+ "f539e9cfcdd3de653729ead5d1024100d32737e7267ffe1341b2d5c0d150a81b" +
+ "586fb3132bed2f8d5262864a9cb9f30af38be448598d413a172efb802c21acf1" +
+ "c11c520c2f26a471dcad212eac7ca39d024100cc8853d1d54da630fac004f471" +
+ "f281c7b8982d8224a490edbeb33d3e3d5cc93c4765703d1dd791642f1f116a0d" +
+ "d852be2419b2af72bfe9a030e860b0288b5d7702400e12bf1718e9cef5599ba1" +
+ "c3882fe8046a90874eefce8f2ccc20e4f2741fb0a33a3848aec9c9305fbecbd2" +
+ "d76819967d4671acc6431e4037968db37878e695c102410095297b0f95a2fa67" +
+ "d00707d609dfd4fc05c89dafc2ef6d6ea55bec771ea333734d9251e79082ecda" +
+ "866efef13c459e1a631386b7e354c899f5f112ca85d7158302404f456c502493" +
+ "bdc0ed2ab756a3a6ed4d67352a697d4216e93212b127a63d5411ce6fa98d5dbe" +
+ "fd73263e3728142743818166ed7dd63687dd2a8ca1d2f4fbd8e1"
+ ),
+ spki: util.hex2abv(
+ "30819f300d06092a864886f70d010101050003818d0030818902818100a8b3b2" +
+ "84af8eb50b387034a860f146c4919f318763cd6c5598c8ae4811a1e0abc4c7e0" +
+ "b082d693a5e7fced675cf4668512772c0cbc64a742c6c630f533c8cc72f62ae8" +
+ "33c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fab9845cb5148ef739" +
+ "2dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb020301" +
+ "0001"
+ ),
+ data: util.hex2abv(
+ "d94ae0832e6445ce42331cb06d531a82b1db4baad30f746dc916df24d4e3c245" +
+ "1fff59a6423eb0e1d02d4fe646cf699dfd818c6e97b051"
+ ),
+ result: util.hex2abv(
+ "709c7d2d4598c96065b6588da2f89fa87f062d7241ef6595898f637ada57eae9" +
+ "0173f0fb4bf6a91ebd96506907c853dacf208494be94d313a04185d474a90741" +
+ "2effc3e024d07e4d09aa245fbcb130219bfa5de02d4f7e2ec9e62e8ad32dee5f" +
+ "f4d8e4cfecbc5033a1c2c61c5233ae16192a481d0075bfc7ce028212cd27bebe"
+ ),
+ },
+
+ // RSA test vectors, Example 1.3 (sig256 generated with PyCrypto)
+ // <ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15sign-vectors.txt>
+ rsassa: {
+ pkcs8: util.hex2abv(
+ "30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
+ "0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
+ "e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
+ "abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
+ "6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
+ "49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
+ "af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
+ "0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
+ "501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
+ "22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
+ "bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
+ "535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
+ "ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
+ "cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
+ "baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
+ "d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
+ "898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
+ "aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
+ "4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
+ "2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
+ ),
+ jwk_priv: {
+ kty: "RSA",
+ n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+ "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+ "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+ e: "AQAB",
+ d: "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
+ "mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
+ "rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
+ p: "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh31W" +
+ "hU1vZs8w0Fgs7bc0-2o5kQw",
+ q: "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
+ "SDccj5pYzZKH5QlRSsmmmeZ_Q",
+ dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt4" +
+ "kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
+ dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFng" +
+ "lWCdYCo5OjhQVHRUQqCo_LnKQ",
+ qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo2" +
+ "FhBlOshkKz4MrhH8To9JKefTQ"
+ },
+ spki: util.hex2abv(
+ "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
+ "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
+ "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
+ "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
+ "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
+ "0001"
+ ),
+ jwk_pub: {
+ kty: "RSA",
+ n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+ "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+ "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+ e: "AQAB",
+ },
+ data: util.hex2abv(
+ "a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef" +
+ "8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955" +
+ "fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c7" +
+ "45e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4" +
+ "564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c" +
+ "031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa" +
+ "299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef4169713" +
+ "38e7d470"
+ ),
+ sig1: util.hex2abv(
+ "0b1f2e5180e5c7b4b5e672929f664c4896e50c35134b6de4d5a934252a3a245f" +
+ "f48340920e1034b7d5a5b524eb0e1cf12befef49b27b732d2c19e1c43217d6e1" +
+ "417381111a1d36de6375cf455b3c9812639dbc27600c751994fb61799ecf7da6" +
+ "bcf51540afd0174db4033188556675b1d763360af46feeca5b60f882829ee7b2"
+ ),
+ sig256: util.hex2abv(
+ "558af496a9900ec497a51723a0bf1be167a3fdd0e40c95764575bcc93d35d415" +
+ "94aef08cd8d339272387339fe5faa5635a1c4ad6c9b622f8c38edce6b26d9b76" +
+ "e3fec5b567e5b996624c4aeef74191c4349e5ac9e29b848c54bcfa538fec58d5" +
+ "9368253f0ff9a7ba0637918dd16b2c95f8c73ad7484482ba4387655f2f7d4b00"
+ ),
+ sig_fail: util.hex2abv(
+ "8000000080e5c7b4b5e672929f664c4896e50c35134b6de4d5a934252a3a245f" +
+ "f48340920e1034b7d5a5b524eb0e1cf12befef49b27b732d2c19e1c43217d6e1" +
+ "417381111a1d36de6375cf455b3c9812639dbc27600c751994fb61799ecf7da6" +
+ "bcf51540afd0174db4033188556675b1d763360af46feeca5b60f882829ee7b2"
+ ),
+ // This RSA private key has p < q
+ jwk_priv_pLTq: {
+ kty: "RSA",
+ n: "p0HwS3l58WCcN5MPJ3TDu2fMRFZdAFhItiuGMvfDGj0myIM2BhJixSsDleu0h" +
+ "x8mSL4CP9c63-zTFdMjwHluYJ_ugK_jV5c4igfyyD2yQ9IAbaBtSh2GV_PrgM" +
+ "l9XCMobLQonC0ktHcMojYMTNSmLBc1kXLxKq1PnY8ja8oNoNKmDzJt3Hx_KlJ" +
+ "mORwrchrrVzKdrRuj37PmMKKl6MCNThrFFn4PtZ6e59cxgwSAWoOqvvTewCdI" +
+ "H4uGuoEnafBcPsuMOLD-oS0CTml_AK3Wgwt-zJ9BSFSial_PSTg0hpUp8pqWv" +
+ "1NZaxG1HiY3gS-8JHSqTVeoGznFmod3FqRG_Q",
+ e: "AQAB",
+ d: "SrxoBxGMr5KfuyV3DAZcv4yt9Ysxm0nXk673FCcpgrv4bHhU13m3sKp7u63Ky" +
+ "OXeUXq1vpkJsa081O-3dfXMoFhWViJBz42-sc7DaT5IPY3EqzeYHhn7Qam4fZ" +
+ "-K6HS9R3VpAAAb-peHiaPk8x_B8MmeIhPeN1ehz6F6DlwGoulDPOI3EMLoOCL" +
+ "V_cus8AV8il5FaJxwuuo2xc4oEbwT24AN2hXVZekTgPkNSGXBMhagu82lDlx8" +
+ "y7awiC1bchWMUJ88BLqgScbl4bpTLqos0fRXDugY957diwF_zRHdr2jsj8Dm_" +
+ "0J1XdgaIeycIApU61PSUy8ZLZbDQ9mlajRlgQ",
+ p: "uQKh1mjslzbWrDBgqAWcCCPpV9tvA5AAotKuyDRe8ohj-WbKcClwQEXTLqT3Z" +
+ "uirrHrqrmronElxdN22ukpmf_Kk301Kz1HU5qJZKTOzwiO8JSJ7wtLDDWkoyV" +
+ "3Zj6On2N8ZX69cvwbo-S5trIv3iDjfsb0ZvmuGjEn-4dcYUxk",
+ q: "52957s9n0wOBwe5sCtttd_R-h-IX03mZ3Jie4K2GugCZy6H2Rs0hPoylOn0X9" +
+ "eI7atHiP3laaHyEwTOQhdC_RUCLsS-ZMa8p0EaewF_Lk6eCL0pDmHpTZiGjeB" +
+ "EwvftzoO_cEpbkRkF-OxjRs6ejppm3MKkgZZJT2-R5iSaQU4U",
+ dp: "ijDlIXoN_oT_pG4eRGKsQYhRa0aEjWyqjPRBiVlU8mPeCRQ2ccECD4AYVebyx" +
+ "PNWB-doFA_W36YcEObq7gtUtI1RiVn6XxEIrZzmbFgqFQEML9CqEMPM3d-Gj6" +
+ "KCN0BOxzcdhNM_u5A1xKphUVja8-1HaUOOTyWRwogi0h4QFUE",
+ dq: "KN1yNkzBFG1mGAw1X6VnKuss_Glbs6ehF2aLhzictXMttNsgVVgbKqRC-JTmC" +
+ "jCsNSxiOrr-z7xM5KBqQHafj2baQ6sX7cH0LCaMGYPQun21aww960qON1ZxOt" +
+ "4uMR2ZSS2ROmcSX6Vo2J6FSKetKdmykxEJ-2VfEVDCdQkuKtE",
+ qi: "pDhm0DZADZiGIsjYMmpoDo4EmYHari-VfxjAqCgcec5nPfNt5BSKQow3_E_v0" +
+ "Yik1qa-AGWuC8vTh8vUFsQ0rE1lVSgXYPalMTjLFNY_hCBXmsDfMS5vcsL0-G" +
+ "Y8F2U_XRY3WEaoNPb9UZqzgp7xl6_XM__2U47LIoUpCgcN9RA",
+ },
+ },
+
+ // RSA test vectors, oaep-vect.txt, Example 1.1: A 1024-bit RSA Key Pair
+ // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+ rsaoaep: {
+ pkcs8: util.hex2abv(
+ "30820276020100300d06092a864886f70d0101010500048202603082025c0201" +
+ "0002818100a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae" +
+ "4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6c630" +
+ "f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fa" +
+ "b9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de" +
+ "88d39f16fb020301000102818053339cfdb79fc8466a655c7316aca85c55fd8f" +
+ "6dd898fdaf119517ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a55" +
+ "3d4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c4831" +
+ "16ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d0c" +
+ "f539e9cfcdd3de653729ead5d1024100d32737e7267ffe1341b2d5c0d150a81b" +
+ "586fb3132bed2f8d5262864a9cb9f30af38be448598d413a172efb802c21acf1" +
+ "c11c520c2f26a471dcad212eac7ca39d024100cc8853d1d54da630fac004f471" +
+ "f281c7b8982d8224a490edbeb33d3e3d5cc93c4765703d1dd791642f1f116a0d" +
+ "d852be2419b2af72bfe9a030e860b0288b5d7702400e12bf1718e9cef5599ba1" +
+ "c3882fe8046a90874eefce8f2ccc20e4f2741fb0a33a3848aec9c9305fbecbd2" +
+ "d76819967d4671acc6431e4037968db37878e695c102410095297b0f95a2fa67" +
+ "d00707d609dfd4fc05c89dafc2ef6d6ea55bec771ea333734d9251e79082ecda" +
+ "866efef13c459e1a631386b7e354c899f5f112ca85d7158302404f456c502493" +
+ "bdc0ed2ab756a3a6ed4d67352a697d4216e93212b127a63d5411ce6fa98d5dbe" +
+ "fd73263e3728142743818166ed7dd63687dd2a8ca1d2f4fbd8e1"
+ ),
+ spki: util.hex2abv(
+ "30819f300d06092a864886f70d010101050003818d0030818902818100a8b3b2" +
+ "84af8eb50b387034a860f146c4919f318763cd6c5598c8ae4811a1e0abc4c7e0" +
+ "b082d693a5e7fced675cf4668512772c0cbc64a742c6c630f533c8cc72f62ae8" +
+ "33c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fab9845cb5148ef739" +
+ "2dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb020301" +
+ "0001"
+ ),
+ data: util.hex2abv(
+ "6628194e12073db03ba94cda9ef9532397d50dba79b987004afefe34"
+ ),
+ result: util.hex2abv(
+ "354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad468fb" +
+ "21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618c21a535f" +
+ "a9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e657a05a266426" +
+ "d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5210035d47ac72e8a"
+ ),
+ },
+
+ // [pss-vect.txt] Example 1.1 from
+ // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+ rsapss: {
+ pkcs8: util.hex2abv(
+ "30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
+ "0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
+ "e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
+ "abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
+ "6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
+ "49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
+ "af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
+ "0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
+ "501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
+ "22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
+ "bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
+ "535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
+ "ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
+ "cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
+ "baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
+ "d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
+ "898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
+ "aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
+ "4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
+ "2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
+ ),
+ spki: util.hex2abv(
+ "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
+ "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
+ "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
+ "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
+ "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
+ "0001"
+ ),
+ data: util.hex2abv(
+ "cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b6" +
+ "2371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb76" +
+ "9757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb0" +
+ "61a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d61" +
+ "93c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c" +
+ "296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16" +
+ "be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0"
+ ),
+ sig: util.hex2abv(
+ "9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887" +
+ "e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215" +
+ "df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65" +
+ "984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"
+ ),
+ saltLength: 20,
+ jwk_priv: {
+ kty: "RSA",
+ n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+ "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+ "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+ e: "AQAB",
+ d: "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
+ "mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
+ "rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
+ p: "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh3" +
+ "1WhU1vZs8w0Fgs7bc0-2o5kQw",
+ q: "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
+ "SDccj5pYzZKH5QlRSsmmmeZ_Q",
+ dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt" +
+ "4kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
+ dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFn" +
+ "glWCdYCo5OjhQVHRUQqCo_LnKQ",
+ qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo" +
+ "2FhBlOshkKz4MrhH8To9JKefTQ",
+ },
+ jwk_pub: {
+ kty: "RSA",
+ n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+ "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+ "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+ e: "AQAB",
+ },
+ },
+
+ // [pss-vect.txt] Example 1.4 from
+ // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+ rsapss2: {
+ spki: util.hex2abv(
+ "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
+ "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
+ "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
+ "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
+ "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
+ "0001"
+ ),
+ data: util.hex2abv(
+ "bc656747fa9eafb3f0"
+ ),
+ sig: util.hex2abv(
+ "4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c" +
+ "178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad" +
+ "8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e18" +
+ "33b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87"
+ ),
+ saltLength: 20
+ },
+
+ // [SigVerPSS_186-3.rsp] from
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+ rsapss3: {
+ spki: util.hex2abv(
+ "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+ "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+ "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+ "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+ "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+ ),
+ data: util.hex2abv(
+ "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+ "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+ "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+ "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+ ),
+ sig: util.hex2abv(
+ "11e169f2fd40b07641b9768a2ab19965fb6c27f10fcf0323fcc6d12eb4f1c06b" +
+ "330ddaa1ea504407afa29de9ebe0374fe9d1e7d0ffbd5fc1cf3a3446e4145415" +
+ "d2ab24f789b3464c5c43a256bbc1d692cf7f04801dac5bb401a4a03ab7d5728a" +
+ "860c19e1a4dc797ca542c8203cec2e601eb0c51f567f2eda022b0b9ebddeeefa"
+ ),
+ saltLength: 10
+ },
+
+ // [SigVerPSS_186-3.rsp] from
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+ rsapss4: {
+ spki: util.hex2abv(
+ "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+ "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+ "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+ "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+ "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+ ),
+ data: util.hex2abv(
+ "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+ "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+ "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+ "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+ ),
+ sig: util.hex2abv(
+ "b281ad934b2775c0cba5fb10aa574d2ed85c7f99b942b78e49702480069362ed" +
+ "394baded55e56cfcbe7b0b8d2217a05a60e1acd725cb09060dfac585bc2132b9" +
+ "9b41cdbd530c69d17cdbc84bc6b9830fc7dc8e1b2412cfe06dcf8c1a0cc3453f" +
+ "93f25ebf10cb0c90334fac573f449138616e1a194c67f44efac34cc07a526267"
+ ),
+ saltLength: 10
+ },
+
+ // [SigVerPSS_186-3.rsp] from
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+ rsapss5: {
+ spki: util.hex2abv(
+ "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+ "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+ "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+ "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+ "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+ ),
+ data: util.hex2abv(
+ "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+ "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+ "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+ "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+ ),
+ sig: util.hex2abv(
+ "8ffc38f9b820ef6b080fd2ec7de5626c658d79056f3edf610a295b7b0546f73e" +
+ "01ffdf4d0070ebf79c33fd86c2d608be9438b3d420d09535b97cd3d846ecaf8f" +
+ "6551cdf93197e9f8fb048044473ab41a801e9f7fc983c62b324361dade9f71a6" +
+ "5952bd35c59faaa4d6ff462f68a6c4ec0b428aa47336f2178aeb276136563b7d"
+ ),
+ saltLength: 10
+ },
+
+ key_wrap_known_answer: {
+ key: util.hex2abv("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"),
+ wrapping_key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ wrapping_iv: util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+ wrapped_key: util.hex2abv("9ed0283a9a2b7e4292ebc5135e6342cc" +
+ "8a7f65802a1f6fd41bd3251c4da0c138")
+ },
+
+ // AES Key Wrap
+ // From RFC 3394, "Wrap 128 bits of Key Data with a 256-bit KEK"
+ // http://tools.ietf.org/html/rfc3394#section-4.3
+ aes_kw: {
+ wrapping_key: {
+ kty: "oct",
+ alg: "A256KW",
+ k: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8"
+ },
+ key: {
+ kty: "oct",
+ k: "ABEiM0RVZneImaq7zN3u_w"
+ },
+ wrapped_key: util.hex2abv("64e8c3f9ce0f5ba263e9777905818a2a"+
+ "93c8191e7d6e8ae7")
+ },
+
+ // RFC 6070 <http://tools.ietf.org/html/rfc6070>
+ pbkdf2_sha1: {
+ password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
+ salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
+ iterations: 4096,
+ length: 25 * 8,
+
+ derived: util.hex2abv(
+ "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"
+ ),
+
+ jwk: {
+ kty: "oct",
+ k: "cGFzc3dvcmRQQVNTV09SRHBhc3N3b3Jk"
+ }
+ },
+
+ // https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
+ pbkdf2_sha256: {
+ password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
+ salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
+ iterations: 4096,
+ length: 40 * 8,
+
+ derived: util.hex2abv(
+ "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1" +
+ "c635518c7dac47e9"
+ )
+ },
+
+ pbkdf2_sha256_no_salt: {
+ password: util.hex2abv(
+ "9c13a23bc58a52be8bb4fa1a2cbdff01747265736f7269745f64625f7265636f72645f6964"),
+ length: 32 * 8,
+ iterations: 1,
+
+ derived: util.hex2abv(
+ "ef29dd382fa66a83a95be7ccfb71f1ccfee494977855a4c260d90c2f8c91e062")
+ },
+
+ broken_pkcs8: {
+ // A DH key with parameters p and g, and a private value.
+ // This currently fails the key import due to the missing public value.
+ // <https://stackoverflow.com/questions/6032675/diffie-hellman-test-vectors>
+ dh: util.hex2abv(
+ "308201340201003082011506072a8648ce3e02013082010802818100da3a8085" +
+ "d372437805de95b88b675122f575df976610c6a844de99f1df82a06848bf7a42" +
+ "f18895c97402e81118e01a00d0855d51922f434c022350861d58ddf60d65bc69" +
+ "41fc6064b147071a4c30426d82fc90d888f94990267c64beef8c304a4b2b26fb" +
+ "93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba042c73a6ade3502818100" +
+ "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5e3e" +
+ "5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc623f648" +
+ "fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f588d535e4" +
+ "c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c09742de989547288" +
+ "0416021442c6ee70beb7465928a1efe692d2281b8f7b53d6"
+ )
+ },
+
+ // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EC]
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+ ecdh_p256: {
+ jwk_pub: {
+ kty: "EC",
+ crv: "P-256",
+ x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+ y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+ },
+
+ jwk_priv: {
+ kty: "EC",
+ crv: "P-256",
+ d: "qq_LEzeJpR00KM5DQvL2MNtJcbi0KcGVcoPIHNnwm2A",
+ x: "FNwJHA-FwnSx5tKXFV_iLN408gbKUHRV06WnQlzTdN4",
+ y: "is9pWAaneK4RdxmdLfsq5IwizDmUS2w8OGS99sKm3ek"
+ },
+
+ // vector with algorithm = id-ecDH
+ spki: util.hex2abv(
+ "3056301006042b81047006082a8648ce3d030107034200045ce7b86e3b326604" +
+ "03e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39405dd1" +
+ "f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+ ),
+
+ // vector with algorithm = id-ecPublicKey
+ spki_id_ecpk: util.hex2abv(
+ "3059301306072a8648ce3d020106082a8648ce3d030107034200045ce7b86e3b" +
+ "32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39" +
+ "405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+ ),
+
+ raw: util.hex2abv(
+ "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+ "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+ ),
+
+ secret: util.hex2abv(
+ "35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d"
+ )
+ },
+
+ // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [ED]
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+ ecdh_p384: {
+ jwk_pub: {
+ kty: "EC",
+ crv: "P-384",
+ x: "YoV6fhCph4kyt7sUkqiZOtbRs0rF6etPqlnrn1nzSB95NElaw4uTK7Pn2nlFFqqH",
+ y: "bf3tRz6icq3-W6hhmoqDTBKjdOQUJ5xHr5kX4X-h5MZk_P_nCrG3IUVl1SAbhWDw"
+ },
+
+ jwk_priv: {
+ kty: "EC",
+ crv: "P-384",
+ d: "RT8f0pRw4CL1Tgk4rwuNnNbFoQBNTTBkr7WVLLm4fDA3boYZpNB_t-rbMVLx0CRp",
+ x: "_XwhXRnOzEfCsWIRCz3QLClaDkigQFvXmqYNdh_7vJdADykPbfGi1VgAu3XJdXoD",
+ y: "S1P_FBCXYGE-5VPvTCRnFT7bPIPmUPV9qKTM24TQFYEUgIDfzCLsyGCWK-rhP6jU"
+ },
+
+ secret: util.hex2abv(
+ "a3d28aa18f905a48a5f166b4ddbf5f6b499e43858ccdd80b869946aba2c5d461" +
+ "db6a1e5b1137687801878ff0f8d9a7b3"
+ )
+ },
+
+ // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EE]
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+ ecdh_p521: {
+ jwk_pub: {
+ kty: "EC",
+ crv: "P-521",
+ x: "AeCLgRZ-BPqfhq4jt409-E26VHW5l29q74cHbIbQiS_-Gcqdo-087jHdPXUksGpr" +
+ "Nyp_RcTZd94t3peXzQziQIqo",
+ y: "AZIAp8QVnU9hBOkLScv0dy540uGtBWHkWj4DGh-Exh4iWZ0E-YBS8-HVx2eB-nfG" +
+ "AGEy4-BzfpFFlfidOS1Tg77J"
+ },
+
+ jwk_priv: {
+ kty: "EC",
+ crv: "P-521",
+ d: "ABtsfkDGFarQU4kb7e2gPszGCTT8GLDaaJbFQenFZce3qp_dh0qZarXHKBZ-BVic" +
+ "NeIW5Sk661UoNfwykSvmh77S",
+ x: "AcD_6Eb4A-8QdUM70c6F0WthN1kvV4fohS8QHbod6B4y1ZDU54mQuCR-3IBjcV1c" +
+ "oh18uxbyUn5szMuCgjZUiD0y",
+ y: "AU3WKJffztkhAQetBXaLvUSIHa87HMn8vZFB04lWipH-SrsrAu_4N-6iam0OD4EJ" +
+ "0kOMH8iEh7yuivaKsFRzm2-m"
+ },
+
+ secret: util.hex2abv(
+ "00561eb17d856552c21b8cbe7d3d60d1ea0db738b77d4050fa2dbd0773edc395" +
+ "09854d9e30e843964ed3fd303339e338f31289120a38f94e9dc9ff7d4b3ea8f2" +
+ "5e01"
+ )
+ },
+
+ // Some test vectors that we should fail to import.
+ ecdh_p256_negative: {
+ // The given curve doesn't exist / isn't supported.
+ jwk_bad_crv: {
+ kty: "EC",
+ crv: "P-123",
+ x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+ y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+ },
+
+ // The crv parameter is missing.
+ jwk_missing_crv: {
+ kty: "EC",
+ x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+ y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+ },
+
+ // The X coordinate is missing.
+ jwk_missing_x: {
+ kty: "EC",
+ crv: "P-256",
+ y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+ },
+
+ // The Y coordinate is missing.
+ jwk_missing_y: {
+ kty: "EC",
+ crv: "P-256",
+ x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+ },
+
+ // Public point with Y not on the curve.
+ raw_bad: util.hex2abv(
+ "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+ "98f4cf075b39405dd1f1adeb090106edcfb2b4963739d87679e3056cb0557d0adf"
+ ),
+
+ // Public point with Y a little too short.
+ raw_short: util.hex2abv(
+ "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+ "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0a"
+ ),
+
+ // Public point with Y a little too long.
+ raw_long: util.hex2abv(
+ "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+ "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adfff"
+ ),
+
+ // Public point with EC_POINT_FORM_COMPRESSED_Y0.
+ raw_compressed: util.hex2abv(
+ "025ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+ "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+ )
+ },
+
+ // NIST ECDSA test vectors
+ // http://csrc.nist.gov/groups/STM/cavp/index.html
+ ecdsa_verify: {
+ pub_jwk: {
+ "kty": "EC",
+ "crv": "P-521",
+
+ // 0061387fd6b95914e885f912edfbb5fb274655027f216c4091ca83e19336740fd8
+ // 1aedfe047f51b42bdf68161121013e0d55b117a14e4303f926c8debb77a7fdaad1
+ "x": "AGE4f9a5WRTohfkS7fu1-ydGVQJ_IWxAkcqD4ZM2dA_Y" +
+ "Gu3-BH9RtCvfaBYRIQE-DVWxF6FOQwP5Jsjeu3en_arR",
+ // 00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3
+ // f58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6
+ "y": "AOfQx1w4Ym6JXKIVJrn5_fhNzsuT8rIzOQVQ0rFGO37j" +
+ "9Y33NGQ1_wQ0GZWDyXxmWpfxL3BvI1faS0Aoje-Ijlnm",
+ },
+
+ raw: util.hex2abv(
+ "040061387fd6b95914e885f912edfbb5fb274655027f216c4091ca83e19336740fd" +
+ "81aedfe047f51b42bdf68161121013e0d55b117a14e4303f926c8debb77a7fdaad1" +
+ "00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3f" +
+ "58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6"
+ ),
+
+ "data": util.hex2abv(
+ "9ecd500c60e701404922e58ab20cc002651fdee7cbc9336adda33e4c1088fab1" +
+ "964ecb7904dc6856865d6c8e15041ccf2d5ac302e99d346ff2f686531d255216" +
+ "78d4fd3f76bbf2c893d246cb4d7693792fe18172108146853103a51f824acc62" +
+ "1cb7311d2463c3361ea707254f2b052bc22cb8012873dcbb95bf1a5cc53ab89f"
+ ),
+ "sig": util.hex2abv(
+ "004de826ea704ad10bc0f7538af8a3843f284f55c8b946af9235af5af74f2b76e0" +
+ "99e4bc72fd79d28a380f8d4b4c919ac290d248c37983ba05aea42e2dd79fdd33e8" +
+ "0087488c859a96fea266ea13bf6d114c429b163be97a57559086edb64aed4a1859" +
+ "4b46fb9efc7fd25d8b2de8f09ca0587f54bd287299f47b2ff124aac566e8ee3b43"
+ ),
+
+ // Same as "sig", but with the last few octets set to 0
+ "sig_tampered": util.hex2abv(
+ "004de826ea704ad10bc0f7538af8a3843f284f55c8b946af9235af5af74f2b76e0" +
+ "99e4bc72fd79d28a380f8d4b4c919ac290d248c37983ba05aea42e2dd79fdd33e8" +
+ "0087488c859a96fea266ea13bf6d114c429b163be97a57559086edb64aed4a1859" +
+ "4b46fb9efc7fd25d8b2de8f09ca0587f54bd287299f47b2ff124aac56600000000"
+ )
+ },
+
+ ecdsa_bad: {
+ pub_jwk: {
+ "kty": "EC",
+ "crv": "P-521",
+ "x": "BhOH_WuVkU6IX5Eu37tfsnRlUCfyFsQJHKg-GTNnQP2B" +
+ "rt_gR_UbQr32gWESEBPg1VsRehTkMD-SbI3rt3p_2q0B",
+ "y": "AUNouOdGgHsraPNhXNeNdhpGTd15GPyN9R0iWWL98ePc" +
+ "JD4mUQD/DsEzNZ4zLkTdSa/Y5fOP6GEzVzQy0zwC+goD"
+ }
+ },
+
+ // RFC 2409 <http://tools.ietf.org/html/rfc2409#section-6.2>
+ dh: {
+ prime: util.hex2abv(
+ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" +
+ "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" +
+ "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" +
+ "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"
+ ),
+
+ prime2: util.hex2abv(
+ "8b79f180cbd3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff" +
+ "748c2abf6226cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26" +
+ "cd64fa97819181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db" +
+ "3cb670c73846bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d09"
+ ),
+ },
+
+ // KASValidityTest_FFCStatic_NOKC_ZZOnly_resp.fax [FA]
+ // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+ dh_nist: {
+ prime: util.hex2abv(
+ "8b79f180cbd3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff" +
+ "748c2abf6226cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26" +
+ "cd64fa97819181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db" +
+ "3cb670c73846bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d09"
+ ),
+
+ gen: util.hex2abv(
+ "029843c81d0ea285c41a49b1a2f8e11a56a4b39040dfbc5ec040150c16f72f87" +
+ "4152f9c44c659d86f7717b2425b62597e9a453b13da327a31cde2cced6009152" +
+ "52d30262d1e54f4f864ace0e484f98abdbb37ebb0ba4106af5f0935b744677fa" +
+ "2f7f3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a93"
+ ),
+
+ raw: util.hex2abv(
+ "4fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d7271f8b266bb39" +
+ "0af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff677a08b0c7c50911" +
+ "0b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77cf5464b316aa49" +
+ "fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636ba3d9acbf9e8ac"
+ ),
+
+ spki: util.hex2abv(
+ "308201a33082011806092a864886f70d01030130820109028181008b79f180cb" +
+ "d3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff748c2abf62" +
+ "26cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26cd64fa9781" +
+ "9181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db3cb670c738" +
+ "46bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d090281800298" +
+ "43c81d0ea285c41a49b1a2f8e11a56a4b39040dfbc5ec040150c16f72f874152" +
+ "f9c44c659d86f7717b2425b62597e9a453b13da327a31cde2cced600915252d3" +
+ "0262d1e54f4f864ace0e484f98abdbb37ebb0ba4106af5f0935b744677fa2f7f" +
+ "3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a930000" +
+ "038184000281804fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d" +
+ "7271f8b266bb390af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff67" +
+ "7a08b0c7c509110b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77" +
+ "cf5464b316aa49fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636" +
+ "ba3d9acbf9e8ac"
+ )
+ },
+
+ // Taken from appendix A of RFC 5869.
+ // <https://tools.ietf.org/html/rfc5869>
+ hkdf: [
+ {
+ prf: "SHA-256",
+ key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ salt: util.hex2abv("000102030405060708090a0b0c"),
+ info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
+ data: util.hex2abv(
+ "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+ "34007208d5b887185865"
+ ),
+ jwk: {
+ kty: "oct",
+ k: "CwsLCwsLCwsLCwsLCwsLCwsLCwsLCw"
+ }
+ },
+ {
+ prf: "SHA-256",
+ key: util.hex2abv(
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+ "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+ "404142434445464748494a4b4c4d4e4f"
+ ),
+ salt: util.hex2abv(
+ "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+ "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ ),
+ info: util.hex2abv(
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+ ),
+ data: util.hex2abv(
+ "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" +
+ "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" +
+ "cc30c58179ec3e87c14c01d5c1f3434f1d87"
+ )
+ },
+ {
+ prf: "SHA-256",
+ key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ salt: util.hex2abv(""),
+ info: util.hex2abv(""),
+ data: util.hex2abv(
+ "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" +
+ "9d201395faa4b61a96c8"
+ )
+ },
+ {
+ prf: "SHA-1",
+ key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b"),
+ salt: util.hex2abv("000102030405060708090a0b0c"),
+ info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
+ data: util.hex2abv(
+ "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2" +
+ "c22e422478d305f3f896"
+ )
+ },
+ {
+ prf: "SHA-1",
+ key: util.hex2abv(
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+ "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+ "404142434445464748494a4b4c4d4e4f"
+ ),
+ salt: util.hex2abv(
+ "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+ "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ ),
+ info: util.hex2abv(
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+ ),
+ data: util.hex2abv(
+ "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe" +
+ "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e" +
+ "927336d0441f4c4300e2cff0d0900b52d3b4"
+ )
+ },
+ {
+ prf: "SHA-1",
+ key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ salt: util.hex2abv(""),
+ info: util.hex2abv(""),
+ data: util.hex2abv(
+ "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0" +
+ "ea00033de03984d34918"
+ )
+ },
+ {
+ prf: "SHA-1",
+ key: util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+ salt: util.hex2abv(""),
+ info: util.hex2abv(""),
+ data: util.hex2abv(
+ "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5" +
+ "673a081d70cce7acfc48"
+ )
+ }
+ ]
+}
diff --git a/dom/crypto/test/test-worker.js b/dom/crypto/test/test-worker.js
new file mode 100644
index 000000000..02f248fae
--- /dev/null
+++ b/dom/crypto/test/test-worker.js
@@ -0,0 +1,44 @@
+/* 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/. */
+
+importScripts("util.js");
+importScripts("test-vectors.js");
+
+var window = this;
+
+function finish(result) {
+ postMessage(result);
+}
+
+function complete(test, valid) {
+ return function(x) {
+ if (valid) {
+ finish(valid(x));
+ } else {
+ finish(true);
+ }
+ };
+}
+
+function memcmp_complete(test, value) {
+ return function (x) {
+ finish(util.memcmp(x, value));
+ };
+}
+
+function error(test) {
+ return function (x) {
+ throw x;
+ };
+}
+
+onmessage = function (msg) {
+ var test = eval("(" + msg.data + ")");
+
+ try {
+ test.call({complete: finish});
+ } catch (err) {
+ error(`Failed to run worker test: ${err}\n`);
+ }
+};
diff --git a/dom/crypto/test/test_WebCrypto.css b/dom/crypto/test/test_WebCrypto.css
new file mode 100644
index 000000000..ab84d1b1a
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto.css
@@ -0,0 +1,86 @@
+/* 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/. */
+
+body {
+ font-family: Helvetica Neue, Helvetica, Trebuchet MS, Sans-serif;
+ font-size: 12pt;
+ text-align: center;
+}
+
+a {
+ color: #FF9500;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+#content {
+ width: 50em;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: left;
+}
+
+#head {
+ font-family: Helvetica Neue, Helvetica, Trebuchet MS, Sans-serif;
+ font-size: 300%;
+ font-weight: lighter;
+ padding: .2ex;
+ padding-bottom: 0ex;
+ margin-bottom: .5ex;
+ border-bottom: 10px solid #FF9500;
+}
+#head b {
+ font-weight: bold;
+ color: #FF9500;
+}
+
+div.content {
+ font-family: Helvetica Neue, Helvetica, Trebuchet MS, Sans-serif;
+ color: #000;
+ margin: 2ex;
+}
+
+#foot {
+ border-bottom: 1ex solid #FF9500;
+ margin-top: 2ex;
+}
+
+/*------------------------------------------*/
+
+#start {
+ background: #FF9500;
+ color: #fff;
+ text-align: center;
+ font-weight: bold;
+ padding: 1em 0 1em 0;
+ width: 50em;
+ cursor: pointer;
+}
+
+
+#results {
+ text-align: left;
+ width: 48em;
+ border: 1px solid black;
+}
+
+.pass {
+ font-weight: bold;
+ color: #00539F;
+}
+
+.fail {
+ font-weight: bold;
+ color: #FF9500;
+}
+
+.pending {
+ font-weight: bold;
+ color: #666;
+}
+
+
diff --git a/dom/crypto/test/test_WebCrypto.html b/dom/crypto/test/test_WebCrypto.html
new file mode 100644
index 000000000..6dd22d88f
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto.html
@@ -0,0 +1,1077 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test for presence of WebCrypto API methods",
+ function() {
+ var that = this;
+ this.complete(
+ exists(window.crypto.subtle) &&
+ exists(window.crypto.subtle.encrypt) &&
+ exists(window.crypto.subtle.decrypt) &&
+ exists(window.crypto.subtle.sign) &&
+ exists(window.crypto.subtle.verify) &&
+ exists(window.crypto.subtle.digest) &&
+ exists(window.crypto.subtle.importKey) &&
+ exists(window.crypto.subtle.exportKey) &&
+ exists(window.crypto.subtle.generateKey) &&
+ exists(window.crypto.subtle.deriveKey) &&
+ exists(window.crypto.subtle.deriveBits)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Clean failure on a mal-formed algorithm",
+ function() {
+ var that = this;
+ var alg = {
+ get name() {
+ throw "Oh no, no name!";
+ }
+ };
+
+ crypto.subtle.importKey("raw", tv.raw, alg, true, ["encrypt"])
+ .then(
+ error(that),
+ complete(that, function(x) { return true; })
+ );
+ }
+)
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import / export round-trip with 'raw'",
+ function() {
+ var that = this;
+ var alg = "AES-GCM";
+
+ function doExport(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ } else if ((x.algorithm.name != alg) ||
+ (x.algorithm.length != 8 * tv.raw.length) ||
+ (x.type != "secret") ||
+ (!x.extractable) ||
+ (x.usages.length != 1) ||
+ (x.usages[0] != 'encrypt')){
+ throw "Invalid key: incorrect key data";
+ }
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", tv.raw, alg, true, ["encrypt"])
+ .then(doExport)
+ .then(
+ memcmp_complete(that, tv.raw),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import failure with format 'raw'",
+ function() {
+ var that = this;
+ var alg = "AES-GCM";
+
+ crypto.subtle.importKey("raw", tv.negative_raw, alg, true, ["encrypt"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Proper handling of an ABV representing part of a buffer",
+ function() {
+ var that = this;
+ var alg = "AES-GCM";
+
+ var u8 = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]);
+ var u32 = new Uint32Array(u8.buffer, 8, 4);
+ var out = u8.subarray(8, 24)
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", u32, alg, true, ["encrypt"])
+ .then(doExport, error(that))
+ .then(memcmp_complete(that, out), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import / export round-trip with 'pkcs8'",
+ function() {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
+
+ function doExport(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ } else if ((x.algorithm.name != alg.name) ||
+ (x.algorithm.hash.name != alg.hash) ||
+ (x.algorithm.modulusLength != 512) ||
+ (x.algorithm.publicExponent.byteLength != 3) ||
+ (x.type != "private") ||
+ (!x.extractable) ||
+ (x.usages.length != 1) ||
+ (x.usages[0] != 'sign')){
+ throw "Invalid key: incorrect key data";
+ }
+ return crypto.subtle.exportKey("pkcs8", x);
+ }
+
+ crypto.subtle.importKey("pkcs8", tv.pkcs8, alg, true, ["sign"])
+ .then(doExport)
+ .then(
+ memcmp_complete(that, tv.pkcs8),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import failure with format 'pkcs8'",
+ function() {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
+
+ crypto.subtle.importKey("pkcs8", tv.negative_pkcs8, alg, true, ["encrypt"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import / export round-trip with 'spki'",
+ function() {
+ var that = this;
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256"
+ };
+
+ function doExport(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ } else if ((x.algorithm.name != alg.name) ||
+ (x.algorithm.modulusLength != 1024) ||
+ (x.algorithm.publicExponent.byteLength != 3) ||
+ (x.type != "public") ||
+ (!x.extractable) ||
+ (x.usages.length != 1) ||
+ (x.usages[0] != 'verify')){
+ throw "Invalid key: incorrect key data";
+ }
+ return crypto.subtle.exportKey("spki", x);
+ }
+
+ crypto.subtle.importKey("spki", tv.spki, alg, true, ["verify"])
+ .then(doExport, error(that))
+ .then(
+ memcmp_complete(that, tv.spki),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import failure with format 'spki'",
+ function() {
+ var that = this;
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256"
+ };
+
+ crypto.subtle.importKey("spki", tv.negative_spki, alg, true, ["encrypt"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Refuse to export non-extractable key",
+ function() {
+ var that = this;
+ var alg = "AES-GCM";
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", tv.raw, alg, false, ["encrypt"])
+ .then(doExport, error(that))
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "IndexedDB store / retrieve round-trip",
+ function() {
+ var that = this;
+ var alg = "AES-GCM";
+ var importedKey;
+ var dbname = "keyDB";
+ var dbstore = "keystore";
+ var dbversion = 1;
+ var dbkey = 0;
+ var db;
+
+ function doIndexedDB(x) {
+ importedKey = x;
+ var req = indexedDB.deleteDatabase(dbname);
+ req.onerror = error(that);
+ req.onsuccess = doCreateDB;
+ }
+
+ function doCreateDB() {
+ var req = indexedDB.open(dbname, dbversion);
+ req.onerror = error(that);
+ req.onupgradeneeded = function(e) {
+ db = e.target.result;
+ db.createObjectStore(dbstore, {keyPath: "id"});
+ }
+
+ req.onsuccess = doPut;
+ }
+
+ function doPut() {
+ var req = db.transaction([dbstore], "readwrite")
+ .objectStore(dbstore)
+ .add({id: dbkey, val: importedKey});
+ req.onerror = error(that);
+ req.onsuccess = doGet;
+ }
+
+ function doGet() {
+ var req = db.transaction([dbstore], "readwrite")
+ .objectStore(dbstore)
+ .get(dbkey);
+ req.onerror = error(that);
+ req.onsuccess = complete(that, function(e) {
+ db.close();
+ return hasKeyFields(e.target.result.val);
+ });
+ }
+
+ crypto.subtle.importKey("raw", tv.raw, alg, false, ['encrypt'])
+ .then(doIndexedDB, error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a 256-bit HMAC-SHA-256 key",
+ function() {
+ var that = this;
+ var alg = { name: "HMAC", length: 256, hash: {name: "SHA-256"} };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+ complete(that, function(x) {
+ return hasKeyFields(x) && x.algorithm.length == 256;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a 256-bit HMAC-SHA-256 key without specifying a key length",
+ function() {
+ var that = this;
+ var alg = { name: "HMAC", hash: {name: "SHA-256"} };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+ complete(that, function(x) {
+ return hasKeyFields(x) && x.algorithm.length == 512;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a 256-bit HMAC-SHA-512 key without specifying a key length",
+ function() {
+ var that = this;
+ var alg = { name: "HMAC", hash: {name: "SHA-512"} };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+ complete(that, function(x) {
+ return hasKeyFields(x) && x.algorithm.length == 1024;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Fail generating an HMAC key when specifying an invalid hash algorithm",
+ function() {
+ var that = this;
+ var alg = { name: "HMAC", hash: {name: "SHA-123"} };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+ error(that),
+ complete(that, function() { return true; })
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Fail generating an HMAC key when specifying a zero length",
+ function() {
+ var that = this;
+ var alg = { name: "HMAC", hash: {name: "SHA-256"}, length: 0 };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+ error(that),
+ complete(that, function() { return true; })
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a 192-bit AES key",
+ function() {
+ var that = this;
+ var alg = { name: "AES-GCM", length: 192 };
+ crypto.subtle.generateKey(alg, true, ["encrypt"]).then(
+ complete(that, function(x) {
+ return hasKeyFields(x);
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a 1024-bit RSA key",
+ function() {
+ var that = this;
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 1024,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"]).then(
+ complete(that, function(x) {
+ return exists(x.publicKey) &&
+ (x.publicKey.algorithm.name == alg.name) &&
+ (x.publicKey.algorithm.modulusLength == alg.modulusLength) &&
+ (x.publicKey.type == "public") &&
+ x.publicKey.extractable &&
+ (x.publicKey.usages.length == 1) &&
+ (x.publicKey.usages[0] == "verify") &&
+ exists(x.privateKey) &&
+ (x.privateKey.algorithm.name == alg.name) &&
+ (x.privateKey.algorithm.modulusLength == alg.modulusLength) &&
+ (x.privateKey.type == "private") &&
+ !x.privateKey.extractable &&
+ (x.privateKey.usages.length == 1) &&
+ (x.privateKey.usages[0] == "sign");
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Fail cleanly when NSS refuses to generate a key pair",
+ function() {
+ var that = this;
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 2299, // NSS does not like this key length
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ crypto.subtle.generateKey(alg, false, ["sign"])
+ .then( error(that), complete(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "SHA-256 digest",
+ function() {
+ var that = this;
+ crypto.subtle.digest("SHA-256", tv.sha256.data).then(
+ memcmp_complete(that, tv.sha256.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Fail cleanly on unknown hash algorithm",
+ function() {
+ var that = this;
+ crypto.subtle.digest("GOST-34_311-95", tv.sha256.data).then(
+ error(that),
+ complete(that, function() { return true; })
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CBC encrypt",
+ function () {
+ var that = this;
+
+ function doEncrypt(x) {
+ return crypto.subtle.encrypt(
+ { name: "AES-CBC", iv: tv.aes_cbc_enc.iv },
+ x, tv.aes_cbc_enc.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_cbc_enc.key, "AES-CBC", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ memcmp_complete(that, tv.aes_cbc_enc.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CBC encrypt with wrong IV size",
+ function () {
+ var that = this;
+
+ function encrypt(x, iv) {
+ return crypto.subtle.encrypt(
+ { name: "AES-CBC", iv: iv },
+ x, tv.aes_cbc_enc.data);
+ }
+
+ function doEncrypt(x) {
+ return encrypt(x, new Uint8Array(15))
+ .then(
+ null,
+ function () { return encrypt(new Uint8Array(17)); }
+ );
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_cbc_enc.key, "AES-CBC", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CBC decrypt",
+ function () {
+ var that = this;
+
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(
+ { name: "AES-CBC", iv: tv.aes_cbc_dec.iv },
+ x, tv.aes_cbc_dec.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_cbc_dec.key, "AES-CBC", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ memcmp_complete(that, tv.aes_cbc_dec.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CBC decrypt with wrong IV size",
+ function () {
+ var that = this;
+
+ function decrypt(x, iv) {
+ return crypto.subtle.decrypt(
+ { name: "AES-CBC", iv: iv },
+ x, tv.aes_cbc_dec.data);
+ }
+
+ function doDecrypt(x) {
+ return decrypt(x, new Uint8Array(15))
+ .then(
+ null,
+ function () { return decrypt(x, new Uint8Array(17)); }
+ );
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_cbc_dec.key, "AES-CBC", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CTR encryption",
+ function () {
+ var that = this;
+
+ function doEncrypt(x) {
+ return crypto.subtle.encrypt(
+ { name: "AES-CTR", counter: tv.aes_ctr_enc.iv, length: 32 },
+ x, tv.aes_ctr_enc.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_ctr_enc.key, "AES-CTR", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ memcmp_complete(that, tv.aes_ctr_enc.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CTR encryption with wrong IV size",
+ function () {
+ var that = this;
+
+ function encrypt(x, iv) {
+ return crypto.subtle.encrypt(
+ { name: "AES-CTR", counter: iv, length: 32 },
+ x, tv.aes_ctr_enc.data);
+ }
+
+ function doEncrypt(x) {
+ return encrypt(x, new Uint8Array(15))
+ .then(
+ null,
+ function () { return encrypt(x, new Uint8Array(17)); }
+ );
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_ctr_enc.key, "AES-CTR", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CTR decryption",
+ function () {
+ var that = this;
+
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(
+ { name: "AES-CTR", counter: tv.aes_ctr_dec.iv, length: 32 },
+ x, tv.aes_ctr_dec.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_ctr_dec.key, "AES-CTR", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ memcmp_complete(that, tv.aes_ctr_dec.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-CTR decryption with wrong IV size",
+ function () {
+ var that = this;
+
+ function doDecrypt(x, iv) {
+ return crypto.subtle.decrypt(
+ { name: "AES-CTR", counter: iv, length: 32 },
+ x, tv.aes_ctr_dec.data);
+ }
+
+ function decrypt(x) {
+ return decrypt(x, new Uint8Array(15))
+ .then(
+ null,
+ function () { return decrypt(x, new Uint8Array(17)); }
+ );
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_ctr_dec.key, "AES-CTR", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-GCM encryption",
+ function () {
+ var that = this;
+
+ function doEncrypt(x) {
+ return crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv: tv.aes_gcm_enc.iv,
+ additionalData: tv.aes_gcm_enc.adata,
+ tagLength: 128
+ },
+ x, tv.aes_gcm_enc.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_gcm_enc.key, "AES-GCM", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ memcmp_complete(that, tv.aes_gcm_enc.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-GCM decryption",
+ function () {
+ var that = this;
+
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(
+ {
+ name: "AES-GCM",
+ iv: tv.aes_gcm_dec.iv,
+ additionalData: tv.aes_gcm_dec.adata,
+ tagLength: 128
+ },
+ x, tv.aes_gcm_dec.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_gcm_dec.key, "AES-GCM", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ memcmp_complete(that, tv.aes_gcm_dec.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-GCM decryption, failing authentication check",
+ function () {
+ var that = this;
+
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(
+ {
+ name: "AES-GCM",
+ iv: tv.aes_gcm_dec_fail.iv,
+ additionalData: tv.aes_gcm_dec_fail.adata,
+ tagLength: 128
+ },
+ x, tv.aes_gcm_dec_fail.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.aes_gcm_dec_fail.key, "AES-GCM", false, ['decrypt'])
+ .then(doDecrypt)
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "HMAC SHA-256 sign",
+ function() {
+ var that = this;
+ var alg = {
+ name: "HMAC",
+ hash: "SHA-256"
+ }
+
+ function doSign(x) {
+ return crypto.subtle.sign("HMAC", x, tv.hmac_sign.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.hmac_sign.key, alg, false, ['sign'])
+ .then(doSign)
+ .then(
+ memcmp_complete(that, tv.hmac_sign.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "HMAC SHA-256 verify",
+ function() {
+ var that = this;
+ var alg = {
+ name: "HMAC",
+ hash: "SHA-256"
+ }
+
+ function doVerify(x) {
+ return crypto.subtle.verify("HMAC", x, tv.hmac_verify.sig, tv.hmac_verify.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.hmac_verify.key, alg, false, ['verify'])
+ .then(doVerify)
+ .then(
+ complete(that, function(x) { return !!x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "HMAC SHA-256, failing verification due to bad signature",
+ function() {
+ var that = this;
+ var alg = {
+ name: "HMAC",
+ hash: "SHA-256"
+ }
+
+ function doVerify(x) {
+ return crypto.subtle.verify("HMAC", x, tv.hmac_verify.sig_fail,
+ tv.hmac_verify.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.hmac_verify.key, alg, false, ['verify'])
+ .then(doVerify)
+ .then(
+ complete(that, function(x) { return !x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "HMAC SHA-256, failing verification due to key usage restriction",
+ function() {
+ var that = this;
+ var alg = {
+ name: "HMAC",
+ hash: "SHA-256"
+ }
+
+ function doVerify(x) {
+ return crypto.subtle.verify("HMAC", x, tv.hmac_verify.sig,
+ tv.hmac_verify.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.hmac_verify.key, alg, false, ['encrypt'])
+ .then(doVerify)
+ .then(
+ error(that),
+ complete(that, function(x) { return true; })
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA/SHA-1 signature",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
+
+ function doSign(x) {
+ return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("pkcs8", tv.rsassa.pkcs8, alg, false, ['sign'])
+ .then( doSign )
+ .then( memcmp_complete(that, tv.rsassa.sig1), error(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA verification (SHA-1)",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg.name, x, tv.rsassa.sig1, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
+ .then( doVerify )
+ .then(
+ complete(that, function(x) { return x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA verification (SHA-1), failing verification",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg.name, x, tv.rsassa.sig_fail, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
+ .then( doVerify )
+ .then(
+ complete(that, function(x) { return !x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA/SHA-256 signature",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doSign(x) {
+ return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("pkcs8", tv.rsassa.pkcs8, alg, false, ['sign'])
+ .then( doSign )
+ .then( memcmp_complete(that, tv.rsassa.sig256), error(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA verification (SHA-256)",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg.name, x, tv.rsassa.sig256, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
+ .then( doVerify )
+ .then(
+ complete(that, function(x) { return x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSASSA verification (SHA-256), failing verification",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+ var use = ['sign', 'verify'];
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg.name, x, tv.rsassa.sig_fail, tv.rsassa.data);
+ }
+
+ crypto.subtle.importKey("spki", tv.rsassa.spki, alg, false, ['verify'])
+ .then( doVerify )
+ .then(
+ complete(that, function(x) { return !x; }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that we return ArrayBuffers not ArrayBufferViews",
+ function() {
+ var that = this;
+
+ crypto.subtle.digest("SHA-256", tv.sha256.data)
+ .then(complete(that, function (x) {
+ return x instanceof ArrayBuffer;
+ }), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Ensure that importing an invalid key doesn't crash",
+ function () {
+ var that = this;
+ // TODO Change the algorithm to "DH" once we support it.
+ var alg = {name: "RSA-OAEP", hash: "SHA-1"};
+
+ crypto.subtle.importKey("pkcs8", tv.broken_pkcs8.dh, alg, false, ["decrypt"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that we check keys before using them for encryption/signatures",
+ function() {
+ var that = this;
+
+ function doCheckRSASSA() {
+ var alg = {name: "HMAC", hash: {name: "SHA-1"}};
+
+ function doSign(x) {
+ return crypto.subtle.sign("RSASSA-PKCS1-v1_5", x, new Uint8Array());
+ }
+
+ return crypto.subtle.generateKey(alg, false, ["sign"]).then(doSign);
+ }
+
+ doCheckRSASSA().then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that we're using the right globals when creating objects",
+ function() {
+ // This test isn't supported in workers.
+ if (window.importScripts) {
+ return this.complete(true);
+ }
+
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(10));
+ var hmacAlg = {name: "HMAC", length: 256, hash: "SHA-1"};
+
+ var rsaAlg = {
+ name: "RSA-PSS",
+ hash: "SHA-1",
+ modulusLength: 1024,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ function checkPrototypes(obj, type) {
+ return obj.__proto__ != window[type].prototype &&
+ obj.__proto__ == frames[0][type].prototype
+ }
+
+ var p1 = crypto.subtle.importKey.call(
+ frames[0].crypto.subtle, "raw", data, hmacAlg, false, ["sign", "verify"]);
+ var p2 = crypto.subtle.generateKey.call(
+ frames[0].crypto.subtle, hmacAlg, false, ["sign", "verify"]);
+ var p3 = crypto.subtle.generateKey.call(
+ frames[0].crypto.subtle, rsaAlg, false, ["sign", "verify"]);
+
+ if (!checkPrototypes(p1, "Promise") ||
+ !checkPrototypes(p2, "Promise") ||
+ !checkPrototypes(p3, "Promise")) {
+ error(that)();
+ }
+
+ Promise.all([p1, p2, p3]).then(complete(that, keys => {
+ return keys.every(key => {
+ if (key instanceof CryptoKey) {
+ return checkPrototypes(key, "CryptoKey");
+ }
+
+ return checkPrototypes(key.publicKey, "CryptoKey") &&
+ checkPrototypes(key.privateKey, "CryptoKey");
+ });
+ }), error(that));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <iframe style="display: none;"></iframe>
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_DH.html b/dom/crypto/test/test_WebCrypto_DH.html
new file mode 100644
index 000000000..55d83e15c
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_DH.html
@@ -0,0 +1,284 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate a DH key",
+ function() {
+ var that = this;
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime,
+ generator: new Uint8Array([0x02])
+ };
+ crypto.subtle.generateKey(alg, false, ["deriveKey", "deriveBits"]).then(
+ complete(that, function(x) {
+ return exists(x.publicKey) &&
+ (x.publicKey.algorithm.name == alg.name) &&
+ util.memcmp(x.publicKey.algorithm.prime, alg.prime) &&
+ util.memcmp(x.publicKey.algorithm.generator, alg.generator) &&
+ (x.publicKey.type == "public") &&
+ x.publicKey.extractable &&
+ (x.publicKey.usages.length == 0) &&
+ exists(x.privateKey) &&
+ (x.privateKey.algorithm.name == alg.name) &&
+ util.memcmp(x.privateKey.algorithm.prime, alg.prime) &&
+ util.memcmp(x.privateKey.algorithm.generator, alg.generator) &&
+ (x.privateKey.type == "private") &&
+ !x.privateKey.extractable &&
+ (x.privateKey.usages.length == 2) &&
+ (x.privateKey.usages[0] == "deriveKey") &&
+ (x.privateKey.usages[1] == "deriveBits");
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive bits from a DH key",
+ function() {
+ var that = this;
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime,
+ generator: new Uint8Array([0x02])
+ };
+
+ function doDerive(x) {
+ var alg = {
+ name: "DH",
+ public: x.publicKey
+ };
+ return crypto.subtle.deriveBits(alg, x.privateKey, 128);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["deriveBits"])
+ .then(doDerive, error(that))
+ .then(complete(that, function (x) {
+ return x.byteLength == 16;
+ }), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that DH deriveBits() fails when the public key is not a DH key",
+ function() {
+ var that = this;
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x.publicKey; }
+ function setPriv(x) { privKey = x.privateKey; }
+
+ function doGenerateDH() {
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime,
+ generator: new Uint8Array([0x02])
+ };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doGenerateRSA() {
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+ return crypto.subtle.generateKey(alg, false, ["encrypt"])
+ }
+
+ function doDerive() {
+ var alg = {name: "DH", public: pubKey};
+ return crypto.subtle.deriveBits(alg, privKey, 128);
+ }
+
+ doGenerateDH()
+ .then(setPriv, error(that))
+ .then(doGenerateRSA, error(that))
+ .then(setPub, error(that))
+ .then(doDerive, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that DH deriveBits() fails when the given keys' primes or bases don't match",
+ function() {
+ var that = this;
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x.publicKey; }
+ function setPriv(x) { privKey = x.privateKey; }
+
+ function doGenerateDH() {
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime,
+ generator: new Uint8Array([0x02])
+ };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doGenerateDH2() {
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime2,
+ generator: new Uint8Array([0x02])
+ };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doGenerateDH3() {
+ var alg = {
+ name: "DH",
+ prime: tv.dh.prime,
+ generator: new Uint8Array([0x03])
+ };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doDerive() {
+ var alg = {name: "DH", public: pubKey};
+ return crypto.subtle.deriveBits(alg, privKey, 128);
+ }
+
+ doGenerateDH()
+ .then(setPriv, error(that))
+ .then(doGenerateDH2, error(that))
+ .then(setPub, error(that))
+ .then(doDerive, error(that))
+ .then(error(that), doGenerateDH3)
+ .then(setPub, error(that))
+ .then(doDerive, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Raw import/export of a public DH key",
+ function () {
+ var that = this;
+ var alg = {
+ name: "DH",
+ prime: tv.dh_nist.prime,
+ generator: tv.dh_nist.gen
+ };
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", tv.dh_nist.raw, alg, true, ["deriveBits"])
+ .then(doExport)
+ .then(memcmp_complete(that, tv.dh_nist.raw), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive bits from an imported public and a generated private DH key",
+ function() {
+ var that = this;
+ var alg = {
+ name: "DH",
+ prime: tv.dh_nist.prime,
+ generator: tv.dh_nist.gen
+ };
+
+ var privKey;
+ function setPriv(x) { privKey = x.privateKey; }
+
+ function doImport() {
+ return crypto.subtle.importKey("raw", tv.dh_nist.raw, alg, true, ["deriveBits"]);
+ }
+
+ function doDerive(pubKey) {
+ var alg = {name: "DH", public: pubKey};
+ return crypto.subtle.deriveBits(alg, privKey, 128);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["deriveBits"])
+ .then(setPriv, error(that))
+ .then(doImport, error(that))
+ .then(doDerive, error(that))
+ .then(complete(that, function (x) {
+ return x.byteLength == 16;
+ }), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "SPKI import/export of a public DH key",
+ function() {
+ var that = this;
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("spki", x);
+ }
+
+ crypto.subtle.importKey("spki", tv.dh_nist.spki, "DH", true, ["deriveBits"])
+ .then(doExport, error(that))
+ .then(memcmp_complete(that, tv.dh_nist.spki), error(that));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_ECDH.html b/dom/crypto/test/test_WebCrypto_ECDH.html
new file mode 100644
index 000000000..671c7c693
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_ECDH.html
@@ -0,0 +1,583 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate an ECDH key for named curve P-256",
+ function() {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ crypto.subtle.generateKey(alg, false, ["deriveKey", "deriveBits"]).then(
+ complete(that, function(x) {
+ return exists(x.publicKey) &&
+ (x.publicKey.algorithm.name == alg.name) &&
+ (x.publicKey.algorithm.namedCurve == alg.namedCurve) &&
+ (x.publicKey.type == "public") &&
+ x.publicKey.extractable &&
+ (x.publicKey.usages.length == 0) &&
+ exists(x.privateKey) &&
+ (x.privateKey.algorithm.name == alg.name) &&
+ (x.privateKey.algorithm.namedCurve == alg.namedCurve) &&
+ (x.privateKey.type == "private") &&
+ !x.privateKey.extractable &&
+ (x.privateKey.usages.length == 2) &&
+ (x.privateKey.usages[0] == "deriveKey") &&
+ (x.privateKey.usages[1] == "deriveBits");
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate an ECDH key and derive some bits",
+ function() {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+
+ var pair;
+ function setKeyPair(x) { pair = x; }
+
+ function doDerive(n) {
+ return function (x) {
+ var alg = { name: "ECDH", public: pair.publicKey };
+ return crypto.subtle.deriveBits(alg, pair.privateKey, n * 8);
+ }
+ }
+
+ crypto.subtle.generateKey(alg, false, ["deriveBits"])
+ .then(setKeyPair, error(that))
+ .then(doDerive(2), error(that))
+ .then(function (x) {
+ // Deriving less bytes works.
+ if (x.byteLength != 2) {
+ throw "should have derived two bytes";
+ }
+ })
+ // Deriving more than the curve yields doesn't.
+ .then(doDerive(33), error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that ECDH deriveBits() fails when the public key is not an ECDH key",
+ function() {
+ var that = this;
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x.publicKey; }
+ function setPriv(x) { privKey = x.privateKey; }
+
+ function doGenerateP256() {
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doGenerateRSA() {
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+ return crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"])
+ }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, 16);
+ }
+
+ doGenerateP256()
+ .then(setPriv, error(that))
+ .then(doGenerateRSA, error(that))
+ .then(setPub, error(that))
+ .then(doDerive, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that ECDH deriveBits() fails when the given keys' curves don't match",
+ function() {
+ var that = this;
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x.publicKey; }
+ function setPriv(x) { privKey = x.privateKey; }
+
+ function doGenerateP256() {
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doGenerateP384() {
+ var alg = { name: "ECDH", namedCurve: "P-384" };
+ return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+ }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, 16);
+ }
+
+ doGenerateP256()
+ .then(setPriv, error(that))
+ .then(doGenerateP384, error(that))
+ .then(setPub, error(that))
+ .then(doDerive, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import an ECDH public and private key and derive bits (P-256)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
+ .then(setPriv, error(that)),
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, false, ["deriveBits"])
+ .then(setPub, error(that))
+ ]).then(doDerive, error(that))
+ .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import an ECDH public and private key and derive bits (P-384)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p384.secret.byteLength * 8);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_priv, alg, false, ["deriveBits"])
+ .then(setPriv, error(that)),
+ crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_pub, alg, false, ["deriveBits"])
+ .then(setPub, error(that))
+ ]).then(doDerive, error(that))
+ .then(memcmp_complete(that, tv.ecdh_p384.secret), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import an ECDH public and private key and derive bits (P-521)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p521.secret.byteLength * 8);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveBits"])
+ .then(setPriv, error(that)),
+ crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, ["deriveBits"])
+ .then(setPub, error(that))
+ ]).then(doDerive, error(that))
+ .then(memcmp_complete(that, tv.ecdh_p521.secret), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import/export roundtrip with ECDH (P-256)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doExportPub() {
+ return crypto.subtle.exportKey("jwk", pubKey);
+ }
+ function doExportPriv() {
+ return crypto.subtle.exportKey("jwk", privKey);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, true, ["deriveBits"])
+ .then(setPriv, error(that)),
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, true, ["deriveBits"])
+ .then(setPub, error(that))
+ ]).then(doExportPub, error(that))
+ .then(function (x) {
+ var tp = tv.ecdh_p256.jwk_pub;
+ if ((tp.kty != x.kty) &&
+ (tp.crv != x.crv) &&
+ (tp.x != x.x) &&
+ (tp.y != x.y)) {
+ throw "exported public key doesn't match";
+ }
+ }, error(that))
+ .then(doExportPriv, error(that))
+ .then(complete(that, function (x) {
+ var tp = tv.ecdh_p256.jwk_priv;
+ return (tp.kty == x.kty) &&
+ (tp.crv == x.crv) &&
+ (tp.d == x.d) &&
+ (tp.x == x.x) &&
+ (tp.y == x.y);
+ }), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing bad JWKs fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+ var tvs = tv.ecdh_p256_negative;
+
+ function doTryImport(jwk) {
+ return function () {
+ return crypto.subtle.importKey("jwk", jwk, alg, false, ["deriveBits"]);
+ }
+ }
+
+ doTryImport(tvs.jwk_bad_crv)()
+ .then(error(that), doTryImport(tvs.jwk_missing_crv))
+ .then(error(that), doTryImport(tvs.jwk_missing_x))
+ .then(error(that), doTryImport(tvs.jwk_missing_y))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK export of a newly generated ECDH private key",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var reBase64URL = /^[a-zA-Z0-9_-]+$/;
+
+ function doExportToJWK(x) {
+ return crypto.subtle.exportKey("jwk", x.privateKey)
+ }
+
+ crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"])
+ .then(doExportToJWK)
+ .then(
+ complete(that, function(x) {
+ return x.ext &&
+ x.kty == 'EC' &&
+ x.crv == 'P-256' &&
+ reBase64URL.test(x.x) &&
+ reBase64URL.test(x.y) &&
+ reBase64URL.test(x.d) &&
+ x.x.length == 43 && // 32 octets, base64-encoded
+ x.y.length == 43 && // 32 octets, base64-encoded
+ shallowArrayEquals(x.key_ops, ['deriveKey', 'deriveBits']);
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive an HMAC key from two ECDH keys and test sign/verify",
+ function() {
+ var that = this;
+ var alg = { name: "ECDH" };
+ var algDerived = { name: "HMAC", hash: {name: "SHA-1"} };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveKey(alg, privKey, algDerived, false, ["sign", "verify"])
+ .then(function (x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ // 512 bit is the default for HMAC-SHA1.
+ if (x.algorithm.length != 512) {
+ throw "Invalid key; incorrect length";
+ }
+
+ return x;
+ });
+ }
+
+ function doSignAndVerify(x) {
+ var data = crypto.getRandomValues(new Uint8Array(1024));
+ return crypto.subtle.sign("HMAC", x, data)
+ .then(function (sig) {
+ return crypto.subtle.verify("HMAC", x, sig, data);
+ });
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveKey"])
+ .then(setPriv),
+ crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, ["deriveKey"])
+ .then(setPub)
+ ]).then(doDerive)
+ .then(doSignAndVerify)
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "SPKI import/export of public ECDH keys (P-256)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+ var keys = ["spki", "spki_id_ecpk"];
+
+ function doImport(key) {
+ return crypto.subtle.importKey("spki", tv.ecdh_p256[key], alg, true, ["deriveBits"]);
+ }
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("spki", x);
+ }
+
+ function nextKey() {
+ var key = keys.shift();
+ var imported = doImport(key);
+ var derived = imported.then(doExport);
+
+ return derived.then(function (x) {
+ if (!util.memcmp(x, tv.ecdh_p256.spki)) {
+ throw "exported key is invalid";
+ }
+
+ if (keys.length) {
+ return nextKey();
+ }
+ });
+ }
+
+ nextKey().then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "SPKI/JWK import ECDH keys (P-256) and derive a known secret",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("spki", tv.ecdh_p256.spki, alg, false, ["deriveBits"])
+ .then(setPub),
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
+ .then(setPriv)
+ ]).then(doDerive)
+ .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Raw import/export of a public ECDH key (P-256)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, true, ["deriveBits"])
+ .then(doExport)
+ .then(memcmp_complete(that, tv.ecdh_p256.raw), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing bad raw ECDH keys fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var tvs = tv.ecdh_p256_negative.raw_bad;
+
+ crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing ECDH keys with an unknown format fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var tvs = tv.ecdh_p256.raw;
+
+ crypto.subtle.importKey("unknown", tv, alg, false, ["deriveBits"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing too short raw ECDH keys fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var tvs = tv.ecdh_p256_negative.raw_short;
+
+ crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing too long raw ECDH keys fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var tvs = tv.ecdh_p256_negative.raw_long;
+
+ crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that importing compressed raw ECDH keys fails",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+ var tvs = tv.ecdh_p256_negative.raw_compressed;
+
+ crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RAW/JWK import ECDH keys (P-256) and derive a known secret",
+ function () {
+ var that = this;
+ var alg = { name: "ECDH", namedCurve: "P-256" };
+
+ var pubKey, privKey;
+ function setPub(x) { pubKey = x; }
+ function setPriv(x) { privKey = x; }
+
+ function doDerive() {
+ var alg = { name: "ECDH", public: pubKey };
+ return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, false, ["deriveBits"])
+ .then(setPub),
+ crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
+ .then(setPriv)
+ ]).then(doDerive)
+ .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_ECDSA.html b/dom/crypto/test/test_WebCrypto_ECDSA.html
new file mode 100644
index 000000000..bc9bf1a3f
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_ECDSA.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Generate an ECDSA key for named curve P-256",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-256" };
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"]).then(
+ complete(that, function(x) {
+ return exists(x.publicKey) &&
+ (x.publicKey.algorithm.name == alg.name) &&
+ (x.publicKey.algorithm.namedCurve == alg.namedCurve) &&
+ (x.publicKey.type == "public") &&
+ x.publicKey.extractable &&
+ (x.publicKey.usages.length == 1) &&
+ (x.publicKey.usages[0] == "verify") &&
+ exists(x.privateKey) &&
+ (x.privateKey.algorithm.name == alg.name) &&
+ (x.privateKey.algorithm.namedCurve == alg.namedCurve) &&
+ (x.privateKey.type == "private") &&
+ !x.privateKey.extractable &&
+ (x.privateKey.usages.length == 1) &&
+ (x.privateKey.usages[0] == "sign")
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "ECDSA JWK import and verify a known-good signature",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
+ }
+
+ crypto.subtle.importKey("jwk", tv.ecdsa_verify.pub_jwk, alg, true, ["verify"])
+ .then(doVerify)
+ .then(complete(that, x => x), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "ECDSA key generation with public key export",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+ var msg = Uint8Array.from([1]);
+
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+ .then(pair => Promise.all([
+ crypto.subtle.sign(alg, pair.privateKey, msg),
+ crypto.subtle.exportKey("spki", pair.publicKey)
+ .then(spki => crypto.subtle.importKey("spki", spki, alg, false, ["verify"]))
+ ]))
+ .then(sigAndKey => crypto.subtle.verify(alg, sigAndKey[1], sigAndKey[0], msg))
+ .then(complete(that, x => x), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "ECDSA JWK import and reject a known-bad signature",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig_tampered,
+ tv.ecdsa_verify.data);
+ }
+
+ crypto.subtle.importKey("jwk", tv.ecdsa_verify.pub_jwk, alg, true, ["verify"])
+ .then(doVerify)
+ .then(complete(that, x => !x), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "ECDSA sign/verify round-trip",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+ var pubKey;
+
+
+ function doSign(keyPair) {
+ pubKey = keyPair.publicKey;
+ return crypto.subtle.sign(alg, keyPair.privateKey, tv.ecdsa_verify.data);
+ }
+ function doVerify(sig) {
+ return crypto.subtle.verify(alg, pubKey, sig, tv.ecdsa_verify.data);
+ }
+
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(doSign)
+ .then(doVerify)
+ .then(complete(that, x => x), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Verify that ECDSA import fails with a known-bad public key",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
+ }
+
+ crypto.subtle.importKey("jwk", tv.ecdsa_bad.pub_jwk, alg, true, ["verify"])
+ .then(error(that), complete(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Raw import/export of a public ECDSA key (P-521)",
+ function () {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("raw", x);
+ }
+
+ crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"])
+ .then(doExport)
+ .then(memcmp_complete(that, tv.ecdsa_verify.raw), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "ECDSA raw import and verify a known-good signature",
+ function() {
+ var that = this;
+ var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
+ }
+
+ crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"])
+ .then(doVerify)
+ .then(complete(that, x => x), error(that))
+ }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_HKDF.html b/dom/crypto/test/test_WebCrypto_HKDF.html
new file mode 100644
index 000000000..dcc871ffa
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_HKDF.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deriving zero bits should fail",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(x => crypto.subtle.deriveBits(alg, x, 0), error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive four bits with HKDF, no salt or info given",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(x => crypto.subtle.deriveBits(alg, x, 4))
+ // The last 4 bits should be zeroes (1000 1101 => 1000 0000).
+ .then(memcmp_complete(that, new Uint8Array([0x80])))
+ .catch(error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deriving too many bits should fail",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ function deriveBits(x) {
+ // The maximum length (in bytes) of output material for HKDF is 255 times
+ // the digest length. In this case, the digest length (in bytes) of
+ // SHA-256 is 32; 32*255 = 8160. deriveBits expects the length to be in
+ // bits, so 8160*8=65280 and add 1 to exceed the maximum length.
+ return crypto.subtle.deriveBits(alg, x, 65281);
+ }
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(deriveBits, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deriving with an unsupported PRF should fail",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "HMAC",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ function deriveBits(x) {
+ return crypto.subtle.deriveBits(alg, x, 4);
+ }
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(deriveBits, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deriving with a non-HKDF key should fail",
+ function() {
+ var that = this;
+
+ var alg = {
+ name: "HKDF",
+ hash: "HMAC",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ function deriveBits(x) {
+ return crypto.subtle.deriveBits(alg, x, 4);
+ }
+
+ var ecAlg = {name: "ECDH", namedCurve: "P-256"};
+ crypto.subtle.generateKey(ecAlg, false, ["deriveBits"])
+ .then(deriveBits, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive known values from test vectors (SHA-1 and SHA-256)",
+ function() {
+ var that = this;
+ var tests = tv.hkdf.slice();
+
+ function next() {
+ if (!tests.length) {
+ return;
+ }
+
+ var test = tests.shift();
+ var {key, data} = test;
+
+ return crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(function (key) {
+ return crypto.subtle.deriveBits({
+ name: "HKDF",
+ hash: test.prf,
+ salt: test.salt,
+ info: test.info
+ }, key, test.data.byteLength * 8);
+ })
+ .then(function (data) {
+ if (!util.memcmp(data, test.data)) {
+ throw new Error("derived bits don't match expected value");
+ }
+
+ // Next test vector.
+ return next();
+ });
+ }
+
+ next().then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive known values from test vectors (JWK, SHA-256)",
+ function() {
+ var that = this;
+ var test = tv.hkdf[0];
+ var alg = {
+ name: "HKDF",
+ hash: test.prf,
+ salt: test.salt,
+ info: test.info
+ };
+
+ crypto.subtle.importKey("jwk", test.jwk, "HKDF", false, ["deriveBits"])
+ .then(x => crypto.subtle.deriveBits(alg, x, test.data.byteLength * 8))
+ .then(memcmp_complete(that, test.data), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test wrapping/unwrapping an HKDF key",
+ function() {
+ var that = this;
+ var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+ var wrappingKey;
+
+ function wrap(x) {
+ wrappingKey = x;
+ return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
+ }
+
+ function unwrap(wrappedKey) {
+ return crypto.subtle.unwrapKey(
+ "raw", wrappedKey, wrappingKey, alg, "HKDF", false, ["deriveBits"])
+ .then(rawKey => {
+ return crypto.subtle.deriveBits({
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ }, rawKey, 4);
+ })
+ .then(derivedBits => {
+ if (!util.memcmp(derivedBits, new Uint8Array([0x80]))) {
+ throw new Error("deriving bits failed");
+ }
+
+ // Forward to reuse.
+ return wrappedKey;
+ });
+ }
+
+ crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+ .then(wrap)
+ .then(unwrap)
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Unwrapping an HKDF key in PKCS8 format should fail",
+ function() {
+ var that = this;
+ var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+ var wrappingKey;
+
+ function wrap(x) {
+ wrappingKey = x;
+ return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
+ }
+
+ function unwrap(x) {
+ return crypto.subtle.unwrapKey(
+ "pkcs8", x, wrappingKey, alg, "HKDF", false, ["deriveBits"]);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+ .then(wrap, error(that))
+ .then(unwrap, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Derive an AES key using with HKDF",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ function deriveKey(x) {
+ var targetAlg = {name: "AES-GCM", length: 256};
+ return crypto.subtle.deriveKey(alg, x, targetAlg, false, ["encrypt"]);
+ }
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
+ .then(deriveKey)
+ .then(complete(that), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deriving an HKDF key with HKDF should fail",
+ function() {
+ var that = this;
+ var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ function deriveKey(x) {
+ return crypto.subtle.deriveKey(alg, x, "HKDF", false, ["deriveBits"]);
+ }
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
+ .then(deriveKey)
+ .then(error(that), complete(that))
+ }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Import_Multiple_Identical_Keys.html b/dom/crypto/test/test_WebCrypto_Import_Multiple_Identical_Keys.html
new file mode 100644
index 000000000..6913bb78e
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Import_Multiple_Identical_Keys.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import the same ECDSA key multiple times and ensure that it can be used.",
+ function () {
+ var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(function(keyPair) {
+ return crypto.subtle.exportKey("jwk", keyPair.privateKey);
+ })
+ .then(function(exportedKey) {
+ let keyImportPromises = [];
+ for (let i = 0; i < 20; i++) {
+ keyImportPromises.push(
+ crypto.subtle.importKey("jwk", exportedKey, alg, false, ["sign"]));
+ }
+ return Promise.all(keyImportPromises);
+ })
+ .then(function(importedKeys) {
+ let signPromises = [];
+ let data = crypto.getRandomValues(new Uint8Array(32));
+ for (let key of importedKeys) {
+ signPromises.push(crypto.subtle.sign(alg, key, data));
+ }
+ return Promise.all(signPromises);
+ })
+ .then(complete(this, function(signatures) {
+ return signatures.length == 20;
+ }), error(this));
+ }
+);
+
+// -----------------------------------------------------------------------------
+// This is the same test, but with an RSA key. This test framework stringifies
+// each test so it can be sent to and ran in a worker, which unfortunately
+// means we can't factor out common code here.
+TestArray.addTest(
+ "Import the same RSA key multiple times and ensure that it can be used.",
+ function () {
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: "SHA-256"
+ };
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(function(keyPair) {
+ return crypto.subtle.exportKey("jwk", keyPair.privateKey);
+ })
+ .then(function(exportedKey) {
+ let keyImportPromises = [];
+ for (let i = 0; i < 20; i++) {
+ keyImportPromises.push(
+ crypto.subtle.importKey("jwk", exportedKey, alg, false, ["sign"]));
+ }
+ return Promise.all(keyImportPromises);
+ })
+ .then(function(importedKeys) {
+ let signPromises = [];
+ let data = crypto.getRandomValues(new Uint8Array(32));
+ for (let key of importedKeys) {
+ signPromises.push(crypto.subtle.sign(alg, key, data));
+ }
+ return Promise.all(signPromises);
+ })
+ .then(complete(this, function(signatures) {
+ return signatures.length == 20;
+ }), error(this));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_JWK.html b/dom/crypto/test/test_WebCrypto_JWK.html
new file mode 100644
index 000000000..f9916f5f2
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_JWK.html
@@ -0,0 +1,369 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import and use of an AES-GCM key",
+ function () {
+ var that = this;
+
+ function doEncrypt(x) {
+ return crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv: tv.aes_gcm_enc.iv,
+ additionalData: tv.aes_gcm_enc.adata,
+ tagLength: 128
+ },
+ x, tv.aes_gcm_enc.data);
+ }
+
+ crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk, "AES-GCM", false, ['encrypt'])
+ .then(doEncrypt)
+ .then(
+ memcmp_complete(that, tv.aes_gcm_enc.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import and use of an RSASSA-PKCS1-v1_5 private key",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doSign(x) {
+ return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
+ }
+ function fail(x) { console.log(x); error(that); }
+
+ crypto.subtle.importKey("jwk", tv.rsassa.jwk_priv, alg, false, ['sign'])
+ .then( doSign, fail )
+ .then( memcmp_complete(that, tv.rsassa.sig256), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import and use of an RSASSA-PKCS1-v1_5 public key",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doVerify(x) {
+ return crypto.subtle.verify(alg.name, x, tv.rsassa.sig256, tv.rsassa.data);
+ }
+ function fail(x) { error(that); }
+
+ crypto.subtle.importKey("jwk", tv.rsassa.jwk_pub, alg, false, ['verify'])
+ .then( doVerify, fail )
+ .then(
+ complete(that, function(x) { return x; }),
+ fail
+ );
+ });
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import failure on incomplete RSA private key (missing 'qi')",
+ function () {
+ var that = this;
+ var alg = { name: "RSA-OAEP", hash: "SHA-256" };
+ var jwk = {
+ kty: "RSA",
+ n: tv.rsassa.jwk_priv.n,
+ e: tv.rsassa.jwk_priv.e,
+ d: tv.rsassa.jwk_priv.d,
+ p: tv.rsassa.jwk_priv.p,
+ q: tv.rsassa.jwk_priv.q,
+ dp: tv.rsassa.jwk_priv.dp,
+ dq: tv.rsassa.jwk_priv.dq,
+ };
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+ .then( error(that), complete(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import failure on algorithm mismatch",
+ function () {
+ var that = this;
+ var alg = "AES-GCM";
+ var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", alg: "A256GCM" };
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+ .then( error(that), complete(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import failure on usages mismatch",
+ function () {
+ var that = this;
+ var alg = "AES-GCM";
+ var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", key_ops: ['encrypt'] };
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+ .then( error(that), complete(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import failure on extractable mismatch",
+ function () {
+ var that = this;
+ var alg = "AES-GCM";
+ var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", ext: false };
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt'])
+ .then( error(that), complete(that) );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK export of a symmetric key",
+ function () {
+ var that = this;
+ var alg = "AES-GCM";
+ var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", kty: "oct" };
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ['k']) &&
+ x.kty == 'oct' &&
+ x.alg == 'A128GCM' &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ['encrypt','decrypt']) &&
+ x.k == jwk.k
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import/export of an RSA private key",
+ function () {
+ var jwk = tv.rsassa.jwk_priv;
+
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['sign'])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']) &&
+ x.kty == 'RSA' &&
+ x.alg == 'RS256' &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ['sign']) &&
+ x.n == jwk.n &&
+ x.e == jwk.e &&
+ x.d == jwk.d &&
+ x.p == jwk.p &&
+ x.q == jwk.q &&
+ x.dp == jwk.dp &&
+ x.dq == jwk.dq &&
+ x.qi == jwk.qi;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK import/export of an RSA private key where p < q",
+ function () {
+ var jwk = tv.rsassa.jwk_priv_pLTq;
+
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['sign'])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']) &&
+ x.kty == 'RSA' &&
+ x.alg == 'RS256' &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ['sign']) &&
+ x.n == jwk.n &&
+ x.e == jwk.e &&
+ x.d == jwk.d &&
+ x.p == jwk.p &&
+ x.q == jwk.q &&
+ x.dp == jwk.dp &&
+ x.dq == jwk.dq &&
+ x.qi == jwk.qi;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK export of an RSA public key",
+ function () {
+ var that = this;
+ var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
+ var jwk = tv.rsassa.jwk_pub;
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ['verify'])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ window.jwk_pub = x;
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ['n', 'e']) &&
+ x.kty == 'RSA' &&
+ x.alg == 'RS256' &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ['verify']) &&
+ x.n == jwk.n &&
+ x.e == jwk.e;
+ }),
+ error(that)
+ );
+ }
+);
+
+// --------
+TestArray.addTest(
+ "Check JWK parameters on generated ECDSA key pair",
+ function() {
+ crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
+ .then(pair => Promise.all([
+ crypto.subtle.exportKey('jwk', pair.privateKey),
+ crypto.subtle.exportKey('jwk', pair.publicKey)
+ ]))
+ .then(
+ complete(this, function(x) {
+ var priv = x[0];
+ var pub = x[1];
+ var pubIsSubsetOfPriv = Object.keys(pub)
+ .filter(k => k !== 'key_ops') // key_ops is the only complex attr
+ .reduce((all, k) => all && pub[k] === priv[k], true);
+ // Can't use hasBaseJwkFields() because EC keys don't get "alg":
+ // "alg" matches curve to hash, but WebCrypto keys are more flexible.
+ return hasFields(pub, ['kty', 'crv', 'key_ops', 'ext']) &&
+ pub.kty === 'EC' &&
+ pub.crv === 'P-256' &&
+ pub.ext &&
+ typeof(pub.x) === 'string' &&
+ typeof(pub.y) === 'string' &&
+ shallowArrayEquals(pub.key_ops, ['verify']) &&
+ pubIsSubsetOfPriv &&
+ shallowArrayEquals(priv.key_ops, ['sign']) &&
+ typeof(priv.d) === 'string';
+ }),
+ error(this));
+ }
+);
+
+// --------
+TestArray.addTest(
+ "Check key_ops parameter on an unusable RSA public key",
+ function() {
+ var parameters = {
+ name: 'RSASSA-PKCS1-v1_5',
+ modulusLength: 1024,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ hash: 'SHA-256'
+ };
+ // The public key generated here will have no usages and will therefore
+ // have an empty key_ops list.
+ crypto.subtle.generateKey(parameters, true, ['sign'])
+ .then(pair => crypto.subtle.exportKey('jwk', pair.publicKey))
+ .then(complete(this, x => x.key_ops.length === 0),
+ error(this));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Normalize.html b/dom/crypto/test/test_WebCrypto_Normalize.html
new file mode 100644
index 000000000..f293c0980
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Normalize.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+TestArray.addTest(
+ "Test that we properly normalize algorithm names",
+ function() {
+ var that = this;
+ var alg = { name: "hmac", hash: {name: "sHa-256"} };
+
+ function doGenerateAesKey() {
+ var alg = { name: "AES-gcm", length: 192 };
+ return crypto.subtle.generateKey(alg, false, ["encrypt"]);
+ }
+
+ function doGenerateRsaOaepKey() {
+ var alg = {
+ name: "rsa-OAEP",
+ hash: "sha-1",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+ return crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"]);
+ }
+
+ function doGenerateRsaSsaPkcs1Key() {
+ var alg = { name: "RSASSA-pkcs1-V1_5", hash: "SHA-1" };
+ return crypto.subtle.importKey("pkcs8", tv.pkcs8, alg, true, ["sign"]);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["sign"])
+ .then(doGenerateAesKey)
+ .then(doGenerateRsaOaepKey)
+ .then(doGenerateRsaSsaPkcs1Key)
+ .then(complete(that), error(that));
+ }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_PBKDF2.html b/dom/crypto/test/test_WebCrypto_PBKDF2.html
new file mode 100644
index 000000000..cba93ceb1
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_PBKDF2.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key",
+ function() {
+ var that = this;
+ var alg = "PBKDF2";
+ var key = new TextEncoder("utf-8").encode("password");
+
+ crypto.subtle.importKey("raw", key, alg, false, ["deriveKey"]).then(
+ complete(that, hasKeyFields),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Unwrapping a PBKDF2 key in PKCS8 format should fail",
+ function() {
+ var that = this;
+ var pbkdf2Key = new TextEncoder("utf-8").encode("password");
+ var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+ var wrappingKey;
+
+ function wrap(x) {
+ wrappingKey = x;
+ return crypto.subtle.encrypt(alg, wrappingKey, pbkdf2Key);
+ }
+
+ function unwrap(x) {
+ return crypto.subtle.unwrapKey(
+ "pkcs8", x, wrappingKey, alg, "PBKDF2", false, ["deriveBits"]);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+ .then(wrap, error(that))
+ .then(unwrap, error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key and derive bits using HMAC-SHA-1",
+ function() {
+ var that = this;
+ var alg = "PBKDF2";
+ var key = tv.pbkdf2_sha1.password;
+
+ function doDerive(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-1",
+ salt: tv.pbkdf2_sha1.salt,
+ iterations: tv.pbkdf2_sha1.iterations
+ };
+ return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha1.length);
+ }
+ function fail(x) { console.log("failing"); error(that)(x); }
+
+ crypto.subtle.importKey("raw", key, alg, false, ["deriveBits"])
+ .then( doDerive, fail )
+ .then( memcmp_complete(that, tv.pbkdf2_sha1.derived), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import a PBKDF2 key in JWK format and derive bits using HMAC-SHA-1",
+ function() {
+ var that = this;
+ var alg = "PBKDF2";
+
+ function doDerive(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-1",
+ salt: tv.pbkdf2_sha1.salt,
+ iterations: tv.pbkdf2_sha1.iterations
+ };
+ return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha1.length);
+ }
+ function fail(x) { console.log("failing"); error(that)(x); }
+
+ crypto.subtle.importKey("jwk", tv.pbkdf2_sha1.jwk, alg, false, ["deriveBits"])
+ .then( doDerive, fail )
+ .then( memcmp_complete(that, tv.pbkdf2_sha1.derived), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key and derive a new key using HMAC-SHA-1",
+ function() {
+ var that = this;
+ var alg = "PBKDF2";
+ var key = tv.pbkdf2_sha1.password;
+
+ function doDerive(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-1",
+ salt: tv.pbkdf2_sha1.salt,
+ iterations: tv.pbkdf2_sha1.iterations
+ };
+
+ var algDerived = {
+ name: "HMAC",
+ hash: {name: "SHA-1"}
+ };
+
+ return crypto.subtle.deriveKey(alg, x, algDerived, false, ["sign", "verify"])
+ .then(function (x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ if (x.algorithm.length != 512) {
+ throw "Invalid key; incorrect length";
+ }
+
+ return x;
+ });
+ }
+
+ function doSignAndVerify(x) {
+ var data = new Uint8Array(1024);
+
+ return crypto.subtle.sign("HMAC", x, data)
+ .then(function (sig) {
+ return crypto.subtle.verify("HMAC", x, sig, data);
+ });
+ }
+
+ function fail(x) { console.log("failing"); error(that)(x); }
+
+ crypto.subtle.importKey("raw", key, alg, false, ["deriveKey"])
+ .then( doDerive, fail )
+ .then( doSignAndVerify, fail )
+ .then( complete(that, x => x), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key and derive a new key using HMAC-SHA-1 with custom length",
+ function() {
+ var that = this;
+
+ function doDerive(x) {
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-1",
+ salt: tv.pbkdf2_sha1.salt,
+ iterations: tv.pbkdf2_sha1.iterations
+ };
+
+ var algDerived = {name: "HMAC", hash: "SHA-1", length: 128};
+ return crypto.subtle.deriveKey(alg, x, algDerived, false, ["sign"]);
+ }
+
+ var password = crypto.getRandomValues(new Uint8Array(8));
+ crypto.subtle.importKey("raw", password, "PBKDF2", false, ["deriveKey"])
+ .then(doDerive)
+ .then(complete(that, function (x) {
+ return hasKeyFields(x) && x.algorithm.length == 128;
+ }), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key and derive bits using HMAC-SHA-256",
+ function() {
+ var that = this;
+ var alg = "PBKDF2";
+ var key = tv.pbkdf2_sha256.password;
+
+ function doDerive(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-256",
+ salt: tv.pbkdf2_sha256.salt,
+ iterations: tv.pbkdf2_sha256.iterations
+ };
+ return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha256.length);
+ }
+ function fail(x) { console.log("failing"); error(that)(x); }
+
+ crypto.subtle.importKey("raw", key, alg, false, ["deriveBits"])
+ .then( doDerive, fail )
+ .then( memcmp_complete(that, tv.pbkdf2_sha256.derived), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Import raw PBKDF2 key and derive bits using HMAC-SHA-256 with zero-length salt",
+ function() {
+ var that = this;
+ var importAlg = { name: "PBKDF2", hash: "SHA-256" };
+ var key = tv.pbkdf2_sha256_no_salt.password;
+
+ function doDerive(x) {
+ if (!hasKeyFields(x)) {
+ throw "Invalid key; missing field(s)";
+ }
+
+ var deriveAlg = {
+ name: "PBKDF2",
+ hash: "SHA-256",
+ salt: new Uint8Array(0),
+ iterations: tv.pbkdf2_sha256_no_salt.iterations
+ };
+ return crypto.subtle.deriveBits(deriveAlg, x, tv.pbkdf2_sha256_no_salt.length);
+ }
+ function fail(x) { console.log("failing"); error(that)(x); }
+
+ crypto.subtle.importKey("raw", key, importAlg, false, ["deriveBits"])
+ .then( doDerive, fail )
+ .then( memcmp_complete(that, tv.pbkdf2_sha256_no_salt.derived), fail );
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_RSA_OAEP.html b/dom/crypto/test/test_WebCrypto_RSA_OAEP.html
new file mode 100644
index 000000000..d2a49caf2
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_RSA_OAEP.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// Generating 2048-bit keys takes some time.
+SimpleTest.requestLongerTimeout(2);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-OAEP encrypt/decrypt round-trip",
+ function () {
+ var that = this;
+ var privKey, pubKey;
+ var alg = {name: "RSA-OAEP", hash: "SHA-1"};
+
+ var privKey, pubKey;
+ function setPriv(x) { privKey = x; }
+ function setPub(x) { pubKey = x; }
+ function doEncrypt() {
+ return crypto.subtle.encrypt(alg, pubKey, tv.rsaoaep.data);
+ }
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(alg, privKey, x);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+ .then(setPriv, error(that)),
+ crypto.subtle.importKey("spki", tv.rsaoaep.spki, alg, false, ['encrypt'])
+ .then(setPub, error(that))
+ ]).then(doEncrypt, error(that))
+ .then(doDecrypt, error(that))
+ .then(
+ memcmp_complete(that, tv.rsaoaep.data),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-OAEP key generation and encrypt/decrypt round-trip (SHA-256)",
+ function () {
+ var that = this;
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ var privKey, pubKey, data = crypto.getRandomValues(new Uint8Array(128));
+ function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+ function doEncrypt() {
+ return crypto.subtle.encrypt(alg, pubKey, data);
+ }
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(alg, privKey, x);
+ }
+
+ crypto.subtle.generateKey(alg, false, ['encrypt', 'decrypt'])
+ .then(setKey, error(that))
+ .then(doEncrypt, error(that))
+ .then(doDecrypt, error(that))
+ .then(
+ memcmp_complete(that, data),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-OAEP decryption known answer",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-OAEP", hash: "SHA-1"};
+
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt(alg, x, tv.rsaoaep.result);
+ }
+ function fail() { error(that); }
+
+ crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+ .then( doDecrypt, fail )
+ .then( memcmp_complete(that, tv.rsaoaep.data), fail );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-OAEP input data length checks (2048-bit key)",
+ function () {
+ var that = this;
+ var privKey, pubKey;
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-1",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ var privKey, pubKey;
+ function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+ function doEncrypt(n) {
+ console.log("entered encrypt("+ n +")");
+ return function () {
+ return crypto.subtle.encrypt(alg, pubKey, new Uint8Array(n));
+ }
+ }
+
+ crypto.subtle.generateKey(alg, false, ['encrypt', 'decrypt'])
+ .then(setKey, error(that))
+ .then(doEncrypt(214), error(that))
+ .then(doEncrypt(215), error(that))
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-OAEP key import with invalid hash",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-OAEP", hash: "SHA-123"};
+
+ crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test that RSA-OAEP encrypt/decrypt accepts strings as AlgorithmIdentifiers",
+ function () {
+ var that = this;
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ var privKey, pubKey, data = crypto.getRandomValues(new Uint8Array(128));
+ function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+ function doEncrypt() {
+ return crypto.subtle.encrypt("RSA-OAEP", pubKey, data);
+ }
+ function doDecrypt(x) {
+ return crypto.subtle.decrypt("RSA-OAEP", privKey, x);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"])
+ .then(setKey)
+ .then(doEncrypt)
+ .then(doDecrypt)
+ .then(memcmp_complete(that, data), error(that));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_RSA_PSS.html b/dom/crypto/test/test_WebCrypto_RSA_PSS.html
new file mode 100644
index 000000000..5c3975140
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_RSA_PSS.html
@@ -0,0 +1,404 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// Generating 2048-bit keys takes some time.
+SimpleTest.requestLongerTimeout(2);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS key generation (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {
+ name: "RSA-PSS",
+ hash: "SHA-1",
+ modulusLength: 1024,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS key generation and sign/verify round-trip (SHA-256, 2048-bit)",
+ function () {
+ var that = this;
+ var alg = {
+ name: "RSA-PSS",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ var privKey, pubKey;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+ function doSign() {
+ var alg = {name: "RSA-PSS", saltLength: 32};
+ return crypto.subtle.sign(alg, privKey, data);
+ }
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: 32};
+ return crypto.subtle.verify(alg, pubKey, x, data);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+ .then(setKey, error(that))
+ .then(doSign, error(that))
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that))
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS verify known signature (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+ var vec = tv.rsapss;
+
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
+ return crypto.subtle.verify(alg, x, vec.sig, vec.data);
+ }
+
+ crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Test invalid RSA-PSS signatures",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+ var vec = tv.rsapss;
+
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
+ var clone1 = new Uint8Array(vec.data);
+ var clone2 = new Uint8Array(vec.data);
+ clone1[clone1.byteLength - 1] ^= 1;
+ clone2[0] ^= 1;
+
+ return Promise.all([
+ crypto.subtle.verify(alg, x, vec.sig, clone1),
+ crypto.subtle.verify(alg, x, vec.sig, clone2),
+ crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(1)),
+ crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(0, vec.data.byteLength - 1)),
+ ]);
+ }
+
+ crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
+ .then(doVerify, error(that))
+ .then(results => results.every(x => !x))
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS verify known signature (SHA-1, 1024-bit, JWK)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+ return crypto.subtle.verify(alg, x, tv.rsapss.sig, tv.rsapss.data);
+ }
+
+ crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"])
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS verify known signatures (SHA-1 to SHA-512, 1024-bit)",
+ function () {
+ var that = this;
+
+ function verifyCase(hash, tv) {
+ var alg = {name: "RSA-PSS", hash, saltLength: tv.saltLength};
+ return crypto.subtle.importKey("spki", tv.spki, alg, false, ["verify"])
+ .then(x => crypto.subtle.verify(alg, x, tv.sig, tv.data));
+ }
+
+ Promise.all([
+ verifyCase("SHA-1", tv.rsapss2),
+ verifyCase("SHA-256", tv.rsapss3),
+ verifyCase("SHA-384", tv.rsapss4),
+ verifyCase("SHA-512", tv.rsapss5),
+ ]).then(complete(that, x => x.every(y => y)), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS import SPKI/PKCS#8 keys and sign/verify (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+ var privKey, pubKey;
+ function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
+ function doSign() {
+ var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+ return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
+ }
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+ return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
+ }
+
+ var spki =
+ crypto.subtle.importKey("spki", tv.rsapss.spki, alg, false, ["verify"]);
+ var pkcs8 =
+ crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, false, ["sign"]);
+
+ Promise.all([spki, pkcs8])
+ .then(setKeys, error(that))
+ .then(doSign, error(that))
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS import JWK keys and sign/verify (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+ var privKey, pubKey;
+ function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
+ function doSign() {
+ var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+ return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
+ }
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+ return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
+ }
+
+ var spki =
+ crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"]);
+ var pkcs8 =
+ crypto.subtle.importKey("jwk", tv.rsapss.jwk_priv, alg, false, ["sign"]);
+
+ Promise.all([spki, pkcs8])
+ .then(setKeys, error(that))
+ .then(doSign, error(that))
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS SPKI import/export (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("spki", x);
+ }
+
+ crypto.subtle.importKey("spki", tv.rsapss.spki, alg, true, ["verify"])
+ .then(doExport, error(that))
+ .then(memcmp_complete(that, tv.rsapss.spki), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS PKCS#8 import/export (SHA-1, 1024-bit)",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("pkcs8", x);
+ }
+
+ crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, true, ["sign"])
+ .then(doExport, error(that))
+ .then(memcmp_complete(that, tv.rsapss.pkcs8), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS JWK export a public key",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+ var jwk = tv.rsapss.jwk_pub;
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("jwk", x);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ["verify"])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ["n", "e"]) &&
+ x.kty == "RSA" &&
+ x.alg == "PS1" &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ["verify"]) &&
+ x.n == jwk.n &&
+ x.e == jwk.e;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "RSA-PSS JWK export a private key",
+ function () {
+ var that = this;
+ var alg = {name: "RSA-PSS", hash: "SHA-1"};
+ var jwk = tv.rsapss.jwk_priv;
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("jwk", x);
+ }
+
+ crypto.subtle.importKey("jwk", jwk, alg, true, ["sign"])
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return hasBaseJwkFields(x) &&
+ hasFields(x, ["n", "e", "d", "p", "q", "dp", "dq", "qi"]) &&
+ x.kty == "RSA" &&
+ x.alg == "PS1" &&
+ x.ext &&
+ shallowArrayEquals(x.key_ops, ["sign"]) &&
+ x.n == jwk.n &&
+ x.e == jwk.e &&
+ x.d == jwk.d &&
+ x.p == jwk.p &&
+ x.q == jwk.q &&
+ x.dp == jwk.dp &&
+ x.dq == jwk.dq &&
+ x.qi == jwk.qi;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Deterministic RSA-PSS signatures with saltLength=0 (SHA-256, 2048-bit)",
+ function () {
+ var that = this;
+ var alg = {
+ name: "RSA-PSS",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ var privKey, pubKey;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+
+ function doSignTwice() {
+ var alg = {name: "RSA-PSS", saltLength: 0};
+ return Promise.all([
+ crypto.subtle.sign(alg, privKey, data),
+ crypto.subtle.sign(alg, privKey, data)
+ ]);
+ }
+
+ function doVerify(x) {
+ var alg = {name: "RSA-PSS", saltLength: 0};
+ return crypto.subtle.verify(alg, pubKey, x, data);
+ }
+
+ crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+ .then(setKey, error(that))
+ .then(doSignTwice, error(that))
+ .then(([sig1, sig2]) => {
+ if (!util.memcmp(sig1, sig2)) {
+ throw new Error("sig1 must be equal to sig2");
+ }
+
+ return sig1;
+ }, error(that))
+ .then(doVerify, error(that))
+ .then(complete(that, x => x), error(that))
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Reject_Generating_Keys_Without_Usages.html b/dom/crypto/test/test_WebCrypto_Reject_Generating_Keys_Without_Usages.html
new file mode 100644
index 000000000..d252ed2da
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Reject_Generating_Keys_Without_Usages.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// Generating 2048-bit keys takes some time.
+SimpleTest.requestLongerTimeout(2);
+
+TestArray.addTest(
+ "Test that we reject generating keys without any usage",
+ function() {
+ var that = this;
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+ };
+
+ function generateKey(usages) {
+ return crypto.subtle.generateKey(alg, false, usages);
+ }
+
+ generateKey(["encrypt", "decrypt"]).then(function () {
+ return generateKey(["encrypt"]);
+ }).then(function () {
+ return generateKey(["decrypt"]);
+ }).then(function () {
+ return generateKey(["sign"])
+ }, error(that)).then(error(that), complete(that));
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Structured_Cloning.html b/dom/crypto/test/test_WebCrypto_Structured_Cloning.html
new file mode 100644
index 000000000..3275c5466
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Structured_Cloning.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: AES-CTR",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var iv = crypto.getRandomValues(new Uint8Array(16));
+ var alg = {name: "AES-CTR", length: 128, iv};
+
+ var counter = new Uint8Array(16);
+ var algEncrypt = {name: "AES-CTR", length: 128, counter};
+
+ crypto.subtle.generateKey(alg, true, ["encrypt"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.encrypt(algEncrypt, x, data))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: AES-CBC",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var iv = crypto.getRandomValues(new Uint8Array(16));
+ var alg = {name: "AES-CBC", length: 128, iv};
+
+ crypto.subtle.generateKey(alg, true, ["encrypt"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.encrypt(alg, x, data))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: AES-GCM",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var iv = crypto.getRandomValues(new Uint8Array(16));
+ var alg = {name: "AES-GCM", length: 128, iv};
+
+ crypto.subtle.generateKey(alg, true, ["encrypt"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.encrypt(alg, x, data))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: AES-KW",
+ function() {
+ var that = this;
+ var alg = {name: "AES-KW", length: 128};
+
+ crypto.subtle.generateKey(alg, true, ["wrapKey"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.wrapKey("raw", x, x, "AES-KW"))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: HMAC",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var alg = {name: "HMAC", length: 256, hash: "SHA-256"};
+
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.sign("HMAC", x, data))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: PBKDF2",
+ function() {
+ var that = this;
+ var key = new TextEncoder("utf-8").encode("password");
+
+ var alg = {
+ name: "PBKDF2",
+ hash: "SHA-1",
+ salt: crypto.getRandomValues(new Uint8Array(8)),
+ iterations: 4096
+ };
+
+ crypto.subtle.importKey("raw", key, "PBKDF2", true, ["deriveBits"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => crypto.subtle.deriveBits(alg, x, 160))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: HKDF",
+ function() {
+ var that = this;
+ var key = new TextEncoder("utf-8").encode("password");
+
+ var alg = {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ };
+
+ crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+ .then(util.clone)
+ .then(x => crypto.subtle.deriveBits(alg, x, 16))
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: RSA-OAEP",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+
+ var alg = {
+ name: "RSA-OAEP",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ };
+
+ crypto.subtle.generateKey(alg, true, ["encrypt", "decrypt"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ return crypto.subtle.encrypt(alg, x.publicKey, data)
+ .then(ct => crypto.subtle.decrypt(alg, x.privateKey, ct));
+ })
+ .then(complete(that), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: RSASSA-PKCS1-v1_5",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+
+ var alg = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ };
+
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ return crypto.subtle.sign(alg, x.privateKey, data)
+ .then(sig => crypto.subtle.verify(alg, x.publicKey, sig, data));
+ })
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Structured Cloning: RSA-PSS",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+
+ var alg = {
+ name: "RSA-PSS",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ saltLength: 20
+ };
+
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ return crypto.subtle.sign(alg, x.privateKey, data)
+ .then(sig => crypto.subtle.verify(alg, x.publicKey, sig, data));
+ })
+ .then(complete(that, x => x), error(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+/*TestArray.addTest(
+ "Structured Cloning: DH",
+ function() {
+ var that = this;
+ var alg = {name: "DH", prime: tv.dh.prime, generator: new Uint8Array([2])};
+
+ crypto.subtle.generateKey(alg, true, ["deriveBits"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ var alg = {name: "DH", public: x.publicKey};
+ return crypto.subtle.deriveBits(alg, x.privateKey, 16);
+ })
+ .then(complete(that), error(that));
+ }
+);*/
+
+// -----------------------------------------------------------------------------
+/*TestArray.addTest(
+ "Structured Cloning: ECDH",
+ function() {
+ var that = this;
+ var alg = {name: "ECDH", namedCurve: "P-256"};
+
+ crypto.subtle.generateKey(alg, true, ["deriveBits"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ var alg = {name: "ECDH", public: x.publicKey};
+ return crypto.subtle.deriveBits(alg, x.privateKey, 16);
+ })
+ .then(complete(that), error(that));
+ }
+);*/
+
+// -----------------------------------------------------------------------------
+/*TestArray.addTest(
+ "Structured Cloning: ECDSA",
+ function() {
+ var that = this;
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var alg = {name: "ECDSA", namedCurve: "P-256", hash: "SHA-256"};
+
+ crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+ .then(util.cloneExportCompareKeys)
+ .then(x => {
+ return crypto.subtle.sign(alg, x.privateKey, data)
+ .then(sig => crypto.subtle.verify(alg, x.publicKey, sig, data));
+ })
+ .then(complete(that), error(that));
+ }
+);*/
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Workers.html b/dom/crypto/test/test_WebCrypto_Workers.html
new file mode 100644
index 000000000..d92f6169f
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Workers.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Send a CryptoKey to a Worker and use it to encrypt data",
+ function () {
+ var worker = new Worker(`data:text/plain,
+ onmessage = ({data: {key, data, nonce}}) => {
+ var alg = { name: "AES-GCM", iv: nonce };
+ crypto.subtle.encrypt(alg, key, data).then(postMessage);
+ };
+ `);
+
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var nonce = crypto.getRandomValues(new Uint8Array(16));
+ var alg = { name: "AES-GCM", length: 128 };
+ var that = this;
+
+ // Generate a new AES key.
+ crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"]).then(key => {
+ // Wait for ciphertext, check and decrypt.
+ worker.addEventListener("message", ({data: ciphertext}) => {
+ var alg = { name: "AES-GCM", iv: nonce };
+ crypto.subtle.decrypt(alg, key, ciphertext)
+ .then(memcmp_complete(that, data), error(that));
+ });
+
+ // Send it to the worker.
+ worker.postMessage({key, data, nonce});
+ });
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Get a CryptoKey from a Worker and encrypt/decrypt data",
+ function () {
+ var worker = new Worker(`data:text/plain,
+ var alg = { name: "AES-GCM", length: 128 };
+ crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"])
+ .then(postMessage);
+ `);
+
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var nonce = crypto.getRandomValues(new Uint8Array(16));
+ var alg = { name: "AES-GCM", iv: nonce };
+ var that = this;
+
+ // Wait for the key from the worker.
+ worker.addEventListener("message", ({data: key}) => {
+ // Encrypt some data with the key.
+ crypto.subtle.encrypt(alg, key, data).then(ciphertext => {
+ // Verify and decrypt.
+ crypto.subtle.decrypt(alg, key, ciphertext)
+ .then(memcmp_complete(that, data), error(that));
+ });
+ });
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Web crypto in terminating Worker",
+ function () {
+ var worker = new Worker(`data:text/plain,
+ function infiniteEncrypt(key, data, nonce) {
+ var alg = { name: "AES-GCM", iv: nonce };
+ return crypto.subtle.encrypt(alg, key, data).then(_ => {
+ infiniteEncrypt(key, data, nonce);
+ });
+ }
+ onmessage = ({data: {key, data, nonce}}) => {
+ infiniteEncrypt(key, data, nonce);
+ postMessage("started");
+ };
+ `);
+
+ var data = crypto.getRandomValues(new Uint8Array(128));
+ var nonce = crypto.getRandomValues(new Uint8Array(16));
+ var alg = { name: "AES-GCM", length: 128 };
+ var that = this;
+
+ // Generate a new AES key.
+ crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"]).then(key => {
+ worker.addEventListener("message", ({data: msg}) => {
+ if (msg === "started") {
+ // Terminate the worker while its busy doing crypto work
+ worker.terminate();
+ worker = null;
+
+ // Just end the test immediate since we can't receive any
+ // more messages from the worker after calling terminate().
+ // If we haven't crashed, then the test is a success.
+ that.complete(true);
+ }
+ });
+
+ // Send it to the worker.
+ worker.postMessage({key, data, nonce});
+ });
+ }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_WebCrypto_Wrap_Unwrap.html b/dom/crypto/test/test_WebCrypto_Wrap_Unwrap.html
new file mode 100644
index 000000000..c2c7e57e6
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_Wrap_Unwrap.html
@@ -0,0 +1,376 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Key wrap known answer, using AES-GCM",
+ function () {
+ var that = this;
+ var alg = {
+ name: "AES-GCM",
+ iv: tv.key_wrap_known_answer.wrapping_iv,
+ tagLength: 128
+ };
+ var key, wrappingKey;
+
+ function doImport(k) {
+ wrappingKey = k;
+ return crypto.subtle.importKey("raw", tv.key_wrap_known_answer.key,
+ alg, true, ['encrypt', 'decrypt']);
+ }
+ function doWrap(k) {
+ key = k;
+ return crypto.subtle.wrapKey("raw", key, wrappingKey, alg);
+ }
+
+ crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key,
+ alg, false, ['wrapKey'])
+ .then(doImport, error(that))
+ .then(doWrap, error(that))
+ .then(
+ memcmp_complete(that, tv.key_wrap_known_answer.wrapped_key),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Key wrap failing on non-extractable key",
+ function () {
+ var that = this;
+ var alg = {
+ name: "AES-GCM",
+ iv: tv.key_wrap_known_answer.wrapping_iv,
+ tagLength: 128
+ };
+ var key, wrappingKey;
+
+ function doImport(k) {
+ wrappingKey = k;
+ return crypto.subtle.importKey("raw", tv.key_wrap_known_answer.key,
+ alg, false, ['encrypt', 'decrypt']);
+ }
+ function doWrap(k) {
+ key = k;
+ return crypto.subtle.wrapKey("raw", key, wrappingKey, alg);
+ }
+
+ crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key,
+ alg, false, ['wrapKey'])
+ .then(doImport, error(that))
+ .then(doWrap, error(that))
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Key unwrap known answer, using AES-GCM",
+ function () {
+ var that = this;
+ var alg = {
+ name: "AES-GCM",
+ iv: tv.key_wrap_known_answer.wrapping_iv,
+ tagLength: 128
+ };
+ var key, wrappingKey;
+
+ function doUnwrap(k) {
+ wrappingKey = k;
+ return crypto.subtle.unwrapKey(
+ "raw", tv.key_wrap_known_answer.wrapped_key,
+ wrappingKey, alg,
+ "AES-GCM", true, ['encrypt', 'decrypt']
+ );
+ }
+ function doExport(k) {
+ return crypto.subtle.exportKey("raw", k);
+ }
+
+ crypto.subtle.importKey("raw", tv.key_wrap_known_answer.wrapping_key,
+ alg, false, ['unwrapKey'])
+ .then(doUnwrap, error(that))
+ .then(doExport, error(that))
+ .then(
+ memcmp_complete(that, tv.key_wrap_known_answer.key),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "Key wrap/unwrap round-trip, using RSA-OAEP",
+ function () {
+ var that = this;
+ var oaep = {
+ name: "RSA-OAEP",
+ hash: "SHA-256"
+ };
+ var gcm = {
+ name: "AES-GCM",
+ iv: tv.aes_gcm_enc.iv,
+ additionalData: tv.aes_gcm_enc.adata,
+ tagLength: 128
+ };
+ var unwrapKey;
+
+ function doWrap(keys) {
+ var originalKey = keys[0];
+ var wrapKey = keys[1];
+ unwrapKey = keys[2];
+ return crypto.subtle.wrapKey("raw", originalKey, wrapKey, oaep);
+ }
+ function doUnwrap(wrappedKey) {
+ return crypto.subtle.unwrapKey("raw", wrappedKey, unwrapKey, oaep,
+ gcm, false, ['encrypt']);
+ }
+ function doEncrypt(aesKey) {
+ return crypto.subtle.encrypt(gcm, aesKey, tv.aes_gcm_enc.data);
+ }
+
+ // 1.Import:
+ // -> HMAC key
+ // -> OAEP wrap key (public)
+ // -> OAEP unwrap key (private)
+ // 2. Wrap the HMAC key
+ // 3. Unwrap it
+ // 4. Compute HMAC
+ // 5. Check HMAC value
+ Promise.all([
+ crypto.subtle.importKey("raw", tv.aes_gcm_enc.key, gcm, true, ['encrypt']),
+ crypto.subtle.importKey("spki", tv.rsaoaep.spki, oaep, true, ['wrapKey']),
+ crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, oaep, false, ['unwrapKey'])
+ ])
+ .then(doWrap, error(that))
+ .then(doUnwrap, error(that))
+ .then(doEncrypt, error(that))
+ .then(
+ memcmp_complete(that, tv.aes_gcm_enc.result),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK wrap/unwrap round-trip, with AES-GCM",
+ function () {
+ var that = this;
+ var genAlg = { name: "HMAC", hash: "SHA-384", length: 512 };
+ var wrapAlg = { name: "AES-GCM", iv: tv.aes_gcm_enc.iv };
+ var wrapKey, originalKey, originalKeyJwk;
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+ function doWrap() {
+ return crypto.subtle.wrapKey("jwk", originalKey, wrapKey, wrapAlg);
+ }
+ function doUnwrap(wrappedKey) {
+ return crypto.subtle.unwrapKey("jwk", wrappedKey, wrapKey, wrapAlg,
+ { name: "HMAC", hash: "SHA-384"},
+ true, ['sign', 'verify']);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk,
+ "AES-GCM", false, ['wrapKey','unwrapKey'])
+ .then(function(x) { wrapKey = x; }),
+ crypto.subtle.generateKey(genAlg, true, ['sign', 'verify'])
+ .then(function(x) { originalKey = x; return x; })
+ .then(doExport)
+ .then(function(x) { originalKeyJwk = x; })
+ ])
+ .then(doWrap)
+ .then(doUnwrap)
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return exists(x.k) && x.k == originalKeyJwk.k;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-KW known answer",
+ function () {
+ var that = this;
+
+ function doWrap(keys) {
+ var wrapKey = keys[0];
+ var originalKey = keys[1];
+ return crypto.subtle.wrapKey("raw", originalKey, wrapKey, "AES-KW");
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.aes_kw.wrapping_key,
+ "AES-KW", false, ['wrapKey']),
+ crypto.subtle.importKey("jwk", tv.aes_kw.key,
+ "AES-GCM", true, ['encrypt'])
+ ])
+ .then(doWrap)
+ .then(
+ memcmp_complete(that, tv.aes_kw.wrapped_key),
+ error(that)
+ );
+
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-KW unwrap failure on tampered key data",
+ function () {
+ var that = this;
+ var tamperedWrappedKey = new Uint8Array(tv.aes_kw.wrapped_key);
+ tamperedWrappedKey[5] ^= 0xFF;
+
+ function doUnwrap(wrapKey) {
+ return crypto.subtle.unwrapKey("raw", tamperedWrappedKey, wrapKey,
+ "AES-KW", "AES-GCM",
+ true, ['encrypt', 'decrypt']);
+ }
+
+ crypto.subtle.importKey("jwk", tv.aes_kw.wrapping_key,
+ "AES-KW", false, ['unwrapKey'])
+ .then(doUnwrap)
+ .then(error(that), complete(that));
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "AES-KW wrap/unwrap round-trip",
+ function () {
+ var that = this;
+ var genAlg = { name: "HMAC", hash: "SHA-384", length: 512 };
+ var wrapKey, originalKey, originalKeyJwk;
+
+ function doExport(k) {
+ return crypto.subtle.exportKey("jwk", k);
+ }
+ function doWrap() {
+ return crypto.subtle.wrapKey("raw", originalKey, wrapKey, "AES-KW");
+ }
+ function doUnwrap(wrappedKey) {
+ return crypto.subtle.unwrapKey("raw", wrappedKey, wrapKey,
+ "AES-KW", { name: "HMAC", hash: "SHA-384"},
+ true, ['sign', 'verify']);
+ }
+
+ Promise.all([
+ crypto.subtle.importKey("jwk", tv.aes_kw.wrapping_key,
+ "AES-KW", false, ['wrapKey','unwrapKey'])
+ .then(function(x) { wrapKey = x; }),
+ crypto.subtle.generateKey(genAlg, true, ['sign'])
+ .then(function(x) { originalKey = x; return x; })
+ .then(doExport)
+ .then(function(x) { originalKeyJwk = x; })
+ ])
+ .then(doWrap)
+ .then(doUnwrap)
+ .then(doExport)
+ .then(
+ complete(that, function(x) {
+ return exists(x.k) && x.k == originalKeyJwk.k;
+ }),
+ error(that)
+ );
+ }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "JWK unwrap attempt on bogus data should error out",
+ function () {
+ // Largely cribbed from the "JWK wrap/unwrap round-trip, with AES-GCM" test
+ var that = this;
+ var wrapAlg = { name: "AES-GCM", iv: tv.aes_gcm_enc.iv };
+ var wrapKey;
+
+ function doBogusWrap() {
+ var abv = new TextEncoder("utf-8").encode("I am so not JSON");
+ return crypto.subtle.encrypt(wrapAlg, wrapKey, abv);
+ }
+ function doUnwrap(wrappedKey) {
+ return crypto.subtle.unwrapKey("jwk", wrappedKey, wrapKey, wrapAlg,
+ {name: "HMAC", hash: "SHA-384"},
+ true, ['sign', 'verify']);
+ }
+
+ crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk,
+ "AES-GCM", false, ['encrypt','unwrapKey'])
+ .then(function(x) { wrapKey = x; })
+ .then(doBogusWrap, error(that))
+ .then(doUnwrap, error(that))
+ .then(
+ error(that),
+ complete(that)
+ );
+ }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+ <div id="head">
+ <b>Web</b>Crypto<br>
+ </div>
+
+ <div id="start" onclick="start();">RUN ALL</div>
+
+ <div id="resultDiv" class="content">
+ Summary:
+ <span class="pass"><span id="passN">0</span> passed, </span>
+ <span class="fail"><span id="failN">0</span> failed, </span>
+ <span class="pending"><span id="pendingN">0</span> pending.</span>
+ <br/>
+ <br/>
+
+ <table id="results">
+ <tr>
+ <th>Test</th>
+ <th>Result</th>
+ <th>Time</th>
+ </tr>
+ </table>
+
+ </div>
+
+ <div id="foot"></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/crypto/test/test_indexedDB.html b/dom/crypto/test/test_indexedDB.html
new file mode 100644
index 000000000..630c16e0e
--- /dev/null
+++ b/dom/crypto/test/test_indexedDB.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1188750 - WebCrypto must ensure NSS is initialized before deserializing</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"?>
+</head>
+<body>
+ <script type="application/javascript;version=1.8">
+ /*
+ * Bug 1188750 - The WebCrypto API must ensure that NSS was initialized
+ * for the current process before trying to deserialize objects like
+ * CryptoKeys from e.g. IndexedDB.
+ */
+ "use strict";
+
+ const TEST_URI = "http://www.example.com/tests/" +
+ "dom/crypto/test/file_indexedDB.html";
+
+ SimpleTest.waitForExplicitFinish();
+
+ function createMozBrowserFrame(cb) {
+ let frame = document.createElement("iframe");
+ SpecialPowers.wrap(frame).mozbrowser = true;
+ frame.src = TEST_URI;
+
+ frame.addEventListener("mozbrowsershowmodalprompt", function onPrompt(e) {
+ frame.removeEventListener("mozbrowsershowmodalprompt", onPrompt);
+ cb(frame, e.detail.message);
+ });
+
+ document.body.appendChild(frame);
+ }
+
+ function runTest() {
+ // Load the test app once, to generate and store keys.
+ createMozBrowserFrame((frame, result) => {
+ is(result, "ok", "stored keys successfully");
+ frame.remove();
+
+ // Load the test app again to retrieve stored keys.
+ createMozBrowserFrame((frame, result) => {
+ is(result, "ok", "retrieved keys successfully");
+ frame.remove();
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ addEventListener("load", function () {
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({set: [
+ ["dom.ipc.browser_frames.oop_by_default", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]
+ ]}, runTest);
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/crypto/test/util.js b/dom/crypto/test/util.js
new file mode 100644
index 000000000..f7b4c260f
--- /dev/null
+++ b/dom/crypto/test/util.js
@@ -0,0 +1,115 @@
+/* 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/. */
+
+var util = {
+ // Compare the contents of two ArrayBuffer(View)s
+ memcmp: function util_memcmp(x, y) {
+ if (!x || !y) { return false; }
+
+ var xb = new Uint8Array(x);
+ var yb = new Uint8Array(y);
+ if (x.byteLength !== y.byteLength) { return false; }
+
+ for (var i=0; i<xb.byteLength; ++i) {
+ if (xb[i] !== yb[i]) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ // Convert an ArrayBufferView to a hex string
+ abv2hex: function util_abv2hex(abv) {
+ var b = new Uint8Array(abv);
+ var hex = "";
+ for (var i=0; i <b.length; ++i) {
+ var zeropad = (b[i] < 0x10) ? "0" : "";
+ hex += zeropad + b[i].toString(16);
+ }
+ return hex;
+ },
+
+ // Convert a hex string to an ArrayBufferView
+ hex2abv: function util_hex2abv(hex) {
+ if (hex.length % 2 !== 0) {
+ hex = "0" + hex;
+ }
+
+ var abv = new Uint8Array(hex.length / 2);
+ for (var i=0; i<abv.length; ++i) {
+ abv[i] = parseInt(hex.substr(2*i, 2), 16);
+ }
+ return abv;
+ },
+
+ clone: function (obj) {
+ return new Promise(resolve => {
+ let {port1, port2} = new MessageChannel();
+
+ // Wait for the cloned object to arrive.
+ port1.onmessage = msg => resolve(msg.data);
+
+ // Clone the object.
+ port2.postMessage(obj);
+ });
+ },
+
+ cloneExportCompareKeys: function (key) {
+ return util.clone(key).then(clone => {
+ var exports = [];
+
+ if (key instanceof CryptoKey) {
+ exports.push(crypto.subtle.exportKey("raw", key));
+ exports.push(crypto.subtle.exportKey("raw", clone));
+ } else {
+ exports.push(crypto.subtle.exportKey("spki", key.publicKey));
+ exports.push(crypto.subtle.exportKey("spki", clone.publicKey));
+ exports.push(crypto.subtle.exportKey("pkcs8", key.privateKey));
+ exports.push(crypto.subtle.exportKey("pkcs8", clone.privateKey));
+ }
+
+ return Promise.all(exports).then(pairs => {
+ for (var i = 0; i < pairs.length; i += 2) {
+ if (!util.memcmp(pairs[i], pairs[i + 1])) {
+ throw new Error("keys don't match");
+ }
+ }
+
+ return clone;
+ });
+ });
+ }
+};
+
+function exists(x) {
+ return (x !== undefined);
+}
+
+function hasFields(object, fields) {
+ return fields
+ .map(x => exists(object[x]))
+ .reduce((x,y) => (x && y));
+}
+
+function hasKeyFields(x) {
+ return hasFields(x, ["algorithm", "extractable", "type", "usages"]);
+}
+
+function hasBaseJwkFields(x) {
+ return hasFields(x, ["kty", "alg", "ext", "key_ops"]);
+}
+
+function shallowArrayEquals(x, y) {
+ if (x.length != y.length) {
+ return false;
+ }
+
+ for (i in x) {
+ if (x[i] != y[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}