forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1137 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1137 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/dom/CryptoKey.h"
 | |
| 
 | |
| #include <cstddef>
 | |
| #include <cstring>
 | |
| #include <memory>
 | |
| #include <new>
 | |
| #include <utility>
 | |
| #include "blapit.h"
 | |
| #include "certt.h"
 | |
| #include "js/StructuredClone.h"
 | |
| #include "js/TypeDecls.h"
 | |
| #include "keyhi.h"
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/ErrorResult.h"
 | |
| #include "mozilla/MacroForEach.h"
 | |
| #include "mozilla/dom/KeyAlgorithmBinding.h"
 | |
| #include "mozilla/dom/RootedDictionary.h"
 | |
| #include "mozilla/dom/SubtleCryptoBinding.h"
 | |
| #include "mozilla/dom/ToJSValue.h"
 | |
| #include "mozilla/dom/WebCryptoCommon.h"
 | |
| #include "nsDebug.h"
 | |
| #include "nsError.h"
 | |
| #include "nsLiteralString.h"
 | |
| #include "nsNSSComponent.h"
 | |
| #include "nsStringFlags.h"
 | |
| #include "nsTArray.h"
 | |
| #include "pk11pub.h"
 | |
| #include "pkcs11t.h"
 | |
| #include "plarena.h"
 | |
| #include "prtypes.h"
 | |
| #include "secasn1.h"
 | |
| #include "secasn1t.h"
 | |
| #include "seccomon.h"
 | |
| #include "secdert.h"
 | |
| #include "secitem.h"
 | |
| #include "secmodt.h"
 | |
| #include "secoid.h"
 | |
| #include "secoidt.h"
 | |
| 
 | |
| namespace mozilla::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.
 | |
| UniqueSECKEYPrivateKey PrivateKeyFromPrivateKeyTemplate(
 | |
|     CK_ATTRIBUTE* aTemplate, CK_ULONG aTemplateSize) {
 | |
|   // Create a generic object with the contents of the key
 | |
|   UniquePK11SlotInfo slot(PK11_GetInternalSlot());
 | |
|   if (!slot) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Generate a random 160-bit object ID. This ID must be unique.
 | |
|   UniqueSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20));
 | |
|   SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
 | |
|   if (rv != SECSuccess) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   // Check if something is already using this ID.
 | |
|   SECKEYPrivateKey* preexistingKey =
 | |
|       PK11_FindKeyByKeyID(slot.get(), objID.get(), 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.get(), objID->data, objID->len);
 | |
|     if (rv != SECSuccess) {
 | |
|       return nullptr;
 | |
|     }
 | |
|     preexistingKey = PK11_FindKeyByKeyID(slot.get(), objID.get(), 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;
 | |
|   UniquePK11GenericObject obj(
 | |
|       PK11_CreateGenericObject(slot.get(), 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 UniqueSECKEYPrivateKey(
 | |
|       PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr));
 | |
| }
 | |
| 
 | |
| CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
 | |
|     : mGlobal(aGlobal),
 | |
|       mAttributes(0),
 | |
|       mSymKey(),
 | |
|       mPrivateKey(nullptr),
 | |
|       mPublicKey(nullptr) {}
 | |
| 
 | |
| JSObject* CryptoKey::WrapObject(JSContext* aCx,
 | |
|                                 JS::Handle<JSObject*> aGivenProto) {
 | |
|   return CryptoKey_Binding::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;
 | |
|   }
 | |
|   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_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_ENCRYPT));
 | |
|   }
 | |
|   if (mAttributes & DECRYPT) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DECRYPT));
 | |
|   }
 | |
|   if (mAttributes & SIGN) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_SIGN));
 | |
|   }
 | |
|   if (mAttributes & VERIFY) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_VERIFY));
 | |
|   }
 | |
|   if (mAttributes & DERIVEKEY) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY));
 | |
|   }
 | |
|   if (mAttributes & DERIVEBITS) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS));
 | |
|   }
 | |
|   if (mAttributes & WRAPKEY) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_WRAPKEY));
 | |
|   }
 | |
|   if (mAttributes & UNWRAPKEY) {
 | |
|     aRetVal.AppendElement(
 | |
|         NS_LITERAL_STRING_FROM_CSTRING(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);
 | |
| 
 | |
|   // Read EC params.
 | |
|   ScopedAutoSECItem params;
 | |
|   SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(),
 | |
|                                        CKA_EC_PARAMS, ¶ms);
 | |
|   if (rv != SECSuccess) {
 | |
|     return NS_ERROR_DOM_OPERATION_ERR;
 | |
|   }
 | |
| 
 | |
|   // Read private value.
 | |
|   ScopedAutoSECItem value;
 | |
|   rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), 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) {
 | |
|   KeyUsage usage;
 | |
|   if (NS_FAILED(StringToUsage(aUsage, usage))) {
 | |
|     return NS_ERROR_DOM_SYNTAX_ERR;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
 | |
| 
 | |
|   // This is harmless if usage is 0, so we don't repeat the assertion check
 | |
|   AddUsage(usage);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::AddAllowedUsage(const nsString& aUsage,
 | |
|                                     const nsString& aAlgorithm) {
 | |
|   return AddAllowedUsageIntersecting(aUsage, aAlgorithm, USAGES_MASK);
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::AddAllowedUsageIntersecting(const nsString& aUsage,
 | |
|                                                 const nsString& aAlgorithm,
 | |
|                                                 uint32_t aUsageMask) {
 | |
|   uint32_t allowedUsages = GetAllowedUsagesForAlgorithm(aAlgorithm);
 | |
|   KeyUsage usage;
 | |
|   if (NS_FAILED(StringToUsage(aUsage, usage))) {
 | |
|     return NS_ERROR_DOM_SYNTAX_ERR;
 | |
|   }
 | |
| 
 | |
|   if ((usage & allowedUsages) != usage) {
 | |
|     return NS_ERROR_DOM_SYNTAX_ERR;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
 | |
| 
 | |
|   // This is harmless if usage is 0, so we don't repeat the assertion check
 | |
|   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;
 | |
| }
 | |
| 
 | |
| uint32_t CryptoKey::GetAllowedUsagesForAlgorithm(const nsString& aAlgorithm) {
 | |
|   uint32_t allowedUsages = 0;
 | |
|   if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
 | |
|       aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
 | |
|       aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
 | |
|       aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
 | |
|     allowedUsages = ENCRYPT | DECRYPT | WRAPKEY | UNWRAPKEY;
 | |
|   } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_KW)) {
 | |
|     allowedUsages = WRAPKEY | UNWRAPKEY;
 | |
|   } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HMAC) ||
 | |
|              aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
 | |
|              aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
 | |
|              aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDSA)) {
 | |
|     allowedUsages = SIGN | VERIFY;
 | |
|   } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
 | |
|              aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
 | |
|              aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
 | |
|     allowedUsages = DERIVEBITS | DERIVEKEY;
 | |
|   }
 | |
|   return allowedUsages;
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey) {
 | |
|   if (!mSymKey.Assign(aSymKey)) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey) {
 | |
|   if (!aPrivateKey) {
 | |
|     mPrivateKey = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mPrivateKey = UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(aPrivateKey));
 | |
|   return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey) {
 | |
|   if (!aPublicKey) {
 | |
|     mPublicKey = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mPublicKey = UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(aPublicKey));
 | |
|   return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 | |
| }
 | |
| 
 | |
| const CryptoBuffer& CryptoKey::GetSymKey() const { return mSymKey; }
 | |
| 
 | |
| UniqueSECKEYPrivateKey CryptoKey::GetPrivateKey() const {
 | |
|   if (!mPrivateKey) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get()));
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPublicKey CryptoKey::GetPublicKey() const {
 | |
|   if (!mPublicKey) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get()));
 | |
| }
 | |
| 
 | |
| // Serialization and deserialization convenience methods
 | |
| 
 | |
| UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData) {
 | |
|   UniquePK11SlotInfo slot(PK11_GetInternalSlot());
 | |
|   if (!slot) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
 | |
|   if (!arena) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   SECItem pkcs8Item = {siBuffer, nullptr, 0};
 | |
|   if (!aKeyData.ToSECItem(arena.get(), &pkcs8Item)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Allow everything, we enforce usage ourselves
 | |
|   unsigned int usage = KU_ALL;
 | |
| 
 | |
|   SECKEYPrivateKey* privKey;
 | |
|   SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
 | |
|       slot.get(), &pkcs8Item, nullptr, nullptr, false, false, usage, &privKey,
 | |
|       nullptr);
 | |
| 
 | |
|   if (rv == SECFailure) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return UniqueSECKEYPrivateKey(privKey);
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPublicKey CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData) {
 | |
|   UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
 | |
|   if (!arena) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   SECItem spkiItem = {siBuffer, nullptr, 0};
 | |
|   if (!aKeyData.ToSECItem(arena.get(), &spkiItem)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   UniqueCERTSubjectPublicKeyInfo spki(
 | |
|       SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
 | |
|   if (!spki) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool isECDHAlgorithm =
 | |
|       SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm);
 | |
| 
 | |
|   // Check for |id-ecDH|. Per old versions of the WebCrypto spec we must
 | |
|   // support this OID but NSS does unfortunately not know it. Let's
 | |
|   // change the algorithm to |id-ecPublicKey| to make NSS happy.
 | |
|   if (isECDHAlgorithm) {
 | |
|     SECOidTag oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
 | |
| 
 | |
|     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;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   UniqueSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
 | |
|   if (!tmp.get() || !PublicKeyValid(tmp.get())) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get()));
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
 | |
|                                       CryptoBuffer& aRetVal) {
 | |
|   UniqueSECItem 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 CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
 | |
|                                     CryptoBuffer& aRetVal) {
 | |
|   UniqueCERTSubjectPublicKeyInfo spki;
 | |
| 
 | |
|   spki.reset(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
 | |
|   if (!spki) {
 | |
|     return NS_ERROR_DOM_OPERATION_ERR;
 | |
|   }
 | |
| 
 | |
|   const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
 | |
|   UniqueSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki.get(), 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;
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk) {
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|     UniquePLArenaPool 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;
 | |
|     }
 | |
| 
 | |
|     // 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_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P256));
 | |
|       break;
 | |
|     case SEC_OID_SECG_EC_SECP384R1:
 | |
|       flen = 48;  // bytes
 | |
|       aRetVal.mCrv.Construct(
 | |
|           NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P384));
 | |
|       break;
 | |
|     case SEC_OID_SECG_EC_SECP521R1:
 | |
|       flen = 66;  // bytes
 | |
|       aRetVal.mCrv.Construct(
 | |
|           NS_LITERAL_STRING_FROM_CSTRING(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;
 | |
|   }
 | |
| 
 | |
|   UniqueSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
 | |
|   UniqueSECItem 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.get()) ||
 | |
|       NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
 | |
|       !y.Assign(ecPointY.get()) ||
 | |
|       NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_EC);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsresult CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
 | |
|                                     JsonWebKey& aRetVal) {
 | |
|   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_FROM_CSTRING(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;
 | |
|   }
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPublicKey CreateECPublicKey(const SECItem* aKeyData,
 | |
|                                         const nsString& aNamedCurve) {
 | |
|   UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
 | |
|   if (!arena) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // It's important that this be a UniqueSECKEYPublicKey, 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.
 | |
|   UniqueSECKEYPublicKey key(PORT_ArenaZNew(arena.get(), 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.get());
 | |
|   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.get())) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key.get()));
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPublicKey CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk) {
 | |
|   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,
 | |
|         }};
 | |
| 
 | |
|     UniqueSECItem pkDer(
 | |
|         SEC_ASN1EncodeItem(nullptr, nullptr, &input, rsaPublicKeyTemplate));
 | |
|     if (!pkDer.get()) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     return UniqueSECKEYPublicKey(
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     UniquePLArenaPool 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) {
 | |
|   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_FROM_CSTRING(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;
 | |
|   }
 | |
| }
 | |
| 
 | |
| UniqueSECKEYPublicKey CryptoKey::PublicECKeyFromRaw(
 | |
|     CryptoBuffer& aKeyData, const nsString& aNamedCurve) {
 | |
|   UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
 | |
|   if (!arena) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   SECItem rawItem = {siBuffer, nullptr, 0};
 | |
|   if (!aKeyData.ToSECItem(arena.get(), &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) {
 | |
|   if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) {
 | |
|     return NS_ERROR_DOM_OPERATION_ERR;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey) {
 | |
|   UniquePK11SlotInfo 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.get(), aPubKey, PR_FALSE);
 | |
|   if (id == CK_INVALID_HANDLE) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   SECStatus rv = PK11_DestroyObject(slot.get(), id);
 | |
|   return (rv == SECSuccess);
 | |
| }
 | |
| 
 | |
| bool CryptoKey::WriteStructuredClone(JSContext* aCX,
 | |
|                                      JSStructuredCloneWriter* aWriter) const {
 | |
|   // 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.get(), priv))) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mPublicKey) {
 | |
|     if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey.get(), pub))) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) &&
 | |
|          WriteBuffer(aWriter, mSymKey) && WriteBuffer(aWriter, priv) &&
 | |
|          WriteBuffer(aWriter, pub) && mAlgorithm.WriteStructuredClone(aWriter);
 | |
| }
 | |
| 
 | |
| // static
 | |
| already_AddRefed<CryptoKey> CryptoKey::ReadStructuredClone(
 | |
|     JSContext* aCx, nsIGlobalObject* aGlobal,
 | |
|     JSStructuredCloneReader* aReader) {
 | |
|   // Ensure that NSS is initialized.
 | |
|   if (!EnsureNSSInitializedChromeOrContent()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<CryptoKey> key = new CryptoKey(aGlobal);
 | |
| 
 | |
|   uint32_t version;
 | |
|   CryptoBuffer sym, priv, pub;
 | |
| 
 | |
|   bool read = JS_ReadUint32Pair(aReader, &key->mAttributes, &version) &&
 | |
|               (version == CRYPTOKEY_SC_VERSION) && ReadBuffer(aReader, sym) &&
 | |
|               ReadBuffer(aReader, priv) && ReadBuffer(aReader, pub) &&
 | |
|               key->mAlgorithm.ReadStructuredClone(aReader);
 | |
|   if (!read) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (sym.Length() > 0 && !key->mSymKey.Assign(sym)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (priv.Length() > 0) {
 | |
|     key->mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv);
 | |
|   }
 | |
|   if (pub.Length() > 0) {
 | |
|     key->mPublicKey = CryptoKey::PublicKeyFromSpki(pub);
 | |
|   }
 | |
| 
 | |
|   // Ensure that what we've read is consistent
 | |
|   // If the attributes indicate a key type, should have a key of that type
 | |
|   if (!((key->GetKeyType() == SECRET && key->mSymKey.Length() > 0) ||
 | |
|         (key->GetKeyType() == PRIVATE && key->mPrivateKey) ||
 | |
|         (key->GetKeyType() == PUBLIC && key->mPublicKey))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return key.forget();
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 | 
