LCOV - code coverage report
Current view: top level - security/manager/ssl - nsNSSU2FToken.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 5 385 1.3 %
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: 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             : }

Generated by: LCOV version 1.13