Bug 1384623 - WebAuthn [SameObject] attributes must cache those objects r?qdot draft
authorJ.C. Jones <jjones@mozilla.com>
Wed, 26 Jul 2017 10:03:17 -0700
changeset 659389 621532f545999f54a8e18f8c8fe3181968b1fd28
parent 659233 973e8b890a62aee4b3170558ac3b608928162ef6
child 729977 95710d6e1623e678397579ddc67a0939c87d8104
push id78116
push userbmo:jjones@mozilla.com
push dateTue, 05 Sep 2017 23:03:31 +0000
reviewersqdot
bugs1384623, 1382888
milestone57.0a1
Bug 1384623 - WebAuthn [SameObject] attributes must cache those objects r?qdot Peter points out [1] that I made assumptions that [SameObject] would handle caching at the JS-layer, but it does not. This bug is to cache those objects [2] on the heap, and add tests that they are indeed the same. [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1382888#c6 [2] https://hg.mozilla.org/mozilla-central/rev/811510fdb51a MozReview-Commit-ID: KQySNAOnyeE
dom/webauthn/AuthenticatorAssertionResponse.cpp
dom/webauthn/AuthenticatorAssertionResponse.h
dom/webauthn/AuthenticatorAttestationResponse.cpp
dom/webauthn/AuthenticatorAttestationResponse.h
dom/webauthn/AuthenticatorResponse.cpp
dom/webauthn/AuthenticatorResponse.h
dom/webauthn/PublicKeyCredential.cpp
dom/webauthn/PublicKeyCredential.h
dom/webauthn/tests/test_webauthn_loopback.html
--- a/dom/webauthn/AuthenticatorAssertionResponse.cpp
+++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp
@@ -5,57 +5,87 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/WebAuthenticationBinding.h"
 #include "mozilla/dom/AuthenticatorAssertionResponse.h"
 
 namespace mozilla {
 namespace dom {
 
+NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorAssertionResponse)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AuthenticatorAssertionResponse,
+                                                AuthenticatorResponse)
+  tmp->mAuthenticatorDataCachedObj = nullptr;
+  tmp->mSignatureCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AuthenticatorAssertionResponse,
+                                               AuthenticatorResponse)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAuthenticatorDataCachedObj)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mSignatureCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AuthenticatorAssertionResponse,
+                                                  AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
 NS_IMPL_ADDREF_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
 NS_IMPL_RELEASE_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorAssertionResponse)
 NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
 
 AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(nsPIDOMWindowInner* aParent)
   : AuthenticatorResponse(aParent)
-{}
+  , mAuthenticatorDataCachedObj(nullptr)
+  , mSignatureCachedObj(nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
 
 AuthenticatorAssertionResponse::~AuthenticatorAssertionResponse()
-{}
+{
+  mozilla::DropJSObjects(this);
+}
 
 JSObject*
 AuthenticatorAssertionResponse::WrapObject(JSContext* aCx,
                                            JS::Handle<JSObject*> aGivenProto)
 {
   return AuthenticatorAssertionResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorAssertionResponse::GetAuthenticatorData(JSContext* aCx,
-                                                     JS::MutableHandle<JSObject*> aRetVal) const
+                                                     JS::MutableHandle<JSObject*> aRetVal)
 {
-  aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+  if (!mAuthenticatorDataCachedObj) {
+    mAuthenticatorDataCachedObj = mAuthenticatorData.ToUint8Array(aCx);
+  }
+  aRetVal.set(mAuthenticatorDataCachedObj);
 }
 
 nsresult
 AuthenticatorAssertionResponse::SetAuthenticatorData(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mAuthenticatorData.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
 void
 AuthenticatorAssertionResponse::GetSignature(JSContext* aCx,
-                                             JS::MutableHandle<JSObject*> aRetVal) const
+                                             JS::MutableHandle<JSObject*> aRetVal)
 {
-  aRetVal.set(mSignature.ToUint8Array(aCx));
+  if (!mSignatureCachedObj) {
+    mSignatureCachedObj = mSignature.ToUint8Array(aCx);
+  }
+  aRetVal.set(mSignatureCachedObj);
 }
 
 nsresult
 AuthenticatorAssertionResponse::SetSignature(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mSignature.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/webauthn/AuthenticatorAssertionResponse.h
+++ b/dom/webauthn/AuthenticatorAssertionResponse.h
@@ -18,39 +18,43 @@
 
 namespace mozilla {
 namespace dom {
 
 class AuthenticatorAssertionResponse final : public AuthenticatorResponse
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AuthenticatorAssertionResponse,
+                                                         AuthenticatorResponse)
 
   explicit AuthenticatorAssertionResponse(nsPIDOMWindowInner* aParent);
 
 protected:
   ~AuthenticatorAssertionResponse() override;
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
-  GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+  GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal);
 
   nsresult
   SetAuthenticatorData(CryptoBuffer& aBuffer);
 
   void
-  GetSignature(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+  GetSignature(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal);
 
   nsresult
   SetSignature(CryptoBuffer& aBuffer);
 
 private:
   CryptoBuffer mAuthenticatorData;
+  JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
   CryptoBuffer mSignature;
+  JS::Heap<JSObject*> mSignatureCachedObj;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AuthenticatorAssertionResponse_h
--- a/dom/webauthn/AuthenticatorAttestationResponse.cpp
+++ b/dom/webauthn/AuthenticatorAttestationResponse.cpp
@@ -5,41 +5,65 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/WebAuthenticationBinding.h"
 #include "mozilla/dom/AuthenticatorAttestationResponse.h"
 
 namespace mozilla {
 namespace dom {
 
+NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorAttestationResponse)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AuthenticatorAttestationResponse,
+                                                AuthenticatorResponse)
+  tmp->mAttestationObjectCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AuthenticatorAttestationResponse,
+                                               AuthenticatorResponse)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAttestationObjectCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AuthenticatorAttestationResponse,
+                                                  AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
 NS_IMPL_ADDREF_INHERITED(AuthenticatorAttestationResponse, AuthenticatorResponse)
 NS_IMPL_RELEASE_INHERITED(AuthenticatorAttestationResponse, AuthenticatorResponse)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorAttestationResponse)
 NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
 
 AuthenticatorAttestationResponse::AuthenticatorAttestationResponse(nsPIDOMWindowInner* aParent)
   : AuthenticatorResponse(aParent)
-{}
+  , mAttestationObjectCachedObj(nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
 
 AuthenticatorAttestationResponse::~AuthenticatorAttestationResponse()
-{}
+{
+  mozilla::DropJSObjects(this);
+}
 
 JSObject*
 AuthenticatorAttestationResponse::WrapObject(JSContext* aCx,
                                              JS::Handle<JSObject*> aGivenProto)
 {
   return AuthenticatorAttestationResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorAttestationResponse::GetAttestationObject(JSContext* aCx,
-                                                       JS::MutableHandle<JSObject*> aRetVal) const
+                                                       JS::MutableHandle<JSObject*> aRetVal)
 {
-  aRetVal.set(mAttestationObject.ToUint8Array(aCx));
+  if (!mAttestationObjectCachedObj) {
+    mAttestationObjectCachedObj = mAttestationObject.ToUint8Array(aCx);
+  }
+  aRetVal.set(mAttestationObjectCachedObj);
 }
 
 nsresult
 AuthenticatorAttestationResponse::SetAttestationObject(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mAttestationObject.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/webauthn/AuthenticatorAttestationResponse.h
+++ b/dom/webauthn/AuthenticatorAttestationResponse.h
@@ -18,32 +18,35 @@
 
 namespace mozilla {
 namespace dom {
 
 class AuthenticatorAttestationResponse final : public AuthenticatorResponse
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AuthenticatorAttestationResponse,
+                                                         AuthenticatorResponse)
 
   explicit AuthenticatorAttestationResponse(nsPIDOMWindowInner* aParent);
 
 protected:
   ~AuthenticatorAttestationResponse() override;
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
-  GetAttestationObject(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+  GetAttestationObject(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal);
 
   nsresult
   SetAttestationObject(CryptoBuffer& aBuffer);
 
 private:
   CryptoBuffer mAttestationObject;
+  JS::Heap<JSObject*> mAttestationObjectCachedObj;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AuthenticatorAttestationResponse_h
--- a/dom/webauthn/AuthenticatorResponse.cpp
+++ b/dom/webauthn/AuthenticatorResponse.cpp
@@ -5,44 +5,67 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/WebAuthenticationBinding.h"
 #include "mozilla/dom/AuthenticatorResponse.h"
 
 namespace mozilla {
 namespace dom {
 
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AuthenticatorResponse, mParent)
+NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AuthenticatorResponse)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->mClientDataJSONCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AuthenticatorResponse)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mClientDataJSONCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AuthenticatorResponse)
+   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AuthenticatorResponse)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AuthenticatorResponse)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorResponse)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 AuthenticatorResponse::AuthenticatorResponse(nsPIDOMWindowInner* aParent)
   : mParent(aParent)
-{}
+  , mClientDataJSONCachedObj(nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
 
 AuthenticatorResponse::~AuthenticatorResponse()
-{}
+{
+  mozilla::DropJSObjects(this);
+}
 
 JSObject*
 AuthenticatorResponse::WrapObject(JSContext* aCx,
                                   JS::Handle<JSObject*> aGivenProto)
 {
   return AuthenticatorResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 AuthenticatorResponse::GetClientDataJSON(JSContext* aCx,
-                                         JS::MutableHandle<JSObject*> aRetVal) const
+                                         JS::MutableHandle<JSObject*> aRetVal)
 {
-  aRetVal.set(mClientDataJSON.ToUint8Array(aCx));
+  if (!mClientDataJSONCachedObj) {
+    mClientDataJSONCachedObj = mClientDataJSON.ToUint8Array(aCx);
+  }
+  aRetVal.set(mClientDataJSONCachedObj);
 }
 
 nsresult
 AuthenticatorResponse::SetClientDataJSON(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mClientDataJSON.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/webauthn/AuthenticatorResponse.h
+++ b/dom/webauthn/AuthenticatorResponse.h
@@ -39,22 +39,23 @@ public:
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   GetFormat(nsString& aRetVal) const;
 
   void
-  GetClientDataJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+  GetClientDataJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal);
 
   nsresult
   SetClientDataJSON(CryptoBuffer& aBuffer);
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   CryptoBuffer mClientDataJSON;
+  JS::Heap<JSObject*> mClientDataJSONCachedObj;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AuthenticatorResponse_h
--- a/dom/webauthn/PublicKeyCredential.cpp
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -6,46 +6,63 @@
 
 #include "mozilla/dom/PublicKeyCredential.h"
 #include "mozilla/dom/WebAuthenticationBinding.h"
 #include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(PublicKeyCredential, Credential, mResponse)
+NS_IMPL_CYCLE_COLLECTION_CLASS(PublicKeyCredential)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PublicKeyCredential,
+                                                Credential)
+  tmp->mRawIdCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PublicKeyCredential, Credential)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRawIdCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PublicKeyCredential, Credential)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
 NS_IMPL_RELEASE_INHERITED(PublicKeyCredential, Credential)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PublicKeyCredential)
 NS_INTERFACE_MAP_END_INHERITING(Credential)
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PublicKeyCredential, Credential)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
 PublicKeyCredential::PublicKeyCredential(nsPIDOMWindowInner* aParent)
   : Credential(aParent)
-{}
+  , mRawIdCachedObj(nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
 
 PublicKeyCredential::~PublicKeyCredential()
-{}
+{
+  mozilla::DropJSObjects(this);
+}
 
 JSObject*
 PublicKeyCredential::WrapObject(JSContext* aCx,
                                 JS::Handle<JSObject*> aGivenProto)
 {
   return PublicKeyCredentialBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 PublicKeyCredential::GetRawId(JSContext* aCx,
-                              JS::MutableHandle<JSObject*> aRetVal) const
+                              JS::MutableHandle<JSObject*> aRetVal)
 {
-  aRetVal.set(mRawId.ToUint8Array(aCx));
+  if (!mRawIdCachedObj) {
+    mRawIdCachedObj = mRawId.ToUint8Array(aCx);
+  }
+  aRetVal.set(mRawIdCachedObj);
 }
 
 already_AddRefed<AuthenticatorResponse>
 PublicKeyCredential::Response() const
 {
   RefPtr<AuthenticatorResponse> temp(mResponse);
   return temp.forget();
 }
--- a/dom/webauthn/PublicKeyCredential.h
+++ b/dom/webauthn/PublicKeyCredential.h
@@ -30,29 +30,30 @@ public:
 protected:
   ~PublicKeyCredential() override;
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
-  GetRawId(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+  GetRawId(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal);
 
   already_AddRefed<AuthenticatorResponse>
   Response() const;
 
   nsresult
   SetRawId(CryptoBuffer& aBuffer);
 
   void
   SetResponse(RefPtr<AuthenticatorResponse>);
 
 private:
   CryptoBuffer mRawId;
+  JS::Heap<JSObject*> mRawIdCachedObj;
   RefPtr<AuthenticatorResponse> mResponse;
   // Extensions are not supported yet.
   // <some type> mClientExtensionResults;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -51,16 +51,21 @@ function() {
        - clientExtensionResults: (not yet supported)
     */
 
     is(aCredInfo.type, "public-key", "Credential type must be public-key")
 
     ok(aCredInfo.rawId.length > 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");
     // WD-05 vs. WD-06: In WD-06, the second parameter should be "window.location.origin". Fix
     // this in Bug 1384776
     is(clientData.origin, document.domain, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
     return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject.buffer)
@@ -85,16 +90,19 @@ function() {
          - 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.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");
     let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
     // WD-05 vs. WD-06: In WD-06, the second parameter should be "window.location.origin". Fix
     // this in Bug 1384776
     is(clientData.origin, document.domain, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");