Getting PIV-based SSH working on a YubiKey
I bought a YubiKey 5C Nano recently. These devices are great – I’ve built a lot of my (metaphorical) empire on top of them, seeing as they’re capable of acting as an SSH agent (store your SSH keys on them, securely!), an OpenPGP smartcard (do encryption and decryption on the key!), FIDO U2F ‘security keys’ (use them as a 2-factor authentication method!), and probably more.
Getting the thing to work as an SSH agent was, however, not the easiest thing I’ve ever done. There are multiple options here – you
can use the OpenPGP applet and then configure GnuPG to work as an SSH agent, but that’s a brittle solution in my experience (gpg-agent
is quite flaky, and often requires restarting when it forgets about the YubiKey). Instead, I wanted to see whether I could use the YubiKey’s
PIV (Personal Identity Verification) applet to get this working.
Procedure
For the SSH agent part, we’re going to use Filippo Valsorda’s yubikey-agent, so you’ll
want to have that installed. In my testing, yubikey-agent’s built-in yubikey-agent -setup
command errored out, so we’ll configure the
PIV applet of the YubiKey manually. (This’ll also leave the door open for you to do other things with the PIV applet later if you like.)
You’ll also need Yubico’s own yubikey-manager (the ykman
cli tool) installed.
Updated on 2023-11-23 to reflect the command names being moved around.
Okay, here goes:
- Ensure
pcscd
(the PC/SC smartcard daemon) is installed and running. This might already have been done for you by your Linux distribution but, if not:$ sudo systemctl enable --now pcscd
- (This is safe to run if it’s already been set up; the command will just do nothing).
- This exercise assumes a fresh YubiKey (i.e. one where you haven’t touched the PIV applet yet). If that’s not the case, and you want to
erase all of the PIV data and start afresh (losing all data encrypted with the PIV keys!), use the
ykman piv reset
command. - First, change the PIN from the default one (
123456
).$ ykman piv access change-pin
- As the command’s help says: The PIN must be between 6 and 8 characters long, and supports any type of alphanumeric characters. For cross-platform compatibility, numeric digits are recommended.
- Then, change the PUK (‘personal unblocking key’). This is used to reset the PIN if you ever forget it.
$ ykman piv access change-puk
- The PUK has the same entry requirements as the PIN (i.e. also 6-8 ASCII characters).
- It might be prudent to generate a random PUK and keep it safe (e.g. by writing it down and locking the paper away). If you lose both the PIN and PUK, you will need to reset the PIV applet, losing all data encrypted with the PIV keys (and SSH access to hosts you don’t otherwise have access to).
- The YubiKey PIV applet by default has a well-known management key used to make changes to the PIV keys (etc.). It’s best practice to
change this to something else. We’ll use the option to generate a random one, store it in the YubiKey, and secure it with the PIN.
$ ykman piv access change-management-key -pt
-p
: Store new management key on your YubiKey, protected by PIN. A random key will be used if no key is provided.-t
: Require touch on YubiKey when prompted for management key.- If you’re more paranoid than me, you can use the
-g
option instead, which will generate a key for you to note down and give back later. However, the extent to which you can cause damage with a management key is limited (you can delete and regenerate keys, but not decrypt data or anything), so this is arguably not worth the hassle – especially since forgetting the management key means you’d have to reset the PIV applet to make changes.
- Generate a public/private keypair on the key, using the
9a
(“PIV Authentication”) key slot. We’ll use ECC (elliptic-curve cryptography) because it’s fast, secure, and has vastly smaller key sizes, and configure the key to always require touches and require the PIN on first use. You might want to use different settings here.$ ykman piv keys generate -a ECCP256 --touch-policy ALWAYS --pin-policy ONCE 9a ./yubikey-public.pem
- Note: Some outdated SSH servers/implementations only support RSA keys. If this applies to you, leave off the
-a
option to use RSA 2048 instead. - See
ykman piv keys generate -h
for a full description of all available options.
- Use the newly generated key to make a self-signed PKCS#11 certificate to act as our SSH identity.
$ ykman piv certificates generate -s 'my-yubikey-ssh' -d 365 9a ./yubikey-public.pem
- Modify the
-s
parameter to include a human-readable description of the key or the machine the key is installed in. - Modify the
-d
parameter to set how many days the key will be valid for. The value above specifices 1 year. - You can get rid of
yubikey-public.pem
after this step.
- Enable yubikey-agent.
$ systemctl --user enable --now yubikey-agent
- Note: You might need to kill any running instances of
gpg-agent
if you had that running (and it decided to try and use your YubiKey), and potentially restartpcscd
after doing so. - Note: Check that yubikey-agent started properly with
systemctl --user status yubikey-agent
.- On my machine, it failed to start because of namespacing issues.
- If this happens, edit the unit file with
systemctl --user edit --full yubikey-agent
, and remove all lines under[Service]
apart fromExecStart
andExecReload
.
- Update
SSH_AUTH_SOCK
to point to the running instance ofyubikey-agent
.$ export SSH_AUTH_SOCK=/run/user/1000/yubikey-agent/yubikey-agent.sock
- The above command assumes bash. Other shells may vary.
- If your user ID isn’t
1000
, you’ll need to change the above command (you can find the right path byps aux | grep yubikey-agent
). - Note: You’ll want to put the above command in
.bashrc
or similar.
- Get the key fingerprint for your newly generated SSH identity.
$ ssh-add -L
- Copy the key fingerprint to your remote host(s), and put it in
~/.ssh/authorized_keys
. - You should now be able to SSH using the YubiKey!
$ ssh <host>
- The first time you try this, it should pop up a window asking for the PIN, after which you’ll need to touch the flashing YubiKey.
- Subsequent attempts will only require a touch.
- If it didn’t work, check the notes under step 8. In particular, using OpenPGP and SSH simultaneously with the YubiKey usually requires
you to kill either
pcscd
orgpg-agent
, depending on what you want to do.
- Ensure you have a backup plan in place for when this YubiKey fails, or is lost.
- The key we generated is local to the YubiKey, and cannot be exported. It can very easily be wiped by anyone running
ykman piv reset
(which requires no authentication!), or you could lose the key. - Make sure you have another way of getting into all hosts you use this YubiKey with (encrypted regular SSH key, out-of-band console, etc.).
- The key we generated is local to the YubiKey, and cannot be exported. It can very easily be wiped by anyone running
- You’re done!
Hopefully that worked. If it didn’t, well, there’s a comments thing below if you can be bothered, I guess?