Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / crypto / CryptoKey.cpp
blob10a60961886e3e6ef5908978dcef18c31fed5807
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/CryptoKey.h"
9 #include <cstddef>
10 #include <cstring>
11 #include <memory>
12 #include <new>
13 #include <utility>
14 #include "blapit.h"
15 #include "certt.h"
16 #include "js/StructuredClone.h"
17 #include "js/TypeDecls.h"
18 #include "keyhi.h"
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/ErrorResult.h"
21 #include "mozilla/MacroForEach.h"
22 #include "mozilla/dom/KeyAlgorithmBinding.h"
23 #include "mozilla/dom/RootedDictionary.h"
24 #include "mozilla/dom/SubtleCryptoBinding.h"
25 #include "mozilla/dom/ToJSValue.h"
26 #include "mozilla/dom/WebCryptoCommon.h"
27 #include "nsDebug.h"
28 #include "nsError.h"
29 #include "nsLiteralString.h"
30 #include "nsNSSComponent.h"
31 #include "nsStringFlags.h"
32 #include "nsTArray.h"
33 #include "pk11pub.h"
34 #include "pkcs11t.h"
35 #include "plarena.h"
36 #include "prtypes.h"
37 #include "secasn1.h"
38 #include "secasn1t.h"
39 #include "seccomon.h"
40 #include "secdert.h"
41 #include "secitem.h"
42 #include "secmodt.h"
43 #include "secoid.h"
44 #include "secoidt.h"
46 namespace mozilla::dom {
48 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal)
49 NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey)
50 NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey)
51 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey)
52 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
53 NS_INTERFACE_MAP_ENTRY(nsISupports)
54 NS_INTERFACE_MAP_END
56 nsresult StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut) {
57 if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_ENCRYPT)) {
58 aUsageOut = CryptoKey::ENCRYPT;
59 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DECRYPT)) {
60 aUsageOut = CryptoKey::DECRYPT;
61 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_SIGN)) {
62 aUsageOut = CryptoKey::SIGN;
63 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_VERIFY)) {
64 aUsageOut = CryptoKey::VERIFY;
65 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEKEY)) {
66 aUsageOut = CryptoKey::DERIVEKEY;
67 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEBITS)) {
68 aUsageOut = CryptoKey::DERIVEBITS;
69 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_WRAPKEY)) {
70 aUsageOut = CryptoKey::WRAPKEY;
71 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)) {
72 aUsageOut = CryptoKey::UNWRAPKEY;
73 } else {
74 return NS_ERROR_DOM_SYNTAX_ERR;
76 return NS_OK;
79 // This helper function will release the memory backing a SECKEYPrivateKey and
80 // any resources acquired in its creation. It will leave the backing PKCS#11
81 // object untouched, however. This should only be called from
82 // PrivateKeyFromPrivateKeyTemplate.
83 static void DestroyPrivateKeyWithoutDestroyingPKCS11Object(
84 SECKEYPrivateKey* key) {
85 PK11_FreeSlot(key->pkcs11Slot);
86 PORT_FreeArena(key->arena, PR_TRUE);
89 // To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
90 // generates a random ID for each key. The given template must contain an
91 // attribute slot for a key ID, but it must consist of a null pointer and have a
92 // length of 0.
93 UniqueSECKEYPrivateKey PrivateKeyFromPrivateKeyTemplate(
94 CK_ATTRIBUTE* aTemplate, CK_ULONG aTemplateSize) {
95 // Create a generic object with the contents of the key
96 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
97 if (!slot) {
98 return nullptr;
101 // Generate a random 160-bit object ID. This ID must be unique.
102 UniqueSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20));
103 SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
104 if (rv != SECSuccess) {
105 return nullptr;
107 // Check if something is already using this ID.
108 SECKEYPrivateKey* preexistingKey =
109 PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
110 if (preexistingKey) {
111 // Note that we can't just call SECKEY_DestroyPrivateKey here because that
112 // will destroy the PKCS#11 object that is backing a preexisting key (that
113 // we still have a handle on somewhere else in memory). If that object were
114 // destroyed, cryptographic operations performed by that other key would
115 // fail.
116 DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
117 // Try again with a new ID (but only once - collisions are very unlikely).
118 rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
119 if (rv != SECSuccess) {
120 return nullptr;
122 preexistingKey = PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
123 if (preexistingKey) {
124 DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
125 return nullptr;
129 CK_ATTRIBUTE* idAttributeSlot = nullptr;
130 for (CK_ULONG i = 0; i < aTemplateSize; i++) {
131 if (aTemplate[i].type == CKA_ID) {
132 if (aTemplate[i].pValue != nullptr || aTemplate[i].ulValueLen != 0) {
133 return nullptr;
135 idAttributeSlot = aTemplate + i;
136 break;
139 if (!idAttributeSlot) {
140 return nullptr;
143 idAttributeSlot->pValue = objID->data;
144 idAttributeSlot->ulValueLen = objID->len;
145 UniquePK11GenericObject obj(
146 PK11_CreateGenericObject(slot.get(), aTemplate, aTemplateSize, PR_FALSE));
147 // Unset the ID attribute slot's pointer and length so that data that only
148 // lives for the scope of this function doesn't escape.
149 idAttributeSlot->pValue = nullptr;
150 idAttributeSlot->ulValueLen = 0;
151 if (!obj) {
152 return nullptr;
155 // Have NSS translate the object to a private key.
156 return UniqueSECKEYPrivateKey(
157 PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr));
160 CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
161 : mGlobal(aGlobal),
162 mAttributes(0),
163 mSymKey(),
164 mPrivateKey(nullptr),
165 mPublicKey(nullptr) {}
167 JSObject* CryptoKey::WrapObject(JSContext* aCx,
168 JS::Handle<JSObject*> aGivenProto) {
169 return CryptoKey_Binding::Wrap(aCx, this, aGivenProto);
172 void CryptoKey::GetType(nsString& aRetVal) const {
173 uint32_t type = mAttributes & TYPE_MASK;
174 switch (type) {
175 case PUBLIC:
176 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC);
177 break;
178 case PRIVATE:
179 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE);
180 break;
181 case SECRET:
182 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_SECRET);
183 break;
187 bool CryptoKey::Extractable() const { return (mAttributes & EXTRACTABLE); }
189 void CryptoKey::GetAlgorithm(JSContext* cx,
190 JS::MutableHandle<JSObject*> aRetVal,
191 ErrorResult& aRv) const {
192 bool converted = false;
193 JS::Rooted<JS::Value> val(cx);
194 switch (mAlgorithm.mType) {
195 case KeyAlgorithmProxy::AES:
196 converted = ToJSValue(cx, mAlgorithm.mAes, &val);
197 break;
198 case KeyAlgorithmProxy::HMAC:
199 converted = ToJSValue(cx, mAlgorithm.mHmac, &val);
200 break;
201 case KeyAlgorithmProxy::RSA: {
202 RootedDictionary<RsaHashedKeyAlgorithm> rsa(cx);
203 converted = mAlgorithm.mRsa.ToKeyAlgorithm(cx, rsa, aRv);
204 if (converted) {
205 converted = ToJSValue(cx, rsa, &val);
207 break;
209 case KeyAlgorithmProxy::EC:
210 converted = ToJSValue(cx, mAlgorithm.mEc, &val);
211 break;
213 if (!converted) {
214 aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
215 return;
218 aRetVal.set(&val.toObject());
221 void CryptoKey::GetUsages(nsTArray<nsString>& aRetVal) const {
222 if (mAttributes & ENCRYPT) {
223 aRetVal.AppendElement(
224 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_ENCRYPT));
226 if (mAttributes & DECRYPT) {
227 aRetVal.AppendElement(
228 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DECRYPT));
230 if (mAttributes & SIGN) {
231 aRetVal.AppendElement(
232 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_SIGN));
234 if (mAttributes & VERIFY) {
235 aRetVal.AppendElement(
236 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_VERIFY));
238 if (mAttributes & DERIVEKEY) {
239 aRetVal.AppendElement(
240 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY));
242 if (mAttributes & DERIVEBITS) {
243 aRetVal.AppendElement(
244 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS));
246 if (mAttributes & WRAPKEY) {
247 aRetVal.AppendElement(
248 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_WRAPKEY));
250 if (mAttributes & UNWRAPKEY) {
251 aRetVal.AppendElement(
252 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_UNWRAPKEY));
256 KeyAlgorithmProxy& CryptoKey::Algorithm() { return mAlgorithm; }
258 const KeyAlgorithmProxy& CryptoKey::Algorithm() const { return mAlgorithm; }
260 CryptoKey::KeyType CryptoKey::GetKeyType() const {
261 return static_cast<CryptoKey::KeyType>(mAttributes & TYPE_MASK);
264 nsresult CryptoKey::SetType(const nsString& aType) {
265 mAttributes &= CLEAR_TYPE;
266 if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_SECRET)) {
267 mAttributes |= SECRET;
268 } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC)) {
269 mAttributes |= PUBLIC;
270 } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE)) {
271 mAttributes |= PRIVATE;
272 } else {
273 mAttributes |= UNKNOWN;
274 return NS_ERROR_DOM_SYNTAX_ERR;
277 return NS_OK;
280 void CryptoKey::SetType(CryptoKey::KeyType aType) {
281 mAttributes &= CLEAR_TYPE;
282 mAttributes |= aType;
285 void CryptoKey::SetExtractable(bool aExtractable) {
286 mAttributes &= CLEAR_EXTRACTABLE;
287 if (aExtractable) {
288 mAttributes |= EXTRACTABLE;
292 // NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the
293 // public value. To properly export the private key to JWK or PKCS #8 we need
294 // the public key data though and so we use this method to augment a private
295 // key with data from the given public key.
296 nsresult CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey) {
297 // This should be a private key.
298 MOZ_ASSERT(GetKeyType() == PRIVATE);
299 // There should be a private NSS key with type 'EC'.
300 MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey);
301 // The given public key should have the same key type.
302 MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType);
304 // Read EC params.
305 ScopedAutoSECItem params;
306 SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(),
307 CKA_EC_PARAMS, &params);
308 if (rv != SECSuccess) {
309 return NS_ERROR_DOM_OPERATION_ERR;
312 // Read private value.
313 ScopedAutoSECItem value;
314 rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), CKA_VALUE,
315 &value);
316 if (rv != SECSuccess) {
317 return NS_ERROR_DOM_OPERATION_ERR;
320 SECItem* point = &aPublicKey->u.ec.publicValue;
321 CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
322 CK_BBOOL falseValue = CK_FALSE;
323 CK_KEY_TYPE ecValue = CKK_EC;
325 CK_ATTRIBUTE keyTemplate[9] = {
326 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
327 {CKA_KEY_TYPE, &ecValue, sizeof(ecValue)},
328 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
329 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
330 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
331 // PrivateKeyFromPrivateKeyTemplate sets the ID.
332 {CKA_ID, nullptr, 0},
333 {CKA_EC_PARAMS, params.data, params.len},
334 {CKA_EC_POINT, point->data, point->len},
335 {CKA_VALUE, value.data, value.len},
338 mPrivateKey =
339 PrivateKeyFromPrivateKeyTemplate(keyTemplate, ArrayLength(keyTemplate));
340 NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
342 return NS_OK;
345 void CryptoKey::ClearUsages() { mAttributes &= CLEAR_USAGES; }
347 nsresult CryptoKey::AddUsage(const nsString& aUsage) {
348 KeyUsage usage;
349 if (NS_FAILED(StringToUsage(aUsage, usage))) {
350 return NS_ERROR_DOM_SYNTAX_ERR;
353 MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
355 // This is harmless if usage is 0, so we don't repeat the assertion check
356 AddUsage(usage);
357 return NS_OK;
360 nsresult CryptoKey::AddAllowedUsage(const nsString& aUsage,
361 const nsString& aAlgorithm) {
362 return AddAllowedUsageIntersecting(aUsage, aAlgorithm, USAGES_MASK);
365 nsresult CryptoKey::AddAllowedUsageIntersecting(const nsString& aUsage,
366 const nsString& aAlgorithm,
367 uint32_t aUsageMask) {
368 uint32_t allowedUsages = GetAllowedUsagesForAlgorithm(aAlgorithm);
369 KeyUsage usage;
370 if (NS_FAILED(StringToUsage(aUsage, usage))) {
371 return NS_ERROR_DOM_SYNTAX_ERR;
374 if ((usage & allowedUsages) != usage) {
375 return NS_ERROR_DOM_SYNTAX_ERR;
378 MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
380 // This is harmless if usage is 0, so we don't repeat the assertion check
381 if (usage & aUsageMask) {
382 AddUsage(usage);
383 return NS_OK;
386 return NS_OK;
389 void CryptoKey::AddUsage(CryptoKey::KeyUsage aUsage) { mAttributes |= aUsage; }
391 bool CryptoKey::HasAnyUsage() { return !!(mAttributes & USAGES_MASK); }
393 bool CryptoKey::HasUsage(CryptoKey::KeyUsage aUsage) {
394 return !!(mAttributes & aUsage);
397 bool CryptoKey::HasUsageOtherThan(uint32_t aUsages) {
398 return !!(mAttributes & USAGES_MASK & ~aUsages);
401 bool CryptoKey::IsRecognizedUsage(const nsString& aUsage) {
402 KeyUsage dummy;
403 nsresult rv = StringToUsage(aUsage, dummy);
404 return NS_SUCCEEDED(rv);
407 bool CryptoKey::AllUsagesRecognized(const Sequence<nsString>& aUsages) {
408 for (uint32_t i = 0; i < aUsages.Length(); ++i) {
409 if (!IsRecognizedUsage(aUsages[i])) {
410 return false;
413 return true;
416 uint32_t CryptoKey::GetAllowedUsagesForAlgorithm(const nsString& aAlgorithm) {
417 uint32_t allowedUsages = 0;
418 if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
419 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
420 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
421 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
422 allowedUsages = ENCRYPT | DECRYPT | WRAPKEY | UNWRAPKEY;
423 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_KW)) {
424 allowedUsages = WRAPKEY | UNWRAPKEY;
425 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HMAC) ||
426 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
427 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
428 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDSA)) {
429 allowedUsages = SIGN | VERIFY;
430 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
431 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
432 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
433 allowedUsages = DERIVEBITS | DERIVEKEY;
435 return allowedUsages;
438 nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey) {
439 if (!mSymKey.Assign(aSymKey)) {
440 return NS_ERROR_OUT_OF_MEMORY;
443 return NS_OK;
446 nsresult CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey) {
447 if (!aPrivateKey) {
448 mPrivateKey = nullptr;
449 return NS_OK;
452 mPrivateKey = UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(aPrivateKey));
453 return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
456 nsresult CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey) {
457 if (!aPublicKey) {
458 mPublicKey = nullptr;
459 return NS_OK;
462 mPublicKey = UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(aPublicKey));
463 return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
466 const CryptoBuffer& CryptoKey::GetSymKey() const { return mSymKey; }
468 UniqueSECKEYPrivateKey CryptoKey::GetPrivateKey() const {
469 if (!mPrivateKey) {
470 return nullptr;
472 return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get()));
475 UniqueSECKEYPublicKey CryptoKey::GetPublicKey() const {
476 if (!mPublicKey) {
477 return nullptr;
479 return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get()));
482 // Serialization and deserialization convenience methods
484 UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData) {
485 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
486 if (!slot) {
487 return nullptr;
490 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
491 if (!arena) {
492 return nullptr;
495 SECItem pkcs8Item = {siBuffer, nullptr, 0};
496 if (!aKeyData.ToSECItem(arena.get(), &pkcs8Item)) {
497 return nullptr;
500 // Allow everything, we enforce usage ourselves
501 unsigned int usage = KU_ALL;
503 SECKEYPrivateKey* privKey;
504 SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
505 slot.get(), &pkcs8Item, nullptr, nullptr, false, false, usage, &privKey,
506 nullptr);
508 if (rv == SECFailure) {
509 return nullptr;
512 return UniqueSECKEYPrivateKey(privKey);
515 UniqueSECKEYPublicKey CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData) {
516 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
517 if (!arena) {
518 return nullptr;
521 SECItem spkiItem = {siBuffer, nullptr, 0};
522 if (!aKeyData.ToSECItem(arena.get(), &spkiItem)) {
523 return nullptr;
526 UniqueCERTSubjectPublicKeyInfo spki(
527 SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
528 if (!spki) {
529 return nullptr;
532 bool isECDHAlgorithm =
533 SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm);
535 // Check for |id-ecDH|. Per old versions of the WebCrypto spec we must
536 // support this OID but NSS does unfortunately not know it. Let's
537 // change the algorithm to |id-ecPublicKey| to make NSS happy.
538 if (isECDHAlgorithm) {
539 SECOidTag oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
541 SECOidData* oidData = SECOID_FindOIDByTag(oid);
542 if (!oidData) {
543 return nullptr;
546 SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
547 &oidData->oid);
548 if (rv != SECSuccess) {
549 return nullptr;
553 UniqueSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
554 if (!tmp.get() || !PublicKeyValid(tmp.get())) {
555 return nullptr;
558 return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get()));
561 nsresult CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
562 CryptoBuffer& aRetVal) {
563 UniqueSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr));
564 if (!pkcs8Item.get()) {
565 return NS_ERROR_DOM_INVALID_ACCESS_ERR;
567 if (!aRetVal.Assign(pkcs8Item.get())) {
568 return NS_ERROR_DOM_OPERATION_ERR;
570 return NS_OK;
573 nsresult CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
574 CryptoBuffer& aRetVal) {
575 UniqueCERTSubjectPublicKeyInfo spki;
577 spki.reset(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
578 if (!spki) {
579 return NS_ERROR_DOM_OPERATION_ERR;
582 const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
583 UniqueSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki.get(), tpl));
585 if (!aRetVal.Assign(spkiItem.get())) {
586 return NS_ERROR_DOM_OPERATION_ERR;
588 return NS_OK;
591 SECItem* CreateECPointForCoordinates(const CryptoBuffer& aX,
592 const CryptoBuffer& aY,
593 PLArenaPool* aArena) {
594 // Check that both points have the same length.
595 if (aX.Length() != aY.Length()) {
596 return nullptr;
599 // Create point.
600 SECItem* point =
601 ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
602 if (!point) {
603 return nullptr;
606 // Set point data.
607 point->data[0] = EC_POINT_FORM_UNCOMPRESSED;
608 memcpy(point->data + 1, aX.Elements(), aX.Length());
609 memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length());
611 return point;
614 UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk) {
615 CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
616 CK_BBOOL falseValue = CK_FALSE;
618 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
619 // Verify that all of the required parameters are present
620 CryptoBuffer x, y, d;
621 if (!aJwk.mCrv.WasPassed() || !aJwk.mX.WasPassed() ||
622 NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || !aJwk.mY.WasPassed() ||
623 NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) || !aJwk.mD.WasPassed() ||
624 NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) {
625 return nullptr;
628 nsString namedCurve;
629 if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
630 return nullptr;
633 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
634 if (!arena) {
635 return nullptr;
638 // Create parameters.
639 SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
640 if (!params) {
641 return nullptr;
644 SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get());
645 if (!ecPoint) {
646 return nullptr;
649 // Populate template from parameters
650 CK_KEY_TYPE ecValue = CKK_EC;
651 CK_ATTRIBUTE keyTemplate[9] = {
652 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
653 {CKA_KEY_TYPE, &ecValue, sizeof(ecValue)},
654 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
655 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
656 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
657 // PrivateKeyFromPrivateKeyTemplate sets the ID.
658 {CKA_ID, nullptr, 0},
659 {CKA_EC_PARAMS, params->data, params->len},
660 {CKA_EC_POINT, ecPoint->data, ecPoint->len},
661 {CKA_VALUE, (void*)d.Elements(), (CK_ULONG)d.Length()},
664 return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
665 ArrayLength(keyTemplate));
668 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
669 // Verify that all of the required parameters are present
670 CryptoBuffer n, e, d, p, q, dp, dq, qi;
671 if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
672 !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
673 !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
674 !aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
675 !aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
676 !aJwk.mDp.WasPassed() ||
677 NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
678 !aJwk.mDq.WasPassed() ||
679 NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
680 !aJwk.mQi.WasPassed() ||
681 NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
682 return nullptr;
685 // Populate template from parameters
686 CK_KEY_TYPE rsaValue = CKK_RSA;
687 CK_ATTRIBUTE keyTemplate[14] = {
688 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
689 {CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue)},
690 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
691 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
692 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
693 // PrivateKeyFromPrivateKeyTemplate sets the ID.
694 {CKA_ID, nullptr, 0},
695 {CKA_MODULUS, (void*)n.Elements(), (CK_ULONG)n.Length()},
696 {CKA_PUBLIC_EXPONENT, (void*)e.Elements(), (CK_ULONG)e.Length()},
697 {CKA_PRIVATE_EXPONENT, (void*)d.Elements(), (CK_ULONG)d.Length()},
698 {CKA_PRIME_1, (void*)p.Elements(), (CK_ULONG)p.Length()},
699 {CKA_PRIME_2, (void*)q.Elements(), (CK_ULONG)q.Length()},
700 {CKA_EXPONENT_1, (void*)dp.Elements(), (CK_ULONG)dp.Length()},
701 {CKA_EXPONENT_2, (void*)dq.Elements(), (CK_ULONG)dq.Length()},
702 {CKA_COEFFICIENT, (void*)qi.Elements(), (CK_ULONG)qi.Length()},
705 return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
706 ArrayLength(keyTemplate));
709 return nullptr;
712 bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
713 CK_ATTRIBUTE_TYPE aAttribute,
714 Optional<nsString>& aDst) {
715 ScopedAutoSECItem item;
716 if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, &item) !=
717 SECSuccess) {
718 return false;
721 CryptoBuffer buffer;
722 if (!buffer.Assign(&item)) {
723 return false;
726 if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) {
727 return false;
730 return true;
733 bool ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey,
734 const SECItem* aEcParams, const SECItem* aPublicValue,
735 JsonWebKey& aRetVal) {
736 aRetVal.mX.Construct();
737 aRetVal.mY.Construct();
739 // Check that the given EC parameters are valid.
740 if (!CheckEncodedECParameters(aEcParams)) {
741 return false;
744 // Construct the OID tag.
745 SECItem oid = {siBuffer, nullptr, 0};
746 oid.len = aEcParams->data[1];
747 oid.data = aEcParams->data + 2;
749 uint32_t flen;
750 switch (SECOID_FindOIDTag(&oid)) {
751 case SEC_OID_SECG_EC_SECP256R1:
752 flen = 32; // bytes
753 aRetVal.mCrv.Construct(
754 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P256));
755 break;
756 case SEC_OID_SECG_EC_SECP384R1:
757 flen = 48; // bytes
758 aRetVal.mCrv.Construct(
759 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P384));
760 break;
761 case SEC_OID_SECG_EC_SECP521R1:
762 flen = 66; // bytes
763 aRetVal.mCrv.Construct(
764 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P521));
765 break;
766 default:
767 return false;
770 // No support for compressed points.
771 if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) {
772 return false;
775 // Check length of uncompressed point coordinates.
776 if (aPublicValue->len != (2 * flen + 1)) {
777 return false;
780 UniqueSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
781 UniqueSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen));
782 if (!ecPointX || !ecPointY) {
783 return false;
786 // Extract point data.
787 memcpy(ecPointX->data, aPublicValue->data + 1, flen);
788 memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen);
790 CryptoBuffer x, y;
791 if (!x.Assign(ecPointX.get()) ||
792 NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
793 !y.Assign(ecPointY.get()) ||
794 NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
795 return false;
798 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_EC);
799 return true;
802 nsresult CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
803 JsonWebKey& aRetVal) {
804 switch (aPrivKey->keyType) {
805 case rsaKey: {
806 aRetVal.mN.Construct();
807 aRetVal.mE.Construct();
808 aRetVal.mD.Construct();
809 aRetVal.mP.Construct();
810 aRetVal.mQ.Construct();
811 aRetVal.mDp.Construct();
812 aRetVal.mDq.Construct();
813 aRetVal.mQi.Construct();
815 if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) ||
816 !ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) ||
817 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) ||
818 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) ||
819 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) ||
820 !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) ||
821 !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
822 !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
823 return NS_ERROR_DOM_OPERATION_ERR;
826 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_RSA);
827 return NS_OK;
829 case ecKey: {
830 // Read EC params.
831 ScopedAutoSECItem params;
832 SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
833 CKA_EC_PARAMS, &params);
834 if (rv != SECSuccess) {
835 return NS_ERROR_DOM_OPERATION_ERR;
838 // Read public point Q.
839 ScopedAutoSECItem ecPoint;
840 rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
841 &ecPoint);
842 if (rv != SECSuccess) {
843 return NS_ERROR_DOM_OPERATION_ERR;
846 if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, &params, &ecPoint, aRetVal)) {
847 return NS_ERROR_DOM_OPERATION_ERR;
850 aRetVal.mD.Construct();
852 // Read private value.
853 if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
854 return NS_ERROR_DOM_OPERATION_ERR;
857 return NS_OK;
859 default:
860 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
864 UniqueSECKEYPublicKey CreateECPublicKey(const SECItem* aKeyData,
865 const nsAString& aNamedCurve) {
866 if (!EnsureNSSInitializedChromeOrContent()) {
867 return nullptr;
870 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
871 if (!arena) {
872 return nullptr;
875 // It's important that this be a UniqueSECKEYPublicKey, as this ensures that
876 // SECKEY_DestroyPublicKey will be called on it. If this doesn't happen, when
877 // CryptoKey::PublicKeyValid is called on it and it gets moved to the internal
878 // PKCS#11 slot, it will leak a reference to the slot.
879 UniqueSECKEYPublicKey key(PORT_ArenaZNew(arena.get(), SECKEYPublicKey));
880 if (!key) {
881 return nullptr;
884 // Transfer arena ownership to the key.
885 key->arena = arena.release();
886 key->keyType = ecKey;
887 key->pkcs11Slot = nullptr;
888 key->pkcs11ID = CK_INVALID_HANDLE;
890 // Create curve parameters.
891 SECItem* params = CreateECParamsForCurve(aNamedCurve, key->arena);
892 if (!params) {
893 return nullptr;
895 key->u.ec.DEREncodedParams = *params;
897 // Set public point.
898 SECStatus ret =
899 SECITEM_CopyItem(key->arena, &key->u.ec.publicValue, aKeyData);
900 if (NS_WARN_IF(ret != SECSuccess)) {
901 return nullptr;
904 // Ensure the given point is on the curve.
905 if (!CryptoKey::PublicKeyValid(key.get())) {
906 return nullptr;
909 return key;
912 UniqueSECKEYPublicKey CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk) {
913 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
914 // Verify that all of the required parameters are present
915 CryptoBuffer n, e;
916 if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
917 !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
918 return nullptr;
921 // Transcode to a DER RSAPublicKey structure
922 struct RSAPublicKeyData {
923 SECItem n;
924 SECItem e;
926 const RSAPublicKeyData input = {
927 {siUnsignedInteger, n.Elements(), (unsigned int)n.Length()},
928 {siUnsignedInteger, e.Elements(), (unsigned int)e.Length()}};
929 const SEC_ASN1Template rsaPublicKeyTemplate[] = {
930 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
932 SEC_ASN1_INTEGER,
933 offsetof(RSAPublicKeyData, n),
936 SEC_ASN1_INTEGER,
937 offsetof(RSAPublicKeyData, e),
943 UniqueSECItem pkDer(
944 SEC_ASN1EncodeItem(nullptr, nullptr, &input, rsaPublicKeyTemplate));
945 if (!pkDer.get()) {
946 return nullptr;
949 return UniqueSECKEYPublicKey(
950 SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA));
953 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
954 // Verify that all of the required parameters are present
955 CryptoBuffer x, y;
956 if (!aJwk.mCrv.WasPassed() || !aJwk.mX.WasPassed() ||
957 NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || !aJwk.mY.WasPassed() ||
958 NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) {
959 return nullptr;
962 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
963 if (!arena) {
964 return nullptr;
967 // Create point.
968 SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
969 if (!point) {
970 return nullptr;
973 nsString namedCurve;
974 if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
975 return nullptr;
978 return CreateECPublicKey(point, namedCurve);
981 return nullptr;
984 nsresult CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
985 JsonWebKey& aRetVal) {
986 switch (aPubKey->keyType) {
987 case rsaKey: {
988 CryptoBuffer n, e;
989 aRetVal.mN.Construct();
990 aRetVal.mE.Construct();
992 if (!n.Assign(&aPubKey->u.rsa.modulus) ||
993 !e.Assign(&aPubKey->u.rsa.publicExponent) ||
994 NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
995 NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
996 return NS_ERROR_DOM_OPERATION_ERR;
999 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_RSA);
1000 return NS_OK;
1002 case ecKey:
1003 if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams,
1004 &aPubKey->u.ec.publicValue, aRetVal)) {
1005 return NS_ERROR_DOM_OPERATION_ERR;
1007 return NS_OK;
1008 default:
1009 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
1013 UniqueSECKEYPublicKey CryptoKey::PublicECKeyFromRaw(
1014 CryptoBuffer& aKeyData, const nsString& aNamedCurve) {
1015 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
1016 if (!arena) {
1017 return nullptr;
1020 SECItem rawItem = {siBuffer, nullptr, 0};
1021 if (!aKeyData.ToSECItem(arena.get(), &rawItem)) {
1022 return nullptr;
1025 uint32_t flen;
1026 if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) {
1027 flen = 32; // bytes
1028 } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) {
1029 flen = 48; // bytes
1030 } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) {
1031 flen = 66; // bytes
1032 } else {
1033 return nullptr;
1036 // Check length of uncompressed point coordinates. There are 2 field elements
1037 // and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED).
1038 if (rawItem.len != (2 * flen + 1)) {
1039 return nullptr;
1042 // No support for compressed points.
1043 if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
1044 return nullptr;
1047 return CreateECPublicKey(&rawItem, aNamedCurve);
1050 nsresult CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
1051 CryptoBuffer& aRetVal) {
1052 if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) {
1053 return NS_ERROR_DOM_OPERATION_ERR;
1055 return NS_OK;
1058 bool CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey) {
1059 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
1060 if (!slot.get()) {
1061 return false;
1064 // This assumes that NSS checks the validity of a public key when
1065 // it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE
1066 // if it is invalid.
1067 CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot.get(), aPubKey, PR_FALSE);
1068 return id != CK_INVALID_HANDLE;
1071 bool CryptoKey::WriteStructuredClone(JSContext* aCX,
1072 JSStructuredCloneWriter* aWriter) const {
1073 // Write in five pieces
1074 // 1. Attributes
1075 // 2. Symmetric key as raw (if present)
1076 // 3. Private key as pkcs8 (if present)
1077 // 4. Public key as spki (if present)
1078 // 5. Algorithm in whatever form it chooses
1079 CryptoBuffer priv, pub;
1081 if (mPrivateKey) {
1082 if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), priv))) {
1083 return false;
1087 if (mPublicKey) {
1088 if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey.get(), pub))) {
1089 return false;
1093 return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) &&
1094 WriteBuffer(aWriter, mSymKey) && WriteBuffer(aWriter, priv) &&
1095 WriteBuffer(aWriter, pub) && mAlgorithm.WriteStructuredClone(aWriter);
1098 // static
1099 already_AddRefed<CryptoKey> CryptoKey::ReadStructuredClone(
1100 JSContext* aCx, nsIGlobalObject* aGlobal,
1101 JSStructuredCloneReader* aReader) {
1102 // Ensure that NSS is initialized.
1103 if (!EnsureNSSInitializedChromeOrContent()) {
1104 return nullptr;
1107 RefPtr<CryptoKey> key = new CryptoKey(aGlobal);
1109 uint32_t version;
1110 CryptoBuffer sym, priv, pub;
1112 bool read = JS_ReadUint32Pair(aReader, &key->mAttributes, &version) &&
1113 (version == CRYPTOKEY_SC_VERSION) && ReadBuffer(aReader, sym) &&
1114 ReadBuffer(aReader, priv) && ReadBuffer(aReader, pub) &&
1115 key->mAlgorithm.ReadStructuredClone(aReader);
1116 if (!read) {
1117 return nullptr;
1120 if (sym.Length() > 0 && !key->mSymKey.Assign(sym)) {
1121 return nullptr;
1123 if (priv.Length() > 0) {
1124 key->mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv);
1126 if (pub.Length() > 0) {
1127 key->mPublicKey = CryptoKey::PublicKeyFromSpki(pub);
1130 // Ensure that what we've read is consistent
1131 // If the attributes indicate a key type, should have a key of that type
1132 if (!((key->GetKeyType() == SECRET && key->mSymKey.Length() > 0) ||
1133 (key->GetKeyType() == PRIVATE && key->mPrivateKey) ||
1134 (key->GetKeyType() == PUBLIC && key->mPublicKey))) {
1135 return nullptr;
1138 return key.forget();
1141 } // namespace mozilla::dom