Bug 1244960 - Complete FIDO u2f NSSToken (Part 1). r=keeler, r=baku
authorJ.C. Jones <jjones@mozilla.com>
Fri, 15 Apr 2016 09:29:12 -0700
changeset 331366 df215bb5bb8c5ad777086d47cad0b26308f1d222
parent 331365 105b3fba6454606849bae897756ad4babfc5527b
child 331367 f7c7877b38a2adaf8c1bb948ee4fe22555da122c
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, baku
bugs1244960, 1255784
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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