Aus Linux-Magazin 07/2021

PKI-Workshop, Teil 4: Mehr Sicherheit durch ein Hardware Security Module

© Enrique Ramos Lopez / 123RF.com

In den vorigen drei Artikeln dieser Serie ging es um Konzepte, Software, Konfiguration und Automation einer PKI. Diesmal wenden wir uns Hardware Security Modules zu.

Wofür braucht man im PKI-Kontext spezielle Hardware? In erster Linie geht es um zwei Herausforderungen, die sich nicht mit reiner Software lösen lassen: Performance und Kopierschutz. Seit es Computer im Consumer-Bereich gibt, versuchen Spielehersteller, ihre Software vor dem unberechtigten Kopieren zu schützen. Dass sie ihre Games auf Steckmodule (zum Beispiel Gameboy) verteilen, also auf Hardware, ist sicherlich auch dem Bemühen geschuldet, das Kopieren zu verhindern.

Nicht ohne Grund haben die meisten Rechner neben einer CPU auch noch andere Processing Units (PUs), beispielsweise eine GPU. Sichere und vor allem asymmetrische Verschlüsselung ist rechenintensiv. Eine darauf spezialisierte PU entlastet die CPU von dieser Arbeit.

Es gibt aber auch Nachteile, zu denen neben den Anschaffungskosten eine eingeschränkte Flexibilität (man kann ein HSM nicht kopieren, in einem Backup sichern oder per E-Mail verschicken) und die fehlende Möglichkeit zählen, Fehler in der Hardware zu fixen. Solche Fehler kamen in der Vergangenheit durchaus schon vor und hatten zur Folge, dass die Hardware ausgetauscht werden musste.

Alle aktuellen Smartphones verwenden einen Crypto- beziehungsweise Security-Chip, der zum einen die Rechenoperationen schneller ausführt und zum anderen den privaten Schlüssel vor dem unberechtigten Auslesen und Kopieren schützt. Setzt man eine PKI im Enterprise-Umfeld ein, stellt man sicherlich mindestens dieselben Anforderungen an die Sicherheit wie bei einem Mobiltelefon.

Die Wahl des HSM

Es stellt sich also die Frage, welches Hardware Security Modul (HSM) es sein soll. Azure, AWS und Konsorten bieten HSM as Service oder CloudHSM an. Der erste Gedanke dürfte wahrscheinlich lauten: Wenn man die teure Hardware mit anderen teilt, was die Auslastung verbessert, wird das ökonomischer sein. Aktuell bietet die Azure Cloud kein HSM mit Standort in der EU an. Entscheidet man sich für die Schweiz als Cloud-Region, wählt zwei HSMs aus (eines als Backup) und beschließt, noch professionellen Support in Anspruch nehmen zu wollen (gut 800 Euro extra), ist man mit knapp 7000 Euro pro Jahr dabei. Sollte es also einen ökonomischen Vorteil geben, sticht der hier nicht wirklich ins Auge.

Losgelöst von dem ökonomischen Aspekt stellt sich aber grundsätzlich die Frage nach der Sinnhaftigkeit, Sicherheit durch Hardware erhöhen zu wollen, dann aber die Hardware aus der Hand zu geben. Wer garantiert, dass da tatsächlich ein HSM verbaut wurde und nicht nur irgendwelche Drei-Buchstaben-Behörden einen Simulator bereitstellen? Das Einzige, was man von einem HSM in der Cloud sieht, ist die Cloud-API.

Kommt man zu dem Schluss, sich eigene Hardware anschaffen zu wollen, stellt man schnell fest, dass es nicht viele namhafte Anbieter gibt. Noch vor Kurzem ging die Geschichte der Schweizer Firma Crypto AG durch die Medien [1]. Seit 1970 waren an der Firma der BND und die CIA zu 50 Prozent beteiligt und manipulierten die Hardware so, dass sie für die Nutzer keinen Schutz mehr bot. Da sieht man sich dann den Hersteller umso genauer an.

Berücksichtigt man die Anforderungen nach Support für OpenSSL (da darauf unsere PKI-Lösung basiert), einer möglichst offenen und vertrauenerweckenden Firmenkultur des Anbieters und einer möglichst erschwinglichen Hardware, landet man recht schnell beim YubiHSM2 von Yubico (Abbildung 1). Es handelt sich dabei allerdings nicht um Open Source, Backdoors in der Hardware sind deshalb auch nicht hundertprozentig auszuschließen.

Abbildung 1: Das Hardware Security Modul 2 von Yubico.

Abbildung 1: Das Hardware Security Modul 2 von Yubico.

Positiv fällt aber auf, dass der Code der Software öffentlich zugänglich ist und unter freien Lizenzen steht. Mit einmalig rund 600 Euro pro HSM liegen die Kosten deutlich unter denen der Azure Cloud. Der Autor hatte für seine Recherchen intensiven Kontakt zu den Entwicklern und erlebte die Kommunikation als sehr hilfreich. Das gab dann letztlich den Ausschlag für das YubiHSM2. Kostenlose Hardware hat der Autor wohlgemerkt nicht erhalten.

Die Integration

Im ersten Schritt geht es darum, die YubiHSM2 in unsere PKI-Lösung zu integrieren. Da das in Teil 3 der Artikelserie beschriebene Playbook OpenSSL über das Bash-Interface verwendet, gilt es, das HSM direkt aus OpenSSL anzusprechen. Das hat den Vorteil, dass man das bisherige Konzept und die Logik des Ansible-Playbook nicht verändern muss.

Es ist in der Regel immer eine gute Idee, Software dafür zu nutzen, wofür sie gedacht ist. Das lässt zwar Kreativität vermissen, hat aber zwei entscheidende Vorteile. Erstens steigert es die Update-Sicherheit, also die Wahrscheinlichkeit, dass man gefahrlos ein Upgrade einspielen kann. Zweitens findet man leichter Hilfe, wenn Probleme auftreten, da viele andere Anwender die Software genauso verwenden und auf ähnliche Probleme gestoßen sein werden.

Damit eine Integration einfach gelingt, kommt es in der Regel auf zwei Dinge an: eine durchdachte API und eine gute Dokumentation. In Bezug auf OpenSSL und das YubiHSM2 empfand der Autor den Weg als sehr steinig und wenig intuitiv. Ohne die Unterstützung der Yubico-Entwickler wäre die Integration zu einem frustrierenden Spiel aus Versuch und Irrtum ausgeartet. Auch so war die Abstimmung der APIs und Konfigurationen aufeinander übermäßig kompliziert.

Um das Zusammenspiel zwischen OpenSSL und dem YubiHSM2 zu demonstrieren, hat der Autor ein Bash-Skript-Projekt erstellt und den Code auf Github bereitgestellt [2]. Dieses Beispiel verwendet noch kein Ansible, um die Komplexität minimal zu halten und das Zusammenspiel zwischen dem YubiHSM2 und OpenSSL leichter verständlich zu machen.

Nach der Installation der notwendigen Software und dem Einstecken des HSM im Auslieferungszustand lässt sich »./demo_script.sh« sofort starten. Das Skript führt alle im Artikel beschriebenen Schritte durch und versetzt dann per Software-Reset das HSM wieder in den Auslieferungszustand zurück. Die fehlerfreie Ausführung beweist, dass alle Befehle korrekt funktionieren. Die Listings zu diesem Artikel verwenden dieselben Befehle wie das Skript, ersetzen aber die Variablen durch ihre jeweiligen Werte, um die Lesbarkeit zu erleichtern.

Software installieren

Die benötigte Software findet sich bei Fedora im RPM-Repository. Auf der Projektseite von Yubico gibt es aber auch Pakete für die Distributionen CentOS und Ubuntu [3]. Für unser Beispiel benötigen wir die Pakete yubihsm-shell, yubihsm-connector, openssl und openssl-pkcs11.

Nach deren Installation starten Sie den Connector mit dem Befehl aus der ersten Zeile von Listing 1. Ab jetzt sollte über das Tool »yubihsm-shell« die Verbindung mit dem HSM funktionieren. Ruft man das Tool ohne Parameter auf, gelangt man in einen interaktiven Modus. Wir verwenden hier aber immer den nicht interaktiven Modus, um am Ende für die Automatisierung reproduzierbare automatische Skripte zu erhalten. Die lassen sich dann in einem zweiten Schritt in einer Ansible-Automatisierung verwenden.

Listing 1

Neues Passwort setzen

$ systemctl start yubihsm-connector
$ yubihsm-shell \
  --action=put-authentication-key \
  --object-id=2 \
  --label="Audit auth key" \
  --capabilities="all" \
  --object-type="all" \
  --delegated="all" \
  --new-password="DasNeuePasswort" \
  --authkey="1" \
  --password="passwort"

Im Auslieferungszustand ist das YubiHSM2 mit dem allgemein bekannten Standardpasswort »password« gesichert, das Sie als Erstes ändern. Der Parameter in der dritten Zeile von Listing 1 zeigt an, dass ein neuer »authentication-key« zu erstellen ist. Zeile 4 legt die ID des neuen Keys fest, Zeile 5 definiert ein aussagekräftiges Label (zur leichteren Zuordnung seiner Funktion).

Sehr wichtig ist Zeile 8, die es erlaubt, mit diesem Passwort weitere Objekte zu erstellen. Es wäre also auch möglich, ein Passwort zu erstellen, mit dem man existierende Schlüssel verwenden, sie aber nicht löschen oder neu erstellen kann. Zeile 9 setzt das neue Passwort. Zeile 10 legt fest, dass für diese Operation das Passwortobjekt mit der ID 1 (das Auslieferungspasswort) verwendet werden soll, um die Legitimität der Operation zu prüfen. In Zeile 11 folgt dann das Passwort dieses Objekts.

Im zweiten Schritt löschen Sie mit dem neu erstellten Passwortobjekt das alte Auslieferungspasswort (Listing 2). Die Objekt-ID lässt sich in verschiedenen Formaten übergeben, entweder wie hier als einstellige Zahl (»4«), mit führenden Nullen (»0004«) oder als Hexadezimalzahl (»0x0004«). Die maximale Anzahl der Objekte, die sich im YubiHSM2 speichern lassen, beträgt 256. Weitere Details zu den Konzepten, Formaten und Begrifflichkeiten des Moduls nennt die Dokumentation des Herstellers [4].

Listing 2

Vorgabepasswort löschen

yubihsm-shell \
  --action=delete-object \
  --object-id="1" \
  --object-type="authentication-key" \
  --authkey="2" \
  --password="DasNeuePasswort"

Private Key erstellen

Jetzt ist das HSM mit einem eigenen Passwort geschützt, und Sie können den privaten Schlüssel anlegen, den Sie für die Signaturen brauchen. In Listing 3 gibt Zeile 2 an, dass ein neues Objekt des Typs »asymmetric-key« erstellt werden soll. Als ID dient die »4« (Zeile 3). Dass wir hier eine Zahl überspringen, spielt keine Rolle, solange die ID noch nicht vergeben wurde. Als Algorithmus kommt RSA mit in der Länge 2048 zum Einsatz (Zeile 5).

Die Verwendungsmöglichkeiten des Keys gibt Zeile 6 an. Hier gäbe es die Möglichkeit, das Flag »exportable-under-wrap« anzugeben. Es erlaubt, einen privaten Key aus dem HSM zu exportieren, etwa zu Backup-Zwecken. Ob man das zulässt, hängt vom Gesamtkonzept der PKI ab. Allerdings lässt sich auch kopieren, was sich exportieren lässt – das untergräbt aber eigentlich die Grundidee von HSMs. Eine sinnvollere Alternative wäre ein Hardware-Backup in Form eines zweiten Moduls, damit beide sich gegenseitig cross-zertifizieren können [5]. Daraus resultieren freilich doppelte Anschaffungskosten. Durch die Cross-Zertifizierung kann das zweite HSM aber jederzeit für das erste einspringen.

Listing 3

Asymmetrischen Schlüssel erstellen

$ yubihsm-shell \
  --action=generate-asymmetric-key \
  --object-id=4 \
  --label=""root_ca_sign_key"" \
  --algorithm="rsa2048" \
  --capabilities=sign-pkcs,sign-pss,sign-ecdsa,sign-eddsa,sign-ssh-certificate \
  --authkey="2" \
  --password="DasNeuePasswort"

Anbindung an OpenSSL

Um das YubiHSM2 aus OpenSSL heraus zu verwenden, müssen Sie noch die OpenSSL-Konfiguration anpassen. Mit OpenSSL und seiner Konfiguration könnte man schon einen eigenen Artikel füllen, weswegen wir hier nur auf die Besonderheiten in Bezug auf die Anbindung des HSM eingehen.

Zunächst benötigen Sie eine Konfiguration für das YubiHSM2 selbst, die Sie in der Datei »yubihsm_pkcs11.conf« ablegen (Listing 4). Sie enthält in der ersten Zeile die Adresse, unter der sich das HSM erreichen lässt, und in der zweiten den Debug-Schalter, mit man für den Problemfall mehr Infos bekommt.

Listing 4

YubiHSM2-Konfiguration

connector = http://127.0.0.1:12345
debug

Die Konfiguration von OpenSSL fällt mit gut 100 Zeilen deutlich umfangreicher aus. Die vollständige Konfiguration finden Sie im Git-Repo unterhalb des Ordners »configs/«, Listing 5 zeigt nur den wichtigsten Teil. Zeile 4 definiert eine Sektion, die die Engines konfiguriert, die OpenSSL verwenden soll. In dieser Sektion verweist Zeile 7 auf eine Sektion, die den Umgang mit PKCS11 regelt. Zeile 10 legt die Engine-ID fest, und Zeile 11 teilt OpenSSL mit, in welchen Pfad das Modul liegt, das diese Funktionalität anbietet.

Listing 5

OpenSSL-Konfiguration (Ausschnitt)

openssl_conf = default_modules
[default_modules]
engines = engine_section
[engine_section]
pkcs11  = pkcs11_section
[pkcs11_section]
engine_id = pkcs11
MODULE_PATH = /usr/lib64/pkcs11/yubihsm_pkcs11.so
INIT_ARGS  = connector=http://127.0.0.1:12345 debug
# PIN format: <auth key id><auth key password>
PIN = "0002password2"
init  = 0
[ca]
default_ca  = CA_default
[CA_default]
dir   = ./tmp/
certs = ./tmp/
crl_dir = ./tmp/
database = ./tmp/root_index
new_certs_dir = ./tmp/
certificate  = ./tmp/root_ca.crt.pem
serial  = ./tmp/serial
crlnumber = ./tmp/crlnumber
crl = ./tmp/crl.pem
# Key format: <slot>:<key id>
private_key = 0:0004

Die Zeilen 12 bis 15 enthalten Parameter, die das Skript an das Modul durchreicht, inklusive des Verweises, wo der Connector die Verbindungen für das HSM entgegennimmt (Zeile 12). Das erfolgt wieder im Debug-Modus. Zeile 14 erscheint dem Autor etwas fragwürdig: Als PIN dient hier die Key-ID zusammen mit dem Passwort. Es gibt jedoch kein Trennzeichen, das erkennen lässt, wo die ID aufhört und das Passwort anfängt. Man muss die Key-ID deshalb zwingend vierstellig mit führenden Nullen schreiben, denn bei den ersten 16 Bit handelt es sich um die Key-ID.

In Zeile 31 steht an der Stelle, wo man normalerweise in der OpenSSL-Konfiguration den Pfad des privaten Schlüssels angibt, die Key-ID des Schlüssels auf dem YubiHSM2. Davor steht durch einen Doppelpunkt abgetrennt der Slot, in dem der Key gespeichert ist. Die Dokumentation spricht neben dem Slot auch von der Domain oder von der Logical Partition. Nach diesem Konzept organisiert das YubiHSM2 Objekte.

Der Hersteller empfiehlt, von mehreren Slots Gebrauch zu machen, wenn sich mehrere Anwendungen ein HSM teilen, um Zugriffskonflikte zu vermeiden. In diesem Fall ist das nicht notwendig, wir verwenden hier immer den Slot 0. Es wäre möglich, bis zu 16 Slots zu benutzen. Durch den Doppelpunkt als Trenner muss man hier die führenden Nullen bei der Key-ID nicht zwingend angeben. Angesichts der an sich schon inkonsistenten Syntax der OpenSSL-Konfiguration wollte der Autor aber wenigstens bei der Schreibweise der Key-IDs konsequent bleiben und setzte hier deshalb ebenfalls führende Nullen.

YubiHSM2 und OpenSSL

Nach dem Anpassen der Konfiguration lässt sich das YubiHSM2 per OpenSSL verwenden. Der Aufruf dazu unterscheidet sich nur geringfügig von dem im Ansible-Code (Listing 6), der im letzten Artikel zur Sprache kam.

Bis zu Zeile 7 ist der Befehl in Listing 7 gleich. Zeile 8 teilt OpenSSL mit, dass es für die Operation eine Engine namens »pkcs11« verwenden soll. Zeile 9 informiert darüber, dass der zu verwendende Schlüssel von dieser Engine stammt, und Zeile 10 gibt den zu verwendenden Schlüssel an. Zeile 11 legt den Speicherort für das Ergebnis fest.

Listing 6

Ausschnitt aus dem Playbook

- name:     Generate a Self Signed OpenSSL root certificate
  shell:    |
            openssl req \
            -new \
            -x509 \
            -days {{ pki_root_ca.not_after_days }} \
            -nodes \
            -config {{ pki_opensslconfig }} \
            -extensions v3_ca \
            -key {{ pki_priv_key }} \
            -out {{ pki_cert_file }}
 args:
   chdir:   "{{ pki_private_dir }}"
   creates: "{{ pki_cert_file }}"

Listing 7

Selbstsigniertes Root-CA-Certificate

$ openssl req \
  -new \
  -x509 \
  -days 9125 \
  -nodes \
  -config ./configs/root_ca/openssl.cnf \
  -extensions v3_ca \
  -engine pkcs11 \
  -keyform engine \
  -key "0:0004" \
  -out ./configs/root_ca.crt.pem

Das Prinzip bleibt immer dasselbe, Sie müssen die OpenSSL-Befehle nur minimal anpassen. Folglich halten sich auch die Änderungen am Ansible-Playbook aus dem vorigen Artikel in engen Grenzen. Listing 8 erstellt noch einen Certificate Signing Request (CSR) für eine Intermediate-CA, den dann in Listing 9 die Root-CA signiert. Der Befehl aus der ersten Zeile von Listing 9 erzeugt eine von OpenSSL zwingend vorausgesetzte Index-Datei.

Listing 8

CSR für eine Intermediate-CA

$ openssl req \
  -new \
  -sha256 \
  -nodes \
  -config ./configs/issue_ca/openssl.cnf \
  -engine pkcs11 \
  -key "0:0005" \
  -keyform engine \
  -out ./configs/issue_ca.csr.pem

Listing 9

Signierung einer Intermediate-CA

$ touch ./configs/root_index
$ openssl ca \
  -config ${DEMO_CONFIG_DIR}/root_ca/openssl.cnf \
  -days 99 \
  -notext \
  -batch \
  -rand_serial \
  -in ./configs/issue_ca.csr.pem \
  -cert ./configs/root_ca.crt.pem \
  -engine pkcs11 \
  -keyform engine \
  -key "0:0004" \
  -out ./configs/issue_ca-v2.crt.pem

YubiHSM2 resetten

Es gibt zwei Methoden, um den YubiHSM2 wieder in den Auslieferungszustand zurückzusetzen. Üblicherweise erledigt man das wie in Listing 10 per Software-Reset. Kennt man das Passwort nicht mehr, hilft ein Hardware-Reset, bei dem man das eingesteckte HSM mehr als zehn Sekunden lang mit dem Finger berührt. Ein Blinken signalisiert, dass die Operation erfolgreich war.

Listing 10

YubiHSM2 zurücksetzen

yubihsm-shell \
  --action=reset \
  --domains=0 \
  --authkey=2 \
  --password="DasNeuePasswort"

Verwendung in Containern

Da der Connector des HSM auf http://localhost:12345 lauscht, lässt sich das Modul aus einem Container nur mit dem Parameter »–network host« verwenden, der dafür sorgt, dass der Container das Host-Netzwerk verwendet. Alternativ könnte man das Modul an eine von außen erreichbare IP-Adresse binden. Dann sollte man aber auch dringend ein Zertifikat in der YubiHSM2-Konfiguration hinterlegen und HTTPS verwenden. Alternativ könnte man zum Beispiel Nginx als Proxy vorschalten und darüber TLS abbilden. Auf diese Weise ließe sich das YubiHSM2 auch via Netzwerk an einen Service in der Cloud durchreichen.

Fazit

Dieser Artikel beendet unsere kleine Serie, die aufzeigte, was eine PKI ist und welche Probleme man damit lösen kann. Sie haben erfahren, welche Werkzeuge aktuell zur Umsetzung zur Verfügung stehen. Am Beispiel von Ansible wurde demonstriert, wie sich eine Lösung mit hohem Automatisierungsgrad umsetzen lässt. Zu guter Letzt wissen Sie nun auch, wie Sie die Sicherheit durch ein Hardware Security Module noch weiter erhöhen können. (jcb/jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben