Bug 1435527 - Run to timeout from navigator.credentials.get() when PublicKeyCredentialRequestOptions.allowCredentials is empty r=jcj
authorTim Taubert <ttaubert@mozilla.com>
Thu, 08 Feb 2018 12:45:21 +0100
changeset 402963 c4034229cf62b13f2ca721f6dc9eb52847db6215
parent 402962 e3cc726cd6206dfd4f89b917c9269af014dafb75
child 402964 6d060ad063beb12d350a612edf9e99ebefb83436
push id33407
push usercbrindusan@mozilla.com
push dateThu, 08 Feb 2018 19:02:31 +0000
treeherdermozilla-central@c5120bcaf7bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj
bugs1435527
milestone60.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 1435527 - Run to timeout from navigator.credentials.get() when PublicKeyCredentialRequestOptions.allowCredentials is empty r=jcj Reviewers: jcj Reviewed By: jcj Bug #: 1435527 Differential Revision: https://phabricator.services.mozilla.com/D559
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/tests/test_webauthn_get_assertion.html
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -534,23 +534,16 @@ WebAuthnManager::GetAssertion(const Publ
   }
 
   srv = HashCString(hashService, clientDataJSON, clientDataHash);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
-  // 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<WebAuthnScopedCredential> allowList;
   for (const auto& s: aOptions.mAllowCredentials) {
     if (s.mType == PublicKeyCredentialType::Public_key) {
       WebAuthnScopedCredential c;
       CryptoBuffer cb;
       cb.Assign(s.mId);
       c.id() = cb;
 
--- a/dom/webauthn/tests/test_webauthn_get_assertion.html
+++ b/dom/webauthn/tests/test_webauthn_get_assertion.html
@@ -1,129 +1,167 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
   <title>Tests for GetAssertion for W3C Web Authentication</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>
   <script type="text/javascript" src="pkijs/common.js"></script>
   <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>Tests for GetAssertion for W3C Web Authentication</h1>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
 
   <script class="testbody" type="text/javascript">
     "use strict";
 
-    // Execute the full-scope test
-    SimpleTest.waitForExplicitFinish();
+    is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+    isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+    isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+    isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+    let gAssertionChallenge = new Uint8Array(16);
+    window.crypto.getRandomValues(gAssertionChallenge);
+
+    let invalidCred = {type: "Magic", id: base64ToBytes("AAA=")};
+    let unknownCred = {type: "public-key", id: base64ToBytes("AAA=")};
+
+    function requestGetAssertion(params) {
+      return navigator.credentials.get(params);
+    }
 
     function arrivingHereIsBad(aResult) {
       ok(false, "Bad result! Received a: " + aResult);
-      return Promise.resolve();
     }
 
     function expectNotAllowedError(aResult) {
       ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
-      return Promise.resolve();
     }
 
     function expectTypeError(aResult) {
       ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
-      return Promise.resolve();
+    }
+
+    function expectAbortError(aResult) {
+      is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
     }
 
-    SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
-                                       ["security.webauth.webauthn_enable_softtoken", true],
-                                       ["security.webauth.webauthn_enable_usbtoken", false]]},
-                              runTests);
+    add_task(() => {
+      return SpecialPowers.pushPrefEnv({"set": [
+        ["security.webauth.webauthn", true],
+        ["security.webauth.webauthn_enable_softtoken", true],
+        ["security.webauth.webauthn_enable_usbtoken", false]
+      ]});
+    });
+
+    // Test basic good call, but without giving a credential so expect failures
+    // this is OK by the standard, but not supported by U2F-backed authenticators
+    // like the soft token in use here.
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge
+      };
+
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectNotAllowedError);
+    });
 
-    function runTests() {
-      is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
-      isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
-      isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
-      isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+    // Test with an unexpected option
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        unknownValue: "hi"
+      };
+
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectNotAllowedError);
+    });
 
-      let credm = navigator.credentials;
+    // Test with an invalid credential
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        allowCredentials: [invalidCred]
+      };
 
-      let gAssertionChallenge = new Uint8Array(16);
-      window.crypto.getRandomValues(gAssertionChallenge);
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectTypeError);
+    });
 
-      let invalidCred = {type: "Magic", id: base64ToBytes("AAA=")};
-      let unknownCred = {type: "public-key", id: base64ToBytes("AAA=")};
+    // Test with an unknown credential
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        allowCredentials: [unknownCred]
+      };
 
-      var testFuncs = [
-        function () {
-          // Test basic good call, but without giving a credential so expect failures
-          // this is OK by the standard, but not supported by U2F-backed authenticators
-          // like the soft token in use here.
-          let publicKeyCredentialRequestOptions = {
-            challenge: gAssertionChallenge
-          };
-          return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsBad)
-                      .catch(expectNotAllowedError);
-        },
-        function () {
-          // Test with an unexpected option
-          let publicKeyCredentialRequestOptions = {
-            challenge: gAssertionChallenge,
-            unknownValue: "hi"
-          };
-          return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsBad)
-                      .catch(expectNotAllowedError);
-        },
-        function () {
-          // Test with an invalid credential
-          let publicKeyCredentialRequestOptions = {
-            challenge: gAssertionChallenge,
-            allowCredentials: [invalidCred]
-          };
-          return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsBad)
-                      .catch(expectTypeError);
-        },
-        function () {
-          // Test with an unknown credential
-          let publicKeyCredentialRequestOptions = {
-            challenge: gAssertionChallenge,
-            allowCredentials: [unknownCred]
-          };
-          return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsBad)
-                      .catch(expectNotAllowedError);
-        },
-        function () {
-          // Test with an unexpected option and an invalid credential
-          let publicKeyCredentialRequestOptions = {
-            challenge: gAssertionChallenge,
-            unknownValue: "hi",
-            allowCredentials: [invalidCred]
-          };
-          return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsBad)
-                      .catch(expectTypeError);
-        }
-      ];
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectNotAllowedError);
+    });
+
+    // Test with an unexpected option and an invalid credential
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        unknownValue: "hi",
+        allowCredentials: [invalidCred]
+      };
+
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectTypeError);
+    });
+
+    // Test with an empty credential list
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        allowCredentials: []
+      };
+
+      await requestGetAssertion({publicKey})
+        .then(arrivingHereIsBad)
+        .catch(expectNotAllowedError);
+    });
 
-      var i = 0;
-      var runNextTest = () => {
-        if (i == testFuncs.length) {
-          SimpleTest.finish();
-          return;
-        }
-        testFuncs[i]().then(() => { runNextTest(); });
-        i = i + 1;
+    add_task(() => {
+      // Enable USB tokens.
+      return SpecialPowers.pushPrefEnv({"set": [
+        ["security.webauth.webauthn", true],
+        ["security.webauth.webauthn_enable_softtoken", false],
+        ["security.webauth.webauthn_enable_usbtoken", true]
+      ]});
+    });
+
+    // Test with an empty credential list
+    add_task(async () => {
+      let publicKey = {
+        challenge: gAssertionChallenge,
+        allowCredentials: []
       };
-      runNextTest();
+
+      let ctrl = new AbortController();
+      let request = requestGetAssertion({publicKey, signal: ctrl.signal})
+                         .then(arrivingHereIsBad)
+                         .catch(expectAbortError);
 
-    }
+      // Wait a tick for the statemachine to start.
+      await Promise.resolve();
 
+      // The request should time out. We'll abort it here and will fail or
+      // succeed upon resolution, when the error code is checked.
+      ctrl.abort();
+      await request;
+    });
   </script>
 
 </body>
 </html>