diff options
Diffstat (limited to 'dom/crypto')
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, ¶ms); + 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, ¶ms); + 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, ¶ms, &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, ¶m, mIv); + break; + case CKM_AES_CTR: + ctrParams.ulCounterBits = mCounterLength; + MOZ_ASSERT(mIv.Length() == 16); + memcpy(&ctrParams.cb, mIv.Elements(), 16); + param.type = siBuffer; + param.data = (unsigned char*) &ctrParams; + param.len = sizeof(ctrParams); + break; + case CKM_AES_GCM: + gcmParams.pIv = mIv.Elements(); + gcmParams.ulIvLen = mIv.Length(); + gcmParams.pAAD = mAad.Elements(); + gcmParams.ulAADLen = mAad.Length(); + gcmParams.ulTagBits = mTagLength; + param.type = siBuffer; + param.data = (unsigned char*) &gcmParams; + param.len = sizeof(gcmParams); + break; + default: + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + + // Import the key + SECItem keyItem = { siBuffer, nullptr, 0 }; + ATTEMPT_BUFFER_TO_SECITEM(arena, &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, ¶m, + mResult.Elements(), &outLen, + mResult.Length(), mData.Elements(), + mData.Length())); + } else { + rv = MapSECStatus(PK11_Decrypt(symKey.get(), mMechanism, ¶m, + mResult.Elements(), &outLen, + mResult.Length(), mData.Elements(), + mData.Length())); + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + + mResult.TruncateLength(outLen); + return rv; + } +}; + +// This class looks like an encrypt/decrypt task, like AesTask, +// but it is only exposed to wrapKey/unwrapKey, not encrypt/decrypt +class AesKwTask : public ReturnArrayBufferViewTask, + public DeferredData +{ +public: + AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + : mMechanism(CKM_NSS_AES_KEY_WRAP) + , mSymKey(aKey.GetSymKey()) + , mEncrypt(aEncrypt) + { + Init(aCx, aAlgorithm, aKey, aEncrypt); + } + + AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, const CryptoOperationData& aData, + bool aEncrypt) + : mMechanism(CKM_NSS_AES_KEY_WRAP) + , mSymKey(aKey.GetSymKey()) + , mEncrypt(aEncrypt) + { + Init(aCx, aAlgorithm, aKey, aEncrypt); + SetData(aData); + } + + void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, bool aEncrypt) + { + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_KW); + + nsString algName; + mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); + if (NS_FAILED(mEarlyRv)) { + return; + } + + // Check that we got a reasonable key + if ((mSymKey.Length() != 16) && + (mSymKey.Length() != 24) && + (mSymKey.Length() != 32)) + { + mEarlyRv = NS_ERROR_DOM_DATA_ERR; + return; + } + + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_AES_KW); + } + +private: + CK_MECHANISM_TYPE mMechanism; + CryptoBuffer mSymKey; + bool mEncrypt; + + virtual nsresult DoCrypto() override + { + nsresult rv; + + if (!mDataIsSet) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Check that the input is a multiple of 64 bits long + if (mData.Length() == 0 || mData.Length() % 8 != 0) { + return NS_ERROR_DOM_DATA_ERR; + } + + 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, ¶m, + mResult.Elements(), &outLen, mResult.Length(), + mData.Elements(), mData.Length(), nullptr)); + } else { + rv = MapSECStatus(PK11_PrivDecrypt( + mPrivKey.get(), CKM_RSA_PKCS_OAEP, ¶m, + mResult.Elements(), &outLen, mResult.Length(), + mData.Elements(), mData.Length())); + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + + mResult.TruncateLength(outLen); + return NS_OK; + } +}; + +class HmacTask : public WebCryptoTask +{ +public: + HmacTask(JSContext* aCx, const ObjectOrString& aAlgorithm, + CryptoKey& aKey, + const CryptoOperationData& aSignature, + const CryptoOperationData& aData, + bool aSign) + : mMechanism(aKey.Algorithm().Mechanism()) + , mSymKey(aKey.GetSymKey()) + , mSign(aSign) + { + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HMAC); + + ATTEMPT_BUFFER_INIT(mData, aData); + if (!aSign) { + ATTEMPT_BUFFER_INIT(mSignature, aSignature); + } + + // Check that we got a symmetric key + if (mSymKey.Length() == 0) { + mEarlyRv = NS_ERROR_DOM_DATA_ERR; + return; + } + + TelemetryAlgorithm telemetryAlg; + switch (mMechanism) { + case CKM_SHA_1_HMAC: telemetryAlg = TA_HMAC_SHA_1; break; + case CKM_SHA224_HMAC: telemetryAlg = TA_HMAC_SHA_224; break; + case CKM_SHA256_HMAC: telemetryAlg = TA_HMAC_SHA_256; break; + case CKM_SHA384_HMAC: telemetryAlg = TA_HMAC_SHA_384; break; + case CKM_SHA512_HMAC: telemetryAlg = TA_HMAC_SHA_512; break; + default: telemetryAlg = TA_UNKNOWN; + } + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg); + } + +private: + CK_MECHANISM_TYPE mMechanism; + CryptoBuffer mSymKey; + CryptoBuffer mData; + CryptoBuffer mSignature; + CryptoBuffer mResult; + bool mSign; + + virtual nsresult DoCrypto() override + { + // Initialize the output buffer + if (!mResult.SetLength(HASH_LENGTH_MAX, fallible)) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + 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(), ¶m)); + if (!ctx.get()) { + return NS_ERROR_DOM_OPERATION_ERR; + } + nsresult rv = MapSECStatus(PK11_DigestBegin(ctx.get())); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + rv = MapSECStatus(PK11_DigestOp(ctx.get(), mData.Elements(), mData.Length())); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + rv = MapSECStatus(PK11_DigestFinal(ctx.get(), mResult.Elements(), + &outLen, mResult.Length())); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + + mResult.TruncateLength(outLen); + return rv; + } + + // Returns mResult as an ArrayBufferView, or an error + virtual void Resolve() override + { + if (mSign) { + // Return the computed MAC + TypedArrayCreator<ArrayBuffer> ret(mResult); + mResultPromise->MaybeResolve(ret); + } else { + // Compare the MAC to the provided signature + // No truncation allowed + bool equal = (mResult.Length() == mSignature.Length()); + if (equal) { + int cmp = NSS_SecureMemcmp(mSignature.Elements(), + mResult.Elements(), + mSignature.Length()); + equal = (cmp == 0); + } + mResultPromise->MaybeResolve(equal); + } + } +}; + +class AsymmetricSignVerifyTask : public WebCryptoTask +{ +public: + AsymmetricSignVerifyTask(JSContext* aCx, + const ObjectOrString& aAlgorithm, + CryptoKey& aKey, + const CryptoOperationData& aSignature, + const CryptoOperationData& aData, + bool aSign) + : mOidTag(SEC_OID_UNKNOWN) + , mHashMechanism(UNKNOWN_CK_MECHANISM) + , mMgfMechanism(UNKNOWN_CK_MECHANISM) + , mPrivKey(aKey.GetPrivateKey()) + , mPubKey(aKey.GetPublicKey()) + , mSaltLength(0) + , mSign(aSign) + , mVerified(false) + , mAlgorithm(Algorithm::UNKNOWN) + { + ATTEMPT_BUFFER_INIT(mData, aData); + if (!aSign) { + ATTEMPT_BUFFER_INIT(mSignature, aSignature); + } + + nsString algName; + nsString hashAlgName; + mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); + if (NS_FAILED(mEarlyRv)) { + return; + } + + if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { + mAlgorithm = Algorithm::RSA_PKCS1; + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1); + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1); + hashAlgName = aKey.Algorithm().mRsa.mHash.mName; + } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) { + mAlgorithm = Algorithm::RSA_PSS; + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS); + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS); + + KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash; + hashAlgName = hashAlg.mName; + mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg); + mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName); + + // Check we found appropriate mechanisms. + if (mHashMechanism == UNKNOWN_CK_MECHANISM || + mMgfMechanism == UNKNOWN_CK_MECHANISM) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + + RootedDictionary<RsaPssParams> params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + mSaltLength = params.mSaltLength; + } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + mAlgorithm = Algorithm::ECDSA; + Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA); + CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA); + + // For ECDSA, the hash name comes from the algorithm parameter + RootedDictionary<EcdsaParams> params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + } else { + // This shouldn't happen; CreateSignVerifyTask shouldn't create + // one of these unless it's for the above algorithms. + MOZ_ASSERT(false); + } + + // Must have a valid algorithm by now. + MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN); + + // Determine hash algorithm to use. + mOidTag = MapHashAlgorithmNameToOID(hashAlgName); + if (mOidTag == SEC_OID_UNKNOWN) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + + // Check that we have the appropriate key + if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) { + mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; + return; + } + } + +private: + SECOidTag mOidTag; + CK_MECHANISM_TYPE mHashMechanism; + CK_MECHANISM_TYPE mMgfMechanism; + 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, ¶ms, + 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; +} |