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