This article is part of a larger series on cryptographic keycards and secrets storage.

I recently ordered two Yubikey devices from Yubico, partly because of a special offer from Github. I ordered both a Yubikey NEO and a Yubikey 4, although I am not sure I remember why I ordered two - you can see their Yubikey product comparison if you want to figure that out, but basically, the main difference is that the NEO has support for NFC while the "4" has support for larger RSA key sizes (4096).

This article details my experiment on the matter. It is partly based on first hand experience, but also links to various other tutorials that helped me along the way. Especially thanks to folks on various IRC channels that really helped me out in understanding this.

My objective in getting a hardware security token like this was three-fold:

  1. use 2FA on important websites like Github, to improve the security of critical infrastructure (like my Borg backup software)
  2. login to remote SSH servers without exposing my password or my private key material on third party computers
  3. store OpenPGP key material on the key securely, so that the private key material can never be compromised

To make a long story short: this article documents step 2 and implicitly step 3 (because I use OpenPGP to login to SSH servers). However it is not possible to use the key on arbitrary third party computers, given how much setup was necessary to make the thing work at all. 2FA on the Github site completely failed, but could be used on other sites, although this is not covered by this article.

I have also not experimented in details the other ways the Yubikey can also be used (sorry for the acronym flood) as:

Update: OATH works! It is easy to configure and i added a section below.

  1. Not recommended
  2. Plugging it in
  3. Configuring a PIN
  4. Configuring GPG
  5. Using SSH
  6. Using OATH
  7. Troubleshooting
  8. Future work

Not recommended

After experimenting with the device and doing a little more research, I am not sure it was the right decision to buy a Yubikey. I would not recommend buying Yubikey devices because they don't allow changing the firmware, making the device basically proprietary, even in the face of an embarrassing security vulnerability on the Yubikey NEO that came out in 2015. A security device, obviously, should be as open as the protocols it uses, otherwise it's basically impossible to trust that the crypto hasn't been backdoored or compromised, or, in this case, is vulnerable to the simplest drive-by attacks.

Furthermore, it turns out that the primary use case that Github was promoting is actually not working as advertised: to use the Yubikey on Github, you actually first need to configure 2FA with another tool, either with your phone's text messages (SMS) or with something like Google Authenticator. After contacting Github support, they explained that the Yubikey is seen as a "backup device", which seems really odd to me, especially considering the promotion and the fact that I don't have a "smart" (aka "Google", it seems these days) phone or the desire to share my personal phone number with Github.

Finally, as I mentioned before, the fact that those devices are fairly new and the configuration necessary to make them work at all is completely obtuse, non-standardized or at least not available by default on arbitrary computers makes them basically impossible to use on other computers than your own specially crafted gems.

Plugging it in

The Yubikey, when inserted into a USB port, seems to be detected properly. It shows up both as a USB keyboard and a generic device.

déc 14 17:23:26 angela kernel: input: Yubico Yubikey NEO OTP+U2F as /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2:1.0/0003:1050:0114.0016/input/input127
déc 14 17:23:26 angela kernel: hid-generic 0003:1050:0114.0016: input,hidraw3: USB HID v1.10 Keyboard [Yubico Yubikey NEO OTP+U2F] on usb-0000:00:12.0-2/input0
déc 14 17:23:26 angela kernel: hid-generic 0003:1050:0114.0017: hiddev0,hidraw4: USB HID v1.10 Device [Yubico Yubikey NEO OTP+U2F] on usb-0000:00:12.0-2/input1

We'll be changing this now - we want to to support OTP, U2F and CCID. Don't worry about those acronyms now, but U2F is for the web, CCID is for GPG/SSH, and OTP is for the One Time Passwords stuff mentionned earlier.

I am using the Yubikey Personalization tool from stretch because the jessie one is too old, according to Gorzen. Indeed, I found out that the jessie version doesn't ship with the proper udev rules. Also, note that we need to run as sudo otherwise we get a permission denied:

$ sudo apt install yubikey-personalization/stretch
$ sudo ykpersonalize -m86
Firmware version 3.4.3 Touch level 1541 Program sequence 1

The USB mode will be set to: 0x86

Commit? (y/n) [n]: y

To understand better what the above does, see the NEO composite device documentation.

The next step is to reconnect the key, for the udev rules to kick in. If you were like me, you enthusiastically plugged in the device before installing the yubikey-personalization package, and the udev rules were not present then.

Configuring a PIN

Various operations will require you to enter a PIN when talking to the key. The default PIN is 123456 and the default admin PIN is 12345678. You will want to change that, otherwise someone that gets a hold of your key could do any operation without your consent. For this, you need to use:

$ gpg --card-edit
> passwd
> admin
> passwd

Be sure to remember those passwords! Of course, the key material on the Yubikey can be revoked when you loose the key, but only if you still have control of the master key, or if you have a OpenPGP revocation certification (which you should have).

Configuring GPG

To do OpenPGP operations (like decryption, signatures and so on), or SSH operations (like authentication on a remote server), you need to talk with GPG. Yes, OpenPGP keys are RSA keys that can be used to authenticate with SSH servers, that's not new and I have already been doing this with Monkeysphere for a while. Now the challenge is how to make GPG talk with the Yubikey.

So the next step is to see if gpg can see the key alright, as described in the Yubikey importing keys howto - you will need first to install scdaemon and pcscd (according to this howto) for gpg-agent to be able to talk with the key:

$ sudo apt install scdaemon gnupg-agent pcscd
$ gpg-connect-agent --hex "scd apdu 00 f1 00 00" /bye
ERR 100663404 Card error <SCD>

Note that you do not need pcscd in later versions of the GnuPG suite. In fact, it can even cause problems because of a bug in the 2.1.18-4 version of scdaemon that fails to fallback to PC/SC mode when the CCID mode fails because pcscd is installed. If that sounds completely alien to you, don't worry, it was the same for me. Just make sure to uninstall pcscd and that have the following line in /lib/udev/rules.d/60-scdaemon.rules - if not, you can add it to a temporary file in /etc/udev/rules.d/yubikey-neo-otp-ccid.rules:

ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0111", MODE="664", GROUP="plugdev"

See Debian bug #854005 and Debian bug #854616 for all the gory details. Those bugs were found while Debian 9.0 (stretch) was in testing, so they may have already been fixed.

Well that failed. At this point, touching the key types a bunch of seemingly random characters wherever my cursor is sitting - fun but totally useless still. That was because I failed to reconnect the key: make sure the udev rules are in place and reconnect the key, the above should work:

$ gpg-connect-agent --hex "scd apdu 00 f1 00 00" /bye
D[0000]  01 00 10 90 00                                     .....
OK

This shows it is running the firmware 1.10, which is not vulnerable to the infamous security issue.

(Note: I also happened to install opensc and gpgsm because of this suggestion but I am not sure they are required at all.)

Using SSH

To make GPG work with SSH, you need somehow to start gpg-agent with ssh support, for example with:

gpg-agent --daemon --enable-ssh-support bash

Of course, this will work better if it's started with your Xsession. Such an agent should already be started, so you just need to add the ssh emulation to its configuration file and restart your X session.

echo 'enable-ssh-support' >> .gnupg/gpg-agent.conf

In Debian Jessie, the ssh-agent wrapper will not start if it detects that you have already one running (for example from gpg-agent) but if that fails, you can try commenting out use-ssh-agent from /etc/X11/Xsession.options to keep it from starting up in your session. (Thanks to stackexchange for that reference.)

Here I assume you have already created an authentication subkey on your PGP key. If you haven't, I suggest trying out simply monkeysphere gen-subkey, which will generate an authentication subkey for you. You can also do it by hand by following one of the OpenPGP/SSH tutorials from Yubikey, especially the more complete one. If you are going to generate a completely new OpenPGP key, you may want to follow this simpler tutorial here.

Then you need to move your authentication subkey to the Yubikey. For this, you need to edit the key and use the keytocard command:

$ gpg2 --edit-key anarcat@debian.org
> toggle
> key 2
> keytocard
> save

Here, we use toggle to show the OpenPGP private key material. You should see a key marked with A for Authentication. Mine was the second one so I selected it with key 2 which put a star next to it. The keytocard command moved it to the key and save ensure the key was removed from the local keyring.

Obviously, backups are essential before doing this, because it's perfectly possible to loose that key in the process, for example if you destroy or lose the key or forget the password. It's probably better to create a completely different authentication subkey for just this purpose, but that may require reconfiguring all remote SSH hosts, and you may not want to do that.

Then SSH should magically talk with the GPG agent and ask you for the PIN! That's pretty much all there is to it - if it doesn't, it means that gpg-agent is not your SSH agent, and obviously things will fail...

Also, you should be able to see the key being loaded in the agent when it is:

$ ssh-add -l
2048 23:f3:be:bf:1e:da:e8:ad:4b:c7:f6:60:5e:03:c2:a6 cardno:000603647189 (RSA)

.. that's about it! I have yet to cover 2FA and OpenPGP support, but that got me going for a while and I'll stop geeking around with that thing for now. It was fun, for sure, but not sure it's worth it for now.

Using OATH

google-authenticator-libpam

I switched from libpam-oath (below) to another (better maintained) plugin, see the procedure in this article instead.

I switched away from libpam-oath because users couldn't edit their own 2FA tokens and I had to patch it to avoid forcing 2FA on all users. The latter was merged in the Debian package, but never upstream, and the former was never fixed at all. It seems the library is not as well maintained as the Google Authenticator one, so I feel more confident using the latter in the future.

libpam-oath

WARNING: those are the old instructions I used before I realized I could use the above "Google Authenticator" plugin. They are kept only for historical reference.

This is pretty neat: it allows you to add two factor authentication to a lot of things. For example, PAM has such a module, which I will configure here to allow myself to login to my server from untrusted machines. While I will expose my main password to keyloggers, the OTP password will prevent that from being reused. This is a simplified version of this OATH tutorial.

We install the PAM module with:

sudo apt install libpam-oath

Then, we can hook it into any PAM consumer, for example with sshd:

--- a/pam.d/sshd
+++ b/pam.d/sshd
@@ -1,5 +1,8 @@
 # PAM configuration for the Secure Shell service

+# for the yubikey
+auth required pam_oath.so usersfile=/etc/users.oath window=5 digits=8
+
 # Standard Un*x authentication.
 @include common-auth

We also needed to allow OTP passwords in sshd explicitely, with:

ChallengeResponseAuthentication yes

This will force the user to enter a valid oath token on the server. Unfortunately, this will affect all users, regardless of whether they are present in the users.oath file. I filed bug #807990 regarding this, with a patch.

Also, it means the main password is still exposed on the client machine - you can use the sufficient keyword instead of required to workaround that, but then it means anyone with your key can login to your machine, which is something to keep in mind.

The /etc/users.oath file needs to be created with something like:

#type   username        pin     start seed
HOTP    anarcat -   00

00 is obviously a fake, and insecure string. Generate a proper one with:

dd if=/dev/random bs=1 count=20 status=none | hexdump -v -e '/1 "%02x"' ; echo

Then the shared secret needs to be added to the Yubikey:

ykpersonalize -1 -o oath-hotp -o oath-hotp8 -o append-cr -a

You simply paste the random secret you created above, when prompted, and that shared secret will be saved in a Yubikey slot for future use. Next time you login to the SSH server, you will be prompted for a OATH password, you just touch the button on the key and it will be pasted there:

$ ssh -o PubkeyAuthentication=no anarc.at
One-time password (OATH) for `anarcat':
Password:
[... logged in!]

Final note: the centralized file approach makes it hard, if not impossible, for users to update their own secret token.. It would be nice if there would be a user-accessible token file, maybe ~/.oath? Filed feature request #807992 about this as well.

Troubleshooting

Resetting failed PIN

It could happen that your key stops functioning because you failed entering the PIN too many times. The solution is to use the admin password to reset the basic PIN password. You can see the retry counter here:

anarcat@angela:monkeysign$ gpg --card-edit
[...]
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 0 3 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
[...]

In the above, you can see I ran out of retry attempts for my main PIN:

PIN retry counter : 0 3 3

It should rather look like this:

PIN retry counter : 3 3 3

To fix this, you need to go back in --card-edit:

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. [...] detected

1 - chanége PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 2
PIN unblocked and new PIN set.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q

gpg/card>

At the passwd step, when selecting 2, I got prompted for the admin PIN and I could set and confirm a new regular PIN. Of course, the above assumes that you have the admin PIN correctly set and you remember it!

General failure

Sometimes, you will get something like this:

$ gpg --card-edit

gpg: selecting openpgp failed: ec=6.108
gpg: la carte OpenPGP n'est pas disponible : erreur générale

gpg/carte> 2$

This roughly translates to "general error" or "general failure". I can't figure out exactly what's going on here, but I figured out that restarting the scdaemon fixes the problem:

killall scdaemon

And off we go again!

Re-synchronizing OATH

It is possible that the key and the server get out of sync if you mistakenly tap the button too many times without actually authenticating. There's a tolerance range on the server side, but it can quickly be exhausted (and with good reason).

To reset the counters, you should simply rerun the configuration procedure and replace the users.oath line with a new one.

scdaemon problems in 2.1.18

Debian bug #854005 came up when 2.1.18 was released. It seems there's a problem with the new multi-device support in GnuPG 2.1.18 that makes it impossible to read the card at all. The symptom is this:

[991]anarcat@curie:~2$ LANG=C gpg --card-status
gpg: selecting openpgp failed: No such device
gpg: OpenPGP card not available: No such device

The workaround documented there is to add disable-ccid to ~/.gnupg/scdaemon.conf. The proper fix is to uninstall pcscd and make sure you have the appropriate udev rule, as mentioned earlier.

accessing key on a new machine

Say you have lost everything and reinstalled from scratch, or you need to use the key on your Yubikey on a different machine than the one you setup earlier. You first need to install GnuPG and necessary utilities:

sudo apt install gnupg gnupg-agent scdaemon

Then gpg --list-secret-keys will tell you you have no keys at all. This is normal: you first need to import the public key and make gpg talk to the smart card:

$ gpg --recv-keys 1676B3FF4BCDB17FB8164E45540F7DBF640EEBDE
$ gpg --card-status

Reader ...........: 1050:0111:X:0
[...]
Version ..........: 2.0
Manufacturer .....: Yubico
[...]
Authentication key: 1676 B3FF 4BCD B17F B816  4E45 540F 7DBF 640E EBDE
  created ....: 1970-01-01 00:00:00
General key info..: sub  rsa2048/[...]

Then the key will be shown in the gpg --list-secret-keys output. Of course you will also need to configure SSH support in the agent:

echo 'enable-ssh-support' >> .gnupg/gpg-agent.conf

And make sure it is not overriden by the regular ssh-agent (comment out use-ssh-agent in /etc/X11/Xsession.options.

This allows you to use the same authentication key on multiple machines without copying secrets, which is pretty useful.

Future work

I may try to put more stuff (like my workstation) on 2FA. Right now I'm using the once package to fake 2FA, but it's basically useless because I'm also storing the passwords in OpenPGP-encrypted files. This is not really a 2FA, except maybe for timing issues.

To use more OTP, I can't really use OATH, because there's a limited set of tokens I can configure there. One way around this would be to use U2F, which I originally described as "for the web", but for which there's an actually quite solid pam package. Plus this would enable more robust 2FA in a bunch of places. This would mean I could lose access if i lose the token, however, so I may need to think about having a backup token. Maybe recovery codes are all I need.

I could also move more key material in the device. Right now, I use only for authentication, but there's no reason why (other than backups) than I wouldn't use it for a certification key. Encryption is tricker, because those keys can be slow, which doesn't matter for signing stuff, but decryption happens way more often and on larger things.

I should also look at alternative device. The FST-01 looks a little bare-metal, but it's actually pretty solid, and can be encased in plastic cray, origami, string, an eraser and so many cute other ideas. A friend just took the cover off another USB key and used that.

The FST-01 is just hardware though, you need to deploy the Gnuk to it to get the usual signing/encryption primitives going. Which is cool because it means the same device can also serve other purposes, like a TRNG (the NeuG). (Why do all those software project names sound like troll names? :)

There is an "app" on the Yubikey NEO called "PIV" which stands for "Personal Identity Verification" and is part of FIPS 201, a US federal authentication standard. It seems that app can be used to do SSH authentication without using gpg-agent, which might be simpler. Those instructions actually work: you can generate a new certificate, which is separate from the ones on the OpenPGP app, and use them for SSH authentication. The caveats are:

  1. the library, on Debian, is in a different location than what is mentioned in the document (/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so, see issue 6)
  2. it's unclear if that mode can be used for OpenPGP signatures (I suspect not) which means it cannot be used while gpg-agent is running for that purpose (that I could confirm) which makes its usefulness very limited for me
  3. it's unclear if the resulting keypair can be embedded in an OpenPGP certificate the same way my current authentication key is
  4. it's unclear how that relates to OpenSSH 8.2 work on FIDO suport, which brings support for Yubikey-style tokens into SSH directly (although using a different app, the FIDO U2F mode)
Do not use this tutorial

Update: I do not recommend people buy a Yubikey anymore.

I got those because at least the desktop side of things was free software, even though the hardware and firmware itself was proprietary. Back then, they were saying it was better if it was impossible for people to access the firmware for security reasons, which is an argument I disagree with, but I can see where it's coming from.

But now, the Yubikey 4 will be completely closed source, including the OpenPGP bits. This is completely unacceptable and I strongly encourage people to boycott Yubico, including previous versions from now on. Contact information for the company can be found on their Github page.

Anyways, there are much better alternatives out there, in terms of software freedom:

  • FST-01 is the second machine running GnuK and I have heard others using it successfully
  • Nitrokey is another project, not using GnuK
  • USB Armory is a broader project but it can also be used as an authentication token

Other recommendations are welcome here, of course.

The biggest problems with those alternatives is they are less physically robust than the Yubikey. But the trade-off is not worth it anymore: stay away from Yubico until they see the light.

Comment by anarcat
tried google authenticator

Because I suspect it is better maintained, I tried the google-authenticator-libpam plugin which claims to also support HOTP/OATH so hit should just work. Unfortunately, I wasn't able to make it work:

  1. the secret is formatted differently, with base32 that base32 -d cannot parse
  2. even if it would, it uses a different secret length

I tried this magic piece of Python to generate a secret that would work in both:

secret = secrets.token_bytes(20)
print(binascii.hexlify(secret).decode('ascii'))
print(base64.b32encode(secret).decode('ascii'))

.. but it doesn't work. Details in https://github.com/Yubico/yubikey-personalization/issues/169

Comment by anarcat
Comments on this page are closed.
Created . Edited .