Bug 1388851 - Implement U2FHIDTokenManager r=jcj,qdot,keeler
authorTim Taubert <ttaubert@mozilla.com>
Wed, 09 Aug 2017 21:24:50 +0200
changeset 380913 c89dde5d6274c859c0fefe000a3c1ad59c13eece
parent 380912 4890481365e66c1ef2f7126481908b64002c113f
child 380914 b51e0600fdc414552dd9dd7e4eefe4534b5dd73c
push id95013
push userttaubert@mozilla.com
push dateThu, 14 Sep 2017 18:35:10 +0000
treeherdermozilla-inbound@c89dde5d6274 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj, qdot, keeler
bugs1388851
milestone57.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 1388851 - Implement U2FHIDTokenManager r=jcj,qdot,keeler
dom/webauthn/U2FHIDTokenManager.cpp
dom/webauthn/U2FHIDTokenManager.h
dom/webauthn/U2FTokenTransport.h
dom/webauthn/moz.build
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -1,25 +1,82 @@
 /* -*- 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 "mozilla/dom/U2FHIDTokenManager.h"
+#include "mozilla/StaticMutex.h"
 
 namespace mozilla {
 namespace dom {
 
-U2FHIDTokenManager::U2FHIDTokenManager()
+static StaticMutex gInstanceMutex;
+static U2FHIDTokenManager* gInstance;
+static nsIThread* gPBackgroundThread;
+
+static void
+u2f_register_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
+{
+  StaticMutexAutoLock lock(gInstanceMutex);
+  if (NS_WARN_IF(!gInstance || !gPBackgroundThread)) {
+    return;
+  }
+
+  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
+  nsCOMPtr<nsIRunnable> r(NewNonOwningRunnableMethod<UniquePtr<U2FResult>&&>(
+      "U2FHIDTokenManager::HandleRegisterResult", gInstance,
+      &U2FHIDTokenManager::HandleRegisterResult, Move(rv)));
+
+  MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
+                                                   NS_DISPATCH_NORMAL));
+}
+
+static void
+u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
 {
+  StaticMutexAutoLock lock(gInstanceMutex);
+  if (NS_WARN_IF(!gInstance || !gPBackgroundThread)) {
+    return;
+  }
+
+  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
+  nsCOMPtr<nsIRunnable> r(NewNonOwningRunnableMethod<UniquePtr<U2FResult>&&>(
+      "U2FHIDTokenManager::HandleSignResult", gInstance,
+      &U2FHIDTokenManager::HandleSignResult, Move(rv)));
+
+  MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
+                                                   NS_DISPATCH_NORMAL));
+}
+
+U2FHIDTokenManager::U2FHIDTokenManager() : mTransactionId(0)
+{
+  StaticMutexAutoLock lock(gInstanceMutex);
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!gInstance);
+
+  mU2FManager = rust_u2f_mgr_new();
+  gPBackgroundThread = NS_GetCurrentThread();
+  MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
+  gInstance = this;
 }
 
 U2FHIDTokenManager::~U2FHIDTokenManager()
 {
+  StaticMutexAutoLock lock(gInstanceMutex);
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+  mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+
+  rust_u2f_mgr_free(mU2FManager);
+  mU2FManager = nullptr;
+  gInstance = nullptr;
 }
 
 // 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.
 //
@@ -37,17 +94,32 @@ U2FHIDTokenManager::~U2FHIDTokenManager(
 // *      attestation signature
 //
 RefPtr<U2FRegisterPromise>
 U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
                              const nsTArray<uint8_t>& aApplication,
                              const nsTArray<uint8_t>& aChallenge,
                              uint32_t aTimeoutMS)
 {
-  return U2FRegisterPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  ClearPromises();
+  mTransactionId = rust_u2f_mgr_register(mU2FManager,
+                                         (uint64_t)aTimeoutMS,
+                                         u2f_register_callback,
+                                         aChallenge.Elements(),
+                                         aChallenge.Length(),
+                                         aApplication.Elements(),
+                                         aApplication.Length());
+
+  if (mTransactionId == 0) {
+    return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+  }
+
+  return mRegisterPromise.Ensure(__func__);
 }
 
 // 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
@@ -62,18 +134,85 @@ U2FHIDTokenManager::Register(const nsTAr
 //  *     Signature
 //
 RefPtr<U2FSignPromise>
 U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
                          const nsTArray<uint8_t>& aApplication,
                          const nsTArray<uint8_t>& aChallenge,
                          uint32_t aTimeoutMS)
 {
-  return U2FSignPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  ClearPromises();
+  mTransactionId = rust_u2f_mgr_sign(mU2FManager,
+                                     (uint64_t)aTimeoutMS,
+                                     u2f_sign_callback,
+                                     aChallenge.Elements(),
+                                     aChallenge.Length(),
+                                     aApplication.Elements(),
+                                     aApplication.Length(),
+                                     U2FKeyHandles(aDescriptors).Get());
+  if (mTransactionId == 0) {
+    return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+  }
+
+  return mSignPromise.Ensure(__func__);
 }
 
 void
 U2FHIDTokenManager::Cancel()
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  ClearPromises();
+  mTransactionId = rust_u2f_mgr_cancel(mU2FManager);
+}
+
+void
+U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  if (aResult->GetTransactionId() != mTransactionId) {
+    return;
+  }
+
+  MOZ_ASSERT(!mRegisterPromise.IsEmpty());
+
+  nsTArray<uint8_t> registration;
+  if (!aResult->CopyRegistration(registration)) {
+    mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    return;
+  }
+
+  U2FRegisterResult result(Move(registration));
+  mRegisterPromise.Resolve(Move(result), __func__);
+}
+
+void
+U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+  if (aResult->GetTransactionId() != mTransactionId) {
+    return;
+  }
+
+  MOZ_ASSERT(!mSignPromise.IsEmpty());
+
+  nsTArray<uint8_t> keyHandle;
+  if (!aResult->CopyKeyHandle(keyHandle)) {
+    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    return;
+  }
+
+  nsTArray<uint8_t> signature;
+  if (!aResult->CopySignature(signature)) {
+    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    return;
+  }
+
+  U2FSignResult result(Move(keyHandle), Move(signature));
+  mSignPromise.Resolve(Move(result), __func__);
 }
 
 }
 }
--- a/dom/webauthn/U2FHIDTokenManager.h
+++ b/dom/webauthn/U2FHIDTokenManager.h
@@ -3,25 +3,93 @@
 /* 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_U2FHIDTokenManager_h
 #define mozilla_dom_U2FHIDTokenManager_h
 
 #include "mozilla/dom/U2FTokenTransport.h"
+#include "u2f-hid-rs/src/u2fhid-capi.h"
 
 /*
  * U2FHIDTokenManager is a Rust implementation of a secure token manager
  * for the U2F and WebAuthn APIs, talking to HIDs.
  */
 
 namespace mozilla {
 namespace dom {
 
+class U2FKeyHandles {
+public:
+  explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors)
+  {
+    mKeyHandles = rust_u2f_khs_new();
+
+    for (auto desc: aDescriptors) {
+      rust_u2f_khs_add(mKeyHandles, desc.id().Elements(), desc.id().Length());
+    }
+  }
+
+  rust_u2f_key_handles* Get() { return mKeyHandles; }
+
+  ~U2FKeyHandles() { rust_u2f_khs_free(mKeyHandles); }
+
+private:
+  rust_u2f_key_handles* mKeyHandles;
+};
+
+class U2FResult {
+public:
+  explicit U2FResult(uint64_t aTransactionId, rust_u2f_result* aResult)
+    : mTransactionId(aTransactionId)
+    , mResult(aResult)
+  { }
+
+  ~U2FResult() { rust_u2f_res_free(mResult); }
+
+  uint64_t GetTransactionId() { return mTransactionId; }
+
+  bool CopyRegistration(nsTArray<uint8_t>& aBuffer)
+  {
+    return CopyBuffer(U2F_RESBUF_ID_REGISTRATION, aBuffer);
+  }
+
+  bool CopyKeyHandle(nsTArray<uint8_t>& aBuffer)
+  {
+    return CopyBuffer(U2F_RESBUF_ID_KEYHANDLE, aBuffer);
+  }
+
+  bool CopySignature(nsTArray<uint8_t>& aBuffer)
+  {
+    return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
+  }
+
+private:
+  bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
+    if (!mResult) {
+      return false;
+    }
+
+    size_t len;
+    if (!rust_u2f_resbuf_length(mResult, aResBufID, &len)) {
+      return false;
+    }
+
+    if (!aBuffer.SetLength(len, fallible)) {
+      return false;
+    }
+
+    return rust_u2f_resbuf_copy(mResult, aResBufID, aBuffer.Elements());
+  }
+
+  uint64_t mTransactionId;
+  rust_u2f_result* mResult;
+};
+
 class U2FHIDTokenManager final : public U2FTokenTransport
 {
 public:
   explicit U2FHIDTokenManager();
 
   virtual RefPtr<U2FRegisterPromise>
   Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
            const nsTArray<uint8_t>& aApplication,
@@ -31,16 +99,29 @@ public:
   virtual RefPtr<U2FSignPromise>
   Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
        const nsTArray<uint8_t>& aApplication,
        const nsTArray<uint8_t>& aChallenge,
        uint32_t aTimeoutMS) override;
 
   void Cancel() override;
 
+  void HandleRegisterResult(UniquePtr<U2FResult>&& aResult);
+  void HandleSignResult(UniquePtr<U2FResult>&& aResult);
+
 private:
   ~U2FHIDTokenManager();
+
+  void ClearPromises() {
+    mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+  }
+
+  rust_u2f_manager* mU2FManager;
+  uint64_t mTransactionId;
+  MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
+  MozPromiseHolder<U2FSignPromise> mSignPromise;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2FHIDTokenManager_h
--- a/dom/webauthn/U2FTokenTransport.h
+++ b/dom/webauthn/U2FTokenTransport.h
@@ -54,17 +54,17 @@ private:
 };
 
 typedef MozPromise<U2FRegisterResult, nsresult, true> U2FRegisterPromise;
 typedef MozPromise<U2FSignResult, nsresult, true> U2FSignPromise;
 
 class U2FTokenTransport
 {
 public:
-  NS_INLINE_DECL_REFCOUNTING(U2FTokenTransport);
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FTokenTransport);
   U2FTokenTransport() {}
 
   virtual RefPtr<U2FRegisterPromise>
   Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
            const nsTArray<uint8_t>& aApplication,
            const nsTArray<uint8_t>& aChallenge,
            uint32_t aTimeoutMS) = 0;
 
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -52,10 +52,15 @@ FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/crypto',
     '/security/manager/ssl',
     '/security/pkix/include',
     '/security/pkix/lib',
 ]
 
+if CONFIG['OS_ARCH'] == 'WINNT':
+    OS_LIBS += [
+        'hid',
+    ]
+
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']