Habe ich TPM?

Eine einfache Prüfung, ob Linux beim Booten ein TPM erkannt und initialisiert hat, besteht darin, den Kernel-Ringpuffer nach der Zeichenkette „tpm“ zu durchsuchen:

journalctl -k --grep=tpm

Auf einem Lenovo t480s wird zum Beispiel unter anderem folgende Meldung ausgegeben:

Jul 26 12:00:00 mylaptop kernel:
tpm_tis IFX0763:00: 2.0 TPM (device-id 0x1B, rev-id 16)

Auf einer mit libvirt und QEMU-KVM erstellten virtuellen Maschine mit „swtpm“ als virtuellem TPM-Device wird unter anderem ausgegeben:

Jul 26 12:00:00 myvm kernel:
ACPI: TPM2 0x000000009BBD2CDC 00004C
(v04 BOCHS BXPC 00000001 BXPC 00000001)

Wenn das TPM vom Linux-Kernel erkannt und initialisiert wurde, dann gibt es zwei zugehörige Character-Devices in /dev:

ls -ld /dev/tpm*

Die Ausgabe sollte wie folgt aussehen:

crw-rw---- 1 tss root 10, 224 4. Aug 00:35 /dev/tpm0
crw-rw---- 1 tss tss 253, 65536 4. Aug 00:35 /dev/tpmrm0

Via /dev/tpm0 erhält ein einzelner Prozess direkten Zugriff auf das TPM; /dev/tpmrm0 ermöglicht parallelen Prozessen und Anwendungen das Arbeiten mit dem TPM über den „In-Kernel Resource-Manager“. Ob es sich um ein TPM der Version 2.0 handelt, kann wie folgt ermittelt werden:

cat /sys/class/tpm/tpm0/tpm_version_major

Erwartete Ausgabe:

2

Welche Linux-Software benötige ich für TPM?

Um die Entwicklung von Anwendungen für TPM zu vereinfachen, hat die TCG den „TPM2 Software Stack“, „TSS2“ verabschiedet. Dieser spezifiziert APIs auf höheren Abstraktionsebenen (unter anderem die “Feature API”, “FAPI” und die “Enhanced System API”, “ESAPI”), des weiteren TCTI und den “TPM Access Broker and Resource Manager”, “TAB/RM”. Von der TSS FAPI und ESAPI gibt es eine Open-Source-Implementation namens „tpm2-tss“.

Für OpenSSL Version 1 gibt es die Engine „tpm2-tss-engine“ und für Version 3 den Provider „tpm2-openssl“. Diese ermöglichen es, die Funktionen von OpenSSL für die Erzeugung von privaten Schlüsseln, das Auslesen öffentlicher Schlüssel und das Erzeugen von Certificate Signing Requests mit TPM zu nutzen.

Unter Linux wird für die Einbindung der kryptografischen TPM-Funktionen in geläufige Programme wie „OpenVPN“ oder „wpa-supplicant“ die PKCS11-Schnittstelle verwendet. Die Brücke schlägt dabei die Open-Source-Bibliothek „tpm2-pkcs11“, die ein PCKS11-Modul mit dem TPM als Speicherort für „virtuelle“ Slots und Token implementiert. Dadurch können „wpa-supplicant“, „OpenVPN“ und andere TLS-Anwendungen einen privaten Schlüssel mittels einer „pkcs11-id“ mit der selben Konfigurations-Syntax anfordern, wie sie es beim Zugriff auf ein Smartcard-Lesegerät tun würden. „tpm2-pkcs11“ bietet ein zentrales Werkzeug „tpm2_ptool“ an, das die Token- und Schlüssel-Verwaltung vereinfacht.

Ein Ressource-Manager unterstützt mehrere parallel laufende Anwendungen dabei, Handles auf Objekten zu unterhalten. Hier gibt es zwei Varianten, den „In-Kernel Resource Manager“ (erreichbar als Device /dev/tpmrm0 für TPM-Device /dev/tpm0) und eine Implementation im Userspace, den „TPM2 Access Broker & Resource Manager“, „tpm2-abrmd“.

An dieser Stelle eine Bemerkung, um Frustration vorzubeugen: Die Werkzeuge zum Umgang mit TPM2 auf Linux stammen aus verschiedenen Quellen und sind oft nicht sonderlich gut aufeinander abgestimmt – das ist bei der Verwendung ihrer Kommandozeilen-Optionen spürbar; auch die Dokumentation (READMEs, man-Pages usw.) mutet zuweilen wie Stückwerk an.

Die in diesem Artikel dargestellten Beispiele wurden auf Debian GNU/Linux Version 12 „bookworm“ erfolgreich getestet. Die folgenden Pakete wurden installiert:

  • tpm2-tools (für tpm2_clear, tpm2_getcap, tpm2_readpublic u.a.)
  • openssl (OpenSSL Version 3)
  • tpm2-openssl (Engine-Erweiterung für OpenSSL Version 3)
  • libtpm2-pkcs11-tools (für tpm2_ptool)
  • opensc (für pkcs11-tool)

Die für die Ver- und Entschlüsselung von Datenträgern benötigte Software wird im dritten Teil dieser Artikelserie vorgestellt.

Wie benutze ich TPM mit Linux?

Eigenschaften des TPM auslesen

Das folgende Kommando zeigt alle statischen Eigenschaften eines TPM an:

tpm2_getcap properties-fixed

Die Ausgabe ist hexadezimal formatiert, zum Beispiel (Auszug):

TPM2_PT_FAMILY_INDICATOR:
raw: 0x322E3000
value: "2.0"
...
TPM2_PT_VENDOR_STRING_1:
raw: 0x534C4239
value: "SLB9"
...

Das folgende Kommando zeigt alle Variablen, also zur Laufzeit änderbaren Eigenschaften eines TPM an:

tpm2_getcap properties-variable

Ein interessanter Teil der Ausgabe sind die persistenten Einstellungen des TPM:

TPM2_PT_PERSISTENT:
ownerAuthSet:                 0
endorsementAuthSet:           0
lockoutAuthSet:               1
reserved1:                    0
disableClear:                 0
inLockout:                    0
tpmGeneratedEPS:              0
reserved2:                    0

Daraus folgt nebst anderem, dass auf diesem TPM keine Auth-Policies für Owner- und Endorsement-Hierarchie gesetzt sind (ownerAuthSet und endorsementAuthSet sind 0), und dass ein Zurücksetzen des TPM mit „tpm2_clear“ nicht verboten ist (disableClear ist 0).

Werkzeuge installieren und TPM zurücksetzen

Benötigte Werkzeuge und Engines werden installiert:

apt-get install libtpm2-pkcs11-tools libtpm2-pkcs11-1 tpm2-openssl

Es wird ein Speicherort für die SQLite-Datenbank von „tpm2-pkcs11“ festgelegt:

export TPM2_PKCS11_STORE=/etc/tpm2/pkcs11
mkdir -p "$TPM2_PKCS11_STORE"

Das TPM wird zurückgesetzt, insbesondere wird die aktuelle Storage-Hierarchie verworfen (Vorsicht, das löscht natürlich auch alle Bitlocker-Schlüssel, oder was auch immer sich aktuell auf dem TPM befindet):

tpm2_clear
rm -f "$TPM2_PKCS11_STORE"/tpm2_pkcs11.sqlite3

Sollte das Ausführen von tpm2_clear scheitern, zum Beispiel weil eine Auth-Policy für die Owner-Hierarchie gesetzt ist (ownerAuthSet ist 1 in der Ausgabe von tpm2_getcap properties-variable), dann kann ein Zurücksetzen durch einen Reboot erzwungen werden. Dafür wird das „Physical Presence Interface“, „PPI“ des TPM angewiesen, beim nächsten Systemstart (vor dem Start des Betriebssystems) eine am Rechner physisch anwesende Person um Bestätigung des Zurücksetzens des TPMs zu bitten.

Dieser Vorgang wird bei Bedarf wie folgt eingeleitet:

echo 5 > /sys/class/tpm/tpm0/ppi/request
reboot

Während des Neustarts wird der Benutzer aufgefordert, der Löschung der Daten in TPM zuzustimmen. Auf einem Lenovo t480s sieht der Dialog etwa so aus:


Beispiel für einen Bestätigungsdialog zum Zurücksetzen des TPMs via Physical Presence Interface, PPI.

RSA-Schlüsselpaar erzeugen, signieren und via PKCS11 verwenden

Sobald das TPM zurückgesetzt wurde, wird ein „virtueller“ PKCS11-Slot mit der ID 1 angelegt; dabei wird in $TPM2_PKCS11_STORE eine neue SQLite-Datenbank angelegt:

tpm2_ptool init

Das Kommando sollte folgende Ausgabe erzeugen:

action: Created
id: 1

tpm2-pkcs11 hat ein Autorisierungsmodell, bei dem zwischen einem „User“ und einem „Security-Officer“ unterschieden wird. Wird ein neues PKCS11-Token mit tpm2-pkcs11 angelegt, dann sind für diese beiden Rollen Passwörter („userpin“ und „sopin“) festzulegen. Das User-Passwort ist für zukünftige Zugriffe auf das Token und den oder die darin enthaltenen Schlüssel notwendig; das Passwort des Security-Officers wird verwendet, um das User-Passwort nachträglich zu ändern:

tpm2_ptool addtoken \
--pid=1 \
--userpin=1234 --sopin=5678 \
--label=test

Als nächstes wird ein 2048-Bit RSA-Schlüsselpaar erzeugt, dessen privater Schlüssel nicht im Klartext extrahiert werden kann. Zur Wiedererkennung durch spätere Kommandos wird es mit einem Label „test“ ausgestattet; zu beachten ist hierbei, dass der private Key nur dann mit einem Label ausgestattet wird, wenn man dies mit der Option --key-label ... ausdrücklich anfordert:

tpm2_ptool addkey \
--algorithm=rsa2048 \
--label=test \
--key-label=test \
--userpin=1234

Für spätere Operationen auf diesem RSA-Schlüssel mit Werkzeugen, die die TSS-ESAPI verwenden (im Folgenden z.B. OpenSSL), wird ein „TSS-Schlüssel“ nach test.pem exportiert (den Dateinamen, das Label des Schlüssels gefolgt von .pem, wählt tpm2_ptool automatisch); der TSS-Schlüssel ist dabei selbst mit einem Passwort geschützt, das in der Variablen passphrase gespeichert wird:

passphrase=$(
tpm2_ptool export \
--label=test \
--key-label=test \
--userpin=1234 | \
grep "object-auth" | cut -d' ' -f2-
)

Als nächstes wird ein X.509 Certificate Sigining Request aus dem TSS-Schlüssel generiert (Hinweis dazu: Die gezeigte Provider-basierte Syntax funktioniert mit OpenSSL Version 3, für OpenSSL Version 1 müsste -engine tpm2tss statt -provider ... geschrieben werden). Der OpenSSL-Provider „tpm2“ greift auf die Signaturfunktion des TPM zu:

openssl req \
-new -provider tpm2 \
-provider base \
-key test.pem \
-passin "pass:$passphrase" \
-subj "/CN=test" \
-out "test.req"

Die PKCS11-Schlüssel-ID des RSA-Schlüsselpaars wird ermittelt; diese wird für den späteren Import und die richtige Zuordnung des zugehörigen signierten Zertifikats benötigt (Achtung: Das gezeigte Verfahren mit grep zur Extraktion der Schlüssel-ID ist nicht geeignet bzw. muss verfeinert werden, sobald mehr als ein Schlüssel verwaltet werden soll):

key_id=$(
pkcs11-tool \
--module /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so \
--list-objects --login --pin 1234 | \
grep ID | uniq | awk '{ print $2 }'
)

Der nächste Schritt wird hier nicht gezeigt: Der CSR wird der CA vorgelegt und von dieser signiert. Das Ergebnis ist eine Datei test.crt, die in das aktuelle Verzeichnis kopiert wird. Das signierte Zertifikat wird dem existierenden Schlüsselpaar zugeordnet:

tpm2_ptool addcert \
--label=test \
--key-id="$key_id" \
test.crt

Ergebnis: Schlüssel und Zertifikat sind gespeichert, was wie folgt geprüft werden kann:

tpm2_ptool listobjects \
--label test

Die Ausgabe sollte so aussehen (zum Beispiel, die Schlüssel-ID unterscheidet sich):

- CKA_CLASS: CKO_PRIVATE_KEY
CKA_ID:
- '33323532383465613435393461343939'
CKA_KEY_TYPE: CKK_RSA
CKA_LABEL: test
id: 1
- CKA_CLASS: CKO_PUBLIC_KEY
CKA_ID:
- '33323532383465613435393461343939'
CKA_KEY_TYPE: CKK_RSA
CKA_LABEL: test
id: 2
- CKA_CLASS: CKO_CERTIFICATE
CKA_ID:
- '33323532383465613435393461343939'
CKA_LABEL: test
id: 3

Schlüssel und Zertifikat können nun von Anwendungen verwendet werden. Wird eine „Token-URL“ für das PKCS11-Token benötigt, dann kann diese wie folgt ermittelt werden:

p11tool --list-token-urls | grep =test

Die Ausgabe sollte in etwa so aussehen (model und manufacturer sind dabei gerätespezifisch):

pkcs11:model=...;manufacturer=...;serial=...;token=test

Auch ein nachträglicher Tausch des Zertifikats ist möglich, zum Beispiel, wenn es erneuert werden muss. Dafür wird das zuvor aus test.crt importierte Zertifikat gelöscht, wobei Kenntnis der User-Passphrase nötig ist:

pkcs11-tool \
--module /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so \
--login --pin 1234 \
--delete-object \
--type cert \
--label test

Danach wird das neue (zum Beispiel von der CA verlängerte) Zertifikat new.crt hinzugefügt. Für diesen Schritt ist Kenntnis der Schlüssel-ID nötig, die wie oben gezeigt mit pkcs11-tool --list-objects ... ermittelt werden kann:

tpm2_ptool addcert \
--label=test \
--key-id="$key_id" \
new.crt

„DA Lockout Mode“ zurücksetzen und Parameter ändern

Werden im vorangegangenen Beispiel das Passwort des Users oder das des Security-Officers zu oft falsch eingegeben, dann wechselt das TPM in den „DA Lockout Mode“. Dieser kann wieder zurückgesetzt werden:

tpm2_dictionarylockout --clear-lockout

Das folgende Kommando zeigt den Parameter TPM2_PT_MAX_AUTH_FAIL (legt fest, nach wie vielen Fehlversuchen der Lockout Mode aktiviert wird) in dezimaler Schreibweise:

tpm2 getcap properties-variable | \
perl -ne '
/TPM2_PT_MAX_AUTH_FAIL: (.*)/ && do printf "%d", hex($1)
'

Das Kommando tpm2_dictionarylockout kann die Parameter der Brute-Force-Erkennung ändern. Der folgende Befehl ändert die Anzahl der tolerierten Fehlversuche auf 100:

tpm2_dictionarylockout \
--setup-parameters \
--max-tries=100

OpenSSH-Schlüsselpaar in TPM erzeugen und verwenden

Das nächste Beispiel baut auf dem Vorangegangenen auf: Das Verzeichnis für die TPM2- PKCS11-Datenbank existiert und wurde als Umgebungsvariable TPM2_PKCS11_STORE exportiert; mit tpm2_ptool addtoken ... wurde ein virtueller PKCS11-Slot angelegt wurde, der die ID 1 hat.

Durch nochmaliges Aufrufen von tpm2_ptool init wird ein weiterer Slot angelegt:

tpm2_ptool init

Das Kommando sollte folgende Ausgabe erzeugen:

action: Created
id: 2

Als nächstes wird auf dem Slot ein Token angelegt; dabei können eigene Passwörter für User und Security-Officer verwendet werden:

tpm2_ptool addtoken \
--pid=2 \
--userpin=1111 \
--sopin=9999 \
--label=ssh

Nun wird ein privater Schlüssel für OpenSSH erzeugt; als kryptographischer Algorithmus kommt „ecc256“ (256-Bit ECC NIST Curve) zum Einsatz; durch die Verwendung des Labels „ssh“ wird dieser im zuvor erstellten Token mit ID 2 angelegt:

tpm2_ptool addkey \
--label=ssh \
--userpin=1111 \
--algorithm=ecc256

Bei Interesse kann die TPM2-PKCS11-Datenbank nach Objekten mit einem gegebenen Label durchsucht werden; dafür ist das User-Passwort erforderlich:

pkcs11-tool \
--module /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so \
--token-label ssh \
--login \
--pin 1111 \
--list-objects

Die Ausgabe sollte die folgende Form haben:

Private Key Object; EC
  label:
  ID:                          ...
  Usage:                       decrypt, sign
  Access:                      sensitive, always sensitive, never extractable, local
  Allowed mechanisms:          ECDSA,ECDSA-SHA1,ECDSA-SHA256
Public Key Object; EC EC_POINT 256 bits
  EC_POINT:                    ...
  EC_PARAMS:                   ...
  label:
  ID:                          ...
  Usage:                       encrypt, verify
  Access:                      local

Der folgende Befehl speichert alle via TPM2-PKCS11-Modul abrufbaren öffentlichen Schlüssel Zeile für Zeile im Public-Key-Format von OpenSSH:

ssh-keygen \
-D /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so | \
tee tpm.pub

Da im vorangegangenen Beispiel bereits ein 2048-Bit RSA-Key erstellt wurde, wird dessen öffentlicher Schlüssel in der Ausgabe enthalten sein. Die Ausgabe auf Schlüssel mit bestimmten Slots, Token oder Labeln einzuschränken, ist mit den Optionen von ssh-keygen nicht möglich. Also wird tpm.pub etwa folgenden Aufbau haben:

ssh-rsa ...Base-64... /CN=test
ecdsa-sha2-nistp256 ...Base-64...

In diesem Beispiel ist natürlich klar, welches der zu verwendende Schlüssel ist. Also wird nur die zweite Zeile in tpm.pub behalten. Außerdem wird auf die Datei der Zugriffs-Modus 0600 gesetzt (das ist für den folgenden Aufruf von ssh-add erforderlich, da dieser sonst mit einer Fehlermeldung abbricht):

sed -i /ssh-rsa/d tpm.pub
chmod 600 tpm.pub

Sollte noch kein OpenSSH-Agent laufen, wird dieser jetzt gestartet:

eval $(ssh-agent)

Schließlich wird der Schlüssel dem Agenten hinzugefügt (für diese Operation ist Kenntnis des User-Passworts erforderlich, in diesem Beispiel 1111):

ssh-add -s /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so tpm.pub

Damit ist der OpenSSH-Schlüssel einsatzbereit. Der öffentliche Schlüssel, wie er in der Datei tpm.pub enthalten ist, kann nun auf entfernten OpenSSH-Servern der Datei ~/.ssh/authorized_keys hinzugefügt werden.

Um sich ausdrücklich mit diesem Schlüssel anzumelden, kann die Option -i ... des OpenSSH-Clients verwendet werden:

ssh -i tpm.pub user@entfernter.server.test

Ausblick

Im dritten und letzten Teil dieser Artikelserie werden Möglichkeiten gezeigt, wie TPM beim Ver- und Entschlüsseln von Datenträgern behilflich sein kann. Dabei wird auch das “Sealing” mit Platform Configuration Registers (PCRs) demonstriert.

Tilman Kranz
Tilman Kranz arbeitet seit 2018 bei B1 Systems GmbH als Trainer und Consultant mit Schwerpunkt Datei- und Verzeichnisdiensten (aber in Wahrheit interessiert ihn alles, was Linux ist). Er benutzt fast ausschließlich Linux, und freut sich deshalb, wenn alles funktioniert.

This site is registered on wpml.org as a development site. Switch to a production site key to remove this banner.