How to Generate and Configure SSH Certificate-Based Authentication

Apr 26, 2022 by 

Honda McLaren

SSH certificates

The SSH protocol offers multiple authentication options: passwords, public keys and certificates. Certificate-based authentication is the most secure of them all, but historically, it has been the most complicated to set up. This tutorial guides you through simple steps to configure certificate-based authentication for an OpenSSH server.

First, let's consider the differences between certificates and keys.

SSH key vs cert

As you can see, an SSH key is a binary proposition. Either you have it or you don't, and a server will grant access to whomever happens to be in possession of the key, very similar to a physical door key. There is very little data about the owner of the key. Even the comment field is not mandatory, and a client does not pass it to a server.

A certificate, on the other hand, contains much more data, and therefore:

  • Certificates are tied to user identity.
  • Certificates automatically expire.
  • Certificates can contain SSH restrictions, e.g. forbidding PTY allocation or port forwarding.
  • SSH certificates can be synchronized with Kubernetes certificates.
  • Certificates include metadata. This enables role-based access control.
  • Certificates solve TOFU (trust on first use) problems. The user and host certificates signed by the same CA establish trust and eliminate the need for TOFU.

How SSH certificate-based authentication works

SSH certificates are built using SSH public keys and don't offer anything extra from a cryptography engineering standpoint. First, you will need to set up a certificate authority (CA). The same ssh-keygen command can be used to create a CA. In OpenSSH, the CA certificate is just public and private key pairs with additional identity and constraints data. The private key of the CA is used to sign user and host (SSH server) certificates. Once the keys are signed, they are distributed to users and hosts, respectively. The public key of the CA is copied to the SSH server, which is used to verify the user's certificate.

For user authentication, the SSH client presents the user certificate (signed certificate by CA) to the SSH server in each new SSH connection. The SSH server validates the certificate by checking it against the CA's public key. Along with the signature validation, the server will also check if the user certificate is not expired and if it violates security constraints. Access is granted upon successful validation of the certificate.

Generating Certificate Authority (CA)

Generate SSH CA keypair using the ssh-keygen command:

$ ssh-keygen -t rsa -b 4096 -f host_ca -C host_ca
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in host_ca.
Your public key has been saved in host_ca.pub.
The key fingerprint is:
SHA256:tltbnMalWg+skhm+VlGLd2xHiVPozyuOPl34WypdEO0 host_ca
The key's randomart image is:
+---[RSA 4096]----+
|              +o.|
|            .+..o|
|           o.o.+ |
|          o o.= E|
|        S  o o=o |
|       ....+ = +.|
|       ..=. %.o.o|
|        *o Oo=.+.|
|       .oo=ooo+..|
+----[SHA256]-----+

$ ls -l
total 8
-rw-------. 1 honda honda 3381 Mar 19 14:30 host_ca
-rw-r--r--. 1 honda honda  737 Mar 19 14:30 host_ca.pub

The host_ca file is the host CA's private key and should be protected. Don't give it out to anyone, don't copy it anywhere and make sure that as few people have access to it as possible. Ideally, it should live on a machine which doesn't allow direct access, and all certificates should be issued by an automated process.

In addition, it's best practice to generate and use two separate CAs — one for signing host certificates, one for signing user certificates. This is because you don't want the same processes that add hosts to your fleet to also be able to add users (and vice versa). Using separate CAs also means that in the event of a private key being compromised, you only need to reissue the certificates for either your hosts or your users, not both at once.

As such, we'll also generate a user_ca with this command:

$ ssh-keygen -t rsa -b 4096 -f user_ca -C user_ca

The user_ca file is the user CA's private key and should also be protected in the same way as the host CA's private key.

Issuing host certificates (to authenticate hosts to users)

Generate new host key and sign it with the CA key:

$ ssh-keygen -f ssh_host_rsa_key -N '' -b 4096 -t rsa

$ ls -l
-rw------- 1 ec2-user ec2-user 3247 Mar 17 14:49 ssh_host_rsa_key
-rw-r--r-- 1 ec2-user ec2-user  764 Mar 17 14:49 ssh_host_rsa_key.pub

$ ssh-keygen -s host_ca -I host.example.com -h -n host.example.com -V +52w ssh_host_rsa_key.pub
Enter passphrase: # the passphrase used for the host CA
Signed host key ssh_host_rsa_key-cert.pub: id "host.example.com" serial 0 for host.example.com valid from 2020-03-16T15:00:00 to 2021-03-15T15:01:37

$ ls -l
-rw------- 1 ec2-user ec2-user 3247 Mar 17 14:49 ssh_host_rsa_key
-rw-r--r-- 1 ec2-user ec2-user 2369 Mar 17 14:50 ssh_host_rsa_key-cert.pub
-rw-r--r-- 1 ec2-user ec2-user  764 Mar 17 14:49 ssh_host_rsa_key.pub

ssh_host_rsa_key-cert.pub contains the signed host certificate.

Here's an explanation of the flags used:

  • -s host_ca: specifies the filename of the CA private key that should be used for signing.
  • -I host.example.com: the certificate's identity — an alphanumeric string that will identify the server. I recommend using the server's hostname. This value can also be used to revoke a certificate in future if needed.
  • -h: specifies that this certificate will be a host certificate rather than a user certificate.
  • -n host.example.com: specifies a comma-separated list of principals that the certificate will be valid for authenticating — for host certificates, this is the hostname used to connect to the server. If you have DNS set up, you should use the server's FQDN (for example host.example.com) here. If not, use the hostname that you will be using in an ~/.ssh/config file to connect to the server.
  • -V +52w: specifies the validity period of the certificate, in this case 52 weeks (one year). Certificates are valid forever by default — expiry periods for host certificates are highly recommended to encourage the adoption of a process for rotating and replacing certificates when needed.

Configuring SSH to use host certificates

First, copy the three files you just generated to the server, store them under the /etc/ssh directory, set the permissions to match the other files there, then add this line to your/etc/ssh/sshd_config file:

HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

Once this is done, restart sshd with systemctl restart sshd.

Your server is now configured to present a certificate to anyone who connects. For your local ssh client to make use of this (and automatically trust the host based on the certificate's identity), you will also need to add the CA's public key to your known_hosts file.

You can do this by taking the contents of the host_ca.pub file, adding @cert-authority *.example.com to the beginning, then appending the contents to ~/.ssh/known_hosts:

@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDwiOso0Q4W+KKQ4OrZZ1o1X7g3yWcmAJtySILZSwo1GXBKgurV4jmmBN5RsHetl98QiJq64e8oKX1vGR251afalWu0w/iW9jL0isZrPrmDg/p6Cb6yKnreFEaDFocDhoiIcbUiImIWcp9PJXFOK1Lu8afdeKWJA2f6cC4lnAEq4sA/Phg4xfKMQZUFG5sQ/Gj1StjIXi2RYCQBHFDzzNm0Q5uB4hUsAYNqbnaiTI/pRtuknsgl97xK9P+rQiNfBfPQhsGeyJzT6Tup/KKlxarjkMOlFX2MUMaAj/cDrBSzvSrfOwzkqyzYGHzQhST/lWQZr4OddRszGPO4W5bRQzddUG8iC7M6U4llUxrb/H5QOkVyvnx4Dw76MA97tiZItSGzRPblU4S6HMmCVpZTwva4LLmMEEIk1lW5HcbB6AWAc0dFE0KBuusgJp9MlFkt7mZkSqnim8wdQApal+E3p13d0QZSH3b6eB3cbBcbpNmYqnmBFrNSKkEpQ8OwBnFvjjdYB7AXqQqrcqHUqfwkX8B27chDn2dwyWb3AdPMg1+j3wtVrwVqO9caeeQ1310CNHIFhIRTqnp2ECFGCCy+EDSFNZM+JStQoNO5rMOvZmecbp35XH/UJ5IHOkh9wE5TBYIeFRUYoc2jHNAuP2FM4LbEagGtP8L5gSCTXNRM1EX2gQ== host_ca

The value *.example.com is a pattern match, indicating that this certificate should be trusted for identifying any host which you connect to that has a domain of *.example.com — such as host.example.com above. This is a comma-separated list of applicable hostnames for the certificate, so if you're using IP addresses or SSH config entries here, you can change this to something like host1,host2,host3 or 1.2.3.4,1.2.3.5 as appropriate.

Once this is configured, remove any old host key entries for host.example.com in your ~/.ssh/known_hosts file, and start an ssh connection. You should be connected straight to the host without needing to trust the host key. You can check that the certificate is being presented correctly with a command like this:

$ ssh -vv host.example.com 2>&1 | grep "Server host certificate"
debug1: Server host certificate: [email protected] SHA256:dWi6L8k3Jvf7NAtyzd9LmFuEkygWR69tZC1NaZJ3iF4, serial 0 ID "host.example.com" CA ssh-rsa SHA256:8gVhYAAW9r2BWBwh7uXsx2yHSCjY5OPo/X3erqQi6jg valid from 2020-03-17T11:49:00 to 2021-03-16T11:50:21
debug2: Server host certificate hostname: host.example.com

At this point, you could continue by issuing host certificates for all hosts in your estate using your host CA. The benefit of doing this is twofold: you no longer need to rely on the insecure trust on first use (TOFU) model for new hosts, and if you ever redeploy a server and therefore change the host key for a certain hostname, your new host could automatically present a signed host certificate and avoid the dreaded WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! message.

Issuing user certificates (to authenticate users to hosts)

Generate user keypair and sign it with our user CA. It's up to you whether you use a passphrase or not.

$ ssh-keygen -f user-key -b 4096 -t rsa

$ ls -l
-rw-r--r--. 1 honda honda  737 Mar 19 16:33 user-key.pub
-rw-------. 1 honda honda 3369 Mar 19 16:33 user-key

$ ssh-keygen -s user_ca -I honda@goteleport.com -n ec2-user,honda -V +1d user-key.pub
Enter passphrase: # the passphrase used for the user CA
Signed user key user-key-cert.pub: id "[email protected]" serial 0 for ec2-user,honda valid from 2020-03-19T16:33:00 to 2020-03-20T16:34:54

$ ls -l
-rw-------. 1 honda honda 3369 Mar 19 16:33 user-key
-rw-r--r--. 1 honda honda 2534 Mar 19 16:34 user-key-cert.pub
-rw-r--r--. 1 honda honda  737 Mar 19 16:33 user-key.pub

user-key-cert.pub contains the signed user certificate. You'll need both this and the private key (user-key) for logging in.

Here's an explanation of the flags used:

  • -s user_ca: specifies the CA private key that should be used for signing
  • -I [email protected]: the certificate's identity, an alphanumeric string that will be visible in SSH logs when the user certificate is presented. I recommend using the email address or internal username of the user that the certificate is for — something which will allow you to uniquely identify a user. This value can also be used to revoke a certificate in future if needed.
  • -n ec2-user,honda: specifies a comma-separated list of principals that the certificate will be valid for authenticating, i.e. the *nix users which this certificate should be allowed to log in as. In our example, we're giving this certificate access to both ec2-user and honda.
  • -V +1d: specifies the validity period of the certificate; in this case +1d means 1 day. Certificates are valid forever by default, so using an expiry period is a good way to limit access appropriately and ensure that certificates can't be used for access perpetually.

If you need to see the options that a given certificate was signed with, you can use ssh-keygen -L:

$ ssh-keygen -L -f user-key-cert.pub
user-key-cert.pub:
        Type: [email protected] user certificate
        Public key: RSA-CERT SHA256:egWNu5cUZaqwm76zoyTtktac2jxKktj30Oi/ydrOqZ8
        Signing CA: RSA SHA256:tltbnMalWg+skhm+VlGLd2xHiVPozyuOPl34WypdEO0 (using ssh-rsa)
        Key ID: "[email protected]"
        Serial: 0
        Valid: from 2020-03-19T16:33:00 to 2020-03-20T16:34:54
        Principals:
                ec2-user
                honda
        Critical Options: (none)
        Extensions:
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

Configuring SSH for user certificate authentication

Once you've signed a certificate, you also need to tell the server that it should trust certificates signed by the user CA. To do this, copy the user_ca.pub file to the server and store it under /etc/ssh, fix the permissions to match the other public key files in the directory, then add this line to /etc/ssh/sshd_config:

TrustedUserCAKeys /etc/ssh/user_ca.pub

Once this is done, restart sshd with systemctl restart sshd.

Your server is now configured to trust anyone who presents a certificate issued by your user CA when they connect. If you have a certificate in the same directory as your private key (specified with the -i flag, for example ssh -i /home/honda/user-key [email protected]), it will automatically be used when connecting to servers.

Conclusion

In this tutorial, we showed you how to generate and configure OpenSSH hosts and clients with certificate-based authentication. Certificate-based authentication is the most secure form of authentication for SSH. In fact, at Teleport, we recommend certificates as a passwordless authentication method for all infrastructure access requirements, including SSH, RDP, Kubernetes clusters, web applications, and database access. Teleport automatically manages the CA for user and host certificate issuance. Learn how Teleport works, or try Teleport now.

Try Teleport today

In the cloud, self-hosted, or open source
Get StartedView developer docs