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
--- 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