Saturday, November 28, 2020

My notes for setting up a new Yubikey 5

A hardware crypto token such as Yubikey is not meant to be used forever. A new release would address old vulnerabilities and add new crypto support. The firmware is not upgradable (for security reasons), so new features and fixing vulnerabilities always require the key to be replaced. It is already a toil for anyone to rotate their hardware token with all the servers they might have used it with for authentication. If they had used a hardware token to encrypt data for long term archival, the hardware token could not be disposed without re-encrypting all data. Cloud service providers can afford to engineer a solution that uses static derived keys encrypted by a master key that could be more easily rotated, but I'm not aware of off-the-shelf solutions for individuals. As such, I do not recommend using hardware tokens such as Yubikey to encrypt archived data.

Even though I don't use Yubikey for encryption, there is still considerable effort to set it up for authentication which is my primary use for it. In this article, I will explain the feature landscape of different crypto key types, followed by a step by step instruction how I configured my key. Hopefully it helps you decide if this is the right time to start using Yubikey or upgrade your existing key, and if it is the right time, how to do it.


My own motivation is to replace my RSA key with ECC key because ECC 256-bit key is shorter and has roughly the same strength as RSA 3072 bit key (here is a rationale that I endorse). However, the NIST P-curves are not safe. There are some criteria for establishing the safety of various ECC curves, courtesy to the author of curve25519.

First of all, a cautionary tale: don't order Yubikey 5 from Amazon. I made the mistake of ordering one there in April 2020, and I got a Yubikey with an old 5.1.2 firmware (it wasn't discounted). The new firmware 5.2.3 was released in August 2019, a handsome 8 months ago. Amazon also refused to post my warning about the old firmware as a review because they said it pertains only to a specific supplier, not the product.

Starting from firmware 5.2.3, Yubikey implements OpenPGP specification 3.4 which added support for ed25519 keys, among other ECC types. However, NIST P-curves (known as "ecdsa" in SSH) and ed25519 are the only new ones that work with SSH. The new firmware also added OpenPGP attestation which certifies that a key is generated on chip, and whether touch is required to use the key (attestation was first introduced in U2F).

The old 5.1.2 firmware lacked ed25519 support. It was to replace my Yubikey 4 which generated weak RSA keys. Before that, I had a Yubikey NEO-n which lacked touch support for OpenPGP even though U2F always required touch. Yubico is already working on implementing biometric touch for the next generation Yubikey.

Also, the software tools provided by Yubico changed over time. The previous generation tools Yubikey NEO Manager and Yubikey Personalization Tool have been deprecated and replaced with Yubikey Manager. It is worth noting that the GUI version only lets you configure FIDO2 and PIV, not OpenPGP. However, a CLI version is bundled with the GUI version, and the CLI has the OpenPGP configuration options. At one point, it was only possible to configure OpenPGP touch policy with a third-party script yubitouch, but it should not be used anymore.

That said, the entire ecosystem around using OpenPGP hardware key to login over SSH is pretty fragile in my opinion. It involved several parties to be perfectly aligned: Achim Pietig who rectifies the OpenPGP specification, the GnuPG team which is mostly just Werner Koch and sometimes NIIBE Yutaka (who designed the Gniibe Gnuk key), Yubico and other hardware vendors, the OpenSSH developers who decide which new key types to support, and the various distributions that decide which versions of GnuPG and OpenSSH are available to the end user. Since OpenSSH 8.1 added FIDO/U2F support, once this is rolled out to the various OS distributions, OpenPGP should not be used with SSH anymore. OpenSSH 8.4 was only accepted into Debian Buster backports two days ago, and before that Buster only had OpenSSH 7.9. Mac OS X Big Sur finally has OpenSSH 8.1. Both client and server need to support U2F over SSH in order to work.

Step by Step Guide

Without further ado, here is what I did to set up my Yubikey 5 on Mac OS X.

First, make an alias for the ykman command. The slash escape is needed in order for the alias to observe the whitespace in the path.

$ alias ykman='/Applications/YubiKey\'

Disable Yubico OTP, otherwise accidental touches will cause the passcode to be typed as if someone was typing it on the keyboard. It appears as a string of random characters.

$ ykman config usb --disable OTP

Initialize the card first with gpg --card-edit before configuring OpenPGP touch policy. That's because generating new keys will cause the touch policy to be reset to OFF. Here is a condensed recipe followed by some explanations.

$ gpg --card-edit
> admin
> factory-reset # see NOTE(1)
> kdf-setup # see NOTE(2)
> passwd # see NOTE(3)
> key-attr # see NOTE(4)
   (2) ECC
   (1) Curve 25519
> generate # see NOTE(5)
Make off-card backup of encryption key? (Y/n) n
         0 = key does not expire
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y # see NOTE(6)

# Optionally:
> name
> url
> login


  1. factory-reset does not require PIN. It only resets the OpenPGP applet and will leave FIDO U2F intact. However, it does not reset the OpenPGP attestation touch policy. This only needs to be done when I f'd up and need to start over.
  2. kdf-setup prevents plain text PIN from being sent over USB.
  3. first change Admin PIN (3), then change PIN (1). Note that PIN does not have to be numeric. I have my Admin PIN set to my login passphrase. Many key configuring operations require both PIN and Admin PIN, and Admin PIN can be used to override PIN, so both must be set for security reasons.
  4. key-attr will repeatedly prompt for signature, encryption, and authentication keys. It will ask for Admin PIN each time. Again, only Curve 25519 is supported by SSH. Only the authentication key is used by SSH, so signature and encryption keys could be other types.
    • Since SSH does not support secp256k1, using it will cause "Agent refused operation" error with SSH.
    • The key types that SSH does not support are hidden unless the session was started in expert mode using gpg --card-edit --expert.
  5. generate will first ask for PIN, then ask for user information, then ask for Admin PIN prior to key generation. Again, generation of new keys will reset the touch policy.
    • Do not make off-card backup. The key will be generated off-card first and then loaded back to Yubikey, which means OpenPGP attestation won't work.
  6. There seems to be no point in setting a key expiration here. The expiration can be changed using gpg --edit-key (note: this is a different session mode than --card-edit) after the key is generated.

Now we need to configure the OpenPGP touch policy. There are a few oddities to be aware of before proceeding.

  • Firstly, after running gpg --card-edit, the ykman command seems to hang, but unplugging and replugging the Yubikey makes it work again.
  • Secondly, ykman seems to have trouble disabling terminal echo on Mac OS X ("Can not control echo on the terminal" with a GetPassWarning), so the PIN and Admin PIN will be echoed to the screen. It is using the Python getpass module, which works when I run it in Python directly. I think ykman wasn't able to import termois because the code signing prohibited it from loading dynamic libraries.
  • Lastly, file output could not be written, possibly due to Catalina's enhanced security policy. Writing to standard output works, but beware that prompts are also written to stdout. It means that output redirected to a file would be polluted with interactive messages. They should read my article To err is human; to out, pipeline.

Now, use ykman to set touch policy. It will ask for the Admin PIN.

$ ykman openpgp set-touch SIG FIXED
$ ykman openpgp set-touch ENC FIXED
$ ykman openpgp set-touch AUT FIXED
$ ykman openpgp set-touch ATT FIXED

Note: set-touch ATT FIXED only needs to be done once per Yubikey ever. This is not affected by factory-reset. Trying to set it again will result in a harmless error.

Finally, I am generating the OpenPGP attestation certificates but haven't found a use for them. The ones generated by Yubikey contains an X.509 v3 extension also indicates what the touch policy is, so an external trust engine may use it to decide whether to give an attested key more trust. Note that unless the touch policy is FIXED or CACHED-FIXED, it can be changed to OFF later, so it makes no sense to attest any other touch policies.

These will ask for the regular PIN and write the attestation certificate to standard output.

$ ykman openpgp attest SIG -
$ ykman openpgp attest ENC -
$ ykman openpgp attest AUT -

After each command, copy and paste the X.509 certificate to its own file. Do not attest ATT which is the attestation key. I suspect it will destroy the preloaded attestation key but haven't tried it. It will warn you that it will overwrite an existing attestation certificate. Yubikey comes with an attestation key preloaded which certifies that the OpenPGP keys are generated by a Yubico manufactured hardware. If the attestation key is overwritten, it could no longer be recovered even after a factory reset.

I use GPG Tools on Mac to add a photo to the key which has a nicer interface, but gpg --edit-key should work. Although the recommended photo size is 120x150, I find that 240x240 produces the best result. It will be scaled down to 120x120 by GnuPG. After this, the public key is ready to be exported.

Old public keys need to be revoked and exported again. The newly exported key will then contain the revocation certificate.

The new key should show up in ssh-add -L without further action. If not, make sure SSH_AUTH_SOCK points to gpg-agent, not launchd. Usually starting a new shell after running gpg --card-edit will do the trick. The output of ssh-add -L contains the public key (identifiable by the cardno which is the Yubikey's serial number) that needs to be added to $HOME/.ssh/authorized_keys on the SSH server.

1 comment:

sporadic said...

Cheers for this.

For those reading who are struggling to get a working gpg agent with the latest version of GPGtools note that the method of operation for the agent changed in gnupg 2.1 and above:

I use zsh as my shell, which is the default in newer macOS installations so I did the following steps. Note the explicit launch in my .zshrc file as autostart does not work with enable-ssh-support option active as per the documentation.

Add this to ~/.gnupg/gpg-agent.conf

Add this to ~/.gnupg/gpg.conf
default-key 12345678 #replace with your keyID

Add this to ~/.zshenv to point the environment variable to the socket

Add this to ~/.zshrc
gpgconf --launch gpg-agent