Eintrag von 2015-10-26
▶ 2-Faktor-Authentifizierung via libpam_oath am Beispiel OpenVPN
In vielen Unternehmen stellt das lokale Netzwerk nach wie vor die zentrale Trust Boundary zwischen vertrauenswürdigem und grundsätzlich misstrautem Traffic dar. Virtuelle Private Netzwerke (VPN) sind deshalb vielerorts unverzichtbar, möchten heute doch Mitarbeiter auch von unterwegs und zu Hause aus auf schützenswerte Ressourcen und Dienste zugreifen können. OpenVPN ist eine sowohl freie als auch beliebte und erprobte Lösung, um ein solches VPN zu errichten. Die Authentifizierungsmethodik, die über die Berechtigung an der Teilnahme am VPN entscheidet, ist dabei normalerweise X.509/TLS: Ein RSA-Schlüsselpaar aus Private (bzw. Secret) Key und Public Key, sowie diverser anderer Metadaten, die von einer Certificate Authority als vertrauenswürdig eingestuft und deshalb kryptographisch signiert werden.
Das ist durchaus gut so, und soll sich aus heutiger Sicht auf die mathematischen Fundamente, die dieses Verfahren ermöglichen, auch nicht so bald ändern - aber es reicht doch oft in der Realität nicht aus, um dieses Paar aus Zertifikat und Schlüssel als einzigen Schranken zwischen potenziellen Tunichtguten und vertraulichen Datenschätzen zu positionieren: Findet sich das Endgerät mit den Schlüsseldaten (über deren etwaige lokale, symmetrische Verschlüsselung die CA ja nicht entscheiden kann) durch Diebstahl oder eine Unachtsamkeit in den falschen Händen wieder, ist die Kompromittierung der mithilfe des VPN abgeschotteten Systeme zu befürchten.
Solche Überlegungen sind der Grund, warum man für immer mehr kritische Systeme auf Authentifizierungsschemata setzt, die mehr als einen validierenden Faktor in Betracht ziehen. Solche Faktoren können verschiedenster Natur sein: Ein Tupel aus Username und Passwort als einen Faktor hat wohl jeder vernetzte Bürger schon einmal zur Anmeldung an irgendeinem Dienst wie bspw. einem E-Mail-Konto verwendet. Vom Online-Banking z. B. kennt man das SMS-TAN-Verfahren, wobei ein einmalig gültiges Passwort, das genau eine bestimmte Transaktion genehmigt, auf ein mit dem Benutzer in Verbindung gebrachtes Mobiltelefon übertragen wird. In wieder anderen Szenarien kommt z. B. die Kombination aus einer Zutrittskarte und einem Fingerabdruckleser zum Zug, um den Zutritt zu kritischer Infrastruktur wie Rechenzentren oder Steuerzentralen zu regeln. Nach oben gibt es hier, ähnlich wie bei der Passwortkomplexität, keine wirkliche Grenzen - es ist durchaus mach- und vorstellbar, fünf Faktoren bzw. Merkmale zu prüfen, und erst beim Überwinden all dieser Hürden den Zugriff bzw. Zutritt zu erlauben. Entscheidend ist es, ein vernünftiges Gleichgewicht aus Sicherheitsvorkehrungen und Benutzungskomfort zu finden, mit dem alle am System Beteiligten zurecht kommen, und das sich durch die organisatorisch Zuständigen ruhigen Gewissens verantworten lässt.
OATH und TOTP - Einwegpasswörter komfortabel und sicher
Auch im OpenVPN-Projekt hat man früh den Bedarf an einem zweiten authentifizierenden Faktor für VPN-Clients erkannt, und sich für eine Implementierung entschieden, die durch ihre Flexibilität und Verbreitung überzeugt: PAM (Pluggable Authentication Modules). PAM ist ein System aus Shared Libraries, die einzelne Rollen zur Authentifizierung und Autorisierung abwickeln können. Unter modernen GNU/Linux-Distributionen ist z. B. das pam_unix-Modul beim Login auf der Konsole dafür verantwortlich, Credentials mit jenen, die in /etc/shadow hinterlegt wurden, beim Anmeldeversuch abzugleichen. Andere Module erlauben es z. B., den Login an das erfolgreiche Entsperren eines zuvor hinterlegten, privaten SSH-Schlüssels zu binden - darüber habe ich vor einigen Jahren schon einmal geschrieben.
Ein anderes Modul, pam_oath, greift die Konzepte aus der Open Authentication-Initiative (OATH) auf und erlaubt das Einbinden von OTP (One-Time Passwords) in den Authenfizierungsstack PAM-kompatibler Services. Um OATH-OTP einsetzen zu können, benötigt man ein Gerät bzw. Software, die die dafür notwendigen Standards implementieren. Wer bereits ein Android- oder iOS-Gerät besitzt, kann auf quelloffene Software für diesen Zweck zurückgreifen. Der YubiKey ist ein beliebter und kostengünstiger Hardware-Token, der stattdessen ebenfalls eingesetzt werden könnte. OATH-OTPs basieren auf einem geteilten Geheimnis zwischen Client und Server - dem Secret. Dies ist eine base32-kodierte, möglichst zufällige Zeichenkette, die als Seed für pseudozufällige Berechnungen benutzt wird. Dadurch ergeben sich zeitlich begrenzt gültige, kurze und rein numerische Passwörter/PINs, die ohne weitere Kommunikation zwischen Client und Server übereinstimmen (müssen). Damit dies funktionieren kann, braucht es außer des initial ausgetauschten Secret auch noch eine weitere Komponente: Entweder den aktuellen Zeitstempel (dann verwendet man TOTP - Time-based OTP), oder die Anzahl der bisher erfolgten Authentifizierungen (dann verwendet man HOTP - HMAC-based OTP). Beide haben gewisse Vor- und Nachteile; TOTP ist auf Geräten, die über eine Echtzeituhr verfügen, sehr einfach umzusetzen. Auf Yubikey-ähnlichen Geräten und Tokens, die selbst ohne Stromversorgung auskommen, eignet sich HOTP aus naheliegenden Gründen besser. Ich werde mich hier auf TOTP beschränken, da die betrachteten Software-Lösungen für Mobiltelefone sowie auch das oathtool problemlos mit diesem Schema arbeiten.
Voraussetzungen am Server
Um nun eine existierende OpenVPN-Installation, die bereits auf die Gültigkeit von X.509-Zertifikaten zur Authorisierung setzt, um TOTP zu erweitern, braucht es auf dem Server drei Schritte:
- Installation der OATH-Software und des OATH-PAM-Moduls
- OpenVPN muss zur Nutzung von PAM konfiguriert werden
- PAM muss für OpenVPN TOTP akzeptieren
Für die ersten beiden Punkt genügt es, (unter Debian) die Pakete oathtool und libpam-oath zu installieren, und das von OpenVPN mitgebrachte auth-pam-Plugin zu aktivieren. Letzteres erledigt man mit einem Eintrag in der OpenVPN-Server-Konfigurationsdatei:
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so "openvpn-otp"
Das erste Argument zur plugin-Direktive ist dabei der absolute Pfad zum Shared Object, das OpenVPN PAM-fähig macht - auf anderen Distributionen könnte dieser anders lauten; im Zweifel weiß es der Paketmanager. Das zweite Argument ist interessanter: Es ist der Name des PAM-Service, das für diese OpenVPN-Server-Instanz die authoritative Konfiguration ergeben wird. Dieses PAM-Service - bzw. seine Konfiguration - gibt es auf dem System so noch nicht, und sie muss erst erstellt werden. Unter Debian (und wohl auch auf allen anderen Distributionen, die Linux-PAM verwenden) legt man dazu eine neue Datei in /etc/pam.d/ an, die den Namen des zu konstituierenden Service trägt - hier also /etc/pam.d/openvpn-otp. Das folgende Listing konfiguriert einen PAM-Stack der sicherstellt dass der vom OpenVPN-Client an den Server übermittelte Username in einer Datei namens /etc/openvpn/users_whitelist vorkommt, und dass das für diesen User übermittelte One-Time Password gegen die in /etc/openvpn/users_oath hinterlegten OATH-Daten valide ist.
# Check if the given username is in the list of allowed names auth requisite pam_listfile.so file=/etc/openvpn/users_whitelist item=user sense=allow onerr=fail # Check for users' time-based One-Time Password from their OATH token device/app auth requisite pam_oath.so usersfile=/etc/openvpn/users_oath window=10 digits=6 # Permit whitelisted usernames - if this is missing, getpwnam() will fail for non-system users account sufficient pam_listfile.so file=/etc/openvpn/users_whitelist item=user sense=allow onerr=fail
Sobald und so lange diese beiden Bedingungen erfüllt sind, wird das OpenVPN-PAM-Plugin keine Einwände gegen die Anmeldung eines Clients haben, welcher auch ein valides X.509-Zertifikat präesentieren konnte. Um nun ein Secret zu errechnen, das man im OATH-OTP-Generator seiner Wahl, sowie im im OATH-Usersfile hinterlegen kann, benutzt man `oathtool`. Durch einen Aufruf wie den folgenden erhält man ein kyryptographisch ausreichend starkes Secret in base32-Notation (wie ihn die meisten Software-Token-Generatoren wie FreeOTP erwarten), sowie in hexadezimaler Darstellung (wie es pam_oath benötigt):
$ oathtool -v --totp $(openssl rand -hex 15) Hex secret: b398741f71c6025eb9476be1d339ca Base32 secret: WOMHIH3RYYBF5OKHNPQ5GOOK [...]
OTP-Generator-Setup und -Überprüfung
Das base32-Secret füttert man nun in die OTP-App auf dem Smartphone, während man das Hex-Secret - mit dem gewünschten Usernamen verknüpft - in /etc/openvpn/users_oath hinterlegt:
#PROTO USER - SECRET COUNTER LASTOTP TS HOTP/T30 vpnuser - b398741f71c6025eb9476be1d339ca
Der Inhalt dieser Datei muss als vertraulich eingestuft werden; es empfiehlt sich, die Ownership des Inodes auf root:root, und die Permissions auf 0600 zu setzen. Wichtig ist auch, dass die Datei nicht immutable gemacht wird, da pam_oath.so hierin Accounting betreiben wird. Weiters darf die Datei kein Symlink sein, da die Inode beim Accouting durch PAM unlinked, und wieder neu geschrieben wird. Das in hexadezimaler Notation anzuschreibende Secret darf eine gewisse (undokumentierte) Länge nicht überschreiten, da sonst ein von pam_oath allozierter Buffer nicht ausreicht, um es zur Validierung zu benutzen, was in stillschweigend fehlschlagenden (pam_oath schreibt Debug-Meldungen auf stdout, welche durch das OpenVPN-PAM-Plugin verschluckt werden - zum Debuggen von pam_oath.so bindet man es am besten in den PAM-Stack fuer `su` ein, das dieses lästige Problem nicht teilt) Logins mündet. Man muss hier also eine Menge beachten, um dieses filigrane Zusammenspiel nicht empfindlich zu stören!
Weiters ist es für die Beispielkonfiguration noch notwendig, den Usernamen in die Whitelist-Datei einzufügen - dazu öffnet man /etc/openvpn/users_whitelist mit einem Texteditor, und setzt die Zeichenfolge vpnuser in eine eigene, neue Zeile - fertig.
Hat man nun in Folge das Secret so im OTP-Generator (siehe unten für eine Liste von Apps, die ich persönlich als tauglich erachte) untergebracht, dass dieser sechsstellige Zifferncodes, die sich im 30-Sekunden-Takt erneuern/verändern, ausspuckt, kann man mithilfe von `oathtool` die Gegenprobe machen, ob der am Mobiltelefon errechnete Wert zu dem, den der OATH-Stack am Server errechnet, passt:
$ oathtool --totp b398741f71c6025eb9476be1d339ca 849562 $ oathtool --totp -b WOMHIH3RYYBF5OKHNPQ5GOOK 849562
Stimmt diese Ausgabe mit der Anzeige am Software-Token innerhalb der selben halben Minute überein, hat man alles richtig gemacht, und die App sollte künftig korrekte OTPs errechnen. Dabei ist (für TOTP) selbstverständlich strikt auf sychronisierte Uhrzeiten auf allen teilnehmenden Systemen zu achten - mithilfe des window-Parameters des pam_oath-Moduls kann man bei unzuverlässigen lokalen Zeitnehmern etwas gegensteuern - die Zahl, die man diesem Parameter als Argument übergibt, bestimmt, wie viele Zeitschritte (bei TOTP/30 sind das 30 Sekunden pro Schritt) das angegebene OTP realtiv zum am Server errechneten in der Zukunft bzw. Vergangenheit liegen darf.
Anpassung der OpenVPN-Client-Konfiguration
Jetzt gilt es noch, den OpenVPN-Client dazu zu bringen, eine Eingabemöglichkeit für Usernamen und OTP bereitzustellen. Dazu fügt man die beiden folgenden Zeilen an die existierende Konfigurationsdatei des Clients an:
auth-nocache auth-user-pass
Die erste Zeile veranlasst den OpenVPN-Client dazu, die erfragte Passphrase nicht zu cachen (was im Falle von One-Time Passwords ohnehin absurd wäre, und immer eine sinnlose Warnung im Client-Log verursacht), während die zweite Zeile vor dem Aufbau der Verbindung zum Server nach Username und Passwort fragt, welche nach dem TLS-Handshake und der erfolgten Zertifikatsvalidierung verschlüsselt an den OpenVPN-Server übertragen, und dort von dessen PAM-Stack gegen die Usernamen-Whitelist und die OATH-TOTP-Daten validiert werden.
pam_oath in Aktion
Startet man nun den OpenVPN-Server neu, und versucht sich mit dem zusätzlich wie eben beschrieben konfigurierten Client anzumelden, kriegt man am Client zunachst die Frage nach Usernamen und Passwort - diese ergeben sich aus den in der user_whitelist und user_oath hinterlegten Daten, und dem aus dem OATH-Secret errechneten OTP, welches von der Smartphone-App angezeigt wird. Ist die Anmeldung mittels OTP einmal erfolgt, kann man in /etc/openvpn/users_oath das pam_oath-Modul beim Arbeiten beobachten. Hier könnte es nach einigen Logins in der Zeile für den Account vpnuser z. B. so aussehen:
#PROTO USER - SECRET COUNTER LASTOTP TS HOTP/T30 vpnuser - b398741f71c6025eb9476be1d339ca 7 865748 2015-10-26T17:33:52L
Anhang: Empfehlenswerte OTP-Generator-Apps:
- FreeOTP (Android F-Droid, Android Google Play, iOS)
- Android Token (Android F-Droid)
© 2007-2020 Johannes Truschnigg | valid xhmtl & css