Bug 1329802 - WebAuthn Unit Tests: Add Unit Tests r=keeler
authorJ.C. Jones <jjones@mozilla.com>
Mon, 09 Jan 2017 13:28:02 -0700
changeset 374855 ea3744df8af6f0d3368f83a789cc33e0fea41278
parent 374854 d5479d988affeb3582021817a10d690000616999
child 374856 c622a6c181bf26d645243ae4f3476b7fd9dc78d4
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1329802, 1286312
milestone53.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 1329802 - WebAuthn Unit Tests: Add Unit Tests r=keeler This uses the new mochitest "scheme" option from Bug 1286312. This cannot land until after Bug 1286312 does. For now, you can test locally by adding --setpref dom.securecontext.whitelist=mochi.test to your command line, such as: ~/hg/mozilla-central/mach mochitest \ --setpref dom.securecontext.whitelist=mochi.test ./dom/u2f/tests/ Updated: Review fixes (thanks keeler!) MozReview-Commit-ID: 7jTxF3Mrtcg
dom/u2f/tests/mochitest.ini
dom/u2f/tests/test_webauthn_get_assertion.html
dom/u2f/tests/test_webauthn_loopback.html
dom/u2f/tests/test_webauthn_make_credential.html
dom/u2f/tests/test_webauthn_no_token.html
dom/u2f/tests/test_webauthn_sameorigin.html
dom/u2f/tests/u2futil.js
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -22,8 +22,23 @@ skip-if = !e10s
 [test_register_sign.html]
 skip-if = !e10s
 [test_appid_facet.html]
 skip-if = !e10s
 [test_appid_facet_insecure.html]
 skip-if = !e10s
 [test_appid_facet_subdomain.html]
 skip-if = !e10s
+[test_webauthn_loopback.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_no_token.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_make_credential.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_get_assertion.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_sameorigin.html]
+skip-if = !e10s
+scheme = https
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_webauthn_get_assertion.html
@@ -0,0 +1,91 @@
+<!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="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();
+
+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();
+}
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.w3c", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
+  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
+  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+
+  let authn = navigator.authentication;
+
+  let gAssertionChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(gAssertionChallenge);
+
+  let invalidCred = { type: "Magic", id: base64ToBytes("AAA=") };
+  let unknownCred = { type: "ScopedCred", id: base64ToBytes("AAA=") };
+
+  Promise.all([
+    // 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.
+    authn.getAssertion(gAssertionChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError),
+
+    // Test with an unexpected option
+    authn.getAssertion(gAssertionChallenge, { unknownValue: "hi" })
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError),
+
+    // Test with an invalid credential
+    authn.getAssertion(gAssertionChallenge, { allowList: [invalidCred] })
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with an unknown credential
+    authn.getAssertion(gAssertionChallenge, { allowList: [unknownCred] })
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError),
+
+    // Test with an unexpected option and an invalid credential
+    authn.getAssertion(gAssertionChallenge, { unknownValue: "hi" })
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError)
+  ])
+  .then(function(){
+    SimpleTest.finish();
+  });
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_webauthn_loopback.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>Full-run test for MakeCredential/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();
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.w3c", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
+  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
+  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+
+  let authn = navigator.authentication;
+
+  let gCredentialChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(gCredentialChallenge);
+  let gAssertionChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(gAssertionChallenge);
+
+  testMakeCredential();
+
+  function checkCredentialValid(aCredInfo) {
+    /* ScopedCredentialInfo
+    - Credential
+    -- ID: Key Handle buffer pulled from U2F Register() Response
+    -- Type: "ScopedCred"
+    - WebAuthnAttestation
+    -- Format: "u2f"
+    -- ClientData: serialized JSON
+    -- AuthenticatorData: RP ID Hash || U2F Sign() Response
+    -- Attestation: U2F Register() Response */
+
+    is(aCredInfo.credential.type, "ScopedCred", "Type is correct");
+    ok(aCredInfo.credential.id.length > 0, "Key ID exists");
+
+    is(aCredInfo.attestation.format, "u2f", "Format is correct");
+    is(aCredInfo.attestation.attestation[0], 0x05, "Reserved byte is correct");
+    ok(aCredInfo.attestation.authenticatorData.length > 0, "Authenticator data exists");
+    let clientData = JSON.parse(buffer2string(aCredInfo.attestation.clientData));
+    is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
+    is(clientData.origin, window.location.origin, "Origin is correct");
+    is(clientData.hashAlg, "S256", "Hash algorithm is correct");
+
+    return decodeU2FRegistration(aCredInfo.attestation.attestation)
+    .then(function(u2fObj) {
+      aCredInfo.u2fReg = u2fObj;
+      return aCredInfo;
+    });
+  }
+
+  function checkAssertionAndSigValid(aPublicKey, aAssertion) {
+    /* WebAuthnAssertion
+    - Credential
+    -- ID: ID of Credential from AllowList that succeeded
+    -- Type: "ScopedCred"
+    - ClientData: serialized JSON
+    - AuthenticatorData: RP ID Hash || U2F Sign() Response
+    - Signature: U2F Sign() Response */
+
+    is(aAssertion.credential.type, "ScopedCred", "Type is correct");
+    ok(aAssertion.credential.id.length > 0, "Key ID exists");
+
+    ok(aAssertion.authenticatorData.length > 0, "Authenticator data exists");
+    let clientData = JSON.parse(buffer2string(aAssertion.clientData));
+    is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
+    is(clientData.origin, window.location.origin, "Origin is correct");
+    is(clientData.hashAlg, "S256", "Hash algorithm is correct");
+
+    // Parse the signature data
+    if (aAssertion.signature[0] != 0x01) {
+      throw "User presence byte not set";
+    }
+    let presenceAndCounter = aAssertion.signature.slice(0,5);
+    let signatureValue = aAssertion.signature.slice(5);
+
+    let rpIdHash = aAssertion.authenticatorData.slice(0,32);
+
+    // Assemble the signed data and verify the signature
+    return deriveAppAndChallengeParam(clientData.origin, aAssertion.clientData)
+    .then(function(aParams) {
+      console.log(aParams.appParam, rpIdHash, presenceAndCounter, aParams.challengeParam);
+      console.log("ClientData buffer: ", hexEncode(aAssertion.clientData));
+      console.log("ClientDataHash: ", hexEncode(aParams.challengeParam));
+      return assembleSignedData(aParams.appParam, presenceAndCounter, aParams.challengeParam);
+    })
+    .then(function(aSignedData) {
+      console.log(aPublicKey, aSignedData, signatureValue);
+      return verifySignature(aPublicKey, aSignedData, signatureValue);
+    })
+  }
+
+  function testMakeCredential() {
+    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
+    let param = {type: "ScopedCred", algorithm: "p-256"};
+
+    authn.makeCredential(acct, [param], gCredentialChallenge)
+    .then(checkCredentialValid)
+    .then(testMakeDuplicate)
+    .catch(function(aReason) {
+      ok(false, aReason);
+      SimpleTest.finish();
+    });
+  }
+
+  function testMakeDuplicate(aCredInfo) {
+    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
+    let param = {type: "ScopedCred", algorithm: "p-256"};
+    let options = {rpId: document.origin,
+                   excludeList: [aCredInfo.credential]};
+
+    authn.makeCredential(acct, [param], gCredentialChallenge, options)
+    .then(function() {
+      // We should have errored here!
+      ok(false, "The excludeList didn't stop a duplicate being created!");
+      SimpleTest.finish();
+    })
+    .catch(function(aReason) {
+      ok(aReason.toString().startsWith("NotAllowedError"), "Expect NotAllowedError, got" + aReason);
+      testAssertion(aCredInfo);
+    });
+  }
+
+  function testAssertion(aCredInfo) {
+    let newCredential = {
+      type: aCredInfo.credential.type,
+      id: Uint8Array.from(aCredInfo.credential.id),
+      transports: [ "usb" ],
+    }
+
+    let assertOptions = {rpId: document.origin, timeoutSeconds: 5,
+                         allowList: [ newCredential ]};
+    authn.getAssertion(gAssertionChallenge, assertOptions)
+    .then(function(aAssertion) {
+      /* Pass along the pubKey. */
+      return checkAssertionAndSigValid(aCredInfo.u2fReg.publicKey, aAssertion);
+    })
+    .then(function(aSigVerifyResult) {
+      ok(aSigVerifyResult, "Signing signature verified");
+      SimpleTest.finish();
+    })
+    .catch(function(reason) {
+      ok(false, "Signing signature invalid: " + reason);
+      SimpleTest.finish();
+    });
+  }
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_webauthn_make_credential.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Test for MakeCredential for W3C Web Authentication</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>Test for MakeCredential 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();
+
+function arrivingHereIsGood(aResult) {
+  ok(true, "Good result! Received a: " + aResult);
+  return Promise.resolve();
+}
+
+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 expectNotSupportedError(aResult) {
+  ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError");
+  return Promise.resolve();
+}
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.w3c", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
+  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
+  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+
+  let authn = navigator.authentication;
+
+  let gCredentialChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(gCredentialChallenge);
+
+  let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
+  let param = {type: "ScopedCred", algorithm: "p-256"};
+  let unsupportedParam = {type: "ScopedCred", algorithm: "3DES"};
+  let badParam = {type: "SimplePassword", algorithm: "MaxLength=2"};
+
+  Promise.all([
+    // Test basic good call
+    authn.makeCredential(acct, [param], gCredentialChallenge)
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad),
+
+    // Test empty account
+    authn.makeCredential({}, [param], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test without a parameter
+    authn.makeCredential(acct, [], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectNotSupportedError),
+
+    // Test without a parameter array at all
+    authn.makeCredential(acct, null, gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with an unsupported parameter
+    authn.makeCredential(acct, [unsupportedParam], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectNotSupportedError),
+
+    // Test with an unsupported parameter and a good one
+    authn.makeCredential(acct, [unsupportedParam, param], gCredentialChallenge)
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad),
+
+    // Test with a bad parameter
+    authn.makeCredential(acct, [badParam], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with an unsupported parameter, and a bad one
+    authn.makeCredential(acct, [unsupportedParam, badParam],
+                         gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with an unsupported parameter, a bad one, and a good one. This
+    // should still fail, as anything with a badParam should fail.
+    authn.makeCredential(acct, [unsupportedParam, badParam, param],
+                         gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test without a challenge
+    authn.makeCredential(acct, [param], null)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with an invalid challenge
+    authn.makeCredential(acct, [param], "begone, thou ill-fitting moist glove!")
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test with duplicate parameters
+    authn.makeCredential(acct, [param, param, param], gCredentialChallenge)
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad),
+
+    // Test an incomplete account
+    authn.makeCredential({id: "none"}, [param], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    authn.makeCredential({name: "none", imageURL: "http://example.com/404"},
+                         [param], gCredentialChallenge)
+    .then(arrivingHereIsBad)
+    .catch(expectTypeError),
+
+    // Test a complete account
+    authn.makeCredential({rpDisplayName: "Foxxy", displayName: "Foxxy V",
+                          id: "foxes_are_the_best@example.com",
+                          name: "Fox F. Foxington",
+                          imageURL: "https://example.com/fox.svg"},
+                         [param], gCredentialChallenge)
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad)
+  ])
+  .then(function() {
+    SimpleTest.finish();
+  });
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_webauthn_no_token.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Test for W3C Web Authentication with no token</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>Test for W3C Web Authentication with no token</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();
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.w3c", true],
+                                   ["security.webauth.u2f_enable_softtoken", false],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
+  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
+  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+
+  let authn = navigator.authentication;
+
+  let credentialChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(credentialChallenge);
+  let assertionChallenge = new Uint8Array(16);
+  window.crypto.getRandomValues(assertionChallenge);
+  let credentialId = new Uint8Array(128);
+  window.crypto.getRandomValues(credentialId);
+
+  testMakeCredential();
+
+  function testMakeCredential() {
+    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
+    let param = {type: "ScopedCred", algorithm: "p-256"};
+    authn.makeCredential(acct, [param], credentialChallenge)
+    .then(function(aResult) {
+      ok(false, "Should have failed.");
+      testAssertion();
+    })
+    .catch(function(aReason) {
+      ok(aReason.toString().startsWith("NotAllowedError"), aReason);
+      testAssertion();
+    });
+  }
+
+  function testAssertion() {
+    let newCredential = {
+      type: "ScopedCred",
+      id: credentialId,
+      transports: [ "usb" ],
+    }
+    let assertOptions = {rpId: document.origin, timeoutSeconds: 5,
+                         allowList: [ newCredential ]};
+    authn.getAssertion(assertionChallenge, assertOptions)
+    .then(function(aResult) {
+      ok(false, "Should have failed.");
+      SimpleTest.finish();
+    })
+    .catch(function(aReason) {
+      ok(aReason.toString().startsWith("NotAllowedError"), aReason);
+      SimpleTest.finish();
+    })
+  }
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_webauthn_sameorigin.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Test for MakeCredential for W3C Web Authentication</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>Test Same Origin Policy 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();
+
+var gTrackedCredential = {};
+
+function arrivingHereIsGood(aResult) {
+  ok(true, "Good result! Received a: " + aResult);
+  return Promise.resolve();
+}
+
+function arrivingHereIsBad(aResult) {
+  // TODO: Change to `ok` when Bug 1329764 lands
+  todo(false, "Bad result! Received a: " + aResult);
+  return Promise.resolve();
+}
+
+function expectSecurityError(aResult) {
+  // TODO: Change to `ok` when Bug 1329764 lands
+  todo(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError");
+  return Promise.resolve();
+}
+
+function keepThisScopedCredential(aScopedCredInfo) {
+  gTrackedCredential = {
+    type: aScopedCredInfo.credential.type,
+    id: Uint8Array.from(aScopedCredInfo.credential.id),
+    transports: [ "usb" ],
+  }
+  return Promise.resolve(aScopedCredInfo);
+}
+
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.w3c", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
+  isnot(navigator.authentication.makeCredential, undefined,
+        "WebAuthn makeCredential API endpoint must exist");
+  isnot(navigator.authentication.getAssertion, undefined,
+        "WebAuthn getAssertion API endpoint must exist");
+
+  let authn = navigator.authentication;
+
+  let chall = new Uint8Array(16);
+  window.crypto.getRandomValues(chall);
+
+  let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
+  let param = {type: "ScopedCred", algorithm: "p-256"};
+
+  Promise.all([
+    // Test basic good call
+    authn.makeCredential(acct, [param], chall, {rpId: document.origin})
+    .then(keepThisScopedCredential)
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad),
+
+    // Test rpId being unset
+    authn.makeCredential(acct, [param], chall, {})
+    .then(arrivingHereIsGood)
+    .catch(arrivingHereIsBad),
+
+    // Test this origin with optional fields
+    authn.makeCredential(acct, [param], chall,
+                         {rpId: "user:pass@" + document.origin + ":8888"})
+    .then(arrivingHereIsBad)
+    .catch(expectSecurityError),
+
+    // Test blank rpId
+    authn.makeCredential(acct, [param], chall, {rpId: ""})
+    .then(arrivingHereIsBad)
+    .catch(expectSecurityError),
+
+    // Test subdomain of this origin
+    authn.makeCredential(acct, [param], chall,
+                         {rpId: "subdomain." + document.origin})
+    .then(arrivingHereIsBad)
+    .catch(expectSecurityError),
+
+    // Test another origin
+    authn.makeCredential(acct, [param], chall, {rpId: "example.com"})
+    .then(arrivingHereIsBad)
+    .catch(expectSecurityError),
+
+    // est a different domain within the same TLD
+    authn.makeCredential(acct, [param], chall, {rpId: "alt.test"})
+    .then(arrivingHereIsBad)
+    .catch(expectSecurityError)
+
+  ])
+  .then(function(){
+    return Promise.all([
+      // Test basic good call
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: document.origin})
+      .then(arrivingHereIsGood)
+      .catch(arrivingHereIsBad),
+
+      // Test rpId being unset
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ]})
+      .then(arrivingHereIsGood)
+      .catch(arrivingHereIsBad),
+
+      // Test this origin with optional fields
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: "user:pass@" + document.origin + ":8888"})
+      .then(arrivingHereIsBad)
+      .catch(expectSecurityError),
+
+      // Test blank rpId
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: ""})
+      .then(arrivingHereIsBad)
+      .catch(expectSecurityError),
+
+      // Test subdomain of this origin
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: "subdomain." + document.origin})
+      .then(arrivingHereIsBad)
+      .catch(expectSecurityError),
+
+      // Test another origin
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: "example.com"})
+      .then(arrivingHereIsBad)
+      .catch(expectSecurityError),
+
+      // Test a different domain within the same TLD
+      authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
+                                 rpId: "alt.test"})
+      .then(arrivingHereIsBad)
+      .catch(expectSecurityError)
+    ]);
+  })
+  .then(function(){
+    SimpleTest.finish();
+  });
+});
+
+</script>
+
+</body>
+</html>
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -122,16 +122,37 @@ function hexEncode(buf) {
               .map(x => ("0"+x.toString(16)).substr(-2))
               .join("");
 }
 
 function hexDecode(str) {
   return new Uint8Array(str.match(/../g).map(x => parseInt(x, 16)));
 }
 
+function decodeU2FRegistration(aRegData) {
+  if (aRegData[0] != 0x05) {
+    return Promise.reject("Sentinal byte != 0x05");
+  }
+
+  let keyHandleLength = aRegData[66];
+  let u2fRegObj = {
+    publicKeyBytes: aRegData.slice(1, 66),
+    keyHandleBytes: aRegData.slice(67, 67 + keyHandleLength),
+    attestationBytes: aRegData.slice(67 + keyHandleLength)
+  }
+
+  u2fRegObj.keyHandle = bytesToBase64UrlSafe(u2fRegObj.keyHandleBytes);
+
+  return importPublicKey(u2fRegObj.publicKeyBytes)
+  .then(function(keyObj) {
+    u2fRegObj.publicKey = keyObj;
+    return u2fRegObj;
+  });
+}
+
 function importPublicKey(keyBytes) {
   if (keyBytes[0] != 0x04 || keyBytes.byteLength != 65) {
     throw "Bad public key octet string";
   }
   var jwk = {
     kty: "EC",
     crv: "P-256",
     x: bytesToBase64UrlSafe(keyBytes.slice(1, 33)),