Bug 1384307 - Set WebAuthn PublicKeyCredential's "id" and "type" fields r?keeler draft
authorJ.C. Jones <jjones@mozilla.com>
Tue, 25 Jul 2017 15:03:59 -0700
changeset 615414 25c0ecf312871120d9ecc46c2ab0267cf4e226c0
parent 615383 85abcbccdff63e5e36752b5d5655e4e9700c0388
child 639169 9e9900b637379fb18a56ccd385d435031f44f6fa
push id70350
push userbmo:jjones@mozilla.com
push dateTue, 25 Jul 2017 22:46:41 +0000
reviewerskeeler
bugs1384307
milestone56.0a1
Bug 1384307 - Set WebAuthn PublicKeyCredential's "id" and "type" fields r?keeler The Web Authentication PublicKeyCredential object has two fields currently unpopulated which, to be spec-compliant, must be set. These fields duplicate available data. [PublicKeyCredential.id] must be set to the base64url encoding with omitted padding of whatever data is in "rawId". [PublicKeyCredential.type] must be the literal "public-key". MozReview-Commit-ID: L6wPYpZdD8A
dom/credentialmanagement/Credential.cpp
dom/credentialmanagement/Credential.h
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/tests/test_webauthn_loopback.html
--- a/dom/credentialmanagement/Credential.cpp
+++ b/dom/credentialmanagement/Credential.cpp
@@ -41,10 +41,22 @@ Credential::GetId(nsAString& aId) const
 }
 
 void
 Credential::GetType(nsAString& aType) const
 {
   aType.Assign(mType);
 }
 
+void
+Credential::SetId(const nsAString& aId)
+{
+  mId.Assign(aId);
+}
+
+void
+Credential::SetType(const nsAString& aType)
+{
+  mType.Assign(aType);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/credentialmanagement/Credential.h
+++ b/dom/credentialmanagement/Credential.h
@@ -39,16 +39,22 @@ public:
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   GetId(nsAString& aId) const;
 
   void
   GetType(nsAString& aType) const;
 
+  void
+  SetId(const nsAString& aId);
+
+  void
+  SetType(const nsAString& aType);
+
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   nsAutoString mId;
   nsAutoString mType;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -690,16 +690,23 @@ WebAuthnManager::FinishMakeCredential(ns
   nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
                                                  attestationCertBuf, signatureBuf);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Cancel(rv);
     return;
   }
   MOZ_ASSERT(keyHandleBuf.Length() <= 0xFFFF);
 
+  nsAutoString keyHandleBase64Url;
+  rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Cancel(rv);
+    return;
+  }
+
   CryptoBuffer clientDataBuf;
   if (!clientDataBuf.Assign(mClientData.ref())) {
     Cancel(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   CryptoBuffer rpIdHashBuf;
   if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) {
@@ -770,16 +777,18 @@ WebAuthnManager::FinishMakeCredential(ns
   // values returned from the authenticator as well as the clientDataJSON
   // computed earlier.
   RefPtr<AuthenticatorAttestationResponse> attestation =
       new AuthenticatorAttestationResponse(mCurrentParent);
   attestation->SetClientDataJSON(clientDataBuf);
   attestation->SetAttestationObject(attObj);
 
   RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mCurrentParent);
+  credential->SetId(keyHandleBase64Url);
+  credential->SetType(NS_LITERAL_STRING("public-key"));
   credential->SetRawId(keyHandleBuf);
   credential->SetResponse(attestation);
 
   mTransactionPromise->MaybeResolve(credential);
   MaybeClearTransaction();
 }
 
 void
@@ -812,16 +821,23 @@ WebAuthnManager::FinishGetAssertion(nsTA
                                              signatureData);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Cancel(rv);
     return;
   }
 
   CryptoBuffer credentialBuf;
   if (!credentialBuf.Assign(aCredentialId)) {
+    Cancel(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  nsAutoString credentialBase64Url;
+  rv = credentialBuf.ToJwkBase64(credentialBase64Url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     Cancel(rv);
     return;
   }
 
   // If any authenticator returns success:
 
   // Create a new PublicKeyCredential object named value and populate its fields
   // with the values returned from the authenticator as well as the
@@ -829,16 +845,18 @@ WebAuthnManager::FinishGetAssertion(nsTA
   RefPtr<AuthenticatorAssertionResponse> assertion =
     new AuthenticatorAssertionResponse(mCurrentParent);
   assertion->SetClientDataJSON(clientDataBuf);
   assertion->SetAuthenticatorData(authenticatorDataBuf);
   assertion->SetSignature(signatureData);
 
   RefPtr<PublicKeyCredential> credential =
     new PublicKeyCredential(mCurrentParent);
+  credential->SetId(credentialBase64Url);
+  credential->SetType(NS_LITERAL_STRING("public-key"));
   credential->SetRawId(credentialBuf);
   credential->SetResponse(assertion);
 
   mTransactionPromise->MaybeResolve(credential);
   MaybeClearTransaction();
 }
 
 void
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -38,23 +38,28 @@ function() {
   let gAssertionChallenge = new Uint8Array(16);
   window.crypto.getRandomValues(gAssertionChallenge);
 
   testMakeCredential();
 
   function decodeCreatedCredential(aCredInfo) {
     /* PublicKeyCredential : Credential
        - rawId: Key Handle buffer pulled from U2F Register() Response
+       - id: Key Handle buffer in base64url form, should == rawId
+       - type: Literal 'public-key'
        - 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");
+    is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
 
     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, "S256", "Hash algorithm is correct");
 
     return webAuthnDecodeAttestation(aCredInfo.response.attestationObject.buffer)
     .then(function(decodedResult) {
@@ -63,23 +68,28 @@ function() {
       aCredInfo.attestationObject = decodedResult.attestationObject;
       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
+       - type: Literal 'public-key'
        - 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.response.authenticatorData.length > 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, "S256", "Hash algorithm is correct");
 
     // Parse the signature data