Ryan Faulhaber
Ryan Faulhaber

Reputation: 76

Webauthn AuthenticatorAttestationResponse "getPublicKey" returns a restricted object?

I'm trying to implement passkeys for my web app according to these guides, and I'm primarily working on the registration flow. After I've created the passkey using navigator.credential.create, I try obtaining the newly generated public key using the getPublicKey method on the returned response attribute. However, when I try inspecting the object, my browser says:

Restricted { }

According to MDN, this object should be an ArrayBuffer. In order to send it to the server I try to convert it to a base64-encoded string, however whenever I try to do something with this (in fact, whenever I try to do anything to it), the browser throws an error saying I can't access this object.

I've already worked around this by sending the attestationObject to the server and having the server decode it, but according to everything I've read, I should be able to do this client-side. Am I missing something obvious, or is this expected behavior?

Upvotes: 0

Views: 254

Answers (1)

Ergin
Ergin

Reputation: 9356

After you call navigator.credential.create() you need to parse the response data to retrieve the newly created public key (among other things). The guides you linked are a little old now, the official Webauthn guide shows more precisely how to parse the attestation response data.

The create call should return a response like:

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
    rawId: ArrayBuffer(59),
    response: AuthenticatorAttestationResponse {
        clientDataJSON: ArrayBuffer(121),
        attestationObject: ArrayBuffer(306),
    },
    type: 'public-key'
}

Next thing you need to do is parse the attestation object from the response:

// note: a CBOR decoder library is needed here.
const decodedAttestationObj = CBOR.decode(
    credential.response.attestationObject);

console.log(decodedAttestationObject);
{
    authData: Uint8Array(196),
    fmt: "fido-u2f",
    attStmt: {
        sig: Uint8Array(70),
        x5c: Array(1),
    },
}

Finally, to retrieve the public key from the authData:

const {authData} = decodedAttestationObject;

// get the length of the credential ID
const dataView = new DataView(
    new ArrayBuffer(2));
const idLenBytes = authData.slice(53, 55);
idLenBytes.forEach(
    (value, index) => dataView.setUint8(
        index, value));
const credentialIdLength = dataView.getUint16();

// get the credential ID
const credentialId = authData.slice(
    55, 55 + credentialIdLength);

// get the public key object
const publicKeyBytes = authData.slice(
    55 + credentialIdLength);

// the publicKeyBytes are encoded again as CBOR
const publicKeyObject = CBOR.decode(
    publicKeyBytes.buffer);
console.log(publicKeyObject)

{
    1: 2,
    3: -7,
    -1: 1,
    -2: Uint8Array(32) ...
    -3: Uint8Array(32) ...
}

The resulting publicKeyObject should then be paired and save with the user registration on the server.

Upvotes: 0

Related Questions