Bug 1388851 - Implement U2FHIDTokenManager r=jcj,qdot,keeler draft
authorTim Taubert <ttaubert@mozilla.com>
Wed, 09 Aug 2017 21:24:50 +0200
changeset 665178 87cc6f7d362ee6d4423f878ec57bb6e7f405f6c8
parent 665177 00793d6f6e85ed9b01ac54536d71a480f8bed9b7
child 665179 97d8a2caa657aeea81413433f9ea9700ff17a5c2
push id79949
push userbmo:jbeich@FreeBSD.org
push dateFri, 15 Sep 2017 00:31:58 +0000
reviewersjcj, qdot, keeler
bugs1388851
milestone57.0a1
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']