Software Development

OpenPGP im Berufsalltag – Teil 8: SSH mit OpenPGP und YubiKey

Als Angestellter in der IT muss ich häufig auf entfernte Rechner zugreifen. Meistens funktioniert das mit Hilfe von SSH und Public-Key-Kryptographie. Obwohl ich Mails und Daten bereits mit meinem OpenPGP-Schlüsselpaar verschlüssele, brauche ich für den SSH-Zugang noch ein weiteres Paar. Das kann ganz schön nerven und ist unpraktisch. In diesem Artikel zeige ich, wie man seinen PGP-Schlüssel auch mit SSH nutzen kann und damit das SSH-Schlüsselpaar überflüssig macht.

Zugriff auf entfernte Rechner mit SSH

Die sogenannte Secure Shell ist in der IT weit verbreitet. Sie bietet einen verschlüsselten Zugang zu einem entfernten Rechner. Meistens öffnet sich eine Kommandozeile, man kann aber auch einfach "nur" Dienste, die im eigenen Netzwerk nicht vorhanden sind, tunneln, oder Daten kopieren.

Zur Authentifizierung eines Nutzers wird heutzutage statt einer Nutzer- / Passwort-Kombination ein Public-Key-Kryptoverfahren angewandt, das ähnlich zu denen in OpenPGP verwendeten ist – beispielsweise können sowohl ein SSH-Zugang, als auch ein OpenPGP-Schlüsselpaar mit RSA umgesetzt werden.

Problem: Doppelte Arbeit

Obwohl beide Technologien im Prinzip gleich funktionieren, sind die verwendeten Schlüsselpaare in der Praxis nicht kompatibel, da sie jeweils in unterschiedlichen Formaten gespeichert werden. SSH kann also OpenPGP-Schlüssel nicht lesen, obwohl das unterliegende Kryptosystem mit diesen ebenfalls funktionieren würde.

Aus diesem Grund ist man meist gezwungen, neben seinem OpenPGP-Schlüsselpaar noch mindestens ein zusätzliches SSH-Schlüsselpaar vorzuhalten. Dieses liegt in einem eigenen Schlüsselring und unterliegt den SSH-Sicherheitsfunktionen und -beschränkungen.

Man muss also nicht nur zwei Systeme lernen, man hat auch mit der Schlüsselverwaltung doppelte Arbeit. Insbesondere muss man auch SSH-Schlüssel für die Nutzung auf anderen Geräten mitführen bzw. kopieren.

Meine SSH-Schlüssel sind oft projektbezogen und über die Jahre tendieren sie bei mir dazu, auf Tauchstation zu gehen, d. h. ich verliere sie hier und da. In diesem Fall muss ich neue Schlüssel generieren und dem entfernten Rechner bekannt machen. Weil solche Probleme natürlich genau dann auftreten, wenn man keine Zeit dafür hat, ist das besonders nervig. Ich habe auch bemerkt, dass ich beim Generieren der Schlüssel und bei der Auswahl der Passphrase nicht soviel Sorgfalt walten lasse, wie bei meinen OpenPGP-Schlüsseln – d. h. durch diesen projektbezogenen "Mal eben"-Charakter kann unter Umständen auch die Sicherheit gefährdet sein.

Lösung: PGP-Schlüssel übersetzen

Schön wäre es also, wenn man die so sorgfältig gepflegten PGP-Schlüssel auch für den SSH-Zugang nutzen könnte. Damit entfiele der doppelte Arbeitsaufwand. Als Lösung kommt einem sofort eine Umwandlung des PGP-Schlüsselformats in das SSH-Format in den Sinn.

Wie es der Zufall so will, kann gpg das bereits seit Version 2.2, d. h. alle aktuellen Versionen (einschließlich gpg4win) sollten diese Funktion beinhalten.

Doch Moment: Einfach eine SSH-Variante des PGP-Schlüsselpaars vorhalten, löst das Problem nicht zufriedenstellend. Immerhin lebt der geheime Schlüssel seit Teil 7 entsprechend sicher auf einer OpenPGP-Card und man hätte ja trotzdem ein zweites Schlüsselpaar, das nicht ohne Aufwand auf anderen Geräten genutzt werden kann.

Die Lösung ist das sogenannte "On the fly"-Übersetzen des geheimen Schlüssels. gpg kann so konfiguriert werden, dass bei einer SSH-Anfrage die geheimen PGP-Schlüssel temporär übersetzt und an ssh geliefert werden. Wie man das mit Windows einrichtet, werde ich im Folgenden erklären.

SSH über OpenPGP einrichten

Es gibt verschiedene Wege, das Ziel zu erreichen. Ich werde hier einen zeigen, der auf dem Einsatz von gpg4win und der Git-bash bzw. des dort eingebauten SSH-Clients auf Basis von MSYS fußt. Das hat den Vorteil, dass man damit in der gleichen Shell ssh, sowie git (mit Commit-Signing) einsetzen kann. Der unter Windows ebenfalls beliebte Client PuTTY ist mir persönlich zu umständlich. Außerdem lassen sich so ssh-Kommandos zwischen Windows und Unix austauschen.

Außerdem benutze ich Schlüssel auf Basis des RSA-Verfahrens. Inwiefern es beispielsweise auch mit ECC-Schlüsseln funktioniert, kann ich im Moment nicht sagen.

Man benötigt also:

  • Eine Unterstützung der  Authentifizierung mit Public Key durch den entfernten SSH-Server.
  • Eine funktionierende gpg4win-Installation (siehe Teil 3).
  • Eine funktionierende Git for Windows-Installation.
  • Einen OpenPGP-Schlüssel mit der Eigenschaft Authenticate. In Teil 4 haben wir einen solchen als Unterschlüssel erzeugt.

Wir werden den in der git-Bash-Installation enthaltenen SSH-Client benutzen. Optional ist außerdem eine Installation von winpty (in unserem Fall die MSYS-Variante) vorteilhaft.

Denn: Beim Umgang mit der git-Bash sind zwei Dinge zu beachten:

  • Native Windows-Konsolen-Applikationen (z. B. der gpg-Client oder auch telnet) können in der git-Bash Probleme bei Ausgabe und Eingabe von Zeichen haben. In diesen Fällen empfiehlt sich der Einsatz von winpty, das ein passendes Terminal simuliert. Der Aufruf ist denkbar einfach: winpty <kommando>, z. B.: winpty telnet
  • git-Bash bringt einen eigenen gpg-Client mit. Diesen möchten wir nicht benutzen, denn wir haben ja gpg4win. Zusätzlich haben einige gpg4win-Befehle Probleme mit der Zeichendarstellung in der git-Bash. Um beide Welten unter einen Hut zu bekommen, kann man ein sogenanntes Alias in der Datei C:\Users\<USER>\.bashrc eintragen. Bei mir sieht deren Inhalt so aus:

    export LC_ALL="de_DE.UTF-8"
    alias gp='git fetch -p && git pull'
    alias gpgw='winpty /c/devtools/gnupg/bin/gpg' #Absoluter Pfad zur ausführbaren Datei von gpg4win
    alias gpg2='gpgw'

Mit der Eingabe von gpgw kann man also das "richtige" gpg inklusive Terminalemulation starten.

Öffentlichen Schlüssel auf Server laden

Als Erstes müssen wir unseren öffentlichen Schlüssel dem entfernten Server bekannt machen, damit dieser die Public-Key-Authentifizierung durchführen kann. Dazu muss unser öffentlicher PGP-Key im SSH-Format exportiert werden. Das geht so:

# The key ID of my public key is 0x37f0780907abef78.
gpg --export-ssh-key 0x37f0780907abef78 > 37f0780907abef78.pub.ssh


Der Inhalt dieser Datei muss dann auf dem Server entsprechend in das SSH-Setup eingetragen werden. Für gewöhnlich muss man dafür den Schlüssel in die Datei ~/.ssh/authorized_keys eintragen. Wenn man keinen entsprechenden Zugriff auf den Server hat, muss man dafür eine Administratorin fragen.

GPG-Agent SSH-fähig machen

Für die "On-the-fly"-Fähigkeit des OpenPGP-Setups, muss man den gpg-agent umkonfigurieren. Dazu fügt man in die Datei <GNUPGHOME>\gpg-agent.conf die Zeile enable-putty-support ein.

Obwohl PuTTY nicht zum Einsatz kommen wird, konfigurieren wir den Agent so um, dass er mit anderen Programmen über das sogenannte PuTTY-Protokoll sprechen kann. Im nächsten Schritt, werden wir SSH beibringen, das gleiche Protokoll zu sprechen und stellen mit diesem Trick eine Verbindung von der SSH-Welt in die OpenPGP-Welt her.

Zunächst müssen wir nach der Änderung den gpg-agent-Daemon neu starten, damit er die Änderung registriert. Auf der Windows-Konsole sieht das dann so aus: 

C:\>gpg-connect-agent killagent /bye
OK closing connection

C:\>gpg-connect-agent /bye
gpg-connect-agent: no running gpg-agent - starting 'C:\devtools\gnupg\bin\gpg-agent.exe'
gpg-connect-agent: waiting for the agent to come up ... (5s)
gpg-connect-agent: connection to agent established


git-Bash SSH-Client mit gpg4win verbinden

Der ssh-Client muss jetzt beim gpg-agent über das PuTTY-Protokoll nach Schlüsseln fragen. Das wird mit Hilfe des Tools ssh-pageant bewerkstelligt. Dieses Tool wird mit der git-Bash geliefert und ersetzt den normalen ssh-agent. Die originäre Aufgabe des ssh-agent ist es, dem ssh-Client sicheren und dennoch bequemen Zugriff auf das Schlüsselmaterial zu gewährleisten, ohne dass dieser die technischen Details kennen muss. Der ssh-pageant bietet ssh die gleichen Funktionen an, beherrscht aber im Gegensatz zum ssh-agent das PuTTY-Protokoll. ssh kann damit also über den ssh-pageant mit dem gpg-agent sprechen, für den wir ja bereits das PuTTY-Protokoll aktiviert haben.

Damit dies funktioniert, muss man ssh-pageant starten und einige Umgebungsvariablen setzen. Beides zusammen kann man mit folgendem Kommando bewerkstelligen:

$ eval $(/usr/bin/ssh-pageant -r -a "/tmp/ssh-pageant-$USERNAME")
ssh-pageant pid 16804

Was dieser Befehl macht, ist für Ungeübte nicht ganz einfach zu verstehen. Ich habe es deshalb mal auseinandergenommen:

  • eval ist ein bash-Kommando, das alle Argumente als Shell-Kommando ausführt.
  • $( ... ) das Kommando innerhalb der Klammern wird noch vor der Ausführung von eval ausgeführt. Sämtliche Ausgaben werden zu Argumenten von eval.
  • /usr/bin/ssh-pageant -r -a <FILE> – Benutze die Datei FILE zum Austausch mit gpg. Lege sie an, wenn sie nicht existiert.
  • /tmp wird von git-Bash automatisch nach C:\Users\<USERNAME>\AppData\Local\Temp aufgelöst, was das temporäre Standardverzeichnis für einen lokalen Windows-Account ist.
  • ssh-pageant-$USERNAME – Über diese Datei werden Daten ausgetauscht. $USERNAME wird noch vor Ausführung des ssh-pageant-Befehls in den Namen des aktuell angemeldeten Nutzers aufgelöst, bei mir also in ssh-pageant-mosig_user.
  • ssh-pageant pid 16804 ist die Ausgabe des eval-Befehls. Der ssh-pageant ist jetzt ein Hintergrunddienst und hat die Prozess-ID 16804 (kann auf anderen Systemen anders sein).
  • Wenn man ssh-pageant ohne eval startet, wird es einige Kommandos zum Setzen von Umgebungsvariablen ausgeben:

    $ /usr/bin/ssh-pageant -r -a "/tmp/.ssh-pageant-$USERNAME"
    SSH_AUTH_SOCK='/tmp/.ssh-pageant-mosig_user'; export SSH_AUTH_SOCK;
    SSH_PAGEANT_PID=16804; export SSH_PAGEANT_PID;
    echo ssh-pageant pid 16804;
    

Diese Ausgaben sind wiederum valide bash-Befehle, die dann von eval ausgeführt werden. Damit werden die genannten Umgebungsvariablen gesetzt.

ssh-pageant automatisch starten

Damit man den eval-Befehl nicht immer manuell suchen und ausführen muss, kann man ihn in die C:\Users\<USER>\.bashrc eintragen. Er wird dann beim Start von git-bash immer ausgeführt. Gibt es bereits eine laufende Instanz, sorgt ssh-pageant automatisch dafür, dass keine neue gestartet wird. Meine .bashrc sieht jetzt so aus:

export LC_ALL="de_DE.UTF-8"
alias gp='git fetch -p && git pull'
alias gpgw='winpty /c/devtools/gnupg/bin/gpg'
alias gpg2='gpgw'
eval $(/usr/bin/ssh-pageant -r -a "/tmp/.ssh-pageant-$USERNAME")
export SSH_PAGEANT_PID=$(ps | grep $(which ssh-pageant) | head -n 1 | awk '{print $1}')


Die letzte Zeile ist ein Kniff, um ssh-pageant jederzeit bei Bedarf wieder beenden zu können. Das kann man mit ssh-pageant -k machen. Allerdings benötigt man dafür die Prozessnummer (PID) der aktuell laufenden Instanz, ansonsten passiert Folgendes:

$ ssh-pageant -k
ssh-pageant: SSH_PAGEANT_PID not set, cannot kill agent


Diese muss in der Umgebungsvariable
SSH_PAGEANT_PID gespeichert sein. Der Befehl in der letzten Zeile der .bashrc sorgt dafür. Unser eval-Befehl wird diese Variable nur setzen, wenn ssh-pageant noch nicht läuft.

# No instance running yet.
$ /usr/bin/ssh-pageant -r -a "/tmp/.ssh-pageant-mosig_user"
SSH_AUTH_SOCK='/tmp/.ssh-pageant-mosig_user'; export SSH_AUTH_SOCK;
SSH_PAGEANT_PID=4772; export SSH_PAGEANT_PID;
echo ssh-pageant pid 4772;

# One instance running. SSH_PAGEANT_PID is not set.
$ /usr/bin/ssh-pageant -r -a "/tmp/.ssh-pageant-mosig_user"
SSH_AUTH_SOCK='/tmp/.ssh-pageant-mosig_user'; export SSH_AUTH_SOCK;


Es braucht also noch den zusätzlichen
export, damit die Variable immer gesetzt ist. Wie man die PID eines laufenden Prozesses mit git-Bash herausfindet, habe ich mir bei superuser.com abgeschaut.

Vorsicht mit pageant.exe

PuTTY bringt ebenfalls einen Agent namens pageant.exe mit. Es kann jeweils entweder ssh-pageant, oder pageant.exe aktiv sein. Beide zusammen scheinen nicht zu funktionieren.

Damit ist unser Setup komplett. Zeit, das Ganze auszuprobieren.

SSH-Verbindung herstellen

Die eigentliche Arbeit mit ssh bleibt gleich. Durch die Entkopplung mit Hilfe des ssh-pageant bekommt ssh selbst gar nichts von unserem Setup mit. Aus ssh-Sicht ist alles wie immer. Das bedeutet auch, dass es für ssh unerheblich ist, ob die OpenPGP-Schlüssel lokal auf dem Rechner oder auf einer OpenPGP-Card, beispielsweise einem YubiKey, liegen. Sobald gpg Zugriff auf die Schlüssel hat, hat auch ssh Zugriff. Das ist das Schöne an diesem Konzept.

Man loggt sich also wie üblich ein. Im Beispiel habe ich mal mittels -v den Debug-Modus aktiviert.

$ ssh mosig@server_url -v


ssh
führt jetzt einen Handshake mit dem Server durch und wird dann nach dem passenden Schlüssel für den vom Server geforderten "suchen". In meinem Fall liegt dieser auf meinem YubiKey. Das ist der Grund, warum beim Login dessen PIN abgefragt wird. Dieser Vorgang wird komplett von gpg4win gesteuert. Nach erfolgreicher Eingabe wird die Verbindung dann schließlich hergestellt.

ssh

Ausblick

Wer alle Teile der Serie bis hierhin nachvollzogen hat, verfügt jetzt über ein ausgereiftes OpenPGP-Setup, das auch für die Nutzung von SSH taugt. Damit fallen viele Stolpersteine in der täglichen Arbeit weg.

Wie es mit der Merhfachnutzung des gleichen Schlüssels aber so ist, besteht natürlich immer die Gefahr, dass mit einem kompromittierten Schlüssel mehrere Ressourcen kompromittiert werden können, beispielsweise E-Mail-Verschlüsselung und SSH-Zugänge. Hier braucht es wie immer eine Abwägung zwischen Benutzbarkeit und Sicherheit. Denkbar wäre auch die Generierung und Ablage weiterer Schlüssel auf anderen YubiKeys, die dann für den Zugang zu besonders kritischen Ressourcen genutzt werden können. Damit hätte man im wahrsten Sinne des Wortes einen Schlüsselbund für seine Zugänge.

    
Über Jan Mosig

Jan Mosig arbeitet für die itemis AG am Standort Leipzig. Er beschäftigt sich mit Problemen im Projektalltag und setzt zu deren Lösung auf technische Softwarequalität, Agile und Mut zur Veränderung.