MasterKale / SimpleWebAuthn

WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
https://simplewebauthn.dev
MIT License
1.62k stars 137 forks source link

Decoding of publicKeyObject not possible? #289

Closed h4gen closed 2 years ago

h4gen commented 2 years ago

Hi 👋

I have a very weird problem. I basically follow the instruction to decode the attestationObject and get the publicKeyObject. However, it does not seem to work for me. I am basically copying the code from the website, but it already starts with the cbor.decode not being able to decode the attestationObject. After a lot of trying I was able to get it working by encoding the object as Base64 buffer. If I don't do this the decoding fails with "data errors".

  const options: PublicKeyCredentialCreationOptionsJSON = {
    challenge: getCreationChallenge?.toString(),
    attestation: "direct",
    pubKeyCredParams: [
      {
        alg: -7,
        type: "public-key",
      },
    ],
    rp: {
      id: "localhost",
      name: "SimpleWebAuthn",
    },
    user: {
      id: "5678",
      displayName: "username",
      name: "username",
    },
    timeout: 1000,
    authenticatorSelection: {
      authenticatorAttachment: "platform",
    },
    excludeCredentials: [],
  };

    const cred = await startRegistration(options);
    const decoded = cbor.decode(Buffer.from(cred.response.attestationObject, "base64"));
    console.log("Decoded:", decoded);
    const { authData } = decoded;
    console.log("Auth data:", authData);
    // 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("Public Key:", publicKeyObject);

The output looks like following:

Decoded:
{fmt: 'packed', attStmt: {…}, authData: Uint8Array(164)}
attStmt
: 
{alg: -7, sig: Uint8Array(71)}
authData
: 
Uint8Array(164) [73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 69, 0, 0, 0, 0, 173, 206, 0, 2, 53, 188, 198, 10, 100, 139, 11, 37, 241, 240, 85, 3, 0, 32, 75, 163, 246, 187, 39, 249, 246, 251, 139, 88, 71, 14, 158, 23, 247, 135, 190, 129, 158, 112, 203, 85, 24, 9, 225, 228, 108, 195, 178, 197, 40, 69, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 24, 0, 231, …]
fmt
: 
"packed"
[[Prototype]]
: 
Object

Auth data: Uint8Array(164) [73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 69, 0, 0, 0, 0, 173, 206, 0, 2, 53, 188, 198, 10, 100, 139, 11, 37, 241, 240, 85, 3, 0, 32, 75, 163, 246, 187, 39, 249, 246, 251, 139, 88, 71, 14, 158, 23, 247, 135, 190, 129, 158, 112, 203, 85, 24, 9, 225, 228, 108, 195, 178, 197, 40, 69, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 24, 0, 231, …]

Public Key:
{fmt: 'packed', attStmt: {…}, authData: Uint8Array(164)}
attStmt
: 
{alg: -7, sig: Uint8Array(71)}
authData
: 
Uint8Array(164) [73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 69, 0, 0, 0, 0, 173, 206, 0, 2, 53, 188, 198, 10, 100, 139, 11, 37, 241, 240, 85, 3, 0, 32, 75, 163, 246, 187, 39, 249, 246, 251, 139, 88, 71, 14, 158, 23, 247, 135, 190, 129, 158, 112, 203, 85, 24, 9, 225, 228, 108, 195, 178, 197, 40, 69, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 24, 0, 231, …]
fmt
: 
"packed"
[[Prototype]]
: 
Object

So, the weird thing is that somehow my publicKeyObject looks again like my attestationObject? Also the function getUint16 is supposed to have an offset parameter (see here)(This is currently wrong on the website). However this does also not change the behaviour for me. Any idea what is going on?

One remark: It seems strange, that there is no convenient way to decode the whole attestation object and that the users have to do it themselves. Is there a reason?

Thank you so much for your help!

Best, Hagen

MasterKale commented 2 years ago

Hello @h4gen, I haven't had a lot of time to dive into this, but one thing that might be throwing things off is trying to use Buffer.from(..., 'base64') to decode any values out of startRegistration(). Base64URL is used exclusively in the public API of any of SimpleWebAuthn's methods, and so trying to decode these using base64 instead is probably not helping your attempts to manually peel apart these data structures.

MasterKale commented 2 years ago

I'm going to close this out, it's been three weeks without a response. Please feel free to re-open if you need to continue troubleshooting this.