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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/dom/U2FSoftTokenManager.h"
8 #include "CryptoBuffer.h"
9 #include "mozilla/Base64.h"
10 #include "mozilla/Casting.h"
11 #include "nsNSSComponent.h"
12 #include "nsThreadUtils.h"
13 #include "pk11pub.h"
14 #include "prerror.h"
15 #include "secerr.h"
16 #include "WebCryptoCommon.h"
17
18 #define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter"
19
20 namespace mozilla {
21 namespace dom {
22
23 using namespace mozilla;
24 using mozilla::dom::CreateECParamsForCurve;
25
26 const nsCString U2FSoftTokenManager::mSecretNickname =
27 NS_LITERAL_CSTRING("U2F_NSSTOKEN");
28
29 namespace {
30 NS_NAMED_LITERAL_CSTRING(kAttestCertSubjectName, "CN=Firefox U2F Soft Token");
31
32 // This U2F-compatible soft token uses FIDO U2F-compatible ECDSA keypairs
33 // on the SEC_OID_SECG_EC_SECP256R1 curve. When asked to Register, it will
34 // generate and return a new keypair KP, where the private component is wrapped
35 // using AES-KW with the 128-bit mWrappingKey to make an opaque "key handle".
36 // In other words, Register yields { KP_pub, AES-KW(KP_priv, key=mWrappingKey) }
37 //
38 // The value mWrappingKey is long-lived; it is persisted as part of the NSS DB
39 // for the current profile. The attestation certificates that are produced are
40 // ephemeral to counteract profiling. They have little use for a soft-token
41 // at any rate, but are required by the specification.
42
43 const uint32_t kParamLen = 32;
44 const uint32_t kPublicKeyLen = 65;
45 const uint32_t kWrappedKeyBufLen = 256;
46 const uint32_t kWrappingKeyByteLen = 128 / 8;
47 const uint32_t kSaltByteLen = 64 / 8;
48 const uint32_t kVersion1KeyHandleLen = 162;
49 NS_NAMED_LITERAL_STRING(kEcAlgorithm, WEBCRYPTO_NAMED_CURVE_P256);
50
51 const PRTime kOneDay = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
52 * PRTime(60) // min
53 * PRTime(24); // hours
54 const PRTime kExpirationSlack = kOneDay; // Pre-date for clock skew
55 const PRTime kExpirationLife = kOneDay;
56
57 static mozilla::LazyLogModule gNSSTokenLog("webauth_u2f");
58
59 enum SoftTokenHandle {
60 Version1 = 0,
61 };
62
63 } // namespace
64
U2FSoftTokenManager(uint32_t aCounter)65 U2FSoftTokenManager::U2FSoftTokenManager(uint32_t aCounter)
66 : mInitialized(false), mCounter(aCounter) {}
67
68 /**
69 * Gets the first key with the given nickname from the given slot. Any other
70 * keys found are not returned.
71 * PK11_GetNextSymKey() should not be called on the returned key.
72 *
73 * @param aSlot Slot to search.
74 * @param aNickname Nickname the key should have.
75 * @return The first key found. nullptr if no key could be found.
76 */
GetSymKeyByNickname(const UniquePK11SlotInfo & aSlot,const nsCString & aNickname)77 static UniquePK11SymKey GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot,
78 const nsCString& aNickname) {
79 MOZ_ASSERT(aSlot);
80 if (NS_WARN_IF(!aSlot)) {
81 return nullptr;
82 }
83
84 MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
85 ("Searching for a symmetric key named %s", aNickname.get()));
86
87 UniquePK11SymKey keyListHead(
88 PK11_ListFixedKeysInSlot(aSlot.get(), const_cast<char*>(aNickname.get()),
89 /* wincx */ nullptr));
90 if (NS_WARN_IF(!keyListHead)) {
91 MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key not found."));
92 return nullptr;
93 }
94
95 // Sanity check PK11_ListFixedKeysInSlot() only returns keys with the correct
96 // nickname.
97 MOZ_ASSERT(aNickname ==
98 UniquePORTString(PK11_GetSymKeyNickname(keyListHead.get())).get());
99 MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key found!"));
100
101 // Free any remaining keys in the key list.
102 UniquePK11SymKey freeKey(PK11_GetNextSymKey(keyListHead.get()));
103 while (freeKey) {
104 freeKey = UniquePK11SymKey(PK11_GetNextSymKey(freeKey.get()));
105 }
106
107 return keyListHead;
108 }
109
GenEcKeypair(const UniquePK11SlotInfo & aSlot,UniqueSECKEYPrivateKey & aPrivKey,UniqueSECKEYPublicKey & aPubKey)110 static nsresult GenEcKeypair(const UniquePK11SlotInfo& aSlot,
111 /*out*/ UniqueSECKEYPrivateKey& aPrivKey,
112 /*out*/ UniqueSECKEYPublicKey& aPubKey) {
113 MOZ_ASSERT(aSlot);
114 if (NS_WARN_IF(!aSlot)) {
115 return NS_ERROR_INVALID_ARG;
116 }
117
118 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
119 if (NS_WARN_IF(!arena)) {
120 return NS_ERROR_OUT_OF_MEMORY;
121 }
122
123 // Set the curve parameters; keyParams belongs to the arena memory space
124 SECItem* keyParams = CreateECParamsForCurve(kEcAlgorithm, arena.get());
125 if (NS_WARN_IF(!keyParams)) {
126 return NS_ERROR_OUT_OF_MEMORY;
127 }
128
129 // Generate a key pair
130 CK_MECHANISM_TYPE mechanism = CKM_EC_KEY_PAIR_GEN;
131
132 SECKEYPublicKey* pubKeyRaw;
133 aPrivKey = UniqueSECKEYPrivateKey(
134 PK11_GenerateKeyPair(aSlot.get(), mechanism, keyParams, &pubKeyRaw,
135 /* ephemeral */ false, false,
136 /* wincx */ nullptr));
137 aPubKey = UniqueSECKEYPublicKey(pubKeyRaw);
138 pubKeyRaw = nullptr;
139 if (NS_WARN_IF(!aPrivKey.get() || !aPubKey.get())) {
140 return NS_ERROR_FAILURE;
141 }
142
143 // Check that the public key has the correct length
144 if (NS_WARN_IF(aPubKey->u.ec.publicValue.len != kPublicKeyLen)) {
145 return NS_ERROR_FAILURE;
146 }
147
148 return NS_OK;
149 }
150
GetOrCreateWrappingKey(const UniquePK11SlotInfo & aSlot)151 nsresult U2FSoftTokenManager::GetOrCreateWrappingKey(
152 const UniquePK11SlotInfo& aSlot) {
153 MOZ_ASSERT(aSlot);
154 if (NS_WARN_IF(!aSlot)) {
155 return NS_ERROR_INVALID_ARG;
156 }
157
158 // Search for an existing wrapping key. If we find it,
159 // store it for later and mark ourselves initialized.
160 mWrappingKey = GetSymKeyByNickname(aSlot, mSecretNickname);
161 if (mWrappingKey) {
162 MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token Key found."));
163 mInitialized = true;
164 return NS_OK;
165 }
166
167 MOZ_LOG(gNSSTokenLog, LogLevel::Info,
168 ("No keys found. Generating new U2F Soft Token wrapping key."));
169
170 // We did not find an existing wrapping key, so we generate one in the
171 // persistent database (e.g, Token).
172 mWrappingKey = UniquePK11SymKey(PK11_TokenKeyGenWithFlags(
173 aSlot.get(), CKM_AES_KEY_GEN,
174 /* default params */ nullptr, kWrappingKeyByteLen,
175 /* empty keyid */ nullptr,
176 /* flags */ CKF_WRAP | CKF_UNWRAP,
177 /* attributes */ PK11_ATTR_TOKEN | PK11_ATTR_PRIVATE,
178 /* wincx */ nullptr));
179
180 if (NS_WARN_IF(!mWrappingKey)) {
181 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
182 ("Failed to store wrapping key, NSS error #%d", PORT_GetError()));
183 return NS_ERROR_FAILURE;
184 }
185
186 SECStatus srv =
187 PK11_SetSymKeyNickname(mWrappingKey.get(), mSecretNickname.get());
188 if (NS_WARN_IF(srv != SECSuccess)) {
189 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
190 ("Failed to set nickname, NSS error #%d", PORT_GetError()));
191 return NS_ERROR_FAILURE;
192 }
193
194 MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
195 ("Key stored, nickname set to %s.", mSecretNickname.get()));
196
197 GetMainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
198 "dom::U2FSoftTokenManager::GetOrCreateWrappingKey", []() {
199 MOZ_ASSERT(NS_IsMainThread());
200 Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, 0);
201 }));
202
203 return NS_OK;
204 }
205
GetAttestationCertificate(const UniquePK11SlotInfo & aSlot,UniqueSECKEYPrivateKey & aAttestPrivKey,UniqueCERTCertificate & aAttestCert)206 static nsresult GetAttestationCertificate(
207 const UniquePK11SlotInfo& aSlot,
208 /*out*/ UniqueSECKEYPrivateKey& aAttestPrivKey,
209 /*out*/ UniqueCERTCertificate& aAttestCert) {
210 MOZ_ASSERT(aSlot);
211 if (NS_WARN_IF(!aSlot)) {
212 return NS_ERROR_INVALID_ARG;
213 }
214
215 UniqueSECKEYPublicKey pubKey;
216
217 // Construct an ephemeral keypair for this Attestation Certificate
218 nsresult rv = GenEcKeypair(aSlot, aAttestPrivKey, pubKey);
219 if (NS_WARN_IF(NS_FAILED(rv) || !aAttestPrivKey || !pubKey)) {
220 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
221 ("Failed to gen keypair, NSS error #%d", PORT_GetError()));
222 return NS_ERROR_FAILURE;
223 }
224
225 // Construct the Attestation Certificate itself
226 UniqueCERTName subjectName(CERT_AsciiToName(kAttestCertSubjectName.get()));
227 if (NS_WARN_IF(!subjectName)) {
228 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
229 ("Failed to set subject name, NSS error #%d", PORT_GetError()));
230 return NS_ERROR_FAILURE;
231 }
232
233 UniqueCERTSubjectPublicKeyInfo spki(
234 SECKEY_CreateSubjectPublicKeyInfo(pubKey.get()));
235 if (NS_WARN_IF(!spki)) {
236 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
237 ("Failed to set SPKI, NSS error #%d", PORT_GetError()));
238 return NS_ERROR_FAILURE;
239 }
240
241 UniqueCERTCertificateRequest certreq(
242 CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
243 if (NS_WARN_IF(!certreq)) {
244 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
245 ("Failed to gen CSR, NSS error #%d", PORT_GetError()));
246 return NS_ERROR_FAILURE;
247 }
248
249 PRTime now = PR_Now();
250 PRTime notBefore = now - kExpirationSlack;
251 PRTime notAfter = now + kExpirationLife;
252
253 UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
254 if (NS_WARN_IF(!validity)) {
255 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
256 ("Failed to gen validity, NSS error #%d", PORT_GetError()));
257 return NS_ERROR_FAILURE;
258 }
259
260 unsigned long serial;
261 unsigned char* serialBytes =
262 mozilla::BitwiseCast<unsigned char*, unsigned long*>(&serial);
263 SECStatus srv =
264 PK11_GenerateRandomOnSlot(aSlot.get(), serialBytes, sizeof(serial));
265 if (NS_WARN_IF(srv != SECSuccess)) {
266 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
267 ("Failed to gen serial, NSS error #%d", PORT_GetError()));
268 return NS_ERROR_FAILURE;
269 }
270 // Ensure that the most significant bit isn't set (which would
271 // indicate a negative number, which isn't valid for serial
272 // numbers).
273 serialBytes[0] &= 0x7f;
274 // Also ensure that the least significant bit on the most
275 // significant byte is set (to prevent a leading zero byte,
276 // which also wouldn't be valid).
277 serialBytes[0] |= 0x01;
278
279 aAttestCert = UniqueCERTCertificate(CERT_CreateCertificate(
280 serial, subjectName.get(), validity.get(), certreq.get()));
281 if (NS_WARN_IF(!aAttestCert)) {
282 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
283 ("Failed to gen certificate, NSS error #%d", PORT_GetError()));
284 return NS_ERROR_FAILURE;
285 }
286
287 PLArenaPool* arena = aAttestCert->arena;
288
289 srv = SECOID_SetAlgorithmID(arena, &aAttestCert->signature,
290 SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE,
291 /* wincx */ nullptr);
292 if (NS_WARN_IF(srv != SECSuccess)) {
293 return NS_ERROR_FAILURE;
294 }
295
296 // Set version to X509v3.
297 *(aAttestCert->version.data) = SEC_CERTIFICATE_VERSION_3;
298 aAttestCert->version.len = 1;
299
300 SECItem innerDER = {siBuffer, nullptr, 0};
301 if (NS_WARN_IF(!SEC_ASN1EncodeItem(arena, &innerDER, aAttestCert.get(),
302 SEC_ASN1_GET(CERT_CertificateTemplate)))) {
303 return NS_ERROR_FAILURE;
304 }
305
306 SECItem* signedCert = PORT_ArenaZNew(arena, SECItem);
307 if (NS_WARN_IF(!signedCert)) {
308 return NS_ERROR_FAILURE;
309 }
310
311 srv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
312 aAttestPrivKey.get(),
313 SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
314 if (NS_WARN_IF(srv != SECSuccess)) {
315 return NS_ERROR_FAILURE;
316 }
317 aAttestCert->derCert = *signedCert;
318
319 MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
320 ("U2F Soft Token attestation certificate generated."));
321 return NS_OK;
322 }
323
324 // Set up the context for the soft U2F Token. This is called by NSS
325 // initialization.
Init()326 nsresult U2FSoftTokenManager::Init() {
327 // If we've already initialized, just return.
328 if (mInitialized) {
329 return NS_OK;
330 }
331
332 UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
333 MOZ_ASSERT(slot.get());
334
335 // Search for an existing wrapping key, or create one.
336 nsresult rv = GetOrCreateWrappingKey(slot);
337 if (NS_WARN_IF(NS_FAILED(rv))) {
338 return rv;
339 }
340
341 mInitialized = true;
342 MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token initialized."));
343 return NS_OK;
344 }
345
346 // Convert a Private Key object into an opaque key handle, using AES Key Wrap
347 // with the long-lived aPersistentKey mixed with aAppParam to convert aPrivKey.
348 // The key handle's format is version || saltLen || salt || wrappedPrivateKey
KeyHandleFromPrivateKey(const UniquePK11SlotInfo & aSlot,const UniquePK11SymKey & aPersistentKey,uint8_t * aAppParam,uint32_t aAppParamLen,const UniqueSECKEYPrivateKey & aPrivKey)349 static UniqueSECItem KeyHandleFromPrivateKey(
350 const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey,
351 uint8_t* aAppParam, uint32_t aAppParamLen,
352 const UniqueSECKEYPrivateKey& aPrivKey) {
353 MOZ_ASSERT(aSlot);
354 MOZ_ASSERT(aPersistentKey);
355 MOZ_ASSERT(aAppParam);
356 MOZ_ASSERT(aPrivKey);
357 if (NS_WARN_IF(!aSlot || !aPersistentKey || !aPrivKey || !aAppParam)) {
358 return nullptr;
359 }
360
361 // Generate a random salt
362 uint8_t saltParam[kSaltByteLen];
363 SECStatus srv =
364 PK11_GenerateRandomOnSlot(aSlot.get(), saltParam, sizeof(saltParam));
365 if (NS_WARN_IF(srv != SECSuccess)) {
366 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
367 ("Failed to generate a salt, NSS error #%d", PORT_GetError()));
368 return nullptr;
369 }
370
371 // Prepare the HKDF (https://tools.ietf.org/html/rfc5869)
372 CK_NSS_HKDFParams hkdfParams = {true, saltParam, sizeof(saltParam),
373 true, aAppParam, aAppParamLen};
374 SECItem kdfParams = {siBuffer, (unsigned char*)&hkdfParams,
375 sizeof(hkdfParams)};
376
377 // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam.
378 // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the
379 // derived symmetric key and don't matter because we ignore them anyway.
380 UniquePK11SymKey wrapKey(
381 PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams,
382 CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen));
383 if (NS_WARN_IF(!wrapKey.get())) {
384 MOZ_LOG(
385 gNSSTokenLog, LogLevel::Warning,
386 ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
387 return nullptr;
388 }
389
390 UniqueSECItem wrappedKey(::SECITEM_AllocItem(/* default arena */ nullptr,
391 /* no buffer */ nullptr,
392 kWrappedKeyBufLen));
393 if (NS_WARN_IF(!wrappedKey)) {
394 MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
395 return nullptr;
396 }
397
398 UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
399 /* default IV */ nullptr));
400
401 srv =
402 PK11_WrapPrivKey(aSlot.get(), wrapKey.get(), aPrivKey.get(),
403 CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(),
404 /* wincx */ nullptr);
405 if (NS_WARN_IF(srv != SECSuccess)) {
406 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
407 ("Failed to wrap U2F key, NSS error #%d", PORT_GetError()));
408 return nullptr;
409 }
410
411 // Concatenate the salt and the wrapped Private Key together
412 mozilla::dom::CryptoBuffer keyHandleBuf;
413 if (NS_WARN_IF(!keyHandleBuf.SetCapacity(
414 wrappedKey.get()->len + sizeof(saltParam) + 2, mozilla::fallible))) {
415 MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
416 return nullptr;
417 }
418
419 // It's OK to ignore the return values here because we're writing into
420 // pre-allocated space
421 keyHandleBuf.AppendElement(SoftTokenHandle::Version1, mozilla::fallible);
422 keyHandleBuf.AppendElement(sizeof(saltParam), mozilla::fallible);
423 keyHandleBuf.AppendElements(saltParam, sizeof(saltParam), mozilla::fallible);
424 keyHandleBuf.AppendSECItem(wrappedKey.get());
425
426 UniqueSECItem keyHandle(::SECITEM_AllocItem(nullptr, nullptr, 0));
427 if (NS_WARN_IF(!keyHandle)) {
428 MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
429 return nullptr;
430 }
431
432 if (NS_WARN_IF(!keyHandleBuf.ToSECItem(/* default arena */ nullptr,
433 keyHandle.get()))) {
434 MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
435 return nullptr;
436 }
437 return keyHandle;
438 }
439
440 // Convert an opaque key handle aKeyHandle back into a Private Key object, using
441 // the long-lived aPersistentKey mixed with aAppParam and the AES Key Wrap
442 // algorithm.
PrivateKeyFromKeyHandle(const UniquePK11SlotInfo & aSlot,const UniquePK11SymKey & aPersistentKey,uint8_t * aKeyHandle,uint32_t aKeyHandleLen,uint8_t * aAppParam,uint32_t aAppParamLen)443 static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(
444 const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey,
445 uint8_t* aKeyHandle, uint32_t aKeyHandleLen, uint8_t* aAppParam,
446 uint32_t aAppParamLen) {
447 MOZ_ASSERT(aSlot);
448 MOZ_ASSERT(aPersistentKey);
449 MOZ_ASSERT(aKeyHandle);
450 MOZ_ASSERT(aAppParam);
451 MOZ_ASSERT(aAppParamLen == SHA256_LENGTH);
452 if (NS_WARN_IF(!aSlot || !aPersistentKey || !aKeyHandle || !aAppParam ||
453 aAppParamLen != SHA256_LENGTH)) {
454 return nullptr;
455 }
456
457 // As we only support one key format ourselves (right now), fail early if
458 // we aren't that length
459 if (NS_WARN_IF(aKeyHandleLen != kVersion1KeyHandleLen)) {
460 return nullptr;
461 }
462
463 if (NS_WARN_IF(aKeyHandle[0] != SoftTokenHandle::Version1)) {
464 // Unrecognized version
465 return nullptr;
466 }
467
468 uint8_t saltLen = aKeyHandle[1];
469 uint8_t* saltPtr = aKeyHandle + 2;
470 if (NS_WARN_IF(saltLen != kSaltByteLen)) {
471 return nullptr;
472 }
473
474 // Prepare the HKDF (https://tools.ietf.org/html/rfc5869)
475 CK_NSS_HKDFParams hkdfParams = {true, saltPtr, saltLen,
476 true, aAppParam, aAppParamLen};
477 SECItem kdfParams = {siBuffer, (unsigned char*)&hkdfParams,
478 sizeof(hkdfParams)};
479
480 // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam.
481 // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the
482 // derived symmetric key and don't matter because we ignore them anyway.
483 UniquePK11SymKey wrapKey(
484 PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams,
485 CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen));
486 if (NS_WARN_IF(!wrapKey.get())) {
487 MOZ_LOG(
488 gNSSTokenLog, LogLevel::Warning,
489 ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
490 return nullptr;
491 }
492
493 uint8_t wrappedLen = aKeyHandleLen - saltLen - 2;
494 uint8_t* wrappedPtr = aKeyHandle + saltLen + 2;
495
496 ScopedAutoSECItem wrappedKeyItem(wrappedLen);
497 memcpy(wrappedKeyItem.data, wrappedPtr, wrappedKeyItem.len);
498
499 ScopedAutoSECItem pubKey(kPublicKeyLen);
500
501 UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
502 /* default IV */ nullptr));
503
504 CK_ATTRIBUTE_TYPE usages[] = {CKA_SIGN};
505 int usageCount = 1;
506
507 UniqueSECKEYPrivateKey unwrappedKey(
508 PK11_UnwrapPrivKey(aSlot.get(), wrapKey.get(), CKM_NSS_AES_KEY_WRAP_PAD,
509 param.get(), &wrappedKeyItem,
510 /* no nickname */ nullptr,
511 /* discard pubkey */ &pubKey,
512 /* not permanent */ false,
513 /* non-exportable */ true, CKK_EC, usages, usageCount,
514 /* wincx */ nullptr));
515 if (NS_WARN_IF(!unwrappedKey)) {
516 // Not our key.
517 MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
518 ("Could not unwrap key handle, NSS Error #%d", PORT_GetError()));
519 return nullptr;
520 }
521
522 return unwrappedKey;
523 }
524
525 // IsRegistered determines if the provided key handle is usable by this token.
IsRegistered(const nsTArray<uint8_t> & aKeyHandle,const nsTArray<uint8_t> & aAppParam,bool & aResult)526 nsresult U2FSoftTokenManager::IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
527 const nsTArray<uint8_t>& aAppParam,
528 bool& aResult) {
529 if (!mInitialized) {
530 nsresult rv = Init();
531 if (NS_WARN_IF(NS_FAILED(rv))) {
532 return rv;
533 }
534 }
535
536 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
537 MOZ_ASSERT(slot.get());
538
539 // Decode the key handle
540 UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(
541 slot, mWrappingKey, const_cast<uint8_t*>(aKeyHandle.Elements()),
542 aKeyHandle.Length(), const_cast<uint8_t*>(aAppParam.Elements()),
543 aAppParam.Length());
544 aResult = privKey.get() != nullptr;
545 return NS_OK;
546 }
547
548 // A U2F Register operation causes a new key pair to be generated by the token.
549 // The token then returns the public key of the key pair, and a handle to the
550 // private key, which is a fancy way of saying "key wrapped private key", as
551 // well as the generated attestation certificate and a signature using that
552 // certificate's private key.
553 //
554 // The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
555 // the actual key wrap/unwrap operations.
556 //
557 // The format of the return registration data is as follows:
558 //
559 // Bytes Value
560 // 1 0x05
561 // 65 public key
562 // 1 key handle length
563 // * key handle
564 // ASN.1 attestation certificate
565 // * attestation signature
566 //
Register(const WebAuthnMakeCredentialInfo & aInfo)567 RefPtr<U2FRegisterPromise> U2FSoftTokenManager::Register(
568 const WebAuthnMakeCredentialInfo& aInfo) {
569 if (!mInitialized) {
570 nsresult rv = Init();
571 if (NS_WARN_IF(NS_FAILED(rv))) {
572 return U2FRegisterPromise::CreateAndReject(rv, __func__);
573 }
574 }
575
576 const WebAuthnAuthenticatorSelection& sel = aInfo.AuthenticatorSelection();
577
578 // The U2F softtoken neither supports resident keys or
579 // user verification, nor is it a platform authenticator.
580 if (sel.requireResidentKey() || sel.requireUserVerification() ||
581 sel.requirePlatformAttachment()) {
582 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR,
583 __func__);
584 }
585
586 // Optional exclusion list.
587 for (const WebAuthnScopedCredential& cred : aInfo.ExcludeList()) {
588 bool isRegistered = false;
589 nsresult rv = IsRegistered(cred.id(), aInfo.RpIdHash(), isRegistered);
590 if (NS_FAILED(rv)) {
591 return U2FRegisterPromise::CreateAndReject(rv, __func__);
592 }
593 if (isRegistered) {
594 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
595 __func__);
596 }
597 }
598
599 // We should already have a wrapping key
600 MOZ_ASSERT(mWrappingKey);
601
602 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
603 MOZ_ASSERT(slot.get());
604
605 // Construct a one-time-use Attestation Certificate
606 UniqueSECKEYPrivateKey attestPrivKey;
607 UniqueCERTCertificate attestCert;
608 nsresult rv = GetAttestationCertificate(slot, attestPrivKey, attestCert);
609 if (NS_WARN_IF(NS_FAILED(rv))) {
610 return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
611 }
612 MOZ_ASSERT(attestCert);
613 MOZ_ASSERT(attestPrivKey);
614
615 // Generate a new keypair; the private will be wrapped into a Key Handle
616 UniqueSECKEYPrivateKey privKey;
617 UniqueSECKEYPublicKey pubKey;
618 rv = GenEcKeypair(slot, privKey, pubKey);
619 if (NS_WARN_IF(NS_FAILED(rv))) {
620 return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
621 }
622
623 // The key handle will be the result of keywrap(privKey, key=mWrappingKey)
624 UniqueSECItem keyHandleItem = KeyHandleFromPrivateKey(
625 slot, mWrappingKey, const_cast<uint8_t*>(aInfo.RpIdHash().Elements()),
626 aInfo.RpIdHash().Length(), privKey);
627 if (NS_WARN_IF(!keyHandleItem.get())) {
628 return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
629 }
630
631 // Sign the challenge using the Attestation privkey (from attestCert)
632 mozilla::dom::CryptoBuffer signedDataBuf;
633 if (NS_WARN_IF(!signedDataBuf.SetCapacity(
634 1 + aInfo.RpIdHash().Length() + aInfo.ClientDataHash().Length() +
635 keyHandleItem->len + kPublicKeyLen,
636 mozilla::fallible))) {
637 return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
638 __func__);
639 }
640
641 // // It's OK to ignore the return values here because we're writing into
642 // // pre-allocated space
643 signedDataBuf.AppendElement(0x00, mozilla::fallible);
644 signedDataBuf.AppendElements(aInfo.RpIdHash(), mozilla::fallible);
645 signedDataBuf.AppendElements(aInfo.ClientDataHash(), mozilla::fallible);
646 signedDataBuf.AppendSECItem(keyHandleItem.get());
647 signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue);
648
649 ScopedAutoSECItem signatureItem;
650 SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
651 signedDataBuf.Length(), attestPrivKey.get(),
652 SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
653 if (NS_WARN_IF(srv != SECSuccess)) {
654 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
655 ("Signature failure: %d", PORT_GetError()));
656 return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
657 }
658
659 // Serialize the registration data
660 mozilla::dom::CryptoBuffer registrationBuf;
661 if (NS_WARN_IF(!registrationBuf.SetCapacity(
662 1 + kPublicKeyLen + 1 + keyHandleItem->len +
663 attestCert.get()->derCert.len + signatureItem.len,
664 mozilla::fallible))) {
665 return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
666 __func__);
667 }
668 registrationBuf.AppendElement(0x05, mozilla::fallible);
669 registrationBuf.AppendSECItem(pubKey->u.ec.publicValue);
670 registrationBuf.AppendElement(keyHandleItem->len, mozilla::fallible);
671 registrationBuf.AppendSECItem(keyHandleItem.get());
672 registrationBuf.AppendSECItem(attestCert.get()->derCert);
673 registrationBuf.AppendSECItem(signatureItem);
674
675 // Will be set by the U2FTokenManager.
676 bool directAttestationPermitted = false;
677 WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)),
678 directAttestationPermitted);
679 return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
680 }
681
FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>> & aAppIds,const nsTArray<WebAuthnScopedCredential> & aCredentials,nsTArray<uint8_t> & aKeyHandle,nsTArray<uint8_t> & aAppId)682 bool U2FSoftTokenManager::FindRegisteredKeyHandle(
683 const nsTArray<nsTArray<uint8_t>>& aAppIds,
684 const nsTArray<WebAuthnScopedCredential>& aCredentials,
685 /*out*/ nsTArray<uint8_t>& aKeyHandle,
686 /*out*/ nsTArray<uint8_t>& aAppId) {
687 for (const nsTArray<uint8_t>& app_id : aAppIds) {
688 for (const WebAuthnScopedCredential& cred : aCredentials) {
689 bool isRegistered = false;
690 nsresult rv = IsRegistered(cred.id(), app_id, isRegistered);
691 if (NS_SUCCEEDED(rv) && isRegistered) {
692 aKeyHandle.Assign(cred.id());
693 aAppId.Assign(app_id);
694 return true;
695 }
696 }
697 }
698
699 return false;
700 }
701
702 // A U2F Sign operation creates a signature over the "param" arguments (plus
703 // some other stuff) using the private key indicated in the key handle argument.
704 //
705 // The format of the signed data is as follows:
706 //
707 // 32 Application parameter
708 // 1 User presence (0x01)
709 // 4 Counter
710 // 32 Challenge parameter
711 //
712 // The format of the signature data is as follows:
713 //
714 // 1 User presence
715 // 4 Counter
716 // * Signature
717 //
Sign(const WebAuthnGetAssertionInfo & aInfo)718 RefPtr<U2FSignPromise> U2FSoftTokenManager::Sign(
719 const WebAuthnGetAssertionInfo& aInfo) {
720 if (!mInitialized) {
721 nsresult rv = Init();
722 if (NS_WARN_IF(NS_FAILED(rv))) {
723 return U2FSignPromise::CreateAndReject(rv, __func__);
724 }
725 }
726
727 // The U2F softtoken doesn't support user verification.
728 if (aInfo.RequireUserVerification()) {
729 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR,
730 __func__);
731 }
732
733 nsTArray<nsTArray<uint8_t>> appIds;
734 appIds.AppendElement(aInfo.RpIdHash());
735
736 // Process extensions.
737 for (const WebAuthnExtension& ext : aInfo.Extensions()) {
738 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
739 appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
740 }
741 }
742
743 nsTArray<uint8_t> chosenAppId;
744 nsTArray<uint8_t> keyHandle;
745
746 // Fail if we can't find a valid key handle.
747 if (!FindRegisteredKeyHandle(appIds, aInfo.AllowList(), keyHandle,
748 chosenAppId)) {
749 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
750 __func__);
751 }
752
753 MOZ_ASSERT(mWrappingKey);
754
755 UniquePK11SlotInfo slot(PK11_GetInternalSlot());
756 MOZ_ASSERT(slot.get());
757
758 if (NS_WARN_IF((aInfo.ClientDataHash().Length() != kParamLen) ||
759 (chosenAppId.Length() != kParamLen))) {
760 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
761 ("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
762 (uint32_t)aInfo.ClientDataHash().Length(),
763 (uint32_t)chosenAppId.Length(), kParamLen));
764
765 return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
766 }
767
768 // Decode the key handle
769 UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(
770 slot, mWrappingKey, const_cast<uint8_t*>(keyHandle.Elements()),
771 keyHandle.Length(), const_cast<uint8_t*>(chosenAppId.Elements()),
772 chosenAppId.Length());
773 if (NS_WARN_IF(!privKey.get())) {
774 MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
775 return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
776 }
777
778 // Increment the counter and turn it into a SECItem
779 mCounter += 1;
780 ScopedAutoSECItem counterItem(4);
781 counterItem.data[0] = (mCounter >> 24) & 0xFF;
782 counterItem.data[1] = (mCounter >> 16) & 0xFF;
783 counterItem.data[2] = (mCounter >> 8) & 0xFF;
784 counterItem.data[3] = (mCounter >> 0) & 0xFF;
785 uint32_t counter = mCounter;
786 GetMainThreadEventTarget()->Dispatch(
787 NS_NewRunnableFunction("dom::U2FSoftTokenManager::Sign", [counter]() {
788 MOZ_ASSERT(NS_IsMainThread());
789 Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, counter);
790 }));
791
792 // Compute the signature
793 mozilla::dom::CryptoBuffer signedDataBuf;
794 if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLen),
795 mozilla::fallible))) {
796 return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
797 }
798
799 // It's OK to ignore the return values here because we're writing into
800 // pre-allocated space
801 signedDataBuf.AppendElements(chosenAppId.Elements(), chosenAppId.Length(),
802 mozilla::fallible);
803 signedDataBuf.AppendElement(0x01, mozilla::fallible);
804 signedDataBuf.AppendSECItem(counterItem);
805 signedDataBuf.AppendElements(aInfo.ClientDataHash().Elements(),
806 aInfo.ClientDataHash().Length(),
807 mozilla::fallible);
808
809 if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
810 nsAutoCString base64;
811 nsresult rv =
812 Base64URLEncode(signedDataBuf.Length(), signedDataBuf.Elements(),
813 Base64URLEncodePaddingPolicy::Omit, base64);
814 if (NS_WARN_IF(NS_FAILED(rv))) {
815 return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
816 }
817
818 MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
819 ("U2F Token signing bytes (base64): %s", base64.get()));
820 }
821
822 ScopedAutoSECItem signatureItem;
823 SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
824 signedDataBuf.Length(), privKey.get(),
825 SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
826 if (NS_WARN_IF(srv != SECSuccess)) {
827 MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
828 ("Signature failure: %d", PORT_GetError()));
829 return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
830 }
831
832 // Assemble the signature data into a buffer for return
833 mozilla::dom::CryptoBuffer signatureBuf;
834 if (NS_WARN_IF(!signatureBuf.SetCapacity(
835 1 + counterItem.len + signatureItem.len, mozilla::fallible))) {
836 return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
837 }
838
839 // It's OK to ignore the return values here because we're writing into
840 // pre-allocated space
841 signatureBuf.AppendElement(0x01, mozilla::fallible);
842 signatureBuf.AppendSECItem(counterItem);
843 signatureBuf.AppendSECItem(signatureItem);
844
845 nsTArray<uint8_t> signature(signatureBuf);
846 nsTArray<WebAuthnExtensionResult> extensions;
847
848 if (chosenAppId != aInfo.RpIdHash()) {
849 // Indicate to the RP that we used the FIDO appId.
850 extensions.AppendElement(WebAuthnExtensionResultAppId(true));
851 }
852
853 WebAuthnGetAssertionResult result(chosenAppId, keyHandle, signature,
854 extensions);
855 return U2FSignPromise::CreateAndResolve(Move(result), __func__);
856 }
857
Cancel()858 void U2FSoftTokenManager::Cancel() {
859 // This implementation is sync, requests can't be aborted.
860 }
861
862 } // namespace dom
863 } // namespace mozilla
864