Warum Zertifikate statt Schlüssel

SSH vertraut beim ersten Kontakt. Verbinde dich mit einer neuen Maschine, und sie zeigt dir einen Fingerabdruck und fragt, ob du sicher bist. Niemand prüft das. Alle tippen yes. Es ist dieselbe Gewohnheit wie das Wegklicken einer Browserwarnung, und genau das braucht eine Maschine in der Mitte von dir.

Die andere Richtung ist nicht besser. Jeder Server führt eine authorized_keys-Datei pro Benutzer, und jede davon wächst. Schlüssel kommen dazu und werden nie entfernt. Jemand geht, und sein Schlüssel bleibt auf einer Kiste, an die sich keiner erinnert, bis zu dem Tag, an dem es darauf ankommt. Bau einen Server neu auf, und jeder Client schreit, dass sich der Host-Schlüssel geändert hat, also lernen die Leute, auch diese Warnung zu löschen.

Eine SSH-Zertifizierungsstelle beendet beides. Die CA signiert den Schlüssel jedes Hosts, und Clients vertrauen der CA, nicht einer Liste von Fingerabdrücken. Die CA signiert den Schlüssel jedes Benutzers für ein paar Stunden, und Server vertrauen der CA, nicht einer Liste von Schlüsseln. Nichts beim ersten Verbinden zu akzeptieren, nichts aufzuräumen, wenn jemand geht, und ein Benutzerzertifikat, das bis morgen von selbst stirbt.

Das ist ein anderer Aufbau als die X.509-Seite von step-ca aus Deine eigene Zertifizierungsstelle: step-ca im Container, deshalb hat er einen eigenen Beitrag. Derselbe Container, andere Schlüssel, andere Dateien auf jeder Maschine. Alles hier lief mit step-ca 0.30.2 und OpenSSH 10.0 in einem Podman-Pod.

Ohne CAMit step-ca
Ein Client trifft einen neuen ServerZeigt einen Fingerabdruck, du tippst yesPrüft das Host-Zertifikat gegen die CA, sagt nichts
Ein Server wird neu aufgebautJeder Client warnt, dass sich der Schlüssel geändert hatDen neuen Schlüssel signieren, Clients merken nichts
Ein Benutzer bekommt ZugangSein Schlüssel kommt auf jedem Server in authorized_keysDie CA signiert seinen Schlüssel, jeder Server akzeptiert ihn
Ein Benutzer gehtSeinen Schlüssel auf jedem Server finden und löschenNicht mehr signieren; sein Zertifikat ist binnen 16 Stunden tot
Was jeder Server führtEine authorized_keys pro BenutzerEinen öffentlichen CA-Schlüssel
Wer was signiert, und wer wem vertrautstep-caHost-CA-SchlüsselBenutzer-CA-SchlüsselClientBenutzerzertifikat, 16 Std.vertraut der Host-CAServerHost-Zertifikat, 30 Tagevertraut der Benutzer-CAsigniert Benutzerschlüsselsigniert Host-Schlüsselssh, keine Rückfragejeder prüft dasZertifikat des anderen
Die CA signiert beide Seiten. Der Client muss nur dem Host-CA-Schlüssel vertrauen, der Server nur dem Benutzer-CA-Schlüssel. Keiner führt eine Liste der Schlüssel des anderen.

Die SSH-CA einschalten

Die SSH-Schlüssel entstehen bei der ersten Einrichtung von step-ca oder gar nicht. Setz DOCKER_STEPCA_INIT_SSH=true beim ersten Start1, und das Log zeigt zwei zusätzliche Schlüssel:

SSH Host CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
SSH User CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
SchlüsselSigniertWer ihm vertraut
Host-CADen Host-Schlüssel jedes ServersJeder Client, in known_hosts
Benutzer-CADen Anmeldeschlüssel jeder PersonJeder Server, in sshd_config

Sie fügt außerdem einen sshpop-Provisioner hinzu, mit dem ein Host später sein eigenes Zertifikat erneuern kann. Läuft die CA schon ohne SSH, gibt es keinen Schalter zum Umlegen. Es ist eine neue CA.

Den Host signieren

Nimm den vorhandenen Host-Schlüssel des Servers, signiere ihn und gib das Zertifikat an den Server zurück:

step ssh certificate --host --sign \
  --provisioner admin --provisioner-password-file prov-pass \
  nas.home.example ssh_host_ecdsa_key.pub

Die Reihenfolge zählt. Mit den Provisioner-Flags hinter Hostname und Schlüsseldatei hat step abgelehnt:

too many positional arguments were provided in 'step ssh certificate <key-id> <key-file>'

Stell alle Flags nach vorn, und es signiert. Das Zertifikat, wie ssh-keygen -L es liest:

FeldWert
Typecdsa-sha2-nistp256-cert-v01@openssh.com host certificate
Key IDnas.home.example
Principalsnas.home.example
Gültig30 Tage, 2026-10-09T18:34:30 bis 2026-11-08T17:35:30

Der Principal ist der Name, den ein Client zum Verbinden benutzen muss. Verbinde dich über einen Namen, der nicht im Zertifikat steht, und der Client akzeptiert es nicht, also signiere jeden Namen, auf den der Host hört.

Der Server

Zwei Zeilen in sshd_config2:

HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
TrustedUserCAKeys /etc/ssh/ssh_user_ca_key.pub

HostCertificate gibt Clients das Host-Zertifikat, und sein Schlüssel „must match a private host key already specified“2. TrustedUserCAKeys ist der öffentliche Schlüssel der Benutzer-CA, „trusted to sign user certificates for authentication“2. Führ vor dem Neustart sshd -t aus. Ein Tippfehler in einem der Pfade, und sshd meldet Could not load host certificate, und das findest du weit lieber jetzt als in einer ausgesperrten Sitzung.

Auf dem Testserver gibt es überhaupt keine authorized_keys. ls /home/damien/.ssh/authorized_keys liefert No such file or directory, und die Anmeldungen unten funktionieren trotzdem.

Der Benutzer

Die CA erzeugt das Schlüsselpaar und signiert es in einem Rutsch:

step ssh certificate damien id_ecdsa \
  --provisioner admin --provisioner-password-file prov-pass
FeldWert
Typuser certificate
Principalsdamien
Gültig16 Stunden
Erweiterungenpermit-agent-forwarding, permit-port-forwarding, permit-X11-forwarding, permit-pty, permit-user-rc

Sechzehn Stunden sind der Sinn der Sache. Sie decken einen Arbeitstag ab und sterben über Nacht. Einen Benutzer zu widerrufen heißt meistens, morgen nicht für ihn zu signieren.

Der Client

Eine Zeile in known_hosts ersetzt jeden Fingerabdruck, den sie sonst sammeln würde3:

@cert-authority *.home.example ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAy...

Das heißt: jeder Host unter home.example, dessen Zertifikat mit diesem Schlüssel signiert ist, ist echt. Die Markierung @cert-authority macht daraus eine CA-Zeile statt des Schlüssels eines einzelnen Hosts.

Was der Test gezeigt hat

Alle vier von einem Client aus, der nichts hatte außer dem Benutzerzertifikat und dieser einen known_hosts-Zeile, mit StrictHostKeyChecking=yes, damit nichts im Vorbeigehen akzeptiert werden konnte:

TestErgebnis
Signierter Schlüssel mit Zertifikat, als damienlogged in as damien, Exit 0
Derselbe Schlüssel, Zertifikat entferntPermission denied (publickey,password,keyboard-interactive)
Zertifikat für eine Anmeldung als root benutztPermission denied: das Zertifikat nennt nur damien
Client ohne die Host-CA-ZeileHost key verification failed.

Der dritte ist der, den die Leute übersehen. Ein Zertifikat öffnet nicht jedes Konto auf der Kiste, nur die Principals, die darin stehen. Ein Schlüssel in jemandes authorized_keys kann das nicht.

Erneuern

Ein Host erneuert sein eigenes Zertifikat mit dem Schlüssel, den er schon hat. Das ist der sshpop-Provisioner, der „allows a client to renew, revoke, or rekey an SSH certificate using that certificate for authentication with the CA“4. Kein Passwort5:

step ssh renew --force ssh_host_ecdsa_key-cert.pub ssh_host_ecdsa_key
SeriennummerGültig bis
Signiert151663258693294169972026-11-08 17:35:30
Erneuert66789577897973167682026-11-08 17:35:59

Eine neue Seriennummer und frische 30 Tage. Häng es an einen wöchentlichen Timer, und der Host läuft nie ab. Lade sshd danach neu, sonst bleibt das alte Zertifikat im Speicher.

Benutzer erneuern, indem sie noch einmal fragen. Ein step ssh certificate am Tag, oder bei der ersten Anmeldung des Tages, und das ist die ganze Arbeit.

Wo es an seine Grenzen kommt

FallWas passiertWas tun
CA ausgefallenVorhandene Zertifikate funktionieren weiter; niemand bekommt ein neuesDie CA auf etwas betreiben, das läuft; Benutzer haben 16 Stunden Spielraum
Host-Zertifikat abgelaufenClients lehnen den Host ab, als wäre er ein FremderEin Erneuerungs-Timer, und sshd danach neu laden
Ein Benutzer muss heute gehen, nicht morgenSein Zertifikat funktioniert, bis es abläuftRevokedKeys in sshd_config nimmt eine Liste widerrufener Schlüssel2
Hostname nicht unter den PrincipalsClient lehnt den Host abJeden Namen signieren, auf den er hört
SSH-CA bei der Einrichtung nicht erzeugtKeine Möglichkeit, sie nachzurüstenEine neue CA, mit eingeschaltetem SSH eingerichtet
Die SSH-Schlüssel der CA gestohlenWer sie hat, kann sich überall anmelden und jeder Host seinDie CA auf einer eigenen Kiste halten, gesichert, offline, wenn es geht

Der letzte ist der Tausch. Jede authorized_keys-Datei war ihr eigenes kleines Risiko. Jetzt gibt es ein großes, an einem Ort. Das ist besser, weil sich ein Ort überwachen und absichern lässt, aber nur, wenn es jemand tut.

Vertrauen dort, wo es sich prüfen lässt

Die Fingerabdruck-Abfrage und die authorized_keys-Datei haben etwas gemeinsam. Beide verlangen von einem Menschen eine Vertrauensentscheidung ohne Informationen, jeden Tag, für immer. Darin sind Menschen nicht gut und werden es nie sein, also hören sie auf zu entscheiden und fangen an, yes zu tippen.

Eine CA verlegt diese Entscheidung an einen Ort, einmal, wo sie ordentlich getroffen werden kann. Das ist die ganze Idee, und es ist dieselbe hinter jedem Standard, der etwas taugt: das Sorgfältige einmal tun, es aufschreiben und aufhören, von Leuten zu verlangen, es hundertmal die Woche im Kopf zu erledigen. Die Maschine prüft die Signatur. Du prüfst die CA. Das ist eine Arbeitsteilung, die hält.


  1. smallstep/certificates — docker/entrypoint.sh — DOCKER_STEPCA_INIT_SSH=true fügt --ssh zu step ca init hinzu. ↩︎

  2. OpenBSD manual — sshd_config(5) — HostCertificate: „The certificate’s public key must match a private host key already specified by HostKey“; TrustedUserCAKeys: „public keys of certificate authorities that are trusted to sign user certificates for authentication“; RevokedKeys: „Keys listed in this file will be refused for public key authentication“. ↩︎ ↩︎ ↩︎ ↩︎

  3. OpenBSD manual — sshd(8), SSH_KNOWN_HOSTS FILE FORMAT — die Markierung „@cert-authority“, „to indicate that the line contains a certification authority (CA) key“. ↩︎

  4. Smallstep — Provisioners, SSHPOP — „An SSHPOP provisioner allows a client to renew, revoke, or rekey an SSH certificate using that certificate for authentication with the CA.“ ↩︎

  5. Smallstep — step ssh renew — der Befehl, mit dem das Host-Zertifikat erneuert wurde. ↩︎