YubiKey als SSH-CA nutzen

  Alexej Schmidt   Lesezeit: 18 Minuten  🗪 1 Kommentar

Security-Tokens können mehr als nur zweiter Faktor sein: Der Praxisartikel beleuchtet die Verwendung als SSH-CA am Beispiel eines YubiKey.

yubikey als ssh-ca nutzen

Security-Tokens sind in ihrer ursprünglichen Form kleine USB-Dongles, die einen zweiten Faktor zur Authentifizierung darstellen (Fast IDentity Online Universal 2nd Factor, FIDO U2F). Bei der Authentifizierung an Computern, Online-Services oder gar VPNs gibt man seinen ersten Faktor ein, bspw. ein Passwort (Wissen), und nutzt dann den Token, um per Taster ein Einmalpasswort zu generieren, das man dann als zweiten Faktor eingibt (Besitz).

Der Funktionsumfang hat sich seitdem sehr stark weiterentwickelt. Neben der Evolution der Authentifizierung in Form von FIDO2 (der gehypte passwortlose Login) ist es nun auch möglich, einen solchen Token zur Signierung von bspw. Dokumenten zu benutzen. Auch die Signierung, Ver- und Entschlüsselung von Mails mittels OpenPGP ist damit möglich. Die Schlüssel und Zertifikate werden dazu direkt auf dem Token gespeichert, mit "gewöhnlichen" Mitteln mehr oder minder unzugänglich. Auch für SSH-Authentifizierung lässt sich dies nutzen.

Als nächstes Abenteuer aus der Welt der Security-Tokens sei hier erläutert, wie man einen solchen als SSH-CA einsetzen kann, um Host- und Userkeys zur gegenseitigen Authentifizierung zu signieren. Zunächst aber zum Problem, das damit gelöst werden soll.

Public Key Authentication

Die Authentifizierung mittels Schlüsseln als zu präferierende Alternative zum Login mit Passwort ist eine unter Admins bekannte und relativ einfach einzurichtende Sache: Es wird mit ssh-keygen ein SSH-Schlüsselpaar erzeugt und der öffentliche Schlüssel in die Datei authorized_keysdes Remote-Accounts geschrieben, zu dem man sich in Zukunft mit dem privaten Schlüssel anmelden möchte. Ganz klassisch steht dafür der Befehl ssh-copy-id. Dies wiederholt man für jede Maschine und jeden Account, von bzw. mit dem man sich anmelden möchte.

Dieses Vorgehen hat allerdings einen entscheidenden Nachteil, den selbst langjährige Admins gut und gerne auf die leichte Schulter nehmen. SSH postet nämlich vor allem beim Erstkontakt die folgende Ikone an Output:

The authenticity of host '<host> (<IP>)' can't be established.
ED25519 key fingerprint is SHA256:<hash>.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Diese Meldung ist gleichbedeutend mit einer Zertifikatsmeldung bei HTTPS: Die Identität des Hosts konnte nicht verifiziert werden. Allzu häufig schreibt man dann einfach yes, und SSH trägt dann diesen Host in die Datei known_hosts ein – der Key wird nun permanent gespeichert. Zukünftige Verbindungen prüfen dann mit Hilfe dieser Datei, ob der Host mal "abgenickt" wurde und die Rückfrage kommt nicht erneut, falls dies der Fall ist. Das yes sagt essentiell ich habe den Fingerabdruck mit meinem Zielserver abgeglichen und weiß, dass das mein intendierter Zielserver ist. Sollte sich der Fingerabdruck am Remotehost ändern (neue Host-Keys, oder gar ein Man-in-the-Middle-Angriff), schlägt SSH unübersehbar Alarm.

Certificate Authentication

Die Authentifizierung mittels SSH-Zertifikaten ist in ihrer Einrichtung ein ganzes Stück komplizierter, denn sie benötigt in ihren Grundfesten ähnlich wie bei TLS eine PKI-Infrastruktur: Eine Certification Authority, der vertraut wird, und mindestens ein Prozess zur Signierung von Schlüsseln. Zertifikate sind aber vor allem deshalb so wertvoll, weil mit ihrer Signierung bspw. eine Host-, Account- und Zeitbeschränkung eingestellt werden kann. Ein kleiner Trost ist, dass SSH-Zertifikate eine ganze Ecke weniger kompliziert im Aufbau sind als die bei TLS verwendeten X.509-Zertifikate, sie bleiben aber zur Einrichtung mit mehr Arbeit verbunden.

Bei SSH-Zertifikaten werden Host- und User-Zertifikate unterschieden. Host-Zertifikate verifizieren Hosts und ihre Namen gegenüber Clients (ich verbinde mich zu example.com, ist das gesendete Host-Zertifikat ausgestellt auf diesen Namen?), User-Zertifikate verifizieren Clients gegenüber Hosts (Client versucht sich mit user123 anzumelden, darf der Client das laut Client-Zertifikat?). Clients und Hosts vertrauen dabei der CA, bei der diese Zertifikate ausgestellt worden sind. Somit kann sich der Client sicher sein, dass example.com der PKI bekannt ist, und der Host, dass der Benutzer sich mit user123 anmelden darf. Der Vertrauensbeweis sind nicht mehr einzelne Schlüsselpaare, sondern das Zertifikat einer zentralen SSH-CA, die andere Schlüssel beglaubigt hatte. Jedes vorher mit ssh-keygen generierte Schlüsselpaar kann dabei zu einem CA-Schlüsselpaar werden.

Stellt sich nur die Frage, wie man dessen Private Key möglichst gut absichert. Wie schon angerissen soll ein Security-Token die Rolle der SSH-CA übernehmen. Hier ist es ein YubiKey 5, aber jeder Token, der Personal Identity Verification (PIV, aka FIPS 201) unterstützt, ist dazu fähig (mindestens auch Solokey und Nitrokey). Damit stellt sich die Frage der Absicherung des Private Keys nicht: Er ist auf dem Token gespeichert und lässt sich nur nutzen, wenn vorher die PIV-PIN eingegeben und ggf. der Taster gedrückt wird.

Vorbereitung

Softwarevoraussetzungen sind pcsclite, p11-kit sowie das libykcs11-Modul dazu (unter Arch Linux das AUR-Paket ykcs11-p11-kit-module). Zum Interfacing mit dem YubiKey wird der YubiKey Manager verwendet (ykman).

Nach der Installation des ykman prüfe man zunächst, ob der Token erkannt wird und die PIV-Applikation aktiviert ist. Für YubiKeys 4 und älter muss der Modus CCID aktiviert sein, der standardmäßig aktiv ist.

$ ykman list
WARNING: PC/SC not available. Smart card protocols will not function.
YubiKey <model> (<fw>) [OTP+FIDO+CCID] Serial: <serial>
$ ykman config usb -l
PIV

Kommt eine Warnung wie die oben, sollte der pcscd-Service gestartet werden, sonst kann weder ykman noch ssh-keygen auf den YubiKey zugreifen. Wenn außerdem PIV beim zweiten Befehl nicht zu lesen ist, muss die Anwendung noch aktiviert werden.

$ ykman config usb -e PIV
Enable PIV.
Configure USB? [y/N]

PIV sollte noch abgesichert werden, denn standardmäßig sind PIN, PUK und Management-Key aufeinanderfolgende Zahlen: 123456, 12345678 und 0123456789012345[…]. Die Befehle unten sind interaktiv.

$ ykman piv change-pin
$ ykman piv change-puk
$ ykman piv change-management-key

Die einzelnen Schritte

Erstellung der CA

Man erstelle zunächst ein Schlüsselpaar und dann ein Zertifikat im PIV-Speicher. Das Zertifikat wird nicht benutzt, ist aber eine Voraussetzung des PKCS#11-Standards. Der verwendete Slot muss 9d sein, siehe dazu auch PIV Slots. Mit --pin-policy und --touch-policy kann man optional entscheiden, ob und wann eine PIN-Abfrage bzw. eine Taster-Anfrage erscheinen soll, wenn dieser Slot angesprochen wird. Es wird der Standard belassen (pin ALWAYS, touch NEVER). Das Argumentsshca.pub ist ein Pfad/Dateiname. Das braucht ykman kurz, um das Zertifikat zu generieren und kann danach gelöscht werden: Einerseits, weil das SSH-Format des Keys gebraucht wird und man andererseits diesen Public Key jederzeit mit ykman wieder extrahieren kann.

$ ykman piv keys generate 9d sshca.pem
$ ykman piv certificates generate -s "SSH CA" -d 3650 9d sshca.pem

Mit ssh-keygen extrahiert man nun den Public Key aus dem Slot im authorized_keys-Format. -D unterscheidet allerdings nicht zwischen den Slots; daher muss gefiltert werden nach dem "Key Management"-Schlüssel. Das Argument zu -D ist der Pfad zum libykcs11-Modul, gesucht wird im Standard-Pfad für Bibliotheken. Die Option nimmt auch Pfade, somit kann man es manuell eingeben, falls es nicht gefunden werden kann. Zu Demozwecken wird auf/tmp geschrieben.

$ ssh-keygen -D libykcs11.so | grep Management > /tmp/sshca.pub

Host-Zertifikate

Man generiere zunächst ganz normal ein Schlüsselpaar. Optionen sind im eigenen Ermessen, hier z.B. ein ed25519-Paar mit treffendem Kommentar und Speicherung in /tmp:

$ ssh-keygen -t ed25519 -C "Host-Key meines Servers" -f /tmp/host-myserver
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /tmp/host-myserver
Your public key has been saved in /tmp/host-myserver.pub
The key fingerprint is:
SHA256:YD40K0V0MTVaS4jF8v5qFnwj5+bCgm3CRxlabMh+BwA Host-Key meines Servers
…

Nun geht es an die Signierung. -s gibt normalerweise den Private Key der CA an, mit der signiert werden soll. Zusammen mit -D allerdings dient es der Identifizierung eines Private Keys anhand des mit -s angegebenen Public Keys – es erfüllt daher eine Art Filterungsfunktion, da -D ja alle Zertifikate ausgibt, die die Bibliothek findet. -I ist der "Name" des Keys. Ein SSH-Client loggt mit, dass ihm der Key mit diesem Namen vom Host präsentiert wurde – ein treffender Name ist vorteilhaft. -n gibt kommasepariert die Principals an, für die das Zertifikat gelten sollen, bei Hosts also die Namen, über die die Maschine erreicht werden kann. Mit -V kann nun noch eine Gültigkeit angegeben werden, ohne sind die Zertifikate ewig gültig. Ganz wichtig ist hier -h – es sagt aus, dass ein Host-Key signiert werden soll.

$ ssh-keygen -h -D libykcs11.so -s /tmp/sshca.pub -I "Host-Key meines Servers" -n example.com -V +52w host-myserver.pub
Enter PIN for 'YubiKey PIV #<serial>': 
Signed host key host-myserver-cert.pub: id "Host-Key meines Servers" serial 0 for example.com valid from <start> to <end>

Es entsteht eine neue Datei: host-myserver-cert.pub. Diese Datei zusammen mit dem Private Key host-myserver und dem Public Key der CA sshca.pub sind nun auf den Host zu übertragen (falls außerhalb generiert) und am allerbesten unter /etc/ssh/ abzuspeichern. Dessen Owner/Group sollten dann auf root:root angepasst werden.

Nun muss dem SSH-Daemon noch mitgeteilt werden, dass statt Host-Keys ein Host-Zertifikat präsentiert werden soll. Außerdem soll der Daemon angewiesen werden, von der CA ausgestellte Zertifikate zu akzeptieren. Dies geschieht in der sshd_config mit folgenden Direktiven:

HostCertificate /etc/ssh/host-myserver-cert.pub
HostKey /etc/ssh/host-myserver
TrustedUserCAKeys /etc/ssh/sshca.pub

HostKey-Direktiven sowie AuthorizedKeysFile können auskommentiert werden. Den SSH-Daemon neustarten, um die Änderungen wirksam zu machen.

User-Zertifikate

Auch hier generiere man zunächst ein normales Schlüsselpaar wie oben (z.B.-f user-user123). Der Signierungsschritt ist ähnlich, lediglich das -h fällt weg, da wir ein User-Zertifikat signieren. -n ist nun stattdessen eine kommaseparierte Liste an Benutzernamen, mit denen sich mit diesem Zertifikat einloggt werden darf. Zur Verdeutlichung: Dieses Zertifikat attestiert, dass man sich remote als user123 einloggen darf. Das Zertifikat dürfte damit also auch von einem lokalen User namens admin321 verwendet werden.

$ ssh-keygen -D libykcs11.so -s /tmp/sshca.pub -I "User-Key des Users user123" -n user123 -V +52w user-user123.pub 
Enter PIN for 'YubiKey PIV #<serial>': 
Signed user key user-user123-cert.pub: id "User-Key des Users user123" serial 0 for user123 valid from <start> to <end>

Es entsteht eine neue Datei: user-user123-cert.pub. Nun entscheidet man sich, ob nur bestimmte User eines Systems oder alle dieses Zertifikat nutzen sollen. Basierend auf der Entscheidung wird die neue Datei zusammen mit dem Private Key user-user123 entweder nach ~/.ssh der betreffenden User oder nach /etc/ssh/ übertragen. Nehmen wir an, dass das Zertifikat zugeschnitten wäre auf einen Benutzer, somit wäre es nach ~/.ssh zu übertragen.

Nun steht noch die Feststellung an, ob es am System andere Benutzer gibt, die andere Zertifikate von derselben CA benutzen müssen (mit anderen Benutzernamen bspw.) oder ob nur dieser einzelne User Zertifikate der CA nutzen soll. Dies entscheidet, ob die benutzerspezifische known_hosts verwendet wird, um der CA zu vertrauen, oder die systemweite ssh_known_hosts in /etc/ssh/. Die Entscheidung wird oft Letzteres sein, daher wird die CA global eingetragen. Man kreiere daher diese Datei und schreibt folgendes hinein:

@cert-authority example.com ssh-rsa AB3z…

Der zweite Abschnitt mit example.com ist eigentlich eine kommaseparierte Liste an Hostnamen, für die SSH versuchen soll, Zertifikate mit dem Public Key, der dahinter kommt, zu verifizieren. Diese Liste akzeptiert Globs: * ist damit ein gültiger Eintrag und sagt aus, dass alle Hosts verifiziert werden sollen. Dahinter ist, wie gerade geschrieben, der Public Key der CA – also der Inhalt der sshca.pub.

Zu guter Letzt könnte man jetzt knownhosts aus ~/.ssh/ löschen.

Verbindungstest

Man verbinde sich von einem so eingerichteten Client zu einem so eingerichteten Host. Mit -v bekommt man noch mit, was SSH so schönes im Hintergrund macht. Hier einmal ein Auszug aus einem echten Setup, mit etwas Privatsphäre:

$ ssh <user>@<host> -v
[…]
debug1: identity file /home/<user>/.ssh/User-AC type 3
debug1: certificate file /home/<user>/.ssh/User-AC-cert.pub type 7
[…]
debug1: Authenticating to <host>:<port> as '<user>'
[…]
debug1: Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:jsup[…], serial 0 ID "Host-DediServer" CA ssh-rsa SHA256:AGQw[…] valid forever
[…]
debug1: Host '<host>' is known and matches the ED25519-CERT host certificate.
debug1: Found CA key in /etc/ssh/ssh_known_hosts:1
debug1: found matching key w/out port
[…]
debug1: Will attempt key: /home/<user>/.ssh/User-AC-cert.pub ED25519-CERT SHA256:vuX7[…] explicit
debug1: Will attempt key: /home/<user>/.ssh/User-AC ED25519 SHA256:vuX7[…] explicit
[…]
debug1: Next authentication method: publickey
debug1: Offering public key: /home/<user>/.ssh/User-AC-cert.pub ED25519-CERT SHA256:vuX7[…] explicit
debug1: Server accepts key: /home/<user>/.ssh/User-AC-cert.pub ED25519-CERT SHA256:vuX7[…] explicit
Authenticated to <host> ([<IP>]:<port>) using "publickey".
[…]
Last login: <time> from <IP>
[<user>@<hostname> ~]$

Besonders interessant sind diese Zeilen, die perfekt zusammenfassen, was nun erreicht wurde.

  • Host … is known and matches the ED25519-CERT host certificate – der Client konnte das präsentierte Host-Zertifikat verifizieren, es handelt sich um die Maschine, für die sie sich ausgibt.
  • Offering public key / Server accepts key – der Server konnte das vom Client präsentierte User-Zertifikat verifizieren, es berechtigt den Client dazu, sich mit dem angegebenen User einzuloggen.

Bekommt man an der Stelle eine Meldung, dass der Host nicht verifiziert werden konnte, gibt es irgendwo ein Problem. ssh -v auf Clientseite und die SSH-Logs auf Hostseite helfen beim Troubleshooting.

Hilfreiche Seiten

Diese Seiten dienten als Anregung für eigene Experimente. Sie beschreiben darüber hinaus weitergehende Topics, bspw., wie eine PKI mit getrennten User- und Host-CAs, die beide der SSH-CA vertrauen, aufgebaut werden kann oder was zu tun ist, wenn mehrere YubiKeys Verwendung finden. Sie gehen aber nicht auf die Automatisierung dieser Prozesse ein.

Quellen:

Tags

Yubikey, SSH, SSH-CA, USB-Dongle, Security

👓
Geschrieben von 👓 am 1. November 2022 um 08:30

Eines ist kla. Ich werde der Anleitung niemals volgen. Viel zu kompliziert. Ich werde mir dann einen Token kaufen, wenn ich das ganz bequem in Gnome, KDE oder auch bash einrichten kann.

Einstecken, ein paar Fragen beantworten. Fertig.

Meiner meinung nach sollte der Token support in Jeder Distribution und in jedem Desktop environment Standard sein!