1

I am using a Yubico security key with an AAGUID of ff8a011f3-8c0a-4d15-8006-17111f9edc7d (Security Key By Yubico v5.1) to perform password-less authentication for my web application. When I create/register a new credential I use the attribute "requireResidentKey = true" in the authenticatorSelection section of the create credential options:

   ...
   authenticatorSelection: {
      requireResidentKey: true, // note the use of this attribute set to true
      userVerification: options.userVerification,
      authenticatorAttachment: options.authenticatorAttachment,
    },
   ...

The Attestation data that is returned contains a credential id of 16 bytes:

 return navigator.credentials.create({
    publicKey: credentialCreateOptions,
    signal: authAbortSignal,
  }).then(rawAttestation => {
    const attestation = {
      id: rawAttestation.id, // returned 16 byte credential id
      type: rawAttestation.type,
      clientDataJSON: arrayBufferToString(rawAttestation.response.clientDataJSON),
      attestationObject: base64encode(rawAttestation.response.attestationObject),
      extensionData: base64encode(rawAttestation.getClientExtensionResults()),
    }

    return attestation
  }).catch((error) => {
    console.log(error)
    if (error === 'AbortError') {
      throw new Error('the operation was aborted')
    } else {
      throw new Error('the operation was cancelled')
    }
  })

so for example I will receive a 16 byte base64url id looking something like: AUpf0KmNJrRluGG-65D54Q

I then save the resulting credential using this 16 byte id as the key. When I use the Yubico key to sign-in the Assertion data returned contains this same 16 byte credential id:

return navigator.credentials.get({
    publicKey: credentialRequestOptions,
  }).then(rawAssertion => {
    const assertion = {
      id: rawAssertion.id, // same 16 byte credential id as returned from create credential
      type: rawAssertion.type,
      clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON),
      authenticatorData: base64encode(rawAssertion.response.authenticatorData),
      signature: base64encode(rawAssertion.response.signature),
      userHandle: base64encode(rawAssertion.response.userHandle),
    }

    return assertion
  }).catch((error) => {
    throw new Error(error.message)
  })

I can then use this credential id to retrieve my stored credential data and verify the assertion. So all is good so far...

I was then reading the W3C Editor's Draft on Web Authentication: An API for accessing Public Key Credentials Level 2 and noted that the "requireResidentKey" has been deprecated in favour of a "residentKey" attribute that takes an enum value:

requireResidentKey, of type boolean, defaulting to false Note: This member is retained for backwards compatibility with WebAuthn Level 1 but is deprecated in favour of residentKey. requireResidentKey is ignored if the caller supplies residentKey and the latter is understood by the client. Otherwise, requireResidentKey's value is used. Note that requireResidentKey's value defaults to false.

If used in absence of residentKey, it describes the Relying Party's requirements regarding resident credentials. If requireResidentKey is set to true, the authenticator MUST create a client-side-resident public key credential source when creating a public key credential.

residentKey, of type ResidentKeyRequirement Note: This member supersedes requireResidentKey. If both are present and the client understands residentKey, then residentKey is used and requireResidentKey is ignored.

See ResidentKeyRequirement for the description of residentKey's values and semantics.

So I changed the requireResidentKey to residentKey with the enum value of "required" as shown:

    authenticatorSelection: {
      residentKey: 'required', // now using residentKey attribute
      requireResidentKey: true,
      userVerification: options.userVerification,
      authenticatorAttachment: options.authenticatorAttachment,
    },

Now when I create a new credential I get a 64 byte credential ID returned. This would have been fine except that when I use the Yubico security key to sign-in I get a 16 byte credential ID returned which clearly does not match with the 64 byte one I saved during the create credentials stage.

Interestingly, when I tried using both requireResidentKey = true and residentKey = 'required' together I got my 16 byte credential ID returned for both Attestation and Assertion.

Could this be that the new residentKey attribute is not supported? If so why did I get a 64 byte credential id? Is this the length of non-resident credential id's perhaps?

My code is back working using the old requireResidentKey attribute but I would love to know what was going on here and if the residentKey attribute will be supported in newer Yubikeys?

Tiglath
  • 11
  • 3
  • Interestingly my non-resident keys (for 2FA only) all have 64-byte credential IDs using a pre FIDO2 Yubikey and the new Yubikey Security Key (the FIDO2-only one). Resident ones have a 16-byte ID. That does suggest that something unexpected is happening when you supply both params. Is there actually any support for Level 2 out there? The MDN docs do not include the new `residentKey` property. – mackie Mar 13 '20 at 16:24
  • 1
    Thanks @mackie that confirms my suspicions. I think, therefore, that the new property is not yet supported and thus it has resorted to the default value for the `requireResidentKey` which is `false`. This would explain why I then get a 64 byte credential id. – Tiglath Mar 13 '20 at 16:47

0 Answers0