GPG authentication keys

For this doc we create a new ed25519 GPG key on the yubikey together with a GPG authentication subkey. This subkey can then be used to authenticate via ssh. All you need for that is a running gpg-agent, which acts as a ssh-agent in this special case.

At the end of the document you will find also some words about FIDO2 keys and why we are not using them here.

To use this feature, you need a yubikey with OpenPGP support. These are the yubikeys of series 4 and 5, but not the Bio and Security Key series. Here you can find an overview of the functionality of the different yubikeys.

Setup your yubikey

First of all, you need some tools installed:

> brew install gnupg pinentry-mac

Now you should change the default PINs, if not already done:

> gpg --card-edit
[...]
gpg/card> admin
Admin commands are allowed

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

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

You can now change PIN (default: 123456) and Admin PIN (default: 12345678).

In the next step we configure our yubikey to create ECC keys instead of RSA. At first glance it seems confusing that you enter the same things three times - but this is for three different keys. So don’t get confused:

gpg/card> key-attr
Changing card key attribute for: Signature key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519
Note: There is no guarantee that the card supports the requested
      key type or size.  If the key generation does not succeed,
      please check the documentation of your card to see which
      key types and sizes are supported.
Changing card key attribute for: Encryption key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: cv25519
Changing card key attribute for: Authentication key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519

Select always the ECC or the Curve option. Now we can create the actual keys. And no, we don’t want to have off-card backups. If you have multiple yubikeys, it could be a good idea to add some infos in the comment section:

gpg/card> generate
Make off-card backup of encryption key? (Y/n) n

Please note that the factory settings of the PINs are
   PIN = '123456'     Admin PIN = '12345678'
You should change them using the command --change-pin

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 4y
Key expires at Mo  7 Dez 18:09:42 2026 CET
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Mathias Nohr
Email address: mathias.nohr@ioki.com
Comment: yubikey red
You selected this USER-ID:
    "Mathias Nohr (yubikey red) <mathias.nohr@ioki.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
gpg: revocation certificate stored as '/Users/mathiasnohr/.gnupg/openpgp-revocs.d/584D3739607298B95802B268B02C537EA1B34572.rev'
public and secret key created and signed.

Okay, that’s it on the yubikey side. We can now leave the card editor by pressing q:

gpg/card> q
pub   ed25519 2022-12-08 [SC] [expires: 2026-12-07]
      XXX
uid                      Mathias Nohr (yubikey red) <mathias.nohr@ioki.com>
sub   ed25519 2022-12-08 [A] [expires: 2026-12-07]
sub   cv25519 2022-12-08 [E] [expires: 2026-12-07]

Configure gpg-agent

To make use of the authentication key you have to configure the gpg-agent first. Edit vi ~/.gnupg/gpg-agent.conf:

default-cache-ttl 600
max-cache-ttl 7200
pinentry-program /opt/homebrew/bin/pinentry-mac
use-standard-socket
enable-ssh-support

The location of the pinentry tool could be different - depending on you MacOS version. Now we can kill all ssh-agent process and start our gpg-agent:

> killall ssh-agent
> killall gpg-agent
> gpgconf --launch gpg-agent
> export SSH_AUTH_SOCK="/Users/mathiasnohr/.gnupg/S.gpg-agent.ssh"

Now you should see your ssh public key in your “ssh-agent”:

> ssh-add -L
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIORVlteRRLuT87FC+7/c1cxnMsk+SPqN/Evh4Cn9cZzk cardno:15 421 439

You can now move your ssh public key to your linux hosts and connect with your yubikey plugged in. From time to time you will be asked for your PIN, but this time the PIN is cached! :)

Different keys for different yubikeys

It’s possible to create the GPG key with the gpg-tools on your local system and copy the same key to different yubikeys (via keytocard) to have backup yubikeys.

Even if it’s not possible to extract GPG keys from HW SCs like yubikeys, you will block your yubikey when you lost it. And if you have the same GPG key on all of your yubikeys, you will also lock out your backup yubikey.

Most of the applications outside also support multiple ssh keys and gpg keys, e.g.:

  • openssh
  • github.com
  • gitlab

So to have backup yubikeys, you can just add multiple ssh or gpg keys to the services and repeat the steps above for all of your yubikeys.

Why FIDO2 isn’t an option

With the latest versions of openssh it’s possible to generate FIDO2 based ssh keys. They are already pretty good adopted and work e.g. for ubuntu 20.04 or later.

The problem with such keys is the way you have to authenticate - each time you connect via ssh to a server, you have to confirm your presence, means you have to touch the yubikey. Every time you connect.

Example: If you want to connect to three different servers, you have to touch your yubikey three times. If you connect to them via a jumphost, you have to touch your yubikey six times! There is no caching available.

I also tried to create ssh keys that don’t require the presence confirmation, but these keys are not accepted by a default ssh server configuration nor for authentication to github.com.

> ssh-keygen -t ed25519-sk -f ~/.ssh/notouch -C 'Mathias Nohr <mathias.nohr@ioki.com>' -O no-touch-required

You can also add an option to require verification, but that just asks to insert a PIN in addition (which is also not cacheable)

> ssh-keygen -t ed25519-sk -f ~/.ssh/notouch -C 'Mathias Nohr <mathias.nohr@ioki.com>' -O no-touch-required -O verify-required

I tried the same with ssh keys generated directly on the yubikey - it’s exactly the same behaviour as described for the standalone keys:

> ssh-keygen -t ed25519-sk -C 'Mathias Nohr <mathias.nohr@ioki.com>' -O resident -O application=ssh:ioki

So I didn’t find a way to have some kind of caching for the presence detection, which makes the whole usage a bit of frustrating.