Hardware Security Key: SSH einrichten

6 Minuten Lesezeit

Motivation

In meinem Post über Hardware Security Keys schrieb ich, dass ich möglichst viele meiner Verbindungen passwortlos mit meinem neuen HSK absichern möchte. Ich verwende SSH-Verbindungen für verschiedene Dienste und Zwecke: Ich greife auf meinen VPS zu, benutze Dienste wie Github, Gitea sowie meine eigene Gitea-Instanz und lasse die Erzeugung von Backups im lokalen Netz über scp laufen, welches das SSH-Protokoll verwendet.

SSH-Zugriff für Hardware Security Keys einrichten

HSKs wie mein Yubikey können private Schlüssel für SecureShell-Zugriffe abspeichern. Somit muss man sie nicht mehr lokal auf jedem Endgerät ablegen (das bleibt aber konfigurierbar, siehe unten). Zudem sind sie gegenüber der Verwahrung auf einem Rechner besser gegen unbefugte Zugriffe geschützt.

Also probiere ich aus, meinen Github-SSH-Zugriff auf HSK umzustellen und folge Yubicos Anleitung für passwordlose Authentisierung für Github. Das Verfahren zur Erzeugung von SSH-Schlüsseln nach FIDO unterscheidet sich für verschiedene Schlüsselhersteller nicht. Ich verlinke ebenfalls die Nitrokey-Anleitung als Referenz.

Was so nicht funktioniert

Doch mit den Empfehlungen von Yubico selbst komme ich auf meinem Betriebssystem und der Standardinstallation von openssh nicht zum Ziel. Das aus der Anleitung zusammengebaute Kommando lautet:

schallbert@machine:~#  ssh-keygen -t ed25519-sk -O resident -O verify-required -O application=ssh:github -O user=schallbert -C "schallbert@github.com"

Nach Erstellung muss man den öffentliche Schlüssel (Dateiendung .pub) in der Web-Anwendung des jeweiligen Dienstes im Menüpunkt SSH-Zugang hinterlegen.

Bedeutung des Kommandos und der wichtigsten Optionen

Dieser Befehl1 sagt der Anwendung ssh-keygen, dass sie mir einen Schlüssel des Typs -t ed25519-sk erzeugen soll. Die Endung -sk sagt aus, dass der Schlüssel auf einem Security Key - in meinem Fall dem Yubikey - gespeichert werden soll.

Um das Flag -O resident zu verstehen muss man wissen, dass der FIDO authenticator-Schlüssel wie mit -sk erzeugt aus zwei Teilen bestehen: Ein key handle Teil, welcher auf der Festplatte des Rechners verbleibt und der private Schlüssel selbst, welcher nur auf dem HSM existiert. Nur zusammen können sie Authentisierungsanfragen korrekt beantworten. Das Flag selbst weist jedoch an, beide Schlüsselteile auf den Yubikey zu schreiben. Das erleichtert die Verwendung mehrerer Geräte mit demselben Schlüssel: Der key handle-Teil kann vom Yubikey ausgelesen und auf anderen Endgeräten abgelegt werden. Auf der anderen Seite jedoch genügt nun das Verlorengehen des Yubikey allein, um möglichen Angreifern SSH-Zugriff zu geben.

-f filename legt Namen für die Schlüsseldateien fest. Macht besonders dann Sinn, wenn man mehrere SSH-Verbindungen oder HSMs verwalten muss. Ich habe mir eine Konvention erstellt, um nicht mehr durcheinander zu kommen:

-f <keyType>_<protocol>:<service>_<user>_<device>

Als konkretes Beispiel würde mein Dateiname hier wie folgt lauten:

-f ed25519-sk_ssh:github_schallbert_yubikey-main

-C schallbert@github.com fügt der Schlüsseldatei selbst einen Kommentar hinzu, zum Beispiel eine Kontaktadresse. Somit bekommt der Schlüssel ein Label, was die Handhabung unter mehreren vereinfacht.

-O application=ssh:github dient nur der sauberen Auflistung im Authenticator und sorgt dafür, dass ich Schlüssel nicht miteinander verwechseln kann. Ansonsten steht dort nämlich nur ssh:keygen und ich kann nicht mehr sehen, für welche Services ich Schlüssel hinterlegt habe. Außerdem hilfreich bei der Verwendung von -resident, da bei der Extrahierung der Schlüssel mit ssh-keygen -K Unterscheidbarkeit gegeben bleibt.

-O user=schallbert hat für mich keinen praktischen Nutzen, da SSH dieses Flag nicht interessiert und der Yubikey Authenticator meinen Nutzernamen anders als erwartet nicht anzeigt.

-O no-touch-required vermeidet, dass der Hardware Security Key bei jeder Verwendung des Schlüssels berührt werden muss. Nützlich bei sehr häufiger Verwendung, allerdings Sicherheitsverlust durch Automatisierbarkeit.

-O verify-required sagt aus, dass die PIN des HSM abzufragen ist, bevor der Schlüssel genutzt werden kann. Eigentlich als zusätzliche Sicherheit gedacht, bekomme ich damit in meiner Shell jedoch Probleme. Und zwar wird die PIN einmal abgefragt, erzeugt aber vom ssh-agent nicht jedes mal eine Abfrage in meiner Konsole. Sobald ich in einer anderen Session versuche mich per SSH zu verbinden, erhalte ich eine Fehlermeldung agent refused operation:

schallbert@machine:~# ssh -T git@github.com
sign_and_send_pubkey: signing failed for ED25519-SK "schallbert@github.com" from agent: agent refused operation
root@opnsense: Permission denied (publickey).

Woran hat es gelegen?

Nach langer, frustrierender Fehlersuche (Was mache ich falsch? Funktioniert es mit dem Zweitschlüssel? Warum geht es beim ersten mal, danach aber nicht mehr? Was ist, wenn ich die Schlüssel erneut erstelle? Was passiert an anderen Rechnern? Was meint “KI” dazu? - Letzteres kostete bestimmt eine Stunde und ließ mich komplett auf der Stelle treten) probiere ich es mit einem anderen Kommando. Das funktioniert auf Anhieb.

Was hingegen funktioniert

schallbert@machine:~#  ssh-keygen -t ed25519-sk -O application=ssh:github -C "schallbert@github.com" -f ed25519-sk_ssh:github_schallbert_yubikey-main

Lasse ich verify-required weg, kann ich mich erfolgreich anmelden und der SSH-agent läuft sauber durch.

Was verify-required benötigt

Dieser Post bringt für mein Fehlerbild Licht ins Dunkel. Die Terminal-Session mit der ich das SSH-Schlüsselpaar erstelle speichert die per verify-required Flag erzwungene Pin-Eingabe zwischen. Daher funktioniert der Login erst einmal. Logge ich mich später oder in einem anderen Fenster ein, wie ich es oben getan habe um z.B. git pull in einem meiner Repos zu prüfen, fragt der SSH-Agent meine PIN nicht ab.

Das liegt wohl daran, dass der SSH-Agent selbst keine Prompts an das anfragende Programm ausgeben kann. Abhilfe schafft ein Untermodul von Openssh: openssh-askpass. Dieses kann man dem Agent übergeben, welcher dann die PIN-Abfrage über askpass an den User übermittelt.

Installation von ssh-askpass

Mein Betriebssystem hatte ssh-askpass nicht standardmäßig installiert, daher hole ich das hier nach. Es verwendet den Paketmanager DNF “Dandified YUM” statt apt oder anderer gängiger Manager. Dabei ist die Namensgebung meiner Meinung nach nicht 100% einheitlich, spreche ich den ssh-Programme doch im Terminal mit ssh-agent an, muss jedoch openssh-agent tippen für die Installation.

# verify-required needs openssh subpackage through Fedora's package manager
schallbert@machine:~# sudo dnf install openssh-askpass
Installing:
 openssh-askpass
[...]
Complete!

Nun will ich dem Agent mitteilen, dass er ssh-askpass zum Erfragen der PIN ansprechen kann. Dafür mus ich wissen, in welchem Pfad sich das Submodul befindet.

schallbert@machine:~# which openssh-askpass
/usr/bin/which: no openssh-askpass [...]

Komisch, aber ich hatte es doch installiert! Frage ich mal den Paketmanager:

schallbert@machine:~# dnf repoquery --list openssh-askpass
[...]
/usr/libexec/openssh/ssh-askpass

Aha, openssh versteckt sich inm Ordner libexec. Na dann kann ich das Modul jetzt dem Agent bekanntgeben.

schallbert@machine:~# eval "$(ssh-agent -s; SSH_ASKPASS=/usr/libexec/openssh/ssh-askpass)"
Agent pid 16747

Image: Testing the ssh connection to Github with ssh -T and getting prompted for HSK device pin Ich prüfe die Verbindung zu Github und bekomme wie gewünscht einen Dialog angezeigt, wo meine PIN abgefragt wird. Anschließend muss ich den HSK noch berühren und bekomme dann die Erfolgsmeldung. Damit das alles auch in einem neuen Terminal und nach Neustart des Rechners funktioniert, füge ich eine Referenz auf den Schlüssel in meine Konfigurationsdatei ein.

# ~/.ssh/config
Host github
  User git
  Hostname github.com
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/ed25519-sk_ssh:github_schallbert_yubikey-main

Um auf Nummer sicher zu gehen, den Agenten neustarten und neue Terminalsessions erstellen. Sollten Zugangsdaten im Cache gewesen sein, ist das hiermit zurückgesetzt.

 schallbert@machine:~# $(ssh-agent -k)" && eval "$(ssh-agent -s)

Nervig: Gnome-Popup “Allow inhibiting shortcuts”

Image: Gnome popup "Allow inhibiting shortcuts" Einziger Wehrmutstropfen ist, dass ich jetzt beim Verbinden mit SSH ein zusätzliches Popup bekomme. Das wird durch die grafische Nutzeroberfläche gnome ausgelöst. Leider finde ich keine Option, die zugehörige Einstellung permanent speichern. Somit wird es bei jeder Verbindung mit verify-required angezeigt. Bei Gnome wird ist das Problem bereits bei anderen Anwendungen bekannt, anscheinend aber noch nicht abgestellt.

Übertragung auf Windows-Systeme

Ich habe meine Schlüssel ja mit der Option -O resident erzeugt. Da liegt es nahe, die Extraktion des key-handle mal auf einem komplett anderen System zu testen: Windows.

Ich öffne die Kommandozeile und tippe

schallbert@windows-machine:~# ssh-keygen -K
Enter PIN for authenticator:
You may need to touch your authenticator to authorize key download.
Provider "internal" returned failure -1
Unable to load resident keys: invalid format

Invalid format? Im Netz steht öfters geschrieben, dass die Schlüssel manchmal am Ende noch ein LineFeed-Zeichen benötigen um richtig erkannt zu werden. Ohne Download vom HSK kann ich sie aber nicht modifizieren. Also übertrage ich öffentlichen Schlüssel und key-handle manuell auf den Rechner und probiere testweise das git pull-Kommando aus. Und siehe da: Es klappt!

Image: FIDO ssh connection test on a Windows machine: OS prompts for PIN entry. It works!

Möchte man also die Schlüssel mit verschiedenen Betriebssystemfamilien verwenden, nutzt die Option -resident anscheinend nichts.

Überlegungen zu Komfort und Bedienbarkeit

Wenn ich mir meine SSH-Verbindungen so ansehe, dann teilen sie sich grob in zwei Kategorien:

  1. Admin-Zugänge (VPS, NAS, Raspberry Pi) und Signaturen
  2. Protokoll-Zugänge (Git etc.)

Erstere erfordern aus meiner Sicht ein höheres Sicherheitsniveau. Zudem nutze ich sie nicht so häufig wie Versionsverwaltungen. Daher empfehle ich für die erste Kategorie verify-required und würde dies bei der Zweiten weglassen. Mit der Option no-touch-required würde ich ebenso verfahren: falls alle paar Minuten ein Commit hochgeladen werden soll, sind ständige Berührungen des Schlüssels eher irritierend.

  1. Alle Optionen bekommt man bei Aufruf des Handbuchs per man ssh-keygen angezeigt.