On 10.08.2020 I updated this post with guide on using YubiKey together with WSL 2, as the way to get SSH auth working on WSL 2 differs from WSL 1.

Recently I spent a week investigating the use of YubiKeys to increase the security of our company. In the process, I've read many different tutorials on using GPG (GNU Privacy Guard), some more up to date, some less, and had to piece together information on getting GPG + YubiKey working from WSL. To simplify this process for the next person (and to have something I can link people to when they ask), I've decided to write everything down .

This post assumes that you already know what GPG is, and why you want to use it, but you don't have your own set of keys yet. If you are further along (e.g. you already have your own set of keys), you can skip those parts and use your already existing keys instead of generating new ones.

It also assumes that your YubiKey can hold 4096 bits RSA keys. As far as I know, this is true for all of the 5th generation Yubikeys, but it is not true for the YubiKey 4 NFC. If your YubiKey can only hold 2048 bits RSA keys, you will need to generate smaller subkeys in the appropriate step (the master key should still be kept at 4096 bits).

Step 1: Install and set up GPG

If you are using Windows, you will need gpg4win. When I was writing this post, the latest version was 3.1.5.

If you are on Linux, you likely already have gpg installed, but you should check it's version -- e.g. on Ubuntu 16.04 LTS, gpg is GPG in version 1.4.20. I strongly recommend getting GPG in version 2.x.x.

If you want to use gpg from within WSL together with YubiKey, you have to install gpg in version 2.x.x inside WSL and install gpg4win on the side of Windows.

Settings

On Windows, GPG (and related) settings are in AppData/Roaming/gnupg . On Linux, the settings can be found in ~/.gnupg/ . The settings files themselves are gpg.conf for the gpg binary, scdaemon.conf for the SmartCard daemon and gpg-agent.conf for the gpg-agent.

These will become important later, but if you are on Windows I recommend placing charset utf-8 into gpg.conf straight away.

Step 2: Generate a new set of keys

After the previous step, you should have GPG set up and ready to generate keys. In my case, the executable name ended up being gpg2 , so I will use that in examples through this post. We will need to generate 3-4 keys, or rather 1 key and 2-3 subkeys. They will be

A master key that should be backed up and kept strictly offline, An Encryption key, a subkey of the master key that is used for encryption A Signing key, a subkey of the master key that is used for signing An (Optional) Authentication key, a subkey of the master key that can be used for SSH or similar

The master key is used for issuing/revoking subkeys and confirming other people's identities. This makes it essentially a person's online identity and thus should be kept securely backed up on offline medium and removed from the PC where it was generated afterwards.

The Encryption and Signing keys are the keys used during everyday activity, and because they are bound to the master key, if they are ever compromised they can be easily revoked, at least as long as you retain control over your master key. The Authentication key is a bit different in that some people think it is pointless (or even that it shouldn't be used), while other people are using it regularly. This post assumes that you will want to use it as well, but you can always just skip those steps.

Generating the master key

Because GPG (at least in the version I have) still defaults to 2048 bits RSA keys and we want to generate 4096 bits RSA keys in the interest of future-proofing , we will have to run GPG with the --full-gen-key option so we can customize properties of the generated key. GPG will then ask you about various properties of your new key, as you can see below, where gpg X> means that GPG is asking you about X:

$ gpg2 --full-gen-key gpg keytype> 1 (RSA and RSA) gpg keysize> 4096 gpg expiry> 3y gpg correct> y gpg real name> ${your name} gpg email addr> ${your email} gpg comment> gpg okay> O gpg passphrase> ${password to protect this key}

You can also embed images in the key, but please don't.

You should always have your key expire eventually -- as long as you have access to it, you can extend its expiration date when it becomes relevant -- and you should also always have a passphrase on your master key. Real name and email address are hopefully self-explanatory, but comments are controversial. Some people hold the opinion that comments are a mistake and should not be used , while other people see comments as OK, as long as you avoid pointless redundancy (e.g. repeating your email address in the comment). Personally, I don't really care if your user-id looks like this

Sir Mix-A-Lot (I like big butts, and I cannot lie) mixalot@yahoo.com

but I would prefer if it didn't look like this

If the key generation was successful, you should see something like this:

gpg: key 6129F208 marked as ultimately trusted gpg: directory '/home/xarn/.gnupg/openpgp-revocs.d' created gpg: revocation certificate stored as '/home/xarn/.gnupg/openpgp-revocs.d/1356ED7D349B649687E5D1ECA8F90C096129F208.rev' public and secret key created and signed. gpg: checking the trustdb gpg: marginals needed: 3 completes needed: 1 trust model: PGP gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u gpg: next trustdb check due at 2021-11-04 pub rsa4096/6129F208 2018-11-09 [S] [expires: 2021-11-08] Key fingerprint = 1356 ED7D 349B 6496 87E5 D1EC A8F9 0C09 6129 F208 uid [ultimate] Jan Novák <email@example.com> sub rsa4096/BF36D4AC 2018-11-09 [] [expires: 2021-11-08]

This tells you that 2 keys were created, a master key with id 6129F208 and an encryption sub key with id BF36D4AC . For now, the master key id is the important one, the subkey id is not. Also note that both of these ids are in so-called "short" (32 bits) format, which is generally considered insecure, and either the long (64 bits) key id, or the full key fingerprint should be used instead. To get the long key id, you can pass --keyid-format long flag to gpg, e.g.:

$ gpg2 --list-keys --keyid-format long /home/xarn/.gnupg/pubring.kbx ----------------------------- pub rsa4096/A8F90C096129F208 2018-11-09 [SC] [expires: 2021-11-08] uid [ultimate] Jan Novák <email@example.com> sub rsa4096/72FBD8C2BF36D4AC 2018-11-09 [E] [expires: 2021-11-08]

This means we actually want to use A8F90C096129F208 as the master key id during the next steps.

Because we are using newer gpg, something called revocation certificate has also been generated -- a revocation certificate can be uploaded to key servers if you lose control of a key to mark the key as invalid. Obviously, you should back up the revocation certificate somewhere.

Adding more user ids

You might want to have more than one user identity (userid) in your master key. This is primarily used to either connect an internet screen name with a real-world name or to add associate more email addresses with your identity. In either case, you can do that by editing the master key:

$ gpg2 --edit-key A8F90C096129F208 gpg> adduid Real name: Email address: Comment:

Generating subkeys

We already have the encryption subkey, now we also have to add the signing and authentication subkeys. This is done by editing the master key in expert mode (note that without --expert we can't set the key type by ourselves) and using the addkey command:

$ gpg2 --expert --edit-key 6129F208 gpg> addkey gpg key-kind> 8 (RSA, own capabilities)

this will open up a menu where you can select what capabilities the new key should have. When using it, keep in mind that "toggle" does mean toggle and that the key starts with the S(ign) and E(ncryption) bits enabled. After you select the right set of capabilities (for this tutorial it means the key has only S or only A capability), you will get to the dialogue for creating keys -- set the size of the key to 4096 bits, expiration date to something reasonable and pick a passphrase again.

After creating both S(ign) and A(uthentication) keys, you should end the editing session and check that your keys were created properly:

gpg> save xarn@DESKTOP-B2A3CNC:~ :) gpg2 --list-keys --keyid-format long

You should see something like this:

/home/xarn/.gnupg/pubring.kbx ----------------------------- pub rsa4096/A8F90C096129F208 2018-11-09 [SC] [expires: 2021-11-08] uid [ultimate] Jan Novák <email@example.com> sub rsa4096/72FBD8C2BF36D4AC 2018-11-09 [E] [expires: 2021-11-08] sub rsa4096/94D8AB7C17FCE986 2018-11-09 [S] [expires: 2021-11-08] sub rsa4096/03F0A89596D8D340 2018-11-09 [A] [expires: 2021-11-08]

i.e. 4 keys, 3 of which are subkeys (marked with sub ) and each of the subkeys has only one of the A/E/S capabilities.

Publishing and backing up the master key

Now that we have our keys ready, it is time to

Publish the public part of the key Backup and securely store the private parts of the master key

Publishing is easy enough, as long as you find a keyserver that accepts uploads. I had some trouble finding one, but as of the time of writing, fks.pgpkeys.edu worked:

$ gpg2 --keyserver fks.pgpkeys.edu --send-key A8F90C096129F208

If this succeeds, people can download your key by its id from the public key server pools.

Backing the key up is also relatively simple, the first step is to export it. This is usually done in a format called ASCII armor, because cat ing a binary file into your terminal is no fun:

$ gpg2 --armor --export-secret-key A8F90C096129F208 > secret-key.asc

The second step is to securely back up secret-key.asc -- the usual recommendation is to use 1 or more encrypted USB cards. You should also delete the master key from the computer, but doing so right now would prevent you from moving the subkeys to the YubiKey.

Step 3: Setting up the YubiKey

If you used gpg inside WSL to generate your keys, you will have to first set up a bridge between gpg-agent inside WSL and gpg-agent inside Windows. See "Extras: gpg-agent bridge" for details.

First, we need to check that gpg can see the YubiKey when it is plugged in -- If it does not, check section "Extras: gpg does not detect YubiKey" for help.

$ gpg2 --card-status Reader ...........: Yubico YubiKey OTP FIDO CCID 0 Application ID ...: D2760001240102010006090200580000 Version ..........: 2.1 Manufacturer .....: Yubico <snip>

Moving subkeys to the YubiKey

The option to move keys to the YubiKey is once again under --edit-key :

$ gpg2 --edit-key A8F90C096129F208 gpg> key 1 gpg> keytocard gpg> <pick the right slot> gpg> <repeat for the other keys> gpg> save

keytocard is a destructive operation and removes the private subkey from the local key store. Now that the subkeys are stored on the YubiKey, you should delete the master key. To do that, you need to know its keygrip:

gpg2 --list-secret-keys --with-keygrip /home/xarn/.gnupg/pubring.kbx ----------------------------- sec rsa4096/6129F208 2018-11-09 [SC] [expires: 2021-11-08] Keygrip = 5436620CA40373692E45B41A7831BEC2ACE624AB uid [ultimate] aslkdjfs (sjsj) ssb> rsa4096/BF36D4AC 2018-11-09 [E] [expires: 2021-11-08] Keygrip = D75AA532535A5E93C90353A3F273C0391FE25516 ssb> rsa4096/17FCE986 2018-11-09 [S] [expires: 2021-11-08] Keygrip = B14D4AE1729E43DD1E1304C6CA083DA1CA8C6059 ssb> rsa4096/96D8D340 2018-11-09 [A] [expires: 2021-11-08] Keygrip = 2F35594B4CFBA552BD73E4542065E7988BDE1564

from the listing above, the keygrip of the master key is 5436620CA40373692E45B41A7831BEC2ACE624AB and it can be deleted via

$ gpg-connect-agent "DELETE_KEY 5436620CA40373692E45B41A7831BEC2ACE624AB" /bye

You can verify that it has been deleted by listing the private keys again -- the master key should have a # next to it to signify that it cannot be used (the > next to the subkeys means that they are on the YubiKey).

Change YubiKey's PIN

All YubiKeys share the same factory PIN, 123456, and the same admin PIN, 12345678. Because PIN is what the YubiKey asks for to use a key, you need to change it. You can either do this via Yubico's management utility or via gpg:

$ gpg2 --change-pin gpg> 1 (change PIN) gpg> 3 (change admin PIN) gpg> q

Enable touch protection for GPG keys

I also recommend enabling touch protection for GPG keys on the YubiKey. This means that to use any of the GPG keys on the YubiKey, you need to do 2 things:

Enter the PIN (this is usually cached for a couple of hours) Touch the YubiKey's touch sensor

The upside is that even in the case a piece of malware manages to get onto your machine and intercepts your PIN, it still will not be able to use the GPG keys on your YubiKey. The downside is that you will be made painfully aware of every single usage of your GPG keys, which can sometimes be annoying .

To enable touch protection, you will need the Yubikey Manager utility. Once you have it installed, you can enable the touch protection for each key slot separately:

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

And that is it, now you have your GPG subkeys on the YubiKey, the YubiKey is set up correctly and you should be able to just use it with gpg.

Extras:

git config

Signing git commits seems to be the most common reason for using GPG, so here are the necessary configuration steps.

Tell git to use the right version of gpg. If you are using Git for Windows, it will likely try to use the wrong gpg binary. Similarly, if you had to install gnupg2 package to get modern gpg, you need to configure git to use gpg2 instead of gpg binary.

# Windows git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe" # Linux git config --global gpg.program gpg2

Tell git which key to use

git config --global user.signingkey <signing-subkey-id>

Tell git to sign each commit

# Add --global if you want to sign every commit of every git tree # Keep it like this to only enable signing for this specific tree git config commit.gpgsign true

SSH Authentication via GPG key on YubiKey

This section does not apply to using YubiKey for SSH auth inside WSL.

To use your Auth subkey for SSH auth, you need to enable ssh support in gpg-agent.

To do so, you need to add enable-ssh-support to gpg-agent.conf , restart the gpg-agent and set it up to run on login (so that it is available when SSH asks for keys). You also need to set environment variable SSH_AUTH_SOCK to ~/.gnupg/S.gpg-agent.ssh .

You can check that everything works with ssh-add -L -> you should see the auth key from YubiKey in SSH format.

Note that keys in Auth slot on the YubiKey are given to SSH even if they are not in the sshcontrol file.

Troubleshooting -- GPG does not see the YubiKey

The most common reason for GPG not to see the YubiKey is that there are multiple SmartCard readers in the system. This is caused by the fact that if there is more than one SmartCard reader in the system, scdaemon just defaults to checking the first one and if that is not a GPG compatible smart card (in our case the YubiKey), it does not try the other ones.

To solve this, you will need to add reader-port <port id or device name> to scdaemon.conf . You can find the proper name from scdaemon logs, because it enumerates all readers, even though it only picks one:

# scdaemon.conf debug-level guru log-file <path>

Afterwards, you need to find lines saying "detected reader", specifically the one talking about YubiKey.

# scdaemon.log: 2018-11-06 18:11:14 scdaemon[11056] detected reader 'Alcor Micro USB Smart Card Reader 0' 2018-11-06 18:11:14 scdaemon[11056] detected reader 'Yubico YubiKey OTP+FIDO+CCID 0' 2018-11-06 18:11:14 scdaemon[11056] reader slot 0: not connected

Going by this log you should set reader-port to Yubico YubiKey OTP+FIDO+CCID 0 .

WSL 1 GPG bridge

Because the only devices visible from WSL are drives, which the YubiKey is not, gpg inside WSL cannot use the YubiKey directly. Luckily, we can work around that by redirecting requests to gpg-agent under WSL to the gpg-agent running under Windows.

This can be done by combining the npiperelay utility on the Windows side with socat on the Linux side.

Getting npiperelay.exe

There are two ways to get the npiperelay.exe binary

You can download it from the GitHub releases Build it yourself

The second option has a small problem in that if you install an older version of go (e.g. 1.6.2 from apt on Ubuntu 16.04), it will compile fine, but it will fail at runtime, and that the Readme in the linked repo is not updated to reflect the fork's address.

Setting things up

You will need the Windows-side gpg-agent to run right after startup. The easiest way of doing that is to add a shortcut to "C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" /bye in the %AppData%\Microsoft\Windows\Start Menu\Programs\Startup folder. You should also set the shortcut to run minimized, to avoid pointless cmd pop-up on login.

On the WSL side, you should add this to ~/.profile or similar:

##### ## Autorun for the gpg-relay bridge ## SOCAT_PID_FILE=$HOME/.misc/socat-gpg.pid if [[ -f $SOCAT_PID_FILE ]] && kill -0 $(cat $SOCAT_PID_FILE); then : # already running else rm -f "$HOME/.gnupg/S.gpg-agent" (trap "rm $SOCAT_PID_FILE" EXIT; socat UNIX-LISTEN:"$HOME/.gnupg/S.gpg-agent,fork" EXEC:'/mnt/c/PATH_TO_NPIPERELAY/npiperelay.exe -ei -ep -s -a "C:/Users/WINDOWS_USERNAME/AppData/Roaming/gnupg/S.gpg-agent"',nofork </dev/null &>/dev/null) & echo $! >$SOCAT_PID_FILE fi

with paths modified accordingly.

WSL 1 SSH bridge

I was unable to create a bridge between the WSL gpg-agent and Windows gpg-agent that would use gpg-agent's ssh support , but I managed to get it to work with gpg-agent's PuTTY support thanks to WSL-SSH-Pageant, and here are the steps:

Enable PuTTY support in Windows-side gpg-agent by adding enable-putty-support to gpg-agent.conf , and restarting the gpg-agent. Get a wsl-ssh-pageant.exe , either from the GitHub Releases page or by compiling it yourself. Once you have it, you need to pick a path where it and a socket file will live -- I picked c:\ubuntu\wsl-ssh-pageant\ , so the path to the executable is c:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe and to the socket is

c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock . Set WSL environment variable SSH_AUTH_SOCK to /mnt/c/ubuntu/wsl-ssh-pageant/ssh-agent.sock (the path to the socket). From the Windows side, run C:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe --wsl c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock to start the bridge.

If everything worked correctly, you can now call ssh-add -L from WSL and see the GPG Auth key on YubiKey in SSH format. If it works, it is time to set up autorun.

Autorun

Because running wsl-ssh-pageant blocks the terminal for as long as it is running, if we just set up an autorun shortcut the terminal will remain open until you log off. To avoid this, we will write a trivial Visual Basic script that will run wsl-ssh-pageant in a hidden window, and place it in the autorun folder:

Set objShell = WScript.CreateObject("WScript.Shell") objShell.Run("C:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe --wsl c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock"), 0, True

Troubleshooting

wsl-ssh-pageant will silently fail if you give it path into a folder that does not exist. This means that you should double check the paths you pass to it.

WSL 2 GPG bridge

You can use the same approach to get YubiKey + GPG bridge to WSL 2 as for WSL 1, so look at the section for WSL 1.

WSL 2 SSH bridge

Unlike WSL 1, WSL 2 does not support AF_UNIX socket, so to get SSH auth from YubiKey working, you need a different approach. After some experiments, I settled on an approach that works well enough, and is very similar to how GPG is handled.

Run wsl-ssh-pageant.exe (see the WSL 1 SSH section for more details with) --winssh ssh-pageant argument, so it starts translating requests on ssh-pageant named pipe. Use socat on the Linux side and npiperelay.exe on the Windows side to relay ssh-agent requests from WSL to the Windows gpg agent.

For the second step, I added this to my .profile :

SOCAT_SSH_PID_FILE=$HOME/.misc/socat-ssh.pid if [[ -f $SOCAT_SSH_PID_FILE ]] && kill -0 $(cat $SOCAT_SSH_PID_FILE); then : # already running else rm -f "$HOME/.gnupg/S.gpg-agent" (trap "rm $SOCAT_SSH_PID_FILE" EXIT; socat UNIX-LISTEN:"$HOME/.misc/wsl2-ssh-agent.sock,fork,unlink-close,unlink-early" EXEC:"/mnt/c/ubuntu/npiperelay/npiperelay.exe /\/\./\pipe/\ssh-pageant",nofork </dev/null &>/dev/null) & echo $! >$SOCAT_SSH_PID_FILE fi export SSH_AUTH_SOCK=$HOME/.misc/wsl2-ssh-agent.sock

Obviously, you will need to update paths to correspond to the set up on your own machine.

Also of note is that using wsl-ssh-pageant to create a named pipe in front of Windows's side gpg agent has one nice side effect in that it means you can also have the Windows 10 OpenSSH client authenticate against it.

Autorun and troubleshooting

I recommend also reading the "autorun" section of WSL 1 for tip on how to set up nicely behaved autorun for wsl-ssh-pageant , and the "troubleshooting" section for, well, troubleshooting tips.

Moving subkeys to a different YubiKey

Sometimes, you lose access to your YubiKey. Maybe it was lost by your kids, and you just cannot find it, maybe it broke or perhaps it was stolen. If it was stolen, you should revoke the old subkeys , remove them from machines you used them to SSH into, create a new set of subkeys for the new YubiKey and update all places where you refer to your keys.

However, if you lost your YubiKey in a manner that cannot compromise the subkeys within, you might want to just reuse the subkeys in the new YubiKey. If you decide to do so, you will likely run into two problems that will make things harder for you.

Retrieving subkeys from backup is non-trivial

GPG will not use old keys from new YubiKey on its own

Retriving subkeys from a backup

Because moving subkey to a SmartCard (like YubiKey) is a destructive operation, you will need to retrieve the subkeys from your backup before you can move them to a new YubiKey. The problem with this is that if the main key is in your keychain, the subkeys will not be loaded from backup. This means that you first need to completely delete the key from your keychain, with --delete-secret-key :

$ gpg2 --delete-secret-key KEY-ID

Now when you load the key from a backup, it will also include the private parts of the subkeys. Note that deleting and reloading the key will set its trust level to "unknown", so you will want to set it back to "ultimate" before continuing. Afterwards, you can just move the subkeys to the new YubiKey the same way you moved them to the old one.

Telling GPG to look for keys on a new YubiKey

If you've used a key saved on a SmartCard on a specific machine, GPG made itself a little note saying "private key ABCDEF can be found on SmartCard 123456". The problem is that it will not update this stub even if a different SmartCard (such as your new YubiKey) with the same key is plugged in.

Thus we will need to force gpg to update the location of our keys, by manually deleting the key's stubs from the private-keys-v1.d , a subdirectory of gpg's directory. Specifically, we will need to find out the keygrips of our keys, and then remove files named {keygrip}.key .

To find the keygrip of a key, you need to provide --with-keygrip option when listing keys, like so :

$ gpg2 --list-keys --with-keygrip ----------------------------- ... sub rsa4096/6DB60DDB 2018-11-05 [E] [expires: 2021-11-04] Keygrip = 89C30607C0E5E0ABE8341B99FB5B69F67982A52C sub rsa4096/8B0D381A 2018-11-05 [S] [expires: 2021-11-04] Keygrip = F1C1895112B44C6AA878D385A651259457B84F6D sub rsa4096/9279285C 2018-11-05 [A] [expires: 2021-11-04] Keygrip = 0263D9699427839943283A3C7F9A228739CE4A5C

Given this output, I would need to delete the files

89C30607C0E5E0ABE8341B99FB5B69F67982A52C.key

F1C1895112B44C6AA878D385A651259457B84F6D.key

0263D9699427839943283A3C7F9A228739CE4A5C.key

Possible problems

A reader has contacted me about running into some problems when following this tutorial. The first one of them was that he could not move 4096 bits RSA keys to his Yubikey 5, because it defaults Key attributes to 2048 bits and gpg refused to write 4k keys there. As it turns out, gpg used to overwrite them automatically, but it no longer does, and you will need to manually set them to the correct size, using

$ gpg --card-edit gpg/card> key-attr