LCOV - code coverage report
Current view: top level - dom/webauthn - U2FSoftTokenManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 6 391 1.5 %
Date: 2017-07-14 16:53:18 Functions: 0 19 0.0 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.13