Bug 1401803 - WebAuthn types need to return ArrayBuffers r?keeler draft
authorJ.C. Jones <jjones@mozilla.com>
Wed, 20 Sep 2017 07:32:07 -0700
changeset 672753 e9cba3c40ea787c3dd99d824322c475f695cbc22
parent 672234 6dea0ee45b66b850c1ec62301724a67db901f81a
child 733909 cbdcee37037d579b581b6fa97804661ee0eae005
push id82361
push userbmo:jjones@mozilla.com
push dateFri, 29 Sep 2017 18:14:15 +0000
reviewerskeeler
bugs1401803
milestone58.0a1
Bug 1401803 - WebAuthn types need to return ArrayBuffers r?keeler The Web Authentication types, by spec, return ArrayBuffer objects, while we were returning a concrete Uint8Array. This is a fairly straightforward change to add functionality to CryptoBuffer and the WebIDL types, however it's a substantial change to the tests. Frankly, the tests just could use another pass of clean-up now, since this is a lot of relative ugliness added in. I refactored tab_webauthn_success.html pretty heavily -- since it was also fairly ugly to start -- but I decided to go with a lighter touch on the other tests. MozReview-Commit-ID: 9vb1wdLo3SI
dom/crypto/CryptoBuffer.cpp
dom/crypto/CryptoBuffer.h
dom/webauthn/AuthenticatorAssertionResponse.cpp
dom/webauthn/AuthenticatorAttestationResponse.cpp
dom/webauthn/AuthenticatorResponse.cpp
dom/webauthn/PublicKeyCredential.cpp
dom/webauthn/tests/browser/browser.ini
dom/webauthn/tests/browser/browser_webauthn_telemetry.js
dom/webauthn/tests/browser/frame_webauthn_success.html
dom/webauthn/tests/browser/tab_webauthn_success.html
dom/webauthn/tests/test_webauthn_loopback.html
dom/webauthn/tests/test_webauthn_sameorigin.html
dom/webauthn/tests/u2futil.js
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -156,16 +156,22 @@ CryptoBuffer::ToSECItem(PLArenaPool *aAr
 }
 
 JSObject*
 CryptoBuffer::ToUint8Array(JSContext* aCx) const
 {
   return Uint8Array::Create(aCx, Length(), Elements());
 }
 
+JSObject*
+CryptoBuffer::ToArrayBuffer(JSContext* aCx) const
+{
+  return ArrayBuffer::Create(aCx, Length(), Elements());
+}
+
 bool
 CryptoBuffer::ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const
 {
   MOZ_ASSERT(aBuf);
   MOZ_ASSERT(aBufLen);
 
   uint32_t dataLen = Length();
   uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -42,16 +42,17 @@ public:
     aArray.ComputeLengthAndData();
     return Assign(aArray.Data(), aArray.Length());
   }
 
   nsresult FromJwkBase64(const nsString& aBase64);
   nsresult ToJwkBase64(nsString& aBase64) const;
   bool ToSECItem(PLArenaPool* aArena, SECItem* aItem) const;
   JSObject* ToUint8Array(JSContext* aCx) const;
+  JSObject* ToArrayBuffer(JSContext* aCx) const;
   bool ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const;
 
   bool GetBigIntValue(unsigned long& aRetVal);
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/webauthn/AuthenticatorAssertionResponse.cpp
+++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp
@@ -54,17 +54,17 @@ AuthenticatorAssertionResponse::WrapObje
   return AuthenticatorAssertionResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorAssertionResponse::GetAuthenticatorData(JSContext* aCx,
                                                      JS::MutableHandle<JSObject*> aRetVal)
 {
   if (!mAuthenticatorDataCachedObj) {
-    mAuthenticatorDataCachedObj = mAuthenticatorData.ToUint8Array(aCx);
+    mAuthenticatorDataCachedObj = mAuthenticatorData.ToArrayBuffer(aCx);
   }
   aRetVal.set(mAuthenticatorDataCachedObj);
 }
 
 nsresult
 AuthenticatorAssertionResponse::SetAuthenticatorData(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mAuthenticatorData.Assign(aBuffer))) {
@@ -73,17 +73,17 @@ AuthenticatorAssertionResponse::SetAuthe
   return NS_OK;
 }
 
 void
 AuthenticatorAssertionResponse::GetSignature(JSContext* aCx,
                                              JS::MutableHandle<JSObject*> aRetVal)
 {
   if (!mSignatureCachedObj) {
-    mSignatureCachedObj = mSignature.ToUint8Array(aCx);
+    mSignatureCachedObj = mSignature.ToArrayBuffer(aCx);
   }
   aRetVal.set(mSignatureCachedObj);
 }
 
 nsresult
 AuthenticatorAssertionResponse::SetSignature(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mSignature.Assign(aBuffer))) {
--- a/dom/webauthn/AuthenticatorAttestationResponse.cpp
+++ b/dom/webauthn/AuthenticatorAttestationResponse.cpp
@@ -51,17 +51,17 @@ AuthenticatorAttestationResponse::WrapOb
   return AuthenticatorAttestationResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorAttestationResponse::GetAttestationObject(JSContext* aCx,
                                                        JS::MutableHandle<JSObject*> aRetVal)
 {
   if (!mAttestationObjectCachedObj) {
-    mAttestationObjectCachedObj = mAttestationObject.ToUint8Array(aCx);
+    mAttestationObjectCachedObj = mAttestationObject.ToArrayBuffer(aCx);
   }
   aRetVal.set(mAttestationObjectCachedObj);
 }
 
 nsresult
 AuthenticatorAttestationResponse::SetAttestationObject(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mAttestationObject.Assign(aBuffer))) {
--- a/dom/webauthn/AuthenticatorResponse.cpp
+++ b/dom/webauthn/AuthenticatorResponse.cpp
@@ -53,17 +53,17 @@ AuthenticatorResponse::WrapObject(JSCont
   return AuthenticatorResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorResponse::GetClientDataJSON(JSContext* aCx,
                                          JS::MutableHandle<JSObject*> aRetVal)
 {
   if (!mClientDataJSONCachedObj) {
-    mClientDataJSONCachedObj = mClientDataJSON.ToUint8Array(aCx);
+    mClientDataJSONCachedObj = mClientDataJSON.ToArrayBuffer(aCx);
   }
   aRetVal.set(mClientDataJSONCachedObj);
 }
 
 nsresult
 AuthenticatorResponse::SetClientDataJSON(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mClientDataJSON.Assign(aBuffer))) {
--- a/dom/webauthn/PublicKeyCredential.cpp
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -50,17 +50,17 @@ PublicKeyCredential::WrapObject(JSContex
   return PublicKeyCredentialBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 PublicKeyCredential::GetRawId(JSContext* aCx,
                               JS::MutableHandle<JSObject*> aRetVal)
 {
   if (!mRawIdCachedObj) {
-    mRawIdCachedObj = mRawId.ToUint8Array(aCx);
+    mRawIdCachedObj = mRawId.ToArrayBuffer(aCx);
   }
   aRetVal.set(mRawIdCachedObj);
 }
 
 already_AddRefed<AuthenticatorResponse>
 PublicKeyCredential::Response() const
 {
   RefPtr<AuthenticatorResponse> temp(mResponse);
--- a/dom/webauthn/tests/browser/browser.ini
+++ b/dom/webauthn/tests/browser/browser.ini
@@ -1,9 +1,9 @@
 [DEFAULT]
 support-files =
-  frame_webauthn_success.html
+  tab_webauthn_success.html
   ../cbor/*
   ../pkijs/*
   ../u2futil.js
 skip-if = !e10s
 
 [browser_webauthn_telemetry.js]
--- a/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
+++ b/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
@@ -56,17 +56,17 @@ async function executeTestPage(aUri) {
     return BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 }
 
 add_task(async function test_loopback() {
   // These tests can't run simultaneously as the preference changes will race.
   // So let's run them sequentially here, but in an async function so we can
   // use await.
-  const testPage = "https://example.com/browser/dom/webauthn/tests/browser/frame_webauthn_success.html";
+  const testPage = "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_success.html";
   {
     cleanupTelemetry();
     // Enable the soft token, and execute a simple end-to-end test
     Services.prefs.setBoolPref("security.webauth.webauthn", true);
     Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
     Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
 
     await executeTestPage(testPage);
rename from dom/webauthn/tests/browser/frame_webauthn_success.html
rename to dom/webauthn/tests/browser/tab_webauthn_success.html
--- a/dom/webauthn/tests/browser/frame_webauthn_success.html
+++ b/dom/webauthn/tests/browser/tab_webauthn_success.html
@@ -27,102 +27,93 @@ window.crypto.getRandomValues(gAssertion
 function signalCompletion(aText) {
   console.log(aText)
   let result = document.createElement('h1');
   result.id = "result";
   result.textContent = aText;
   document.body.append(result);
 }
 
-function util_decodeCreatedCredential(aCredInfo) {
-  /* PublicKeyCredential : Credential
-     - rawId: Key Handle buffer pulled from U2F Register() Response
-     - response : AuthenticatorAttestationResponse : AuthenticatorResponse
-       - attestationObject: CBOR object
-       - clientDataJSON: serialized JSON
-     - clientExtensionResults: (not yet supported)
-  */
-
-  return webAuthnDecodeAttestation(aCredInfo.response.attestationObject.buffer)
-  .then(function(decodedResult) {
-    aCredInfo.publicKeyHandle = decodedResult.publicKeyHandle;
-    aCredInfo.attestationObject = decodedResult.attestationObject;
-    return aCredInfo;
-  });
-}
-
-function util_checkAssertionAndSigValid(aPublicKey, aAssertion) {
-  /* PublicKeyCredential : Credential
-     - rawId: ID of Credential from AllowList that succeeded
-     - response : AuthenticatorAssertionResponse : AuthenticatorResponse
-       - clientDataJSON: serialized JSON
-       - authenticatorData: RP ID Hash || U2F Sign() Response
-       - signature: U2F Sign() Response
-  */
-
-  // Parse the signature data
-  if (aAssertion.response.signature[0] != 0x01) {
-    throw "User presence byte not set";
-  }
-
-  let presenceAndCounter = aAssertion.response.signature.slice(0,5);
-  let signatureValue = aAssertion.response.signature.slice(5);
-
-  let rpIdHash = aAssertion.response.authenticatorData.slice(0,32);
-
-  // Assemble the signed data and verify the signature
-  return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON)
-  .then(function(aParams) {
-    console.log(aParams.appParam, rpIdHash, presenceAndCounter, aParams.challengeParam);
-    console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
-    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);
-  })
-}
-
-let rp = {id: document.domain, name: "none", icon: "none"};
-let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-let param = {type: "public-key", algorithm: "ES256"};
+let gState = {};
 let makeCredentialOptions = {
-  rp: rp,
-  user: user,
+  rp: {id: document.domain, name: "none", icon: "none"},
+  user: {id: "none", name: "none", icon: "none", displayName: "none"},
   challenge: gCredentialChallenge,
-  parameters: [param]
+  timeout: 5000, // the minimum timeout is actually 15 seconds
+  parameters: [{type: "public-key", algorithm: "ES256"}],
 };
 
-let credm = navigator.credentials;
-credm.create({publicKey: makeCredentialOptions})
-.then(util_decodeCreatedCredential)
+navigator.credentials.create({publicKey: makeCredentialOptions})
+.then(function (aNewCredentialInfo) {
+  gState.credential = aNewCredentialInfo;
+
+  return webAuthnDecodeCBORAttestation(aNewCredentialInfo.response.attestationObject);
+})
 .then(function testAssertion(aCredInfo) {
+  gState.publicKeyHandle = aCredInfo.authDataObj.publicKeyHandle;
+
   let newCredential = {
     type: "public-key",
-    id: Uint8Array.from(aCredInfo.rawId),
+    id: new Uint8Array(gState.credential.rawId),
     transports: ["usb"],
   }
 
   let publicKeyCredentialRequestOptions = {
     challenge: gAssertionChallenge,
     timeout: 5000, // the minimum timeout is actually 15 seconds
     rpId: document.domain,
     allowList: [newCredential]
   };
 
-  return credm.get({publicKey: publicKeyCredentialRequestOptions})
-  .then(function(aAssertion) {
-    /* Pass along the pubKey. */
-    return util_checkAssertionAndSigValid(aCredInfo.publicKeyHandle, aAssertion);
+  return navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions});
+})
+.then(function(aAssertion) {
+  let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
+
+  gState.assertion = aAssertion;
+
+  return webAuthnDecodeAuthDataArray(new Uint8Array(aAssertion.response.authenticatorData));
+})
+.then(function(aAttestationObj) {
+  gState.attestation = aAttestationObj;
+
+  // Make sure the RP ID hash matches what we calculate.
+  return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
+  .then(function(calculatedRpIdHash) {
+    let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
+    let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash));
+
+    if (calcHashStr != providedHashStr) {
+      return Promise.reject("Calculated RP ID hash must match what the browser derived.");
+    }
+    return Promise.resolve(aAttestationObj);
   });
 })
+.then(function(aAttestation) {
+  if (new Uint8Array(aAttestation.flags) != flag_TUP) {
+    return Promise.reject("Assertion's user presence byte not set correctly.");
+  }
+
+  let clientDataJSON = gState.assertion.response.clientDataJSON;
+  return deriveAppAndChallengeParam(document.domain, clientDataJSON, aAttestation);
+})
+.then(function(aParams) {
+  return assembleSignedData(aParams.appParam, aParams.attestation.flags,
+                            aParams.attestation.counter, aParams.challengeParam);
+})
+.then(function(aSignedData) {
+  let signature = gState.assertion.response.signature;
+  console.log(gState.publicKeyHandle, aSignedData, signature);
+  return verifySignature(gState.publicKeyHandle, aSignedData, new Uint8Array(signature));
+})
 .then(function(aSigVerifyResult) {
   signalCompletion("Signing signature verified: " + aSigVerifyResult);
+  gState = {};
 })
-.catch(function failure(aReason) {
+.catch(function(aReason) {
   signalCompletion("Failure: " + aReason);
+  gState = {};
 });
 
 </script>
 
 </body>
 </html>
\ No newline at end of file
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -48,46 +48,49 @@ function() {
        - response : AuthenticatorAttestationResponse : AuthenticatorResponse
          - attestationObject: CBOR object
          - clientDataJSON: serialized JSON
        - clientExtensionResults: (not yet supported)
     */
 
     is(aCredInfo.type, "public-key", "Credential type must be public-key")
 
-    ok(aCredInfo.rawId.length > 0, "Key ID exists");
+    ok(aCredInfo.rawId.byteLength > 0, "Key ID exists");
     is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
 
     ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
     ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
     ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
     ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
 
     let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
     is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
-    return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject.buffer)
-    .then(function(decodedResult) {
+    return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject)
+    .then(function(aAttestationObj) {
       // Make sure the RP ID hash matches what we calculate.
       return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
-      .then(function(calculatedHash) {
-        is(bytesToBase64(new Uint8Array(calculatedHash)), bytesToBase64(decodedResult.rpIdHash),
+      .then(function(calculatedRpIdHash) {
+        let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
+        let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash));
+
+        is(calcHashStr, providedHashStr,
            "Calculated RP ID hash must match what the browser derived.");
-        return Promise.resolve(decodedResult);
+        return Promise.resolve(aAttestationObj);
       });
     })
-    .then(function(decodedResult) {
-      ok(decodedResult.flags == (flag_TUP | flag_AT),
+    .then(function(aAttestationObj) {
+      ok(aAttestationObj.authDataObj.flags == (flag_TUP | flag_AT),
          "User presence and Attestation Object must both be set");
 
       aCredInfo.clientDataObj = clientData;
-      aCredInfo.publicKeyHandle = decodedResult.publicKeyHandle;
-      aCredInfo.attestationObject = decodedResult.attestationObject;
+      aCredInfo.publicKeyHandle = aAttestationObj.authDataObj.publicKeyHandle;
+      aCredInfo.attestationObject = aAttestationObj.authDataObj.attestationAuthData;
       return aCredInfo;
     });
   }
 
   function checkAssertionAndSigValid(aPublicKey, aAssertion) {
     /* PublicKeyCredential : Credential
        - rawId: ID of Credential from AllowList that succeeded
        - id: Key Handle buffer in base64url form, should == rawId
@@ -95,33 +98,33 @@ function() {
        - response : AuthenticatorAssertionResponse : AuthenticatorResponse
          - clientDataJSON: serialized JSON
          - authenticatorData: RP ID Hash || U2F Sign() Response
          - signature: U2F Sign() Response
     */
 
     is(aAssertion.type, "public-key", "Credential type must be public-key")
 
-    ok(aAssertion.rawId.length > 0, "Key ID exists");
-    is(aAssertion.id, bytesToBase64UrlSafe(aAssertion.rawId), "Encoded Key ID and Raw Key ID match");
+    ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
+    is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
 
     ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
     ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
 
-    ok(aAssertion.response.authenticatorData.length > 0, "Authenticator data exists");
+    ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
     let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
     is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
-    return webAuthnDecodeAttestation(aAssertion.response.authenticatorData)
-    .then(function(decodedResult) {
-      ok(decodedResult.flags == flag_TUP, "User presence must be the only flag set");
-      is(decodedResult.counter.length, 4, "Counter must be 4 bytes");
-      return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, decodedResult)
+    return webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData)
+    .then(function(aAttestation) {
+      ok(new Uint8Array(aAttestation.flags) == flag_TUP, "User presence must be the only flag set");
+      is(aAttestation.counter.byteLength, 4, "Counter must be 4 bytes");
+      return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, aAttestation)
     })
     .then(function(aParams) {
       console.log(aParams);
       console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
       console.log("ClientDataHash: ", hexEncode(aParams.challengeParam));
       return assembleSignedData(aParams.appParam, aParams.attestation.flags,
                                 aParams.attestation.counter, aParams.challengeParam);
     })
@@ -154,17 +157,17 @@ function() {
     let rp = {id: document.domain, name: "none", icon: "none"};
     let user = {id: "none", name: "none", icon: "none", displayName: "none"};
     let param = {type: "public-key", algorithm: "ES256"};
     let makeCredentialOptions = {
       rp: rp,
       user: user,
       challenge: gCredentialChallenge,
       parameters: [param],
-      excludeList: [{type: "public-key", id: Uint8Array.from(aCredInfo.rawId),
+      excludeList: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
                      transports: ["usb"]}]
     };
     credm.create({publicKey: makeCredentialOptions})
     .then(function() {
       // We should have errored here!
       ok(false, "The excludeList didn't stop a duplicate being created!");
       SimpleTest.finish();
     })
@@ -172,17 +175,17 @@ function() {
       ok(aReason.toString().startsWith("NotAllowedError"), "Expect NotAllowedError, got " + aReason);
       testAssertion(aCredInfo);
     });
   }
 
   function testAssertion(aCredInfo) {
     let newCredential = {
       type: "public-key",
-      id: Uint8Array.from(aCredInfo.rawId),
+      id: new Uint8Array(aCredInfo.rawId),
       transports: ["usb"],
     }
 
     let publicKeyCredentialRequestOptions = {
       challenge: gAssertionChallenge,
       timeout: 5000, // the minimum timeout is actually 15 seconds
       rpId: document.domain,
       allowList: [newCredential]
--- a/dom/webauthn/tests/test_webauthn_sameorigin.html
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -37,17 +37,17 @@
       ok(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError");
       return Promise.resolve();
     }
 
     function keepThisPublicKeyCredential(aIdentifier) {
       return function(aPublicKeyCredential) {
         gTrackedCredential[aIdentifier] = {
           type: "public-key",
-          id: Uint8Array.from(aPublicKeyCredential.rawId),
+          id: new Uint8Array(aPublicKeyCredential.rawId),
           transports: [ "usb" ],
         }
         return Promise.resolve(aPublicKeyCredential);
       }
     }
 
     function runTests() {
       is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
--- a/dom/webauthn/tests/u2futil.js
+++ b/dom/webauthn/tests/u2futil.js
@@ -77,25 +77,29 @@ function local_finished() {
 }
 
 function string2buffer(str) {
   return (new Uint8Array(str.length)).map((x, i) => str.charCodeAt(i));
 }
 
 function buffer2string(buf) {
   let str = "";
-  buf.map(x => str += String.fromCharCode(x));
+  if (!(buf.constructor === Uint8Array)) {
+    buf = new Uint8Array(buf);
+  }
+  buf.map(function(x){ return str += String.fromCharCode(x) });
   return str;
 }
 
 function bytesToBase64(u8a){
   let CHUNK_SZ = 0x8000;
   let c = [];
-  for (let i = 0; i < u8a.length; i += CHUNK_SZ) {
-    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
+  let array = new Uint8Array(u8a);
+  for (let i = 0; i < array.length; i += CHUNK_SZ) {
+    c.push(String.fromCharCode.apply(null, array.subarray(i, i + CHUNK_SZ)));
   }
   return window.btoa(c.join(""));
 }
 
 function base64ToBytes(b64encoded) {
   return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
     return c.charCodeAt(0);
   }));
@@ -129,66 +133,66 @@ function hexEncode(buf) {
 function hexDecode(str) {
   return new Uint8Array(str.match(/../g).map(x => parseInt(x, 16)));
 }
 
 function webAuthnDecodeCBORAttestation(aCborAttBuf) {
   let attObj = CBOR.decode(aCborAttBuf);
   console.log(":: Attestation CBOR Object ::");
   if (!("authData" in attObj && "fmt" in attObj && "attStmt" in attObj)) {
-    throw "Invalid CBOR Attestation Object";
+    return Promise.reject("Invalid CBOR Attestation Object");
   }
   if (!("sig" in attObj.attStmt && "x5c" in attObj.attStmt)) {
-    throw "Invalid CBOR Attestation Statement";
+    return Promise.reject("Invalid CBOR Attestation Statement");
   }
 
-  return webAuthnDecodeAttestation(attObj.authData)
-  .then(function (aAttestationObj) {
-    aAttestationObj.attestationObject = attObj;
-    return Promise.resolve(aAttestationObj);
+  return webAuthnDecodeAuthDataArray(new Uint8Array(attObj.authData))
+  .then(function (aAuthDataObj) {
+    attObj.authDataObj = aAuthDataObj;
+    return Promise.resolve(attObj);
   });
 }
 
-function webAuthnDecodeAttestation(aAuthData) {
+function webAuthnDecodeAuthDataArray(aAuthData) {
   let rpIdHash = aAuthData.slice(0, 32);
   let flags = aAuthData.slice(32, 33);
   let counter = aAuthData.slice(33, 37);
 
-  console.log(":: Attestation Object Data ::");
+  console.log(":: Authenticator Data ::");
   console.log("RP ID Hash: " + hexEncode(rpIdHash));
   console.log("Counter: " + hexEncode(counter) + " Flags: " + flags);
 
   if ((flags & flag_AT) == 0x00) {
     // No Attestation Data, so we're done.
     return Promise.resolve({
       rpIdHash: rpIdHash,
       flags: flags,
       counter: counter,
     });
   }
 
   if (aAuthData.length < 38) {
-    throw "Attestation Data flag was set, but not enough data passed in!";
+    return Promise.reject("Authenticator Data flag was set, but not enough data passed in!");
   }
 
   let attData = {};
   attData.aaguid = aAuthData.slice(37, 53);
   attData.credIdLen = (aAuthData[53] << 8) + aAuthData[54];
   attData.credId = aAuthData.slice(55, 55 + attData.credIdLen);
 
-  console.log(":: Attestation Data ::");
+  console.log(":: Authenticator Data ::");
   console.log("AAGUID: " + hexEncode(attData.aaguid));
 
   cborPubKey = aAuthData.slice(55 + attData.credIdLen);
   var pubkeyObj = CBOR.decode(cborPubKey.buffer);
   if (!("alg" in pubkeyObj && "x" in pubkeyObj && "y" in pubkeyObj)) {
-    throw "Invalid CBOR Public Key Object";
+    return Promise.reject("Invalid CBOR Public Key Object");
   }
   if (pubkeyObj.alg != "ES256") {
-    throw "Unexpected public key algorithm";
+    return Promise.reject("Unexpected public key algorithm");
   }
 
   let pubKeyBytes = assemblePublicKeyBytesData(pubkeyObj.x, pubkeyObj.y);
   console.log(":: CBOR Public Key Object Data ::");
   console.log("Algorithm: " + pubkeyObj.alg);
   console.log("X: " + pubkeyObj.x);
   console.log("Y: " + pubkeyObj.y);
   console.log("Uncompressed (hex): " + hexEncode(pubKeyBytes));
@@ -244,47 +248,52 @@ function assemblePublicKeyBytesData(xCoo
   keyBytes[0] = 0x04;
   xCoord.map((x, i) => keyBytes[1 + i] = x);
   yCoord.map((x, i) => keyBytes[33 + i] = x);
   return keyBytes;
 }
 
 function assembleSignedData(appParam, flags, counter, challengeParam) {
   let signedData = new Uint8Array(32 + 1 + 4 + 32);
-  appParam.map((x, i) => signedData[0 + i] = x);
-  signedData[32] = flags;
-  counter.map((x, i) => signedData[33 + i] = x);
-  challengeParam.map((x, i) => signedData[37 + i] = x);
+  new Uint8Array(appParam).map((x, i) => signedData[0 + i] = x);
+  signedData[32] = new Uint8Array(flags)[0];
+  new Uint8Array(counter).map((x, i) => signedData[33 + i] = x);
+  new Uint8Array(challengeParam).map((x, i) => signedData[37 + i] = x);
   return signedData;
 }
 
 function assembleRegistrationSignedData(appParam, challengeParam, keyHandle, pubKey) {
   let signedData = new Uint8Array(1 + 32 + 32 + keyHandle.length + 65);
   signedData[0] = 0x00;
-  appParam.map((x, i) => signedData[1 + i] = x);
-  challengeParam.map((x, i) => signedData[33 + i] = x);
-  keyHandle.map((x, i) => signedData[65 + i] = x);
-  pubKey.map((x, i) => signedData[65 + keyHandle.length + i] = x);
+  new Uint8Array(appParam).map((x, i) => signedData[1 + i] = x);
+  new Uint8Array(challengeParam).map((x, i) => signedData[33 + i] = x);
+  new Uint8Array(keyHandle).map((x, i) => signedData[65 + i] = x);
+  new Uint8Array(pubKey).map((x, i) => signedData[65 + keyHandle.length + i] = x);
   return signedData;
 }
 
 function sanitizeSigArray(arr) {
   // ECDSA signature fields into WebCrypto must be exactly 32 bytes long, so
   // this method strips leading padding bytes, if added, and also appends
   // padding zeros, if needed.
   if (arr.length > 32) {
     arr = arr.slice(arr.length - 32)
   }
   let ret = new Uint8Array(32);
   ret.set(arr, ret.length - arr.length);
   return ret;
 }
 
 function verifySignature(key, data, derSig) {
-  let sigAsn1 = org.pkijs.fromBER(derSig.buffer);
+  if (derSig.byteLength < 70) {
+    console.log("bad sig: " + hexEncode(new Uint8Array(derSig)))
+    return Promise.reject("Invalid signature length: " + derSig.byteLength);
+  }
+
+  let sigAsn1 = org.pkijs.fromBER(derSig);
   let sigR = new Uint8Array(sigAsn1.result.value_block.value[0].value_block.value_hex);
   let sigS = new Uint8Array(sigAsn1.result.value_block.value[1].value_block.value_hex);
 
   // The resulting R and S values from the ASN.1 Sequence must be fit into 32
   // bytes. Sometimes they have leading zeros, sometimes they're too short, it
   // all depends on what lib generated the signature.
   let R = sanitizeSigArray(sigR);
   let S = sanitizeSigArray(sigS);