Es ist jetzt schon einige Jahre her. Ich hatte mir einen neuen Laptop gekauft und habe mir überlegt, eine Full-Disk Encryption wäre nicht verkehrt. Das war das erste Mal, dass ich eine FDE aufgesetzt habe. Und weil ich nicht wäre, wer ich bin, konnte ich es natürlich nicht einfach LUKS aufsetzen und glücklich sein.
Ich hatte doch was von TPM im Datenblatt gelesen. Wäre es nicht cool (und vielleicht sinnvoll) das Encryptionpasswort im TPM zu speichern? Das scheint mit aktuellen systemd Versionen und systemd-cryptenroll
mittlerweile recht unkompliziert zu sein; damals war es das nicht. Ich musste mir die nötigen Befehle aus Beispielen und Vortragsfolien zusammenklauben.
Aber wenn das doch jetzt so einfach geht, warum dann noch einen Blogpost? Nun, erstens gibt es Menschen, die systemd meiden. Zweitens ist das weiterhin mein aktuelles Setup und dieser Post dient auch zur Dokumentation für mich. Und drittens, es ist auch schon ein paar Jahre her, ich habe diesen Blogpost einem Freund versprochen.
Die folgenden Ausführungen sind nicht als Tutorial gedacht. Sie beschreiben, wie ich meine Festplattenverschlüsselung aufgesetzt habe, sind aber nicht zwangsläufig vollständig. Ich freue mich über Diskussionen und Fragen unter folgendem Toot, kann aber keine Schritt-für-Schritt Hilfestellung geben: https://toot.queer-lexikon.net/@zombie/111714809995749183
Zum Technischen
Wie gesagt, das ist jetzt schon was her, ich kann daher nur die nötigen Befehle präsentieren und was diese tun. Nicht, wie ich damals darauf gekommen bin. Ich kann außerdem nur einen extrem groben Überblick über die TPM-Konzepte geben, mit denen ich mich rumgeschlagen habe.
TPM initialisieren
Zunächst muss einmal das TPM initialisiert werden und idealerweise Passwörter für die Standard TPM-Kontexte gesetzt werden. Hier ist das erste Beispiel zu tpm2-changeauth hilfreich.
tpm2_changeauth -c owner newpass
tpm2_changeauth -c endorsement newpass
tpm2_changeauth -c lockout newpass
Was passiert hier? Hier werden Passwörter gesetzt, aber wozu? TPM2 hat vier Root-Kontexte, das sind owner
, endorsement
, lockout
und plattform
. Der platform
Kontext enthält Informationen über das TPM selber und kann (meines Wissens) im Allgemeinen nicht geändert werden. Hierfür kann ich auch kein Authorization Passwort setzen. Für alle anderen der Kontexte haben wir mit den obigen Befehlen ein Passwort gesetzt, im Folgenden werden wir nur den owner
Kontext verwenden. Über den lockout
Kontext können wir aber z.B. das TPM resetten und das soll ja auch nicht einfach jemand machen können, der nicht ich ist.
To be safe, habe ich also Passwörter für alle Root-Kontexte vergeben, für die ich das kann. (Ich denke es erklärt sich von selbst, dass newpass jeweils durch ein sicheres Passwort ersetzt werden sollte.) Die Passwörter habe ich mir alle ausgedruckt und verwahre sie (relativ) sicher auf Totholz.
Das war noch der Teil, zu dem sich einfach Dokumentation finden ließ. Kommen wir zum komplexeren.
Encryptionkey im TPM ablegen
Den Encryptionkey im TPM abzulegen ist eigentlich ganz einfach. Wir definieren einen Non-Volatile Index und schreiben den da rein:
tpm2_nvdefine -C o -s 512 -a "ownerwrite|policyread" -L policy.dat -p <bootpass> -P <ownerpass> 0x01800000
tpm2_nvwrite 0x1800000 -i <keyfile> -P <ownerpass> -C o
Was passiert diesmal? Im ersten Befehl definieren wir den Non-Volatile Index. Dieser soll im owner
Kontext sein -C o
eine Größe von 512 Bit haben -s 512
und den Index 0x01800000
haben. Um im owner
Kontext zu schreiben, müssen wir das owner Passwort angeben -P <ownerpass>
. Mittels Attributen können wir angeben, dass der Index nur vom Owner geschrieben werden können soll, aber mittels Policy gelesen werden können soll -a "ownerwrite|policyread"
. Verbleibt noch -L policy.dat -p <bootpass>
. Hier werden die (in dem Fall read) Policy (die müssen wir vorher erzeugen, dazu gleich mehr) sowie das dazugehörige Passwort angegeben.
Der zweite Befehl ist deutlich weniger komplex. Wir schreiben in den eben definierten Index den Inhalt der Keyfile. Dies ist diejenige die für die Full-Disk Encryption verwendet wird. Wir müssen uns wieder im owner Kontext -C o
autorisieren, mittels des owner
Passworts.
Der verwendete Index 0x01800000
ist dabei der erste Index, der für den owner
Kontext vorgesehen ist. Hier könnte aber auch genauso gut 0x01801234
stehen.
Die Policy
Aber was ist denn jetzt mit dieser ominösen policy.dat
? Die Policy ist eine Menge an Bedingungen die erfüllt sein müssen, um den NV-Index zu lesen. Die zu erzeugen sind auch nur drei Befehle, aber bis ich da die richtige Kombination zusammen hatte verging einiges an Zeit und Recherche. Zunächst die drei (bzw. vier aber der letzte ist nur zum Aufräumen) Befehle:
tpm2_startauthsession -S session.ctx
tpm2_policyauthvalue -L policy.dat -S session.ctx
tpm2_policypcr -l sha256:0,2,3,7 -L policy.dat -S session.ctx
tpm2_flushcontext session.ctx
Und wieder von Anfang. Der erste Befehl startet eine Auth-Session und speichert das Handle in session.ctx
ich habe bis heute nicht verstanden, warum die folgenden Befehle die brauchen; aber sie brauchen diese.
Der zweite Befehl sagt “füge der Policy die Bedingung hinzu, dass das übergebene Passwort mit dem des zugegriffenen Objektes übereinstimmt”. Das Passwort haben wir oben im tpm2_nvdefine
mittels der -p
Flag festgelegt. Ich habe an der Stelle gelogen; das ist nicht das zur Policy gehörende Passwort. Eine Policy kann kein Passwort haben. Das angegebene Passwort ist das Authentication Passwort für den Index. Aber, da wir bei den Attributen -a [...]
weder authwrite
noch authread
angegeben haben kann man sich für den Index nicht mittels des Passwortes autorisieren. Man kann nur über den owner Kontext schreiben oder mittels der angegebenen Policy lesen. Mit diesem Befehl sagen wir jetzt aber, dass die Policy beim Lesen das angegebene Passwort, mit dem des Indexes abgleichen soll.
Der folgende Befehl fügt der Policy eine weitere Bedingung hinzu. Wir binden die Policy an die PCR-Register 0, 2, 3 und 7. Die PCR-Register sind Register des TPM, die beim Boot von den verschiedenen Komponenten (UEFI, Bootloader, Kernel, …) geschrieben werden. Wenn diese einmal geschrieben sind können sie nicht mehr verändert werden (bis zum Reboot). In diese Register werden Hashes über den Zustand der Boot-Komponenten geschrieben. So Enthält PCR 0 z.B. einen Hash über die ausgeführte Firmware; sollte diese sich ändern z.B. durch ein BIOS-Update, würde PCR 0 beim nächsten Boot einen anderen Wert enthalten – dies würde in dem Fall bedeuten, dass die Policy nicht mehr erfüllt wäre. Die Register 0, 2, 3 und 7 habe ich damals mit nur spärlicher Doku über den Zweck der einzelnen Register zusammen gesucht mittlerweile gibt es auch hier mehr Doku, z.B. im Arch Linux Wiki. Wer z.B. sicherstellen möchte, dass keine Einstellungen im UEFI geändert wurden kann noch das Register 1 dazu nehmen usw.
Wenn wir alle Befehle in der richtigen Reihenfolge ausführen, sollten wir jetzt also einen Disk-Encryptionkey im TPM haben.
Encryptionkey aus dem TPM auslesen
Wie bekommen wir den da nun wieder raus. Damit soll ja schließlich die Festplatte verschlüsselt werden. Zunächst müssen wir uns eine Policy-Session bauen. Die Befehle sehen bekannt aus unterscheiden sich aber in Details:
tpm2_startauthsession --policy-session -S session_read.ctx
tpm2_policyauthvalue -S session_read.ctx
tpm2_policypcr -l sha256:0,2,3,7 -S session_read.ctx
Dem tpm2_startauthsession
haben wir noch ein --policy-session
spendiert. Dies ist notwendig, um sich gegen eine Policy zu autorisieren. Die nächsten beiden Befehle sind ebenfalls fast identisch zu denen oben, als wir die Policy gebaut haben, nur dass ihnen der -L
Parameter fehlt, da wir ja keine Policy erstellen möchten.
Im Anschluss können wir auch schon den Non-Volatile Index lesen. Hierfür benötigen wir die Policy-Session, sowie das Passwort des Index. Anschließend schließen wir noch die Session.
tpm2_nvread 0x01800000 -P "session:session_read.ctx+<bootpass>"
tpm2_flushcontext session_read.ctx
Wenn alles richtig lief, gibt der erste dieser beiden Befehle den Inhalt des Indexes, also unsere Encryption-Keyfile aus.
Festplatten Verschlüsselung
Die Verschlüsselung der Festplatte läuft ganz normal ab. Wir füttern cryptsetup
mit den richtigen Parametern. Hierzu habe ich keine Aufzeichnungen mehr, aber der Befehl muss bei mir in etwa so ausgesehen haben:
cryptsetup --cipher=aes-xts-plain --key-file=<keyfile> --key-size=512 --type=plain open /dev/nvme0n1p2 root
Ja, ich habe eine plain dm-crypt verschlüsselte Festplatte. Ich habe damals entschieden, dass ich das Key-Management über das TPM abwickle. Bisher war das kein großes Problem, mit einem LUKS könnte ich leichter ein Backup Passwort vergeben, falls das TPM mal kaputt sein sollte, oder die PCR Register sich geändert haben. Ich würde das heute eventuell anders machen.
Festplatte entschlüsseln
Das Entschlüsseln der Festplatte brauchte etwas mehr Liebe, da diese ja zu einem sehr frühen Zeitpunkt im Bootprozess geschehen muss.
Hierzu habe ich einen initcpio Hook angelegt, der aus folgenden Zwei Dateien besteht:
/etc/initcpio/install/encrypt-tpm
#!/bin/bash
build() {
local mod
add_module "tpm"
add_module "tpm_tis"
add_binary "tpm2_startauthsession"
add_binary "tpm2_policyauthvalue"
add_binary "tpm2_policypcr"
add_binary "tpm2_nvread"
add_binary "tpm2_flushcontext"
add_binary "/usr/lib/libtss2-tcti-device.so"
add_runscript
echo "Installed TPM hook"
}
help() {
cat <<HELPEOF
This hook allows for reading the encryption key from TPM.
HELPEOF
}
Hier werden beim Generieren der initialen Ramdisk die Kernel-Module tpm
und tpm_tis
sowie die genannten Binaries hinzugefügt. Das sind alles Dinge, die wir später im Early-Boot brauchen, um auf das TPM zuzugreifen.
Die Datei /etc/initcpio/hooks/encrypt-tpm
enthält eine Funktion, die bei jedem Boot ausgeführt wird und den Encryptionkey aus dem TPM liest.
#!/bin/bash
run_hook() {
modprobe tpm_tis
tpm2_startauthsession --policy-session -S /tpm_session_discdecrypt.ctx -Q
tpm2_policyauthvalue -S /tpm_session_discdecrypt.ctx -Q
tpm2_policypcr -l sha256:0,2,3,7 -S /tpm_session_discdecrypt.ctx -Q
read -p "Encryption password:" -s tpm_pass
tpm2_nvread 0x1800000 -P "session:/tpm_session_discdecrypt.ctx+$tpm_pass" -o /crypto_keyfile.bin
tpm2_flushcontext /tpm_session_discdecrypt.ctx
rm /tpm_session_disdecrypt.ctx
}
Die Befehle sollten weitestgehend bekannt sein. Im Gegensatz zu den obigen Befehlen, ist teilweise noch ein -Q
hinzugekommen, um Ausgaben zu unterdrücken, das read
liest das Passwort von der Kommandozeile und das -o
vom tpm2_nvread
speichert den Index Inhalt in der angegebenen Datei.
Dieser Hook macht noch keine Decryption – diese wird wie gewohnt vom encrypt
Hook vorgenommen. Wenn die Datei /crypto_keyfile.bin
vorhanden ist, probiert der encrypt
Hook zunächst diese zum Öffnen der Verschlüsselung zu verwenden. Wir müssen also nur dafür sorgen, dass der encrypt-tpm
vor dem encrypt
Hook ausgeführt wird.
In meiner /etc/mkinitcpio.conf
steht also folgendes:
HOOKS=([...] encrypt-tpm encrypt [...])
Wenn wir nun das initramfs neu generieren, sollte in der Ausgabe Installed TPM hook
zu lesen sein und beim nächsten Boot das Passwort abgefragt und die Festplatte entschlüsselt werden.
Considerations
Wer seine eigene Festplatte so verschlüsseln sollte, wie hier beschrieben, sollte einige Dinge bedenken.
Secure Boot?
Das initramfs liegt unverschlüsselt und ohne Secure Boot unsigniert auf der Festplatte. Ein Angreifer, der die Möglichkeit hat, auf die Festplatte zuzugreifen, könnte den encrypt-tpm
Hook im initramfs modifizieren, um das TPM Passwort oder die Keyfile beim nächsten Boot zu extrahieren. Hiergegen sollte Secure Boot helfen, wenn das initramfs entsprechend Signiert wird müsste der Angreifer zunächst Secure Boot überwinden bzw. an den Signaturschlüssel kommen, um mit diesem Angriff erfolgreich zu sein.
crypto_keyfile.bin
Im encrypt-tpm
Hook wirkt es so, als ob die Keyfile auf die Festplatte geschrieben würde. Wir müssen aber bedenken, wir befinden uns in einem frühen Boot-Stadium, zu dem der Hook ausgeführt wird. Die Festplatte ist gar nicht gemounted. Auf /
wurde eine Instanz eines tempfs gemounted, wir schreiben hier also “nur” in den RAM. Ich habe zwar ein klein wenig IT-Sicherheits-Background, aber bin keine Expertin in dem Gebiet – eventuell ist es möglich diesen Key zu einem späteren Zeitpunkt noch aus dem RAM zu recovern. Wenn jemand so viel Zugriff auf meinen Laptop hat, ist es meiner Meinung nach aber eh schon zu spät.
Recovery
Es kann passieren, dass wir zur Boot-Zeit nicht auf den Index, der die Keyfile enthält, zugreifen können. Dies kann sein wegen eines Firmwareupdates, Änderungen in der UEFI-Config, oder ein Dual-Boot Windows, welches das TPM resettet (das tut es nicht automatisch, ich war einfach unbedacht). In dem Fall brauchen wir eine Recovery-Strategie.
Dies kann entweder mittels LUKS ein zweites Encryptionpasswort sein. Oder man legt einen zweiten Index mit komplexerem Passwort, dafür ohne PCR Binding an.
Meine Recovery-Strategie ist, im Zweifel die Keyfile per Hand einzutippen. Ich habe dazu einen Hexdump der Keyfile ausgedruckt, den ich, wenn nötig, über einen Hex-Editor wieder eintippen kann. Das funktioniert relativ okay, das war bisher zweimal nötig. Ich hatte mal vor dazu noch ein Script mit dem Hook zu installieren, dazu bin ich aber nie gekommen.
Weitere Ressourcen
Zum Schreiben dieses Posts habe ich widerholt auf die tpm2-tools
Doku zurückgegriffen. Diese ist ein guter Start, um Informationen zu den einzelnen Befehlen zu bekommen. Die zugrunde liegenden TPM Konzepte haben es leider nicht zu “Read the Docs” geschafft, obwohl sie im tpm2-tools
Repository dokumentiert sind. Mit diesen beiden Links, sowie den dort verlinkten weitern Informationen z.B. zu Spezifikationen, sollte man mittlerweile recht weit kommen.