Table of contents


The two types of certificates

The article will discuss and give practical examples of SSH certificates, highlighting their advantages over traditional public key authentication.

There are two type of SSH certificates:

  • Host certificates authenticate hosts to users.
  • User certificates authenticate users to hosts, just like your ssh key pair.

The fingerprint problem

You may be familiar with this SSH prompt when you try to connect to a server for the first time.

ssh -T git@github.com
The authenticity of host 'github.com (140.82.121.4)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

You don't actually know if the server you're trying to connect to is the real one.
Someone could be performing a man-in-the-middle (MITM) attack.

What you're supposed to do is connect to the server (if you own it) and compare the fingerprint with the host's public key.

Here's how to do it for an ED25519 key with the SHA256 hash.

ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub

In our case we do not own the server, Github provides their fingerprint here.


Create your own SSH CA

To use any of the two certificates types we need to generate our own certificate authority (CA).

This is literally the same as creating a key pair; you can execute these commands on any machine.

We want to create two CAs: one for host certificates and the other for user certificates.

You can name them whatever you want, just make sure to add passphrases to encrypt the private key.

ssh-keygen -t ed25519 -f hosts_ca -C "Hosts Certificate Authority created at $(date)"
ssh-keygen -t ed25519 -f users_ca -C "Users Certificate Authority created at $(date)"

Did you know that when you provide a comment, it's written in both the public and private key?
You can read the private key comment with the following: ssh-keygen -l -f hosts_ca.

ls -l
(truncated..)
-rw------- 1 user group hosts_ca
-rw-r--r-- 1 user group hosts_ca.pub
-rw------- 1 user group users_ca
-rw-r--r-- 1 user group users_ca.pub


Host certificates

Host certificates eliminate the need for fingerprint verification by authenticating the server directly to the user.

We will do this in five steps.

  1. Generate new host keys for our ssh server
  2. Sign the host key with our CA hosts private key
  3. Migrate hosts keys to the server
  4. Configure our server with the host certificate
  5. Configure our clients with the CA hosts public key

1. Generate host keys

Keep in mind that this set of keys will be on our server.

We create them without passphrases since sshd will not be able to decrypt the private key.

The name ssh_host_ed25519_key is intended; the daemon will be able to load the key without additionnal configuration.

ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N ""

2. Sign the host key

his step will sign our server host's public key with our hosts_ca private key.
This generate an third file ssh_host_ed25519_key-cert.pub.

Arguments:
-h When signing a key, create a host certificate instead of a user certificate.
-s ca_key Certify (sign) a public key using the specified CA key.
-I certificate_identity Specify the key identity when signing a public key.
-n principals (optional but recommanded) Specify one or more principals.

I use the hostname as the certificate_identity.
Since this value is logged quite often, consider changing it to something you can identify.

If you do not specify -n, the certificate will be valid for any destination.
In the host certificate case, it's the ssh user@**destination**.

-n is a comma-separated list of "principals", for hosts certificate you can have:

TypeExample value
Fully qualified domain nameserver-name.example.com
Hostnameserver-name
IPv4 address192.0.2.1
IPv6 address2001:db8::1

You cannot have any pattern matching either for the FQDN, or IP addresses.
![*.example.com, 192.0.2.*, 2001::db8::*]

ssh-keygen -h -s hosts_ca -I $(hostname) \
    -n "server-name.example.com,server-name,192.0.2.1,2001:db8::1" \
    ssh_host_ed25519_key.pub

Inspect the certificate:
ssh-keygen -L -f ssh_host_ed25519_key-cert.pub


3. Migrate host keys on the server

The ssh daemon needs all of those files in the /etc/ssh/ directory.

ls -l /etc/ssh/ssh_host_ed25519_key*
(truncated..)
-rw------- 1 root root /etc/ssh/ssh_host_ed25519_key
-rw-r--r-- 1 root root /etc/ssh/ssh_host_ed25519_key-cert.pub
-rw-r--r-- 1 root root /etc/ssh/ssh_host_ed25519_key.pub


4. Configure the ssh daemon to use the host certificate

HostCertificate: Specifies a file containing a public host certificate.

  • Add this line to your /etc/ssh/sshd_config:
    HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

Validate your sshd configuration: sshd -t -f /etc/ssh/sshd_config
Restart the ssh daemon: sudo systemctl restart sshd.service


5. Configure our client

Add the CA host public key hosts_ca.pub to ~/.ssh/known_hosts.

Each line in this file contain the following fields:
marker (optional), hostnames, keytype, base64-encoded key, comment (optional)
The fields are separated by spaces.

Marker: @cert-authority.

Hostnames: comma-separated list of patterns *.example.com,*.home.arpa,192.0.2.*,2001:db8::*.

Certificate authority public key (keytype + base64-encoded key + comment):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH40iWq8lkJQo64rJ/rVJiS4MUdw0hzHMxtOWBKYDD4/ Hosts Certificate Authority created at Fri Jul 12 07:46:55 CEST 2024

echo "@cert-authority \
*.example.com,*.home.arpa,192.0.2.*,2001:db8::* \
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH40iWq8lkJQo64rJ/rVJiS4MUdw0hzHMxtOWBKYDD4/ \
Hosts Certificate Authority created at Fri Jul 12 07:46:55 CEST 2024" >> ~/.ssh/known_hosts

Hostnames should include all servers that will be configured with an host certificate signed with the same CA private key.

Here, we use pattern matching, unlike when we signed the host certificate, because this line is a product of the Host CA certificate. It will be common for all clients that will connect to any server with an public key signed with this CA.

Any clients with this line in ~/.ssh/known_hosts will now be able to connect to the server without the fingerprint check.

If you have multiple servers repeat all of those step but make sure to sign with the same CA private key.

You can clean old fingerprint with this command:
ssh-keygen -R hostname


The scaling problem

Suppose you have 3 hosts and 3 users, with the traditional authorized_key file you will need to specify 3 keys for each hosts on each server. We have a scaling problem on our hands, SSH user certificate will help us to fix it.

Ultimately, any client with a signed SSH key will be able to connect to any server that has the CA user's public key in its sshd configuration.


Users Certificates

We will create our ssh user certificate in four steps:

  1. Generate new users keys for our users (clients)
  2. Sign the user key with our CA user private key
  3. Move the CA Users public key onto the server
  4. Configure our server with the CA user public key

1. Generate user keys (optional)

We will generate a new ssh keypair, you can skip this if you have existing keys.
The name id_ed25519 is intended; the SSH client will use this key automatically. You can chose to add an passphrases or not, I like to keep them encrypted.

ssh-keygen -t ed25519 -f id_ed25519 -C "SSH user keys created at $(date) for $USER"

2. Sign the user key

-s ca_key Certify (sign) a public key using the specified CA key.
-I certificate_identity Specify the key identity when signing a public key.
-n principals Specify one or more principals.

-I certificate_identity this value will be logged by the server, you can revoke the certificate with it.

-n is a comma-separated list of "principals", for users certificate you can only have usernames.
A minimum of one principal is required.

This defines the usernames you're allowed to connect to.

ssh-keygen -s emanon_user_ca -I $(whoami)@$(hostname) -n "user01,user02,root" id_ed25519.pub

3. Migrate CA public key on the server

The SSH daemon needs the CA public key users_ca.pub in the /etc/ssh/ directory.


4. Configure the ssh daemon to use the CA public key

Only one file is needed, the user CA public key.
Add this line to your /etc/ssh/sshd_config:
TrustedUserCAKeys /etc/ssh/users_ca.pub

Verify your sshd configuration sshd -t -f /etc/ssh/sshd_config.
Restart the ssh daemon sudo systemctl restart sshd.service.

The /home/$USER/.ssh/authorized_keys file is not needed anymore; now any user with their SSH keys signed by the users_ca will be able to connect to the server without additional configuration.