diff options
Diffstat (limited to 'security/manager/ssl/nsNSSU2FToken.cpp')
-rw-r--r-- | security/manager/ssl/nsNSSU2FToken.cpp | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSU2FToken.cpp b/security/manager/ssl/nsNSSU2FToken.cpp new file mode 100644 index 000000000..f8492df02 --- /dev/null +++ b/security/manager/ssl/nsNSSU2FToken.cpp @@ -0,0 +1,752 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNSSU2FToken.h" + +#include "CryptoBuffer.h" +#include "mozilla/Casting.h" +#include "nsNSSComponent.h" +#include "pk11pub.h" +#include "prerror.h" +#include "secerr.h" +#include "WebCryptoCommon.h" + +using namespace mozilla; +using mozilla::dom::CreateECParamsForCurve; + +NS_IMPL_ISUPPORTS(nsNSSU2FToken, nsIU2FToken, nsINSSU2FToken) + +// Not named "security.webauth.u2f_softtoken_counter" because setting that +// name causes the window.u2f object to disappear until preferences get +// reloaded, as its' pref is a substring! +#define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter" + +const nsCString nsNSSU2FToken::mSecretNickname = + NS_LITERAL_CSTRING("U2F_NSSTOKEN"); +const nsString nsNSSU2FToken::mVersion = + NS_LITERAL_STRING("U2F_V2"); +NS_NAMED_LITERAL_CSTRING(kAttestCertSubjectName, "CN=Firefox U2F Soft Token"); + +// This U2F-compatible soft token uses FIDO U2F-compatible ECDSA keypairs +// on the SEC_OID_SECG_EC_SECP256R1 curve. When asked to Register, it will +// generate and return a new keypair KP, where the private component is wrapped +// using AES-KW with the 128-bit mWrappingKey to make an opaque "key handle". +// In other words, Register yields { KP_pub, AES-KW(KP_priv, key=mWrappingKey) } +// +// The value mWrappingKey is long-lived; it is persisted as part of the NSS DB +// for the current profile. The attestation certificates that are produced are +// ephemeral to counteract profiling. They have little use for a soft-token +// at any rate, but are required by the specification. + +const uint32_t kParamLen = 32; +const uint32_t kPublicKeyLen = 65; +const uint32_t kWrappedKeyBufLen = 256; +const uint32_t kWrappingKeyByteLen = 128/8; +NS_NAMED_LITERAL_STRING(kEcAlgorithm, WEBCRYPTO_NAMED_CURVE_P256); + +const PRTime kOneDay = PRTime(PR_USEC_PER_SEC) + * PRTime(60) // sec + * PRTime(60) // min + * PRTime(24); // hours +const PRTime kExpirationSlack = kOneDay; // Pre-date for clock skew +const PRTime kExpirationLife = kOneDay; + +static mozilla::LazyLogModule gNSSTokenLog("webauth_u2f"); + +nsNSSU2FToken::nsNSSU2FToken() + : mInitialized(false) +{} + +nsNSSU2FToken::~nsNSSU2FToken() +{ + nsNSSShutDownPreventionLock locker; + + if (isAlreadyShutDown()) { + return; + } + + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +void +nsNSSU2FToken::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void +nsNSSU2FToken::destructorSafeDestroyNSSReference() +{ + mWrappingKey = nullptr; +} + +/** + * 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; +} + +static nsresult +GenEcKeypair(const UniquePK11SlotInfo& aSlot, + /*out*/ UniqueSECKEYPrivateKey& aPrivKey, + /*out*/ UniqueSECKEYPublicKey& aPubKey, + const nsNSSShutDownPreventionLock&) +{ + MOZ_ASSERT(aSlot); + if (!aSlot) { + return NS_ERROR_INVALID_ARG; + } + + UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + 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 (!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 (!aPrivKey.get() || !aPubKey.get()) { + return NS_ERROR_FAILURE; + } + + // Check that the public key has the correct length + if (aPubKey->u.ec.publicValue.len != kPublicKeyLen) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsNSSU2FToken::GetOrCreateWrappingKey(const UniquePK11SlotInfo& aSlot, + const nsNSSShutDownPreventionLock& locker) +{ + MOZ_ASSERT(aSlot); + 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 (!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 (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())); + + Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, 0); + return NS_OK; +} + +static nsresult +GetAttestationCertificate(const UniquePK11SlotInfo& aSlot, + /*out*/ UniqueSECKEYPrivateKey& aAttestPrivKey, + /*out*/ UniqueCERTCertificate& aAttestCert, + const nsNSSShutDownPreventionLock& locker) +{ + MOZ_ASSERT(aSlot); + 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_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 (!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 (!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 (!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 (!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 (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 (!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 (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 (!SEC_ASN1EncodeItem(arena, &innerDER, aAttestCert.get(), + SEC_ASN1_GET(CERT_CertificateTemplate))) { + return NS_ERROR_FAILURE; + } + + SECItem* signedCert = PORT_ArenaZNew(arena, SECItem); + if (!signedCert) { + return NS_ERROR_FAILURE; + } + + srv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len, + aAttestPrivKey.get(), + SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE); + if (srv != SECSuccess) { + return NS_ERROR_FAILURE; + } + aAttestCert->derCert = *signedCert; + + MOZ_LOG(gNSSTokenLog, LogLevel::Debug, + ("U2F Soft Token attestation certificate generated.")); + return NS_OK; +} + +// Set up the context for the soft U2F Token. This is called by NSS +// initialization. +NS_IMETHODIMP +nsNSSU2FToken::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mInitialized); + if (mInitialized) { + return NS_ERROR_FAILURE; + } + + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); + MOZ_ASSERT(slot.get()); + + // Search for an existing wrapping key, or create one. + nsresult rv = GetOrCreateWrappingKey(slot, locker); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInitialized = true; + MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token initialized.")); + return NS_OK; +} + +// 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 +// 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; +} + +// Return whether the provided version is supported by this token. +NS_IMETHODIMP +nsNSSU2FToken::IsCompatibleVersion(const nsAString& aVersion, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + MOZ_ASSERT(mInitialized); + *aResult = (mVersion == aVersion); + return NS_OK; +} + +// IsRegistered determines if the provided key handle is usable by this token. +NS_IMETHODIMP +nsNSSU2FToken::IsRegistered(uint8_t* aKeyHandle, uint32_t aKeyHandleLen, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aKeyHandle); + NS_ENSURE_ARG_POINTER(aResult); + + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSU2FToken::IsRegistered called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mInitialized); + if (!mInitialized) { + return NS_ERROR_FAILURE; + } + + UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + // Decode the key handle + UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey, + aKeyHandle, + aKeyHandleLen, + locker); + *aResult = (privKey.get() != nullptr); + return NS_OK; +} + +// A U2F Register operation causes a new key pair to be generated by the token. +// The token then returns the public key of the key pair, and a handle to the +// private key, which is a fancy way of saying "key wrapped private key", as +// well as the generated attestation certificate and a signature using that +// certificate's private key. +// +// The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform +// the actual key wrap/unwrap operations. +// +// The format of the return registration data is as follows: +// +// Bytes Value +// 1 0x05 +// 65 public key +// 1 key handle length +// * key handle +// ASN.1 attestation certificate +// * attestation signature +// +NS_IMETHODIMP +nsNSSU2FToken::Register(uint8_t* aApplication, + uint32_t aApplicationLen, + uint8_t* aChallenge, + uint32_t aChallengeLen, + uint8_t** aRegistration, + uint32_t* aRegistrationLen) +{ + NS_ENSURE_ARG_POINTER(aApplication); + NS_ENSURE_ARG_POINTER(aChallenge); + NS_ENSURE_ARG_POINTER(aRegistration); + NS_ENSURE_ARG_POINTER(aRegistrationLen); + + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSU2FToken::Register called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + MOZ_ASSERT(mInitialized); + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + // We should already have a wrapping key + MOZ_ASSERT(mWrappingKey); + + UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + // Construct a one-time-use Attestation Certificate + UniqueSECKEYPrivateKey attestPrivKey; + UniqueCERTCertificate attestCert; + nsresult rv = GetAttestationCertificate(slot, attestPrivKey, attestCert, + locker); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(attestCert); + MOZ_ASSERT(attestPrivKey); + + // Generate a new keypair; the private will be wrapped into a Key Handle + UniqueSECKEYPrivateKey privKey; + UniqueSECKEYPublicKey pubKey; + rv = GenEcKeypair(slot, privKey, pubKey, locker); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + // The key handle will be the result of keywrap(privKey, key=mWrappingKey) + UniqueSECItem keyHandleItem = KeyHandleFromPrivateKey(slot, mWrappingKey, + privKey, locker); + if (!keyHandleItem.get()) { + return NS_ERROR_FAILURE; + } + + // Sign the challenge using the Attestation privkey (from attestCert) + mozilla::dom::CryptoBuffer signedDataBuf; + if (!signedDataBuf.SetCapacity(1 + aApplicationLen + aChallengeLen + + keyHandleItem->len + kPublicKeyLen, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // It's OK to ignore the return values here because we're writing into + // pre-allocated space + signedDataBuf.AppendElement(0x00, mozilla::fallible); + signedDataBuf.AppendElements(aApplication, aApplicationLen, mozilla::fallible); + signedDataBuf.AppendElements(aChallenge, aChallengeLen, mozilla::fallible); + signedDataBuf.AppendSECItem(keyHandleItem.get()); + signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue); + + ScopedAutoSECItem signatureItem; + SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(), + signedDataBuf.Length(), attestPrivKey.get(), + SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE); + if (srv != SECSuccess) { + MOZ_LOG(gNSSTokenLog, LogLevel::Warning, + ("Signature failure: %d", PORT_GetError())); + return NS_ERROR_FAILURE; + } + + // Serialize the registration data + mozilla::dom::CryptoBuffer registrationBuf; + if (!registrationBuf.SetCapacity(1 + kPublicKeyLen + 1 + keyHandleItem->len + + attestCert.get()->derCert.len + + signatureItem.len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + registrationBuf.AppendElement(0x05, mozilla::fallible); + registrationBuf.AppendSECItem(pubKey->u.ec.publicValue); + registrationBuf.AppendElement(keyHandleItem->len, mozilla::fallible); + registrationBuf.AppendSECItem(keyHandleItem.get()); + registrationBuf.AppendSECItem(attestCert.get()->derCert); + registrationBuf.AppendSECItem(signatureItem); + if (!registrationBuf.ToNewUnsignedBuffer(aRegistration, aRegistrationLen)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +// A U2F Sign operation creates a signature over the "param" arguments (plus +// some other stuff) using the private key indicated in the key handle argument. +// +// The format of the signed data is as follows: +// +// 32 Application parameter +// 1 User presence (0x01) +// 4 Counter +// 32 Challenge parameter +// +// The format of the signature data is as follows: +// +// 1 User presence +// 4 Counter +// * Signature +// +NS_IMETHODIMP +nsNSSU2FToken::Sign(uint8_t* aApplication, uint32_t aApplicationLen, + uint8_t* aChallenge, uint32_t aChallengeLen, + uint8_t* aKeyHandle, uint32_t aKeyHandleLen, + uint8_t** aSignature, uint32_t* aSignatureLen) +{ + NS_ENSURE_ARG_POINTER(aApplication); + NS_ENSURE_ARG_POINTER(aChallenge); + NS_ENSURE_ARG_POINTER(aKeyHandle); + NS_ENSURE_ARG_POINTER(aKeyHandleLen); + NS_ENSURE_ARG_POINTER(aSignature); + NS_ENSURE_ARG_POINTER(aSignatureLen); + + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSU2FToken::Sign called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + MOZ_ASSERT(mInitialized); + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + MOZ_ASSERT(mWrappingKey); + + UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + if ((aChallengeLen != kParamLen) || (aApplicationLen != kParamLen)) { + MOZ_LOG(gNSSTokenLog, LogLevel::Warning, + ("Parameter lengths are wrong! challenge=%d app=%d expected=%d", + aChallengeLen, aApplicationLen, kParamLen)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + // Decode the key handle + UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey, + aKeyHandle, + aKeyHandleLen, + locker); + if (!privKey.get()) { + MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!")); + return NS_ERROR_FAILURE; + } + + // Increment the counter and turn it into a SECItem + uint32_t counter = Preferences::GetUint(PREF_U2F_NSSTOKEN_COUNTER) + 1; + Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, counter); + ScopedAutoSECItem counterItem(4); + counterItem.data[0] = (counter >> 24) & 0xFF; + counterItem.data[1] = (counter >> 16) & 0xFF; + counterItem.data[2] = (counter >> 8) & 0xFF; + counterItem.data[3] = (counter >> 0) & 0xFF; + + // Compute the signature + mozilla::dom::CryptoBuffer signedDataBuf; + if (!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLen), mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // It's OK to ignore the return values here because we're writing into + // pre-allocated space + signedDataBuf.AppendElements(aApplication, aApplicationLen, mozilla::fallible); + signedDataBuf.AppendElement(0x01, mozilla::fallible); + signedDataBuf.AppendSECItem(counterItem); + signedDataBuf.AppendElements(aChallenge, aChallengeLen, mozilla::fallible); + + ScopedAutoSECItem signatureItem; + SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(), + signedDataBuf.Length(), privKey.get(), + SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE); + if (srv != SECSuccess) { + MOZ_LOG(gNSSTokenLog, LogLevel::Warning, + ("Signature failure: %d", PORT_GetError())); + return NS_ERROR_FAILURE; + } + + // Assemble the signature data into a buffer for return + mozilla::dom::CryptoBuffer signatureBuf; + if (!signatureBuf.SetCapacity(1 + counterItem.len + signatureItem.len, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // It's OK to ignore the return values here because we're writing into + // pre-allocated space + signatureBuf.AppendElement(0x01, mozilla::fallible); + signatureBuf.AppendSECItem(counterItem); + signatureBuf.AppendSECItem(signatureItem); + + if (!signatureBuf.ToNewUnsignedBuffer(aSignature, aSignatureLen)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} |