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

[Device registration] Leftover bytes detected while parsing authenticator data #440

Closed AntonyChiossi closed 1 year ago

AntonyChiossi commented 1 year ago

Describe the issue

When using Firefox (version 117), I encounter the error message "Leftover bytes detected while parsing authenticator data," but this issue is not replicated in Chrome—where the functionality works as expected. I am seeking assistance in understanding the cause of this discrepancy and finding a solution to ensure consistent behavior across browsers.

Reproduction Steps

  1. I was able to reproduce it only locally, is there some demo project with the latest version where I can test it?

Expected behavior

Be able to register a Fido2 device

Code Samples + WebAuthn Options and Responses

{
  "conf": {
    "registrationRequest": {
      "challenge": "XXXXXXXXX",
      "rp": {
        "name": "XXXXX",
        "id": "localhost"
      },
      "user": {
        "id": "XXXX",
        "name": "XXXX",
        "displayName": "XXXX"
      },
      "pubKeyCredParams": [
        {
          "alg": -8,
          "type": "public-key"
        },
        {
          "alg": -7,
          "type": "public-key"
        },
        {
          "alg": -257,
          "type": "public-key"
        }
      ],
      "timeout": 60000,
      "attestation": "indirect",
      "excludeCredentials": [],
      "authenticatorSelection": {
        "residentKey": "preferred",
        "userVerification": "preferred",
        "requireResidentKey": false
      },
      "extensions": {
        "credProps": true
      }
    }
  }
}

Dependencies

Both browser and server packages are at latest versions.

SimpleWebAuthn Libraries

Additional context

I tried commenting out this code:

// if (authData.byteLength > pointer) {
//     throw new Error('Leftover bytes detected while parsing authenticator data');
// } 

And the next error encountered was:

if (fidoUserVerification === 'required') {
            // Require `flags.uv` be true (implies `flags.up` is true)
    if (!flags.uv) {
        throw new Error('User verification required, but user could not be verified');
    }
}
CleyFaye commented 1 year ago

Can you try to manually provide a list of algorithms and only keep -7 and -257 ? This is in the parameter for generateRegistrationOptions():

{
  supportedAlgorithmIDs: [-7, -257],
}

We also noticed a similar behavior (invalid length of the auth data response in firefox) but only when the algorithm identified by -8 was used. It may help to narrow the issue.

Geam commented 1 year ago

I have the same "Leftover bytes detected" issue and as far as I understand, it seems to be related to bad publicKey cbor, the cbor start with a3 instead of a4 which makes the isoCBOR.decodeFirst function return truncated data leaving the public key out.

Here's my actual data

{
  "id": "ctOho_p88y9ENn34R1hf8IUMe9YsM4q0W-H9pv23mYL5bCDvwLtu2TR-jB53aQ5nsiW0haCY9vRv3j8qhazQF3oE1rtcdWb7iYgd_kjqerw2H3rK-GpZZq3vVXkw-lwEXGNvUM-TjlCKgbhFE06ymI3Drwq2-Yz8YVUyaEtKY2M",
  "rawId": "ctOho_p88y9ENn34R1hf8IUMe9YsM4q0W-H9pv23mYL5bCDvwLtu2TR-jB53aQ5nsiW0haCY9vRv3j8qhazQF3oE1rtcdWb7iYgd_kjqerw2H3rK-GpZZq3vVXkw-lwEXGNvUM-TjlCKgbhFE06ymI3Drwq2-Yz8YVUyaEtKY2M",
  "response": {
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBBrQEmbAnGmiVcmfeTsQAVqdMh1jGWC4eAfzzV9cxAee6RQAAAAQAAAAAAAAAAAAAAAAAAAAAAIBy06Gj-nzzL0Q2ffhHWF_whQx71iwzirRb4f2m_beZgvlsIO_Au27ZNH6MHndpDmeyJbSFoJj29G_ePyqFrNAXegTWu1x1ZvuJiB3-SOp6vDYfesr4allmre9VeTD6XARcY29Qz5OOUIqBuEUTTrKYjcOvCrb5jPxhVTJoS0pjY6MBY09LUAMnIGdFZDI1NTE5IZggGNUYWBhzGOYYiRjrGKsYfhj9GP0YXRhLCBhLGHMY6BjhGPgYxxUY_xj1GDoY_RijGGsYXxEJGD4YOxQ",
    "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJXd0hTZ2pibFJRbXZnZmowUV9EZ3p2a0NBRlFYRzVEVGszcE9SZEZ2cUVFIiwib3JpZ2luIjoiaHR0cHM6Ly9lZHdpbi5jbGllbnRzLmJldGEua2VlZXgubWUiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
  },
  "type": "public-key",
  "clientExtensionResults": {}
}

Here's the decoded public key I got with original data

Map(3) {
  1 => 'OKP', // kty
  3 => -8, // alg
  -1 => 'Ed25519' // crv
}

If I change the byte to a4, here's what I got

Map(4) {
  1 => 'OKP', // kty
  3 => -8, // alg
  -1 => 'Ed25519', // crv
  -2 => [
    213,  88, 115, 230, 137, 235, 171, 126,
    253, 253,  93,  75,   8,  75, 115, 232,
    225, 248, 199,  21, 255, 245,  58, 253,
    163, 107,  95,  17,   9,  62,  59,  20
  ]
}

Which seems better. Though I'm not sure as to why kty and crv are strings instead of respectively 1 and 6.

MasterKale commented 1 year ago

Which seems better. Though I'm not sure as to why kty and crv are strings instead of respectively 1 and 6.

Ah, it seems this issue I resolved over in the py_webauthn project has finally found its way to SimpleWebAuthn:

https://github.com/duo-labs/py_webauthn/issues/160

There's a follow-up issue that tracks this down to an issue with Firefox:

https://github.com/duo-labs/py_webauthn/issues/175

Are you using Firefox 117 by chance?

Geam commented 1 year ago

Thank you for the reply. Yes, I'm using firefox 117.0.1

MasterKale commented 1 year ago

This is fixed in the newly published @simplewebauthn/server@8.2.0 ✌️

AntonyChiossi commented 1 year ago

@MasterKale

After updating both packages to their latest version, registration fails but with a different error this time. Chrome keeps working.

Here some debugging info:

Working with Chrome:

{
  request: {
     challenge: 'NkUgPU8KfTEuMF-78rbkr81NIF0SoU9hBhkFRffApI0',
     rp: { name: 'REDACTED', id: 'localhost' },
     user: {
       id: 'REDACTED',
       name: 'REDACTED',
       displayName: 'REDACTED'
     },
     pubKeyCredParams: [ [Object], [Object], [Object] ],
     timeout: 60000,
     attestation: 'indirect',
     excludeCredentials: [],
     authenticatorSelection: {
       residentKey: 'preferred',
       userVerification: 'preferred',
       requireResidentKey: false
     },
     extensions: { credProps: true }
   },
   response: {
     id: 'KEthTlOv7trIQ0g9hvZmvAOxVOmhh-UPa7QotW5nNDUNsJXdIviXylbk5HvEhtfx',
     rawId: 'KEthTlOv7trIQ0g9hvZmvAOxVOmhh-UPa7QotW5nNDUNsJXdIviXylbk5HvEhtfx',
     response: {
       attestationObject: 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEYwRAIgaDfm9cLN7bE8fIIT2JwNNjpggnHXClnAiBNZ3WYrNBMCICoDf7lc6Y3eX70TNVWyYTy75BdmPPSmN88lcfQ97eAzY3g1Y4FZAt0wggLZMIIBwaADAgECAgkA4RPwzmmndbYwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDEzMjQzNTY1NzcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS-EvB5JATjjS2-GSN4diiQ48S8kAj5g26rCBkDfZIcQYjKL6qE6gKM39rgzxD1khkn6i1A40ik2wC_8Z_60--qo4GBMH8wEwYKKwYBBAGCxAoNAQQFBAMFBAMwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjEwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQpOn8bUy-R1i4ujdZi7W7qjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQChRXSOMBqLK8u9kS4S3r2Ma7Dz740fXnifG3l8Il57IXCDLH9r3qXAG9-a8cBVTF4MvGOBMK90wB1B8LXlCUM6lRM2sKDYN2c1AxhYB3T4EEmVuxT9dDwumyOOm68aOi2nnGDKtgxelx-71dj-bKvZGN3ToOysx8WKbsnxHnDo1qNOTxyxeEgKKG_AXjxLIAgZHTXaYTrY_h6uGVr96lPzKGD-Im7lVsdWE9Bce7xzRoO62faYWC71xbBiNWU-t53v8QBDi_Ua4zYR0Zm_ULnwRUqHQ4X5MPLzxRw59NBh3JUuVKWC69ByxFqIOlFm56Y_UuLvYo8E3Q_wsPBj1WBraGF1dGhEYXRhWJ9Jlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY8UAAAAEpOn8bUy-R1i4ujdZi7W7qgAwKEthTlOv7trIQ0g9hvZmvAOxVOmhh-UPa7QotW5nNDUNsJXdIviXylbk5HvEhtfxpAEBAycgBiFYIChLYU5Tr-7ayENIPYac8k9s8ypXjiqCFkDg_FpuygxpoWtjcmVkUHJvdGVjdAI',
       clientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiNVFVQml0NjRiZFViQ21faGtFSWN1ZnJkNDY3VVhidjdXa1U3VW5xYlM5OCIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjQyMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
       transports: [Array],
       publicKeyAlgorithm: -8,
       publicKey: 'MCowBQYDK2VwAyEAKEthTlOv7trIQ0g9hpzyT2zzKleOKoIWQOD8Wm7KDGk',
       authenticatorData: 'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2PFAAAABKTp_G1MvkdYuLo3WYu1u6oAMChLYU5Tr-7ayENIPYb2ZrwDsVTpoYflD2u0KLVuZzQ1DbCV3SL4l8pW5OR7xIbX8aQBAQMnIAYhWCAoS2FOU6_u2shDSD2GnPJPbPMqV44qghZA4PxabsoMaaFrY3JlZFByb3RlY3QC'
     },
     type: 'public-key',
     clientExtensionResults: { credProps: [Object] },
     authenticatorAttachment: 'cross-platform'
   },
   expectedChallenge: '5QUBit64bdUbCm_hkEIcufrd467UXbv7WkU7UnqbS98',
   expectedOrigin: 'https://localhost:4200',
   expectedRPID: 'localhost',
   verification: {
     verified: true,
     registrationInfo: {
       fmt: 'packed',
       counter: 4,
       aaguid: 'a4e9fc6d-4cbe-4758-b8ba-37598bb5bbaa',
       credentialID: [Uint8Array],
       credentialPublicKey: [Uint8Array],
       credentialType: 'public-key',
       attestationObject: [Uint8Array],
       userVerified: true,
       credentialDeviceType: 'singleDevice',
       credentialBackedUp: false,
       origin: 'https://localhost:4200',
       rpID: 'localhost',
       authenticatorExtensionResults: [Object]
     }
   }
 }

Failing with Firefox

{
  request: {
     challenge: 'OqmoL2yMQdmwcL4jLL85SkcEq1_6cI0InUDUcO1WE1o',
     rp: { name: 'REDACTED', id: 'localhost' },
     user: {
       id: '63d3d082b7610778c308f25c',
       name: 'REDACTED',
       displayName: 'REDACTED'
     },
     pubKeyCredParams: [ [Object], [Object], [Object] ],
     timeout: 60000,
     attestation: 'indirect',
     excludeCredentials: [],
     authenticatorSelection: {
       residentKey: 'preferred',
       userVerification: 'preferred',
       requireResidentKey: false
     },
     extensions: { credProps: true }
   },
   response: {
     id: '2PvlO37W-pdWdMvEnigOU0BlBZ-PtE4OZSZsxPbRN8bodRw378VPVmaur1Oks6u5',
     rawId: '2PvlO37W-pdWdMvEnigOU0BlBZ-PtE4OZSZsxPbRN8bodRw378VPVmaur1Oks6u5',
     response: {
       attestationObject: 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEYwRAIgHcyLYtm1V14B1Y90sNTpZGXpi2OV40za0-8xkyYtTeECICkGdaW-oRAJHrkbUuEetcGVgRXxGXHtAbkZt2fTKS2kY3g1Y4FZAt0wggLZMIIBwaADAgECAgkA4RPwzmmndbYwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDEzMjQzNTY1NzcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS-EvB5JATjjS2-GSN4diiQ48S8kAj5g26rCBkDfZIcQYjKL6qE6gKM39rgzxD1khkn6i1A40ik2wC_8Z_60--qo4GBMH8wEwYKKwYBBAGCxAoNAQQFBAMFBAMwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjEwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQpOn8bUy-R1i4ujdZi7W7qjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQChRXSOMBqLK8u9kS4S3r2Ma7Dz740fXnifG3l8Il57IXCDLH9r3qXAG9-a8cBVTF4MvGOBMK90wB1B8LXlCUM6lRM2sKDYN2c1AxhYB3T4EEmVuxT9dDwumyOOm68aOi2nnGDKtgxelx-71dj-bKvZGN3ToOysx8WKbsnxHnDo1qNOTxyxeEgKKG_AXjxLIAgZHTXaYTrY_h6uGVr96lPzKGD-Im7lVsdWE9Bce7xzRoO62faYWC71xbBiNWU-t53v8QBDi_Ua4zYR0Zm_ULnwRUqHQ4X5MPLzxRw59NBh3JUuVKWC69ByxFqIOlFm56Y_UuLvYo8E3Q_wsPBj1WBraGF1dGhEYXRhWLlJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0UAAAAEpOn8bUy-R1i4ujdZi7W7qgAw2PvlO37W-pdWdMvEnigOU0BlBZ-PtE4OZSZsxPbRN8bodRw378VPVmaur1Oks6u5owFjT0tQAycgZ0VkMjU1MTkhmCAY2Bj7GOUYOxh-GNYY-hiXGFYYdBjLGMQYnhjCGJYYuRjrGF8Yvhh0GB0YkhhHDhg3GE4YewcYORhPGNAY6A',
       clientDataJSON: 'eyJjaGFsbGVuZ2UiOiJEVHlCRUxQWEx5WHIwZlQ1ZmttaGlzbF9BRU1jMTNld1FUUVMteExDdkNRIiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDIwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ'
     },
     type: 'public-key',
     clientExtensionResults: {}
   },
   expectedChallenge: 'DTyBELPXLyXr0fT5fkmhisl_AEMc13ewQTQS-xLCvCQ',
   expectedOrigin: 'https://localhost:4200',
   expectedRPID: 'localhost',
   verification: { verified: false }
 }

The verification key is the result of the method verifyRegistrationResponse

MasterKale commented 1 year ago

After updating both packages to their latest version, registration fails but with a different error this time. Chrome keeps working.

@AntonyChiossi This is a separate issue, please create a new issue. I did some digging and it doesn't seem related to Firefox because that's a "packed" attestation statement in there which means signature verification is failing in a way that's related to the public key in the leaf cert, not the one provided in authData.