Source code

Revision control

Other Tools

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