/** * Gets the first key with the given nickname from the given slot. Any other * keys found are not returned. * PK11_GetNextSymKey() should not be called on the returned key. * * @param aSlot Slot to search. * @param aNickname Nickname the key should have. * @return The first key found. nullptr if no key could be found. */ static UniquePK11SymKey GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot, const nsCString& aNickname, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); if (!aSlot) { return nullptr; } MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Searching for a symmetric key named %s", aNickname.get())); UniquePK11SymKey keyListHead( PK11_ListFixedKeysInSlot(aSlot.get(), const_cast<char*>(aNickname.get()), /* wincx */ nullptr)); if (!keyListHead) { MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key not found.")); return nullptr; } // Sanity check PK11_ListFixedKeysInSlot() only returns keys with the correct // nickname. MOZ_ASSERT(aNickname == UniquePORTString(PK11_GetSymKeyNickname(keyListHead.get())).get()); MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key found!")); // Free any remaining keys in the key list. UniquePK11SymKey freeKey(PK11_GetNextSymKey(keyListHead.get())); while (freeKey) { freeKey = UniquePK11SymKey(PK11_GetNextSymKey(freeKey.get())); } return keyListHead; }
nsresult U2FSoftTokenManager::GetOrCreateWrappingKey(const UniquePK11SlotInfo& aSlot, const nsNSSShutDownPreventionLock& locker) { MOZ_ASSERT(aSlot); if (NS_WARN_IF(!aSlot)) { return NS_ERROR_INVALID_ARG; } // Search for an existing wrapping key. If we find it, // store it for later and mark ourselves initialized. mWrappingKey = GetSymKeyByNickname(aSlot, mSecretNickname, locker); if (mWrappingKey) { MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token Key found.")); mInitialized = true; return NS_OK; } MOZ_LOG(gNSSTokenLog, LogLevel::Info, ("No keys found. Generating new U2F Soft Token wrapping key.")); // We did not find an existing wrapping key, so we generate one in the // persistent database (e.g, Token). mWrappingKey = UniquePK11SymKey( PK11_TokenKeyGenWithFlags(aSlot.get(), CKM_AES_KEY_GEN, /* default params */ nullptr, kWrappingKeyByteLen, /* empty keyid */ nullptr, /* flags */ CKF_WRAP | CKF_UNWRAP, /* attributes */ PK11_ATTR_TOKEN | PK11_ATTR_PRIVATE, /* wincx */ nullptr)); if (NS_WARN_IF(!mWrappingKey)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to store wrapping key, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } SECStatus srv = PK11_SetSymKeyNickname(mWrappingKey.get(), mSecretNickname.get()); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to set nickname, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Key stored, nickname set to %s.", mSecretNickname.get())); AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction( [] () { MOZ_ASSERT(NS_IsMainThread()); Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, 0); })); return NS_OK; }
NS_IMETHODIMP nsPKCS11Module::FindSlotByName(const char16_t* aName, nsIPKCS11Slot** _retval) { // Note: It's OK for |aName| to be null. NS_ENSURE_ARG_POINTER(_retval); nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) return NS_ERROR_NOT_AVAILABLE; NS_ConvertUTF16toUTF8 asciiname(aName); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Getting \"%s\"\n", asciiname.get())); UniquePK11SlotInfo slotInfo; UniquePK11SlotList slotList(PK11_FindSlotsByNames(mModule->dllName, asciiname.get() /*slotName*/, nullptr /*tokenName*/, false)); if (!slotList) { /* name must be the token name */ slotList.reset(PK11_FindSlotsByNames(mModule->dllName, nullptr /*slotName*/, asciiname.get() /*tokenName*/, false)); } if (slotList && slotList->head && slotList->head->slot) { slotInfo.reset(PK11_ReferenceSlot(slotList->head->slot)); } if (!slotInfo) { // workaround - the builtin module has no name if (!asciiname.EqualsLiteral("Root Certificates")) { // Give up. return NS_ERROR_FAILURE; } slotInfo.reset(PK11_ReferenceSlot(mModule->slots[0])); } nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotInfo.get()); slot.forget(_retval); return NS_OK; }
NS_IMETHODIMP nsPKCS11Module::FindSlotByName(const nsACString& name, /*out*/ nsIPKCS11Slot** _retval) { NS_ENSURE_ARG_POINTER(_retval); nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) return NS_ERROR_NOT_AVAILABLE; const nsCString& flatName = PromiseFlatCString(name); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Getting \"%s\"", flatName.get())); UniquePK11SlotInfo slotInfo; UniquePK11SlotList slotList(PK11_FindSlotsByNames(mModule->dllName, flatName.get() /*slotName*/, nullptr /*tokenName*/, false)); if (!slotList) { /* name must be the token name */ slotList.reset(PK11_FindSlotsByNames(mModule->dllName, nullptr /*slotName*/, flatName.get() /*tokenName*/, false)); } if (slotList && slotList->head && slotList->head->slot) { slotInfo.reset(PK11_ReferenceSlot(slotList->head->slot)); } if (!slotInfo) { // workaround - the builtin module has no name if (!flatName.EqualsLiteral("Root Certificates")) { // Give up. return NS_ERROR_FAILURE; } slotInfo.reset(PK11_ReferenceSlot(mModule->slots[0])); } nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotInfo.get()); slot.forget(_retval); return NS_OK; }
static nsresult GenEcKeypair(const UniquePK11SlotInfo& aSlot, /*out*/ UniqueSECKEYPrivateKey& aPrivKey, /*out*/ UniqueSECKEYPublicKey& aPubKey, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); if (NS_WARN_IF(!aSlot)) { return NS_ERROR_INVALID_ARG; } UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (NS_WARN_IF(!arena)) { return NS_ERROR_OUT_OF_MEMORY; } // Set the curve parameters; keyParams belongs to the arena memory space SECItem* keyParams = CreateECParamsForCurve(kEcAlgorithm, arena.get()); if (NS_WARN_IF(!keyParams)) { return NS_ERROR_OUT_OF_MEMORY; } // Generate a key pair CK_MECHANISM_TYPE mechanism = CKM_EC_KEY_PAIR_GEN; SECKEYPublicKey* pubKeyRaw; aPrivKey = UniqueSECKEYPrivateKey( PK11_GenerateKeyPair(aSlot.get(), mechanism, keyParams, &pubKeyRaw, /* ephemeral */ false, false, /* wincx */ nullptr)); aPubKey = UniqueSECKEYPublicKey(pubKeyRaw); pubKeyRaw = nullptr; if (NS_WARN_IF(!aPrivKey.get() || !aPubKey.get())) { return NS_ERROR_FAILURE; } // Check that the public key has the correct length if (NS_WARN_IF(aPubKey->u.ec.publicValue.len != kPublicKeyLen)) { return NS_ERROR_FAILURE; } return NS_OK; }
// Convert an opaque key handle aKeyHandle back into a Private Key object, using // aWrappingKey and the AES Key Wrap algorithm. static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aWrappingKey, uint8_t* aKeyHandle, uint32_t aKeyHandleLen, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aWrappingKey); MOZ_ASSERT(aKeyHandle); if (!aSlot || !aWrappingKey || !aKeyHandle) { return nullptr; } ScopedAutoSECItem pubKey(kPublicKeyLen); ScopedAutoSECItem keyHandleItem(aKeyHandleLen); memcpy(keyHandleItem.data, aKeyHandle, keyHandleItem.len); UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); CK_ATTRIBUTE_TYPE usages[] = { CKA_SIGN }; int usageCount = 1; UniqueSECKEYPrivateKey unwrappedKey( PK11_UnwrapPrivKey(aSlot.get(), aWrappingKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), &keyHandleItem, /* no nickname */ nullptr, /* discard pubkey */ &pubKey, /* not permanent */ false, /* non-exportable */ true, CKK_EC, usages, usageCount, /* wincx */ nullptr)); if (!unwrappedKey) { // Not our key. MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Could not unwrap key handle, NSS Error #%d", PORT_GetError())); return nullptr; } return unwrappedKey; }
// Convert a Private Key object into an opaque key handle, using AES Key Wrap // and aWrappingKey to convert aPrivKey. static UniqueSECItem KeyHandleFromPrivateKey(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aWrappingKey, const UniqueSECKEYPrivateKey& aPrivKey, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aWrappingKey); MOZ_ASSERT(aPrivKey); if (!aSlot || !aWrappingKey || !aPrivKey) { return nullptr; } UniqueSECItem wrappedKey(SECITEM_AllocItem(/* default arena */ nullptr, /* no buffer */ nullptr, kWrappedKeyBufLen)); if (!wrappedKey) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory, NSS error #%d", PORT_GetError())); return nullptr; } UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); SECStatus srv = PK11_WrapPrivKey(aSlot.get(), aWrappingKey.get(), aPrivKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(), /* wincx */ nullptr); if (srv != SECSuccess) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to wrap U2F key, NSS error #%d", PORT_GetError())); return nullptr; } return wrappedKey; }
// Convert an opaque key handle aKeyHandle back into a Private Key object, using // the long-lived aPersistentKey mixed with aAppParam and the AES Key Wrap // algorithm. static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey, uint8_t* aKeyHandle, uint32_t aKeyHandleLen, uint8_t* aAppParam, uint32_t aAppParamLen, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aPersistentKey); MOZ_ASSERT(aKeyHandle); MOZ_ASSERT(aAppParam); MOZ_ASSERT(aAppParamLen == SHA256_LENGTH); if (NS_WARN_IF(!aSlot || !aPersistentKey || !aKeyHandle || !aAppParam || aAppParamLen != SHA256_LENGTH)) { return nullptr; } // As we only support one key format ourselves (right now), fail early if // we aren't that length if (NS_WARN_IF(aKeyHandleLen != kVersion1KeyHandleLen)) { return nullptr; } if (NS_WARN_IF(aKeyHandle[0] != SoftTokenHandle::Version1)) { // Unrecognized version return nullptr; } uint8_t saltLen = aKeyHandle[1]; uint8_t* saltPtr = aKeyHandle + 2; if (NS_WARN_IF(saltLen != kSaltByteLen)) { return nullptr; } // Prepare the HKDF (https://tools.ietf.org/html/rfc5869) CK_NSS_HKDFParams hkdfParams = { true, saltPtr, saltLen, true, aAppParam, aAppParamLen }; SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams, sizeof(hkdfParams) }; // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam. // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the // derived symmetric key and don't matter because we ignore them anyway. UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen)); if (NS_WARN_IF(!wrapKey.get())) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError())); return nullptr; } uint8_t wrappedLen = aKeyHandleLen - saltLen - 2; uint8_t* wrappedPtr = aKeyHandle + saltLen + 2; ScopedAutoSECItem wrappedKeyItem(wrappedLen); memcpy(wrappedKeyItem.data, wrappedPtr, wrappedKeyItem.len); ScopedAutoSECItem pubKey(kPublicKeyLen); UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); CK_ATTRIBUTE_TYPE usages[] = { CKA_SIGN }; int usageCount = 1; UniqueSECKEYPrivateKey unwrappedKey( PK11_UnwrapPrivKey(aSlot.get(), wrapKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), &wrappedKeyItem, /* no nickname */ nullptr, /* discard pubkey */ &pubKey, /* not permanent */ false, /* non-exportable */ true, CKK_EC, usages, usageCount, /* wincx */ nullptr)); if (NS_WARN_IF(!unwrappedKey)) { // Not our key. MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Could not unwrap key handle, NSS Error #%d", PORT_GetError())); return nullptr; } return unwrappedKey; }
// Convert a Private Key object into an opaque key handle, using AES Key Wrap // with the long-lived aPersistentKey mixed with aAppParam to convert aPrivKey. // The key handle's format is version || saltLen || salt || wrappedPrivateKey static UniqueSECItem KeyHandleFromPrivateKey(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey, uint8_t* aAppParam, uint32_t aAppParamLen, const UniqueSECKEYPrivateKey& aPrivKey, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aPersistentKey); MOZ_ASSERT(aAppParam); MOZ_ASSERT(aPrivKey); if (NS_WARN_IF(!aSlot || !aPersistentKey || !aPrivKey || !aAppParam)) { return nullptr; } // Generate a random salt uint8_t saltParam[kSaltByteLen]; SECStatus srv = PK11_GenerateRandomOnSlot(aSlot.get(), saltParam, sizeof(saltParam)); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to generate a salt, NSS error #%d", PORT_GetError())); return nullptr; } // Prepare the HKDF (https://tools.ietf.org/html/rfc5869) CK_NSS_HKDFParams hkdfParams = { true, saltParam, sizeof(saltParam), true, aAppParam, aAppParamLen }; SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams, sizeof(hkdfParams) }; // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam. // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the // derived symmetric key and don't matter because we ignore them anyway. UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen)); if (NS_WARN_IF(!wrapKey.get())) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError())); return nullptr; } UniqueSECItem wrappedKey(::SECITEM_AllocItem(/* default arena */ nullptr, /* no buffer */ nullptr, kWrappedKeyBufLen)); if (NS_WARN_IF(!wrappedKey)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); srv = PK11_WrapPrivKey(aSlot.get(), wrapKey.get(), aPrivKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(), /* wincx */ nullptr); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to wrap U2F key, NSS error #%d", PORT_GetError())); return nullptr; } // Concatenate the salt and the wrapped Private Key together mozilla::dom::CryptoBuffer keyHandleBuf; if (NS_WARN_IF(!keyHandleBuf.SetCapacity(wrappedKey.get()->len + sizeof(saltParam) + 2, mozilla::fallible))) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } // It's OK to ignore the return values here because we're writing into // pre-allocated space keyHandleBuf.AppendElement(SoftTokenHandle::Version1, mozilla::fallible); keyHandleBuf.AppendElement(sizeof(saltParam), mozilla::fallible); keyHandleBuf.AppendElements(saltParam, sizeof(saltParam), mozilla::fallible); keyHandleBuf.AppendSECItem(wrappedKey.get()); UniqueSECItem keyHandle(::SECITEM_AllocItem(nullptr, nullptr, 0)); if (NS_WARN_IF(!keyHandle)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } if (NS_WARN_IF(!keyHandleBuf.ToSECItem(/* default arena */ nullptr, keyHandle.get()))) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } return keyHandle; }
static nsresult GetAttestationCertificate(const UniquePK11SlotInfo& aSlot, /*out*/ UniqueSECKEYPrivateKey& aAttestPrivKey, /*out*/ UniqueCERTCertificate& aAttestCert, const nsNSSShutDownPreventionLock& locker) { MOZ_ASSERT(aSlot); if (NS_WARN_IF(!aSlot)) { return NS_ERROR_INVALID_ARG; } UniqueSECKEYPublicKey pubKey; // Construct an ephemeral keypair for this Attestation Certificate nsresult rv = GenEcKeypair(aSlot, aAttestPrivKey, pubKey, locker); if (NS_WARN_IF(NS_FAILED(rv) || !aAttestPrivKey || !pubKey)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to gen keypair, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } // Construct the Attestation Certificate itself UniqueCERTName subjectName(CERT_AsciiToName(kAttestCertSubjectName.get())); if (NS_WARN_IF(!subjectName)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to set subject name, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } UniqueCERTSubjectPublicKeyInfo spki( SECKEY_CreateSubjectPublicKeyInfo(pubKey.get())); if (NS_WARN_IF(!spki)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to set SPKI, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } UniqueCERTCertificateRequest certreq( CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr)); if (NS_WARN_IF(!certreq)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to gen CSR, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } PRTime now = PR_Now(); PRTime notBefore = now - kExpirationSlack; PRTime notAfter = now + kExpirationLife; UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter)); if (NS_WARN_IF(!validity)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to gen validity, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } unsigned long serial; unsigned char* serialBytes = mozilla::BitwiseCast<unsigned char*, unsigned long*>(&serial); SECStatus srv = PK11_GenerateRandomOnSlot(aSlot.get(), serialBytes, sizeof(serial)); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to gen serial, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } // Ensure that the most significant bit isn't set (which would // indicate a negative number, which isn't valid for serial // numbers). serialBytes[0] &= 0x7f; // Also ensure that the least significant bit on the most // significant byte is set (to prevent a leading zero byte, // which also wouldn't be valid). serialBytes[0] |= 0x01; aAttestCert = UniqueCERTCertificate( CERT_CreateCertificate(serial, subjectName.get(), validity.get(), certreq.get())); if (NS_WARN_IF(!aAttestCert)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to gen certificate, NSS error #%d", PORT_GetError())); return NS_ERROR_FAILURE; } PLArenaPool* arena = aAttestCert->arena; srv = SECOID_SetAlgorithmID(arena, &aAttestCert->signature, SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, /* wincx */ nullptr); if (NS_WARN_IF(srv != SECSuccess)) { return NS_ERROR_FAILURE; } // Set version to X509v3. *(aAttestCert->version.data) = SEC_CERTIFICATE_VERSION_3; aAttestCert->version.len = 1; SECItem innerDER = { siBuffer, nullptr, 0 }; if (NS_WARN_IF(!SEC_ASN1EncodeItem(arena, &innerDER, aAttestCert.get(), SEC_ASN1_GET(CERT_CertificateTemplate)))) { return NS_ERROR_FAILURE; } SECItem* signedCert = PORT_ArenaZNew(arena, SECItem); if (NS_WARN_IF(!signedCert)) { return NS_ERROR_FAILURE; } srv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len, aAttestPrivKey.get(), SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE); if (NS_WARN_IF(srv != SECSuccess)) { return NS_ERROR_FAILURE; } aAttestCert->derCert = *signedCert; MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token attestation certificate generated.")); return NS_OK; }