Saturday, November 28, 2020

My notes for setting up a new Yubikey 5

Once in a blue moon, a new Yubikey is released, addressing old vulnerabilities, so I have to go through the pain to enroll my new key and deprecate my old key. Since this happens somewhat rarely, it tends to take me a considerable amount of time to figure out again what needs to be done. Besides, there is always some small differences and new features. To help me remember things for my future self, I'm writing down my notes for this key.

First of all, 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 at full price (and Amazon refused to post my review which warned other customers of the old firmware, what a shocker). Starting from firmware 5.2.3 which was released a year ago in August 2019, Yubikey implements OpenPGP specification 3.4 which added support for ed25519 keys, among other ECC types. However, NIST P-curves (as "ecdsa" in SSH) and ed25519 are the only new ones that work with SSH, besides RSA which shouldn't be used anymore. 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 is useless for me for the lack of ed25519 support. It is 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. This is just to illustrate that Yubikey isn't supposed to be used forever, so I don't recommend encrypting data that needs long term archival using the key generated on chip. Using it for signing and authentication certainly works. The firmware is not upgradable (for security reasons), so new features and fixing vulnerabilities always require the key to be replaced. 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, but is useless for 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.

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\ Manager.app/Contents/MacOS/ykman'

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

Notes:

  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. 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 prompts are also written to stdout which is utterly ridiculous. 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.

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

After each command, copy the X.509 certificate to its own file. Do not attest ATT which is the attestation key. I believe it will destroy the preloaded attestation key but haven't had the gut to try 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.