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
FIDO U2F is technically obsolete and the popular web browsers no longer support a FIDO U2F API. Instead, they offer support for FIDO2/WebAuthn, which is “backwards-compatible” in the sense that existing FIDO U2F authenticators may be used in a FIDO2 context, with certain limitations. Since LinOTP does not yet offer actual FIDO2 support, to use a FIDO U2F authenticator the application must take special care to translate the FIDO2-style authenticator replies provided by the browser’s FIDO2 API to FIDO U2F replies that LinOTP can handle. The details of this are beyond the scope of this document.
For detailed information about U2F please read:
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: media.ccc or YouTube
Check whether your Linux system comes with a udev rule for Yubikey
devices. This is usually in /usr/lib/udev/rules.d/69-yubikey.rules and
should look approximately like this:
ACTION!="add|change", GOTO="yubico_end"
# Udev rules for letting the console user access the Yubikey USB
# device node, needed for challenge/response to work correctly.
# Yubico Yubikey II
ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0010|0110|0111|0114|0116|0401|0403|0405|0407|0410", \
ENV{ID_SECURITY_TOKEN}="1"
LABEL="yubico_end"
Note
If you don’t have this file, you should add it as
/etc/udev/rules.d/69-yubikey.rules (note that local
modifications to udev go into /etc even though the files provided
by your distribution are in /usr/lib (or, on older systems,
/lib). Remember to actiivate your new rules file using
udevadm trigger --attr-match=idVendor=1050 --settle
(or, if you’re a Windows administrator, by rebooting the system).
Furthermore you will need tools from the software package u2f-host. It can be retrieved from Yubico/libu2f-host or installed in Debian or Ubuntu with:
apt-get install u2f-host
Set up required policy#
In the following example we will use https://u2fdemo.de as an
placeholder “application ID”. (If you plan to use FIDO U2F in earnest,
you should use the URL of the server your application is running on
instead, because otherwise your browser will go on strike when you try
to authenticate.)
[u2f_single]
scope = enrollment
action = u2f_app_id=https://u2fdemo.de
active = True
realm = *
client = *
user = *
time = * * * * * *
More details about U2F policies can be found at 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.
Log in to LinOTP (via the /admin/login API endpoint) in order to obtain a valid JWT for API access. The JWT will be stored in the
access_token_cookie, and you need to pass this cookie back to LinOTP with subsequent API calls. There is also acsrf_access_tokencookie whose value should be passed back to LinOTP using theX-Csrf-TokenHTTP request header.curl --cookie-jar ./cookiejar.txt \ -F username=admin -F password=Secret.123 \ https://linotp.example.com/admin/login
Generate a challenge using POST /admin/init. The parameters are:
type=u2fphase=registration1appid=APPLICATION_ID(as mentioned above)user=USERNAME
Example API call for registration:
curl --cookie ./cookiejar.txt --header "X-Csrf-Token: CSRF-TOKEN" \ -F type=u2f -F phase=registration1 -F appid=https://u2fdemo.de -F user=tux \ https://linotp.example.com/admin/init
(
CSRF-TOKENstands for the value of thecsrf_access_tokencookie from the /admin/login response.)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 FIDO U2F authenticator (except, as mentioned above, web browsers don’t do this anymore). In this example we will save the information to a file (e.g.
registration_challenge.txt) instead and process it manually using the u2f-host tool:{ "challenge": "xzNGB7yFutq1yftkbGrw_1cSDcc1v1jXztdw6oPTsgM=", "version": "U2F_V2", "appId": "https://u2fdemo.de" }
Sign the challenge with the FIDO U2F authenticator. The commmand expects you to press the button of your Yubikey:
u2f-host -aregister -o https://u2fdemo.de <registration_challenge.txt \ >signed_registration_challenge.txt
The new file
signed_registration_challenge.txtcontains all necessary data to complete the registration with another POST to /admin/init. The valid parameters this time around are:type=u2fphase=registration2user=USERNAME(like the first time around)serial=NUMBER(included in the first /admin/init response)otpkey=U2FRESPONSE(the content of thesigned_registration_challenge.txtfile)
The final registration request then looks like
curl --cookie ./cookiejar.txt --header "X-Csrf-Token: CSRF-TOKEN" \ -F type=u2f -F phase=registration2 -F user=tux -F serial=NUMBER \ -F "otpkey=<signed_registration_challenge.txt" \ https://linotp.example.com/admin/init
You can now check in the LinOTP management interface that a new FIDO U2F token has indeed been enrolled for the user.
Authentication with the assigned token#
Generate a sign request via /validate/check. Valid parameters are:
user=USERNAMEpass=PIN(optional)
Example:
curl -F user=tux -F pass=herring123 https://linotp.example.com/validate/check
The “signrequest” will look similar to the following. 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 FIDO U2F authenticator. 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 (in
signed_validate_challenge.txt) looks like this:{ "signatureData": "AQAAAAQdv[..]", "clientData": "eyAiY2hh[..]", "keyHandle": "9v8Hyt[..]" }
Validate the signature with LinOTP using another POST /validate/check request. This time, the parameters are:
user=USERNAME(as previously)transactionid=TRANSACTIONID(from the first /validate/check response)pass=U2FRESPONSE(the content of thesigned_validate_challenge.txtfile)
For example,
curl -F user=tux -F transactionid=… \ -F "pass=<signed_validate_challenge.txt" \ https://linotp.example.com/validate/checkThe JSON result should now include
"result": { "status": true, "value": true }
to indicate that the user was authenticated successfully using their FIDO U2F token.