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