javascriptgoogle-chromefirefoxwebauthnyubikey

Is Chrome ignoring WebAuthentication "userVerification" setting?


I'm currently working on an authentication flow based in web authentication. I've a yubikey 5 device for testing it.

For the authentication ceremony, I'm using the following settings:

What I see is that when using Chrome with those settings, the device PIN is always requested to the user. This behavior is different in Firefox. In that case the PIN is not being requested to the user.

This can be easily reproduced in this demo site: https://webauthn.io/

Does anybody have an idea about why the behavior is different between both browsers? Could this be a bug in Chrome?

Thank you in advance.

Note: tested with versions:


Solution

  • Update

    So original answer holds true, and is how this works, but I did forget about one part as most of what I do is CTAP and not WebAuthn. While WebAuthn doesn't cover CredProtect much at all, the MDM API Implementation does through Extensions, more specifically credentialProtectionPolicy.

    This would let you manually set credProtect to level 1 on your request which would allow you to use that credential to do assertions without userVerification.

    As you tagged the questions with javascript I assume you are doing this through navigator.credentials.create(options) and the request would then need to include

    extensions: {
      credentialProtectionPolicy: "userVerificationOptional"
    }
    

    Inside the options object. Chrome will then respect the CredProtect Level and set it to 1 while setting RK to true given the usual request. This isn't possible to test on webauthn.io but you would be able to do it on Auth0's test implementation

    Original Answer

    There are subtle differences in how Chrome has implemented Webauthn vs how Firefox has done it. If I generate a request through Firefox Webauthn will trigger the following CTAP request

    {
      1 => <Buffer 34 9d 9d c8 ef 9a c9 36 b3 3c dd f9 54 aa 57 b4 b1 f0 52 dd c5 7f 86 2e 37 9e ff b4 57 f7 48 57>,
      2 => {
        id: 'webauthn.io',
        name: 'webauthn.io'
      },
      3 => {
        displayName: 'abcde',
        id: <Buffer 58 4c 54 67 73 77 4e 6a 37 34 5f 49 62 4d 75 68 67 47 74 43 5a 4c 6f 67 55 48 50 72 63 4d 49 4c 51 45 72 78 77 73 64 37 35 48 51>,
        name: 'abcde'
      },
      4 => [
        {
          alg: -7,
          type: 'public-key'
        },
        {
          alg: -257,
          type: 'public-key'
        }
      ],
      7 => {
        rk: true
      },
      8 => <Buffer c3 3b a4 9b 74 06 ae e8 76 df 66 a7 57 4e 6b 00 a9 a2 00 e4 ce 40 a4 d4 89 40 06 92 bd a2 80 10>,
      9 => 2
    }
    

    This request as seen contains what you'd expect, the request itself has rk = true which would make a discoverable credential on the device. The request has a token, but that only affects our creation and not the assertion afterwards.

    If I make the same request in Chrome however I get the following request

    {
      1 => <Buffer b9 da 62 ad 82 89 4d 87 c8 8c b2 e0 a2 2d 74 23 ef a2 af 04 a9 16 06 76 d6 c4 13 c0 08 04 fa 19>,
      2 => {
        id: 'webauthn.io',
        name: 'webauthn.io'
      },
      3 => {
        displayName: 'Testsetesrsdr',
        id: <Buffer 45 37 4b 4c 76 50 48 59 46 4d 42 7a 5a 5a 75 48 6f 6d 31 4f 65 6f 43 33 68 41 5f 70 44 68 73 57 56 53 45 48 32 41 39 57 71 79 4d>,
        name: 'Testsetesrsdr'
      },
      4 => [
        {
          alg: -7,
          type: 'public-key'
        },
        {
          alg: -257,
          type: 'public-key'
        }
      ],
      6 => {
        credProtect: 2
      },
      7 => {
        rk: true
      },
      8 => <Buffer 1d df 5a dc 36 8b 59 92 95 dd 07 32 c5 8b 1d e8 64 57 c1 e2 b0 67 54 22 b1 7b 50 24 b3 4d 3f 05>,
      9 => 2
    }
    

    Almost the same request, rk is also true here among other but there is one critical difference. Chrome has added a value to key 6 which is credProtect: 2. CredProtect Level 2 is defined in CTAP as userVerificationOptionalWithCredentialIDList which means the key would only be discoverable through either some sort of User Verification (in your case, the pin), or by including it in the allowList. More info on CredProtect

    I strongly doubt this is a bug in Chrome, but actually something Chrome has decided they want have as part of their implementation. Webauthn in itself does very little to cover anything regarding CredProtect so this decision doesn't affect their conformance with the specification either.

    If this is the correct way to do it by Chrome becomes very opinion-based and it is not necessarily the right way as supplying allowList to requests comes with a lot of privacy concerns, many of whom are covered in webAuthn, especially chapter 14.6.3.

    So, unless Chrome changes, which I doubt, your only options here is to either maintain and supply the allowList or do the UserVerification ceremony. The key generated through Firefox would however be possible to get without either as the default value for CredProtect in CTAP is 1. And it would be possible to get it on either Chrome or Firefox.