/src/mozilla-central/dom/crypto/CryptoKey.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "CryptoKey.h" |
8 | | |
9 | | #include "cryptohi.h" |
10 | | #include "mozilla/ArrayUtils.h" |
11 | | #include "mozilla/dom/SubtleCryptoBinding.h" |
12 | | #include "mozilla/dom/ToJSValue.h" |
13 | | #include "nsNSSComponent.h" |
14 | | #include "pk11pub.h" |
15 | | |
16 | | // Templates taken from security/nss/lib/cryptohi/seckey.c |
17 | | // These would ideally be exported by NSS and until that |
18 | | // happens we have to keep our own copies. |
19 | | const SEC_ASN1Template SECKEY_DHPublicKeyTemplate[] = { |
20 | | { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.publicValue), }, |
21 | | { 0, } |
22 | | }; |
23 | | const SEC_ASN1Template SECKEY_DHParamKeyTemplate[] = { |
24 | | { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPublicKey) }, |
25 | | { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.prime), }, |
26 | | { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.base), }, |
27 | | { SEC_ASN1_SKIP_REST }, |
28 | | { 0, } |
29 | | }; |
30 | | |
31 | | namespace mozilla { |
32 | | namespace dom { |
33 | | |
34 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal) |
35 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey) |
36 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey) |
37 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey) |
38 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
39 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
40 | 0 | NS_INTERFACE_MAP_END |
41 | | |
42 | | nsresult |
43 | | StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut) |
44 | 0 | { |
45 | 0 | if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_ENCRYPT)) { |
46 | 0 | aUsageOut = CryptoKey::ENCRYPT; |
47 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DECRYPT)) { |
48 | 0 | aUsageOut = CryptoKey::DECRYPT; |
49 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_SIGN)) { |
50 | 0 | aUsageOut = CryptoKey::SIGN; |
51 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_VERIFY)) { |
52 | 0 | aUsageOut = CryptoKey::VERIFY; |
53 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEKEY)) { |
54 | 0 | aUsageOut = CryptoKey::DERIVEKEY; |
55 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEBITS)) { |
56 | 0 | aUsageOut = CryptoKey::DERIVEBITS; |
57 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_WRAPKEY)) { |
58 | 0 | aUsageOut = CryptoKey::WRAPKEY; |
59 | 0 | } else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)) { |
60 | 0 | aUsageOut = CryptoKey::UNWRAPKEY; |
61 | 0 | } else { |
62 | 0 | return NS_ERROR_DOM_SYNTAX_ERR; |
63 | 0 | } |
64 | 0 | return NS_OK; |
65 | 0 | } |
66 | | |
67 | | // This helper function will release the memory backing a SECKEYPrivateKey and |
68 | | // any resources acquired in its creation. It will leave the backing PKCS#11 |
69 | | // object untouched, however. This should only be called from |
70 | | // PrivateKeyFromPrivateKeyTemplate. |
71 | | static void |
72 | | DestroyPrivateKeyWithoutDestroyingPKCS11Object(SECKEYPrivateKey* key) |
73 | 0 | { |
74 | 0 | PK11_FreeSlot(key->pkcs11Slot); |
75 | 0 | PORT_FreeArena(key->arena, PR_TRUE); |
76 | 0 | } |
77 | | |
78 | | // To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate |
79 | | // generates a random ID for each key. The given template must contain an |
80 | | // attribute slot for a key ID, but it must consist of a null pointer and have a |
81 | | // length of 0. |
82 | | UniqueSECKEYPrivateKey |
83 | | PrivateKeyFromPrivateKeyTemplate(CK_ATTRIBUTE* aTemplate, |
84 | | CK_ULONG aTemplateSize) |
85 | 0 | { |
86 | 0 | // Create a generic object with the contents of the key |
87 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalSlot()); |
88 | 0 | if (!slot) { |
89 | 0 | return nullptr; |
90 | 0 | } |
91 | 0 | |
92 | 0 | // Generate a random 160-bit object ID. This ID must be unique. |
93 | 0 | UniqueSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20)); |
94 | 0 | SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len); |
95 | 0 | if (rv != SECSuccess) { |
96 | 0 | return nullptr; |
97 | 0 | } |
98 | 0 | // Check if something is already using this ID. |
99 | 0 | SECKEYPrivateKey* preexistingKey = PK11_FindKeyByKeyID(slot.get(), |
100 | 0 | objID.get(), |
101 | 0 | nullptr); |
102 | 0 | if (preexistingKey) { |
103 | 0 | // Note that we can't just call SECKEY_DestroyPrivateKey here because that |
104 | 0 | // will destroy the PKCS#11 object that is backing a preexisting key (that |
105 | 0 | // we still have a handle on somewhere else in memory). If that object were |
106 | 0 | // destroyed, cryptographic operations performed by that other key would |
107 | 0 | // fail. |
108 | 0 | DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey); |
109 | 0 | // Try again with a new ID (but only once - collisions are very unlikely). |
110 | 0 | rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len); |
111 | 0 | if (rv != SECSuccess) { |
112 | 0 | return nullptr; |
113 | 0 | } |
114 | 0 | preexistingKey = PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr); |
115 | 0 | if (preexistingKey) { |
116 | 0 | DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey); |
117 | 0 | return nullptr; |
118 | 0 | } |
119 | 0 | } |
120 | 0 | |
121 | 0 | CK_ATTRIBUTE* idAttributeSlot = nullptr; |
122 | 0 | for (CK_ULONG i = 0; i < aTemplateSize; i++) { |
123 | 0 | if (aTemplate[i].type == CKA_ID) { |
124 | 0 | if (aTemplate[i].pValue != nullptr || aTemplate[i].ulValueLen != 0) { |
125 | 0 | return nullptr; |
126 | 0 | } |
127 | 0 | idAttributeSlot = aTemplate + i; |
128 | 0 | break; |
129 | 0 | } |
130 | 0 | } |
131 | 0 | if (!idAttributeSlot) { |
132 | 0 | return nullptr; |
133 | 0 | } |
134 | 0 | |
135 | 0 | idAttributeSlot->pValue = objID->data; |
136 | 0 | idAttributeSlot->ulValueLen = objID->len; |
137 | 0 | UniquePK11GenericObject obj(PK11_CreateGenericObject(slot.get(), |
138 | 0 | aTemplate, |
139 | 0 | aTemplateSize, |
140 | 0 | PR_FALSE)); |
141 | 0 | // Unset the ID attribute slot's pointer and length so that data that only |
142 | 0 | // lives for the scope of this function doesn't escape. |
143 | 0 | idAttributeSlot->pValue = nullptr; |
144 | 0 | idAttributeSlot->ulValueLen = 0; |
145 | 0 | if (!obj) { |
146 | 0 | return nullptr; |
147 | 0 | } |
148 | 0 | |
149 | 0 | // Have NSS translate the object to a private key. |
150 | 0 | return UniqueSECKEYPrivateKey( |
151 | 0 | PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr)); |
152 | 0 | } |
153 | | |
154 | | CryptoKey::CryptoKey(nsIGlobalObject* aGlobal) |
155 | | : mGlobal(aGlobal) |
156 | | , mAttributes(0) |
157 | | , mSymKey() |
158 | | , mPrivateKey(nullptr) |
159 | | , mPublicKey(nullptr) |
160 | 0 | { |
161 | 0 | } |
162 | | |
163 | | JSObject* |
164 | | CryptoKey::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
165 | 0 | { |
166 | 0 | return CryptoKey_Binding::Wrap(aCx, this, aGivenProto); |
167 | 0 | } |
168 | | |
169 | | void |
170 | | CryptoKey::GetType(nsString& aRetVal) const |
171 | 0 | { |
172 | 0 | uint32_t type = mAttributes & TYPE_MASK; |
173 | 0 | switch (type) { |
174 | 0 | case PUBLIC: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC); break; |
175 | 0 | case PRIVATE: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE); break; |
176 | 0 | case SECRET: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_SECRET); break; |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | | bool |
181 | | CryptoKey::Extractable() const |
182 | 0 | { |
183 | 0 | return (mAttributes & EXTRACTABLE); |
184 | 0 | } |
185 | | |
186 | | void |
187 | | CryptoKey::GetAlgorithm(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, |
188 | | ErrorResult& aRv) const |
189 | 0 | { |
190 | 0 | bool converted = false; |
191 | 0 | JS::RootedValue val(cx); |
192 | 0 | switch (mAlgorithm.mType) { |
193 | 0 | case KeyAlgorithmProxy::AES: |
194 | 0 | converted = ToJSValue(cx, mAlgorithm.mAes, &val); |
195 | 0 | break; |
196 | 0 | case KeyAlgorithmProxy::HMAC: |
197 | 0 | converted = ToJSValue(cx, mAlgorithm.mHmac, &val); |
198 | 0 | break; |
199 | 0 | case KeyAlgorithmProxy::RSA: { |
200 | 0 | RootedDictionary<RsaHashedKeyAlgorithm> rsa(cx); |
201 | 0 | converted = mAlgorithm.mRsa.ToKeyAlgorithm(cx, rsa); |
202 | 0 | if (converted) { |
203 | 0 | converted = ToJSValue(cx, rsa, &val); |
204 | 0 | } |
205 | 0 | break; |
206 | 0 | } |
207 | 0 | case KeyAlgorithmProxy::EC: |
208 | 0 | converted = ToJSValue(cx, mAlgorithm.mEc, &val); |
209 | 0 | break; |
210 | 0 | case KeyAlgorithmProxy::DH: { |
211 | 0 | RootedDictionary<DhKeyAlgorithm> dh(cx); |
212 | 0 | converted = mAlgorithm.mDh.ToKeyAlgorithm(cx, dh); |
213 | 0 | if (converted) { |
214 | 0 | converted = ToJSValue(cx, dh, &val); |
215 | 0 | } |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | } |
219 | 0 | if (!converted) { |
220 | 0 | aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); |
221 | 0 | return; |
222 | 0 | } |
223 | 0 | |
224 | 0 | aRetVal.set(&val.toObject()); |
225 | 0 | } |
226 | | |
227 | | void |
228 | | CryptoKey::GetUsages(nsTArray<nsString>& aRetVal) const |
229 | 0 | { |
230 | 0 | if (mAttributes & ENCRYPT) { |
231 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_ENCRYPT)); |
232 | 0 | } |
233 | 0 | if (mAttributes & DECRYPT) { |
234 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DECRYPT)); |
235 | 0 | } |
236 | 0 | if (mAttributes & SIGN) { |
237 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_SIGN)); |
238 | 0 | } |
239 | 0 | if (mAttributes & VERIFY) { |
240 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_VERIFY)); |
241 | 0 | } |
242 | 0 | if (mAttributes & DERIVEKEY) { |
243 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY)); |
244 | 0 | } |
245 | 0 | if (mAttributes & DERIVEBITS) { |
246 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS)); |
247 | 0 | } |
248 | 0 | if (mAttributes & WRAPKEY) { |
249 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_WRAPKEY)); |
250 | 0 | } |
251 | 0 | if (mAttributes & UNWRAPKEY) { |
252 | 0 | aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)); |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | | KeyAlgorithmProxy& |
257 | | CryptoKey::Algorithm() |
258 | 0 | { |
259 | 0 | return mAlgorithm; |
260 | 0 | } |
261 | | |
262 | | const KeyAlgorithmProxy& |
263 | | CryptoKey::Algorithm() const |
264 | 0 | { |
265 | 0 | return mAlgorithm; |
266 | 0 | } |
267 | | |
268 | | CryptoKey::KeyType |
269 | | CryptoKey::GetKeyType() const |
270 | 0 | { |
271 | 0 | return static_cast<CryptoKey::KeyType>(mAttributes & TYPE_MASK); |
272 | 0 | } |
273 | | |
274 | | nsresult |
275 | | CryptoKey::SetType(const nsString& aType) |
276 | 0 | { |
277 | 0 | mAttributes &= CLEAR_TYPE; |
278 | 0 | if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_SECRET)) { |
279 | 0 | mAttributes |= SECRET; |
280 | 0 | } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC)) { |
281 | 0 | mAttributes |= PUBLIC; |
282 | 0 | } else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE)) { |
283 | 0 | mAttributes |= PRIVATE; |
284 | 0 | } else { |
285 | 0 | mAttributes |= UNKNOWN; |
286 | 0 | return NS_ERROR_DOM_SYNTAX_ERR; |
287 | 0 | } |
288 | 0 | |
289 | 0 | return NS_OK; |
290 | 0 | } |
291 | | |
292 | | void |
293 | | CryptoKey::SetType(CryptoKey::KeyType aType) |
294 | 0 | { |
295 | 0 | mAttributes &= CLEAR_TYPE; |
296 | 0 | mAttributes |= aType; |
297 | 0 | } |
298 | | |
299 | | void |
300 | | CryptoKey::SetExtractable(bool aExtractable) |
301 | 0 | { |
302 | 0 | mAttributes &= CLEAR_EXTRACTABLE; |
303 | 0 | if (aExtractable) { |
304 | 0 | mAttributes |= EXTRACTABLE; |
305 | 0 | } |
306 | 0 | } |
307 | | |
308 | | // NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the |
309 | | // public value. To properly export the private key to JWK or PKCS #8 we need |
310 | | // the public key data though and so we use this method to augment a private |
311 | | // key with data from the given public key. |
312 | | nsresult |
313 | | CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey) |
314 | 0 | { |
315 | 0 | // This should be a private key. |
316 | 0 | MOZ_ASSERT(GetKeyType() == PRIVATE); |
317 | 0 | // There should be a private NSS key with type 'EC'. |
318 | 0 | MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey); |
319 | 0 | // The given public key should have the same key type. |
320 | 0 | MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType); |
321 | 0 |
|
322 | 0 | // Read EC params. |
323 | 0 | ScopedAutoSECItem params; |
324 | 0 | SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), |
325 | 0 | CKA_EC_PARAMS, ¶ms); |
326 | 0 | if (rv != SECSuccess) { |
327 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
328 | 0 | } |
329 | 0 | |
330 | 0 | // Read private value. |
331 | 0 | ScopedAutoSECItem value; |
332 | 0 | rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), CKA_VALUE, |
333 | 0 | &value); |
334 | 0 | if (rv != SECSuccess) { |
335 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
336 | 0 | } |
337 | 0 | |
338 | 0 | SECItem* point = &aPublicKey->u.ec.publicValue; |
339 | 0 | CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY; |
340 | 0 | CK_BBOOL falseValue = CK_FALSE; |
341 | 0 | CK_KEY_TYPE ecValue = CKK_EC; |
342 | 0 |
|
343 | 0 | CK_ATTRIBUTE keyTemplate[9] = { |
344 | 0 | { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) }, |
345 | 0 | { CKA_KEY_TYPE, &ecValue, sizeof(ecValue) }, |
346 | 0 | { CKA_TOKEN, &falseValue, sizeof(falseValue) }, |
347 | 0 | { CKA_SENSITIVE, &falseValue, sizeof(falseValue) }, |
348 | 0 | { CKA_PRIVATE, &falseValue, sizeof(falseValue) }, |
349 | 0 | // PrivateKeyFromPrivateKeyTemplate sets the ID. |
350 | 0 | { CKA_ID, nullptr, 0 }, |
351 | 0 | { CKA_EC_PARAMS, params.data, params.len }, |
352 | 0 | { CKA_EC_POINT, point->data, point->len }, |
353 | 0 | { CKA_VALUE, value.data, value.len }, |
354 | 0 | }; |
355 | 0 |
|
356 | 0 | mPrivateKey = PrivateKeyFromPrivateKeyTemplate(keyTemplate, |
357 | 0 | ArrayLength(keyTemplate)); |
358 | 0 | NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR); |
359 | 0 |
|
360 | 0 | return NS_OK; |
361 | 0 | } |
362 | | |
363 | | void |
364 | | CryptoKey::ClearUsages() |
365 | 0 | { |
366 | 0 | mAttributes &= CLEAR_USAGES; |
367 | 0 | } |
368 | | |
369 | | nsresult |
370 | | CryptoKey::AddUsage(const nsString& aUsage) |
371 | 0 | { |
372 | 0 | return AddUsageIntersecting(aUsage, USAGES_MASK); |
373 | 0 | } |
374 | | |
375 | | nsresult |
376 | | CryptoKey::AddUsageIntersecting(const nsString& aUsage, uint32_t aUsageMask) |
377 | 0 | { |
378 | 0 | KeyUsage usage; |
379 | 0 | if (NS_FAILED(StringToUsage(aUsage, usage))) { |
380 | 0 | return NS_ERROR_DOM_SYNTAX_ERR; |
381 | 0 | } |
382 | 0 | |
383 | 0 | if (usage & aUsageMask) { |
384 | 0 | AddUsage(usage); |
385 | 0 | return NS_OK; |
386 | 0 | } |
387 | 0 | |
388 | 0 | return NS_OK; |
389 | 0 | } |
390 | | |
391 | | void |
392 | | CryptoKey::AddUsage(CryptoKey::KeyUsage aUsage) |
393 | 0 | { |
394 | 0 | mAttributes |= aUsage; |
395 | 0 | } |
396 | | |
397 | | bool |
398 | | CryptoKey::HasAnyUsage() |
399 | 0 | { |
400 | 0 | return !!(mAttributes & USAGES_MASK); |
401 | 0 | } |
402 | | |
403 | | bool |
404 | | CryptoKey::HasUsage(CryptoKey::KeyUsage aUsage) |
405 | 0 | { |
406 | 0 | return !!(mAttributes & aUsage); |
407 | 0 | } |
408 | | |
409 | | bool |
410 | | CryptoKey::HasUsageOtherThan(uint32_t aUsages) |
411 | 0 | { |
412 | 0 | return !!(mAttributes & USAGES_MASK & ~aUsages); |
413 | 0 | } |
414 | | |
415 | | bool |
416 | | CryptoKey::IsRecognizedUsage(const nsString& aUsage) |
417 | 0 | { |
418 | 0 | KeyUsage dummy; |
419 | 0 | nsresult rv = StringToUsage(aUsage, dummy); |
420 | 0 | return NS_SUCCEEDED(rv); |
421 | 0 | } |
422 | | |
423 | | bool |
424 | | CryptoKey::AllUsagesRecognized(const Sequence<nsString>& aUsages) |
425 | 0 | { |
426 | 0 | for (uint32_t i = 0; i < aUsages.Length(); ++i) { |
427 | 0 | if (!IsRecognizedUsage(aUsages[i])) { |
428 | 0 | return false; |
429 | 0 | } |
430 | 0 | } |
431 | 0 | return true; |
432 | 0 | } |
433 | | |
434 | | nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey) |
435 | 0 | { |
436 | 0 | if (!mSymKey.Assign(aSymKey)) { |
437 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
438 | 0 | } |
439 | 0 | |
440 | 0 | return NS_OK; |
441 | 0 | } |
442 | | |
443 | | nsresult |
444 | | CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey) |
445 | 0 | { |
446 | 0 | if (!aPrivateKey) { |
447 | 0 | mPrivateKey = nullptr; |
448 | 0 | return NS_OK; |
449 | 0 | } |
450 | 0 | |
451 | 0 | mPrivateKey = UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(aPrivateKey)); |
452 | 0 | return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY; |
453 | 0 | } |
454 | | |
455 | | nsresult |
456 | | CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey) |
457 | 0 | { |
458 | 0 | if (!aPublicKey) { |
459 | 0 | mPublicKey = nullptr; |
460 | 0 | return NS_OK; |
461 | 0 | } |
462 | 0 | |
463 | 0 | mPublicKey = UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(aPublicKey)); |
464 | 0 | return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY; |
465 | 0 | } |
466 | | |
467 | | const CryptoBuffer& |
468 | | CryptoKey::GetSymKey() const |
469 | 0 | { |
470 | 0 | return mSymKey; |
471 | 0 | } |
472 | | |
473 | | UniqueSECKEYPrivateKey |
474 | | CryptoKey::GetPrivateKey() const |
475 | 0 | { |
476 | 0 | if (!mPrivateKey) { |
477 | 0 | return nullptr; |
478 | 0 | } |
479 | 0 | return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get())); |
480 | 0 | } |
481 | | |
482 | | UniqueSECKEYPublicKey |
483 | | CryptoKey::GetPublicKey() const |
484 | 0 | { |
485 | 0 | if (!mPublicKey) { |
486 | 0 | return nullptr; |
487 | 0 | } |
488 | 0 | return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get())); |
489 | 0 | } |
490 | | |
491 | | |
492 | | // Serialization and deserialization convenience methods |
493 | | |
494 | | UniqueSECKEYPrivateKey |
495 | | CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData) |
496 | 0 | { |
497 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalSlot()); |
498 | 0 | if (!slot) { |
499 | 0 | return nullptr; |
500 | 0 | } |
501 | 0 | |
502 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
503 | 0 | if (!arena) { |
504 | 0 | return nullptr; |
505 | 0 | } |
506 | 0 | |
507 | 0 | SECItem pkcs8Item = { siBuffer, nullptr, 0 }; |
508 | 0 | if (!aKeyData.ToSECItem(arena.get(), &pkcs8Item)) { |
509 | 0 | return nullptr; |
510 | 0 | } |
511 | 0 | |
512 | 0 | // Allow everything, we enforce usage ourselves |
513 | 0 | unsigned int usage = KU_ALL; |
514 | 0 |
|
515 | 0 | SECKEYPrivateKey* privKey; |
516 | 0 | SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( |
517 | 0 | slot.get(), &pkcs8Item, nullptr, nullptr, false, false, |
518 | 0 | usage, &privKey, nullptr); |
519 | 0 |
|
520 | 0 | if (rv == SECFailure) { |
521 | 0 | return nullptr; |
522 | 0 | } |
523 | 0 | |
524 | 0 | return UniqueSECKEYPrivateKey(privKey); |
525 | 0 | } |
526 | | |
527 | | UniqueSECKEYPublicKey |
528 | | CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData) |
529 | 0 | { |
530 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
531 | 0 | if (!arena) { |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | 0 | |
535 | 0 | SECItem spkiItem = { siBuffer, nullptr, 0 }; |
536 | 0 | if (!aKeyData.ToSECItem(arena.get(), &spkiItem)) { |
537 | 0 | return nullptr; |
538 | 0 | } |
539 | 0 | |
540 | 0 | UniqueCERTSubjectPublicKeyInfo spki( |
541 | 0 | SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem)); |
542 | 0 | if (!spki) { |
543 | 0 | return nullptr; |
544 | 0 | } |
545 | 0 | |
546 | 0 | bool isECDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, |
547 | 0 | &spki->algorithm.algorithm); |
548 | 0 | bool isDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_DH_KEY_AGREEMENT, |
549 | 0 | &spki->algorithm.algorithm); |
550 | 0 |
|
551 | 0 | // Check for |id-ecDH| and |dhKeyAgreement|. Per the WebCrypto spec we must |
552 | 0 | // support these OIDs but NSS does unfortunately not know about them. Let's |
553 | 0 | // change the algorithm to |id-ecPublicKey| or |dhPublicKey| to make NSS happy. |
554 | 0 | if (isECDHAlgorithm || isDHAlgorithm) { |
555 | 0 | SECOidTag oid = SEC_OID_UNKNOWN; |
556 | 0 | if (isECDHAlgorithm) { |
557 | 0 | oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY; |
558 | 0 | } else if (isDHAlgorithm) { |
559 | 0 | oid = SEC_OID_X942_DIFFIE_HELMAN_KEY; |
560 | 0 | } else { |
561 | 0 | MOZ_ASSERT(false); |
562 | 0 | } |
563 | 0 |
|
564 | 0 | SECOidData* oidData = SECOID_FindOIDByTag(oid); |
565 | 0 | if (!oidData) { |
566 | 0 | return nullptr; |
567 | 0 | } |
568 | 0 | |
569 | 0 | SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm, |
570 | 0 | &oidData->oid); |
571 | 0 | if (rv != SECSuccess) { |
572 | 0 | return nullptr; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | |
576 | 0 | UniqueSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get())); |
577 | 0 | if (!tmp.get() || !PublicKeyValid(tmp.get())) { |
578 | 0 | return nullptr; |
579 | 0 | } |
580 | 0 | |
581 | 0 | return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get())); |
582 | 0 | } |
583 | | |
584 | | nsresult |
585 | | CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey, CryptoBuffer& aRetVal) |
586 | 0 | { |
587 | 0 | UniqueSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr)); |
588 | 0 | if (!pkcs8Item.get()) { |
589 | 0 | return NS_ERROR_DOM_INVALID_ACCESS_ERR; |
590 | 0 | } |
591 | 0 | if (!aRetVal.Assign(pkcs8Item.get())) { |
592 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
593 | 0 | } |
594 | 0 | return NS_OK; |
595 | 0 | } |
596 | | |
597 | | nsresult |
598 | | PublicDhKeyToSpki(SECKEYPublicKey* aPubKey, |
599 | | CERTSubjectPublicKeyInfo* aSpki) |
600 | 0 | { |
601 | 0 | SECItem* params = ::SECITEM_AllocItem(aSpki->arena, nullptr, 0); |
602 | 0 | if (!params) { |
603 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
604 | 0 | } |
605 | 0 | |
606 | 0 | SECItem* rvItem = SEC_ASN1EncodeItem(aSpki->arena, params, aPubKey, |
607 | 0 | SECKEY_DHParamKeyTemplate); |
608 | 0 | if (!rvItem) { |
609 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
610 | 0 | } |
611 | 0 | |
612 | 0 | SECStatus rv = SECOID_SetAlgorithmID(aSpki->arena, &aSpki->algorithm, |
613 | 0 | SEC_OID_X942_DIFFIE_HELMAN_KEY, params); |
614 | 0 | if (rv != SECSuccess) { |
615 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
616 | 0 | } |
617 | 0 | |
618 | 0 | rvItem = SEC_ASN1EncodeItem(aSpki->arena, &aSpki->subjectPublicKey, aPubKey, |
619 | 0 | SECKEY_DHPublicKeyTemplate); |
620 | 0 | if (!rvItem) { |
621 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
622 | 0 | } |
623 | 0 | |
624 | 0 | // The public value is a BIT_STRING encoded as an INTEGER. After encoding |
625 | 0 | // an INT we need to adjust the length to reflect the number of bits. |
626 | 0 | aSpki->subjectPublicKey.len <<= 3; |
627 | 0 |
|
628 | 0 | return NS_OK; |
629 | 0 | } |
630 | | |
631 | | nsresult |
632 | | CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey, CryptoBuffer& aRetVal) |
633 | 0 | { |
634 | 0 | UniqueCERTSubjectPublicKeyInfo spki; |
635 | 0 |
|
636 | 0 | // NSS doesn't support exporting DH public keys. |
637 | 0 | if (aPubKey->keyType == dhKey) { |
638 | 0 | // Mimic the behavior of SECKEY_CreateSubjectPublicKeyInfo() and create |
639 | 0 | // a new arena for the SPKI object. |
640 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
641 | 0 | if (!arena) { |
642 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
643 | 0 | } |
644 | 0 | |
645 | 0 | spki.reset(PORT_ArenaZNew(arena.get(), CERTSubjectPublicKeyInfo)); |
646 | 0 | if (!spki) { |
647 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
648 | 0 | } |
649 | 0 | |
650 | 0 | // Assign |arena| to |spki| and null the variable afterwards so that the |
651 | 0 | // arena created above that holds the SPKI object is free'd when |spki| |
652 | 0 | // goes out of scope, not when |arena| does. |
653 | 0 | spki->arena = arena.release(); |
654 | 0 |
|
655 | 0 | nsresult rv = PublicDhKeyToSpki(aPubKey, spki.get()); |
656 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
657 | 0 | } else { |
658 | 0 | spki.reset(SECKEY_CreateSubjectPublicKeyInfo(aPubKey)); |
659 | 0 | if (!spki) { |
660 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
661 | 0 | } |
662 | 0 | } |
663 | 0 | |
664 | 0 | // Per WebCrypto spec we must export ECDH SPKIs with the algorithm OID |
665 | 0 | // id-ecDH (1.3.132.112) and DH SPKIs with OID dhKeyAgreement |
666 | 0 | // (1.2.840.113549.1.3.1). NSS doesn't know about these OIDs and there is |
667 | 0 | // no way to specify the algorithm to use when exporting a public key. |
668 | 0 | if (aPubKey->keyType == ecKey || aPubKey->keyType == dhKey) { |
669 | 0 | const SECItem* oidData = nullptr; |
670 | 0 | if (aPubKey->keyType == ecKey) { |
671 | 0 | oidData = &SEC_OID_DATA_EC_DH; |
672 | 0 | } else if (aPubKey->keyType == dhKey) { |
673 | 0 | oidData = &SEC_OID_DATA_DH_KEY_AGREEMENT; |
674 | 0 | } else { |
675 | 0 | MOZ_ASSERT(false); |
676 | 0 | } |
677 | 0 |
|
678 | 0 | SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm, |
679 | 0 | oidData); |
680 | 0 | if (rv != SECSuccess) { |
681 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
682 | 0 | } |
683 | 0 | } |
684 | 0 | |
685 | 0 | const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate); |
686 | 0 | UniqueSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki.get(), tpl)); |
687 | 0 |
|
688 | 0 | if (!aRetVal.Assign(spkiItem.get())) { |
689 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
690 | 0 | } |
691 | 0 | return NS_OK; |
692 | 0 | } |
693 | | |
694 | | SECItem* |
695 | | CreateECPointForCoordinates(const CryptoBuffer& aX, |
696 | | const CryptoBuffer& aY, |
697 | | PLArenaPool* aArena) |
698 | 0 | { |
699 | 0 | // Check that both points have the same length. |
700 | 0 | if (aX.Length() != aY.Length()) { |
701 | 0 | return nullptr; |
702 | 0 | } |
703 | 0 | |
704 | 0 | // Create point. |
705 | 0 | SECItem* point = ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1); |
706 | 0 | if (!point) { |
707 | 0 | return nullptr; |
708 | 0 | } |
709 | 0 | |
710 | 0 | // Set point data. |
711 | 0 | point->data[0] = EC_POINT_FORM_UNCOMPRESSED; |
712 | 0 | memcpy(point->data + 1, aX.Elements(), aX.Length()); |
713 | 0 | memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length()); |
714 | 0 |
|
715 | 0 | return point; |
716 | 0 | } |
717 | | |
718 | | UniqueSECKEYPrivateKey |
719 | | CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk) |
720 | 0 | { |
721 | 0 | CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY; |
722 | 0 | CK_BBOOL falseValue = CK_FALSE; |
723 | 0 |
|
724 | 0 | if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) { |
725 | 0 | // Verify that all of the required parameters are present |
726 | 0 | CryptoBuffer x, y, d; |
727 | 0 | if (!aJwk.mCrv.WasPassed() || |
728 | 0 | !aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || |
729 | 0 | !aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) || |
730 | 0 | !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) { |
731 | 0 | return nullptr; |
732 | 0 | } |
733 | 0 | |
734 | 0 | nsString namedCurve; |
735 | 0 | if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) { |
736 | 0 | return nullptr; |
737 | 0 | } |
738 | 0 | |
739 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
740 | 0 | if (!arena) { |
741 | 0 | return nullptr; |
742 | 0 | } |
743 | 0 | |
744 | 0 | // Create parameters. |
745 | 0 | SECItem* params = CreateECParamsForCurve(namedCurve, arena.get()); |
746 | 0 | if (!params) { |
747 | 0 | return nullptr; |
748 | 0 | } |
749 | 0 | |
750 | 0 | SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get()); |
751 | 0 | if (!ecPoint) { |
752 | 0 | return nullptr; |
753 | 0 | } |
754 | 0 | |
755 | 0 | // Populate template from parameters |
756 | 0 | CK_KEY_TYPE ecValue = CKK_EC; |
757 | 0 | CK_ATTRIBUTE keyTemplate[9] = { |
758 | 0 | { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) }, |
759 | 0 | { CKA_KEY_TYPE, &ecValue, sizeof(ecValue) }, |
760 | 0 | { CKA_TOKEN, &falseValue, sizeof(falseValue) }, |
761 | 0 | { CKA_SENSITIVE, &falseValue, sizeof(falseValue) }, |
762 | 0 | { CKA_PRIVATE, &falseValue, sizeof(falseValue) }, |
763 | 0 | // PrivateKeyFromPrivateKeyTemplate sets the ID. |
764 | 0 | { CKA_ID, nullptr, 0 }, |
765 | 0 | { CKA_EC_PARAMS, params->data, params->len }, |
766 | 0 | { CKA_EC_POINT, ecPoint->data, ecPoint->len }, |
767 | 0 | { CKA_VALUE, (void*) d.Elements(), (CK_ULONG) d.Length() }, |
768 | 0 | }; |
769 | 0 |
|
770 | 0 | return PrivateKeyFromPrivateKeyTemplate(keyTemplate, |
771 | 0 | ArrayLength(keyTemplate)); |
772 | 0 | } |
773 | 0 | |
774 | 0 | if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) { |
775 | 0 | // Verify that all of the required parameters are present |
776 | 0 | CryptoBuffer n, e, d, p, q, dp, dq, qi; |
777 | 0 | if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) || |
778 | 0 | !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) || |
779 | 0 | !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) || |
780 | 0 | !aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) || |
781 | 0 | !aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) || |
782 | 0 | !aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) || |
783 | 0 | !aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) || |
784 | 0 | !aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) { |
785 | 0 | return nullptr; |
786 | 0 | } |
787 | 0 | |
788 | 0 | // Populate template from parameters |
789 | 0 | CK_KEY_TYPE rsaValue = CKK_RSA; |
790 | 0 | CK_ATTRIBUTE keyTemplate[14] = { |
791 | 0 | { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) }, |
792 | 0 | { CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue) }, |
793 | 0 | { CKA_TOKEN, &falseValue, sizeof(falseValue) }, |
794 | 0 | { CKA_SENSITIVE, &falseValue, sizeof(falseValue) }, |
795 | 0 | { CKA_PRIVATE, &falseValue, sizeof(falseValue) }, |
796 | 0 | // PrivateKeyFromPrivateKeyTemplate sets the ID. |
797 | 0 | { CKA_ID, nullptr, 0 }, |
798 | 0 | { CKA_MODULUS, (void*) n.Elements(), (CK_ULONG) n.Length() }, |
799 | 0 | { CKA_PUBLIC_EXPONENT, (void*) e.Elements(), (CK_ULONG) e.Length() }, |
800 | 0 | { CKA_PRIVATE_EXPONENT, (void*) d.Elements(), (CK_ULONG) d.Length() }, |
801 | 0 | { CKA_PRIME_1, (void*) p.Elements(), (CK_ULONG) p.Length() }, |
802 | 0 | { CKA_PRIME_2, (void*) q.Elements(), (CK_ULONG) q.Length() }, |
803 | 0 | { CKA_EXPONENT_1, (void*) dp.Elements(), (CK_ULONG) dp.Length() }, |
804 | 0 | { CKA_EXPONENT_2, (void*) dq.Elements(), (CK_ULONG) dq.Length() }, |
805 | 0 | { CKA_COEFFICIENT, (void*) qi.Elements(), (CK_ULONG) qi.Length() }, |
806 | 0 | }; |
807 | 0 |
|
808 | 0 | return PrivateKeyFromPrivateKeyTemplate(keyTemplate, |
809 | 0 | ArrayLength(keyTemplate)); |
810 | 0 | } |
811 | 0 | |
812 | 0 | return nullptr; |
813 | 0 | } |
814 | | |
815 | | bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey, |
816 | | CK_ATTRIBUTE_TYPE aAttribute, |
817 | | Optional<nsString>& aDst) |
818 | 0 | { |
819 | 0 | ScopedAutoSECItem item; |
820 | 0 | if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, &item) |
821 | 0 | != SECSuccess) { |
822 | 0 | return false; |
823 | 0 | } |
824 | 0 | |
825 | 0 | CryptoBuffer buffer; |
826 | 0 | if (!buffer.Assign(&item)) { |
827 | 0 | return false; |
828 | 0 | } |
829 | 0 | |
830 | 0 | if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) { |
831 | 0 | return false; |
832 | 0 | } |
833 | 0 | |
834 | 0 | return true; |
835 | 0 | } |
836 | | |
837 | | bool |
838 | | ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey, const SECItem* aEcParams, |
839 | | const SECItem* aPublicValue, JsonWebKey& aRetVal) |
840 | 0 | { |
841 | 0 | aRetVal.mX.Construct(); |
842 | 0 | aRetVal.mY.Construct(); |
843 | 0 |
|
844 | 0 | // Check that the given EC parameters are valid. |
845 | 0 | if (!CheckEncodedECParameters(aEcParams)) { |
846 | 0 | return false; |
847 | 0 | } |
848 | 0 | |
849 | 0 | // Construct the OID tag. |
850 | 0 | SECItem oid = { siBuffer, nullptr, 0 }; |
851 | 0 | oid.len = aEcParams->data[1]; |
852 | 0 | oid.data = aEcParams->data + 2; |
853 | 0 |
|
854 | 0 | uint32_t flen; |
855 | 0 | switch (SECOID_FindOIDTag(&oid)) { |
856 | 0 | case SEC_OID_SECG_EC_SECP256R1: |
857 | 0 | flen = 32; // bytes |
858 | 0 | aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P256)); |
859 | 0 | break; |
860 | 0 | case SEC_OID_SECG_EC_SECP384R1: |
861 | 0 | flen = 48; // bytes |
862 | 0 | aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P384)); |
863 | 0 | break; |
864 | 0 | case SEC_OID_SECG_EC_SECP521R1: |
865 | 0 | flen = 66; // bytes |
866 | 0 | aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P521)); |
867 | 0 | break; |
868 | 0 | default: |
869 | 0 | return false; |
870 | 0 | } |
871 | 0 | |
872 | 0 | // No support for compressed points. |
873 | 0 | if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) { |
874 | 0 | return false; |
875 | 0 | } |
876 | 0 | |
877 | 0 | // Check length of uncompressed point coordinates. |
878 | 0 | if (aPublicValue->len != (2 * flen + 1)) { |
879 | 0 | return false; |
880 | 0 | } |
881 | 0 | |
882 | 0 | UniqueSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen)); |
883 | 0 | UniqueSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen)); |
884 | 0 | if (!ecPointX || !ecPointY) { |
885 | 0 | return false; |
886 | 0 | } |
887 | 0 | |
888 | 0 | // Extract point data. |
889 | 0 | memcpy(ecPointX->data, aPublicValue->data + 1, flen); |
890 | 0 | memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen); |
891 | 0 |
|
892 | 0 | CryptoBuffer x, y; |
893 | 0 | if (!x.Assign(ecPointX.get()) || |
894 | 0 | NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) || |
895 | 0 | !y.Assign(ecPointY.get()) || |
896 | 0 | NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) { |
897 | 0 | return false; |
898 | 0 | } |
899 | 0 | |
900 | 0 | aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_EC); |
901 | 0 | return true; |
902 | 0 | } |
903 | | |
904 | | nsresult |
905 | | CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey, JsonWebKey& aRetVal) |
906 | 0 | { |
907 | 0 | switch (aPrivKey->keyType) { |
908 | 0 | case rsaKey: { |
909 | 0 | aRetVal.mN.Construct(); |
910 | 0 | aRetVal.mE.Construct(); |
911 | 0 | aRetVal.mD.Construct(); |
912 | 0 | aRetVal.mP.Construct(); |
913 | 0 | aRetVal.mQ.Construct(); |
914 | 0 | aRetVal.mDp.Construct(); |
915 | 0 | aRetVal.mDq.Construct(); |
916 | 0 | aRetVal.mQi.Construct(); |
917 | 0 |
|
918 | 0 | if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) || |
919 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) || |
920 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) || |
921 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) || |
922 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) || |
923 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) || |
924 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) || |
925 | 0 | !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) { |
926 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
927 | 0 | } |
928 | 0 | |
929 | 0 | aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA); |
930 | 0 | return NS_OK; |
931 | 0 | } |
932 | 0 | case ecKey: { |
933 | 0 | // Read EC params. |
934 | 0 | ScopedAutoSECItem params; |
935 | 0 | SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, |
936 | 0 | CKA_EC_PARAMS, ¶ms); |
937 | 0 | if (rv != SECSuccess) { |
938 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
939 | 0 | } |
940 | 0 | |
941 | 0 | // Read public point Q. |
942 | 0 | ScopedAutoSECItem ecPoint; |
943 | 0 | rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT, |
944 | 0 | &ecPoint); |
945 | 0 | if (rv != SECSuccess) { |
946 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
947 | 0 | } |
948 | 0 | |
949 | 0 | if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, ¶ms, &ecPoint, aRetVal)) { |
950 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
951 | 0 | } |
952 | 0 | |
953 | 0 | aRetVal.mD.Construct(); |
954 | 0 |
|
955 | 0 | // Read private value. |
956 | 0 | if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) { |
957 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
958 | 0 | } |
959 | 0 | |
960 | 0 | return NS_OK; |
961 | 0 | } |
962 | 0 | default: |
963 | 0 | return NS_ERROR_DOM_NOT_SUPPORTED_ERR; |
964 | 0 | } |
965 | 0 | } |
966 | | |
967 | | UniqueSECKEYPublicKey |
968 | | CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve) |
969 | 0 | { |
970 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
971 | 0 | if (!arena) { |
972 | 0 | return nullptr; |
973 | 0 | } |
974 | 0 | |
975 | 0 | // It's important that this be a UniqueSECKEYPublicKey, as this ensures that |
976 | 0 | // SECKEY_DestroyPublicKey will be called on it. If this doesn't happen, when |
977 | 0 | // CryptoKey::PublicKeyValid is called on it and it gets moved to the internal |
978 | 0 | // PKCS#11 slot, it will leak a reference to the slot. |
979 | 0 | UniqueSECKEYPublicKey key(PORT_ArenaZNew(arena.get(), SECKEYPublicKey)); |
980 | 0 | if (!key) { |
981 | 0 | return nullptr; |
982 | 0 | } |
983 | 0 | |
984 | 0 | key->arena = nullptr; // key doesn't own the arena; it won't get double-freed |
985 | 0 | key->keyType = ecKey; |
986 | 0 | key->pkcs11Slot = nullptr; |
987 | 0 | key->pkcs11ID = CK_INVALID_HANDLE; |
988 | 0 |
|
989 | 0 | // Create curve parameters. |
990 | 0 | SECItem* params = CreateECParamsForCurve(aNamedCurve, arena.get()); |
991 | 0 | if (!params) { |
992 | 0 | return nullptr; |
993 | 0 | } |
994 | 0 | key->u.ec.DEREncodedParams = *params; |
995 | 0 |
|
996 | 0 | // Set public point. |
997 | 0 | key->u.ec.publicValue = *aKeyData; |
998 | 0 |
|
999 | 0 | // Ensure the given point is on the curve. |
1000 | 0 | if (!CryptoKey::PublicKeyValid(key.get())) { |
1001 | 0 | return nullptr; |
1002 | 0 | } |
1003 | 0 | |
1004 | 0 | return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key.get())); |
1005 | 0 | } |
1006 | | |
1007 | | UniqueSECKEYPublicKey |
1008 | | CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk) |
1009 | 0 | { |
1010 | 0 | if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) { |
1011 | 0 | // Verify that all of the required parameters are present |
1012 | 0 | CryptoBuffer n, e; |
1013 | 0 | if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) || |
1014 | 0 | !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) { |
1015 | 0 | return nullptr; |
1016 | 0 | } |
1017 | 0 | |
1018 | 0 | // Transcode to a DER RSAPublicKey structure |
1019 | 0 | struct RSAPublicKeyData { |
1020 | 0 | SECItem n; |
1021 | 0 | SECItem e; |
1022 | 0 | }; |
1023 | 0 | const RSAPublicKeyData input = { |
1024 | 0 | { siUnsignedInteger, n.Elements(), (unsigned int) n.Length() }, |
1025 | 0 | { siUnsignedInteger, e.Elements(), (unsigned int) e.Length() } |
1026 | 0 | }; |
1027 | 0 | const SEC_ASN1Template rsaPublicKeyTemplate[] = { |
1028 | 0 | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)}, |
1029 | 0 | {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),}, |
1030 | 0 | {SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),}, |
1031 | 0 | {0,} |
1032 | 0 | }; |
1033 | 0 |
|
1034 | 0 | UniqueSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input, |
1035 | 0 | rsaPublicKeyTemplate)); |
1036 | 0 | if (!pkDer.get()) { |
1037 | 0 | return nullptr; |
1038 | 0 | } |
1039 | 0 | |
1040 | 0 | return UniqueSECKEYPublicKey(SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA)); |
1041 | 0 | } |
1042 | 0 | |
1043 | 0 | if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) { |
1044 | 0 | // Verify that all of the required parameters are present |
1045 | 0 | CryptoBuffer x, y; |
1046 | 0 | if (!aJwk.mCrv.WasPassed() || |
1047 | 0 | !aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) || |
1048 | 0 | !aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) { |
1049 | 0 | return nullptr; |
1050 | 0 | } |
1051 | 0 | |
1052 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
1053 | 0 | if (!arena) { |
1054 | 0 | return nullptr; |
1055 | 0 | } |
1056 | 0 | |
1057 | 0 | // Create point. |
1058 | 0 | SECItem* point = CreateECPointForCoordinates(x, y, arena.get()); |
1059 | 0 | if (!point) { |
1060 | 0 | return nullptr; |
1061 | 0 | } |
1062 | 0 | |
1063 | 0 | nsString namedCurve; |
1064 | 0 | if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) { |
1065 | 0 | return nullptr; |
1066 | 0 | } |
1067 | 0 | |
1068 | 0 | return CreateECPublicKey(point, namedCurve); |
1069 | 0 | } |
1070 | 0 | |
1071 | 0 | return nullptr; |
1072 | 0 | } |
1073 | | |
1074 | | nsresult |
1075 | | CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey, JsonWebKey& aRetVal) |
1076 | 0 | { |
1077 | 0 | switch (aPubKey->keyType) { |
1078 | 0 | case rsaKey: { |
1079 | 0 | CryptoBuffer n, e; |
1080 | 0 | aRetVal.mN.Construct(); |
1081 | 0 | aRetVal.mE.Construct(); |
1082 | 0 |
|
1083 | 0 | if (!n.Assign(&aPubKey->u.rsa.modulus) || |
1084 | 0 | !e.Assign(&aPubKey->u.rsa.publicExponent) || |
1085 | 0 | NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) || |
1086 | 0 | NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) { |
1087 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
1088 | 0 | } |
1089 | 0 | |
1090 | 0 | aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA); |
1091 | 0 | return NS_OK; |
1092 | 0 | } |
1093 | 0 | case ecKey: |
1094 | 0 | if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams, |
1095 | 0 | &aPubKey->u.ec.publicValue, aRetVal)) { |
1096 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
1097 | 0 | } |
1098 | 0 | return NS_OK; |
1099 | 0 | default: |
1100 | 0 | return NS_ERROR_DOM_NOT_SUPPORTED_ERR; |
1101 | 0 | } |
1102 | 0 | } |
1103 | | |
1104 | | UniqueSECKEYPublicKey |
1105 | | CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData, |
1106 | | const CryptoBuffer& aPrime, |
1107 | | const CryptoBuffer& aGenerator) |
1108 | 0 | { |
1109 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
1110 | 0 | if (!arena) { |
1111 | 0 | return nullptr; |
1112 | 0 | } |
1113 | 0 | |
1114 | 0 | SECKEYPublicKey* key = PORT_ArenaZNew(arena.get(), SECKEYPublicKey); |
1115 | 0 | if (!key) { |
1116 | 0 | return nullptr; |
1117 | 0 | } |
1118 | 0 | |
1119 | 0 | key->keyType = dhKey; |
1120 | 0 | key->pkcs11Slot = nullptr; |
1121 | 0 | key->pkcs11ID = CK_INVALID_HANDLE; |
1122 | 0 |
|
1123 | 0 | // Set DH public key params. |
1124 | 0 | if (!aPrime.ToSECItem(arena.get(), &key->u.dh.prime) || |
1125 | 0 | !aGenerator.ToSECItem(arena.get(), &key->u.dh.base) || |
1126 | 0 | !aKeyData.ToSECItem(arena.get(), &key->u.dh.publicValue)) { |
1127 | 0 | return nullptr; |
1128 | 0 | } |
1129 | 0 | |
1130 | 0 | key->u.dh.prime.type = siUnsignedInteger; |
1131 | 0 | key->u.dh.base.type = siUnsignedInteger; |
1132 | 0 | key->u.dh.publicValue.type = siUnsignedInteger; |
1133 | 0 |
|
1134 | 0 | return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key)); |
1135 | 0 | } |
1136 | | |
1137 | | nsresult |
1138 | | CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey, CryptoBuffer& aRetVal) |
1139 | 0 | { |
1140 | 0 | if (!aRetVal.Assign(&aPubKey->u.dh.publicValue)) { |
1141 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
1142 | 0 | } |
1143 | 0 | return NS_OK; |
1144 | 0 | } |
1145 | | |
1146 | | UniqueSECKEYPublicKey |
1147 | | CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData, |
1148 | | const nsString& aNamedCurve) |
1149 | 0 | { |
1150 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
1151 | 0 | if (!arena) { |
1152 | 0 | return nullptr; |
1153 | 0 | } |
1154 | 0 | |
1155 | 0 | SECItem rawItem = { siBuffer, nullptr, 0 }; |
1156 | 0 | if (!aKeyData.ToSECItem(arena.get(), &rawItem)) { |
1157 | 0 | return nullptr; |
1158 | 0 | } |
1159 | 0 | |
1160 | 0 | uint32_t flen; |
1161 | 0 | if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) { |
1162 | 0 | flen = 32; // bytes |
1163 | 0 | } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) { |
1164 | 0 | flen = 48; // bytes |
1165 | 0 | } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) { |
1166 | 0 | flen = 66; // bytes |
1167 | 0 | } else { |
1168 | 0 | return nullptr; |
1169 | 0 | } |
1170 | 0 | |
1171 | 0 | // Check length of uncompressed point coordinates. There are 2 field elements |
1172 | 0 | // and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED). |
1173 | 0 | if (rawItem.len != (2 * flen + 1)) { |
1174 | 0 | return nullptr; |
1175 | 0 | } |
1176 | 0 | |
1177 | 0 | // No support for compressed points. |
1178 | 0 | if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) { |
1179 | 0 | return nullptr; |
1180 | 0 | } |
1181 | 0 | |
1182 | 0 | return CreateECPublicKey(&rawItem, aNamedCurve); |
1183 | 0 | } |
1184 | | |
1185 | | nsresult |
1186 | | CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey, CryptoBuffer& aRetVal) |
1187 | 0 | { |
1188 | 0 | if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) { |
1189 | 0 | return NS_ERROR_DOM_OPERATION_ERR; |
1190 | 0 | } |
1191 | 0 | return NS_OK; |
1192 | 0 | } |
1193 | | |
1194 | | bool |
1195 | | CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey) |
1196 | 0 | { |
1197 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalSlot()); |
1198 | 0 | if (!slot.get()) { |
1199 | 0 | return false; |
1200 | 0 | } |
1201 | 0 | |
1202 | 0 | // This assumes that NSS checks the validity of a public key when |
1203 | 0 | // it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE |
1204 | 0 | // if it is invalid. |
1205 | 0 | CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot.get(), aPubKey, PR_FALSE); |
1206 | 0 | if (id == CK_INVALID_HANDLE) { |
1207 | 0 | return false; |
1208 | 0 | } |
1209 | 0 | |
1210 | 0 | SECStatus rv = PK11_DestroyObject(slot.get(), id); |
1211 | 0 | return (rv == SECSuccess); |
1212 | 0 | } |
1213 | | |
1214 | | bool |
1215 | | CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const |
1216 | 0 | { |
1217 | 0 | // Write in five pieces |
1218 | 0 | // 1. Attributes |
1219 | 0 | // 2. Symmetric key as raw (if present) |
1220 | 0 | // 3. Private key as pkcs8 (if present) |
1221 | 0 | // 4. Public key as spki (if present) |
1222 | 0 | // 5. Algorithm in whatever form it chooses |
1223 | 0 | CryptoBuffer priv, pub; |
1224 | 0 |
|
1225 | 0 | if (mPrivateKey) { |
1226 | 0 | if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), priv))) { |
1227 | 0 | return false; |
1228 | 0 | } |
1229 | 0 | } |
1230 | 0 | |
1231 | 0 | if (mPublicKey) { |
1232 | 0 | if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey.get(), pub))) { |
1233 | 0 | return false; |
1234 | 0 | } |
1235 | 0 | } |
1236 | 0 | |
1237 | 0 | return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) && |
1238 | 0 | WriteBuffer(aWriter, mSymKey) && |
1239 | 0 | WriteBuffer(aWriter, priv) && |
1240 | 0 | WriteBuffer(aWriter, pub) && |
1241 | 0 | mAlgorithm.WriteStructuredClone(aWriter); |
1242 | 0 | } |
1243 | | |
1244 | | bool |
1245 | | CryptoKey::ReadStructuredClone(JSStructuredCloneReader* aReader) |
1246 | 0 | { |
1247 | 0 | // Ensure that NSS is initialized. |
1248 | 0 | if (!EnsureNSSInitializedChromeOrContent()) { |
1249 | 0 | return false; |
1250 | 0 | } |
1251 | 0 | |
1252 | 0 | uint32_t version; |
1253 | 0 | CryptoBuffer sym, priv, pub; |
1254 | 0 |
|
1255 | 0 | bool read = JS_ReadUint32Pair(aReader, &mAttributes, &version) && |
1256 | 0 | (version == CRYPTOKEY_SC_VERSION) && |
1257 | 0 | ReadBuffer(aReader, sym) && |
1258 | 0 | ReadBuffer(aReader, priv) && |
1259 | 0 | ReadBuffer(aReader, pub) && |
1260 | 0 | mAlgorithm.ReadStructuredClone(aReader); |
1261 | 0 | if (!read) { |
1262 | 0 | return false; |
1263 | 0 | } |
1264 | 0 | |
1265 | 0 | if (sym.Length() > 0 && !mSymKey.Assign(sym)) { |
1266 | 0 | return false; |
1267 | 0 | } |
1268 | 0 | if (priv.Length() > 0) { |
1269 | 0 | mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv); |
1270 | 0 | } |
1271 | 0 | if (pub.Length() > 0) { |
1272 | 0 | mPublicKey = CryptoKey::PublicKeyFromSpki(pub); |
1273 | 0 | } |
1274 | 0 |
|
1275 | 0 | // Ensure that what we've read is consistent |
1276 | 0 | // If the attributes indicate a key type, should have a key of that type |
1277 | 0 | if (!((GetKeyType() == SECRET && mSymKey.Length() > 0) || |
1278 | 0 | (GetKeyType() == PRIVATE && mPrivateKey) || |
1279 | 0 | (GetKeyType() == PUBLIC && mPublicKey))) { |
1280 | 0 | return false; |
1281 | 0 | } |
1282 | 0 | |
1283 | 0 | return true; |
1284 | 0 | } |
1285 | | |
1286 | | } // namespace dom |
1287 | | } // namespace mozilla |