Yubikey for EVERYTHING

Posted by Wxcafé on Sat 07 July 2018
EDIT: Update 07/07/2018, added SSH_AUTH_SOCK information, a few pointers about key generation and backup, and info about gpg-agent’s bad behavior.

When I first started at the job I’m currently at at Gandi, I was given a Yubikey NEO, looked into it for a few minutes and quickly decided to not give more thought about it. I put it away and didn’t look back, partly because the yubikey-personalization-gui is maybe the most unusable and unintelligible interface I’ve ever seen, and partly because I had a 4096 bit key and didn’t think to make smaller subkeys to put in the smartcard (the NEO only supports 2048 bit keys).

A few weeks ago I decided to buy a Yubikey 4 on a drunken evening and got it in the mail a few days later, having completely forgotten I had ordered it. I decided to finally take a look at how this thing worked and what I could actually do with it.

So, first things first. The Yubikey is basically a GPG smartcard, with an added X.509 smartcard, WITH added U2F support. It can also be used to do TOTP/HOTP with the Yubico app on android smartphones or computers. It can probably also be programmed to solve quadratic equations, but I haven’t tried.

To clarify: functionally, a smartcard is a device that has an integrated small CPU and storage space for keys, and that you can only write keys to, not read from. The integrated CPU can then be asked to sign/encrypt/decrypt arbitrary data, but the keys can never be compromised from the key itself.

Anyway, my use case is as such: beforehand, I stored my GPG and SSH keys on my everyday computers, but I couldn’t use either of them on my work computers (I didn’t want to leave personal stuff like that on work equipment). I wanted to be able to connect to my servers and ideally decrypt files on any computer I encounter (okay, decrypting files on anyone’s computer is not a great idea, but there are a few computers I trust enough to decrypt files on them but not to give them my private keys). I also use pass to store my passwords, decrypting them with the yubikey everywhere is nice. Since the key allows me to access my passwords, I feel like putting the second factor of authentication on it just wouldn’t be reasonable, so I’m still using my phone as a 2fa TOTP source. Finally, I stored an X.509 cert for OpenVPN to try and see how it would work with the yubikey. I’m not using it for now but it’s there.


GPG key storage

Anyways, here’s how to use this thing:

  • First, always keep a backup of your private keys. You’ll need them to sign other people’s keys at keysigning parties, and if you lose the yubikey you’ll also need them to recover… well, everything. Anyway, keeping a good offline backup or two is important.

  • We’re gonna start by adding our GPG subkeys to the yubikey. That article covers pretty much everything, except generating an Authentication subkey, which is done by doing gpg --expert --edit-key <KeyID>, then addkey. You now need to select “(8) RSA (set your own capabilities)” as the type of key, then type S to toggle signing off, E to toggle encryption off, and finally A to toggle authentication on. Type Q to confirm and quit, then keep as usual for the key size/expiration date/etc. You’re now done, and we can start by setting up the yubikey. This is really easy, since the yubikey is detected as a smartcard by gpg:

$ gpg --edit-card

gpg --card-status      
Reader ...........: Yubico Yubikey 4 OTP U2F CCID 00 00
Application ID ...: D2760001240102010006076003030000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 07600303
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. D2760001240102010006076003030000 detected

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

Your selection? 3
PIN changed.

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

Your selection? 1
Pin changed.

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

Your selection? q

gpg/card> url
URL to retrieve public key: https://pub.wxcafe.net/wxcafe.asc

gpg/card> name
Cardholder's surname: Hertling
Cardholder's given name: Clément
Error: Only plain ASCII is currently allowed.
Cardholder's given name: Clement

gpg/card> login
Login data (account name): wxcafe

gpg/card> sex
Sex ((M)ale, (F)emale or space): M

gpg/card> lang
Language preferences: en

gpg/card> list

Reader ...........: Yubico Yubikey 4 OTP U2F CCID 00 00
Application ID ...: D2760001240102010006076003030000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 07600303
Name of cardholder: Clement Hertling
Language prefs ...: en
Sex ..............: male
URL of public key : https://pub.wxcafe.net/wxcafe.asc
Login data .......: wxcafe
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

gpg/card> %

The default PINs are 123456 for the user PIN and 12345678 for the admin PIN.

Do take caution to export the private keys for safekeeping BEFORE moving them to the yubikey (the gpg keytocard command MOVES the keys, after you’ve run it you don’t have the private keys available anymore to backup) (backups are easily done with gpg --armor --export-secret-keys <KeyID> > out.asc and gpg --armor --export-secret-subkeys <KeyID> > subkeys_out.asc. You obviously need to save these to a secure location.)

Now that we’ve prepped the card, we’re gonna move the keys over to it. We’re gonna move only the subkeys over, and since we’re gonna need to use the yubikey for everything we’ll have an Encryption subkey, a Signing subkey and an Authentication subkey.

$ gpg --edit-key wxcafe@wxcafe.net
gpg (GnuPG) 2.2.7; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 1

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb* rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> keytocard
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb* rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 1

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 2

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb* rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> keytocard

Please select where to store the key:
   (2) Encryption key
Your selection? 2

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb* rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 2

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb  rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 3

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb* rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> keytocard
Please select where to store the key:
   (3) Authentication key
Your selection? 3

sec  rsa4096/58DD226B3EA71DC7
     created: 2016-12-29  expires: 2019-06-18  usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/11E99643DEE9E336
     created: 2018-06-29  expires: 2019-06-29  usage: S   
ssb  rsa4096/E00F13324D0D1703
     created: 2018-06-29  expires: 2019-06-29  usage: E   
ssb* rsa4096/FD92FB8BD73D1D70
     created: 2018-06-29  expires: 2019-06-29  usage: A   
[ultimate] (1). Wxcafé <wxcafe@wxcafe.net>
[ultimate] (2)  Wxcafé <wx‮cafe@wxcafe.net>
[ultimate] (3)  Wxcafé <wxcaf​e@wxcafe.net>
[ultimate] (4)  Wxcafé <🖕@fu.cking.network>
[ultimate] (5)  [jpeg image of size 22243]

gpg> key 3

gpg> save

gpg> %

Now that this is done, we only need to gpg --card-status should show us this:

Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240102010006076003030000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 07600303
Name of cardholder: Clement Hertling
Language prefs ...: en
Sex ..............: male
URL of public key : https://pub.wxcafe.net/wxcafe.asc
Login data .......: wxcafe
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa4096 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: 752F 1ED2 D038 BCB6 F09C  A942 11E9 9643 DEE9 E336
      created ....: 2018-06-29 22:27:20
Encryption key....: 37B4 4050 8744 344C 0975  2656 E00F 1332 4D0D 1703
      created ....: 2018-06-29 22:28:22
Authentication key: 775D 1613 1B3F FEE7 843F  D3BC FD92 FB8B D73D 1D70
      created ....: 2018-06-29 22:29:29
General key info..: sub  rsa4096/11E99643DEE9E336 2018-06-29 Wxcafé <wxcafe@wxcafe.net>
sec#  rsa4096/58DD226B3EA71DC7  created: 2016-12-29  expires: 2019-06-18
ssb>  rsa4096/11E99643DEE9E336  created: 2018-06-29  expires: 2019-06-29
                                card-no: 0006 07600303
ssb>  rsa4096/E00F13324D0D1703  created: 2018-06-29  expires: 2019-06-29
                                card-no: 0006 07600303
ssb>  rsa4096/FD92FB8BD73D1D70  created: 2018-06-29  expires: 2019-06-29
                                card-no: 0006 07600303

To actually use the keys we just copied over, we need to install a smarcard daemon (apt install scdaemon) and authorize our user to access it. For that we write a udev rule in (/etc/udev/rules.d/70-yubikey.conf) with this in it: https://raw.githubusercontent.com/Yubico/libu2f-host/master/70-u2f.rules

Now we should be able to use the yubikey to sign and encrypt whatever we want! We can test this by doing touch test; gpg -a --sign test, which should prompt us for the yubikey PIN and then create the test.asc file.

We can still do gpg --export-secret-keys <key-id>, and while it looks like it works (because GPG’s output is undecipherable and it’s UX is the worst thing I’ve ever seen), it actually outputs “stubs”, which allows a system to “see” the keys on the card. They’re not needed anymore since for a while now, gpg –card-status automatically detects the keys on the card.

Now we want to use our gpg authentication key with SSH, to log in to our servers. To do that, we need to tell gpg-agent to act as an ssh-agent, by adding a single line to its configuration: echo 'enable-ssh-support' >> .gnupg/gpg-agent.conf. Then we restart gpg-agent (gpgconf --kill gpg-agent). Then, we need to tell ssh to use gpg-agent’s socket as its agent. We do this by adding a small snippet to our $shrc (for me, ~/.zshrc):

## use gpg agent as ssh agent
if which gpgconf 2>&1 >>/dev/null ; then
    unset SSH_AGENT_PID
    if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
      export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
    fi
fi

Unplug the key, plug it back in, run gpg --card-status, then ssh-add -L should show you a public key that ends with cardno:xxxxxxxxxxxx. That means it’s done, you can now add this public key to .ssh/authorized_keys on your remote systems and you should be able to log in with that key.

Oh, and, side note. gpg-agent won’t actually delete your cached keys when you ssh-add -D, which is fucking bullshit, but in the meantime the solution is to gpg-connect-agent, then KEYINFO --ssh-list --ssh-fpr to list the cached keys, and then you can DELETE_KEY <FINGERPRINT> that particular key, with the fingerprint being the part right after KEYINFO. Quit by saying /bye


X.509 key and certificate storage

Now for the X.509 storage, this is a bit easier. You will need to make a pkcs12 out of your certificate and associated key (openssl pkcs12 -export -out out.p12 -inkey key.pem -in cert.crt --certfile ca.crt -nodes), and then we can import it into the yubikey (this will not destroy the .p12 file, nor the key or cert, because this has a sensible UX, as opposed to gnupg): yubico-piv-tool -s 9c -i out.p12 -K PKCS12 -a import-key -a import-cert -k

In my case, I was using this with OpenVPN, so I needed to install opensc-pkcs11 (which is a library that allows applications to see certificates from a smartcard), then to look up under which ID OpenVPN saw my certificate with openvpn --show-pkcs11-ids /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so. After that, I updated my OpenVPN configuration by replacing the cert and key lines with

pkcs11-id 'piv_II/PKCS\x2315\x20emulated/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
pkcs11-providers /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so

And then it worked like a charm! What actually stopped me is that I use networkmanager on my machines since wicd is dead, and it doesn’t support that kind of config. Of course, wicd would since it can launch arbitrary scripts… but I’m stuck with NM and so I can’t use this easily, sadly.


Pass

So, technically pass is not related to the yubikey at all, and doesn’t directly interact with it. If you haven’t heard about it, it’s a simple password manager for unix written in bash by zx2c4. But it uses GPG as an encryption mechanism, and since I was moving towards using GPG to authenticate SSH connections, I thought I might as well start using pass with the yubikey. So, here goes.

Pass is a very simple password manager, so simple that all it does is keep your passwords for you and output them when you ask nicely. It’s installed on basically all systems with the package manager (in my case, apt install pass), and then it’s a matter of pass init <keyid> to initialize the folder with the specified gpg key id, pass git init and pass git remote add origin <remote> to use git to store the passwords (that’s optional, but it’s so much better it’d be dumb not to do it…). Then you can add a password with pass insert <path>, or generate one with pass generate <path> <length>. Don’t forget to pass git push (it’ll commit automatically if it’s in git).

To see all the passwords you have in pass, use pass ls, and to read a specific password use pass <path>. The problem with that is that the password is written to stdout, and we don’t really want to a) have people able to snoop the password, nor b) open a terminal and copy-paste every time we want to get a password. That’s where passmenu comes in! It’s a simple script included with pass (at least it is in debian) in /usr/share/doc/pass/examples/dmenu/passmenu, and it relies on dmenu to show you all of your passwords. From there, you can type a few letters and select which password you want, and once you confirm your selection it will copy it into your clipboard, ready for you to paste into any password field. There are extensions to have it support TOTP and other stuff too, but as previously I’m not too comfortable with putting all my eggs in the same basket.

So yeah, move all your passwords to pass and make them as complex as possible! When you get to a new machine you can simply git clone <repo> ~/.password-store and apt install pass and you’re all set! Don’t lose your GPG key though, or you’re SCREWED.

Don’t lose it. Ever. Print it out and put it in the bank. Whatever. But don’t lose it.

Anyway, that was it! I’m seriously happy with how this solution works for me, and how secure it is without sacrificing much towards usability, even improving it in most cases.

(P.S.: I don’t get anything by saying this, but my employer (gandi) has a partnership thing in place with yubico, such that if you login on this page with a gandi account, you get 20% off your yubikey. You don’t even need to buy anything with gandi afaik, only to have an account)