1.5.9. FIDO U2F#

Overview#

Token based 2FA is a great improvement regarding the security of login procedures. But it requires additional efforts from users: they have to handle the tokens. Acceptance is a very important factor for a successful implementation of 2FA. The more logins are secured with an additional factor the more tokens must be handled by the users and the administrators. Using the same token for different logins is sometimes an option, but would - beside other problems - imply to share the same secret between different vendors. From security perspective this is not acceptable. FIDO U2F is designed exactly to solve this issue: one Universal second Factor (U2F) is specifically designed for the logins to different websites without compromising the security and providing a convenient workflow with only one token for users and administrators.

Starting with version 2.8 LinOTP supports the FIDO U2F standard completely. Now U2F can be implemented easily in existing web environments via the LinOTP WEB API. The token/user management itself is performed through the convenient LinOTP web frontends or can be scripted for the API as well.

FIDO U2F is technically based on the proven private/public key concept. For every website login a public key is generated and stored in the database of LinOTP. This means - in difference to common tokens - the secret stays on the token alone. The public keys are not of any value (for an attacker) except to authenticate the user with the correct token.

The login procedure looks like this:

  • When the user tries to login a challenge is generated by the web browser for this specific URL.

  • This challenge is signed with the private key from the token requiring a user interaction (for example: insert the USB token and press a key) and sent to the LinOTP server.

  • LinOTP can validate the correctness of the used token with the help of the stored public key and allow (or deny) the login to this specific web site.

Note

The users need a compatible web browser to validate via the new U2F standard. At the moment only Google Chrome supports U2F, but all other vendors are working on it.

For detailed information about U2F please read:

https://fidoalliance.org/

Example for U2F with LinOTP#

This short guide gives you a better understanding of the usage of U2F and the involved LinOTP API calls. For testing we recommend a Yubikey with U2F support. You do not have to configure a web site nor do you need a U2F compliant web browser.

Note

Watch the talk of our colleague Christan Pommranz at the FrOSCon 2015 for a detailed explanation of the FIDO U2F protocol and the usage with LinOTP via:

or

You probably need to add the following udev rule (e.g. in a new file /etc/udev/rules.d/99-yubikey.rules) to make the Yubikey work in Linux:

# this udev file should be used with udev 188 and newer
ACTION!="add|change", GOTO="u2f_end"

KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050",\
ATTRS{idProduct}=="0113|0114|0115|0116|0120|0402|0403|0406|0407|0410",\
TAG+="uaccess"

LABEL="u2f_end"

Activate the new rule:

udevadm trigger

Furthermore you will need tools from the software package u2f-host. It can be retrieve from Yubico/libu2f-host or installed in Debian(testing)/Ubuntu with:

apt-get install u2f-host

Set up required policy#

In the following example we will use “https://u2fdemo.de” as mocked authentication

[u2f_single]
realm = *
active = True
client = *
user = *
time = * * * * * *
action = u2f_app_id=https://u2fdemo.de

More details about U2F policies can be found U2F App ID

U2F token enrollment#

First we make the token known to LinOTP, assign it to a certain user (tux), generate the public key for a specific web site and store it (https://u2fdemo.de) in the token database of LinOTP.

  • Generate a challenge via /admin/init.

The parameters are:

  • type=u2f

  • phase=registration1

  • appid=URL_OF_THE_WEBSITE_THE_TOKEN_IS_ENROLLED_FOR

  • user=<USERNAME>

  • session=<SESSIONCOOKIE>

Example API call for registration:

https://LINOTP/admin/init?type=u2f&phase=registration1&appid=https://u2fdemo.de&user=tux&session=4e3[...]

The JSON Response contains a “registerrequest” with a challenge and the appID. This information will normally be handled by the web browser of the user who can then sign the request with his U2F token. In this example we will save the information to a file (e.g. registration_challenge.txt) instead and process it manually:

{
 "challenge": "xzNGB7yFutq1yftkbGrw_1cSDcc1v1jXztdw6oPTsgM=",
 "version": "U2F_V2",
 "appId": "https://u2fdemo.de"
}
  • Sign the challenge with the U2F token. The commmand expects you to press the button of your Yubikey:

u2f-host -aregister -o https://u2fdemo.de < registartion_challenge.txt >\
signed_registration_challenge
  • The new file signed_registration_challenge.txt contains all necessary data to complete the registration.

The valid parameters are:

  • type=u2f

  • phase=registration2

  • user=<USERNAME>

  • serial=<SERIAL> (included in the first response)

  • session=<SESSIONCOOKIE>

  • otpkey=<U2FRESPONSE> (from signed_registration_challenge)

The final registration request looks like this:

https://LINOTP/admin/init?type=u2f&phase=registration2&user=tux&serial=SERIAL&otpkey={
"registrationData": "BQQIg[...]", "clientData": "eyAiY[..]"}&session=4e377[..]

Authentication with the assigned token#

  • Generate a sign request via /validate/check. Valid parameters are:

  • user=<USERNAME>

  • pass=<PIN> (optional)

Example:

https://LINOTP/validate/check?user=tux&pass=1234

The “signrequest” will look like this - please save it to a file (e.g. validate_challenge.txt):

{
"challenge": "tyknADy_TZ-AIrjgnVix5Raq1d0wAh8NEquhBB_25zs=",
"version": "U2F_V2",
"keyHandle": "9v8Hyt[..]",
"appId": "https://u2fdemo.de"
}
  • Sign the challenge with the U2F token. The commmand expects you to press the button of the Yubikey:

u2f-host -aauthenticate -o https://u2fdemo.de < validate_challenge.txt > \
signed_validate_challenge.txt

The signature looks like this:

{ "signatureData": "AQAAAAQdv[..]",
"clientData": "eyAiY2hh[..]",
"keyHandle": "9v8Hyt[..]"
}
  • Validate the signature with LinOTP.

The parameters are:

  • user=<USERNAME>

  • transactionid=<TRANSACTIONID> (from signed_validate_challenge.txt)

  • pass=<RESPONSE> (from signed_validate_challenge.txt)

https://LINOTP/validate/check?user=tux&transactionid=324[..]&pass={"signatureData": "AQAA[..]}