Yubico / python-fido2

Provides library functionality for FIDO 2.0, including communication with a device over USB.
BSD 2-Clause "Simplified" License
432 stars 109 forks source link

Token2 throws PIN_AUTH_INVALID when PIN Auth Protocol V2 and hmac-secret are used #192

Open ashleysommer opened 1 year ago

ashleysommer commented 1 year ago

This bug has a very specific set of circumstances to reproduce. This happens when performing GetAssertion on the device, under the following conditions:

  1. Using Token2 T2F2 device with FIDO 2.1 spec.
  2. Using PIN Auth Protocol V2 (this is the default in python-fido2 if V2 is supported on the device)
  3. Including hmac-secret extension in the assert request
  4. UV is not requested, and PinAuth Token is not included in the request (this is default on Token2)

If PinAuth Protocol V1 is forced, the error does not occur. If hmac-secret extension is not requested, the error does not occur. If UV is set to Preferred or Required, the request is authenticated with a PinAuth Token, and the error does not occur. When calling "CreateCredential", with "hmac-secret" extension, the error does not occur. It is only when calling "GetAssertion".

I don't know if this is a python-fido2 issue or a Token2 firmware issue. I am planning initiate a support request with Token2 and relay the same details. I also haven't yet tried to reproduce the configuration in libfido2 to see if it produces the same error.

ashleysommer commented 1 year ago

Note, I don't think this is related to the use of pinAuth sharedKey encryption on the hmac-secret salt. That part works fine, and when testing with PIN Protocol V1 that payload is sent without UV and without PinAuth token on the request and it still works as expected.

ashleysommer commented 1 year ago

I worked out the issue. It all comes down to this line in the code.

When PinProtocol is not set on the Client when doing GetAssertion (because uv is false, verification is not required), it sets the PinUVAuthVersion parameter on the GetAssertion request to None, that the FIDO2 spec says it implies Pin Protocol V1. But during the process of encrypting the HMAC-secret salt for the HMAC-secret extension, the code uses PIN Protocol V2 (because that is the default on Token2). That puts the Token2 in a situation where the call is assumed to be using Protocol V1, but the HMAC-secret extension has used Protocol V2.

I don't really know what the solution would be for this. The ClientBackend needs to know if any of the extensions have instantiated a Pin Protocol, and ensure it matches the PIN protocol itself is currently using (or if it doesn't have one, set it).

ashleysommer commented 1 year ago

Another thing to consider is the fact that Extensions are instantiated and processed (process_get_input()) before the ClientBackend runs _get_auth_params(), where it would normally create its own PinProtocol instance if required. This seems out of order to me. But it can't be the other way around because extensions can define permissions requirements, that are used by _get_auth_params(), so it cannot be the other way around. It seems to me that a PinProtocol instance should always be created as the first operation, then that instance is passed to each of the extensions as they require it, then that same instance is used in _get_auth_params() for creating ClientPIN if required. That would ensure the same PIN Protocol is used consistently across the whole backend.

dainnilsson commented 1 year ago

Version 1.1.2 is now released and should hopefully resolve this.

BryanJacobs commented 1 year ago

I raised https://github.com/Yubico/python-fido2/pull/197 before I saw this issue.