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