SSH Certificates
SSH Certificates
Section titled “SSH Certificates”This guide covers SSH certificate management using the OpenSSH certificate format (PROTOCOL.certkeys).
1. What are SSH Certificates?
Section titled “1. What are SSH Certificates?”SSH certificates replace static key-based authentication with short-lived, auditable, scoped credentials signed by a trusted Certificate Authority.
SSH Keys vs SSH Certificates
Section titled “SSH Keys vs SSH Certificates”| Criterion | SSH Keys | SSH Certificates |
|---|---|---|
| Trust model | Per-key (authorized_keys) | CA-based (TrustedUserCAKeys) |
| Expiration | None (manual revocation) | Built-in validity period |
| Scope | Full access | Principals, force-command, source-address |
| Provisioning | Copy public key to each server | Sign once, accepted everywhere |
| Audit | Key fingerprint only | Key ID, serial, principals |
| Rotation | Replace key on all servers | Re-sign, no server changes |
User vs Host Certificates
Section titled “User vs Host Certificates”| Type | Purpose | Principals | Typical Validity |
|---|---|---|---|
| User | Authenticate users to servers | Usernames (alice, deploy) | 8h - 24h |
| Host | Authenticate servers to clients | Hostnames, IPs | 30 - 90 days |
2. SSH CA Management
Section titled “2. SSH CA Management”ssh ca-init
Section titled “ssh ca-init”Initialize a new SSH Certificate Authority.
# User CA (recommended: Ed25519)qpki ssh ca-init --name user-ca --algorithm ed25519 --type user --ca-dir ./ssh-user-ca
# Host CA (separate from user CA)qpki ssh ca-init --name host-ca --algorithm ed25519 --type host --ca-dir ./ssh-host-ca
# With ECDSAqpki ssh ca-init --name user-ca --algorithm ecdsa-p256 --type user --ca-dir ./ssh-user-caOptions:
| Flag | Description | Default |
|---|---|---|
--name | CA name | Required |
--algorithm | Key algorithm | ed25519 |
--type | Certificate type: user or host | Required |
--ca-dir | CA directory | Required |
Directory structure created:
ssh-user-ca/ ssh-ca.meta.json # CA metadata (name, algorithm, cert type) ssh-ca.pub # CA public key (authorized_keys format) ssh-ca.key # CA private key (PEM) serial # Next serial number (decimal) certs/ # Issued certificates ({serial}-cert.pub) krl/ # Key Revocation Lists index.json # Certificate index (JSON)ssh ca-info
Section titled “ssh ca-info”Display SSH CA information.
qpki ssh ca-info --ca-dir ./ssh-user-caOutput:
SSH Certificate Authority
Name: user-ca Type: user Algorithm: ed25519 Fingerprint: SHA256:abc123... Created: 2026-03-10T10:00:00Z Directory: ./ssh-user-ca
Certificates: 12 total (11 valid, 1 revoked)3. Issuing Certificates
Section titled “3. Issuing Certificates”ssh issue
Section titled “ssh issue”Issue an SSH certificate signed by the specified CA.
# User certificate (8h validity)qpki ssh issue --ca-dir ./ssh-user-ca \ --public-key ~/.ssh/id_ed25519.pub \ --key-id alice@example.com \ --principals alice,deploy \ --validity 8h \ --out ~/.ssh/id_ed25519-cert.pub
# Host certificate (90 days)qpki ssh issue --ca-dir ./ssh-host-ca \ --public-key /etc/ssh/ssh_host_ed25519_key.pub \ --key-id web01.example.com \ --principals web01.example.com,192.168.1.10 \ --validity 2160h \ --out /etc/ssh/ssh_host_ed25519_key-cert.pub
# Restricted certificate (CI/CD)qpki ssh issue --ca-dir ./ssh-user-ca \ --public-key ci-key.pub \ --key-id ci@example.com \ --principals deploy \ --validity 1h \ --force-command "/usr/bin/deploy.sh" \ --source-address "10.0.0.0/8" \ --no-pty \ --out ci-cert.pubOptions:
| Flag | Description | Default |
|---|---|---|
--ca-dir | CA directory | Required |
--public-key | Path to subject’s public key | Required |
--key-id | Human-readable certificate identifier | Required |
--principals | Comma-separated principals | Required |
--validity | Certificate validity duration | 8h |
--passphrase | CA key passphrase | - |
--out | Output file | stdout |
--force-command | Force a specific command (critical option) | - |
--source-address | Restrict to source IPs/CIDRs (critical option) | - |
--no-pty | Disable pseudo-terminal allocation | false |
--no-port-forwarding | Disable port forwarding | false |
--no-agent-forwarding | Disable agent forwarding | false |
Extensions and Critical Options
Section titled “Extensions and Critical Options”Extensions (permissions, user certificates only):
| Extension | Default | Description |
|---|---|---|
permit-pty | Enabled | Allow pseudo-terminal allocation |
permit-port-forwarding | Enabled | Allow port forwarding |
permit-agent-forwarding | Enabled | Allow SSH agent forwarding |
permit-X11-forwarding | Enabled | Allow X11 forwarding |
permit-user-rc | Enabled | Allow execution of ~/.ssh/rc |
Critical options (restrictions, enforced by sshd):
| Option | Description |
|---|---|
force-command | Only the specified command can be executed |
source-address | Restrict to specific IPs/CIDRs (comma-separated) |
4. Inspecting Certificates
Section titled “4. Inspecting Certificates”ssh inspect
Section titled “ssh inspect”Display detailed information about an SSH certificate.
qpki ssh inspect ~/.ssh/id_ed25519-cert.pubOutput:
SSH Certificate:
Type: user certificate Serial: 1 Key ID: alice@example.com Principals: alice, deploy Valid After: 2026-03-10T10:00:00Z Valid Before: 2026-03-10T18:00:00Z Status: VALID Key Type: ssh-ed25519 Fingerprint: SHA256:abc123... Signing CA: SHA256:def456...
Extensions: permit-pty permit-port-forwarding permit-agent-forwarding permit-X11-forwarding permit-user-rcCross-validation with OpenSSH:
# OpenSSH native inspectionssh-keygen -L -f ~/.ssh/id_ed25519-cert.pubssh list
Section titled “ssh list”List all certificates issued by a CA.
qpki ssh list --ca-dir ./ssh-user-caOutput:
SERIAL STATUS TYPE KEY ID PRINCIPALS VALID BEFORE------------------------------------------------------------------------------------------------------------------------1 V user alice@example.com alice,deploy 2026-03-10 18:002 V user bob@example.com bob 2026-03-10 20:003 R user ci@example.com deploy 2026-03-10 11:005. Deployment Guide
Section titled “5. Deployment Guide”User Certificate Authentication
Section titled “User Certificate Authentication”┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ qpki CA │ sign │ User Client │ auth │ SSH Server ││ (ssh-user-ca) │────────>│ (cert + key) │────────>│ (sshd) ││ │ │ │ │ ││ ssh-ca.pub ────│─────────│─────────────────>│─────────│─> TrustedUser ││ │ │ │ │ CAKeys │└──────────────────┘ └──────────────────┘ └──────────────────┘Step 1: Initialize the CA
qpki ssh ca-init --name user-ca --algorithm ed25519 --type user --ca-dir ./ssh-user-caStep 2: Configure sshd
# Copy CA public key to serverscp ./ssh-user-ca/ssh-ca.pub server:/etc/ssh/user-ca.pub
# On the server, add to /etc/ssh/sshd_config:TrustedUserCAKeys /etc/ssh/user-ca.pub
# Optionally restrict principals per user:AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# Restart sshdsystemctl restart sshdStep 3: Issue a certificate
qpki ssh issue --ca-dir ./ssh-user-ca \ --public-key ~/.ssh/id_ed25519.pub \ --key-id alice@example.com \ --principals alice \ --validity 8h \ --out ~/.ssh/id_ed25519-cert.pubStep 4: Connect
# SSH automatically uses the cert if named {key}-cert.pubssh alice@serverHost Certificate Authentication
Section titled “Host Certificate Authentication”Step 1: Initialize the host CA
qpki ssh ca-init --name host-ca --algorithm ed25519 --type host --ca-dir ./ssh-host-caStep 2: Issue a host certificate
qpki ssh issue --ca-dir ./ssh-host-ca \ --public-key /etc/ssh/ssh_host_ed25519_key.pub \ --key-id web01.example.com \ --principals web01.example.com,192.168.1.10 \ --validity 2160h \ --out /etc/ssh/ssh_host_ed25519_key-cert.pubStep 3: Configure sshd
# Add to /etc/ssh/sshd_config:HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pubStep 4: Configure clients
# Add to ~/.ssh/known_hosts:@cert-authority *.example.com ssh-ed25519 AAAA... (contents of ssh-host-ca/ssh-ca.pub)6. Certificate Revocation and KRL
Section titled “6. Certificate Revocation and KRL”SSH certificates can be revoked using OpenSSH Key Revocation Lists (KRL).
KRL is a compact binary format defined in OpenSSH PROTOCOL.krl that lists revoked
certificates. It integrates directly with sshd via the RevokedKeys directive.
ssh revoke
Section titled “ssh revoke”Revoke a certificate by serial number. This updates the CA index and generates an updated KRL.
qpki ssh revoke --ca-dir ./ssh-user-ca --serial 3Output:
Certificate serial 3 revoked.KRL updated: ./ssh-user-ca/krl/krl.bin
To use with sshd, add to sshd_config: RevokedKeys ./ssh-user-ca/krl/krl.binssh krl
Section titled “ssh krl”Generate or regenerate a KRL from all revoked certificates.
# Generate KRL (saved to CA directory)qpki ssh krl --ca-dir ./ssh-user-ca
# Generate KRL to a custom pathqpki ssh krl --ca-dir ./ssh-user-ca --out /etc/ssh/krl.bin --comment "Production KRL"Output:
KRL generated: /etc/ssh/krl.bin Revoked certificates: 2 KRL size: 143 bytesDeploying KRL
Section titled “Deploying KRL”Step 1: Configure sshd
RevokedKeys /etc/ssh/krl.binStep 2: Validate with ssh-keygen
# Check if a certificate is revokedssh-keygen -Q -f /etc/ssh/krl.bin cert.pub
# Output for revoked cert: "cert.pub: REVOKED"# Output for valid cert: "cert.pub: ok"Step 3: Automate distribution
Distribute the KRL to all servers after each revocation (e.g., via Ansible, rsync, or a configuration management tool).
Note: Unlike OCSP, KRL is a static file —
sshdreads it at connection time without network access. Update the file on each server to propagate revocations.
7. SSH Profiles
Section titled “7. SSH Profiles”QPKI provides built-in SSH profiles in profiles/ssh/. Profiles define
validity, extensions, and critical options so that ssh issue commands
remain short and reproducible.
Usage with --profile
Section titled “Usage with --profile”# Issue a user certificate using the default profileqpki ssh issue --ca-dir ./ssh-user-ca \ --profile ssh/user-default \ --public-key ~/.ssh/id_ed25519.pub \ --key-id alice@example.com \ --principals alice,deploy
# Same thing using --var instead of --key-id / --principalsqpki ssh issue --ca-dir ./ssh-user-ca \ --profile ssh/user-default \ --public-key ~/.ssh/id_ed25519.pub \ --var key_id=alice@example.com \ --var principals=alice,deploy
# Override validity from profile (8h → 1h)qpki ssh issue --ca-dir ./ssh-user-ca \ --profile ssh/user-default \ --public-key ~/.ssh/id_ed25519.pub \ --key-id ci@example.com \ --principals deploy \ --validity 1hWhen --profile is used:
- Validity comes from the profile (unless
--validityis explicitly set). - Extensions (permit-pty, permit-port-forwarding, etc.) come from the profile. Explicit flags (
--no-pty,--force-command, etc.) override profile values. - key_id and principals can be provided via
--key-id/--principalsflags or--var key_id=…/--var principals=….
user-default.yaml
Section titled “user-default.yaml”Default user certificate profile: Ed25519, 8h validity.
name: ssh/user-defaultdescription: "SSH user certificate Ed25519 (short-lived, 8h)"cert_type: sshalgorithm: ed25519validity: 8h
variables: key_id: type: string required: true description: "Key identifier (usually email or username)" principals: type: list required: true description: "Allowed usernames on target servers"
ssh_extensions: type: user permissions: permit_pty: true permit_port_forwarding: true permit_agent_forwarding: true permit_x11_forwarding: false permit_user_rc: truehost-default.yaml
Section titled “host-default.yaml”Default host certificate profile: Ed25519, 90 days validity.
name: ssh/host-defaultdescription: "SSH host certificate Ed25519 (90 days)"cert_type: sshalgorithm: ed25519validity: 2160h
variables: key_id: type: string required: true description: "Key identifier (usually hostname FQDN)" principals: type: list required: true description: "Allowed hostnames and IP addresses"
ssh_extensions: type: host8. Supported Algorithms
Section titled “8. Supported Algorithms”| Algorithm | SSH Certificate Type | Recommended |
|---|---|---|
ed25519 | ssh-ed25519-cert-v01@openssh.com | Yes (default) |
ecdsa-p256 | ecdsa-sha2-nistp256-cert-v01@openssh.com | Yes |
ecdsa-p384 | ecdsa-sha2-nistp384-cert-v01@openssh.com | Yes |
ecdsa-p521 | ecdsa-sha2-nistp521-cert-v01@openssh.com | - |
rsa-2048 | ssh-rsa-cert-v01@openssh.com | Legacy only |
rsa-4096 | ssh-rsa-cert-v01@openssh.com | Legacy only |
Post-Quantum Note: PQC algorithms (ML-DSA, SLH-DSA, ML-KEM) are not supported for SSH certificates. The SSH protocol has no standardized post-quantum signature algorithms. OpenSSH 10+ only supports PQ key exchange (ML-KEM), not PQ signatures. QPKI will add PQC SSH support when the protocol standardizes it. Attempting to create an SSH CA with a PQC algorithm returns an explicit error.
9. Troubleshooting
Section titled “9. Troubleshooting”Common Errors
Section titled “Common Errors”| Error | Cause | Solution |
|---|---|---|
algorithm X is not supported for SSH | PQC algorithm used for SSH CA | Use a classical algorithm: ed25519, ecdsa-p256, rsa-4096 |
SSH CA already exists | ca-init on existing directory | Use a different --ca-dir or delete existing CA |
at least one principal is required | Missing --principals flag | Specify at least one principal |
Permission denied (publickey) | Certificate not accepted by sshd | Check: TrustedUserCAKeys configured, principals match, cert not expired |
no matching host certificate | Host cert not found by sshd | Check: HostCertificate path in sshd_config, cert file permissions |
Debugging SSH Certificate Authentication
Section titled “Debugging SSH Certificate Authentication”# Verbose SSH connection (shows cert details)ssh -vvv user@server
# Check if sshd accepts the CAsshd -T | grep trustedusercakeys
# Verify certificate is validqpki ssh inspect ~/.ssh/id_ed25519-cert.pubssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
# Check certificate matches the key# The cert must be named {keyfile}-cert.publs -la ~/.ssh/id_ed25519.pub ~/.ssh/id_ed25519-cert.pubSee Also
Section titled “See Also”- CA - X.509 Certificate Authority management
- Keys - Key generation
- Profiles - Certificate profile system
- HSM - Hardware Security Module integration
- OpenSSH PROTOCOL - SSH certificate format specification
- OpenSSH PROTOCOL.krl - Key Revocation List format