SSH Certificates; Understanding and Using Them
Table of contents
- The two types of certificates
- The fingerprint problem
- Create your own SSH certificate authority
- Host certificate
- User certificate
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.
- Generate new host keys for our ssh server
- Sign the host key with our CA hosts private key
- Migrate hosts keys to the server
- Configure our server with the host certificate
- 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:
Type | Example value |
---|---|
Fully qualified domain name | server-name.example.com |
Hostname | server-name |
IPv4 address | 192.0.2.1 |
IPv6 address | 2001: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:
- Generate new users keys for our users (clients)
- Sign the user key with our CA user private key
- Move the CA Users public key onto the server
- 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.