init: fix TPM2 integrity check failure for systemd v252+ enrolled tokens (closes #233)#342
Merged
Merged
Conversation
…and PBKDF2 PIN) systemd-cryptenroll v252 began provisioning a persistent SRK at handle 0x81000001 and recording it as tpm2_srk in the LUKS2 token JSON. The old code always derived a transient primary via CreatePrimary using a well-known ECC template. When tpm2_srk is present, tpm2.Load must be called against the persistent handle — using a freshly derived primary produces a different parent key, causing the integrity check to fail. systemd v255 added a PBKDF2 salt (tpm2_salt) to PIN-protected tokens. The old code computed authValue = SHA256_trimmed(pin); the new formula is SHA256_trimmed(base64(PBKDF2-HMAC-SHA256(pin, salt, 10000, 32))). Tokens without tpm2_salt continue to use the old path. Changes: - extractSRKHandle: parses the 10-byte IESYS_RESOURCE_SERIALIZE header that systemd writes into tpm2_srk; falls back to 0x81000001 on any parse failure so pre-v252 tokens (no tpm2_srk field) still work. - tpm2PINAuthValue: centralises PIN-to-authValue derivation for both the salted (v255+) and unsalted (pre-v255) paths. - tpm2Unseal: accepts a srkHandle parameter; when non-zero it loads against the persistent handle directly without calling CreatePrimary (and without FlushContext, which would evict a persistent object). - recoverSystemdTPM2Password: reads tpm2_salt and tpm2_srk from the token JSON and passes them through the updated helpers. Note: the PIN prompt does not yet include the mapping name and falls back to the keyboard on the first wrong PIN with no retry. The FIDO2 path gained both of these (mapping name in prompt, 3-attempt retry with progressive feedback) during recent work; parity for the TPM2 PIN path is planned for a follow-up PR.
TestSystemdTPM2SRK: unlocks a LUKS2 volume enrolled with systemd v252+, which provisions a persistent SRK at 0x81000001 and records it as tpm2_srk in the token JSON. Exercises the new persistent-handle path in tpm2Unseal. TestSystemdTPM2LegacyPin: unlocks a LUKS2 volume whose token was constructed to match the v252-254 format — tpm2_srk present but no tpm2_salt, so PIN auth uses SHA256_trimmed(pin) without PBKDF2. Generated with raw tpm2-tools rather than systemd-cryptenroll to be independent of the installed systemd version; skipped when tpm2-tools are absent. Generator/infrastructure changes: - systemd_tpm2.sh: wait for swtpm readiness before enrolling (prevents fallback to /dev/tpm0 on hosts with a real TPM), use explicit TCTI string, save swtpm state back to pristine on success so the provisioned SRK persists across test runs. - swtpm.sh: fix ownership of state directory after swtpm_setup; add optional SRK pre-provisioning when tpm2-tools are available. - systemd_tpm2_legacy_pin.sh: new generator; builds the v252-254 format token using tpm2-tools and Python JSON construction. - Both generators use mktemp for temporary files to avoid collisions.
anatol
approved these changes
Apr 27, 2026
Owner
|
Fantastic improvement! Thank you again. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Users who enrolled with
systemd-cryptenrollv252 or later see:Two changes in systemd broke compatibility:
1. Persistent SRK (v252+)
systemd-cryptenrollv252 began provisioning a persistent SRK at handle0x81000001viatpm2_get_or_create_srkand recording it in the LUKS2 token JSON astpm2_srk. Booster was always deriving a transient primary viaCreatePrimary. When a persistent SRK was used during enrollment, loading the sealed object against a freshly derived primary produces a different parent key, causing the integrity check to fail.The fix avoids the
IESYS_RESOURCEdeserialization problem noted in #233 — we only need theTPM2_HANDLEembedded in the IESYS header (bytes 6–9, big-endian), which maps directly to atpmutil.Handle. Whentpm2_srkis present,tpm2.Loadis called against the persistent handle.FlushContextis intentionally skipped on the persistent handle (it would evict it from the TPM). Pre-v252 tokens with notpm2_srkcontinue to use the oldCreatePrimarypath.2. PBKDF2 PIN auth (v255+)
systemd-cryptenrollv255 added atpm2_saltfield to PIN-protected tokens. The old formula wasauthValue = SHA256_trimmed(pin); the new formula isSHA256_trimmed(base64(PBKDF2-HMAC-SHA256(pin, salt, 10000, 32))). Tokens withouttpm2_saltcontinue to use the old path.Coverage
Four QEMU integration tests cover all relevant paths:
TestSystemdTPM2(existing)TestSystemdTPM2SRK(new)TestSystemdTPM2WithPin(existing)TestSystemdTPM2LegacyPin(new)The legacy-PIN test uses raw
tpm2-toolsto construct the token independently of the installed systemd version and is skipped iftpm2-toolsare absent.Known limitation
The TPM2 PIN prompt does not yet include the mapping name and has no retry loop on wrong PIN. The FIDO2 path gained both (mapping name in prompt, 3-attempt retry with progressive feedback) in a recent PR; parity for TPM2 is planned as a follow-up.