Bug 1244960 - Complete FIDO u2f NSSToken (Part 1) r?keeler r?baku draft
authorJ.C. Jones <jjones@mozilla.com>
Fri, 15 Apr 2016 09:29:12 -0700
changeset 352083 7726ce532c75043e416c042c62e276c9083accf4
parent 351979 afd82f887093e5e9e4015115ca5795ec82a6f732
child 352084 c1006e362201e771b42fd82dc557d4e6861425c2
push id15608
push userjjones@mozilla.com
push dateFri, 15 Apr 2016 16:33:37 +0000
reviewerskeeler, baku
bugs1244960, 1255784
milestone48.0a1
Bug 1244960 - Complete FIDO u2f NSSToken (Part 1) r?keeler r?baku - Merge in test changes from Bug 1255784. - Remove the unnecessary mutex - Stop doing direct memory work in NSS Token - Clean up direct memory work in ContentParent - In order to store persistent crypto parameters, the NSSToken had to move onto the main thread and be interfaced with via IDL/IPDL. - Support Register/Sign via NSS using a long-lived secret key - Rename the softtoken/usbtoken "enable" prefs, because of hierarchy issues with the WebIDL Pref shadowing. - Also orders the includes on nsNSSModule.cpp - Attestation Certificates are in Part 2. Updates per keeler review comments: - Use //-style comments everywhere - Refactor the PrivateKeyFromKeyHandle method - Rename the logging and fix extraneous NS_WARN_IF/logging combinations - Other updates from review April 11-12: - Correct usage of the "usageCount" flag for PK11_UnwrapPrivKey - Rebase up to latest April 15: - Rebase to latest MozReview-Commit-ID: 6T8jNmwFvHJ
config/external/nss/nss.symbols
dom/crypto/CryptoBuffer.cpp
dom/crypto/CryptoBuffer.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/u2f/NSSToken.cpp
dom/u2f/NSSToken.h
dom/u2f/U2F.cpp
dom/u2f/U2F.h
dom/u2f/moz.build
dom/u2f/tests/mochitest.ini
dom/u2f/tests/test_frame.html
dom/u2f/tests/test_frame_register_sign.html
dom/u2f/tests/test_no_token.html
dom/u2f/tests/test_util_methods.html
dom/u2f/tests/u2futil.js
netwerk/base/security-prefs.js
security/manager/ssl/moz.build
security/manager/ssl/nsINSSU2FToken.idl
security/manager/ssl/nsNSSModule.cpp
security/manager/ssl/nsNSSU2FToken.cpp
security/manager/ssl/nsNSSU2FToken.h
--- 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,
+                                   &registrationBuffer)) {
+    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