YubiKey as an SSH Certificate Authority
This is a guide to setting up a YubiKey for use as an offline SSH certificate authority.
This assumes a brand new YubiKey with no prior configuration on it, to be used solely as a CA.
Typically a CA should be on a secured, isolated machine. Using a dedicated YubiKey means you can isolate your CA and keep it in a drawer so that it can’t be accessed. YubiKeys offer protections such as requiring a PIN and/or touching the key for PIV operations.
Using an SSH CA might sound like overkill for personal use, but it provides a lot of conveniences: you only need to configure remote machines once, with the CA public key. Any new key pairs you create don’t need distributing to those machines. As long as the client presents a certificate signed by the CA, the remote machine will accept any new keys. This removes the need for manually copying public keys or adding them to configuration management.
It also provides automatic verification of your servers: you don’t need to accept the server’s public key and add it to the
known_hosts file. As with the client keys, as long as the server presents a certificate signed by your CA, you can automatically verify it.
- YubiKey Manager (aka
- A password manager or other tool to generate random passwords and store your CA management key, PIN and PUK
- A PKCS#11 library for OpenSSH, one of:
- yubico-piv-tool, which provides
- OpenSC, which provides
- yubico-piv-tool, which provides
I’ll assume the use of
libykcs11.so as I found it more straightforward to use. OpenSC works, but depending how it’s installed it might not work out of the box with SSH (looking at you, Homebrew), which requires libraries to be in a location that’s hard-coded at compile-time.
(If you have multiple YubiKeys) Identify the YubiKey to use
List all connected YubiKeys
$ ykman list YubiKey 5 NFC [OTP+FIDO+CCID] Serial: xxxxx YubiKey 5 NFC [OTP+FIDO+CCID] Serial: yyyyyyyy
The first one is used as my current SSH key. The second is the new one to set up as the CA. We can check this by looking at the PIV application on each.
$ ykman --device yyyyyyyy piv info PIV version: 5.2.6 PIN tries remaining: 3 CHUID: No data available. CCC: No data available. $ ykman --device xxxxx piv info PIV version: 5.1.2 PIN tries remaining: 3 CHUID: No data available. CCC: No data available. Slot 9a: Algorithm: ECCP256 Subject DN: CN=SSH key Issuer DN: O=yubikey-agent,OU=v0.1.1 Serial: xxxxx Fingerprint: abcdef Not before: 2020-05-10 11:50:33 Not after: 2062-05-10 12:50:33
We can see the second one has PIV configured by yubikey-agent and the first has nothing configured in any slots.
Let’s set up the new YubiKey by changing the management key, PIN and PUK.
ykmancall as I had two keys plugged in. If you only have your CA key plugged in you can omit this.
Here we change the management key (hit
Enter at the first prompt to use the default key) and tell
ykman to generate a random key (
--generate), and require touching the YubiKey whenever the management key is used (
Be sure to save the generated management key somewhere safe! You will need it any time you want to sign.
$ ykman --device yyyyyyyy piv access change-management-key --touch --generate Enter your current management key [blank to use default key]: Generated management key: <some string of characters>
123456 is the default PIN and 12345678 is the default PUK. You can use
pwgen or a password manager to generate a new PIN and PUK. See Yubico’s PIN and Management Key documentation for PIN limitations.
$ pwgen -sy 8 1 $ ykman --device yyyyyyyy piv access change-pin -P 123456 Enter your new PIN: Repeat for confirmation: New PIN set. $ ykman --device yyyyyyyy piv access change-puk -p 12345678 Enter your new PUK: Repeat for confirmation: New PUK set.
We only need CCID mode on the CA YubiKey, OTP and FIDO won’t be used, so let’s disable them.
$ ykman --device yyyyyyyy mode CCID Set mode of YubiKey to CCID? [y/N]: y
ykman.driver_ccid.CCIDError: Failed to transmit with protocol T1. Card was reset.
Creating the CA
First we create a private key. We’ll use slot 9c.
ca-key.pub argument tells
ykman to write the public key for the generated key to the file
ca-key.pub. This will be used when we generate the certificate in the next step.
$ ykman --device yyyyyyyy piv keys generate 9c ca-key.pem Enter a management key [blank to use default key]: Touch your YubiKey...
Now generate a certificate whose subject is “SSH CA” and is valid for ~10 years, using the public key from the last step.
$ ykman --device yyyyyyyy piv certificates generate -s "SSH CA" -d 3650 9c ca-key.pem Enter PIN: Enter a management key [blank to use default key]: Touch your YubiKey...
At this point we have a private key and self-signed certificate on the YubiKey.
$ ykman --device yyyyyyyy piv info PIV version: 5.2.6 PIN tries remaining: 3 CHUID: aaaa CCC: bbbb Slot 9d: Algorithm: RSA2048 Subject DN: CN=SSH CA Issuer DN: CN=SSH CA Serial: abcde Fingerprint: abcde Not before: 2020-10-04 15:21:26 Not after: 2030-10-02 15:21:26
Let’s get the SSH public key for this. You’ll need the path to the shared library for the PKCS#11 library as mentioned in the prerequisites.
ssh-keygen -D /opt/local/lib/libykcs11.dylib
This will output two or three
ssh-rsa keys. Take the second one with comment “Public key for Digital Signature” and put it in a file called
ca.pub. This is the signing key used by
ssh-keygen to sign keys, and by
ssh to validate certificates.
Signing SSH public keys
The part you’ve probably been waiting for! This section is what you’ll do any time you create a new SSH key pair.
I keep all public keys and certificates stored on the computer I primarily use my CA key with, mostly for convenience. I structure it as:
/Users/jamesog/ssh-ca ├── host └── user
I copy all host keys into
~/ssh-ca/host/ and user keys to
~/ssh-ca/user/. The signed certificates then live alongside the public key.
When signing keys you have to provide a “key ID”. For user keys, this is what’s logged on the server when you authenticate with a certificate. I use my email address for the ID.
ssh-keygen(1)for information on how to use that.
The certificate needs an ID (
-I), which is usually the machine’s hostname, and a principal (
-n) of the machine’s hostname (usually a DNS name).
ssh-keygen -s ca.pub -D /opt/local/lib/libykcs11.dylib -I <hostname> -h -n <hostname> host/somehost_ed_25519.pub
If the machine can be accessed using several hostnames, add each of them to the
-n flag separated by commas. If you ever need to access the machine by IP address instead of a hostname, add that to the principals too, otherwise it won’t verify.
The certificate needs an ID (
-I) which should be something to identify you — I use my email address — and your remote username as a principal (
$ ssh-keygen -s ca.pub -D /opt/local/lib/libykcs11.dylib -I <email address> -n jamesog user/id_ed25519.pub Enter PIN for 'YubiKey PIV #yyyyyyyy': Signed user key user/id_ed25519-cert.pub: id "<email address>" serial 0 valid forever
You can inspect the generated certificate with
$ ssh-keygen -L -f user/id_ed25519-cert.pub user/id_ed25519-cert.pub: Type: email@example.com user certificate Public key: ED25519-CERT SHA256:xxx Signing CA: RSA SHA256:xxx (using rsa-sha2-512) Key ID: "<email address>" Serial: 0 Valid: forever Principals: jamesog Critical Options: (none) Extensions: permit-X11-forwarding permit-agent-forwarding permit-port-forwarding permit-pty permit-user-rc
Signing another YubiKey
If you use another YubiKey SSH private key (for example using yubikey-agent), you can also sign the public key for this.
Export the SSH public key from the YubiKey:
ssh-keygen -D /opt/local/lib/libykcs11.dylib > user/yubikey.pub
And then sign the public key as above.
Setting up servers
Presenting the signed host certificate
Copy the signed host certificate to each server, e.g.
host/somehost-cert.pub, and place it alongside the host’s private keys, usually in
/etc/ssh, ensuring the name matches the private key name. Add the following directive to
sshd after adding this directive (or wait until you’ve configured client keys, below).
Authenticating client certificates
sshd can be configured with the CA certificate so that any user presenting a certificate signed by this CA can be authenticated with no other configuration, assuming the certificate has a valid principal for the user they’re trying to authenticate as.
ca.pub from earlier and install it on each server, e.g. at
/etc/ssh/ca.pub and add the following directive to
sshd after adding this directive. See
sshd_config(5) for more information, and also the
AuthorizedPrincipalsFile in case you want to do more advanced things with user principals.
Alternatively, each user can add the CA to their
cert-authority,principals="jamesog" ssh-rsa ...
principals= here ensures only certificates with the correct usernames can authenticate.
Setting up clients to use the certificate
~/.ssh/config use the
CertificateFile directive to use the certificate. If your key is loaded in
ssh will match the certificate to the private key, otherwise add
IdentityFile to your SSH config too.
If your private key is not a file on disk (i.e. you’re using a YubiKey or a Mac’s Secure Enclave) you should set the
IdentityFile directive to be the path to your public key. It’s nonsensical, I know, but somehow it works.
Setting up clients to authenticate server host keys
~/.ssh/known_hosts the DNS name(s) or a wildcard of remote servers that have the signed certificate, followed by the contents of
@cert-authority *.example.com ssh-rsa ... SSH CA
With this you no longer need TOFU or to accept individual host keys.
Secure hardware-backed private keys
Using a regular key pair generated with
ssh-keygen is just fine, however for added security you can make the private key completely inaccessible by using a hardware device.
As we’ve already discused, the YubiKey is great for this. yubikey-agent makes setting up the key and your computer very straightforward.
The bonus of a YubiKey is it is transportable and can be used across computers.
Mac Secure Enclave
Every modern (since 2016) Mac comes with a built-in security device called a Secure Enclave. This is where things like your Touch ID fingerprints are safely stored.
Secretive uses the Secure Enclave to generate and store SSH keys. The private key can never be extracted so you can’t transfer the key to aonther Mac.
Secretive has a couple of great usability features: you can get notifications whenever the key is used, in case some nefarious process is using your key in the background, and you can require biometrics (Touch ID or Apple Watch) every time the key is used for maximum security. This is similar to requiring a tap of the YubiKey when the key is used, but only you can authenticate it.