summaryrefslogtreecommitdiffstats
path: root/dom/crypto/CryptoKey.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/crypto/CryptoKey.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/crypto/CryptoKey.cpp')
-rw-r--r--dom/crypto/CryptoKey.cpp1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/dom/crypto/CryptoKey.cpp b/dom/crypto/CryptoKey.cpp
new file mode 100644
index 0000000000..10a6096188
--- /dev/null
+++ b/dom/crypto/CryptoKey.cpp
@@ -0,0 +1,1141 @@
+/* -*- 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::Rooted<JS::Value> 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, aRv);
+ 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, &params);
+ 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, &params);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // Read public point Q.
+ ScopedAutoSECItem ecPoint;
+ rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
+ &ecPoint);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, &params, &ecPoint, aRetVal)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ aRetVal.mD.Construct();
+
+ // Read private value.
+ if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+}
+
+UniqueSECKEYPublicKey CreateECPublicKey(const SECItem* aKeyData,
+ const nsAString& aNamedCurve) {
+ if (!EnsureNSSInitializedChromeOrContent()) {
+ return nullptr;
+ }
+
+ 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;
+ }
+
+ // Transfer arena ownership to the key.
+ key->arena = arena.release();
+ key->keyType = ecKey;
+ key->pkcs11Slot = nullptr;
+ key->pkcs11ID = CK_INVALID_HANDLE;
+
+ // Create curve parameters.
+ SECItem* params = CreateECParamsForCurve(aNamedCurve, key->arena);
+ if (!params) {
+ return nullptr;
+ }
+ key->u.ec.DEREncodedParams = *params;
+
+ // Set public point.
+ SECStatus ret =
+ SECITEM_CopyItem(key->arena, &key->u.ec.publicValue, aKeyData);
+ if (NS_WARN_IF(ret != SECSuccess)) {
+ return nullptr;
+ }
+
+ // Ensure the given point is on the curve.
+ if (!CryptoKey::PublicKeyValid(key.get())) {
+ return nullptr;
+ }
+
+ return key;
+}
+
+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);
+ return id != CK_INVALID_HANDLE;
+}
+
+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