LJ Archive

Secure Token-Based Authentication with YubiKey 4

Todd A. Jacobs

Issue #265, May 2016

YubiKeys replace other one-time password and two-factor authentication systems with a USB token. This article provides in-depth coverage of common YubiKey configurations and defines a Vagrant virtual machine to speed up configuration testing.

One-time password systems like S/Key and OTPW (see my article “Configuring One-Time Password Authentication with OTPW” in the January 2013 issue of Linux Journal) are designed to protect your login credentials when you need to connect from an untrusted host, such as a co-worker's desktop, or a public terminal at an Internet café or over an unencrypted protocol like telnet. Such systems rely on a challenge-response password list that you must carry around, and periodically update and print from secure terminals. This can be a huge hassle.

Figure 1. YubiKey 4 from Yubico

In contrast, two-factor authentication systems (with or without one-time passwords) require a second authentication step in order to prove your identity. This “second authentication factor” can be provided through token-based systems, such as Yubico's YubiKeys. YubiKeys are commercial (but inexpensive) security devices that provide support for a variety of one-time password and two-factor authentication methods in several portable and platform-independent USB form-factors.

Unlike tokens that require you to enter a PIN or transcribe a result, the YubiKey 4 is designed for ease of use. Simply plug it in to a USB port, and it will be seen as a USB keyboard that can interact directly with your authentication dialogs.

Figure 2. Inserting a YubiKey 4 into a USB Port

In some modes, a user may be required to activate a touch-sensitive spot on the token to authenticate when challenged, and in others, all that's required is the device's physical presence. In either case, this simplified approach to token-based authentication improves the user experience tremendously.

YubiKey Configuration for Yubico OTP

Using a Factory-Default YubiKey:

The YubiKey 4 is a rugged USB token that comes pre-configured for cloud-based authentication using 128-bit AES keys. In this mode, YubiKeys generate 44-character one-time passwords on demand. Although the YubiKey 4 is ready to use the Yubico OTP protocol via the YubiCloud service out of the box, an additional configuration slot is available to enable other protocols and local authentication options.

In other words, if you simply want to use the factory-default configuration for token-based validation against the YubiCloud, the YubiKey token requires no further configuration. The key itself is ready to use the moment you open the package and plug it in to a USB port!

Of course, even though the YubiKey arrives pre-configured, Linux services still need to know how to validate a key and how to associate an identity with that key. To do that, configure your Linux devices to support the Yubico OTP protocol through OpenSSH and PAM as described below.

Configure SSH for Challenge-Response Authentication:

To use Yubico OTP or any of the YubiKey's other challenge-response protocols for SSH authentication, you must first configure your OpenSSH dæmon to support it. On Ubuntu 15.04, which is the reference system for this article, the following settings must be present in the /etc/ssh/sshd_config file:

ChallengeResponseAuthentication yes
UsePAM yes

Reload the SSH server to enable the new configuration options to take effect. Note that the Vagrantfile provided in the next section will handle the configuration changes and reloading automatically for you.

Supporting Yubico OTP in SSH with PAM:

The standard Linux mechanism for authentication is PAM, which stands for Pluggable Authentication Module. Yubico provides a BSD-licensed PAM module, which is available on GitHub and in the repositories of many recent distributions.

Once you have installed Yubico PAM, include the module in any PAM-enabled service that you want to protect with a YubiKey. Although this most often is the SSH service, you also can use it for console logins, X11 logins or other PAM-protected services. Because it's possible to lock yourself out of a machine through improper PAM configuration, and because not all X11 display managers support challenge-response authentication, this article remains focused on the common use case of protecting the SSH service.

If you have Vagrant and VirtualBox installed, along with direct access to the Internet, you can use the Vagrantfile shown in Listing 1 to create a test instance. The Vagrantfile also will set up Yubico PAM for you while provisioning the virtual machine (VM). This can save you a great deal of time troubleshooting when setting up a YubiKey test environment.

Bring up the VM with the following command:

$ vagrant up --provider virtualbox

If you have a real server to work with or prefer to configure an existing box rather than a VM, ensure that the pam_yubico.so module is included at the very top of your PAM configuration file for the SSH service. On the Ubuntu reference platform for this article, the PAM configuration file is /etc/pam.d/sshd, but it may be different on your distribution.

As an example, the top of your file should look similar to the following:

# PAM configuration for the Secure Shell service

# Enable YubiCloud authentication.
auth    sufficient  pam_yubico.so id=16 authfile=/etc/yubikey_mappings

# Standard Un*x authentication.
@include common-auth

This particular PAM configuration prompts the user for YubiKey authentication before falling back to a standard password prompt. See the sidebar on precedence for more information about the order in which authentications happens.

Map Users to YubiKeys:

With the OpenSSH and PAM configuration now done, there is one more step to complete before YubiKey authentication will work: mapping system users to authorized tokens.

First, it's important to understand why this step is necessary, especially when using the YubiCloud. The challenge-response configuration you've implemented here pairs a token holding a secure element (the YubiKey) with an AES key stored in the YubiCloud. Without a way to authorize tokens, a user could use any valid token to log in, even if the token wasn't issued to that user. In addition, unless you map tokens to specific accounts, you can't identify the holder of a token nor limit the accounts a given token can be used to access.

To address these concerns, you will use a file defined by your PAM configuration to map tokens to users. This restricts who may use YubiKeys to authenticate to the system, as well as tying a given YubiKey to a specific user or system account.

This mapping is done using a 12-character modified hexadecimal (ModHex) prefix unique to each AES key. The prefix for factory-configured YubiKeys starts with “cccccc”, although custom or reconfigured YubiKeys always start with “vv”. The following script will enable you to identify the necessary prefix for your YubiKey easily:

#!/usr/bin/env bash

read -p 'Enter Yubico OTP: '
echo "${REPLY:0:12}"

  1. Place the script into your path and make it executable.

  2. Insert your YubiKey into an available USB port.

  3. Run the script.

  4. When prompted, briefly press the round sensor for 0.3 to 1.5 seconds to issue a one-time password using the factory-default setting in Slot 1. Please note that longer presses of 2.5–5.0 or 8–15 seconds trigger other behaviors; see the YubiKey Manual for details.

  5. Copy the prefix; this will be associated with a user name in the mapping file defined in your PAM configuration.

When you run the script and trigger your YubiKey, you should see output similar to the following:

$ bash yubikey-id.sh
Enter Yubico OTP: ccccccrlivbljtkhdinnclelldrktdefttuuuecfjdbh
ModHex Prefix: ccccccrlivbl

Using this example, you would map “ccccccrlivbl” to the user assigned that particular YubiKey device. You do that in the authfile defined in your PAM configuration file.

If you use the Vagrantfile from Listing 1, you can configure the mapping file from the host machine with a single command and a tap of your YubiKey:

vagrant ssh -- \
    "echo 'vagrant:$(yubikey-id.sh)' |
     sudo tee -a /etc/yubikey_mappings"

Otherwise, log in to your test system and edit the mapping file in the form of <username>:<prefix>. To assign more than one YubiKey per user, separate each prefix from the next one with a colon.

Testing Your YubiKey:

Now you're ready to test authentication with Yubico OTP! If possible, remain logged in as root on the box that you're testing while you verify the configuration in another window. If you have made a mistake in one of the configuration files, you may not be able to reconnect.

If you are using the Vagrant test instance, use the following command to perform a valid test of Yubico OTP:

vagrant ssh -- \
    -S none \
    -o PreferredAuthentications=keyboard-interactive

This will force the SSH client to disable connection sharing so that it won't reuse an existing connection where you already may have authenticated with an SSH key or password. In addition, it will tell the client to try challenge-response before presenting any SSH keys.

If you are not using Vagrant, use the -S none and -o PreferredAuthentications=keyboard-interactive arguments anyway to ensure you are performing a valid test. However, feel free to modify other aspects of SSH client behavior to fit your specific environment.

If your OpenSSH and PAM settings are correct, you will see a prompt similar to the one shown in Figure 3.

Figure 3. OpenSSH Dialog Prompting User for YubiKey

To continue, press the sensor on your YubiKey for 0.3 to 1.5 seconds to submit your one-time password. Assuming that your mapping file is correct, and that the PAM module is able to connect to the Internet and reach the YubiCloud service, you will be logged in.

If you're also prompted for your system password but haven't configured two-factor authentication, as described later in this article, it's likely that your mapping file or ability to connect to the YubiCloud service is the problem. Double-check the mapping file and your Internet connectivity. If problems persist, refer to the Yubico PAM documentation for instructions on how to turn on debug-level logging.

Adding a Second Authentication Factor

Two-factor authentication is sometimes defined as something you know (such as a PIN) and something you have, such as an OTP calculator or token. As you've implemented the system thus far, you've really implemented only one-time passwords in a way that is similar to S/Key or OTPW, but much easier for the end user. No password lists to carry around, no password prefixes to memorize—just connect, press the sensor, and bingo!

You can implement true two-factor authentication by making a minor change to your SSH server's PAM configuration. For example, you can require the use of a password (something you know) in addition to a YubiKey (something you have).

To do this, change the control value of Yubico PAM from “sufficient” to “requisite”. In your original one-time password configuration, the module was sufficient by itself to grant access when Yubico OTP succeeded, but would fall back to the next authentication option if it failed. With the change to requisite, Yubico OTP is now required, and the PAM authentication stack will return immediately from a failure. The first four lines of /etc/pam.d/sshd now contain:

# PAM configuration for the Secure Shell service

# Require token-based Yubico OTP authentication.
auth    requisite  pam_yubico.so id=16 authfile=/etc/yubikey_mappings

Your login dialog now should look like Figure 4, even when your YubiKey successfully validates against the YubiCloud.

Figure 4. Two-Factor Authentication with YubiKey and Password

A compromised machine can't extract elements from the YubiKey for later use or replay a one-time password using the Yubico OTP protocol once the password has been used. Even assuming that you are logged in on a compromised workstation, two-factor authentication is surprisingly robust even when exposing your password to the local host. By combining your password with the one-time passwords provided by the YubiKey token, and given the necessity of having physical possession of the token in order to issue one-time passwords, your overall risk is actually reduced.

However, if you are exposing a password that you use on multiple machines, and not all of those machines are protected by two-factor authentication, this would be an unacceptable risk. You can do better!

It's possible to modify the PAM stack further to replace the request for your Linux system password with a request for a different one-time password system, such as OTPW. For example, on the example reference platform, you might require the OTPW module in addition to Yubico OTP. Assuming that OTPW has been installed and configured, your PAM module in /etc/pam.d/sshd would look like this:

# PAM configuration for the Secure Shell service

# Require token-based Yubico OTP authentication.
auth    requisite  pam_yubico.so id=16 authfile=/etc/yubikey_mappings

# Require OTPW one-time passwords.
auth        requisite  pam_otpw.so
session     optional   pam_otpw.so

Of course, to ensure that the system requires both Yubico OTPW and your second-factor authenticator, you also must disable standard password authentication via PAM. On the reference platform, edit /etc/pam.d/sshd and prefix @include common-auth with a comment character.

Once you've initialized OTPW with:

$ vagrant ssh -- otpw-gen > password-list.txt

you're ready to test out two-factor authentication. With public key authentication disabled, you should be prompted for an OTPW password after successfully authenticating your YubiKey.

Local Validation

In some cases, it's undesirable to validate to the YubiCloud or other networked resource. If you can physically plug the YubiKey token in to your server or workstation, HMAC-SHA1 is a good alternative. In this mode, YubiKey's challenge-response feature is very similar to S/Key, OPIE or OTPW one-time password systems, but requires only the physical presence of the token.

Installing the YubiKey Command-Line Personalization Tool:

The YubiKey configuration tools ykpersonalize and ykpamcfg are available from the yubikey-personalization package on Ubuntu 15.04. If you're using the Vagrantfile provided here, this package already has been installed and configured for you. Otherwise, go ahead and install it now.

Next, install the udev rules to give non-root users access to the YubiKey device. The yubikey_udev_rules.sh script below downloads the latest rules for you and places them into the correct location on an Ubuntu machine (adjust the download directory on other distributions if necessary):

#!/usr/bin/env bash

# Purpose:
#     Download the latest udev rules for the YubiKey
#     Personalization Tools.

url="https://raw.githubusercontent.com"
url="${url}/Yubico/yubikey-personalization"
url="${url}/master"
url="${url}/[69-70]-yubikey.rules"

cd /etc/udev/rules.d/
sudo curl -sLO "$url"

Finally, run sudo udevadm trigger to force udev to use the new rules you just installed. Otherwise, the rules may not take effect until the next reboot.

Make sure you download the latest udev rules from the GitHub repository and connect directly to a console or through your virtual machine's graphical user interface.

Configuring HMAC-SHA1 Challenge-Response:

Once the udev rules are installed, you're ready to configure the key from a local console. If using the VM, log in through the graphical user interface using the user name “vagrant” and the password “vagrant”.

First, use the YubiKey personalization tools to enable HMAC-SHA1 challenge-response from Slot 2:

# Configure Slot 2 for HMAC-SHA1.
yes | ykpersonalize -2 \
                    -ochal-resp \
                    -ochal-hmac \
                    -ohmac-lt64 \
                    -oserial-api-visible

Next, initialize the file that holds the challenge-response pairs using your new Slot 2 configuration:

ykpamcfg -2

Finally, configure PAM to accept the HMAC-SHA1 credentials during console logins by placing challenge-response authentication before the primary block in /etc/pam.d/common-auth. Use the following excerpt as guidance:

# /etc/pam.d/common-auth - authentication settings common to all 
# services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use 
# the traditional Unix authentication mechanisms.
#
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
# To take advantage of this, it is recommended that you configure any
# local modules either before or after the default block, and use
# pam-auth-update to manage selection of other modules.  See
# pam-auth-update(8) for details.

# Enable HMAC-SHA1 to succeed without a second factor.
auth    sufficient    pam_yubico.so mode=challenge-response

# here are the per-package modules (the "Primary" block)
auth    [success=1 default=ignore]      pam_unix.so nullok_secure

If you're using the VM, just uncomment module in common-auth. However, leave the directory argument commented out for now. At this time, if you specify a global directory for HMAC-SHA1, the PAM module will not find the challenge-response file in the user's home directory. (I'll discuss use of the global directory in the next section.)

The changes you made in common-auth won't affect non-local connections such as SSH unless you're connecting to a local VM. This is typically because the host machine and the VM share access to the YubiKey. Remember that HMAC-SHA1 is for authenticating console access only!

Now that PAM has been updated, attempt to log in again from a console. You should be authenticated automatically immediately after typing your user name at the login prompt. However, when you modify PAM, login may not pick up the configuration changes immediately. If this happens, just press return a few times on your console until the screen clears and the login prompt starts again at the top of the screen.

HMAC-SHA1 with Encrypted Home Directories:

If you use encrypted home directories, such as Ubuntu's eCryptfs, challenge-response files must be moved out of the user's home directory and into a central location, such as /etc/yubico. Otherwise, the challenge-response files can't be read or updated by PAM during the authentication process when the directory is unmounted.

On such systems, the module authors recommend a publicly writable central location with the sticky bit set. This is similar to the /tmp directory and allows users to update their challenge-response files after each log in:

sudo mkdir --mode=1777 /etc/yubico

Next, add this central directory to your PAM configuration using the chalresp_path option. If using the VM, just uncomment the provided argument:

# /etc/pam.d/common-auth - authentication settings common to all 
# services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use the
# traditional Unix authentication mechanisms.
#
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
# To take advantage of this, it is recommended that you configure any
# local modules either before or after the default block, and use
# pam-auth-update to manage selection of other modules.  See
# pam-auth-update(8) for details.

# Enable HMAC-SHA1 to succeed without a second factor, even with
# encrypted home directories.
auth    sufficient    pam_yubico.so mode=challenge-response
chalresp_path=/etc/yubico

# here are the per-package modules (the "Primary" block)
auth    [success=1 default=ignore]      pam_unix.so nullok_secure

All users must then move their challenge-response file into this central directory and rename the file's prefix from “challenge” to their login name. For example:

mv ~/.yubico/challenge-* \
   "/etc/yubico/$LOGNAME-$(ykinfo -qs)"

Adding a Second Factor to HMAC-SHA1:

As with Yubico OTP, it's easy to add a second factor to HMAC-SHA1 challenge-response authentication. Simply substitute auth sufficient pam_yubico.so with auth required pam_yubico.so in the common-auth file, and PAM will mandate the presence of the user's YubiKey in addition to any other required authentications. On the reference system, this will be the system password by default, but other configurations are possible.

In general, use required here instead of requisite. Using “required” ensures that the rest of the PAM stack (such as the system password prompt) is evaluated before returning a response even though authentication can't succeed if the YubiKey isn't present or valid. Not returning immediately prevents a potential attacker from seeing “Login incorrect” immediately after typing in a user name. However, this represents a trade-off for users. With “required”, if users forget to insert their YubiKeys before attempting to login, they may think they've simply mistyped their password and become frustrated. The trade-off between user experience and security in this particular case is yours to make.

Summary

I have described two types of challenge-response systems using the YubiKey 4 USB tokens: Yubico OTP with YubiCloud validation and HMAC-SHA1 for local validation. I've also explored different ways to configure YubiKey authentication using PAM, including support for two-factor authentication.

The YubiKey 4 is a worthwhile replacement for other one-time password and two-factor authentication systems due to its flexibility, rugged design and convenient form factor.

In addition, the device's support for OpenPGP smartcard standards and other code-signing features makes it a good choice for administrators, developers and DevOps engineers who need secure access to their secret keys when on the go.

This article provides a solid foundation for implementing the device's basic security features. Hopefully, it has also whet your appetite for exploring additional uses for the YubiKey 4.

Todd A. Jacobs is a veteran IT consultant who currently specializes in automation and security. In what passes for his free time, Todd is also a top contributor on Stack Overflow and Project Management Stack Exchange. In between writing, coding and (of course) drinking way too much coffee, he practices the ancient arts of marriage and fatherhood to the very best of his ability.

LJ Archive