Using YubiKeys with Fedora

The Fedora Documentation Team, Alexander Wellbrock Versi F37 Last review: 2023-03-11

What is a YubiKey?

A YubiKey is a small USB and NFC based device, a so called hardware security token, with modules for many security related use-cases. It generates one time passwords (OTPs), stores private keys and in general implements different authentication protocols. They are created and sold via a company called Yubico.

For more information about YubiKey features, see their product page.

How do I get a YubiKey?

You can purchase a YubiKey from Yubico’s website.

Consider a backup YubiKey

As soon as you start working with security tokens you have to account for the potential to lock yourself out of accounts tied to these tokens. As hardware security tokens are unique and designed to be extremely hard to copy you can’t just make a backup of it like you can with software vaults like Keepass or AndOTP. Because of this all registrations you do with your primary key you should immediately do with a second backup key that you store in a secure location like a safe or at least always leave at home.

In practice this means to register both hardware tokens with your linux and web accounts, generate private keys twice and configure both public keys at e.g. github.

Storage limitations

For some features private keys and other secrets are stored on the YubiKey. Each feature has it’s own storage space and hence maximum number of credential slots:

  • OTP - Unlimited, as only one secret per key is required

  • FIDO U2F - Unlimited, as only one secret per key is required

  • FIDO2 - 25 credentials

  • OATH - 32 credentials

  • PIV - 24 x509 certificates and their respective private keys

  • OpenPGP - 3 keys; one for encryption, signing and authentication each

Using a YubiKey to authenticate to a machine running Fedora

Local system authentication uses Pluggable Authentication Modules (PAM). You have two options here: pam_yubico and pam_u2f. The former is required for YubiKeys without FIDO2/U2F. If your key supports the FIDO2 standard depends on firmware and hardware model.

The setup is as follows: install the PAM module, register a YubiKey with your user account, create base configuration for either of the two authentication options and then choose the PAM configuration you want to use the YubiKey with.

Dependencies

The packages required for both PAM modules are available in the official repositories.

=== Note that one difference of both PAM modules is, with pam_yubico you don’t need to touch your yubikey, its enought if the key is inserted in your device. With pam_u2f you have to touch your key every time authentication is required. ===

For pam_yubico

Install the PAM yubico module from the official repositories:

[…]$ sudo dnf install pam_yubico

For pam_u2f

Install the PAM u2f module and the CLI tool from the official repositories:

[…]$ sudo dnf install pam-u2f pamu2fcfg

Base configuration files

For pam_yubico

There are two ways to configure the YubiKey PAM module to authenticate users. Either via the YubiCloud or using challenge-response. The YubiCloud is the standard method but depends on Yubico’s cloud to validate your OTPs and hence requires constant internet access.

Create two base configuration files in /etc/pam.d/yubikey-required and yubikey-sufficient.

For YubiCloud use the following:

#%PAM-1.0
auth       required     pam_yubico.so id=[Your API Client ID] key=[Your API Client Key]
#%PAM-1.0
auth       sufficient     pam_yubico.so id=[Your API Client ID] key=[Your API Client Key]

Note that the key is optional but without it there is no TLS verification which makes this susceptible to MitM attacks by default. Obtain a key at Yubico.

Note that the online auth method won’t work if the device is offline and can’t reach the YubiCloud.

If you have SELinux on the enforcing mode (the default mode), you should flip on the allow_ypbind boolean first, because pam_yubico needs to be able to connect to Yubico’s online authentication. servers.

[…]$ sudo setsebool -P allow_ypbind=1

For challenge-response use the following:

#%PAM-1.0
auth       required     pam_yubico.so mode=challenge-response
#%PAM-1.0
auth       sufficient     pam_yubico.so mode=challenge-response

You may add the debug option at the end of these lines right after the mode option to get troubleshooting information in journald.

If you want to use both methods for different use-cases just create the respective configuration files and use them as includes as described in the next section accordingly.

For pam_u2f

Create two base configuration files in /etc/pam.d/u2f-required and u2f-sufficient.

#%PAM-1.0
auth       required     pam_u2f.so
#%PAM-1.0
auth       sufficient     pam_u2f.so

You may add the debug option at the end of these lines right after the mode option to get troubleshooting information in journald.

Register YubiKey(s) with your local account(s)

For pam_yubico

If you use the online YubiCloud method you need the ID of your YubiKey. For this just enter the key and retrieve an OTP code with a short press on the button and extract the first 12 characters - this is your key ID.

cccccbcgebif | bclbtjihhbfbduejkuhgvhkehnicrfdj

Create a configuration file ~/.yubico/authorized_keys with your user account followed by key IDs separated by colons.

fedora-user:cccccbcgebif[:<another-key-id>]

Alternatively, activate challenge-response in slot 2 and register with your user account. The first command (ykman) can be skipped if you already have a challenge-response credential stored in slot 2 on your YubiKey. (Verify with 'ykman otp info') Repeat both or only the last step if you have a backup key (strongly recommended).

[…]$ ykman otp chalresp --generate --touch 2
[…]$ ykpamcfg -2
Stored initial challenge and expected response in '/home/<username>/.yubico/challenge-1...5'.

Or for any other system user using sudo.

[…]$ sudo -u someuser ykpamcfg -2

For pam_u2f

Use the tool pamu2fcfg to retrieve a configuration line that goes into ~/.config/Yubico/u2f_keys. This configuration line consists of a username and a part tied to a key separated by colon.

fedora-user:owBYtPIH2yzjlSQaRrVcxB...Pg==,es256,+presence

If the key is PIN protected you’ll be asked to enter the PIN for this operation.

[…]$ mkdir -p ~/.config/Yubico
[…]$ pamu2fcfg > ~/.config/Yubico/u2f_keys

If you have a backup key add it with the --nouser option and append it to the existing key (line). (All output should end up in the same line.)

[…]$ pamu2fcfg -n >> ~/.config/Yubico/u2f_keys

Configure desired PAM modules

Next configure PAM to accept a YubiKey as a means of authentication. There are many options in /etc/pam.d to modify and add a YubiKey, but the most common use-cases are:

  • /etc/pam.d/login

  • /etc/pam.d/gdm

  • /etc/pam.d/sudo

  • /etc/pam.d/sshd

In a PAM configuration file if using {yubikey,u2f}-sufficient add an include line before or if using {yubikey,u2f}-required add it after a line that reads "auth substack system-auth" or "auth include system-auth". An include of yubikey-sufficient looks like this:

auth include yubikey-sufficient

The following example sets a YubiKey OTP as 'sufficient' factor for terminal login. This means that a YubiKey alone is enough to authenticate a user when logging in on a terminal.

Open /etc/pam.d/login with your editor of choice. Find the line that reads "auth substack system-auth". Above that, insert the following:

auth include yubikey-sufficient

The result looks similar to this:

#%PAM-1.0
auth       include      yubikey-sufficient
auth       substack     system-auth
auth       include      postlogin
account    required     pam_nologin.so
account    include      system-auth
password   include      system-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open
session    required     pam_namespace.so
session    optional     pam_keyinit.so force revoke
session    include      system-auth
session    include      postlogin
-session   optional     pam_ck_connector.so

Next time you open a console (local, not ssh session) and attempt to login you should be prompted YubiKey for '<user>':. Tap your YubiKey to input an OTP and you will be logged without entering a password.

When using the yubikey-required option make sure to test this thoroughly in another session without closing your current one to mitigate locking yourself out of the system.

To add a YubiKey to more than terminal login, like local sshd servers, sudo or GDM login, add the respective auth include to one of the other configuration files in /etc/pam.d.

Customizing a YubiKey with Fedora

A YubiKey comes pre-configured for Yubico OTP, but apart from that it uses default PINs for every other feature which you’ll most likely want to change before use. There is software for customizing the YubiKey in the official repositories.

There are essentially two tools to use together with their respective GUI variants. 'yubikey-manager' and 'ykpersonalize'. The former is newer but supports less options than the latter. For all available options install both.

[…]$ sudo dnf install ykpers

There is a gui for this command:

[…]$ sudo dnf install yubikey-personalization-gui

There is a more recent, simpler tool, ykman:

[…]$ sudo dnf install yubikey-manager

YubiKey manager also has a gui:

[…]$ sudo dnf install yubikey-manager-qt

Writing a new static password to the second slot of the key

Newer YubiKeys (YubiKey 2+) have the ability to store two separate configurations. The first is generally used for OTPs, the second for a strong, static password. If the button is pressed shortly, something up to 1.5 seconds, the first configuration is triggered. If the button is pressed longer, in the range of 2.5 to 5 seconds, the second configuration is triggered.

Write a static key using ykman otp static.

[…]$ ykman otp static 2 cbdefghijklnrtuv

A more elaborate example: write a new static key to the second configuration slot using a specific AES key.

[…]$ ykpersonalize -2 -o append-cr -a 123456deadcafebeef65432112345678 -o -man-update

This writes a static key to the YubiKey based on the 32-byte AES key specified with the -a option. The -2 option sets the second slot as target. The other two options are a matter of personal taste. The append-cr option sends a carriage return as the last character of the key. That way I do not have to press <ENTER> myself. The -man-update option disables easy updating of the static key in the YubiKey. Enabling this will allow for altering the static password without the use of ykpersonalize.

Writing a new AES key to the first slot of the key

Slot 1 is special as it contains a factory credential already uploaded to YubiCloud. Deleting and recreating a Yubico OTP secret and uploading it to YubiCloud yourself will put a special mark on it which has consequences: service providers might not trust such a key and Yubico might delete those secrets at anytime for practically any reason.

If we want to write a new configuration to the first slot of the key, we need to specify some more options. If you want to be able to upload you key to Yubico, in order to authenticate against their servers, remember what the values are that you use below. You will need them later on.

[…]$ ykpersonalize -1 -o fixed=vvhhhrhkhgidic -o uid=deadbeefcafe -a 123456deadcfaebeef65432112345678 -o append-cr

The -1 option tells ykpersonalize to use the first configuration. The fixed option specifies the public ID of the YubiKey. This is referred to as the 'prefix' later on, when we go uploading it. The value you use here has to start with 'ff' in hex or 'vv' in modhex (see below). Yubico enforces this when you try to upload your key to their servers. The value for the fixed option can be up to 16 characters in length.

As part of the OTP, you can specify an internal identifier for your key. This is what the uid option does. The value is in plain hex, not modhex and ''exactly'' 12 character long.

The -a option, again, is the 32-byte AES key and append-cr appends a carriage return to my key as the last character.

When you hit the <ENTER> key, the ykpersonalize program will present you with my options and ask for confirmation before continuing:

Firmware version 2.1.1 Touch level 1795 Program sequence 3 Configuration data to be written to key configuration 1:

fixed: m:vvhhhrhkhgidic uid: h:deadbeefcafe key: h:123456deadcfaebeef65432112345678 acc_code: h:000000000000 ticket_flags: APPEND_CR config_flags:

Commit? (y/n) [n]:

After pressing 'y', I am able to generate OTPs with my new key!

What is modhex?

When plugged in, the operating system treats the YubiKey as a USB keyboard. USB keyboards send scancodes to the operating system, which the operating system then interprets as keystrokes. The YubiKey has to make sure no ambiguity arises: there are many different kinds of keyboard layouts and the scancodes have to be interpreted as the same character on machines using every random keyboard layout out there. To fix this, the people of Yubico have created 'modhex', which is a modified representation of hexadecimal characters that uses only 'safe' characters. 'Safe' characters are basically characters which have the same scancode on all keyboard layouts.

Uploading the generated AES key to Yubico

If you want to customize your YubiKey’s AES key but still want to use it to authenticate through Yubico’s servers, you can upload the key through https://upgrade.yubico.com/getapikey/. You will need to enter your email address and YubiKey’s OTP.

Update the PINs of the PIV module

The Personal Identitiy Verification (PIV) module stores private keys and corresponding certificate files for purposes such as encryption, authentication and signatures. If your YubiKey supports this you want to change the PIN and PUK as well as the Management Key.

Set the PIN.

[…]$ ykman piv access change-pin
Enter the current PIN: 123456
Enter the new PIN: ********
Repeat for confirmation: ********
New PIN set.

Set the PUK.

[…]$ ykman piv access change-puk
Enter the current PUK: 12345678
Enter the new PUK: ********
Repeat for confirmation: ********
New PUK set.

Update the Management Key.

[…]$ ykman piv access change-management-key --generate --protect
Enter the current management key [blank to use default key]:
Enter PIN: ********

You can now safely use the PIV module to generate private keys and store certificates.

Change the PIN of the FIDO2 module

FIDO2 is an open authentication standard and encompasses sub-standards and protocols to either provide two-factor or even passwordless authentication methods.

One interesting use case of the FIDO module to note is storing OpenSSH public-key identities, which modern OpenSSH agents can pick up right away and use. This makes ssh keys quite portable.

If your key supports FIDO change its pin with ykman fido access like this:

[…]$ ykman piv access change-pin
Enter the current PIN: 123456
Enter the new PIN: ********
Repeat for confirmation: ********
New PIN set.

Configure a password for OATH

The OATH feature provides TOTP and HOTP authentication protocols. It can be protected with a passphrase to access and generate OTP codes. This is different from the Yubico OTP feature, which uses a single stored secret on the YubiKey for challenge-response.

Change the OATH password with:

[…]$ ykman oath access change
Enter the new password:
Repeat for confirmation:

Configure your device to remember this password so you don’t have to re-enter it anymore.

[…]$ ykman oath access remember

Using the YubiKey to authenticate against OpenSSH servers

Using FIDO2 and OpenSSH 8.2+ you can generate OpenSSH keys that are only usable if the YubiKey is connected. It’s possible to protect the key usage by either presence or presence + pin-entry.

Generate a public key on every host you intend to use the private key, so an OpenSSH agent may discover it:

[…]$ ssh-keygen -t ed25519-sk

Generate the public key and store its identity in the FIDO2 module to make the private-public key-pair portable:

[…]$ ssh-keygen -t ed25519-sk -O resident -O application=ssh:fedora -O verify-required
So called resident keys require that the private key is protected by a PIN.

Alternative for keys without FIDO2 support

If the key does not support FIDO2 you have to use an alternative method via the PIV module and PKCS#11.

Create an ED25519 private key inside the PIV module, requiring pin entry upon use and always require a touch of the YubiKey button:

[…]$ ykman piv keys generate --algorithm ED25519 --pin-policy ONCE --touch-policy ALWAYS 9a public.pem
Enter PIN: ********

The slot 9a on the key is dedicated to authentication. There are more slots for features like encryption or signing.

Create a certificate in this same slot for the PIV/PKCS#11 library:

[…]$ ykman piv certificates generate --subject "CN=OpenSSH" --hash-algorithm SHA384 9a pubkey.pem
Enter PIN: ********
Touch your YubiKey…

Now generate a public key from the X.509 certificate stored on the YubiKey. Other features like resident keys work the same as with the FIDO2 approach, but you have to add the additional option as shown below.

[…]$ ssh-keygen -D /usr/lib/libykcs11.so -e

Login to systems with this public key:

[…]$ ssh -I /usr/lib/libykcs11.so user@remote.example.org

The ssh-agent may also load keys from the YubiKey with:

[…]$ ssh -s /usr/lib/libykcs11.so

Using the YubiKey to authenticate to websites

As of 2019, there is work in place to attempt to standardize using a YubiKey on the web. The new standard is called WebAuthn, and you can learn more about it here: https://www.yubico.com/solutions/webauthn/. For now, the easiest way to see which platforms support the YubiKey is by browsing yubico’s catalog.

As an alternative to Yubico OTP or WebAuthn, neither of which require storage of credentials on the YubiKey by default, you may also use plain old TOTP like employed in most websites today. There are desktop and at least android apps to work with this conveniently. You may store up to 32 TOTP credentials on a YubiKey 5.

Install the desktop application from the official repositories:

[…]$ sudo dnf install -y yubioath-desktop

Add an TOTP account with ykman like this:

[…]$ ykman oath accounts add google <TOTP secret>

Retrieve a TOTP code like this:

[…]$ ykman oath accounts code google