This project provides a set of bash scripts for managing SSH CA signing keys
using the PIV application of a Yubikey security device. The tools allow you to
do the following:
-
Create user and host CA keys and import them into one or more Yubikeys.
-
Change the PIN, PUK and Management codes for the Yubikey to randomly generated values
-
Generate user and host SSH certificates by signing SSH public keys using the CA key stored in the Yubikey.
The diceware application can be used generate strong passphrases for your SSH
CA keys:
$ sudo apt install diceware
$ diceware -d ' '
This is only one of many ways to generate strong passphrases.
First, clone this repo to the server system:
$ git clone https://github.com/openavr/sshca-yk.git
$ cd sshca-yk
To generate the User and Host SSH CA keys and load them into a Yubikey:
Usage: ./ca-create.sh <org-domain> <org-common-name>
NOTE: The keys could also be generated inside the Yubikey (which is much more secure since they can not be extracted), but leaves you with no backup should the Yubikey fail or get lost. As such, the keys are generated externally and then imported into the Yubikey. This allows the same keys to be imported into multiple Yubikeys.
For example, for the example.com domain, with a common name of Example Inc
(this will prompt you to enter the passphrase for the keys):
$ ./ca-create.sh example.com 'Example Inc'
The generated private keys are now on-disk and in the Yubikey. The on-disk copies must be protected and not shared.
Verify the contents of the Yubikey:
$ ykman piv info
...
Slot 84 (RETIRED3):
Private key type: ED25519
Public key type: ED25519
Subject DN: CN=Example Inc User SSH CA,O=example.com
Issuer DN: CN=Example Inc User SSH CA,O=example.com
Serial: 54:6d:90:a7:42:e7:56:d1:ae:92:e3:84:a4:82:33:48:a6:ec:16:82
Fingerprint: 7fa671502d10506db173093e36fae5fcf4a0062de946037903e564080e041867
Not before: 2026-08-09T16:10:31+00:00
Not after: 2026-11-07T16:10:31+00:00
...
Set the PIV access codes on the Yubikey so that they are not the default values:
$ ./ca-yk-set-access.sh
You can setup another Yubikey with the same keys by plugging in a different Yubikey and repeating the process:
$ ./ca-create.sh example.com 'Example Inc'
$ ./ca-yk-set-access.sh
Running the ca-create.sh multiple times will not generate new keys given the
same arguments, but note that it may fail if run again after the PIV access
codes have been changed on the Yubikey due to the defaults no longer being
usable, or the access codes were modified.
Generate an authorized_keys_ca file that has entries you can copy to your
~/.ssh/authorized_keys file on the server that you want to allow users to log
into using certificates:
$ ./generate-auth-keys.sh example.org
The generated authorized_keys_ca can be copied to any server as it does not
contain any sensitive material. You can also copy only the keys entries that
you desire from the authorized_keys_ca file instead of using theentire file.
Generate user certificates with either of the following:
$ ./ca-sign-user.sh example.com jsmith
$ ./ca-sign-user.sh example.com jsmith ~/.ssh/id_ed25519_jsmith
Generate host certificates with:
$ ./ca-sign-host.sh myserver example.com /etc/ssh/ssh_host_ed25519_key.pub
You can also make use of an SSH agent to ease signing operations. Loading the
pkcs#11 module into the agent with ssh-add:
$ ssh-add -s /usr/lib/x86_64-linux-gnu/libykcs11.so
That will ask for the PIN to unlock the keys in the Yubikey and subsequent invocations of the signing scripts will not ask you to re-enter the PIN.
Remove the keys from the agent with:
$ ssh-add -e /usr/lib/x86_64-linux-gnu/libykcs11.so
Copy the User SSH CA line from the generated authorized_keys_ca file into
your ~/.authorized_keys file on the server(s).
Alternatively, you could install the User SSH CA public key globally so that
it can be used for all user accounts by adding it to the TrustedUserCAKeys
entry in the /etc/ssh/sshd_config file. See the sshd_config(5) man page for
more details.
$ sudo cp keys-ca/example.com/id_ed25519_user_ca.pub /etc/ssh
$ echo "TrustedUserCAKeys /etc/ssh/id_ed25519_user_ca.pub" \
| sudo tee /etc/ssh/sshd_config.d/user_ca.conf
$ sudo systemctl restart sshd
The target user account name on the server should be at least one of the principals in the certificate file.
The server side user account is now setup to accept logins from clients using certificates signed by the SSH User CA.
Clone this repo to the client system:
$ git clone https://github.com/openavr/sshca-yk.git
$ cd sshca-yk
Plug the Yubikey into the client system.
Extract the CA public key from the Yubikey:
$ ykman piv info
$ ssh-keygen -D /usr/lib/x86_64-linux-gnu/libykcs11.so \
| grep 'Retired Key 3' > keys-ca/example.com/id_ed25519_user_ca.pub
Generate and sign a user ssh key pair and certificate:
$ ./ca-sign-user.sh example.com ${USER}
You should now be able to login to the server as ${USER}:
$ ssh -i ./keys-user/id_ed25519_${USER} <server>
You can also create the key and sign the cert in your ~/.ssh directory:
$ ./ca-sign-user.sh example.com ${USER} ~/.ssh/id_ed25519_${USER}
By default, openssh generates ed25519 keys. Yubikeys can be used to store
those keys and provide cryptographics operations on those keys using PKCS#11
API's.
Generating ed25519 keys with openssh is trivial, but importing them into a
Yubikey is not possible without converting the format of the keys from
openssh format into openssl PEM format. In theory, the conversion should be
a trivial openssh command, but alas, running that command failed with
openssh stating that the PEM format was not supported for the ed25519 key
type.
This has been frustrating for the past few years, but it is now possible on Linux with:
- Yubikey firmware >= 5.7.0
- OpenSSL >= 3.5.0
- OpenSSH >= 10.3p1
The following instructions show you how to do manually what the scripts in this project help to automate.
The format of the private generated by openssh can not be imported directly
into a Yubikey, it must be converted first.
You must use openssh >= 10.3p1 in order to be able to generate ed25519 keys
and convert them to PEM or PKCS8 format that can be loaded into Yubikey.
For Debian-13 (Trixie), you need to install the openssh tools using debian
backports. Setup debian backports with the instructions here:
Once backports is setup, install the newer version of openssh:
$ sudo apt update
$ sudo apt install \
openssh-client/trixie-backports \
openssh-server/trixie-backports \
openssh-sftp-server/trixie-backports
You only need openssh >= 10.3p1 if you are loading the CA or user keys into
the Yubikey, and do not need the newer version if you are only using the
Yubikey to sign user or host certificates.
Install the Yubikey tools:
$ sudo apt install yubikey-manager ykcs11 yubico-piv-tool
NOTE: The -N '' will generate a key without a passphrase which is
probably not what you want.
Generate a ed25519 key pair:
$ ssh-keygen -t ed25519 -N '' -f id_ed25519_foo
Convert the private key to a format that can be imported into a Yubikey:
$ cp id_ed25519_foo id_ed25519_foo.key
$ ssh-keygen -p -m PKCS8 -N '' -f id_ed25519_foo_key.pem
Import the private key into a Yubikey:
$ ykman piv keys import 85 id_ed25519_foo_key.pem
$ ykman piv info
To verify is a private key and a public match, use this:
# match if no differences and exit code of 0
$ diff <(ssh-keygen -l -v -f $sshkeyfile) <(ssh-keygen -l -v -f $sshpubfile)
To see if a public key matches the private key stored in a Yubikey slot, you first need to dump all of the public keys stored in the Yubikey. This can be done with the following command:
$ ssh-keygen -D /usr/lib/x86_64-linux-gnu/libykcs11.so
Then you can compare against another public key, but this is probably not needed since now you have the public keys of the private keys in the Yubikey and can just use them.
Run the following command to install basic tool dependencies needed for these scripts:
$ sudo apt install ykcs11 yubico-piv-tool yubikey-manager libyubikey-udev