I have just acquired a YubiKey (the standard one) and wanted to use it to provide 2-factor authentication for SSH. In case I lose the key, I also want a fallback option so I don’t lose access to the server. The documentation for this seems a bit scattered, so here’s my reference for how I made it work.
Some things to know first:
- HOTP is a counter-based system, where each password follows a sequence based on a counter that ticks up by one each time you generate the password.
- TOTP is a time-based system, where a new password is generated every so often (30 seconds in this case.)
The result is that when you SSH in it will ask first for your password, and then for the one-time password (OTP) provided by the YubiKey.
There are two parts to this: setting up the YubiKey to work, and setting up the fallback using the Google Authenticator Android app. This was done on a Debian Wheezy server, but should work on anything that is similar.
Packages that are required on the server: libpam-oath (>= 1.12.4-1 or you can’t have a fallback in this way)
Packages that are required on the desktop that you’re using to configure this: yubikey-personalization-gui, oathtool, and libmime-base32-perl.
When doing this, make sure that you have a root shell open on the server so that if something breaks you can undo things.
The keys here are just examples, make your own.
Setting up the YubiKey
The SSHD and PAM configuration is based on these posts: “One Time Passwords for SSH on Ubuntu and OS X” and “2 factor authenticatie met Yubikey en OATH – Installatie op Ubuntu”.
First, use yubikey-personalisation-gui to generate a key and set up the YubiKey with it. To do this, configure it in OATH-HOTP mode, turn off the token identifier, and copy the key somewhere as you’ll need it a few times. Write this to the YubiKey.
Create a file on the server, /etc/users.oath, containing:
HOTP robin - 8a54ac40689f0bb99f306fdf186b0ef6bd153429
where robin is the username that’ll be using this key, and the big string of hex digits is the key that was generated earlier with the spaces removed.
Set the permissions on this file to be unreadable by anyone except root:
# chmod 600 /etc/users.oath
Modify /etc/pam.d/sshd such that the line:
is commented out, and this is added just below it:
# Normally we would `@include common-auth`, but that leaks info with OATH
auth required pam_unix.so nullok_secure
# OATH OTP
auth required pam_oath.so usersfile=/etc/users.oath window=20
window=20 says how many times the button on the YubiKey can be pressed when it’s not logging in to the server and it’ll still work; or to put it another way, how far into the OTP sequence the module will search to try to match the one it’s been given.
Modify sshd_config to allow challenge-response in SSH:
sed -i.bak -E -e 's/(ChallengeResponseAuthentication) no/\1 yes/' /etc/ssh/sshd_config
You can verify the sequence is right by running:
$ oathtool -w10 8a54ac40689f0bb99f306fdf186b0ef6bd153429
If you press the button on the YubiKey a few times, the output should match the results of this.
Restart the SSH daemon, and it should work. First you’ll have to enter your password, then you’ll have to press the YubiKey button.
Setting up Google Authenticator
This is the fallback, in case you lose the key. It will use the time-based TOTP method rather than a counter-based sequence of codes. This is based on this mailing list thread.
First generate a random key of 10 bytes (20 hex characters):
$ head -c 1024 /dev/urandom | openssl sha1 | tail -c 21 # 21 due to newline
Add a line to /etc/users.oath:
HOTP/T30 robin - 2c2d309a7a92e117df5a
The T30 tells it that this is time-based with a 30 second rotation, which the authenticator app requires.
Before we can add this key into the authenticator app, we need to convert it to base32:
$ perl -e 'use MIME::Base32 qw( RFC ); print lc(MIME::Base32::encode(pack("H*","2c2d309a7a92e117df5a")))."\n";'
This takes the hex key, converts it to binary, and then converts that to base32.
In the app, go to the menu -> Set up account -> Enter provided key. Give it a name, and put the base32 string you got into the key field. Leave the selector at time-based.
To check this is working, you can run:
$ oathtool --totp -w10 2c2d309a7a92e117df5a
The sequence shown should match what the app is saying. Note that this is time dependent, so everything should have clocks that are set to within a few seconds.
Now you should be able to log in to the server by providing either the time-based code in the authenticator app, or by pressing the YubiKey button.
Whenever a login happens, the users.oath file gets modified with the new counter value (for HTOP) and the timestamp, so be aware of that if you leave it open in an editor.
If someone messes with the YubiKey and presses the button many times (more than 20 in this example) it will end up out of sync with the server, and you should probably just reset it by replacing the relevant line in users.oath with a new key and writing that new key to the YubiKey.
There is also libpam-google-authenticator which behaves similarly, but isn’t available in Debian Wheezy.
If you have multiple servers that you want to use one key with, you need to find some way of having a central server doing the authentication to prevent counters going out of sync.
SSH keys should bypass this totally, though I haven’t tested that yet.