--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -362,24 +362,26 @@ PK11_GetInternalSlot
PK11_GetIVLength
PK11_GetKeyData
PK11_GetKeyGen
PK11_GetLowLevelKeyIDForPrivateKey
PK11_GetMechanism
PK11_GetMinimumPwdLength
PK11_GetModInfo
PK11_GetNextSafe
+PK11_GetNextSymKey
PK11_GetPadMechanism
PK11_GetPrivateKeyNickname
PK11_GetPrivateModulusLen
PK11_GetSlotID
PK11_GetSlotInfo
PK11_GetSlotName
PK11_GetSlotPWValues
PK11_GetSlotSeries
+PK11_GetSymKeyNickname
PK11_GetTokenInfo
PK11_GetTokenName
PK11_HashBuf
PK11_HasRootCerts
PK11_ImportCert
PK11_ImportCertForKey
PK11_ImportCRL
PK11_ImportDERPrivateKeyInfoAndReturnKey
@@ -395,16 +397,17 @@ PK11_IsLoggedIn
PK11_IsPresent
PK11_IsReadOnly
PK11_IsRemovable
PK11_KeyForCertExists
PK11_KeyGen
PK11_KeyGenWithTemplate
PK11_ListCerts
PK11_ListCertsInSlot
+PK11_ListFixedKeysInSlot
PK11_ListPrivateKeysInSlot
PK11_ListPrivKeysInSlot
PK11_LoadPrivKey
PK11_Logout
PK11_LogoutAll
PK11_MakeIDFromPubKey
PK11_MapSignKeyType
PK11_MechanismToAlgtag
@@ -424,19 +427,21 @@ PK11_PubWrapSymKey
PK11_RandomUpdate
PK11_ReadRawAttribute
PK11_ReferenceSlot
PK11_ResetToken
PK11SDR_Decrypt
PK11SDR_Encrypt
PK11_SetPasswordFunc
PK11_SetSlotPWValues
+PK11_SetSymKeyNickname
PK11_Sign
PK11_SignatureLen
PK11_SignWithMechanism
+PK11_TokenKeyGenWithFlags
PK11_UnwrapPrivKey
PK11_UnwrapSymKey
PK11_UpdateSlotAttribute
PK11_UserDisableSlot
PK11_UserEnableSlot
PK11_VerifyWithMechanism
PK11_WrapPrivKey
PK11_WrapSymKey
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -37,16 +37,23 @@ CryptoBuffer::Assign(const nsACString& a
uint8_t*
CryptoBuffer::Assign(const SECItem* aItem)
{
MOZ_ASSERT(aItem);
return Assign(aItem->data, aItem->len);
}
uint8_t*
+CryptoBuffer::Assign(const InfallibleTArray<uint8_t>& aData)
+{
+ return ReplaceElementsAt(0, Length(), aData.Elements(), aData.Length(),
+ fallible);
+}
+
+uint8_t*
CryptoBuffer::Assign(const ArrayBuffer& aData)
{
aData.ComputeLengthAndData();
return Assign(aData.Data(), aData.Length());
}
uint8_t*
CryptoBuffer::Assign(const ArrayBufferView& aData)
@@ -80,16 +87,29 @@ CryptoBuffer::Assign(const OwningArrayBu
}
// If your union is uninitialized, something's wrong
MOZ_ASSERT(false);
Clear();
return nullptr;
}
+uint8_t*
+CryptoBuffer::AppendSECItem(const SECItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+ return AppendElements(aItem->data, aItem->len, fallible);
+}
+
+uint8_t*
+CryptoBuffer::AppendSECItem(const SECItem& aItem)
+{
+ return AppendElements(aItem.data, aItem.len, fallible);
+}
+
// Helpers to encode/decode JWK's special flavor of Base64
// * No whitespace
// * No padding
// * URL-safe character set
nsresult
CryptoBuffer::FromJwkBase64(const nsString& aBase64)
{
NS_ConvertUTF16toUTF8 temp(aBase64);
@@ -166,16 +186,29 @@ CryptoBuffer::ToSECItem(PLArenaPool *aAr
}
JSObject*
CryptoBuffer::ToUint8Array(JSContext* aCx) const
{
return Uint8Array::Create(aCx, Length(), Elements());
}
+bool
+CryptoBuffer::ToNewUnsignedBuffer(uint8_t** buf, uint32_t* bufLen) const
+{
+ uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(Length()));
+ if (!tmp) {
+ return false;
+ }
+
+ memcpy(tmp, Elements(), Length());
+ *buf = tmp;
+ *bufLen = Length();
+ return true;
+}
// "BigInt" comes from the WebCrypto spec
// ("unsigned long" isn't very "big", of course)
// Likewise, the spec calls for big-endian ints
bool
CryptoBuffer::GetBigIntValue(unsigned long& aRetVal)
{
if (Length() > sizeof(aRetVal)) {
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -19,35 +19,40 @@ class OwningArrayBufferViewOrArrayBuffer
class CryptoBuffer : public FallibleTArray<uint8_t>
{
public:
uint8_t* Assign(const CryptoBuffer& aData);
uint8_t* Assign(const uint8_t* aData, uint32_t aLength);
uint8_t* Assign(const nsACString& aString);
uint8_t* Assign(const SECItem* aItem);
+ uint8_t* Assign(const InfallibleTArray<uint8_t>& aData);
uint8_t* Assign(const ArrayBuffer& aData);
uint8_t* Assign(const ArrayBufferView& aData);
uint8_t* Assign(const ArrayBufferViewOrArrayBuffer& aData);
uint8_t* Assign(const OwningArrayBufferViewOrArrayBuffer& aData);
+ uint8_t* AppendSECItem(const SECItem* aItem);
+ uint8_t* AppendSECItem(const SECItem& aItem);
+
template<typename T,
JSObject* UnwrapArray(JSObject*),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)>
uint8_t* Assign(const TypedArray_base<T, UnwrapArray,
GetLengthAndDataAndSharedness>& aArray)
{
aArray.ComputeLengthAndData();
return Assign(aArray.Data(), aArray.Length());
}
nsresult FromJwkBase64(const nsString& aBase64);
nsresult ToJwkBase64(nsString& aBase64);
bool ToSECItem(PLArenaPool* aArena, SECItem* aItem) const;
JSObject* ToUint8Array(JSContext* aCx) const;
+ bool ToNewUnsignedBuffer(uint8_t** buf, uint32_t* bufLen) const;
bool GetBigIntValue(unsigned long& aRetVal);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_CryptoBuffer_h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -134,16 +134,17 @@
#include "nsIFormProcessor.h"
#include "nsIGfxInfo.h"
#include "nsIIdleService.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"
#include "nsIMutable.h"
+#include "nsINSSU2FToken.h"
#include "nsIObserverService.h"
#include "nsIPresShell.h"
#include "nsIRemoteWindowContext.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsISiteSecurityService.h"
#include "nsISpellChecker.h"
#include "nsISupportsPrimitives.h"
@@ -4242,16 +4243,91 @@ ContentParent::RecvSetURITitle(const URI
nsCOMPtr<IHistory> history = services::GetHistoryService();
if (history) {
history->SetURITitle(ourURI, title);
}
return true;
}
bool
+ContentParent::RecvNSSU2FTokenIsCompatibleVersion(const nsString& version,
+ bool* isCompatible)
+{
+ nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+ if (NS_WARN_IF(!nssToken)) {
+ return false;
+ }
+
+ nsresult rv = nssToken->IsCompatibleVersion(version, isCompatible);
+ return NS_SUCCEEDED(rv);
+}
+
+bool
+ContentParent::RecvNSSU2FTokenIsRegistered(nsTArray<uint8_t>&& keyHandle,
+ bool* isValidKeyHandle)
+{
+ nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+ if (NS_WARN_IF(!nssToken)) {
+ return false;
+ }
+
+ nsresult rv = nssToken->IsRegistered(keyHandle.Elements(), keyHandle.Length(),
+ isValidKeyHandle);
+ return NS_SUCCEEDED(rv);
+}
+
+bool
+ContentParent::RecvNSSU2FTokenRegister(nsTArray<uint8_t>&& application,
+ nsTArray<uint8_t>&& challenge,
+ nsTArray<uint8_t>* registration)
+{
+ nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+ if (NS_WARN_IF(!nssToken)) {
+ return false;
+ }
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv = nssToken->Register(application.Elements(), application.Length(),
+ challenge.Elements(), challenge.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ registration->ReplaceElementsAt(0, registration->Length(), buffer, bufferlen);
+ free(buffer);
+ return NS_SUCCEEDED(rv);
+}
+
+bool
+ContentParent::RecvNSSU2FTokenSign(nsTArray<uint8_t>&& application,
+ nsTArray<uint8_t>&& challenge,
+ nsTArray<uint8_t>&& keyHandle,
+ nsTArray<uint8_t>* signature)
+{
+ nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+ if (NS_WARN_IF(!nssToken)) {
+ return false;
+ }
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv = nssToken->Sign(application.Elements(), application.Length(),
+ challenge.Elements(), challenge.Length(),
+ keyHandle.Elements(), keyHandle.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ signature->ReplaceElementsAt(0, signature->Length(), buffer, bufferlen);
+ free(buffer);
+ return NS_SUCCEEDED(rv);
+}
+
+bool
ContentParent::RecvGetSystemMemory(const uint64_t& aGetterId)
{
uint32_t memoryTotal = 0;
#if defined(XP_LINUX)
memoryTotal = mozilla::hal::GetTotalSystemMemoryLevel();
#endif
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -744,16 +744,31 @@ private:
virtual bool
RecvPBlobConstructor(PBlobParent* aActor,
const BlobConstructorParams& params) override;
virtual bool
DeallocPCrashReporterParent(PCrashReporterParent* crashreporter) override;
+ virtual bool RecvNSSU2FTokenIsCompatibleVersion(const nsString& version,
+ bool* isCompatible) override;
+
+ virtual bool RecvNSSU2FTokenIsRegistered(nsTArray<uint8_t>&& keyHandle,
+ bool* isValidKeyHandle) override;
+
+ virtual bool RecvNSSU2FTokenRegister(nsTArray<uint8_t>&& application,
+ nsTArray<uint8_t>&& challenge,
+ nsTArray<uint8_t>* registration) override;
+
+ virtual bool RecvNSSU2FTokenSign(nsTArray<uint8_t>&& application,
+ nsTArray<uint8_t>&& challenge,
+ nsTArray<uint8_t>&& keyHandle,
+ nsTArray<uint8_t>* signature) override;
+
virtual bool RecvIsSecureURI(const uint32_t& aType, const URIParams& aURI,
const uint32_t& aFlags, bool* aIsSecureURI) override;
virtual bool RecvAccumulateMixedContentHSTS(const URIParams& aURI,
const bool& aActive) override;
virtual bool DeallocPHalParent(PHalParent*) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -759,16 +759,60 @@ parent:
async PJavaScript();
async PRemoteSpellcheckEngine();
async PDeviceStorageRequest(DeviceStorageParams params);
sync PCrashReporter(NativeThreadId tid, uint32_t processType);
+ /**
+ * Is this token compatible with the provided version?
+ *
+ * |version| The offered version to test
+ * Returns |True| if the offered version is compatible
+ */
+ sync NSSU2FTokenIsCompatibleVersion(nsString version)
+ returns (bool result);
+
+ /**
+ * Return whether the provided KeyHandle belongs to this Token
+ *
+ * |keyHandle| Key Handle to evaluate.
+ * Returns |True| if the Key Handle is ours.
+ */
+ sync NSSU2FTokenIsRegistered(uint8_t[] keyHandle)
+ returns (bool isValidKeyHandle);
+
+ /**
+ * Generates a public/private keypair for the provided application
+ * and challenge, returning the pubkey, challenge response, and
+ * key handle in the registration data.
+ *
+ * |application| The FIDO Application data to associate with the key.
+ * |challenge| The Challenge to satisfy in the response.
+ * |registration| An array containing the pubkey, challenge response,
+ * and key handle.
+ */
+ sync NSSU2FTokenRegister(uint8_t[] application, uint8_t[] challenge)
+ returns (uint8_t[] registration);
+
+ /**
+ * Creates a signature over the "param" arguments using the private key
+ * provided in the key handle argument.
+ *
+ * |application| The FIDO Application data to associate with the key.
+ * |challenge| The Challenge to satisfy in the response.
+ * |keyHandle| The Key Handle opaque object to use.
+ * |signature| The resulting signature.
+ */
+ sync NSSU2FTokenSign(uint8_t[] application, uint8_t[] challenge,
+ uint8_t[] keyHandle)
+ returns (uint8_t[] signature);
+
async GetSystemMemory(uint64_t getterId);
sync IsSecureURI(uint32_t type, URIParams uri, uint32_t flags)
returns (bool isSecureURI);
async AccumulateMixedContentHSTS(URIParams uri, bool active);
sync GetLookAndFeelCache()
deleted file mode 100644
--- a/dom/u2f/NSSToken.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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 "NSSToken.h"
-
-#include "nsNSSComponent.h"
-#include "pk11pub.h"
-
-namespace mozilla {
-namespace dom {
-
-const nsString NSSToken::mVersion = NS_LITERAL_STRING("U2F_V2");
-
-const uint32_t kParamLen = 32;
-const uint32_t kPublicKeyLen = 65;
-const uint32_t kSignedDataLen = (2 * kParamLen) + 1 + 4;
-
-NSSToken::NSSToken()
- : mInitialized(false)
- , mMutex("NSSToken::mMutex")
-{}
-
-NSSToken::~NSSToken()
-{
- nsNSSShutDownPreventionLock locker;
-
- if (isAlreadyShutDown()) {
- return;
- }
-
- destructorSafeDestroyNSSReference();
- shutdown(calledFromObject);
-}
-
-void
-NSSToken::virtualDestroyNSSReference()
-{
- destructorSafeDestroyNSSReference();
-}
-
-void
-NSSToken::destructorSafeDestroyNSSReference()
-{
- mSlot = nullptr;
-}
-
-nsresult
-NSSToken::Init()
-{
- MOZ_ASSERT(!mInitialized);
- if (mInitialized) {
- return NS_OK;
- }
-
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- MutexAutoLock lock(mMutex);
-
- if (!EnsureNSSInitializedChromeOrContent()) {
- return NS_ERROR_FAILURE;
- }
-
- mSlot = PK11_GetInternalSlot();
- if (!mSlot.get()) {
- return NS_ERROR_FAILURE;
- }
-
- mInitialized = true;
- return NS_OK;
-}
-
-bool
-NSSToken::IsCompatibleVersion(const nsString& aVersionParam) const
-{
- MOZ_ASSERT(mInitialized);
- return mVersion == aVersionParam;
-}
-
-/*
- * IsRegistered determines if the provided key handle is usable by this token.
- */
-bool
-NSSToken::IsRegistered(const CryptoBuffer& aKeyHandle) const
-{
- MOZ_ASSERT(mInitialized);
- return false;
-}
-
-/*
- * 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. The input parameters are used only for attestation, which this
- * token does not provide. (We'll see how that works!)
- *
- * The format of the return registration data is as follows:
- *
- * Bytes Value
- * 1 0x05
- * 65 public key
- * 1 key handle length
- * * key handle
- * * attestation certificate (omitted for now)
- * * attestation signature (omitted for now)
- *
- */
-nsresult
-NSSToken::Register(const CryptoBuffer& /* aChallengeParam */,
- const CryptoBuffer& /* aApplicationParam */,
- CryptoBuffer& aRegistrationData)
-{
- MOZ_ASSERT(mInitialized);
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- MutexAutoLock lock(mMutex);
-
- if (!mInitialized) {
- return NS_ERROR_NOT_INITIALIZED;
- }
-
- 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
- *
- */
-nsresult
-NSSToken::Sign(const CryptoBuffer& aApplicationParam,
- const CryptoBuffer& aChallengeParam,
- const CryptoBuffer& aKeyHandle,
- CryptoBuffer& aSignatureData)
-{
- MOZ_ASSERT(mInitialized);
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- MutexAutoLock lock(mMutex);
-
- if (!mInitialized) {
- return NS_ERROR_NOT_INITIALIZED;
- }
-
- return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/u2f/NSSToken.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-#ifndef mozilla_dom_NSSToken_h
-#define mozilla_dom_NSSToken_h
-
-#include "mozilla/dom/CryptoBuffer.h"
-#include "mozilla/Mutex.h"
-#include "nsNSSShutDown.h"
-#include "ScopedNSSTypes.h"
-
-namespace mozilla {
-namespace dom {
-
-// NSSToken will support FIDO U2F operations using NSS for the crypto layer.
-// This is a stub. It will be implemented in bug 1244960.
-class NSSToken final : public nsNSSShutDownObject
-{
-public:
- NSSToken();
-
- ~NSSToken();
-
- nsresult Init();
-
- bool IsCompatibleVersion(const nsString& aVersionParam) const;
-
- bool IsRegistered(const CryptoBuffer& aKeyHandle) const;
-
- nsresult Register(const CryptoBuffer& aApplicationParam,
- const CryptoBuffer& aChallengeParam,
- CryptoBuffer& aRegistrationData);
-
- nsresult Sign(const CryptoBuffer& aApplicationParam,
- const CryptoBuffer& aChallengeParam,
- const CryptoBuffer& aKeyHandle,
- CryptoBuffer& aSignatureData);
-
- // For nsNSSShutDownObject
- virtual void virtualDestroyNSSReference() override;
- void destructorSafeDestroyNSSReference();
-
-private:
- bool mInitialized;
- ScopedPK11SlotInfo mSlot;
- mozilla::Mutex mMutex;
-
- static const nsString mVersion;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_NSSToken_h
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -1,58 +1,65 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "hasht.h"
+#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CryptoBuffer.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/U2FBinding.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
#include "nsIEffectiveTLDService.h"
+#include "nsNetCID.h"
+#include "nsNSSComponent.h"
#include "nsURLParsers.h"
-#include "nsNetCID.h"
#include "pk11pub.h"
+using mozilla::dom::ContentChild;
+
namespace mozilla {
namespace dom {
// These enumerations are defined in the FIDO U2F Javascript API under the
// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
// Any changes to these must occur in both locations.
enum class ErrorCode {
OK = 0,
OTHER_ERROR = 1,
BAD_REQUEST = 2,
CONFIGURATION_UNSUPPORTED = 3,
DEVICE_INELIGIBLE = 4,
TIMEOUT = 5
};
-#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f.softtoken"
-#define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f.usbtoken"
+#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
+#define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f_enable_usbtoken"
-const nsString
-U2F::FinishEnrollment = NS_LITERAL_STRING("navigator.id.finishEnrollment");
+const nsString U2F::FinishEnrollment =
+ NS_LITERAL_STRING("navigator.id.finishEnrollment");
-const nsString
-U2F::GetAssertion = NS_LITERAL_STRING("navigator.id.getAssertion");
+const nsString U2F::GetAssertion =
+ NS_LITERAL_STRING("navigator.id.getAssertion");
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
+static mozilla::LazyLogModule gU2FLog("fido_u2f");
+
U2F::U2F()
{}
U2F::~U2F()
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
@@ -83,31 +90,133 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
return;
}
if (NS_WARN_IF(mOrigin.IsEmpty())) {
return;
}
if (!EnsureNSSInitializedChromeOrContent()) {
+ MOZ_LOG(gU2FLog, LogLevel::Debug, ("Failed to get NSS context for U2F"));
return;
}
- aRv = mSoftToken.Init();
- if (NS_WARN_IF(aRv.Failed())) {
- return;
+ if (XRE_IsParentProcess()) {
+ mNSSToken = do_GetService(NS_NSSU2FTOKEN_CONTRACTID);
+ if (NS_WARN_IF(!mNSSToken)) {
+ return;
+ }
}
aRv = mUSBToken.Init();
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
nsresult
+U2F::NSSTokenIsCompatible(const nsString& versionString, bool* isCompatible)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(mNSSToken);
+ return mNSSToken->IsCompatibleVersion(versionString, isCompatible);
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenIsCompatibleVersion(versionString, isCompatible)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+U2F::NSSTokenIsRegistered(CryptoBuffer& keyHandle, bool* isRegistered)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(mNSSToken);
+ return mNSSToken->IsRegistered(keyHandle.Elements(), keyHandle.Length(),
+ isRegistered);
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenIsRegistered(keyHandle, isRegistered)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+U2F::NSSTokenRegister(CryptoBuffer& application, CryptoBuffer& challenge,
+ CryptoBuffer& registrationData)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(mNSSToken);
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv;
+ rv = mNSSToken->Register(application.Elements(), application.Length(),
+ challenge.Elements(), challenge.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ registrationData.Assign(buffer, bufferlen);
+ free(buffer);
+ return NS_OK;
+ }
+
+ nsTArray<uint8_t> registrationBuffer;
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenRegister(application, challenge,
+ ®istrationBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ registrationData.Assign(registrationBuffer);
+ return NS_OK;
+}
+
+nsresult
+U2F::NSSTokenSign(CryptoBuffer& keyHandle, CryptoBuffer& application,
+ CryptoBuffer& challenge, CryptoBuffer& signatureData)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(mNSSToken);
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv = mNSSToken->Sign(application.Elements(), application.Length(),
+ challenge.Elements(), challenge.Length(),
+ keyHandle.Elements(), keyHandle.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ signatureData.Assign(buffer, bufferlen);
+ free(buffer);
+ return NS_OK;
+ }
+
+ nsTArray<uint8_t> signatureBuffer;
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenSign(application, challenge, keyHandle,
+ &signatureBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ signatureData.Assign(signatureBuffer);
+ return NS_OK;
+}
+
+nsresult
U2F::AssembleClientData(const nsAString& aTyp,
const nsAString& aChallenge,
CryptoBuffer& aClientData) const
{
ClientData clientDataObject;
clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
clientDataObject.mChallenge.Construct(aChallenge);
clientDataObject.mOrigin.Construct(mOrigin);
@@ -253,17 +362,17 @@ U2F::Register(const nsAString& aAppId,
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::BAD_REQUEST);
return;
}
for (size_t i = 0; i < aRegisteredKeys.Length(); ++i) {
RegisteredKey request(aRegisteredKeys[i]);
- // Check for equired attributes
+ // Check for required attributes
if (!(request.mKeyHandle.WasPassed() &&
request.mVersion.WasPassed())) {
continue;
}
// Verify the appId for this Registered Key, if set
if (request.mAppId.WasPassed() &&
!ValidAppID(request.mAppId.Value())) {
@@ -277,32 +386,48 @@ U2F::Register(const nsAString& aAppId,
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::BAD_REQUEST);
return;
}
// We ignore mTransports, as it is intended to be used for sorting the
// available devices by preference, but is not an exclusion factor.
+ bool isCompatible = false;
+ bool isRegistered = false;
+
// Determine if the provided keyHandle is registered at any device. If so,
// then we'll return DEVICE_INELIGIBLE to signify we're already registered.
if (usbTokenEnabled &&
mUSBToken.IsCompatibleVersion(request.mVersion.Value()) &&
mUSBToken.IsRegistered(keyHandle)) {
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::DEVICE_INELIGIBLE);
return;
}
+ if (softTokenEnabled) {
+ rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
+ if (NS_FAILED(rv)) {
+ SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
+ }
- if (softTokenEnabled &&
- mSoftToken.IsCompatibleVersion(request.mVersion.Value()) &&
- mSoftToken.IsRegistered(keyHandle)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
+ rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
+ if (NS_FAILED(rv)) {
+ SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
+ }
+
+ if (isCompatible && isRegistered) {
+ SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::DEVICE_INELIGIBLE);
- return;
+ return;
+ }
}
}
// Search the requests in order for the first some token can fulfill
for (size_t i = 0; i < aRegisterRequests.Length(); ++i) {
RegisterRequest request(aRegisterRequests[i]);
// Check for equired attributes
@@ -348,38 +473,41 @@ U2F::Register(const nsAString& aAppId,
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::OTHER_ERROR);
return;
}
// Get the registration data from the token
CryptoBuffer registrationData;
bool registerSuccess = false;
-
- if (usbTokenEnabled &&
- mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
- rv = mUSBToken.Register(opt_aTimeoutSeconds, challengeParam,
- appParam, registrationData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
- registerSuccess = true;
+ bool isCompatible = false;
+ if (usbTokenEnabled) {
+ // TODO: Implement in Bug 1245527
+ SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
}
- if (!registerSuccess && softTokenEnabled &&
- mSoftToken.IsCompatibleVersion(request.mVersion.Value())) {
- rv = mSoftToken.Register(challengeParam, appParam, registrationData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (!registerSuccess && softTokenEnabled) {
+ rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
+ if (NS_FAILED(rv)) {
SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
ErrorCode::OTHER_ERROR);
return;
}
- registerSuccess = true;
+
+ if (isCompatible) {
+ rv = NSSTokenRegister(appParam, challengeParam, registrationData);
+ if (NS_FAILED(rv)) {
+ SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
+ }
+ registerSuccess = true;
+ }
}
if (!registerSuccess) {
// Try another request
continue;
}
// Assemble a response object to return
@@ -516,35 +644,49 @@ U2F::Sign(const nsAString& aAppId,
CryptoBuffer signatureData;
bool signSuccess = false;
// We ignore mTransports, as it is intended to be used for sorting the
// available devices by preference, but is not an exclusion factor.
if (usbTokenEnabled &&
mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
- rv = mUSBToken.Sign(opt_aTimeoutSeconds, appParam, challengeParam,
- keyHandle, signatureData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
+ // TODO: Implement in Bug 1245527
+ SendError<U2FSignCallback, SignResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
+ }
+
+ if (!signSuccess && softTokenEnabled) {
+ bool isCompatible = false;
+ bool isRegistered = false;
+
+ rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
+ if (NS_FAILED(rv)) {
SendError<U2FSignCallback, SignResponse>(aCallback,
ErrorCode::OTHER_ERROR);
return;
}
- signSuccess = true;
- }
- if (!signSuccess && softTokenEnabled &&
- mSoftToken.IsCompatibleVersion(request.mVersion.Value())) {
- rv = mSoftToken.Sign(appParam, challengeParam, keyHandle, signatureData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
+ if (NS_FAILED(rv)) {
SendError<U2FSignCallback, SignResponse>(aCallback,
ErrorCode::OTHER_ERROR);
return;
}
- signSuccess = true;
+
+ if (isCompatible && isRegistered) {
+ rv = NSSTokenSign(keyHandle, appParam, challengeParam, signatureData);
+ if (NS_FAILED(rv)) {
+ SendError<U2FSignCallback, SignResponse>(aCallback,
+ ErrorCode::OTHER_ERROR);
+ return;
+ }
+ signSuccess = true;
+ }
}
if (!signSuccess) {
// Try another request
continue;
}
// Assemble a response object to return
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -8,20 +8,21 @@
#define mozilla_dom_U2F_h
#include "js/TypeDecls.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/ErrorResult.h"
#include "nsCycleCollectionParticipant.h"
+#include "nsINSSU2FToken.h"
+#include "nsNSSShutDown.h"
#include "nsPIDOMWindow.h"
#include "nsWrapperCache.h"
-#include "NSSToken.h"
#include "USBToken.h"
namespace mozilla {
namespace dom {
struct RegisterRequest;
struct RegisteredKey;
class U2FRegisterCallback;
@@ -73,18 +74,18 @@ public:
// No NSS resources to release.
virtual
void virtualDestroyNSSReference() override {};
private:
nsCOMPtr<nsPIDOMWindowInner> mParent;
nsString mOrigin;
- NSSToken mSoftToken;
USBToken mUSBToken;
+ nsCOMPtr<nsINSSU2FToken> mNSSToken;
static const nsString FinishEnrollment;
static const nsString GetAssertion;
~U2F();
nsresult
AssembleClientData(const nsAString& aTyp,
@@ -93,14 +94,28 @@ private:
// ValidAppID determines whether the supplied FIDO AppID is valid for
// the current FacetID, e.g., the current origin. If the supplied
// aAppId param is null or empty, it will be filled in per the algorithm.
// See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
// for a description of the algorithm.
bool
ValidAppID(/* in/out */ nsString& aAppId) const;
+
+ nsresult
+ NSSTokenIsCompatible(const nsString& versionString, bool* isCompatible);
+
+ nsresult
+ NSSTokenIsRegistered(CryptoBuffer& keyHandle, bool* isRegistered);
+
+ nsresult
+ NSSTokenRegister(CryptoBuffer& application, CryptoBuffer& challenge,
+ CryptoBuffer& registrationData);
+
+ nsresult
+ NSSTokenSign(CryptoBuffer& keyHandle, CryptoBuffer& application,
+ CryptoBuffer& challenge, CryptoBuffer& signatureData);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_U2F_h
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,26 +1,26 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.dom += [
- 'NSSToken.h',
'U2F.h',
'USBToken.h',
]
UNIFIED_SOURCES += [
- 'NSSToken.cpp',
'U2F.cpp',
'USBToken.cpp',
]
+include('/ipc/chromium/chromium-config.mozbuild')
+
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
'/dom/crypto',
'/security/manager/ssl',
'/security/pkix/include',
]
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -1,14 +1,15 @@
[DEFAULT]
support-files =
frame_no_token.html
u2futil.js
test_frame_appid_facet.html
test_frame_register.html
+ test_frame_register_sign.html
test_frame_appid_facet_remoteload.html
test_frame_appid_facet_insecure.html
test_frame_appid_facet_subdomain.html
facet/facetList.txt
facet/facetList-good
facet/facetList-good^headers^
facet/facetList-no_overlap
facet/facetList-no_overlap^headers^
--- a/dom/u2f/tests/test_frame.html
+++ b/dom/u2f/tests/test_frame.html
@@ -14,17 +14,17 @@
<iframe id="testing_frame"></iframe>
</div>
<pre id="log"></pre>
<script class="testbody" type="text/javascript">
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
- ["security.webauth.u2f.softtoken", true]]},
+ ["security.webauth.u2f_enable_softtoken", true]]},
function() {
var testList = [
"https://example.com/tests/dom/u2f/tests/test_frame_register.html",
"http://mochi.test:8888/tests/dom/u2f/tests/test_frame_appid_facet_insecure.html",
"https://example.com/tests/dom/u2f/tests/test_frame_appid_facet.html",
"https://example.com/tests/dom/u2f/tests/test_frame_appid_facet_remoteload.html",
"https://test1.example.com/tests/dom/u2f/tests/test_frame_appid_facet_subdomain.html"
];
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_frame_register_sign.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <script type="text/javascript" src="u2futil.js"></script>
+</head>
+<body>
+<p>Register and Sign Test for FIDO Universal Second Factor</p>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var state = {
+ // Raw messages
+ regRequest: null,
+ regResponse: null,
+
+ regKey: null,
+ signChallenge: null,
+ signResponse: null,
+
+ // Parsed values
+ publicKey: null,
+ keyHandle: null,
+
+ // Constants
+ version: "U2F_V2",
+ appId: window.location.origin,
+};
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+ ["security.webauth.u2f_enable_softtoken", true]]},
+function() {
+ local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
+ local_isnot(window.u2f.register, undefined, "U2F Register API endpoint must exist");
+ local_isnot(window.u2f.sign, undefined, "U2F Sign API endpoint must exist");
+
+ testRegistering();
+
+ function testRegistering() {
+ var challenge = new Uint8Array(16);
+ window.crypto.getRandomValues(challenge);
+
+ state.regRequest = {
+ version: state.version,
+ challenge: bytesToBase64UrlSafe(challenge),
+ };
+
+ u2f.register(state.appId, [state.regRequest], [], function(regResponse) {
+ state.regResponse = regResponse;
+
+ local_is(regResponse.errorCode, 0, "The registration did not error");
+ local_isnot(regResponse.registrationData, undefined, "The registration did not provide registration data");
+ if (regResponse.errorCode > 0) {
+ local_finished();
+ return;
+ }
+
+ // Parse the response data from the U2F token
+ var registrationData = base64ToBytesUrlSafe(regResponse.registrationData);
+ local_is(registrationData[0], 0x05, "Reserved byte is correct")
+
+ state.publicKeyBytes = registrationData.slice(1, 66);
+ var keyHandleLength = registrationData[66];
+ state.keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
+ state.keyHandle = bytesToBase64UrlSafe(state.keyHandleBytes);
+ state.attestation = registrationData.slice(67 + keyHandleLength);
+
+ // Verify that the clientData from the U2F token makes sense
+ var clientDataJSON = "";
+ base64ToBytesUrlSafe(regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
+ var clientData = JSON.parse(clientDataJSON);
+ local_is(clientData.typ, "navigator.id.finishEnrollment", "Data type matches");
+ local_is(clientData.challenge, state.regRequest.challenge, "Register challenge matches");
+ local_is(clientData.origin, window.location.origin, "Origins are the same");
+
+ // Import the public key of the U2F token into WebCrypto
+ deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
+ .then(function(params){
+ state.appParam = params.appParam;
+ state.challengeParam = params.challengeParam;
+ return importPublicKey(state.publicKeyBytes)
+ }).then(function(key) {
+ state.publicKey = key;
+ local_ok(true, "Imported public key")
+
+ testReRegister()
+ }).catch(function(err) {
+ console.log(err);
+ local_ok(false, "Public key import failed");
+ local_finished();
+ });
+ }
+ }
+
+ function testReRegister() {
+ state.regKey = {
+ version: state.version,
+ keyHandle: state.keyHandle,
+ };
+
+ // Test that we don't re-register if we provide regKey as an
+ // "already known" key handle. The U2F module should recognize regKey
+ // as being usable and, thus, give back errorCode 4.
+ u2f.register(state.appId, [state.regRequest], [state.regKey], function(regResponse) {
+ // Since we attempted to register with state.regKey as a known key, expect
+ // ineligible (=4).
+ local_is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
+ local_is(regResponse.registrationData, undefined, "The re-registration did not provide registration data");
+
+ testSigning();
+ });
+ }
+
+ function testSigning() {
+ var challenge = new Uint8Array(16);
+ window.crypto.getRandomValues(challenge);
+ state.signChallenge = bytesToBase64UrlSafe(challenge);
+
+ // Now try to sign the signature challenge
+ u2f.sign(state.appId, state.signChallenge, [state.regKey], function(signResponse) {
+ state.signResponse = signResponse;
+
+ // Make sure this signature op worked, bailing early if it failed.
+ local_is(signResponse.errorCode, 0, "The signing did not error");
+ local_isnot(signResponse.clientData, undefined, "The signing did not provide client data");
+
+ if (signResponse.errorCode > 0) {
+ local_finished();
+ return;
+ }
+
+ // Decode the clientData that was returned from the module
+ var clientDataJSON = "";
+ base64ToBytesUrlSafe(signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
+ var clientData = JSON.parse(clientDataJSON);
+ local_is(clientData.typ, "navigator.id.getAssertion", "Data type matches");
+ local_is(clientData.challenge, state.signChallenge, "Sign challenge matches");
+ local_is(clientData.origin, window.location.origin, "Origins are the same");
+
+ // Parse the signature data
+ var signatureData = base64ToBytesUrlSafe(signResponse.signatureData);
+ if (signatureData[0] != 0x01) {
+ throw "User presence byte not set";
+ }
+ var presenceAndCounter = signatureData.slice(0,5);
+ var signatureValue = signatureData.slice(5);
+
+ // Assemble the signed data and verify the signature
+ deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
+ .then(function(params){
+ return assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam);
+ })
+ .then(function(signedData) {
+ return verifySignature(state.publicKey, signedData, signatureValue);
+ })
+ .then(function(verified) {
+ console.log("No error verifying signing signature");
+ local_ok(verified, "Signing signature verified")
+
+ local_finished();
+ })
+ .catch(function(err) {
+ console.log(err);
+ local_ok(false, "Signing signature invalid");
+
+ local_finished();
+ });
+ });
+ }
+
+</script>
+</body>
+</html>
--- a/dom/u2f/tests/test_no_token.html
+++ b/dom/u2f/tests/test_no_token.html
@@ -13,18 +13,18 @@
<iframe id="testing_frame"></iframe>
</div>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
- ["security.webauth.u2f.softtoken", false],
- ["security.webauth.u2f.usbtoken", false]]},
+ ["security.webauth.u2f_enable_softtoken", false],
+ ["security.webauth.u2f_enable_usbtoken", false]]},
function() {
onmessage = function(event) {
//event.data is the response.errorCode
isnot(event.data, 0, "The registration should be rejected.");
SimpleTest.finish();
}
document.getElementById('testing_frame').src = 'frame_no_token.html';
--- a/dom/u2f/tests/test_util_methods.html
+++ b/dom/u2f/tests/test_util_methods.html
@@ -12,39 +12,40 @@
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
- ["security.webauth.u2f.softtoken", true],
- ["security.webauth.u2f.usbtoken", false]]},
+ ["security.webauth.u2f_enable_softtoken", true],
+ ["security.webauth.u2f_enable_usbtoken", false]]},
function() {
// Example from:
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html
//
// Run this example from the console to check that the u2futil methods work
var pubKey = hexDecode("04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d");
var appId = "https://gstatic.com/securitykey/a/example.com";
var clientData = string2buffer('{"typ":"navigator.id.getAssertion","challenge":"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}');
var presenceAndCounter = hexDecode("0100000001");
var signature = hexDecode("304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f");
// Import the key
// Assemble the client data
// Verify
Promise.all([
importPublicKey(pubKey),
- assembleSignedData(appId, presenceAndCounter, clientData)
+ deriveAppAndChallengeParam(appId, clientData)
])
.then(function(results) {
var importedKey = results[0];
- var signedData = new Uint8Array(results[1]);
+ var params = results[1];
+ var signedData = new Uint8Array(assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam));
return verifySignature(importedKey, signedData, signature);
})
.then(function(verified) {
console.log("verified:", verified);
ok(true, "Utility methods work")
SimpleTest.finish();
})
.catch(function(err) {
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -93,32 +93,46 @@ function importPublicKey(keyBytes) {
kty: "EC",
crv: "P-256",
x: bytesToBase64UrlSafe(keyBytes.slice(1, 33)),
y: bytesToBase64UrlSafe(keyBytes.slice(33))
};
return crypto.subtle.importKey("jwk", jwk, {name: "ECDSA", namedCurve: "P-256"}, true, ["verify"])
}
-function assembleSignedData(appId, presenceAndCounter, clientData) {
+function deriveAppAndChallengeParam(appId, clientData) {
var appIdBuf = string2buffer(appId);
return Promise.all([
crypto.subtle.digest("SHA-256", appIdBuf),
crypto.subtle.digest("SHA-256", clientData)
])
.then(function(digests) {
- var appParam = new Uint8Array(digests[0]);
- var clientParam = new Uint8Array(digests[1]);
+ return {
+ appParam: new Uint8Array(digests[0]),
+ challengeParam: new Uint8Array(digests[1]),
+ };
+ });
+}
- var signedData = new Uint8Array(32 + 1 + 4 + 32);
- appParam.map((x, i) => signedData[0 + i] = x);
- presenceAndCounter.map((x, i) => signedData[32 + i] = x);
- clientParam.map((x, i) => signedData[37 + i] = x);
- return signedData;
- });
+function assembleSignedData(appParam, presenceAndCounter, challengeParam) {
+ var signedData = new Uint8Array(32 + 1 + 4 + 32);
+ appParam.map((x, i) => signedData[0 + i] = x);
+ presenceAndCounter.map((x, i) => signedData[32 + i] = x);
+ challengeParam.map((x, i) => signedData[37 + i] = x);
+ return signedData;
+}
+
+function assembleRegistrationSignedData(appParam, challengeParam, keyHandle, pubKey) {
+ var signedData = new Uint8Array(1 + 32 + 32 + keyHandle.length + 65);
+ signedData[0] = 0x00;
+ appParam.map((x, i) => signedData[1 + i] = x);
+ challengeParam.map((x, i) => signedData[33 + i] = x);
+ keyHandle.map((x, i) => signedData[65 + i] = x);
+ pubKey.map((x, i) => signedData[65 + keyHandle.length + i] = x);
+ return signedData;
}
function verifySignature(key, data, derSig) {
if (derSig.byteLength < 70) {
console.log("bad sig: " + hexEncode(derSig))
throw "Invalid signature length: " + derSig.byteLength;
}
@@ -136,9 +150,9 @@ function verifySignature(key, data, derS
);
console.log("data: " + hexEncode(data));
console.log("der: " + hexEncode(derSig));
console.log("raw: " + hexEncode(sig));
var alg = {name: "ECDSA", hash: "SHA-256"};
return crypto.subtle.verify(alg, key, sig, data);
-}
\ No newline at end of file
+}
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -59,14 +59,14 @@ pref("security.pki.sha1_enforcement_leve
// 2: only use name information from the subject alternative name extension
#ifdef RELEASE_BUILD
pref("security.pki.name_matching_mode", 1);
#else
pref("security.pki.name_matching_mode", 2);
#endif
pref("security.webauth.u2f", false);
-pref("security.webauth.u2f.softtoken", false);
-pref("security.webauth.u2f.usbtoken", false);
+pref("security.webauth.u2f_enable_softtoken", false);
+pref("security.webauth.u2f_enable_usbtoken", false);
pref("security.ssl.errorReporting.enabled", true);
pref("security.ssl.errorReporting.url", "https://data.mozilla.com/submit/sslreports");
pref("security.ssl.errorReporting.automatic", false);
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -18,16 +18,17 @@ XPIDL_SOURCES += [
'nsICertOverrideService.idl',
'nsICertPickDialogs.idl',
'nsIClientAuthDialogs.idl',
'nsIContentSignatureVerifier.idl',
'nsIDataSignatureVerifier.idl',
'nsIGenKeypairInfoDlg.idl',
'nsIKeygenThread.idl',
'nsIKeyModule.idl',
+ 'nsINSSU2FToken.idl',
'nsINSSVersion.idl',
'nsIPK11Token.idl',
'nsIPK11TokenDB.idl',
'nsIPKCS11.idl',
'nsIPKCS11Module.idl',
'nsIPKCS11ModuleDB.idl',
'nsIPKCS11Slot.idl',
'nsIProtectedAuthThread.idl',
@@ -56,16 +57,17 @@ EXPORTS += [
'CryptoTask.h',
'nsClientAuthRemember.h',
'nsCrypto.h',
'nsNSSCallbacks.h',
'nsNSSCertificate.h',
'nsNSSComponent.h',
'nsNSSHelper.h',
'nsNSSShutDown.h',
+ 'nsNSSU2FToken.h',
'nsRandomGenerator.h',
'nsSecurityHeaderParser.h',
'NSSErrorsService.h',
'ScopedNSSTypes.h',
'SharedCertVerifier.h',
]
EXPORTS.mozilla += [
@@ -107,16 +109,17 @@ UNIFIED_SOURCES += [
'nsNSSCertificateFakeTransport.cpp',
'nsNSSCertTrust.cpp',
'nsNSSCertValidity.cpp',
'nsNSSComponent.cpp',
'nsNSSErrors.cpp',
'nsNSSIOLayer.cpp',
'nsNSSModule.cpp',
'nsNSSShutDown.cpp',
+ 'nsNSSU2FToken.cpp',
'nsNSSVersion.cpp',
'nsNTLMAuthModule.cpp',
'nsPK11TokenDB.cpp',
'nsPKCS11Slot.cpp',
'nsPKCS12Blob.cpp',
'nsProtectedAuthThread.cpp',
'nsPSMBackgroundThread.cpp',
'nsRandomGenerator.cpp',
@@ -156,16 +159,17 @@ if CONFIG['MOZ_XUL']:
UNIFIED_SOURCES += [
'md4.c',
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
+ '/dom/crypto',
'/security/certverifier',
'/security/pkix/include',
]
LOCAL_INCLUDES += [
'!/dist/public/nss',
]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsINSSU2FToken.idl
@@ -0,0 +1,75 @@
+/* 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 "nsISupports.idl"
+
+interface nsIArray;
+/**
+ * Interface used to interact with the NSS-backed software U2F Token
+ */
+[scriptable, uuid(d9104a00-140b-4f86-a4b0-4998878ef4e6 )]
+interface nsINSSU2FToken : nsISupports {
+ /**
+ * Initializes the token and constructs and persists keys, if needed. Asserts
+ * that it is only called by the main thread.
+ */
+ void init();
+
+ /**
+ * Is this token compatible with the provided version?
+ *
+ * @param version The offered version to test
+ * @return True if the offered version is compatible
+ */
+ void isCompatibleVersion(in AString version, [retval] out boolean result);
+
+ /**
+ * Return whether the provided KeyHandle belongs to this Token
+ *
+ * @param keyHandle Key Handle to evaluate.
+ * @return True if the Key Handle is ours.
+ */
+ void isRegistered([array, size_is(keyHandleLen)] in octet keyHandle,
+ in uint32_t keyHandleLen,
+ [retval] out boolean result);
+
+ /**
+ * Generates a public/private keypair for the provided application
+ * and challenge, returning the pubkey, challenge response, and
+ * key handle in the registration data.
+ *
+ * @param application The FIDO Application data to associate with the key.
+ * @param challenge The Challenge to satisfy in the response.
+ * @param registration An array containing the pubkey, challenge response,
+ * and key handle.
+ */
+ void register([array, size_is(applicationLen)] in octet application,
+ in uint32_t applicationLen,
+ [array, size_is(challengeLen)] in octet challenge,
+ in uint32_t challengeLen,
+ [array, size_is(registrationLen)] out octet registration,
+ out uint32_t registrationLen);
+
+ /**
+ * Creates a signature over the "param" arguments using the private key
+ * provided in the key handle argument.
+ *
+ * @param application The FIDO Application data to associate with the key.
+ * @param challenge The Challenge to satisfy in the response.
+ * @param keyHandle The Key Handle opaque object to use.
+ * @param signature The resulting signature.
+ */
+ void sign([array, size_is(applicationLen)] in octet application,
+ in uint32_t applicationLen,
+ [array, size_is(challengeLen)] in octet challenge,
+ in uint32_t challengeLen,
+ [array, size_is(keyHandleLen)] in octet keyHandle,
+ in uint32_t keyHandleLen,
+ [array, size_is(signatureLen)] out octet signature,
+ out uint32_t signatureLen);
+};
+
+%{C++
+#define NS_NSSU2FTOKEN_CONTRACTID "@mozilla.org/dom/u2f/nss-u2f-token;1"
+%}
--- a/security/manager/ssl/nsNSSModule.cpp
+++ b/security/manager/ssl/nsNSSModule.cpp
@@ -19,16 +19,17 @@
#include "nsKeyModule.h"
#include "mozilla/ModuleUtils.h"
#include "nsNetCID.h"
#include "nsNSSCertificate.h"
#include "nsNSSCertificateDB.h"
#include "nsNSSCertificateFakeTransport.h"
#include "nsNSSComponent.h"
#include "NSSErrorsService.h"
+#include "nsNSSU2FToken.h"
#include "nsNSSVersion.h"
#include "nsNTLMAuthModule.h"
#include "nsPK11TokenDB.h"
#include "nsPKCS11Slot.h"
#include "PSMContentListener.h"
#include "nsRandomGenerator.h"
#include "nsSDR.h"
#include "nsSecureBrowserUIImpl.h"
@@ -205,16 +206,17 @@ NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEn
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nssEnsure, nsNTLMAuthModule, InitTest)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsCryptoHash)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsCryptoHMAC)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsKeyObject)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsKeyObjectFactory)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsDataSignatureVerifier)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, ContentSignatureVerifier)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsRandomGenerator)
+NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nssEnsure, nsNSSU2FToken, Init)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureOnChromeOnly, nsSSLStatus)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureOnChromeOnly, TransportSecurityInfo)
typedef mozilla::psm::NSSErrorsService NSSErrorsService;
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NSSErrorsService, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsNSSVersion)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCertOverrideService, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntropyCollector)
@@ -243,16 +245,17 @@ NS_DEFINE_NAMED_CID(NS_CRYPTO_HMAC_CID);
NS_DEFINE_NAMED_CID(NS_CERT_PICKER_CID);
NS_DEFINE_NAMED_CID(NS_NTLMAUTHMODULE_CID);
NS_DEFINE_NAMED_CID(NS_KEYMODULEOBJECT_CID);
NS_DEFINE_NAMED_CID(NS_KEYMODULEOBJECTFACTORY_CID);
NS_DEFINE_NAMED_CID(NS_DATASIGNATUREVERIFIER_CID);
NS_DEFINE_NAMED_CID(NS_CONTENTSIGNATUREVERIFIER_CID);
NS_DEFINE_NAMED_CID(NS_CERTOVERRIDE_CID);
NS_DEFINE_NAMED_CID(NS_RANDOMGENERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_NSSU2FTOKEN_CID);
NS_DEFINE_NAMED_CID(NS_SSLSTATUS_CID);
NS_DEFINE_NAMED_CID(TRANSPORTSECURITYINFO_CID);
NS_DEFINE_NAMED_CID(NS_NSSERRORSSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_NSSVERSION_CID);
NS_DEFINE_NAMED_CID(NS_ENTROPYCOLLECTOR_CID);
NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID);
NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID);
@@ -279,16 +282,17 @@ static const mozilla::Module::CIDEntry k
{ &kNS_CERT_PICKER_CID, false, nullptr, nsCertPickerConstructor },
{ &kNS_NTLMAUTHMODULE_CID, false, nullptr, nsNTLMAuthModuleConstructor },
{ &kNS_KEYMODULEOBJECT_CID, false, nullptr, nsKeyObjectConstructor },
{ &kNS_KEYMODULEOBJECTFACTORY_CID, false, nullptr, nsKeyObjectFactoryConstructor },
{ &kNS_DATASIGNATUREVERIFIER_CID, false, nullptr, nsDataSignatureVerifierConstructor },
{ &kNS_CONTENTSIGNATUREVERIFIER_CID, false, nullptr, ContentSignatureVerifierConstructor },
{ &kNS_CERTOVERRIDE_CID, false, nullptr, nsCertOverrideServiceConstructor },
{ &kNS_RANDOMGENERATOR_CID, false, nullptr, nsRandomGeneratorConstructor },
+ { &kNS_NSSU2FTOKEN_CID, false, nullptr, nsNSSU2FTokenConstructor },
{ &kNS_SSLSTATUS_CID, false, nullptr, nsSSLStatusConstructor },
{ &kTRANSPORTSECURITYINFO_CID, false, nullptr, TransportSecurityInfoConstructor },
{ &kNS_NSSERRORSSERVICE_CID, false, nullptr, NSSErrorsServiceConstructor },
{ &kNS_NSSVERSION_CID, false, nullptr, nsNSSVersionConstructor },
{ &kNS_ENTROPYCOLLECTOR_CID, false, nullptr, nsEntropyCollectorConstructor },
{ &kNS_SECURE_BROWSER_UI_CID, false, nullptr, nsSecureBrowserUIImplConstructor },
{ &kNS_SITE_SECURITY_SERVICE_CID, false, nullptr, nsSiteSecurityServiceConstructor },
{ &kNS_CERT_BLOCKLIST_CID, false, nullptr, CertBlocklistConstructor},
@@ -320,16 +324,17 @@ static const mozilla::Module::ContractID
{ NS_CRYPTO_FIPSINFO_SERVICE_CONTRACTID, &kNS_PKCS11MODULEDB_CID },
{ NS_NTLMAUTHMODULE_CONTRACTID, &kNS_NTLMAUTHMODULE_CID },
{ NS_KEYMODULEOBJECT_CONTRACTID, &kNS_KEYMODULEOBJECT_CID },
{ NS_KEYMODULEOBJECTFACTORY_CONTRACTID, &kNS_KEYMODULEOBJECTFACTORY_CID },
{ NS_DATASIGNATUREVERIFIER_CONTRACTID, &kNS_DATASIGNATUREVERIFIER_CID },
{ NS_CONTENTSIGNATUREVERIFIER_CONTRACTID, &kNS_CONTENTSIGNATUREVERIFIER_CID },
{ NS_CERTOVERRIDE_CONTRACTID, &kNS_CERTOVERRIDE_CID },
{ NS_RANDOMGENERATOR_CONTRACTID, &kNS_RANDOMGENERATOR_CID },
+ { NS_NSSU2FTOKEN_CONTRACTID, &kNS_NSSU2FTOKEN_CID },
{ NS_ENTROPYCOLLECTOR_CONTRACTID, &kNS_ENTROPYCOLLECTOR_CID },
{ NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID },
{ NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID },
{ NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID },
{ NS_WEAKCRYPTOOVERRIDE_CONTRACTID, &kNS_WEAKCRYPTOOVERRIDE_CID },
{ nullptr }
};
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsNSSU2FToken.cpp
@@ -0,0 +1,707 @@
+/* -*- 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 "nsNSSComponent.h"
+#include "pk11pub.h"
+#include "prerror.h"
+#include "secerr.h"
+#include "WebCryptoCommon.h"
+
+using mozilla::dom::CreateECParamsForCurve;
+
+NS_IMPL_ISUPPORTS(nsNSSU2FToken, 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(calledFromObject);
+}
+
+void
+nsNSSU2FToken::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void
+nsNSSU2FToken::destructorSafeDestroyNSSReference()
+{
+ mWrappingKey = nullptr;
+}
+
+static PK11SymKey*
+GetSymKeyByNickname(PK11SlotInfo* aSlot,
+ nsCString aNickname,
+ const nsNSSShutDownPreventionLock&)
+{
+ MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+ ("Searching for a symmetric key named %s", aNickname.get()));
+
+ PK11SymKey* keyList;
+ keyList = PK11_ListFixedKeysInSlot(aSlot, const_cast<char*>(aNickname.get()),
+ /* wincx */ nullptr);
+ while (keyList) {
+ ScopedPK11SymKey freeKey(keyList);
+
+ UniquePtr<char, void(&)(void*)>
+ freeKeyName(PK11_GetSymKeyNickname(freeKey), PORT_Free);
+
+ if (aNickname == freeKeyName.get()) {
+ MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key found!"));
+ return freeKey.forget();
+ }
+
+ keyList = PK11_GetNextSymKey(keyList);
+ }
+
+ MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key not found."));
+ return nullptr;
+}
+
+static nsresult
+GenEcKeypair(PK11SlotInfo* aSlot, ScopedSECKEYPrivateKey& aPrivKey,
+ ScopedSECKEYPublicKey& aPubKey, const nsNSSShutDownPreventionLock&)
+{
+ 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 = PK11_GenerateKeyPair(aSlot, mechanism, keyParams, &pubKeyRaw,
+ /* ephemeral */ PR_FALSE, PR_FALSE,
+ /* wincx */ nullptr);
+ aPubKey = pubKeyRaw;
+ 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(PK11SlotInfo* aSlot,
+ const nsNSSShutDownPreventionLock& locker)
+{
+ // 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 = PK11_TokenKeyGenWithFlags(aSlot, 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, 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(PK11SlotInfo* aSlot,
+ ScopedSECKEYPrivateKey& aAttestPrivKey,
+ ScopedCERTCertificate& aAttestCert,
+ const nsNSSShutDownPreventionLock& locker)
+{
+ ScopedSECKEYPublicKey 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
+ ScopedCERTName 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;
+ }
+
+ ScopedCERTSubjectPublicKeyInfo spki(
+ SECKEY_CreateSubjectPublicKeyInfo(pubKey));
+ if (!spki) {
+ MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+ ("Failed to set SPKI, NSS error #%d", PORT_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ ScopedCERTCertificateRequest certreq(
+ CERT_CreateCertificateRequest(subjectName, spki, 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;
+
+ ScopedCERTValidity 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 = reinterpret_cast<unsigned char *>(&serial);
+ SECStatus srv = PK11_GenerateRandomOnSlot(aSlot, 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 = CERT_CreateCertificate(serial, subjectName, validity, certreq);
+ 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,
+ 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, 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;
+ }
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
+ MOZ_ASSERT(slot.get());
+
+ // Search for an existing wrapping key, or create one.
+ nsresult rv = GetOrCreateWrappingKey(slot.get(), 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 SECItem*
+KeyHandleFromPrivateKey(PK11SlotInfo* aSlot,
+ PK11SymKey* aWrappingKey,
+ SECKEYPrivateKey* aPrivKey,
+ const nsNSSShutDownPreventionLock&)
+{
+ ScopedSECItem 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;
+ }
+
+ ScopedSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
+ /* default IV */ nullptr ));
+
+ SECStatus srv = PK11_WrapPrivKey(aSlot, aWrappingKey, aPrivKey,
+ CKM_NSS_AES_KEY_WRAP_PAD, param,
+ 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.forget();
+}
+
+// Convert an opaque key handle aKeyHandle back into a Private Key object, using
+// aWrappingKey and the AES Key Wrap algorithm.
+static SECKEYPrivateKey*
+PrivateKeyFromKeyHandle(PK11SlotInfo* aSlot, PK11SymKey* aWrappingKey,
+ uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+ const nsNSSShutDownPreventionLock&)
+{
+ ScopedAutoSECItem pubKey(kPublicKeyLen);
+
+ ScopedAutoSECItem keyHandleItem(aKeyHandleLen);
+ memcpy(keyHandleItem.data, aKeyHandle, keyHandleItem.len);
+
+ ScopedSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
+ /* default IV */ nullptr ));
+
+ CK_ATTRIBUTE_TYPE usages[] = { CKA_SIGN };
+ int usageCount = 1;
+
+ SECKEYPrivateKey* unwrappedKey;
+ unwrappedKey = PK11_UnwrapPrivKey(aSlot, aWrappingKey,
+ CKM_NSS_AES_KEY_WRAP_PAD,
+ param, &keyHandleItem,
+ /* no nickname */ nullptr,
+ /* discard pubkey */ &pubKey,
+ /* not permanent */ PR_FALSE,
+ /* non-exportable */ PR_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;
+ }
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ // Decode the key handle
+ ScopedSECKEYPrivateKey privKey(PrivateKeyFromKeyHandle(slot.get(),
+ mWrappingKey.get(),
+ 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);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ // Construct a one-time-use Attestation Certificate
+ ScopedSECKEYPrivateKey attestPrivKey;
+ ScopedCERTCertificate attestCert;
+ nsresult rv = GetAttestationCertificate(slot.get(), 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
+ ScopedSECKEYPrivateKey privKey;
+ ScopedSECKEYPublicKey pubKey;
+ rv = GenEcKeypair(slot.get(), 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)
+ ScopedSECItem keyHandleItem(KeyHandleFromPrivateKey(slot.get(),
+ mWrappingKey.get(),
+ privKey.get(),
+ 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);
+
+ ScopedSECItem signatureItem(::SECITEM_AllocItem(/* default arena */ nullptr,
+ /* no buffer */ nullptr, 0));
+ if (!signatureItem) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SECStatus srv = SEC_SignData(signatureItem.get(), 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.get());
+ 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);
+
+ ScopedPK11SlotInfo 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
+ ScopedSECKEYPrivateKey privKey(PrivateKeyFromKeyHandle(slot.get(),
+ mWrappingKey.get(),
+ 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);
+
+ ScopedSECItem signatureItem(::SECITEM_AllocItem(/* default arena */ nullptr,
+ /* no buffer */ nullptr, 0));
+ if (!signatureItem) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SECStatus srv = SEC_SignData(signatureItem.get(), 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;
+ }
+
+ // Assmeble 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;
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsNSSU2FToken.h
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+#ifndef nsNSSU2FToken_h
+#define nsNSSU2FToken_h
+
+#include "nsINSSU2FToken.h"
+
+#include "nsNSSShutDown.h"
+#include "ScopedNSSTypes.h"
+
+#define NS_NSSU2FTOKEN_CID \
+ {0x79f95a6c, 0xd0f7, 0x4d7d, {0xae, 0xaa, 0xcd, 0x0a, 0x04, 0xb6, 0x50, 0x89}}
+
+class nsNSSU2FToken : public nsINSSU2FToken,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINSSU2FTOKEN
+
+ nsNSSU2FToken();
+
+ // For nsNSSShutDownObject
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+
+private:
+ bool mInitialized;
+ mozilla::ScopedPK11SymKey mWrappingKey;
+
+ static const nsCString mSecretNickname;
+ static const nsString mVersion;
+
+ ~nsNSSU2FToken();
+ nsresult GetOrCreateWrappingKey(PK11SlotInfo* aSlot,
+ const nsNSSShutDownPreventionLock&);
+};
+
+#endif // nsNSSU2FToken_h