Update Nov 29, 2020: If your ssh client tells you check_host_cert: certificate signature algorithm ssh-rsa: signature algorithm not supported, see the end of this article.

For years I have been using SSH keys for password-less access to my computers at home. This method is superior to passwords, but has its flaws, especially in a datacenter with many actors. SSH certificates are a much better solution, but they are not that well documented.

So I decided to explore them. Due to sloppy reading and my ignorance in security matters, it took me a bit of trial and error before I successfully implemented SSH certificates. My mistakes were of two categories: Incorrect key signing, which leads to invalid certificates, and performing steps on the wrong computer. Since three computers (Client, Server, Trusted Server) are involved, it was easy to get confused.

Public key authentication without certificates

Let’s review how authentication works without certificates. A computer has one of two roles: Client or Server. A Client initiates an SSH connection to the Server. The sshd process runs on the Server.

First, the Server proves its identity by presenting its public host key. The Client may contain a copy of the Server’s key in its known_hosts file, in which case it trusts the Server and accepts authentication. If the Server’s key is not in known_hosts, the Client asks the user whether it should trust the Server:

The authenticity of host 'xxx' can't be established.
RSA key fingerprint is SHA256:kfcwi9X8T4nMRw1OM0xDXETGcqjU26/zbM+KqNB6CKw.
Are you sure you want to continue connecting (yes/no)?

Humans being fundamentally lazy, the usual user reaction is to type “yes” without checking the fingerprint. Unconditionally accepting the Server’s authentication request is one of the weaknesses of the non-certified public key protocol.

Next, the Client authenticates the user by sending the user’s public key to the Server. The Server accepts the key if it is contained in its authorized_keys file. This highlights two other weaknesses: How can users add their keys to the Server’s authorized_keys if they have no password access, and how will they manage the hundreds of copies of their key accumulating in the datacenter?

Another weakness: Keys never expire. Neither users nor hosts are forced to refresh their keys.

How certificates change the process

When certificates are used, user keys are not stored in the Server’s authorized_keys, and host keys are not stored in the Client’s known_hosts. So how are keys validated?

Here is where a third role comes in, the Trusted Server. Located on the Trusted Server is a private key named Certificate Authority (CA), which is used to digitally sign host and user keys. The result of the signing process is a so-called certificate. To authenticate, Client and Server now exchange certificates instead of keys. A certificate is then validated by the CA’s public key.

Certificates solve the above-mentioned weaknesses inherent in non-certified public key authentication: Certificates have an expiration date; there is no need to copy user keys to Servers and manage key copies, because user keys are not stored on Servers.

There is one inconvenience: Users can’t sign their public keys themselves, since they have no access to the private CA key. A process must be in place to get keys signed by a privileged operator or an application.

Note that SSH certification follows the OpenPGP standard, not SSL/TLS, and that the certificate format is OpenPGP, not X509.

Certificate authentication in practice

A few misunderstandings and my generally shallow knowledge of IT security caused me a bit of trial and error to get the process working. My steps for creating and using host and user certificates are below. To avoid the pitfalls that I stumbled into, pay attention to where the steps are executed (Client, Server, Trusted Server) and how certificates are signed.

On the Trusted Server, create a key pair for signing. I will call it CA:

ssh-keygen -f CA

This generates two files, private key CA and public key CA.pub. You can store them anywhere; I put then in /etc/ssh. Feel free to enter a passphrase.

Obviously, the private CA key must be kept secret. Consequently, non-privileged users are unable to sign their own keys.

Host certificates

On the Trusted Server, use private key CA to sign the public host key of each Server in the datacenter. The command is:

ssh-keygen -h -s CA -n LIST-OF-PRINCIPALS -I ID -V +52w KEYFILE.pub

The -h option is required for signing host keys. -s specifies the signature key (must be a private key). ID is meant to be a short, human-readable description of the certificate. -V is the expiration time; in the example, 52 weeks from now. KEYFILE.pub is the host key to be signed.

LIST-OF-PRINCIPALS is a comma-separated list of the domain names by which the Server is accessed, for example -n ceres,ceres.example.com. IP addresses don’t seem to work. I made my first mistake here: Instead of the respective domain names of each server, I entered the Trusted Server’s names in all certificates.

The result of this command is a new file named KEYFILE-cert.pub. This is the certificate the Server will use to prove its identity from now on. Store it under /etc/ssh on the Server.

Servers’ ssh daemons need to know that they have to present their host certificates to Clients. Add this clause to sshd_config on each Server:

HostCertificate /etc/ssh/KEYFILE-cert.pub

sshd must be restarted after changing its configuration.

When using non-certified public keys, Clients keep host keys that they trust in known_hosts. For example, a line like this:

ceres.example.com ssh-rsa AAAAB3Nza...

tells the Client that Server ceres.example.com should present the host key starting with ssh-rsa. If the Server presents a different host key, the Client will refuse to connect.

Instead of unsigned host keys, you want Clients to trust the CA from now on.

On all Clients, remove such host key lines from known_hosts. Don’t forget that there is a global /etc/ssh/ssh_known_hosts, and each user account may have its own ~/.ssh/known_hosts.

Instead of the host keys, include the CA in known_hosts on each Client:

@cert-authority LIST-OF-SERVERS ssh-rsa AAAAB3Nza...

LIST-OF-SERVERS is a comma-separated list of those Servers that signed their host key. Wildcards are permitted, for example *.example.com. The string starting with ssh-rsa is the public CA key.

Now, when establishing a connection to a Server that corresponds to the LIST-OF-SERVERS, this Client uses the public CA key to validate the host key.

On a Client that trusts the CA (step 3), ssh to one of the Servers with signed host key. If you see the familiar message

The authenticity of host 'odroid (' can't be established.
RSA key fingerprint is SHA256:kfcwi9X8T4nMRw1OM0xDXETGcqjU26/zbM+KqNB6CKw.
Are you sure you want to continue connecting (yes/no)?

the test failed. Host certificates don’t require the user to confirm that this is the correct Server. ssh -vvv helps to understand what’s wrong.

User certificates

Since normal users don’t have access to the private CA key, they can’t sign their keys themselves. An administrator or a special application are required. The command to sign a user key is

ssh-keygen -s CA -I ID -n USERNAME -V +52w KEYFILE.pub

Compared to signing host keys, there are two differences: There is no -h option, and more importantly the username is used as the sole principal. Again, the command generates a certificate named KEYFILE-cert.pub. It needs to be stored on the client, for example in ~/.ssh.

Here is where I made my second major mistake. At first, I used the computer’s domain name as principal. ssh-keygen happily obliges and creates an invalid user certificate. It took me a while to find out that the principal of a user certificate is the user, not the host. Makes sense, doesn’t it! Unfortunately, the only error message I got was “invalid certificate”.

To allow a Server to validate a user certificate, it must have the CA’s public key. If you followed the Prerequisite instructions, this key is /etc/ssh/CA.pub on the Trusted Server. Copy it to /etc/ssh on each Server.

On each Server, add this clause to sshd_config and restart sshd:

TrustedUserCAKeys /etc/ssh/CA.pub

Remove or rename authorized_keys on one of the Servers. From a Client that trusts the CA, ssh into the Server. Even though authorized_keys doesn’t exist anymore, you should be able to log on without password. The servers authentication log (probably /var/log/secure or /var/log/auth.log) should contain a message like this:

Accepted publickey for bbausch from port 39498 ssh2: RSA-CERT ID odin-bbausch (serial 0) CA RSA SHA256:BmLWnPGoPg2Edyk2NsZGQ62lm7Cae6j5bOj3uKvXzcs

Remarks and tips

While this article deals with a single CA, there is no problem having several CAs in an environment.

Update Nov 29, 2020: RSA key signing deprecated

OpenSSH 8.2, released in February 2020, deprecates key signing using the ssh-rsa algorithm (see release notes). Consequently, newer ssh clients may issue the error check_host_cert: certificate signature algorithm ssh-rsa: signature algorithm not supported.

The secure solution is to update OpenSSH on the servers and generate new certificates. If that is too much hassle right now, you can tell your ssh client to accept RSA signing by adding this line to the global /etc/ssh/ssh_config or your account’s $HOME/.ssh/config:

CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2–512,rsa-sha2–256,ssh-rsa

Or by adding it to the command line:

ssh -oCASignatureAlgorithms=ssh-rsa user@server

I discovered this solution at https://ibug.io/blog/2020/04/ssh-8.2-rsa-ca.

Further reading

To understand the theory of the process, I used https://smallstep.com/blog/use-ssh-certificates/. The practical steps are explained rather well by https://www.sweharris.org/post/2016-10-30-ssh-certs/. The man pages for ssh and ssh-keygen are also useful.

Open-Source and Cloud enthusiast. IT training for IT professionals. Special interests: OpenStack and Kubernetes.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store