YubiKey as a portable SSH key

This blog post is just a bunch of shell snippets quickly put together that explain how to use YubiKey as your ssh key.

Setup

So first things first, we will have to enable CCID (smartcard interface) on YubiKey:

For YubiKey Neo or YubiKey 4 run following:

$ ykpersonalize -m82

For YubiKey 5:

$ ykman config usb --enable-all # For USB
$ ykman config nfc --enable-all # For NFC

Next step would be to change default PINs on YubiKey:

$ gpg --card-edit

gpg/card> admin
Admin commands are allowed

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

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

# default password here is 123456
Your selection? 1

...

gpg: OpenPGP card no. _____ detected

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

# default password here is 12345678
Your selection? 3

...

If you mess anything up (forgot your PIN for example), you can always nuke OpenPGP configuration on your YubiKey:

$ ykman openpgp reset
WARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: y

There are two ways to use your YubiKey as a ssh key:

Method #1: Generating keys on YubiKey itself

Simply run the following commands:

gpg --card-edit

gpg/card> generate

Benefit of this method is that your keys and YubiKey are self contained, this is also a downside of this method. If you lost a key, you also going to loose access to your private key. There is no way to backup secret keys from YubiKey itself. I did not really test this method extensively, but still felt like mentioning this as a valuable option.

Method #2: Generating a subkey for your own private key

So lets say you generated your own private key with id ABCDEFG, now we can generate 2 subkeys and store them on our YubiKey. The advantage of this approach is in the fact that subkeys can be revoked by using your private key.

$ gpg --edi-key ABCDEFG

gpg> addcardkey

Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]

Please select the type of key to generate:
   (1) Signature key
   (2) Encryption key
   (3) Authentication key
Your selection? 3

# Now answer bunch of questions and enter PIN couple of times to make gpg happy
# After that we can do the same for the Signature key

...

gpg> addcardkey

Signature key ....: [none]
Encryption key....: [none]
Authentication key: OUR NEW KEY ID

Please select the type of key to generate:
   (1) Signature key
   (2) Encryption key
   (3) Authentication key
Your selection? 1

# Now lets use toggle to switch in to private key listing mode
> toggle

# And now lets select our main private key
> key 1

# Time to upload card keys to our YubiKey in to encryption slot
> keytocard

Signature key ....: ANOTHER ID
Encryption key....: [none]
Authentication key: OUR NEW KEY ID

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

# And we are done here
gpg> save

Don’t forget to distribute your private key if needed:

$ gpg --keyserver hkps://keys.gnupg.net --send-key ABCDEFG

Final steps

Generating ssh key out of our gpg key is pretty straightforward:

$ gpg --export-ssh-key ABCDEFG
ssh-rsa BLABLABLA openpgp:someid

Now its time to start gpg-agent with ssh support on:

$ gpg-agent --daemon --enable-ssh-support
# or on older gpg versions you can generate env file that you can source in your .bashrc directly
$ gpg-agent --daemon --enable-ssh-support --write-env-file ~/.gpg-agent-env

Or if you are on systemd based system there is a chance that your user already has a bunch of systemd sockets enabled for this purpose. One socket that you should be interested in is gpg-agent-ssh.socket, you can see if its running by running systemctl --user status gpg-agent-ssh.socket.

Add this to your .bashrc to initialize env var properly:

export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"

Last improvement that can be made to this setup is forcing gpg-agent to use pcscd instead of ccid. This should solve some issues with a card being unavailable when some other application is accessing it. Just add following to the ~/.gnupg/scdaemon.conf file:

pcsc-driver /usr/lib/libpcsclite.so
card-timeout 5
disable-ccid

Setting up key on a new machine

# Pull key info from a keyserver
$ gpg --keyserver hkps://pgp.mit.edu --recv-key ABCDEFG

# Initialize smart card data in gpg db
$ gpg --card-status

And if gpg-agent is setup properly you should be ready to go. Just plug in your YubiKey and try to ssh to some host, you should see PIN prompt which means everything works as expected.

Caching PIN on a key itself

It might be annoying to type in PIN on every action every time, there is an option to cache PIN on YubiKey itself, so you will have to input PIN only once after you plugged your key (will have to do that every time you unplug/plug your key) for every action (separate key cache for signing/authenticating using the key). To do that you should enable forcesig in gpg:

$ gpg --card-edit

gpg/card> admin
Admin commands are allowed

gpg/card> forcesig
gpg/card> save

To add extra layer of security, you can enable YubiKey 4 Touch feature (every action will have to be confirmed with a touch), this can be enabled using yubikey-manager cli:

$ ykman openpgp touch aut on
$ ykman openpgp touch sig on
$ ykman openpgp touch enc on

P.S.

GPG agent for NixOS:

programs.ssh.startAgent = false;
programs.gnupg.agent = { enable = true; enableSSHSupport = true; };

Comments