Managing SSH Users with Certs
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
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
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
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)
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)
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)
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
The user sends their public key to the administrator for signing:
user@endpoint:~$ cat ~/.ssh/userkey.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOyTPxjJwF3AXvAqTaN6ch1QLmnE9oy0PUyS3nHD7l2e user@endpoint
The CA administrator signs the user’s public key:
_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
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
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
Confirm that the account exists:
root@sshserver:~# cat /etc/passwd | grep authprin
authprin:x:997:984::/home/authprin:/usr/sbin/nologin
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
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
#!/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)
Remember to make this script executable.
root@sshserver:~# chmod +x /home/authprin/authprincmd.py
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
Once done, remember to restart the service.
root@sshserver:~# systemctl restart ssh
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
Connect to the SSH server using your signed certificate and verify that authentication succeeds.
user@endpoint:~$ ssh -i ~/.ssh/userkey admin@sshserver.example.lan
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)
...
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.