Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using FIDO2 authenticators with a PIN #2194

Merged
merged 9 commits into from
Aug 19, 2024

Conversation

TheMartinizer
Copy link
Contributor

@TheMartinizer TheMartinizer commented Feb 22, 2024

Added PIN verification for FIDO2 authenticators. I am not a great Kotlin or Android programmer, so this could probably have been done in a more elegant way.

When the AuthenticatorActivityFragment is started, a ModelView is created which contains two variables: a boolean that says whether the user has input a PIN, and a nullable string containing the PIN. These variables are then passed to the TransportHandler. If the authenticator wants a client PIN and the user has not yet been asked for a PIN (ie. the boolean is false), a MissingPin exception is thrown up to the AuthenticatorActivity. This makes the activity redirect the user to a PinFragment, where the user can enter a pin that is stored in the ModelView. If the user has previously cancelled entering the pin, the code tries to authenticate without a PIN (since some authenticators still seem to return something when no PIN is entered). If the authenticator returns the status code for wrong pin, the user is redirected to the PIN fragment with a message saying wrong pin.

This code has only been tested on a Yubikey over NFC, but seems to work reasonably well for both signing and registering. It is not tested for USB
To be implemented:

  • Telling the user how many PIN retries are left
  • Telling the user when the PIN has been wrongly entered too many times, and the authenticator reset
  • Probably a whole lot of error handling

when (options.registerOptions.authenticatorSelection?.requireUserVerification) {
REQUIRED -> true
DISCOURAGED -> false
else -> connection.hasUserVerificationSupport
}
// According to newer drafts of CTAP2.1, the user verification key MUST NOT be included
// if the authenticator is not capable of built-in verification
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you refer to where the specification says this? If the requirement is stripped if the authenticator is not capable, I wonder what the difference between requireUserVerification values of preferred and required is supposed to mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is stated in the v2.2 review draft: https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#authenticatorGetAssertion. However, since that is still a review draft, and it deprecates the use of the "uv" flag and mandates the use of a pinUvAuthParam option (which I have not implemented), I have dropped this from the code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In this case we should probably just bail out early if requireUserVerification is set to required and the authenticator doesn't support it. That way we still honor the requirement from WebAuthN correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have written it so that if no PIN is provided, the requireUserVerification variable is passed to the authenticator in the "uv" flag. On my Yubikey, this leads to a status code of 0x2b (unsupported error). I haven't changed the code that deals with CTAP error messages, so this is currently logged as an exception and then the program continues.

@TheMartinizer
Copy link
Contributor Author

Thank you for the very comprehensive feedback. I have rewritten and refactored the code a bit. The PIN protocol version and PIN subcommands are now named variables. The requireResidentKey variable should now be set in accordance with the WebAuthn standard. However, I moved the code where the variable is determined to a different place in the code, so it could be used to determine whether to use CTAP1 or CTAP2.

If I've understood it correctly, CTAP1 is not capable of handling resident keys, or of handling user verification with a PIN (although as I understand it built-in user verification on the authenticator itself can still be handled). I've therefore set it so that CTAP2 registering is only used if the requesting party requires resident keys, or if user verification is required and the only available method of user verification is client PIN. Otherwise, it attempts CTAP1 registering.

As the code is currently written, the first credential matching the requesting party ID is returned if the allowList is empty. I think this a good behaviour for the moment, since most sites where the passkey is registered with a resident key will send an empty allowList. Using the first available credential at least allows the user to log in with one credential, although if the user has several credentials they will not be able to choose. I can look into creating a menu for choosing the correct credential later.

@BryanJacobs
Copy link

If I've understood CTAP1 correctly, it can do a lot of the same things as CTAP2, but it cannot handle resident keys or PIN authentication

This is correct. CTAP1/U2F can use built-in user verification but not clientPin. It also can't handle any extensions, so your logic should be to use CTAP2 when any of the following is true:

  • UV required and built-in UV unavailable
  • Resident key / discoverable credential required
  • Extensions present that are passed to the authenticator (ie, not the appId extension)

I have written it so that if no PIN is provided, the requireUserVerification variable is passed to the authenticator in the "uv" flag

For clarity, setting uv=true means you want the authenticator to do on-board user verification (NOT USING A PIN). If the PIN is being passed you should not pass uv=true to the authenticator, but it will still return uv=true in the response.

This is a bit confusing, yes. UV in the INput to the authenticator means that the authenticator should pop up a prompt or use its own fingerprint or something like that. UV in the OUTput means the user was verified by any means, including the given clientPin.

As the code is currently written, the first credential matching the requesting party ID is returned if the allowList is empty. I think this a good behaviour for the moment, since most sites where the passkey is registered with a resident key will send an empty allowList. Using the first available credential at least allows the user to log in with one credential, although if the user has several credentials they will not be able to choose. I can look into creating a menu for choosing the correct credential later.

Authenticators return the most recently created or updated credential first, so this is a good way to do it if you have no menu.

@BryanJacobs
Copy link

Just want to add that I built this branch (merged with microg master) and it works pretty darn well with a https://github.com/BryanJacobs/FIDO2Applet install over NFC. I can do webauthn in Fennec with PINs.

@mar-v-in mar-v-in modified the milestones: 0.3.2, 0.3.3 Apr 30, 2024
@CoelacanthusHex
Copy link
Contributor

Firefox Android has merged FIDO2 Passwordless feature. But it can't be used because microG doesn't support PIN (user verify). Hope this PR can be merged soon.

https://bugzilla.mozilla.org/show_bug.cgi?id=1862132

@mu88
Copy link

mu88 commented Jul 27, 2024

@mar-v-in will this PR make it into the next version 0.3.3? 🤩

@TheMartinizer
Copy link
Contributor Author

Sorry, I didn't realise force-pushing the branch this pull request was based on would close the pull request. I guess I need to learn how Github works.

@TheMartinizer
Copy link
Contributor Author

Recreated the branch from the pull request

@ale5000-git
Copy link
Member

Don't worry, the PR can always be reopened.

@CoelacanthusHex
Copy link
Contributor

Hope this PR can be merged soon. This is the key feature to use FIDO2 Passwordless on Android.

@mar-v-in mar-v-in merged commit 25deea5 into microg:master Aug 19, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants