Differences between U2F and FIDO2

3,041 views
Skip to first unread message

Sebastian K

unread,
Mar 19, 2019, 10:32:30 AM3/19/19
to FIDO Dev (fido-dev)
Dear community,

as far as I understood by reading the specs, one of the main differences between U2F and FIDO2 is that with U2F you always need a second factor, e.g. a password.
This is because U2F authenticators do not store a user handle for a credential, which FIDO2 authenticators do.

Am I right and is this the only conceptual difference between U2F and FIDO2?
Does this mean, that with a U2F-enabled but not FIDO2-enabled authenticator I will not be able to use a FIDO2 registration/login form, if the Relying Party does not offer a second registration/login form which takes a password as a second factor?

Kind regards and many thanks
Sebastian

James Manger

unread,
Mar 20, 2019, 2:07:31 AM3/20/19
to FIDO Dev (fido-dev)
You are basically correct, Sebastian.

A FIDO2 authenticator can have the same functionality as a U2F authenticator: no User Verification, just checks a User is Present (by, say, pressing a button); and needs the key handle that the authenticator generated when registering with a specific server to be passed back to the authenticator for each login.

Other FIDO2 authenticators can have extra functionality: User Verification (eg fingerprint, face recognition, or PIN); and/or storing the {server id, user id, key pair, key handle} on the authenticator (called a "resident key").

The different functionality that FIDO2 authenticators can have does require a few different login sequences.

A Relying Party (RP) could choose to only support FIDO2 authenticators that do User Verification, for instance. Such an RP will reject U2F and FIDO2 authenticators that only have the U2F-style User-Presence-only functionality. Another Relying Party can support any FIDO2 authenticator, and add its own password-prompt when a particular authenticator does not do User Verification.

--
James Manger

Sebastian K

unread,
Mar 20, 2019, 3:42:10 AM3/20/19
to FIDO Dev (fido-dev)
Thank you for the clarification!

So I come to the following conclusions:

A relying party server providing a website with FIDO2 login MAY choose to support U2F devices. If it does, a single login flow MUST require a username in order to allow the relying party to look up the correct public key for U2F Authenticator assertions using the downwards compatibility of FIDO2, OR the relying party provides two different login flows, one for U2F with username and MAYBE a password using only the U2F legacy JavaScript APIs with CTAP1 OR using the FIDO2 WebAuthn/CTAP2 APIs. And another flow for FIDO2 non-U2F without any username and password. This is because the FIDO2 authenticators can store the user handle.

In other words: If I as a relying party choose to support only logins without any username and password, I choose to drop support for U2F-only devices that do not speak FIDO2 and therefore do not return user handles during assertions.

Are my assumptions correct?

Fred Le Tamanoir

unread,
Mar 20, 2019, 4:51:29 AM3/20/19
to Sebastian K, FIDO Dev (fido-dev)
All your assertions are correct.

Not recommended but for the record: "cheating" to use U2F tokens without first identification is only possible in a very small deployment scale. Let's say you have 5 tokens for 5 admin users => your server/RP can blindly ask for all the 5 key handles you previously recorded during registration to any of the 5 tokens.

Regards
--
Fred

--
You received this message because you are subscribed to the Google Groups "FIDO Dev (fido-dev)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fido-dev+u...@fidoalliance.org.
To post to this group, send email to fido...@fidoalliance.org.
Visit this group at https://groups.google.com/a/fidoalliance.org/group/fido-dev/.
To view this discussion on the web visit https://groups.google.com/a/fidoalliance.org/d/msgid/fido-dev/01ae015a-26cb-4f82-b62f-dfb8c18ddf92%40fidoalliance.org.

James Manger

unread,
Mar 20, 2019, 9:02:18 PM3/20/19
to FIDO Dev (fido-dev)
Close, but not precisely right.

WebAuthn implementations in the browser/OS look for external Security Keys connected via USB/NFC/BLE using CTAP1 (U2F Security Keys) and CTAP2 (FIDO2 Security Keys). CTAP = client-to-authenticator protocol, which is the comms over USB, NFC, or BLE to an external authenticator.
Consequently there is no need for a site to use the legacy U2F Javascript APIs. U2F Security Keys still work when a site uses the FIDO2 WebAuthn API.

The username is used by the server to lookup the "credential ids" (not the actual public keys) to put in the allowedCredentials field of a WebAuthn call.

Authenticators don't return user handles. They return a credential id, which the server will have linked to a specific user in the server's DB.
The issue with nearly-stateless U2F or FIDO2 authenticators is that they need state (in the form of a previously registered credential id) to operate (ie from which to re-derive or decrypt the private signing key).

Shane Weeden

unread,
Mar 20, 2019, 9:37:47 PM3/20/19
to James Manger, FIDO Dev (fido-dev)
For passwordless flow FIDO2 authenticators do return both a user handle and credential id. There are rules (section 7.2 step 2) in the WebAuthn spec for validation of same. 

Regards,
Shane. 

Sent from my iPhone
--
You received this message because you are subscribed to the Google Groups "FIDO Dev (fido-dev)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fido-dev+u...@fidoalliance.org.
To post to this group, send email to fido...@fidoalliance.org.
Visit this group at https://groups.google.com/a/fidoalliance.org/group/fido-dev/.

James Manger

unread,
Mar 21, 2019, 12:46:17 AM3/21/19
to FIDO Dev (fido-dev), James.H...@team.telstra.com

Sorry, I forgot the optional userHandle field in the WebAuthn login response.
Curiously a (FIDO2) Security Key from Yubico with a resident key will sometimes return the userHandle and sometime not, depending on whether allowCredentials is empty or includes the resident key's id respectively.
I'm not sure about the purpose of the unsigned userHandle field in a response.

Shane Weeden

unread,
Mar 21, 2019, 1:04:03 AM3/21/19
to James Manger, FIDO Dev (fido-dev)
About the only thing I can think of that userHandle validation actually tries to do is ensure that for password-less login flows that the user the credential was originally registered by is still the current owner. It would be interesting to find out from the original WebAuthn spec authors what the motivating use case was that led to its inclusion...

Sent from my iPhone
--
You received this message because you are subscribed to the Google Groups "FIDO Dev (fido-dev)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fido-dev+u...@fidoalliance.org.
To post to this group, send email to fido...@fidoalliance.org.
Visit this group at https://groups.google.com/a/fidoalliance.org/group/fido-dev/.

Sebastian K

unread,
Mar 21, 2019, 3:51:41 AM3/21/19
to FIDO Dev (fido-dev), James.H...@team.telstra.com
Hi everyone,

thank you so much for your explanations so far! I'm happy that this community is active and answering so quickly.
So from my perspective the optional userHandle in AuthenticatorAssertionResponse (see §5.2.2 https://www.w3.org/TR/webauthn/#iface-authenticatorassertionresponse) can be used to support completeley username-less and password-less login flows:

Registration without username:
  1. Relying party creates a challenge and a user id (user handle), stores { challenge -> userId } and returns it to the browser together with some more options in a PublicKeyCredentialCreationOptions object. The field authenticatorSelection.requireResidentKey is true
  2. The browser passes the PublicKeyCredentialCreationOptions to the authenticator via WebAuthn - this object includes a PublicKeyCredentialUserEntity which in turn contains an id. According to the specs this id is the user handle (see https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity) generated by the relying party.
  3. The user is asked to confirm his/her registration using one of the available authenticators (that must support resident keys), user selects an authenticator and confirms
  4. The authenticator generates a public/private key pair, stores { rpIdHash -> { userId, privateKey} } and returns the public key in an AuthenticatorAttestationResponse
  5. The browser sends the AuthenticatorAttestationResponse back to the relying party server where it is verified (challenge check, attestation check and a lot of other steps). The relying party can look up the challenge in { challenge -> userId } and therefore store { userId -> publicKey } for subsequent logins
Login without username:
  1. Without a username, a relying party does not have a clue which user and therefore which private key will be used to sign any challenge. It nevertheless generates a challenge at the beginning of the login ceremony and the browser passes it in form of a PublicKeyCredentialRequestOptions object to the authenticator. The allowCredentials field of this data structure is empty
  2. The browser talks to all authenticators available via BLE/USB/NFC via CTAP2. Each authenticator sees the rpId Hash and will look up the tuple { userId, privateKey } using the { rpIdHash -> { userId, privateKey } } structure which was stored during registration
  3. User chooses an authenticator, confirms, and the authenticator signs the challenge with the private key and returns an AuthenticatorAssertionResponse containing the userId and the signed challenge. Note: As far as I can see this AuthenticatorAssertionResponse does not include any key handle which the relying party could use, because during login/assertion no attestation object is present (which could include the credential ID) - therefore the user handle apparently is necessary, otherwise the relying party has no chance to look up the public key with which the challenge was signed
  4. The browser sends back the AuthenticatorAssertionResponse object containing the user handle and the signed challenge
  5. Relying party can look up the public key for the user handle using the { userId -> publicKey } structure that was created during registration and validate the signature for the challenge
  6. User is logged in, we now get out of scope of specification
I hope I got it right. So this is, from my perspective, why registrations/logins without username require always a resident key. Otherwise the relying party can not know which key was used to sign.

What confuses me is that the user handle is not part of the signature of the AuthenticatorAssertionResponse, so it's integrity is not protected as much as the rest of the data.
I could imagine that this has something to do with the downwards compatibility between FIDO2 and U2F, but this is just guessing.

Kind regards
Sebastian

Shane Weeden

unread,
Mar 21, 2019, 6:29:42 AM3/21/19
to Sebastian K, FIDO Dev (fido-dev), James.H...@team.telstra.com
Assertion responses with empty allowCredentials list do contain a credential id as well as the userHandle. 

Sent from my iPhone

Thomas Duboucher

unread,
Mar 21, 2019, 7:05:44 AM3/21/19
to James Manger, FIDO Dev (fido-dev), James.H...@team.telstra.com
If the allowList is not empty, it means the server already knows the user's identity, thus there is no need to return a userHandle in the attestation.

Best regards,
--
Thomas Duboucher
signature.asc

Sebastian K

unread,
Mar 21, 2019, 8:02:16 AM3/21/19
to FIDO Dev (fido-dev), sebastia...@googlemail.com, James.H...@team.telstra.com
According to the spec, for authentications (i.e. logins) no attestedCredentialData is included in the authenticator response (see §6.1 of https://www.w3.org/TR/webauthn/#sec-authenticator-data), it is only included for attestations (i.e. registrations):

"For attestation signatures, the authenticator MUST set the AT flag and include the attestedCredentialData. For authentication signatures, the AT flag MUST NOT be set and the attestedCredentialData MUST NOT be included."

Therefore I assume that the credentialId is not included in the assertion response in case the RP passed an empty allowCredentials list to the authenticator. And because of that the authenticator has to return a user handle in the assertion response. Don't hesitate to correct me if I'm wrong.

Thank you
Sebastian

Ackermann Yuriy

unread,
Mar 21, 2019, 8:44:10 AM3/21/19
to Sebastian K, FIDO Dev (fido-dev), James Manger
The credentialId is included in every assertion. It will be in the id/rawId field

Yuriy Ackermann
FIDO, Identity, Standards
skype: ackermann.yuriy
github: @herrjemand
twitter: @herrjemand
medium: @herrjemand


Sebastian K

unread,
Mar 21, 2019, 11:00:06 AM3/21/19
to FIDO Dev (fido-dev)
Thanks for clarifying that!
Of course the assertion contains the credential id as well.
Sorry for having missed something which is pretty obvious.

Can you think of a use case where the user handle plays an important role after all?
Isn't the credential id in the asserrion always enough for the relying party to create an association to the public key and therefore with a user?

Regards
Sebastian

James Manger

unread,
Mar 22, 2019, 2:46:02 AM3/22/19
to FIDO Dev (fido-dev), James.H...@team.telstra.com
userHandle does have a purpose. It allows an RP to avoid having to index its user accounts using credential ids, which are chosen by authenticators and may not always have as much entropy as they should (or could even be maliciously chosen).
I've noticed a few bugs with userHandle: Chrome sets it to an empty array (instead of omitting it); Firefox always omits it (though there is a fix on the way).

Bobby

unread,
Jun 10, 2020, 7:54:02 AM6/10/20
to FIDO Dev (fido-dev)
Since i found this thread from googling 'userHandle fido2' i thought id share my observed behaviour:

Using a Feitian FIDO2 K9 key

1. Having user.id set and requireResidentKey: false at register(), returns userHandle: null in the subsequent get() operation. 

2. Setting requireResidentKey: true at register() will return user.id as userHandle upon get(), both with and without populated allowCredentials array

3. Having no credentials registred with requireResidentKey: true will return an OS dialog with "This key cannot be used" if you get() with an empty/abscent allowCredentials array

4. Credentials id is always populated, regardless of get() and register() options - in all cases ive seen.

Side note 1: Chrome 83 returns userHandle: null and not empty array as James states - probably bug fixed

Side note 2: user.id/userHandle is up to 64 bytes, free to choose at registration time, credentials id on the Feitian key is 32 bytes, seemingly random
Reply all
Reply to author
Forward
0 new messages