Zum Inhalt springen
ecdsa.com
← Alle Artikel

SSH-Zertifikate vs. authorized_keys: CAs und Principals

authorized_keys skaliert, indem du Schlüssel auf jeden Server kopierst und nie vergisst, sie wieder zu entfernen. OpenSSH-Zertifikate ersetzen das durch eine einzige vertrauenswürdige CA, kurzlebige signierte Schlüssel und Namen statt Fingerabdrücken. Hier ist das vollständige Setup, getestet mit OpenSSH 10.0, samt allen Fehlern, die dabei auftreten.

Kurz gesagt. Bei authorized_keys führt jeder Server jeden öffentlichen Schlüssel auf, der sich anmelden darf, und der Zugang besteht, bis jemand die Zeile löscht. Bei OpenSSH-Zertifikaten vertrauen die Server einer einzigen Benutzer-CA (TrustedUserCAKeys). Benutzer lassen ihren eigenen Schlüssel mit ssh-keygen -s signieren; dabei kommen eine Identität (-I), die Konten, bei denen er sich anmelden darf (Principals, -n), und ein Gültigkeitszeitraum (-V) hinzu. Clients vertrauen einer einzigen Host-CA (@cert-authority in known_hosts), statt den Fingerabdruck jedes Hosts bei der ersten Verbindung zu akzeptieren. Mit kurzlebigen Zertifikaten erledigt sich das Offboarding von selbst, und den Rest übernehmen Sperrlisten, die Key Revocation Lists (RevokedKeys). Der Preis: Du betreibst jetzt eine CA und musst ihren Schlüssel schützen.

Was authorized_keys nicht leistet

~/.ssh/authorized_keys ist einfach und hat keine beweglichen Teile, deshalb ist es überall im Einsatz. Seine Grenzen zeigen sich, sobald mehr Leute und mehr Server dazukommen:

  • Zugang läuft nie ab. Ein Schlüssel, der 2023 für einen externen Dienstleister eingetragen wurde, funktioniert 2026 noch immer auf jedem Server, auf den er kopiert wurde, sofern ihn niemand entfernt. (expiry-time= gibt es pro Zeile, aber jemand muss es setzen.)
  • Keine Identität. Die Zeile besteht aus einem Schlüssel und einem Kommentar. Zu welcher Person, welchem Team oder welchem Job sie gehört, steht in einer Tabelle, wenn überhaupt irgendwo.
  • Verteilung ist dein Problem. Wer jemanden hinzufügt oder entfernt, muss jeden Server anfassen, per Konfigurationsmanagement oder von Hand.
  • Host-Schlüssel: Vertrauen beim ersten Kontakt (Trust on First Use). Jeder Client akzeptiert den Fingerabdruck jedes Servers beim ersten Mal, und wird ein Server neu aufgesetzt, erscheint überall REMOTE HOST IDENTIFICATION HAS CHANGED.

OpenSSH-Zertifikate gibt es seit OpenSSH 5.4 (2010), und sie lösen alle vier Probleme. X.509 sind sie nicht: Das Format stammt von OpenSSH selbst, ein öffentlicher Schlüssel plus Identität und Einschränkungen, signiert von einem CA-Schlüssel, der selbst ein gewöhnlicher SSH-Schlüssel ist. 2025 wanderte die Formatbeschreibung aus der Datei PROTOCOL.certkeys in den OpenSSH-Quellen in einen Internet-Draft, heute draft-ietf-sshm-cert in der IETF-Arbeitsgruppe SSHM.

Zwei CAs, zwei Richtungen

Du brauchst zwei getrennte Signaturschlüssel, denn sie beantworten unterschiedliche Fragen:

  • Die Benutzer-CA beantwortet die Frage „Darf sich dieser Schlüssel als dieses Konto anmelden?“ Server vertrauen ihr.
  • Die Host-CA beantwortet die Frage „Ist das wirklich web01?“ Clients vertrauen ihr.
Die CAs erstellen (OpenSSH 10.0p2)
$ ssh-keygen -q -t ed25519 -f user_ca -C "Example user CA" -N ""
$ ssh-keygen -q -t ed25519 -f host_ca -C "Example host CA" -N ""

-N "" hält den Testaufbau einfach. Ein echter CA-Schlüssel gehört in ein HSM, ein Hardware-Token oder einen Signaturdienst (die SSH Secrets Engine von HashiCorp Vault, smallstep, Teleport und entsprechende Dienste der Cloud-Anbieter leisten das alle). Wer den Schlüssel der Benutzer-CA besitzt, kann sich als jeder anmelden, den die Server zulassen.

Benutzerzertifikate

Signiere Alices vorhandenen öffentlichen Schlüssel für acht Stunden, für die Konten alice und deploy:

ssh-keygen -s: ein Benutzerzertifikat ausstellen
$ ssh-keygen -s user_ca -I alice@example.test -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub
Signed user key /home/alice-laptop/id_ed25519-cert.pub: id "alice@example.test" serial 1001 for alice,deploy valid from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
  • -I ist die Schlüssel-ID (Key ID). sshd schreibt sie bei jeder Anmeldung ins Log, also wähle eine Person oder einen Job, keinen Hostnamen.
  • -n listet die Principals auf, also die Namen, unter denen dieses Zertifikat auftreten darf. Laut Handbuch von ssh-keygen sind Zertifikate ohne Principals „für alle Benutzer oder Hosts gültig“, aber sshd 10.0 lehnt ein solches Benutzerzertifikat unter TrustedUserCAKeys ab (Certificate lacks principal list). Setze -n immer.
  • -V -5m:+8h setzt die Gültigkeit von vor fünf Minuten (als Puffer für abweichende Uhren) bis in acht Stunden.
  • -z setzt eine Seriennummer, auf die sich ein Widerruf beziehen kann.

ssh-keygen -L zeigt, was signiert wurde:

ssh-keygen -L -f id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s
        Signing CA: ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60 (using ssh-ed25519)
        Key ID: "alice@example.test"
        Serial: 1001
        Valid: from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

Standardmäßig gewährt ein Benutzerzertifikat alle fünf Erweiterungen. -O clear entfernt sie, danach fügst du nur wieder hinzu, was du brauchst, zum Beispiel -O clear -O permit-pty. Kritische Optionen schränken das Zertifikat ein. force-command ersetzt jeden Befehl, den der Client anfordert, und source-address begrenzt, von wo aus es verwendet werden darf. Mit source-address=10.0.0.0/8 wird eine Anmeldung von 127.0.0.1 abgelehnt. Kommt die Anmeldung von einer erlaubten Quelle, läuft der erzwungene Befehl, ganz gleich, was der Client angefordert hat:

Kritische Optionen in der Praxis (sshd-Logzeilen bei LogLevel VERBOSE)
        Critical Options: 
                force-command echo backup only
                source-address 10.0.0.0/8
$ ssh deploy@web01.example.test
deploy@web01.example.test: Permission denied (publickey).
cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).
Refused by certificate options
...
$ ssh deploy@web01.example.test "rm -rf /tmp/anything"
backup only
Starting session: forced-command (key-option) 'echo backup only' for deploy from 127.0.0.1 port 48976 id 0

Der Client ssh findet das Zertifikat automatisch, wenn es als id_ed25519-cert.pub neben dem privaten Schlüssel liegt. Andernfalls verweist du mit CertificateFile darauf. ssh-add lädt beides in den Agenten.

sshd konfigurieren

Die gesamte Serverseite besteht aus ein paar Zeilen. Diese Drop-in-Datei hat sshd -t bestanden und wurde für jeden Test auf dieser Seite verwendet:

/etc/ssh/sshd_config.d/ca.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedKeys /etc/ssh/revoked_keys
PasswordAuthentication no
LogLevel VERBOSE
  • TrustedUserCAKeys enthält einen oder mehrere öffentliche Schlüssel von CAs. Während einer CA-Rotation trägst du beide ein.
  • AuthorizedPrincipalsFile ordnet Konten Principals zu. Hier enthält /etc/ssh/auth_principals/deploy eine einzige Zeile, deploy, sodass sich nur Zertifikate, die den Principal deploy aufführen, als deploy anmelden dürfen. Ohne diese Option verlangt sshd, dass der Kontoname selbst unter den Principals steht. Die Zuordnung ist flexibler: Ein Principal wie oncall lässt sich für viele Konten zulassen, und AuthorizedPrincipalsCommand kann ihn in einem Verzeichnisdienst nachschlagen.
  • RevokedKeys verweist auf eine Sperrliste (siehe unten).
  • LogLevel VERBOSE protokolliert bei jeder Anmeldung die Schlüssel-ID und die Seriennummer des Zertifikats. Das ist der Audit-Trail, den authorized_keys nie hatte.

Eine Anmeldung mit Alices Zertifikat, von beiden Seiten gesehen:

ssh -v (Client) und sshd-Log (Server), gekürzt
Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:IuzvI4QsLmSx1+6p3yrvlRx2wdl+LUCY1zXqSzssLAc, serial 0 ID "web01" CA ssh-ed25519 SHA256:+iie/YHPulJReHN2GQ8UvrorBVrtayD2T1MGxeg/QCQ valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
Host 'web01.example.test' is known and matches the ED25519-CERT host certificate.
Offering public key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Server accepts key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Authenticated to web01.example.test ([127.0.0.1]:22) using "publickey".

Accepted publickey for deploy from 127.0.0.1 port 57432 ssh2: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s ID alice@example.test (serial 1001) CA ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60

Wenn du sshd_config nicht bearbeiten kannst, kann ein einzelnes Konto einer CA über seine eigene Datei authorized_keys vertrauen. Die Option principals= funktioniert wie die Principals-Datei:

~deploy/.ssh/authorized_keys: einer CA für ein einzelnes Konto vertrauen (Schlüssel gekürzt)
cert-authority,principals="deploy-team" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK63...
$ ssh deploy@web01.example.test 'echo logged in as $(whoami)'
logged in as deploy

Host-Zertifikate

Signiere den Host-Schlüssel jedes Servers mit der Host-CA. -h macht daraus ein Host-Zertifikat, und die Principals sind die Namen, unter denen sich Clients verbinden:

Einen Host-Schlüssel signieren
$ ssh-keygen -s host_ca -I web01 -h -n web01.example.test,web01 -V -5m:+52w /etc/ssh/ssh_host_ed25519_key.pub
Signed host key /etc/ssh/ssh_host_ed25519_key-cert.pub: id "web01" serial 0 for web01.example.test,web01 valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08

Lass dann HostCertificate auf das Ergebnis zeigen (wie in der sshd-Datei oben) und gib den Clients eine einzige Zeile in known_hosts (oder /etc/ssh/ssh_known_hosts) statt eines Fingerabdrucks pro Host:

known_hosts: der Host-CA für eine Domain vertrauen
@cert-authority *.example.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBtSsqoqxwcl31DTJg3kTPsv9eLuuw5gU1BcvYybODlu Example host CA

Neue und neu aufgesetzte Server gelten dann schon bei der ersten Verbindung ohne Rückfrage als vertrauenswürdig, sofern ihr Zertifikat den Namen aufführt, den der Client verwendet hat. Genau diese Bedingung sorgt regelmäßig für Überraschungen. Verbindet sich der Client unter einem Namen, der kein Principal ist, fällt er auf einfache Host-Schlüssel zurück, und bei strikter Prüfung bricht er ab:

Verbindung als db01.example.test zu einem Host, der nur als web01 zertifiziert ist
$ ssh deploy@db01.example.test
Certificate invalid: name is not a listed principal
No ED25519 host key is known for db01.example.test and you have requested strict checking.
Host key verification failed.

Führe jeden Namen auf, den Clients verwenden, auch Kurznamen, CNAMEs, IP-Adressen, falls sich Leute per IP verbinden, und Namen von Load Balancern. ssh-keyscan -c ruft das Zertifikat eines Servers zur Untersuchung ab, und Zeilen mit @revoked in known_hosts weisen einen bestimmten Host-Schlüssel oder eine CA zurück.

Wie Fehler aussehen

Bei Problemen mit Benutzerzertifikaten meldet der Client immer nur Permission denied (publickey). Den Grund findest du im Log des Servers, bei LogLevel VERBOSE:

Situationsshd-Log (OpenSSH 10.0p2)
Konto nicht unter den Principals des Zertifikats (nach der Zuordnung)Certificate does not contain an authorized principal
Zertifikat ohne -n ausgestellt, kein AuthorizedPrincipalsFileCertificate lacks principal list
Zertifikat nach Ablauf seines Gültigkeitszeitraums (-V)Certificate invalid: expired
Seriennummer oder Schlüssel in der KRLAuthentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked_keys
source-address passt nichtcert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).

Zwei Dinge, die wie Zertifikatsprobleme aussehen, aber keine sind:

  • Abweichende Uhren. Die Gültigkeit wird gegen die Uhr des Servers geprüft. Stelle Zertifikate aus, deren Gültigkeit ein paar Minuten in der Vergangenheit beginnt (-V -5m:...), und halte die Server per NTP synchron.
  • Strafzeiten nach wiederholten Fehlschlägen. Seit OpenSSH 9.8 belegt sshd Adressen, deren Authentifizierung immer wieder scheitert, mit Strafzeiten (PerSourcePenalties, standardmäßig aktiv). Beim Testen von Zertifikaten von einem einzigen Rechner aus führte ein fehlgeschlagener Versuch dazu, dass die nächste Verbindung noch vor der Authentifizierung getrennt wurde:
sshd 10.0p2 nach einer fehlgeschlagenen Anmeldung mit Zertifikat
srclimit_penalise: 127.0.0.1/32: activating ipv4 penalty of 19 seconds for penalty: failed authentication
$ ssh deploy@web01.example.test
Connection closed by 127.0.0.1 port 22
drop connection #0 from [127.0.0.1]:55080 on [127.0.0.1]:22 penalty: failed authentication

Meldet ssh direkt nach einer Reihe von Fehlschlägen Connection closed, warte die Strafzeit ab, bevor du deine Konfiguration erneut änderst.

Widerruf mit KRLs

Kurze Gültigkeit ist der wichtigste Widerrufsmechanismus: Ein Zertifikat, das acht Stunden gilt, erledigt sich von selbst. Für den Rest hat OpenSSH Key Revocation Lists (KRLs, seit OpenSSH 6.2), Sperrlisten in einem kompakten Binärformat, die nach Seriennummer, Schlüssel-ID oder Schlüssel widerrufen können. So widerrufst du Alices Zertifikat mit der Seriennummer 1001:

ssh-keygen -k: eine KRL erstellen und ein Zertifikat dagegen prüfen
$ printf 'serial: 1001\n' > /tmp/krl-spec
$ ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub /tmp/krl-spec
Revoking from /tmp/krl-spec
$ ssh-keygen -Q -f /etc/ssh/revoked_keys id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub (alice@laptop): REVOKED

sshd liest RevokedKeys bei jeder Authentifizierung, die nächste Anmeldung scheitert also ohne Neustart. Mit -u aktualisierst du eine vorhandene KRL, statt sie zu ersetzen. Verteile die KRL mit denselben Tools wie den CA-Schlüssel. Ein Server mit veralteter KRL akzeptiert alles, von dessen Widerruf er nichts weiß. Das ist ein Grund mehr, Zertifikate kurzlebig zu halten und die KRL als Sicherheitsnetz für die verbleibenden Stunden zu betrachten.

Im direkten Vergleich

authorized_keysZertifikate
Zugang gewährenSchlüssel auf jeden Server kopierenSchlüssel einmal signieren
Zugang entziehenAuf jedem Server löschenAblaufen lassen oder in die KRL eintragen
AblaufOptional pro Zeile (expiry-time=)Jedes Zertifikat hat einen Gültigkeitszeitraum
Identität in LogsFingerabdruck des SchlüsselsSchlüssel-ID und Seriennummer
Einschränkungenfrom=, command=, restrict pro Zeilesource-address, force-command, Erweiterungen pro Zertifikat
Vertrauen in HostsFingerabdruck pro Host (TOFU)Eine Zeile @cert-authority
Kritisches GeheimnisKeines an zentraler StelleDer private Schlüssel der CA
Bewegliche TeileKeineAusstellung, Schutz der CA, Verteilung der KRL

Die Einschränkungen pro Zeile in authorized_keys funktionieren tatsächlich, getestet auf demselben Server:

Optionen in authorized_keys, OpenSSH 10.0p2 (Schlüssel gekürzt)
restrict,from="10.0.0.0/8",command="echo backup only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
/home/deploy/.ssh/authorized_keys:1: Authentication tried for deploy with correct key but not from a permitted host (host=127.0.0.1, ip=127.0.0.1, required=10.0.0.0/8).

expiry-time="20261001" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).

Für eine Handvoll Server und Leute reicht authorized_keys mit restrict und expiry-time völlig aus. Zertifikate lohnen sich, sobald Onboarding, Offboarding und Neuaufbauten von Hosts jede Woche anfallen oder wenn du wissen musst, wer sich angemeldet hat, nicht nur mit welchem Schlüssel.

Umstieg von authorized_keys

Du brauchst keinen Stichtag. sshd prüft authorized_keys und vertrauenswürdige CAs nebeneinander, sodass während der Umstellung beide Arten der Anmeldung funktionieren:

  1. Rolle zuerst das Vertrauen in die CA aus. Verteile TrustedUserCAKeys, die Principals-Dateien und eine leere KRL auf alle Server und lass authorized_keys unangetastet.
  2. Stelle einem ersten Team Zertifikate aus und lass es sie ein paar Wochen lang nutzen. Jede Anmeldung erscheint jetzt im Log mit ID ... (serial ...).
  3. Finde die Nachzügler. Eine Anmeldung über authorized_keys wird ohne Zertifikats-ID protokolliert, zum Beispiel Accepted publickey for deploy from 127.0.0.1 port 58770 ssh2: ED25519 SHA256:tT5X32Wds5InYaLSh7mV+QRqAtxA4KXwlKV3hkf0YkI. Meist steckt Automatisierung dahinter: CI-Runner, Backup-Jobs, Monitoring. Gib jedem davon ein eigenes kurzlebiges oder eng eingeschränktes Zertifikat.
  4. Schalte die Schlüsseldateien ab, sobald nichts Legitimes sie mehr nutzt: mit AuthorizedKeysFile none. Halte einen Notfallzugang bereit, etwa Konsolenzugriff oder eine separate Notfall-CA, die offline aufbewahrt wird, für den Tag, an dem der Signaturdienst ausfällt.
  5. Kümmere dich separat um die Host-Seite. Signiere die Host-Schlüssel und trage die Zeile @cert-authority in die globale /etc/ssh/ssh_known_hosts der Clients ein. Die alten Einträge pro Host in known_hosts können bleiben. Diese funktionieren weiter, bis sich die Host-Schlüssel ändern.

Betrieb in der Praxis

  • Stelle Zertifikate über deinen Identity Provider aus. Ein Signaturdienst prüft die SSO-Anmeldung des Benutzers (OIDC) und gibt ein Zertifikat mit dessen Principals zurück, das ein paar Stunden gilt. Niemand hantiert mit dem CA-Schlüssel, und wer das Unternehmen verlässt, hat spätestens am nächsten Tag keinen SSH-Zugang mehr, ohne dass jemand einen Server anfasst.
  • Halte den Benutzerschlüssel nach Möglichkeit in Hardware. ssh-keygen -t ed25519-sk erzeugt einen FIDO2-gestützten Schlüssel. Zertifikate funktionieren damit wie mit jedem anderen Schlüssel.
  • Rotiere die CA mit Überlappung. Trage die neue Benutzer-CA als zweite Zeile in TrustedUserCAKeys ein oder die neue Host-CA als zweite Zeile mit @cert-authority. Stelle Zertifikate mit der neuen CA aus und entferne dann die alte.
  • Behalte auch die Host-Seite im Blick. Signiere Host-Schlüssel neu, bevor ihre Zertifikate ablaufen. Ein abgelaufenes Host-Zertifikat lässt Clients auf einfache Host-Schlüssel zurückfallen, die sie meist nicht haben:
ssh zu einem Host, dessen Zertifikat gestern abgelaufen ist
Certificate invalid: expired
No ED25519 host key is known for web01.example.test and you have requested strict checking.
Host key verification failed.

Willst du eine Schlüssel- oder Zertifikatsdatei untersuchen, ohne etwas auszuführen, dekodiert der SSH-Schlüsselinspektor OpenSSH-Schlüssel und zeigt Typ, Länge und Fingerabdruck. Das SSH-Server-Audit prüft, was ein Server anbietet, einschließlich seiner Algorithmen für Host-Schlüssel und Zertifikate und des Post-Quanten-Schlüsselaustauschs.

Quellen

  • OpenSSH-Manpages: ssh-keygen(1) (Abschnitte CERTIFICATES und KEY REVOCATION LISTS), sshd_config(5) (TrustedUserCAKeys, AuthorizedPrincipalsFile, RevokedKeys, HostCertificate, PerSourcePenalties), sshd(8) (Formate von authorized_keys und known_hosts).
  • draft-ietf-sshm-cert: SSH Certificate Format (der Nachfolger von PROTOCOL.certkeys aus OpenSSH). PROTOCOL.krl: das KRL-Format.
  • Release Notes von OpenSSH: 5.4 (Zertifikate), 6.2 (KRLs), 9.8 (PerSourcePenalties).

Alle Ausgaben auf dieser Seite wurden am 20. Oktober 2026 mit OpenSSH 10.0p2 (Pakete von Debian 13) in debian:trixie-20261005-slim erzeugt, mit Testschlüsseln, die im Container generiert wurden.