Bug 1660051 [wpt PR 25111] - Origin isolation: expand getter test coverage, a=testonly
[gecko.git] / dom / crypto / CryptoKey.cpp
blob1c114dca550a9511fc775a46bc00f3ef5e7a627b
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 "CryptoKey.h"
9 #include "cryptohi.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/dom/RootedDictionary.h"
12 #include "mozilla/dom/SubtleCryptoBinding.h"
13 #include "mozilla/dom/ToJSValue.h"
14 #include "nsNSSComponent.h"
15 #include "pk11pub.h"
17 namespace mozilla {
18 namespace dom {
20 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal)
21 NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey)
22 NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey)
23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey)
24 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
25 NS_INTERFACE_MAP_ENTRY(nsISupports)
26 NS_INTERFACE_MAP_END
28 nsresult StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut) {
29 if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_ENCRYPT)) {
30 aUsageOut = CryptoKey::ENCRYPT;
31 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DECRYPT)) {
32 aUsageOut = CryptoKey::DECRYPT;
33 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_SIGN)) {
34 aUsageOut = CryptoKey::SIGN;
35 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_VERIFY)) {
36 aUsageOut = CryptoKey::VERIFY;
37 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEKEY)) {
38 aUsageOut = CryptoKey::DERIVEKEY;
39 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEBITS)) {
40 aUsageOut = CryptoKey::DERIVEBITS;
41 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_WRAPKEY)) {
42 aUsageOut = CryptoKey::WRAPKEY;
43 } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)) {
44 aUsageOut = CryptoKey::UNWRAPKEY;
45 } else {
46 return NS_ERROR_DOM_SYNTAX_ERR;
48 return NS_OK;
51 // This helper function will release the memory backing a SECKEYPrivateKey and
52 // any resources acquired in its creation. It will leave the backing PKCS#11
53 // object untouched, however. This should only be called from
54 // PrivateKeyFromPrivateKeyTemplate.
55 static void DestroyPrivateKeyWithoutDestroyingPKCS11Object(
56 SECKEYPrivateKey* key) {
57 PK11_FreeSlot(key->pkcs11Slot);
58 PORT_FreeArena(key->arena, PR_TRUE);
61 // To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
62 // generates a random ID for each key. The given template must contain an
63 // attribute slot for a key ID, but it must consist of a null pointer and have a
64 // length of 0.
65 UniqueSECKEYPrivateKey PrivateKeyFromPrivateKeyTemplate(
66 CK_ATTRIBUTE* aTemplate, CK_ULONG aTemplateSize) {
67 // Create a generic object with the contents of the key
68 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
69 if (!slot) {
70 return nullptr;
73 // Generate a random 160-bit object ID. This ID must be unique.
74 UniqueSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20));
75 SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
76 if (rv != SECSuccess) {
77 return nullptr;
79 // Check if something is already using this ID.
80 SECKEYPrivateKey* preexistingKey =
81 PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
82 if (preexistingKey) {
83 // Note that we can't just call SECKEY_DestroyPrivateKey here because that
84 // will destroy the PKCS#11 object that is backing a preexisting key (that
85 // we still have a handle on somewhere else in memory). If that object were
86 // destroyed, cryptographic operations performed by that other key would
87 // fail.
88 DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
89 // Try again with a new ID (but only once - collisions are very unlikely).
90 rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
91 if (rv != SECSuccess) {
92 return nullptr;
94 preexistingKey = PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
95 if (preexistingKey) {
96 DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
97 return nullptr;
101 CK_ATTRIBUTE* idAttributeSlot = nullptr;
102 for (CK_ULONG i = 0; i < aTemplateSize; i++) {
103 if (aTemplate[i].type == CKA_ID) {
104 if (aTemplate[i].pValue != nullptr || aTemplate[i].ulValueLen != 0) {
105 return nullptr;
107 idAttributeSlot = aTemplate + i;
108 break;
111 if (!idAttributeSlot) {
112 return nullptr;
115 idAttributeSlot->pValue = objID->data;
116 idAttributeSlot->ulValueLen = objID->len;
117 UniquePK11GenericObject obj(
118 PK11_CreateGenericObject(slot.get(), aTemplate, aTemplateSize, PR_FALSE));
119 // Unset the ID attribute slot's pointer and length so that data that only
120 // lives for the scope of this function doesn't escape.
121 idAttributeSlot->pValue = nullptr;
122 idAttributeSlot->ulValueLen = 0;
123 if (!obj) {
124 return nullptr;
127 // Have NSS translate the object to a private key.
128 return UniqueSECKEYPrivateKey(
129 PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr));
132 CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
133 : mGlobal(aGlobal),
134 mAttributes(0),
135 mSymKey(),
136 mPrivateKey(nullptr),
137 mPublicKey(nullptr) {}
139 JSObject* CryptoKey::WrapObject(JSContext* aCx,
140 JS::Handle<JSObject*> aGivenProto) {
141 return CryptoKey_Binding::Wrap(aCx, this, aGivenProto);
144 void CryptoKey::GetType(nsString& aRetVal) const {
145 uint32_t type = mAttributes & TYPE_MASK;
146 switch (type) {
147 case PUBLIC:
148 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC);
149 break;
150 case PRIVATE:
151 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE);
152 break;
153 case SECRET:
154 aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_SECRET);
155 break;
159 bool CryptoKey::Extractable() const { return (mAttributes & EXTRACTABLE); }
161 void CryptoKey::GetAlgorithm(JSContext* cx,
162 JS::MutableHandle<JSObject*> aRetVal,
163 ErrorResult& aRv) const {
164 bool converted = false;
165 JS::RootedValue val(cx);
166 switch (mAlgorithm.mType) {
167 case KeyAlgorithmProxy::AES:
168 converted = ToJSValue(cx, mAlgorithm.mAes, &val);
169 break;
170 case KeyAlgorithmProxy::HMAC:
171 converted = ToJSValue(cx, mAlgorithm.mHmac, &val);
172 break;
173 case KeyAlgorithmProxy::RSA: {
174 RootedDictionary<RsaHashedKeyAlgorithm> rsa(cx);
175 converted = mAlgorithm.mRsa.ToKeyAlgorithm(cx, rsa);
176 if (converted) {
177 converted = ToJSValue(cx, rsa, &val);
179 break;
181 case KeyAlgorithmProxy::EC:
182 converted = ToJSValue(cx, mAlgorithm.mEc, &val);
183 break;
185 if (!converted) {
186 aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
187 return;
190 aRetVal.set(&val.toObject());
193 void CryptoKey::GetUsages(nsTArray<nsString>& aRetVal) const {
194 if (mAttributes & ENCRYPT) {
195 aRetVal.AppendElement(
196 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_ENCRYPT));
198 if (mAttributes & DECRYPT) {
199 aRetVal.AppendElement(
200 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DECRYPT));
202 if (mAttributes & SIGN) {
203 aRetVal.AppendElement(
204 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_SIGN));
206 if (mAttributes & VERIFY) {
207 aRetVal.AppendElement(
208 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_VERIFY));
210 if (mAttributes & DERIVEKEY) {
211 aRetVal.AppendElement(
212 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY));
214 if (mAttributes & DERIVEBITS) {
215 aRetVal.AppendElement(
216 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS));
218 if (mAttributes & WRAPKEY) {
219 aRetVal.AppendElement(
220 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_WRAPKEY));
222 if (mAttributes & UNWRAPKEY) {
223 aRetVal.AppendElement(
224 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_KEY_USAGE_UNWRAPKEY));
228 KeyAlgorithmProxy& CryptoKey::Algorithm() { return mAlgorithm; }
230 const KeyAlgorithmProxy& CryptoKey::Algorithm() const { return mAlgorithm; }
232 CryptoKey::KeyType CryptoKey::GetKeyType() const {
233 return static_cast<CryptoKey::KeyType>(mAttributes & TYPE_MASK);
236 nsresult CryptoKey::SetType(const nsString& aType) {
237 mAttributes &= CLEAR_TYPE;
238 if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_SECRET)) {
239 mAttributes |= SECRET;
240 } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC)) {
241 mAttributes |= PUBLIC;
242 } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE)) {
243 mAttributes |= PRIVATE;
244 } else {
245 mAttributes |= UNKNOWN;
246 return NS_ERROR_DOM_SYNTAX_ERR;
249 return NS_OK;
252 void CryptoKey::SetType(CryptoKey::KeyType aType) {
253 mAttributes &= CLEAR_TYPE;
254 mAttributes |= aType;
257 void CryptoKey::SetExtractable(bool aExtractable) {
258 mAttributes &= CLEAR_EXTRACTABLE;
259 if (aExtractable) {
260 mAttributes |= EXTRACTABLE;
264 // NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the
265 // public value. To properly export the private key to JWK or PKCS #8 we need
266 // the public key data though and so we use this method to augment a private
267 // key with data from the given public key.
268 nsresult CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey) {
269 // This should be a private key.
270 MOZ_ASSERT(GetKeyType() == PRIVATE);
271 // There should be a private NSS key with type 'EC'.
272 MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey);
273 // The given public key should have the same key type.
274 MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType);
276 // Read EC params.
277 ScopedAutoSECItem params;
278 SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(),
279 CKA_EC_PARAMS, &params);
280 if (rv != SECSuccess) {
281 return NS_ERROR_DOM_OPERATION_ERR;
284 // Read private value.
285 ScopedAutoSECItem value;
286 rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), CKA_VALUE,
287 &value);
288 if (rv != SECSuccess) {
289 return NS_ERROR_DOM_OPERATION_ERR;
292 SECItem* point = &aPublicKey->u.ec.publicValue;
293 CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
294 CK_BBOOL falseValue = CK_FALSE;
295 CK_KEY_TYPE ecValue = CKK_EC;
297 CK_ATTRIBUTE keyTemplate[9] = {
298 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
299 {CKA_KEY_TYPE, &ecValue, sizeof(ecValue)},
300 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
301 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
302 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
303 // PrivateKeyFromPrivateKeyTemplate sets the ID.
304 {CKA_ID, nullptr, 0},
305 {CKA_EC_PARAMS, params.data, params.len},
306 {CKA_EC_POINT, point->data, point->len},
307 {CKA_VALUE, value.data, value.len},
310 mPrivateKey =
311 PrivateKeyFromPrivateKeyTemplate(keyTemplate, ArrayLength(keyTemplate));
312 NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
314 return NS_OK;
317 void CryptoKey::ClearUsages() { mAttributes &= CLEAR_USAGES; }
319 nsresult CryptoKey::AddUsage(const nsString& aUsage) {
320 KeyUsage usage;
321 if (NS_FAILED(StringToUsage(aUsage, usage))) {
322 return NS_ERROR_DOM_SYNTAX_ERR;
325 MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
327 // This is harmless if usage is 0, so we don't repeat the assertion check
328 AddUsage(usage);
329 return NS_OK;
332 nsresult CryptoKey::AddAllowedUsage(const nsString& aUsage,
333 const nsString& aAlgorithm) {
334 return AddAllowedUsageIntersecting(aUsage, aAlgorithm, USAGES_MASK);
337 nsresult CryptoKey::AddAllowedUsageIntersecting(const nsString& aUsage,
338 const nsString& aAlgorithm,
339 uint32_t aUsageMask) {
340 uint32_t allowedUsages = GetAllowedUsagesForAlgorithm(aAlgorithm);
341 KeyUsage usage;
342 if (NS_FAILED(StringToUsage(aUsage, usage))) {
343 return NS_ERROR_DOM_SYNTAX_ERR;
346 if ((usage & allowedUsages) != usage) {
347 return NS_ERROR_DOM_SYNTAX_ERR;
350 MOZ_ASSERT(usage & USAGES_MASK, "Usages should be valid");
352 // This is harmless if usage is 0, so we don't repeat the assertion check
353 if (usage & aUsageMask) {
354 AddUsage(usage);
355 return NS_OK;
358 return NS_OK;
361 void CryptoKey::AddUsage(CryptoKey::KeyUsage aUsage) { mAttributes |= aUsage; }
363 bool CryptoKey::HasAnyUsage() { return !!(mAttributes & USAGES_MASK); }
365 bool CryptoKey::HasUsage(CryptoKey::KeyUsage aUsage) {
366 return !!(mAttributes & aUsage);
369 bool CryptoKey::HasUsageOtherThan(uint32_t aUsages) {
370 return !!(mAttributes & USAGES_MASK & ~aUsages);
373 bool CryptoKey::IsRecognizedUsage(const nsString& aUsage) {
374 KeyUsage dummy;
375 nsresult rv = StringToUsage(aUsage, dummy);
376 return NS_SUCCEEDED(rv);
379 bool CryptoKey::AllUsagesRecognized(const Sequence<nsString>& aUsages) {
380 for (uint32_t i = 0; i < aUsages.Length(); ++i) {
381 if (!IsRecognizedUsage(aUsages[i])) {
382 return false;
385 return true;
388 uint32_t CryptoKey::GetAllowedUsagesForAlgorithm(const nsString& aAlgorithm) {
389 uint32_t allowedUsages = 0;
390 if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
391 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
392 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
393 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
394 allowedUsages = ENCRYPT | DECRYPT | WRAPKEY | UNWRAPKEY;
395 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_AES_KW)) {
396 allowedUsages = WRAPKEY | UNWRAPKEY;
397 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HMAC) ||
398 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
399 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
400 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDSA)) {
401 allowedUsages = SIGN | VERIFY;
402 } else if (aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
403 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
404 aAlgorithm.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
405 allowedUsages = DERIVEBITS | DERIVEKEY;
407 return allowedUsages;
410 nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey) {
411 if (!mSymKey.Assign(aSymKey)) {
412 return NS_ERROR_OUT_OF_MEMORY;
415 return NS_OK;
418 nsresult CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey) {
419 if (!aPrivateKey) {
420 mPrivateKey = nullptr;
421 return NS_OK;
424 mPrivateKey = UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(aPrivateKey));
425 return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
428 nsresult CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey) {
429 if (!aPublicKey) {
430 mPublicKey = nullptr;
431 return NS_OK;
434 mPublicKey = UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(aPublicKey));
435 return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
438 const CryptoBuffer& CryptoKey::GetSymKey() const { return mSymKey; }
440 UniqueSECKEYPrivateKey CryptoKey::GetPrivateKey() const {
441 if (!mPrivateKey) {
442 return nullptr;
444 return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get()));
447 UniqueSECKEYPublicKey CryptoKey::GetPublicKey() const {
448 if (!mPublicKey) {
449 return nullptr;
451 return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get()));
454 // Serialization and deserialization convenience methods
456 UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData) {
457 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
458 if (!slot) {
459 return nullptr;
462 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
463 if (!arena) {
464 return nullptr;
467 SECItem pkcs8Item = {siBuffer, nullptr, 0};
468 if (!aKeyData.ToSECItem(arena.get(), &pkcs8Item)) {
469 return nullptr;
472 // Allow everything, we enforce usage ourselves
473 unsigned int usage = KU_ALL;
475 SECKEYPrivateKey* privKey;
476 SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
477 slot.get(), &pkcs8Item, nullptr, nullptr, false, false, usage, &privKey,
478 nullptr);
480 if (rv == SECFailure) {
481 return nullptr;
484 return UniqueSECKEYPrivateKey(privKey);
487 UniqueSECKEYPublicKey CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData) {
488 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
489 if (!arena) {
490 return nullptr;
493 SECItem spkiItem = {siBuffer, nullptr, 0};
494 if (!aKeyData.ToSECItem(arena.get(), &spkiItem)) {
495 return nullptr;
498 UniqueCERTSubjectPublicKeyInfo spki(
499 SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
500 if (!spki) {
501 return nullptr;
504 bool isECDHAlgorithm =
505 SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm);
507 // Check for |id-ecDH|. Per old versions of the WebCrypto spec we must
508 // support this OID but NSS does unfortunately not know it. Let's
509 // change the algorithm to |id-ecPublicKey| to make NSS happy.
510 if (isECDHAlgorithm) {
511 SECOidTag oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
513 SECOidData* oidData = SECOID_FindOIDByTag(oid);
514 if (!oidData) {
515 return nullptr;
518 SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
519 &oidData->oid);
520 if (rv != SECSuccess) {
521 return nullptr;
525 UniqueSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
526 if (!tmp.get() || !PublicKeyValid(tmp.get())) {
527 return nullptr;
530 return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get()));
533 nsresult CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
534 CryptoBuffer& aRetVal) {
535 UniqueSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr));
536 if (!pkcs8Item.get()) {
537 return NS_ERROR_DOM_INVALID_ACCESS_ERR;
539 if (!aRetVal.Assign(pkcs8Item.get())) {
540 return NS_ERROR_DOM_OPERATION_ERR;
542 return NS_OK;
545 nsresult CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
546 CryptoBuffer& aRetVal) {
547 UniqueCERTSubjectPublicKeyInfo spki;
549 spki.reset(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
550 if (!spki) {
551 return NS_ERROR_DOM_OPERATION_ERR;
554 const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
555 UniqueSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki.get(), tpl));
557 if (!aRetVal.Assign(spkiItem.get())) {
558 return NS_ERROR_DOM_OPERATION_ERR;
560 return NS_OK;
563 SECItem* CreateECPointForCoordinates(const CryptoBuffer& aX,
564 const CryptoBuffer& aY,
565 PLArenaPool* aArena) {
566 // Check that both points have the same length.
567 if (aX.Length() != aY.Length()) {
568 return nullptr;
571 // Create point.
572 SECItem* point =
573 ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
574 if (!point) {
575 return nullptr;
578 // Set point data.
579 point->data[0] = EC_POINT_FORM_UNCOMPRESSED;
580 memcpy(point->data + 1, aX.Elements(), aX.Length());
581 memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length());
583 return point;
586 UniqueSECKEYPrivateKey CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk) {
587 CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
588 CK_BBOOL falseValue = CK_FALSE;
590 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
591 // Verify that all of the required parameters are present
592 CryptoBuffer x, y, d;
593 if (!aJwk.mCrv.WasPassed() || !aJwk.mX.WasPassed() ||
594 NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || !aJwk.mY.WasPassed() ||
595 NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) || !aJwk.mD.WasPassed() ||
596 NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) {
597 return nullptr;
600 nsString namedCurve;
601 if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
602 return nullptr;
605 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
606 if (!arena) {
607 return nullptr;
610 // Create parameters.
611 SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
612 if (!params) {
613 return nullptr;
616 SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get());
617 if (!ecPoint) {
618 return nullptr;
621 // Populate template from parameters
622 CK_KEY_TYPE ecValue = CKK_EC;
623 CK_ATTRIBUTE keyTemplate[9] = {
624 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
625 {CKA_KEY_TYPE, &ecValue, sizeof(ecValue)},
626 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
627 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
628 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
629 // PrivateKeyFromPrivateKeyTemplate sets the ID.
630 {CKA_ID, nullptr, 0},
631 {CKA_EC_PARAMS, params->data, params->len},
632 {CKA_EC_POINT, ecPoint->data, ecPoint->len},
633 {CKA_VALUE, (void*)d.Elements(), (CK_ULONG)d.Length()},
636 return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
637 ArrayLength(keyTemplate));
640 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
641 // Verify that all of the required parameters are present
642 CryptoBuffer n, e, d, p, q, dp, dq, qi;
643 if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
644 !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
645 !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
646 !aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
647 !aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
648 !aJwk.mDp.WasPassed() ||
649 NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
650 !aJwk.mDq.WasPassed() ||
651 NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
652 !aJwk.mQi.WasPassed() ||
653 NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
654 return nullptr;
657 // Populate template from parameters
658 CK_KEY_TYPE rsaValue = CKK_RSA;
659 CK_ATTRIBUTE keyTemplate[14] = {
660 {CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue)},
661 {CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue)},
662 {CKA_TOKEN, &falseValue, sizeof(falseValue)},
663 {CKA_SENSITIVE, &falseValue, sizeof(falseValue)},
664 {CKA_PRIVATE, &falseValue, sizeof(falseValue)},
665 // PrivateKeyFromPrivateKeyTemplate sets the ID.
666 {CKA_ID, nullptr, 0},
667 {CKA_MODULUS, (void*)n.Elements(), (CK_ULONG)n.Length()},
668 {CKA_PUBLIC_EXPONENT, (void*)e.Elements(), (CK_ULONG)e.Length()},
669 {CKA_PRIVATE_EXPONENT, (void*)d.Elements(), (CK_ULONG)d.Length()},
670 {CKA_PRIME_1, (void*)p.Elements(), (CK_ULONG)p.Length()},
671 {CKA_PRIME_2, (void*)q.Elements(), (CK_ULONG)q.Length()},
672 {CKA_EXPONENT_1, (void*)dp.Elements(), (CK_ULONG)dp.Length()},
673 {CKA_EXPONENT_2, (void*)dq.Elements(), (CK_ULONG)dq.Length()},
674 {CKA_COEFFICIENT, (void*)qi.Elements(), (CK_ULONG)qi.Length()},
677 return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
678 ArrayLength(keyTemplate));
681 return nullptr;
684 bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
685 CK_ATTRIBUTE_TYPE aAttribute,
686 Optional<nsString>& aDst) {
687 ScopedAutoSECItem item;
688 if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, &item) !=
689 SECSuccess) {
690 return false;
693 CryptoBuffer buffer;
694 if (!buffer.Assign(&item)) {
695 return false;
698 if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) {
699 return false;
702 return true;
705 bool ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey,
706 const SECItem* aEcParams, const SECItem* aPublicValue,
707 JsonWebKey& aRetVal) {
708 aRetVal.mX.Construct();
709 aRetVal.mY.Construct();
711 // Check that the given EC parameters are valid.
712 if (!CheckEncodedECParameters(aEcParams)) {
713 return false;
716 // Construct the OID tag.
717 SECItem oid = {siBuffer, nullptr, 0};
718 oid.len = aEcParams->data[1];
719 oid.data = aEcParams->data + 2;
721 uint32_t flen;
722 switch (SECOID_FindOIDTag(&oid)) {
723 case SEC_OID_SECG_EC_SECP256R1:
724 flen = 32; // bytes
725 aRetVal.mCrv.Construct(
726 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P256));
727 break;
728 case SEC_OID_SECG_EC_SECP384R1:
729 flen = 48; // bytes
730 aRetVal.mCrv.Construct(
731 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P384));
732 break;
733 case SEC_OID_SECG_EC_SECP521R1:
734 flen = 66; // bytes
735 aRetVal.mCrv.Construct(
736 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P521));
737 break;
738 default:
739 return false;
742 // No support for compressed points.
743 if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) {
744 return false;
747 // Check length of uncompressed point coordinates.
748 if (aPublicValue->len != (2 * flen + 1)) {
749 return false;
752 UniqueSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
753 UniqueSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen));
754 if (!ecPointX || !ecPointY) {
755 return false;
758 // Extract point data.
759 memcpy(ecPointX->data, aPublicValue->data + 1, flen);
760 memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen);
762 CryptoBuffer x, y;
763 if (!x.Assign(ecPointX.get()) ||
764 NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
765 !y.Assign(ecPointY.get()) ||
766 NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
767 return false;
770 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_EC);
771 return true;
774 nsresult CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
775 JsonWebKey& aRetVal) {
776 switch (aPrivKey->keyType) {
777 case rsaKey: {
778 aRetVal.mN.Construct();
779 aRetVal.mE.Construct();
780 aRetVal.mD.Construct();
781 aRetVal.mP.Construct();
782 aRetVal.mQ.Construct();
783 aRetVal.mDp.Construct();
784 aRetVal.mDq.Construct();
785 aRetVal.mQi.Construct();
787 if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) ||
788 !ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) ||
789 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) ||
790 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) ||
791 !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) ||
792 !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) ||
793 !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
794 !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
795 return NS_ERROR_DOM_OPERATION_ERR;
798 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_RSA);
799 return NS_OK;
801 case ecKey: {
802 // Read EC params.
803 ScopedAutoSECItem params;
804 SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
805 CKA_EC_PARAMS, &params);
806 if (rv != SECSuccess) {
807 return NS_ERROR_DOM_OPERATION_ERR;
810 // Read public point Q.
811 ScopedAutoSECItem ecPoint;
812 rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
813 &ecPoint);
814 if (rv != SECSuccess) {
815 return NS_ERROR_DOM_OPERATION_ERR;
818 if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, &params, &ecPoint, aRetVal)) {
819 return NS_ERROR_DOM_OPERATION_ERR;
822 aRetVal.mD.Construct();
824 // Read private value.
825 if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
826 return NS_ERROR_DOM_OPERATION_ERR;
829 return NS_OK;
831 default:
832 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
836 UniqueSECKEYPublicKey CreateECPublicKey(const SECItem* aKeyData,
837 const nsString& aNamedCurve) {
838 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
839 if (!arena) {
840 return nullptr;
843 // It's important that this be a UniqueSECKEYPublicKey, as this ensures that
844 // SECKEY_DestroyPublicKey will be called on it. If this doesn't happen, when
845 // CryptoKey::PublicKeyValid is called on it and it gets moved to the internal
846 // PKCS#11 slot, it will leak a reference to the slot.
847 UniqueSECKEYPublicKey key(PORT_ArenaZNew(arena.get(), SECKEYPublicKey));
848 if (!key) {
849 return nullptr;
852 key->arena = nullptr; // key doesn't own the arena; it won't get double-freed
853 key->keyType = ecKey;
854 key->pkcs11Slot = nullptr;
855 key->pkcs11ID = CK_INVALID_HANDLE;
857 // Create curve parameters.
858 SECItem* params = CreateECParamsForCurve(aNamedCurve, arena.get());
859 if (!params) {
860 return nullptr;
862 key->u.ec.DEREncodedParams = *params;
864 // Set public point.
865 key->u.ec.publicValue = *aKeyData;
867 // Ensure the given point is on the curve.
868 if (!CryptoKey::PublicKeyValid(key.get())) {
869 return nullptr;
872 return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key.get()));
875 UniqueSECKEYPublicKey CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk) {
876 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
877 // Verify that all of the required parameters are present
878 CryptoBuffer n, e;
879 if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
880 !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
881 return nullptr;
884 // Transcode to a DER RSAPublicKey structure
885 struct RSAPublicKeyData {
886 SECItem n;
887 SECItem e;
889 const RSAPublicKeyData input = {
890 {siUnsignedInteger, n.Elements(), (unsigned int)n.Length()},
891 {siUnsignedInteger, e.Elements(), (unsigned int)e.Length()}};
892 const SEC_ASN1Template rsaPublicKeyTemplate[] = {
893 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
895 SEC_ASN1_INTEGER,
896 offsetof(RSAPublicKeyData, n),
899 SEC_ASN1_INTEGER,
900 offsetof(RSAPublicKeyData, e),
906 UniqueSECItem pkDer(
907 SEC_ASN1EncodeItem(nullptr, nullptr, &input, rsaPublicKeyTemplate));
908 if (!pkDer.get()) {
909 return nullptr;
912 return UniqueSECKEYPublicKey(
913 SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA));
916 if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
917 // Verify that all of the required parameters are present
918 CryptoBuffer x, y;
919 if (!aJwk.mCrv.WasPassed() || !aJwk.mX.WasPassed() ||
920 NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || !aJwk.mY.WasPassed() ||
921 NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) {
922 return nullptr;
925 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
926 if (!arena) {
927 return nullptr;
930 // Create point.
931 SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
932 if (!point) {
933 return nullptr;
936 nsString namedCurve;
937 if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
938 return nullptr;
941 return CreateECPublicKey(point, namedCurve);
944 return nullptr;
947 nsresult CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
948 JsonWebKey& aRetVal) {
949 switch (aPubKey->keyType) {
950 case rsaKey: {
951 CryptoBuffer n, e;
952 aRetVal.mN.Construct();
953 aRetVal.mE.Construct();
955 if (!n.Assign(&aPubKey->u.rsa.modulus) ||
956 !e.Assign(&aPubKey->u.rsa.publicExponent) ||
957 NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
958 NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
959 return NS_ERROR_DOM_OPERATION_ERR;
962 aRetVal.mKty = NS_LITERAL_STRING_FROM_CSTRING(JWK_TYPE_RSA);
963 return NS_OK;
965 case ecKey:
966 if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams,
967 &aPubKey->u.ec.publicValue, aRetVal)) {
968 return NS_ERROR_DOM_OPERATION_ERR;
970 return NS_OK;
971 default:
972 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
976 UniqueSECKEYPublicKey CryptoKey::PublicECKeyFromRaw(
977 CryptoBuffer& aKeyData, const nsString& aNamedCurve) {
978 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
979 if (!arena) {
980 return nullptr;
983 SECItem rawItem = {siBuffer, nullptr, 0};
984 if (!aKeyData.ToSECItem(arena.get(), &rawItem)) {
985 return nullptr;
988 uint32_t flen;
989 if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) {
990 flen = 32; // bytes
991 } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) {
992 flen = 48; // bytes
993 } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) {
994 flen = 66; // bytes
995 } else {
996 return nullptr;
999 // Check length of uncompressed point coordinates. There are 2 field elements
1000 // and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED).
1001 if (rawItem.len != (2 * flen + 1)) {
1002 return nullptr;
1005 // No support for compressed points.
1006 if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
1007 return nullptr;
1010 return CreateECPublicKey(&rawItem, aNamedCurve);
1013 nsresult CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
1014 CryptoBuffer& aRetVal) {
1015 if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) {
1016 return NS_ERROR_DOM_OPERATION_ERR;
1018 return NS_OK;
1021 bool CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey) {
1022 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
1023 if (!slot.get()) {
1024 return false;
1027 // This assumes that NSS checks the validity of a public key when
1028 // it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE
1029 // if it is invalid.
1030 CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot.get(), aPubKey, PR_FALSE);
1031 if (id == CK_INVALID_HANDLE) {
1032 return false;
1035 SECStatus rv = PK11_DestroyObject(slot.get(), id);
1036 return (rv == SECSuccess);
1039 bool CryptoKey::WriteStructuredClone(JSContext* aCX,
1040 JSStructuredCloneWriter* aWriter) const {
1041 // Write in five pieces
1042 // 1. Attributes
1043 // 2. Symmetric key as raw (if present)
1044 // 3. Private key as pkcs8 (if present)
1045 // 4. Public key as spki (if present)
1046 // 5. Algorithm in whatever form it chooses
1047 CryptoBuffer priv, pub;
1049 if (mPrivateKey) {
1050 if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), priv))) {
1051 return false;
1055 if (mPublicKey) {
1056 if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey.get(), pub))) {
1057 return false;
1061 return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) &&
1062 WriteBuffer(aWriter, mSymKey) && WriteBuffer(aWriter, priv) &&
1063 WriteBuffer(aWriter, pub) && mAlgorithm.WriteStructuredClone(aWriter);
1066 // static
1067 already_AddRefed<CryptoKey> CryptoKey::ReadStructuredClone(
1068 JSContext* aCx, nsIGlobalObject* aGlobal,
1069 JSStructuredCloneReader* aReader) {
1070 // Ensure that NSS is initialized.
1071 if (!EnsureNSSInitializedChromeOrContent()) {
1072 return nullptr;
1075 RefPtr<CryptoKey> key = new CryptoKey(aGlobal);
1077 uint32_t version;
1078 CryptoBuffer sym, priv, pub;
1080 bool read = JS_ReadUint32Pair(aReader, &key->mAttributes, &version) &&
1081 (version == CRYPTOKEY_SC_VERSION) && ReadBuffer(aReader, sym) &&
1082 ReadBuffer(aReader, priv) && ReadBuffer(aReader, pub) &&
1083 key->mAlgorithm.ReadStructuredClone(aReader);
1084 if (!read) {
1085 return nullptr;
1088 if (sym.Length() > 0 && !key->mSymKey.Assign(sym)) {
1089 return nullptr;
1091 if (priv.Length() > 0) {
1092 key->mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv);
1094 if (pub.Length() > 0) {
1095 key->mPublicKey = CryptoKey::PublicKeyFromSpki(pub);
1098 // Ensure that what we've read is consistent
1099 // If the attributes indicate a key type, should have a key of that type
1100 if (!((key->GetKeyType() == SECRET && key->mSymKey.Length() > 0) ||
1101 (key->GetKeyType() == PRIVATE && key->mPrivateKey) ||
1102 (key->GetKeyType() == PUBLIC && key->mPublicKey))) {
1103 return nullptr;
1106 return key.forget();
1109 } // namespace dom
1110 } // namespace mozilla