1. Introduction

Traditional SSH authentication methods typically rely on passwords or static public keys stored in the authorized_keys file. While this works well for a handful of systems, it quickly becomes unmanageable at scale. Every user key must be manually distributed, and revoked keys stay valid until manually removed. Today, we are going to explore how SSH credentials can be centrally managed by the server using SSH certificates and a certificate authority (CA).

2. Generating CA Key Pair

Start by generating a CA key pair. Although it is possible to create one without a passphrase, this is strongly discouraged since the CA key represents the trust anchor for your entire SSH ecosystem.

root@ubuntu2404:~# ssh-keygen -t ed25519 -f /etc/ssh/cakey
Generating SSH CA Key Pair

The resulting /etc/ssh/cakey (private) and /etc/ssh/cakey.pub (public) files will be used for signing and verification respectively.

3. Signing SSH Server’s Host Public Keys

By default, the server’s host keys are generated during installation.

root@ubuntu2404:~# ls -l /etc/ssh/ssh_host_*.pub
-rw------- 1 root root  505 Mar 17  2025 /etc/ssh/ssh_host_ecdsa_key
-rw-r--r-- 1 root root  177 Mar 17  2025 /etc/ssh/ssh_host_ecdsa_key.pub
-rw------- 1 root root  411 Mar 17  2025 /etc/ssh/ssh_host_ed25519_key
-rw-r--r-- 1 root root   97 Mar 17  2025 /etc/ssh/ssh_host_ed25519_key.pub
-rw------- 1 root root 2602 Mar 17  2025 /etc/ssh/ssh_host_rsa_key
-rw-r--r-- 1 root root  569 Mar 17  2025 /etc/ssh/ssh_host_rsa_key.pub
Default SSH Host Key Pairs

We can now use the CA private key to sign each host key, embedding trust in the certificate itself.

root@ubuntu2404:~# ssh-keygen -s /etc/ssh/cakey -I sshserver.example.lan -h /etc/ssh/ssh_host_ecdsa_key.pub
root@ubuntu2404:~# ssh-keygen -s /etc/ssh/cakey -I sshserver.example.lan -h /etc/ssh/ssh_host_ed25519_key.pub
root@ubuntu2404:~# ssh-keygen -s /etc/ssh/cakey -I sshserver.example.lan -h /etc/ssh/ssh_host_rsa_key.pub
Signing SSH Host Public Keys

Once signed, verify the certificates. Each certificate should show your CA’s fingerprint under Signing CA, confirming that it’s now trusted under the CA. (See Listing 4, 5 and 6)

root@ubuntu2404:~# ssh-keygen -Lf /etc/ssh/ssh_host_ecdsa_key-cert.pub
/etc/ssh/ssh_host_ecdsa_key-cert.pub:
        Type: ecdsa-sha2-nistp256-cert-v01@openssh.com host certificate
        Public key: ECDSA-CERT SHA256:M5UVEWLHMsGajtBY8LpeqNHdgzpslsmGYUSNG5KjnA8
        Signing CA: ED25519 SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w (using ssh-ed25519)
        Key ID: "sshserver.example.lan"
        Serial: 0
        Valid: forever
        Principals: (none)
        Critical Options: (none)
        Extensions: (none)
Verifying /etc/ssh/ssh_host_ecdsa_key-cert.pub

root@ubuntu2404:~# ssh-keygen -Lf /etc/ssh/ssh_host_ed25519_key-cert.pub
/etc/ssh/ssh_host_ed25519_key-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com host certificate
        Public key: ED25519-CERT SHA256:gelQKbN1l2V4rkg2jxvEzi9kY4obPLY8SPe7KReiK3A
        Signing CA: ED25519 SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w (using ssh-ed25519)
        Key ID: "sshserver.example.lan"
        Serial: 0
        Valid: forever
        Principals: (none)
        Critical Options: (none)
        Extensions: (none)
Verifying /etc/ssh/ssh_host_ed25519_key-cert.pub

root@ubuntu2404:~# ssh-keygen -Lf /etc/ssh/ssh_host_rsa_key-cert.pub
/etc/ssh/ssh_host_rsa_key-cert.pub:
        Type: ssh-rsa-cert-v01@openssh.com host certificate
        Public key: RSA-CERT SHA256:8lUB4WRpzAL9XoiMBoZ12lhnivNhN28JzVxjof/kD+U
        Signing CA: ED25519 SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w (using ssh-ed25519)
        Key ID: "sshserver.example.lan"
        Serial: 0
        Valid: forever
        Principals: (none)
        Critical Options: (none)
        Extensions: (none)
Verifying /etc/ssh/ssh_host_rsa_key-cert.pub

4. Signing User’s Public Key

Each user begins by generating their own SSH key pair:

user@endpoint:~$ ssh-keygen -t ed25519 -f ~/.ssh/userkey
Generating SSH User Key Pair

The user sends their public key to the administrator for signing:

user@endpoint:~$ cat ~/.ssh/userkey.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOyTPxjJwF3AXvAqTaN6ch1QLmnE9oy0PUyS3nHD7l2e user@endpoint
Contents of User’s Public Key

The CA administrator signs the user’s public key:

Tip
Adding a _id suffix in the certificate identity (Key ID) is a useful convention to distinguish users and simplify management. The -z flag sets the certificate’s serial number, which can later be incremented to revoke or reissue credentials.

root@ubuntu2404:~# ssh-keygen -s /etc/ssh/cakey -I user_id -z 0 -n user userkey.pub
Signing User’s Public Key with CA Private Key

Verify the signed certificate:

root@sshserver:~# ssh-keygen -Lf userkey-cert.pub 
userkey-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:ITanlqeRyy9C5TjesakzsrGeVQOoWHzmDj2SqqkuwWQ
        Signing CA: ED25519 SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w (using ssh-ed25519)
        Key ID: "user_id"
        Serial: 0
        Valid: forever
        Principals: 
                user
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc
User’s Certificate Information

5. Creating an Authorized Principal System User

Create a dedicated system account with no login shell:

root@ssh-server# mkdir -p /home/authprin
root@ssh-server# useradd --no-create-home --home-dir /home/authprin --system --shell /usr/sbin/nologin authprin
Creating authprin System User

Confirm that the account exists:

root@sshserver:~# cat /etc/passwd | grep authprin
authprin:x:997:984::/home/authprin:/usr/sbin/nologin
Verify Existence of authprin System User

6. Configuring Authentication

Now, we need to configure the authorized principals mechanism to control which users and certificates are allowed to log in.

6.1 User Whitelist

We’ll maintain a simple text file mapping SSH usernames and certificate serials to authorized principals. Each line follows the format: <uid> <serial> <principal>

root@sshserver:~# echo "admin 0 user" >> /home/authprin/users.txt
Populate the User Whitelist

6.2 User Lookup Script

The SSH daemon will call this Python script each time a user attempts to log in. It looks up the matching entry and prints the allowed principal.

root@sshserver:~# vim /home/authprin/authprincmd.py
Add Lookup Script

#!/usr/bin/python3

import sys

if len(sys.argv) == 3:
    with open("/home/authprin/users.txt", "rt") as f:
        for line in f:
            line = line.rstrip()
            uid, serial, prin = line.split()
            if uid == sys.argv[1] and serial == sys.argv[2]:
                print(prin)
Lookup Script - /home/authprin/authprincmd.py

Remember to make this script executable.

root@sshserver:~# chmod +x /home/authprin/authprincmd.py
Setting Executable Permissions on Script

6.3 Checking Ownership and Permissions

The SSH daemon will refuse to execute the script if permissions are too loose or incorrect. Ensure that the home directory and its files of the authprin system user are owned by root with the following permissions:

root@sshserver:~# ls -la /home/authprin
total 16
drwxr-xr-x 2 root root 4096 Oct 14 23:18 .
drwxr-xr-x 6 root root 4096 Oct 14 23:12 ..
-rwxr-xr-x 1 root root  299 Oct 14 23:18 authprincmd.py
-rw-r--r-- 1 root root   13 Oct 14 23:07 users.txt

6.4 SSH Configuration

Edit /etc/ssh/sshd_config:

Port 22
AddressFamily inet
ListenAddress 0.0.0.0

# Security Related Config
LogLevel VERBOSE
PermitRootLogin no
AuthorizedKeysFile none
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
AllowAgentForwarding no
AllowTcpForwarding yes 
X11Forwarding no 
UseDNS no
AllowStreamLocalForwarding no
DisableForwarding no

# Authorized Principals Related Config
AuthorizedPrincipalsCommandUser authprin
AuthorizedPrincipalsCommand /home/authprin/authprincmd.py %u %s
TrustedUserCAKeys /etc/ssh/ca_key.pub
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub
SSH Configuration for Strict Cert Based Authentication

Once done, remember to restart the service.

root@sshserver:~# systemctl restart ssh
Restarting SSH Service

7. Connecting to SSH Server

Return the signed certificate back to the user, putting key pair along with the certificate inside their .ssh directory.

user@endpoint:~$ ls -l ~/.ssh/userkey*
-rw-------. 1 gerald gerald 411 Oct 14 21:13 /home/gerald/.ssh/userkey
-rw-r--r--. 1 gerald gerald 640 Oct 14 22:28 /home/gerald/.ssh/userkey-cert.pub
-rw-r--r--. 1 gerald gerald  95 Oct 14 21:16 /home/gerald/.ssh/userkey.pub
User’s .ssh Directory

Connect to the SSH server using your signed certificate and verify that authentication succeeds.

user@endpoint:~$ ssh -i ~/.ssh/userkey admin@sshserver.example.lan
SSH with Signed Certificate

Server logs should show a successful certificate-based authentication:

root@sshserver:~# tail -f /var/log/auth.log
...
2025-10-14T23:23:18.222462+08:00 ubuntu2404 sshd[9066]: Accepted certificate ID "user_id" (serial 0) signed by ED25519 CA SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w via /etc/ssh/cakey.pub
2025-10-14T23:23:18.235705+08:00 ubuntu2404 sshd[9066]: Accepted publickey for admin from 172.16.205.1 port 34734 ssh2: ED25519-CERT SHA256:ITanlqeRyy9C5TjesakzsrGeVQOoWHzmDj2SqqkuwWQ ID user_id (serial 0) CA ED25519 SHA256:UATkQvdX31/hbAwNB+4sHmASXI/xmfDsqvQgtS8hW0w
2025-10-14T23:23:18.237900+08:00 ubuntu2404 sshd[9066]: pam_unix(sshd:session): session opened for user admin(uid=1001) by admin(uid=0)
...
Server Authentication Logs

8. Conclusion

SSH certificates provide a scalable, centralized, and revocable way to manage user access without scattering static keys across multiple servers. By delegating trust to a CA and using an AuthorizedPrincipalsCommand, administrators can dynamically control who is allowed to log in, even revoking or rotating user credentials instantly.

This approach brings the flexibility of enterprise-grade identity systems to traditional SSH environments, making it far easier to manage large fleets of machines while maintaining strong security hygiene.