Bug 1406467 - Web Authentication - WD-07 Updates to Make Assertion r=jcj,smaug
authorTim Taubert <ttaubert@mozilla.com>
Tue, 09 Jan 2018 07:27:35 +0100
changeset 452621 7b0bbd94352de559b0656c2b19e482c679ea34b8
parent 452620 d37f5eba96bab7fd25ff935c97f169dc64714886
child 452622 eef0978b42ad4765f71c8c9abe9e5b4a7356da68
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj, smaug
bugs1406467
milestone59.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 1406467 - Web Authentication - WD-07 Updates to Make Assertion r=jcj,smaug Summary: Add support for PublicKeyCredentialRequestOptions.userVerification. For now this basically means that we'll abort the operation with NotAllowed, as we don't support user verification yet. Pass PublicKeyCredentialDescriptor.transports through to the token manager implementations. The softoken will ignore those and pretend to support all transports defined by the spec. The USB HID token will check for the "usb" transport and either ignore credentials accordingly, or abort the operation. Note: The `UserVerificationRequirement` in WebIDL is defined at https://w3c.github.io/webauthn/#assertion-options Reviewers: jcj, smaug Reviewed By: jcj, smaug Bug #: 1406467 Differential Revision: https://phabricator.services.mozilla.com/D338
dom/u2f/U2F.cpp
dom/webauthn/PWebAuthnTransaction.ipdl
dom/webauthn/U2FHIDTokenManager.cpp
dom/webauthn/U2FHIDTokenManager.h
dom/webauthn/U2FSoftTokenManager.cpp
dom/webauthn/U2FSoftTokenManager.h
dom/webauthn/U2FTokenManager.cpp
dom/webauthn/U2FTokenTransport.h
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/tests/mochitest.ini
dom/webauthn/tests/test_webauthn_authenticator_selection.html
dom/webauthn/tests/test_webauthn_authenticator_transports.html
dom/webauthn/u2f-hid-rs/examples/main.rs
dom/webauthn/u2f-hid-rs/src/capi.rs
dom/webauthn/u2f-hid-rs/src/lib.rs
dom/webauthn/u2f-hid-rs/src/manager.rs
dom/webauthn/u2f-hid-rs/src/statemachine.rs
dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
dom/webidl/WebAuthentication.webidl
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -91,17 +91,17 @@ AssembleClientData(const nsAString& aOri
   }
 
   return NS_OK;
 }
 
 static void
 RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
   const nsTArray<RegisteredKey>& aKeys,
-  nsTArray<WebAuthnScopedCredentialDescriptor>& aList)
+  nsTArray<WebAuthnScopedCredential>& aList)
 {
   for (const RegisteredKey& key : aKeys) {
     // Check for required attributes
     if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() ||
         key.mVersion.Value() != kRequiredU2FVersion) {
       continue;
     }
 
@@ -111,17 +111,17 @@ RegisteredKeysToScopedCredentialList(con
     }
 
     CryptoBuffer keyHandle;
     nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       continue;
     }
 
-    WebAuthnScopedCredentialDescriptor c;
+    WebAuthnScopedCredential c;
     c.id() = keyHandle;
     aList.AppendElement(c);
   }
 }
 
 static ErrorCode
 EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
               /* in/out */ nsString& aAppId)
@@ -387,17 +387,17 @@ U2F::Register(const nsAString& aAppId,
   if (clientDataJSON.IsEmpty()) {
     RegisterResponse response;
     response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
     ExecuteCallback(response, callback);
     return;
   }
 
   // Build the exclusion list, if any
-  nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
+  nsTArray<WebAuthnScopedCredential> excludeList;
   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
                                        excludeList);
 
   auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
 
   CryptoBuffer rpIdHash, clientDataHash;
   if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
                                        rpIdHash, clientDataHash))) {
@@ -535,17 +535,17 @@ U2F::Sign(const nsAString& aAppId,
   if (NS_WARN_IF(NS_FAILED(rv))) {
     SignResponse response;
     response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
     ExecuteCallback(response, callback);
     return;
   }
 
   // Build the key list, if any
-  nsTArray<WebAuthnScopedCredentialDescriptor> permittedList;
+  nsTArray<WebAuthnScopedCredential> permittedList;
   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
                                        permittedList);
 
   auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
 
   CryptoBuffer rpIdHash, clientDataHash;
   if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
                                        rpIdHash, clientDataHash))) {
@@ -568,16 +568,17 @@ U2F::Sign(const nsAString& aAppId,
   nsTArray<WebAuthnExtension> extensions;
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
   WebAuthnGetAssertionInfo info(rpIdHash,
                                 clientDataHash,
                                 adjustedTimeoutMillis,
                                 permittedList,
+                                false, /* requireUserVerification */
                                 extensions);
 
   MOZ_ASSERT(mTransaction.isNothing());
   mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
   mChild->SendRequestSign(mTransaction.ref().mId, info);
 }
 
 void
--- a/dom/webauthn/PWebAuthnTransaction.ipdl
+++ b/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -15,43 +15,45 @@
  */
 
 include protocol PBackground;
 
 namespace mozilla {
 namespace dom {
 
 struct WebAuthnAuthenticatorSelection {
-    bool requireResidentKey;
-    bool requireUserVerification;
-    bool requirePlatformAttachment;
+  bool requireResidentKey;
+  bool requireUserVerification;
+  bool requirePlatformAttachment;
 };
 
-struct WebAuthnScopedCredentialDescriptor {
+struct WebAuthnScopedCredential {
   uint8_t[] id;
+  uint8_t transports;
 };
 
 struct WebAuthnExtension {
   /* TODO Fill in with predefined extensions */
 };
 
 struct WebAuthnMakeCredentialInfo {
   uint8_t[] RpIdHash;
   uint8_t[] ClientDataHash;
   uint32_t TimeoutMS;
-  WebAuthnScopedCredentialDescriptor[] ExcludeList;
+  WebAuthnScopedCredential[] ExcludeList;
   WebAuthnExtension[] Extensions;
   WebAuthnAuthenticatorSelection AuthenticatorSelection;
 };
 
 struct WebAuthnGetAssertionInfo {
   uint8_t[] RpIdHash;
   uint8_t[] ClientDataHash;
   uint32_t TimeoutMS;
-  WebAuthnScopedCredentialDescriptor[] AllowList;
+  WebAuthnScopedCredential[] AllowList;
+  bool RequireUserVerification;
   WebAuthnExtension[] Extensions;
 };
 
 async protocol PWebAuthnTransaction {
   manager PBackground;
 
   parent:
     async RequestRegister(uint64_t aTransactionId, WebAuthnMakeCredentialInfo aTransactionInfo);
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -95,17 +95,17 @@ U2FHIDTokenManager::~U2FHIDTokenManager(
 // 1      0x05
 // 65     public key
 // 1      key handle length
 // *      key handle
 // ASN.1  attestation certificate
 // *      attestation signature
 //
 RefPtr<U2FRegisterPromise>
-U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
                              const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
                              const nsTArray<uint8_t>& aApplication,
                              const nsTArray<uint8_t>& aChallenge,
                              uint32_t aTimeoutMS)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
 
   uint64_t registerFlags = 0;
@@ -125,17 +125,17 @@ U2FHIDTokenManager::Register(const nsTAr
   mTransactionId = rust_u2f_mgr_register(mU2FManager,
                                          registerFlags,
                                          (uint64_t)aTimeoutMS,
                                          u2f_register_callback,
                                          aChallenge.Elements(),
                                          aChallenge.Length(),
                                          aApplication.Elements(),
                                          aApplication.Length(),
-                                         U2FKeyHandles(aDescriptors).Get());
+                                         U2FKeyHandles(aCredentials).Get());
 
   if (mTransactionId == 0) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
   }
 
   return mRegisterPromise.Ensure(__func__);
 }
 
@@ -151,32 +151,41 @@ U2FHIDTokenManager::Register(const nsTAr
 //
 // The format of the signature data is as follows:
 //
 //  1     User presence
 //  4     Counter
 //  *     Signature
 //
 RefPtr<U2FSignPromise>
-U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
                          const nsTArray<uint8_t>& aApplication,
                          const nsTArray<uint8_t>& aChallenge,
+                         bool aRequireUserVerification,
                          uint32_t aTimeoutMS)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
 
+  uint64_t signFlags = 0;
+
+  // Set flags for credential requests.
+  if (aRequireUserVerification) {
+    signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
+  }
+
   ClearPromises();
   mTransactionId = rust_u2f_mgr_sign(mU2FManager,
+                                     signFlags,
                                      (uint64_t)aTimeoutMS,
                                      u2f_sign_callback,
                                      aChallenge.Elements(),
                                      aChallenge.Length(),
                                      aApplication.Elements(),
                                      aApplication.Length(),
-                                     U2FKeyHandles(aDescriptors).Get());
+                                     U2FKeyHandles(aCredentials).Get());
   if (mTransactionId == 0) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
   }
 
   return mSignPromise.Ensure(__func__);
 }
 
 void
--- a/dom/webauthn/U2FHIDTokenManager.h
+++ b/dom/webauthn/U2FHIDTokenManager.h
@@ -15,22 +15,25 @@
  * for the U2F and WebAuthn APIs, talking to HIDs.
  */
 
 namespace mozilla {
 namespace dom {
 
 class U2FKeyHandles {
 public:
-  explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors)
+  explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
   {
     mKeyHandles = rust_u2f_khs_new();
 
-    for (auto desc: aDescriptors) {
-      rust_u2f_khs_add(mKeyHandles, desc.id().Elements(), desc.id().Length());
+    for (auto cred: aCredentials) {
+      rust_u2f_khs_add(mKeyHandles,
+                       cred.id().Elements(),
+                       cred.id().Length(),
+                       cred.transports());
     }
   }
 
   rust_u2f_key_handles* Get() { return mKeyHandles; }
 
   ~U2FKeyHandles() { rust_u2f_khs_free(mKeyHandles); }
 
 private:
@@ -86,26 +89,27 @@ private:
 };
 
 class U2FHIDTokenManager final : public U2FTokenTransport
 {
 public:
   explicit U2FHIDTokenManager();
 
   virtual RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
            const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
            const nsTArray<uint8_t>& aApplication,
            const nsTArray<uint8_t>& aChallenge,
            uint32_t aTimeoutMS) override;
 
   virtual RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
        const nsTArray<uint8_t>& aApplication,
        const nsTArray<uint8_t>& aChallenge,
+       bool aRequireUserVerification,
        uint32_t aTimeoutMS) override;
 
   void Cancel() override;
 
   void HandleRegisterResult(UniquePtr<U2FResult>&& aResult);
   void HandleSignResult(UniquePtr<U2FResult>&& aResult);
 
 private:
--- a/dom/webauthn/U2FSoftTokenManager.cpp
+++ b/dom/webauthn/U2FSoftTokenManager.cpp
@@ -620,17 +620,17 @@ U2FSoftTokenManager::IsRegistered(const 
 // 1      0x05
 // 65     public key
 // 1      key handle length
 // *      key handle
 // ASN.1  attestation certificate
 // *      attestation signature
 //
 RefPtr<U2FRegisterPromise>
-U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
                               const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
                               const nsTArray<uint8_t>& aApplication,
                               const nsTArray<uint8_t>& aChallenge,
                               uint32_t aTimeoutMS)
 {
   nsNSSShutDownPreventionLock locker;
   if (NS_WARN_IF(isAlreadyShutDown())) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
@@ -647,19 +647,19 @@ U2FSoftTokenManager::Register(const nsTA
   // user verification, nor is it a platform authenticator.
   if (aAuthenticatorSelection.requireResidentKey() ||
       aAuthenticatorSelection.requireUserVerification() ||
       aAuthenticatorSelection.requirePlatformAttachment()) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
   }
 
   // Optional exclusion list.
-  for (auto desc: aDescriptors) {
+  for (auto cred: aCredentials) {
     bool isRegistered = false;
-    nsresult rv = IsRegistered(desc.id(), aApplication, isRegistered);
+    nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
     if (NS_FAILED(rv)) {
       return U2FRegisterPromise::CreateAndReject(rv, __func__);
     }
     if (isRegistered) {
       return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
     }
   }
 
@@ -753,32 +753,38 @@ U2FSoftTokenManager::Register(const nsTA
 //
 // The format of the signature data is as follows:
 //
 //  1     User presence
 //  4     Counter
 //  *     Signature
 //
 RefPtr<U2FSignPromise>
-U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
                           const nsTArray<uint8_t>& aApplication,
                           const nsTArray<uint8_t>& aChallenge,
+                          bool aRequireUserVerification,
                           uint32_t aTimeoutMS)
 {
   nsNSSShutDownPreventionLock locker;
   if (NS_WARN_IF(isAlreadyShutDown())) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
   }
 
+  // The U2F softtoken doesn't support user verification.
+  if (aRequireUserVerification) {
+    return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+  }
+
   nsTArray<uint8_t> keyHandle;
-  for (auto desc: aDescriptors) {
+  for (auto cred: aCredentials) {
     bool isRegistered = false;
-    nsresult rv = IsRegistered(desc.id(), aApplication, isRegistered);
+    nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
     if (NS_SUCCEEDED(rv) && isRegistered) {
-      keyHandle.Assign(desc.id());
+      keyHandle.Assign(cred.id());
       break;
     }
   }
 
   // Fail if we didn't recognize a key id.
   if (keyHandle.IsEmpty()) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
   }
--- a/dom/webauthn/U2FSoftTokenManager.h
+++ b/dom/webauthn/U2FSoftTokenManager.h
@@ -21,26 +21,27 @@ namespace dom {
 
 class U2FSoftTokenManager final : public U2FTokenTransport,
                                   public nsNSSShutDownObject
 {
 public:
   explicit U2FSoftTokenManager(uint32_t aCounter);
 
   virtual RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
            const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
            const nsTArray<uint8_t>& aApplication,
            const nsTArray<uint8_t>& aChallenge,
            uint32_t aTimeoutMS) override;
 
   virtual RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
        const nsTArray<uint8_t>& aApplication,
        const nsTArray<uint8_t>& aChallenge,
+       bool aRequireUserVerification,
        uint32_t aTimeoutMS) override;
 
   virtual void Cancel() override;
 
   // For nsNSSShutDownObject
   virtual void virtualDestroyNSSReference() override;
   void destructorSafeDestroyNSSReference();
 
--- a/dom/webauthn/U2FTokenManager.cpp
+++ b/dom/webauthn/U2FTokenManager.cpp
@@ -317,16 +317,17 @@ U2FTokenManager::Sign(PWebAuthnTransacti
     return;
   }
 
   uint64_t tid = mLastTransactionId = aTransactionId;
   mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
   mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
                           aTransactionInfo.RpIdHash(),
                           aTransactionInfo.ClientDataHash(),
+                          aTransactionInfo.RequireUserVerification(),
                           aTransactionInfo.TimeoutMS())
                    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                      [tid, startTime](U2FSignResult&& aResult) {
                        U2FTokenManager* mgr = U2FTokenManager::Get();
                        mgr->MaybeConfirmSign(tid, aResult);
                        Telemetry::ScalarAdd(
                          Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
                          NS_LITERAL_STRING("U2FSignFinish"), 1);
--- a/dom/webauthn/U2FTokenTransport.h
+++ b/dom/webauthn/U2FTokenTransport.h
@@ -58,26 +58,27 @@ typedef MozPromise<U2FSignResult, nsresu
 
 class U2FTokenTransport
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FTokenTransport);
   U2FTokenTransport() {}
 
   virtual RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
            const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
            const nsTArray<uint8_t>& aApplication,
            const nsTArray<uint8_t>& aChallenge,
            uint32_t aTimeoutMS) = 0;
 
   virtual RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredentialDescriptor>& aDescriptors,
+  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
        const nsTArray<uint8_t>& aApplication,
        const nsTArray<uint8_t>& aChallenge,
+       bool aRequireUserVerification,
        uint32_t aTimeoutMS) = 0;
 
   virtual void Cancel() = 0;
 
 protected:
   virtual ~U2FTokenTransport() = default;
 };
 
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -356,19 +356,19 @@ WebAuthnManager::MakeCredential(const Ma
   }
 
   srv = HashCString(hashService, clientDataJSON, clientDataHash);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
-  nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
+  nsTArray<WebAuthnScopedCredential> excludeList;
   for (const auto& s: aOptions.mExcludeCredentials) {
-    WebAuthnScopedCredentialDescriptor c;
+    WebAuthnScopedCredential c;
     CryptoBuffer cb;
     cb.Assign(s.mId);
     c.id() = cb;
     excludeList.AppendElement(c);
   }
 
   if (!MaybeCreateBackgroundActor()) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
@@ -528,42 +528,67 @@ WebAuthnManager::GetAssertion(const Publ
 
   // Note: we only support U2F-style authentication for now, so we effectively
   // require an AllowList.
   if (aOptions.mAllowCredentials.Length() < 1) {
     promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     return promise.forget();
   }
 
-  nsTArray<WebAuthnScopedCredentialDescriptor> allowList;
+  nsTArray<WebAuthnScopedCredential> allowList;
   for (const auto& s: aOptions.mAllowCredentials) {
-    WebAuthnScopedCredentialDescriptor c;
-    CryptoBuffer cb;
-    cb.Assign(s.mId);
-    c.id() = cb;
-    allowList.AppendElement(c);
+    if (s.mType == PublicKeyCredentialType::Public_key) {
+      WebAuthnScopedCredential c;
+      CryptoBuffer cb;
+      cb.Assign(s.mId);
+      c.id() = cb;
+
+      // Serialize transports.
+      if (s.mTransports.WasPassed()) {
+        uint8_t transports = 0;
+        for (const auto& t: s.mTransports.Value()) {
+          if (t == AuthenticatorTransport::Usb) {
+            transports |= U2F_AUTHENTICATOR_TRANSPORT_USB;
+          }
+          if (t == AuthenticatorTransport::Nfc) {
+            transports |= U2F_AUTHENTICATOR_TRANSPORT_NFC;
+          }
+          if (t == AuthenticatorTransport::Ble) {
+            transports |= U2F_AUTHENTICATOR_TRANSPORT_BLE;
+          }
+        }
+        c.transports() = transports;
+      }
+
+      allowList.AppendElement(c);
+    }
   }
 
   if (!MaybeCreateBackgroundActor()) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
     return promise.forget();
   }
 
+  // Does the RP require user verification?
+  bool requireUserVerification =
+    aOptions.mUserVerification == UserVerificationRequirement::Required;
+
   // TODO: Add extension list building
   // If extensions was specified, process any extensions supported by this
   // client platform, to produce the extension data that needs to be sent to the
   // authenticator. If an error is encountered while processing an extension,
   // skip that extension and do not produce any extension data for it. Call the
   // result of this processing clientExtensions.
   nsTArray<WebAuthnExtension> extensions;
 
   WebAuthnGetAssertionInfo info(rpIdHash,
                                 clientDataHash,
                                 adjustedTimeout,
                                 allowList,
+                                requireUserVerification,
                                 extensions);
 
   ListenForVisibilityEvents();
 
   AbortSignal* signal = nullptr;
   if (aSignal.WasPassed()) {
     signal = &aSignal.Value();
     Follow(signal);
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -3,16 +3,17 @@ support-files =
   cbor/*
   pkijs/*
   u2futil.js
 skip-if = !e10s
 scheme = https
 
 [test_webauthn_abort_signal.html]
 [test_webauthn_authenticator_selection.html]
+[test_webauthn_authenticator_transports.html]
 [test_webauthn_loopback.html]
 [test_webauthn_no_token.html]
 [test_webauthn_make_credential.html]
 [test_webauthn_get_assertion.html]
 [test_webauthn_override_request.html]
 [test_webauthn_store_credential.html]
 [test_webauthn_sameorigin.html]
 [test_webauthn_isplatformauthenticatoravailable.html]
--- a/dom/webauthn/tests/test_webauthn_authenticator_selection.html
+++ b/dom/webauthn/tests/test_webauthn_authenticator_selection.html
@@ -6,32 +6,37 @@
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>W3C Web Authentication - Authenticator Selection Criteria</h1>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406462">Mozilla Bug 1406462</a>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
 
   <script class="testbody" type="text/javascript">
     "use strict";
 
     function arrivingHereIsGood(aResult) {
       ok(true, "Good result! Received a: " + aResult);
     }
 
     function arrivingHereIsBad(aResult) {
       ok(false, "Bad result! Received a: " + aResult);
     }
 
     function expectNotAllowedError(aResult) {
       ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
     }
 
+    // We store the credential of the first successful make credential
+    // operation so we can use it for get assertion tests later.
+    let gCredential;
+
     add_task(() => {
       // Enable the softtoken.
       return SpecialPowers.pushPrefEnv({"set": [
         ["security.webauth.webauthn", true],
         ["security.webauth.webauthn_enable_softtoken", true],
         ["security.webauth.webauthn_enable_usbtoken", false],
       ]});
     });
@@ -45,20 +50,44 @@
         timeout: 5000, // the minimum timeout is actually 15 seconds
         pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
         authenticatorSelection,
       };
 
       return navigator.credentials.create({publicKey});
     }
 
-    // Test success cases.
+    // Start a new GetAssertion() request.
+    function requestGetAssertion(userVerification) {
+      let newCredential = {
+        type: "public-key",
+        id: gCredential,
+        transports: ["usb"],
+      };
+
+      let publicKey = {
+        challenge: crypto.getRandomValues(new Uint8Array(16)),
+        timeout: 5000, // the minimum timeout is actually 15 seconds
+        rpId: document.domain,
+        allowCredentials: [newCredential]
+      };
+
+      if (userVerification) {
+        publicKey.userVerification = userVerification;
+      }
+
+      return navigator.credentials.get({publicKey});
+    }
+
+    // Test success cases for make credential.
     add_task(async () => {
       // No selection criteria.
       await requestMakeCredential({})
+         // Save the credential so we can use it for sign success tests.
+        .then(res => gCredential = res.rawId)
         .then(arrivingHereIsGood)
         .catch(arrivingHereIsBad);
 
       // Request a cross-platform authenticator.
       await requestMakeCredential({authenticatorAttachment: "cross-platform"})
         .then(arrivingHereIsGood)
         .catch(arrivingHereIsBad);
 
@@ -73,29 +102,55 @@
         .catch(arrivingHereIsBad);
 
       // Discourage user verification.
       await requestMakeCredential({userVerification: "discouraged"})
         .then(arrivingHereIsGood)
         .catch(arrivingHereIsBad);
     });
 
-    // Test the failure cases.
+    // Test success cases for get assertion.
+    add_task(async () => {
+      // No selection criteria.
+      await requestGetAssertion()
+        .then(arrivingHereIsGood)
+        .catch(arrivingHereIsBad);
+
+      // Prefer user verification.
+      await requestGetAssertion("preferred")
+        .then(arrivingHereIsGood)
+        .catch(arrivingHereIsBad);
+
+      // Discourage user verification.
+      await requestGetAssertion("discouraged")
+        .then(arrivingHereIsGood)
+        .catch(arrivingHereIsBad);
+    });
+
+    // Test failure cases for make credential.
     add_task(async () => {
       // Request a platform authenticator.
       await requestMakeCredential({authenticatorAttachment: "platform"})
         .then(arrivingHereIsBad)
         .catch(expectNotAllowedError);
 
       // Require a resident key.
       await requestMakeCredential({requireResidentKey: true})
         .then(arrivingHereIsBad)
         .catch(expectNotAllowedError);
 
       // Require user verification.
       await requestMakeCredential({userVerification: "required"})
         .then(arrivingHereIsBad)
         .catch(expectNotAllowedError);
     });
+
+    // Test failure cases for get assertion.
+    add_task(async () => {
+      // Require user verification.
+      await requestGetAssertion("required")
+        .then(arrivingHereIsBad)
+        .catch(expectNotAllowedError);
+    });
   </script>
 
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_authenticator_transports.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>W3C Web Authentication - Authenticator Transports</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+  <h1>W3C Web Authentication - Authenticator Transports</h1>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
+
+  <script class="testbody" type="text/javascript">
+    "use strict";
+
+    function arrivingHereIsGood(aResult) {
+      ok(true, "Good result! Received a: " + aResult);
+    }
+
+    function arrivingHereIsBad(aResult) {
+      ok(false, "Bad result! Received a: " + aResult);
+    }
+
+    function expectNotAllowedError(aResult) {
+      ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
+    }
+
+    // Store the credential of the first successful make credential
+    // operation so we can use it to get assertions later.
+    let gCredential;
+
+    add_task(() => {
+      // Enable the softtoken.
+      return SpecialPowers.pushPrefEnv({"set": [
+        ["security.webauth.webauthn", true],
+        ["security.webauth.webauthn_enable_softtoken", true],
+        ["security.webauth.webauthn_enable_usbtoken", false],
+      ]});
+    });
+
+    // Start a new MakeCredential() request.
+    function requestMakeCredential(excludeCredentials) {
+      let publicKey = {
+        rp: {id: document.domain, name: "none", icon: "none"},
+        user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
+        challenge: crypto.getRandomValues(new Uint8Array(16)),
+        timeout: 5000, // the minimum timeout is actually 15 seconds
+        pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+        excludeCredentials
+      };
+
+      return navigator.credentials.create({publicKey});
+    }
+
+    // Start a new GetAssertion() request.
+    function requestGetAssertion(allowCredentials) {
+      let publicKey = {
+        challenge: crypto.getRandomValues(new Uint8Array(16)),
+        timeout: 5000, // the minimum timeout is actually 15 seconds
+        rpId: document.domain,
+        allowCredentials
+      };
+
+      return navigator.credentials.get({publicKey});
+    }
+
+    // Test make credential behavior.
+    add_task(async () => {
+      // Make a credential.
+      await requestMakeCredential([])
+         // Save the credential for later.
+        .then(res => gCredential = res.rawId)
+        .then(arrivingHereIsGood)
+        .catch(arrivingHereIsBad);
+
+      // Pass a random credential to exclude.
+      await requestMakeCredential([{
+        type: "public-key",
+        id: crypto.getRandomValues(new Uint8Array(16)),
+        transports: ["usb"],
+      }]).then(arrivingHereIsGood)
+         .catch(arrivingHereIsBad);
+
+      // Pass gCredential with transport=usb.
+      await requestMakeCredential([{
+        type: "public-key",
+        id: gCredential,
+        transports: ["usb"],
+      }]).then(arrivingHereIsBad)
+         .catch(expectNotAllowedError);
+
+      // Pass gCredential with transport=nfc.
+      // The softoken pretends to support all transports.
+      await requestMakeCredential([{
+        type: "public-key",
+        id: gCredential,
+        transports: ["nfc"],
+      }]).then(arrivingHereIsBad)
+         .catch(expectNotAllowedError);
+
+      // Pass gCredential with an empty transports list.
+      await requestMakeCredential([{
+        type: "public-key",
+        id: gCredential,
+        transports: [],
+      }]).then(arrivingHereIsBad)
+         .catch(expectNotAllowedError);
+    });
+
+    // Test get assertion behavior.
+    add_task(async () => {
+      // Request an assertion for gCredential.
+      await requestGetAssertion([{
+        type: "public-key",
+        id: gCredential,
+        transports: ["usb"],
+      }]).then(arrivingHereIsGood)
+         .catch(arrivingHereIsBad);
+
+      // Request an assertion for a random credential.
+      await requestGetAssertion([{
+        type: "public-key",
+        id: crypto.getRandomValues(new Uint8Array(16)),
+        transports: ["usb"],
+      }]).then(arrivingHereIsBad)
+         .catch(expectNotAllowedError);
+
+      // Request an assertion for gCredential with transport=nfc.
+      // The softoken pretends to support all transports.
+      await requestGetAssertion([{
+        type: "public-key",
+        id: gCredential,
+        transports: ["nfc"],
+      }]).then(arrivingHereIsGood)
+         .catch(arrivingHereIsBad);
+
+      // Request an assertion for gCredential with an empty transports list.
+      await requestGetAssertion([{
+        type: "public-key",
+        id: gCredential,
+        transports: [],
+      }]).then(arrivingHereIsGood)
+         .catch(arrivingHereIsBad);
+    });
+  </script>
+
+</body>
+</html>
--- a/dom/webauthn/u2f-hid-rs/examples/main.rs
+++ b/dom/webauthn/u2f-hid-rs/examples/main.rs
@@ -4,17 +4,17 @@
 
 extern crate base64;
 extern crate crypto;
 extern crate u2fhid;
 use crypto::digest::Digest;
 use crypto::sha2::Sha256;
 use std::io;
 use std::sync::mpsc::channel;
-use u2fhid::{RegisterFlags, U2FManager};
+use u2fhid::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, U2FManager};
 
 extern crate log;
 extern crate env_logger;
 
 fn u2f_get_key_handle_from_register_response(register_response: &Vec<u8>) -> io::Result<Vec<u8>> {
     if register_response[0] != 0x05 {
         return Err(io::Error::new(
             io::ErrorKind::InvalidData,
@@ -61,21 +61,27 @@ fn main() {
             vec![],
             move |rv| { tx.send(rv.unwrap()).unwrap(); },
         )
         .unwrap();
 
     let register_data = rx.recv().unwrap();
     println!("Register result: {}", base64::encode(&register_data));
     println!("Asking a security key to sign now, with the data from the register...");
-    let key_handle = u2f_get_key_handle_from_register_response(&register_data).unwrap();
+    let credential = u2f_get_key_handle_from_register_response(&register_data).unwrap();
+    let key_handle = KeyHandle {
+        credential,
+        transports: AuthenticatorTransports::empty(),
+    };
 
+    let flags = SignFlags::empty();
     let (tx, rx) = channel();
     manager
         .sign(
+            flags,
             15_000,
             chall_bytes,
             app_bytes,
             vec![key_handle],
             move |rv| { tx.send(rv.unwrap()).unwrap(); },
         )
         .unwrap();
 
--- a/dom/webauthn/u2f-hid-rs/src/capi.rs
+++ b/dom/webauthn/u2f-hid-rs/src/capi.rs
@@ -4,17 +4,17 @@
 
 use libc::size_t;
 use rand::{thread_rng, Rng};
 use std::collections::HashMap;
 use std::{ptr, slice};
 
 use U2FManager;
 
-type U2FKeyHandles = Vec<Vec<u8>>;
+type U2FKeyHandles = Vec<::KeyHandle>;
 type U2FResult = HashMap<u8, Vec<u8>>;
 type U2FCallback = extern "C" fn(u64, *mut U2FResult);
 
 const RESBUF_ID_REGISTRATION: u8 = 0;
 const RESBUF_ID_KEYHANDLE: u8 = 1;
 const RESBUF_ID_SIGNATURE: u8 = 2;
 
 // Generates a new 64-bit transaction id with collision probability 2^-32.
@@ -47,18 +47,22 @@ pub unsafe extern "C" fn rust_u2f_khs_ne
     Box::into_raw(Box::new(vec![]))
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_khs_add(
     khs: *mut U2FKeyHandles,
     key_handle_ptr: *const u8,
     key_handle_len: usize,
+    transports: u8,
 ) {
-    (*khs).push(from_raw(key_handle_ptr, key_handle_len));
+    (*khs).push(::KeyHandle {
+        credential: from_raw(key_handle_ptr, key_handle_len),
+        transports: ::AuthenticatorTransports::from_bits_truncate(transports),
+    });
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
     if !khs.is_null() {
         Box::from_raw(khs);
     }
 }
@@ -151,16 +155,17 @@ pub unsafe extern "C" fn rust_u2f_mgr_re
     );
 
     if res.is_ok() { tid } else { 0 }
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_mgr_sign(
     mgr: *mut U2FManager,
+    flags: u64,
     timeout: u64,
     callback: U2FCallback,
     challenge_ptr: *const u8,
     challenge_len: usize,
     application_ptr: *const u8,
     application_len: usize,
     khs: *const U2FKeyHandles,
 ) -> u64 {
@@ -173,31 +178,39 @@ pub unsafe extern "C" fn rust_u2f_mgr_si
         return 0;
     }
 
     // Need at least one key handle.
     if (*khs).len() < 1 {
         return 0;
     }
 
+    let flags = ::SignFlags::from_bits_truncate(flags);
     let challenge = from_raw(challenge_ptr, challenge_len);
     let application = from_raw(application_ptr, application_len);
     let key_handles = (*khs).clone();
 
     let tid = new_tid();
-    let res = (*mgr).sign(timeout, challenge, application, key_handles, move |rv| {
-        if let Ok((key_handle, signature)) = rv {
-            let mut result = U2FResult::new();
-            result.insert(RESBUF_ID_KEYHANDLE, key_handle);
-            result.insert(RESBUF_ID_SIGNATURE, signature);
-            callback(tid, Box::into_raw(Box::new(result)));
-        } else {
-            callback(tid, ptr::null_mut());
-        };
-    });
+    let res = (*mgr).sign(
+        flags,
+        timeout,
+        challenge,
+        application,
+        key_handles,
+        move |rv| {
+            if let Ok((key_handle, signature)) = rv {
+                let mut result = U2FResult::new();
+                result.insert(RESBUF_ID_KEYHANDLE, key_handle);
+                result.insert(RESBUF_ID_SIGNATURE, signature);
+                callback(tid, Box::into_raw(Box::new(result)));
+            } else {
+                callback(tid, ptr::null_mut());
+            };
+        },
+    );
 
     if res.is_ok() { tid } else { 0 }
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) -> u64 {
     if !mgr.is_null() {
         // Ignore return value.
--- a/dom/webauthn/u2f-hid-rs/src/lib.rs
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -51,15 +51,33 @@ pub use capi::*;
 // Keep this in sync with the constants in u2fhid-capi.h.
 bitflags! {
     pub struct RegisterFlags: u64 {
         const REQUIRE_RESIDENT_KEY        = 1;
         const REQUIRE_USER_VERIFICATION   = 2;
         const REQUIRE_PLATFORM_ATTACHMENT = 4;
     }
 }
+bitflags! {
+    pub struct SignFlags: u64 {
+        const REQUIRE_USER_VERIFICATION = 1;
+    }
+}
+bitflags! {
+    pub struct AuthenticatorTransports: u8 {
+        const USB = 1;
+        const NFC = 2;
+        const BLE = 4;
+    }
+}
+
+#[derive(Clone)]
+pub struct KeyHandle {
+    pub credential: Vec<u8>,
+    pub transports: AuthenticatorTransports,
+}
 
 #[cfg(fuzzing)]
 pub use u2fprotocol::*;
 #[cfg(fuzzing)]
 pub use u2ftypes::*;
 #[cfg(fuzzing)]
 pub use consts::*;
--- a/dom/webauthn/u2f-hid-rs/src/manager.rs
+++ b/dom/webauthn/u2f-hid-rs/src/manager.rs
@@ -12,24 +12,25 @@ use runloop::RunLoop;
 use util::{to_io_err, OnceCallback};
 
 enum QueueAction {
     Register {
         flags: ::RegisterFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: OnceCallback<Vec<u8>>,
     },
     Sign {
+        flags: ::SignFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     },
     Cancel,
 }
 
 pub struct U2FManager {
     queue: RunLoop,
     tx: Sender<QueueAction>,
@@ -59,24 +60,32 @@ impl U2FManager {
                             timeout,
                             challenge,
                             application,
                             key_handles,
                             callback,
                         );
                     }
                     Ok(QueueAction::Sign {
+                           flags,
                            timeout,
                            challenge,
                            application,
                            key_handles,
                            callback,
                        }) => {
                         // This must not block, otherwise we can't cancel.
-                        sm.sign(timeout, challenge, application, key_handles, callback);
+                        sm.sign(
+                            flags,
+                            timeout,
+                            challenge,
+                            application,
+                            key_handles,
+                            callback,
+                        );
                     }
                     Ok(QueueAction::Cancel) => {
                         // Cancelling must block so that we don't start a new
                         // polling thread before the old one has shut down.
                         sm.cancel();
                     }
                     Err(RecvTimeoutError::Disconnected) => {
                         break;
@@ -96,32 +105,32 @@ impl U2FManager {
     }
 
     pub fn register<F>(
         &self,
         flags: ::RegisterFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: F,
     ) -> io::Result<()>
     where
         F: FnOnce(io::Result<Vec<u8>>),
         F: Send + 'static,
     {
         if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
             return Err(io::Error::new(
                 io::ErrorKind::InvalidInput,
                 "Invalid parameter sizes",
             ));
         }
 
         for key_handle in &key_handles {
-            if key_handle.len() > 256 {
+            if key_handle.credential.len() > 256 {
                 return Err(io::Error::new(
                     io::ErrorKind::InvalidInput,
                     "Key handle too large",
                 ));
             }
         }
 
         let callback = OnceCallback::new(callback);
@@ -133,20 +142,21 @@ impl U2FManager {
             key_handles,
             callback,
         };
         self.tx.send(action).map_err(to_io_err)
     }
 
     pub fn sign<F>(
         &self,
+        flags: ::SignFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: F,
     ) -> io::Result<()>
     where
         F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
         F: Send + 'static,
     {
         if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
             return Err(io::Error::new(
@@ -158,26 +168,27 @@ impl U2FManager {
         if key_handles.len() < 1 {
             return Err(io::Error::new(
                 io::ErrorKind::InvalidInput,
                 "No key handles given",
             ));
         }
 
         for key_handle in &key_handles {
-            if key_handle.len() > 256 {
+            if key_handle.credential.len() > 256 {
                 return Err(io::Error::new(
                     io::ErrorKind::InvalidInput,
                     "Key handle too large",
                 ));
             }
         }
 
         let callback = OnceCallback::new(callback);
         let action = QueueAction::Sign {
+            flags,
             timeout,
             challenge,
             application,
             key_handles,
             callback,
         };
         self.tx.send(action).map_err(to_io_err)
     }
--- a/dom/webauthn/u2f-hid-rs/src/statemachine.rs
+++ b/dom/webauthn/u2f-hid-rs/src/statemachine.rs
@@ -5,33 +5,37 @@
 use consts::PARAMETER_SIZE;
 use platform::device::Device;
 use platform::transaction::Transaction;
 use std::thread;
 use std::time::Duration;
 use util::{io_err, OnceCallback};
 use u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
 
+fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
+    transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
+}
+
 #[derive(Default)]
 pub struct StateMachine {
     transaction: Option<Transaction>,
 }
 
 impl StateMachine {
     pub fn new() -> Self {
         Default::default()
     }
 
     pub fn register(
         &mut self,
         flags: ::RegisterFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: OnceCallback<Vec<u8>>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
         let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
@@ -55,18 +59,19 @@ impl StateMachine {
             // same anyway.
             if !flags.is_empty() {
                 return;
             }
 
             // Iterate the exclude list and see if there are any matches.
             // Abort the state machine if we found a valid key handle.
             if key_handles.iter().any(|key_handle| {
-                u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
-                    .unwrap_or(false) /* no match on failure */
+                is_valid_transport(key_handle.transports) &&
+                    u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
+                        .unwrap_or(false) /* no match on failure */
             })
             {
                 return;
             }
 
             while alive() {
                 if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
                     callback.call(Ok(bytes));
@@ -80,20 +85,21 @@ impl StateMachine {
 
         self.transaction = Some(try_or!(transaction, |_| {
             cbc.call(Err(io_err("couldn't create transaction")))
         }));
     }
 
     pub fn sign(
         &mut self,
+        flags: ::SignFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
-        key_handles: Vec<Vec<u8>>,
+        key_handles: Vec<::KeyHandle>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
         let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
@@ -103,39 +109,68 @@ impl StateMachine {
                 _ => return,
             };
 
             // Try initializing it.
             if !dev.is_u2f() || !u2f_init_device(dev) {
                 return;
             }
 
+            // We currently don't support user verification because we can't
+            // ask tokens whether they do support that. If the flag is set,
+            // ignore all tokens for now.
+            //
+            // Technically, this is a ConstraintError because we shouldn't talk
+            // to this authenticator in the first place. But the result is the
+            // same anyway.
+            if !flags.is_empty() {
+                return;
+            }
+
             // Find all matching key handles.
             let key_handles = key_handles
                 .iter()
                 .filter(|key_handle| {
-                    u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
+                    u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
                         .unwrap_or(false) /* no match on failure */
                 })
                 .collect::<Vec<_>>();
 
+            // Aggregate distinct transports from all given credentials.
+            let transports = key_handles.iter().fold(
+                ::AuthenticatorTransports::empty(),
+                |t, k| t | k.transports,
+            );
+
+            // We currently only support USB. If the RP specifies transports
+            // and doesn't include USB it's probably lying.
+            if !is_valid_transport(transports) {
+                return;
+            }
+
             while alive() {
                 // If the device matches none of the given key handles
                 // then just make it blink with bogus data.
                 if key_handles.is_empty() {
                     let blank = vec![0u8; PARAMETER_SIZE];
                     if let Ok(_) = u2f_register(dev, &blank, &blank) {
                         callback.call(Err(io_err("invalid key")));
                         break;
                     }
                 } else {
                     // Otherwise, try to sign.
                     for key_handle in &key_handles {
-                        if let Ok(bytes) = u2f_sign(dev, &challenge, &application, key_handle) {
-                            callback.call(Ok((key_handle.to_vec(), bytes)));
+                        if let Ok(bytes) = u2f_sign(
+                            dev,
+                            &challenge,
+                            &application,
+                            &key_handle.credential,
+                        )
+                        {
+                            callback.call(Ok((key_handle.credential.clone(), bytes)));
                             break;
                         }
                     }
                 }
 
                 // Sleep a bit before trying again.
                 thread::sleep(Duration::from_millis(100));
             }
--- a/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
+++ b/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
@@ -14,16 +14,20 @@ extern "C" {
 const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
 const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
 const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
 
 const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
 const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
 const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4;
 
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1;
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
+
 // NOTE: Preconditions
 // * All rust_u2f_mgr* pointers must refer to pointers which are returned
 //   by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
 // * All rust_u2f_khs* pointers must refer to pointers which are returned
 //   by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free.
 // * All rust_u2f_res* pointers must refer to pointers passed to the
 //   register() and sign() callbacks. They can be null on failure.
 
@@ -51,33 +55,35 @@ uint64_t rust_u2f_mgr_register(rust_u2f_
                                rust_u2f_callback,
                                const uint8_t* challenge_ptr,
                                size_t challenge_len,
                                const uint8_t* application_ptr,
                                size_t application_len,
                                const rust_u2f_key_handles* khs);
 
 uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
+                           uint64_t flags,
                            uint64_t timeout,
                            rust_u2f_callback,
                            const uint8_t* challenge_ptr,
                            size_t challenge_len,
                            const uint8_t* application_ptr,
                            size_t application_len,
                            const rust_u2f_key_handles* khs);
 
 uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
 
 
 /// U2FKeyHandles functions.
 
 rust_u2f_key_handles* rust_u2f_khs_new();
 void rust_u2f_khs_add(rust_u2f_key_handles* khs,
                       const uint8_t* key_handle,
-                      size_t key_handle_len);
+                      size_t key_handle_len,
+                      uint8_t transports);
 /* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
 
 
 /// U2FResult functions.
 
 // Call this before `[..]_copy()` to allocate enough space.
 bool rust_u2f_resbuf_length(const rust_u2f_result *res, uint8_t bid, size_t* len);
 bool rust_u2f_resbuf_copy(const rust_u2f_result *res, uint8_t bid, uint8_t* dst);
--- a/dom/webidl/WebAuthentication.webidl
+++ b/dom/webidl/WebAuthentication.webidl
@@ -1,15 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  *
  * The origin of this IDL file is
- * https://www.w3.org/TR/webauthn/
+ * https://w3c.github.io/webauthn/
  */
 
 /***** Interfaces to Data *****/
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface PublicKeyCredential : Credential {
     [SameObject] readonly attribute ArrayBuffer              rawId;
     [SameObject] readonly attribute AuthenticatorResponse    response;
@@ -89,16 +89,17 @@ enum UserVerificationRequirement {
     "discouraged"
 };
 
 dictionary PublicKeyCredentialRequestOptions {
     required BufferSource                challenge;
     unsigned long                        timeout;
     USVString                            rpId;
     sequence<PublicKeyCredentialDescriptor> allowCredentials = [];
+    UserVerificationRequirement          userVerification = "preferred";
     // Extensions are not supported yet.
     // AuthenticationExtensions             extensions; // Add in Bug 1406458
 };
 
 typedef record<DOMString, any>       AuthenticationExtensions;
 
 dictionary CollectedClientData {
     required DOMString           challenge;