Secure Boot & LUKS Auto-Unlock
Tying UEFI, TPM, and LUKS together
UEFI Secure Boot Recap
Secure Boot is a UEFI feature that enforces signature checks on every boot-time EFI binary. Before executing anything, the firmware verifies the binary's PE/COFF Authenticode signature against a database of trusted keys and hashes stored in NVRAM. Unsigned or unknown binaries simply won't run.
The Key Hierarchy in NVRAM
On every stage-transition (firmware → bootloader, bootloader → kernel when using a shim-linked chain), the previous stage checks the next binary's signature against db and ensures it is not in dbx. Failure aborts the boot.
The Linux Signed Boot Chain
Microsoft's UEFI CA is present in almost every OEM's db. Linux distros exploit this via a tiny first-stage bootloader called shim that Microsoft has signed. Shim then gets to decide what it will load — effectively delegating trust from Microsoft to each distro.
UEFI
Verifies
shimx64.efiagainstdb(signed by MS UEFI CA)shim
Embeds distro CA; verifies GRUB against it + user-enrolled MOK list
GRUB
(Signed) verifies kernel & initramfs signatures before handoff
Kernel
Enforces signed modules (
module.sig_enforce=1)initramfs
Unlocks root (TPM unseal or passphrase) →
switch_root→ init
- shim ships with every major distro (Ubuntu, Debian, Fedora, RHEL, SUSE). It embeds a distro-specific certificate used to verify the next stage.
- MOK (Machine Owner Key): a user-enrolled certificate held in a UEFI variable. Useful for self-compiled kernels or third-party drivers (NVIDIA, ZFS DKMS). Enrolled via the
MokManagerUI on the next reboot. - GRUB: the signed build of GRUB ships with a configuration that requires
--pubkeyto verify further artifacts. Distros configure it to verify kernel and initrd. - Kernel lockdown (
lockdown=integrityorconfidentiality) is typically auto-enabled when booted with Secure Boot — blocks kexec of unsigned kernels, restricts/dev/mem, etc.
Measured Boot vs Secure Boot — Orthogonal
"Don't run unsigned code."
- Firmware refuses to execute binaries missing a trusted signature.
- Stops the attack at the door.
- Provides no evidence afterwards of what ran.
- Can be turned off at the keyboard (physical access).
"Record what ran, whether trusted or not."
- Every stage extends a hash of the next into PCRs.
- Lets a third party (TPM, remote verifier) gate secrets on the result.
- Provides evidence but does not prevent anything.
- Cannot be turned off without physically replacing the TPM.
Tip
Both together is the strong configuration: Secure Boot keeps random unsigned binaries out, and measured boot produces a PCR fingerprint of exactly which signed binaries ran — so a TPM-sealed disk key only unlocks on a known, enforced, recorded chain.
Sealing a LUKS Key to PCRs
This is the mechanism that replaces typed passphrases on boot. Storage 06 showed that a LUKS2 volume has up to 32 key slots, each holding an encrypted copy of the master volume key (MK). Any slot's unwrap-key can open the volume. Adding a TPM slot means: one key slot is unlocked by a TPM-sealed secret instead of a user passphrase.
- Generate a random 256-bit key
K. Add it to a new LUKS2 key slot withcryptsetup luksAddKey. - Seal
Kwith a TPM policy bound to the current values of chosen PCRs (e.g. PCR 7 only). - Store the sealed blob in the LUKS header token area (
systemd-cryptenrolldoes this automatically — the blob lives in the LUKS2 JSON metadata). - On next boot, the initramfs asks the TPM to unseal the blob. If the current PCR values match the sealed policy,
Kis released, the slot opens, the volume unlocks, root mounts — no passphrase typed. - If anything in the measured chain differs (malicious bootloader, altered kernel, different firmware), PCRs differ, the TPM refuses to release
K, and the initramfs falls back to prompting for a passphrase.
Warning
Evil-maid defence: an attacker with brief physical access cannot just swap in a tampered GRUB — it would change PCR 4/7, the TPM would refuse to unseal, and the attack visibly degrades to "please type your passphrase", which the user will notice. This is the whole point of binding to PCRs.
systemd-cryptenroll --tpm2-device
systemd-cryptenroll is the distro-native tool for managing LUKS2 key slots backed by TPM, FIDO2, or PKCS#11 tokens. It handles all the sealing mechanics, stores the blob as a LUKS2 token, and integrates with systemd-cryptsetup at boot.
Basic Enrollment
# systemd-cryptenroll --tpm2-device=auto \
--tpm2-pcrs=7 \
/dev/nvme0n1p3
Please enter current passphrase for /dev/nvme0n1p3:
New TPM2 token enrolled as key slot 1.
Options
| Option | Meaning |
|---|---|
--tpm2-device=auto | Discovers /dev/tpmrm0 automatically. Use an explicit path to pick a specific TPM. |
--tpm2-pcrs=LIST | PCR selection, e.g. 0+2+7 or 7+11+14. Default varies by distro (often 7). |
--tpm2-with-pin=yes | Adds a user-typed PIN to the unseal policy. Two-factor: PIN + TPM presence. |
--tpm2-public-key=FILE | Seal against a signed PCR policy — policy authority, not current values. Survives updates signed by the same authority. |
--tpm2-signature=FILE | Signature blob produced for a given set of future PCR values (used with --tpm2-public-key). |
--recovery-key | Generate and print a one-time recovery key simultaneously (store it in a password manager). |
--wipe-slot=TYPE | Remove existing slots of a type (tpm2, recovery, password) before enrolling. Use to re-enroll after an update. |
Inspecting and Removing
# systemd-cryptenroll /dev/nvme0n1p3
SLOT TYPE
0 password
1 tpm2
2 recovery
# systemd-cryptenroll --wipe-slot=tpm2 /dev/nvme0n1p3
Wiped slot 1.
/etc/crypttab Entry
# <name> <device> <keyfile> <options>
root-crypt UUID=a1b2c3d4-... none tpm2-device=auto,discard
The tpm2-device=auto option tells systemd-cryptsetup to try the TPM token first. Failure falls through to interactive passphrase entry.
PCR Selection — The Critical Trade-off
Which PCRs you bind to determines two things: what kinds of tampering will block unseal, and what routine events will accidentally block unseal. There is no single right answer — the choice depends on the platform's update cadence and the threat model.
| Selection | Binds to | Breaks on | Typical use |
|---|---|---|---|
| 7 only | Secure Boot policy: PK/KEK/db/dbx + MOK list | Secure Boot toggled, dbx update, new MOK enrolled | Default. Loose but stable across kernel/bootloader updates (as long as they stay signed by trusted keys) |
| 0+2+7 | + firmware code + option ROMs | Any BIOS/UEFI update, GPU firmware update | Tighter; acceptable if firmware rarely changes |
| 4+7 or 7+12 | + bootloader image | Every grub-install / update-grub |
Only if you control bootloader updates |
| 7+8+9 | + kernel cmdline + kernel + initrd | Every update-initramfs, kernel upgrade, cmdline edit |
Tight; requires automated re-seal on every update |
| 0..9 (full) | Entire legacy boot chain | Basically any system update | Locked appliances only |
| 7+11 (UKI) | SB policy + Unified Kernel Image measurement | UKI change — but UKI is a single signed artifact | Modern best practice for systemd-boot setups |
Note
Default reasoning: PCR 7 alone is the common default because it binds to policy, not artifacts. As long as signed kernels/bootloaders continue to be signed by a key in db, they all match. An attacker who disables Secure Boot, enrolls their own MOK, or rolls back dbx will fail to unseal. This gives "policy integrity" without update pain.
Handling Updates
If the PCR selection includes anything that changes with system updates (firmware, bootloader, kernel, initramfs), the sealed policy will not match current PCRs after the update, and the TPM will refuse to unseal. The machine reboots to a passphrase prompt. You must re-enroll.
# # After a kernel update that invalidated PCR 9 binding:
# systemd-cryptenroll --wipe-slot=tpm2 \
--tpm2-device=auto --tpm2-pcrs=7+9 \
/dev/nvme0n1p3
Automating Re-seal
- Distro hooks: Ubuntu's full-disk encryption, Fedora Silverblue, and systemd-boot UKI setups precompute expected PCR values for the new kernel/initrd and re-seal automatically as part of the update transaction.
dracut-install-kernel-style hooks trigger this. - Signed PCR policies (
--tpm2-public-key): instead of sealing to a concrete PCR value, seal to "any PCR value signed by this public key". The vendor ships a signature alongside each update that authorises the new expected PCRs. No re-enroll needed on the machine. This is how modern "stable auto-unlock across updates" is built.
Unified Kernel Images (UKI)
A UKI is a single PE executable containing the kernel, initramfs, cmdline, microcode, and os-release — all signed as one artifact. UEFI loads it directly (often via systemd-boot); no GRUB, no initramfs generation on the target host.
Why it helps TPM sealing
- The kernel+initrd are a single signed file measured as a unit.
systemd-stubrecords the UKI hash into PCR 11 before entry.- Sealing to
7+11covers Secure Boot policy and the exact UKI, without the fragility of PCR 8/9 across initramfs regenerations.
Where you see it
- Fedora CoreOS, RHEL 10 roadmap, Ubuntu Core, NixOS images.
- Any systemd-boot-based server deployment pushing toward reproducible images.
- Proxmox guests can use UKIs the same way; they simplify the PCR story inside the VM.
Clevis + Tang (Network-Bound Disk Encryption)
TPM sealing binds unlock to local state. NBDE binds unlock to network presence: the disk only decrypts when the server can reach a particular internal service. Stolen hardware off-site = cannot unlock.
- Stateless HTTP server. No database.
- Advertises ECDH public keys.
- Performs a blinded ECDH exchange on request.
- Does not see the secret — cannot, by design, unseal for anyone.
- Survives restarts; can be replicated; trivial to run (systemd unit, ~50 LoC).
- Binds a LUKS slot to one or more Tang servers ("pins").
- At boot, performs the matching ECDH step to recover the unwrap key.
- Pin types:
tang,tpm2,sss(Shamir Secret Sharing). - Configured via
clevis luks bind; auto-unlocks viaclevis-luks-askpassin initramfs.
# clevis luks bind -d /dev/nvme0n1p3 tang \
'{"url":"http://tang.internal.example"}'
# # M-of-N with Shamir: need 2 of (TPM, tang1, tang2)
# clevis luks bind -d /dev/nvme0n1p3 sss '{
"t": 2,
"pins": {
"tpm2": {"pcr_ids":"7"},
"tang": [
{"url":"http://tang1.internal"},
{"url":"http://tang2.internal"}
]
}
}'
Tip
Defence in depth: Combine TPM + Tang via SSS. Stolen from the rack → no Tang network → cannot unlock. Stolen and the TPM cloned → still needs Tang. Tang compromised → still needs the TPM. This is a common pattern for on-prem server fleets.
Recovery Planning
Warning
Read this before enrolling anything. Every TPM-sealed disk needs a working recovery path. TPM hardware fails, motherboards get replaced, firmware updates invalidate policies, admins lock themselves out.
Recovery Checklist
- Always keep a passphrase slot. Before enrolling TPM,
cryptsetup luksAddKeya strong passphrase. Never let TPM be the only unlock path. - Back up the LUKS header (
cryptsetup luksHeaderBackup --header-backup-file) to offline storage. A corrupted header bricks the whole volume regardless of slot contents. - Generate a recovery key at enrollment (
--recovery-key) and store it in a password manager or a paper safe. This is a long random string, not a user-chosen passphrase — resistant to brute-force. - Document the PCR policy. Record which PCRs each host is bound to and why. After firmware/kernel changes you want to know what broke without guessing.
- TPM hardware replacement = total loss of sealed secrets. A new motherboard, a new CPU (for fTPM), or a BIOS "Clear TPM" gives you a new EK, new SRK, new everything rooted in the TPM. Nothing previously sealed is recoverable. The passphrase slot is your only way in.
- Major firmware updates: expect to re-enroll. Schedule a maintenance window where you can type the passphrase.
- Test recovery periodically. Boot with the TPM slot wiped and verify the passphrase works. Do this at least once after setup.
Putting It All Together — Secure Server Recipe
The full configuration a modern DevOps/SRE would deploy on a Proxmox host, bare-metal node, or VM with a virtual TPM:
- UEFI: Secure Boot enabled with the default distro keys. Optionally enroll a site MOK for signing custom kernel modules (ZFS, OpenVSwitch DKMS).
- Boot chain: shim → GRUB (or systemd-boot + UKI) → signed kernel. Kernel lockdown at
integritymode via Secure Boot auto-detection. - LUKS2 root with three slots:
· slot 0: strong passphrase (emergency / updates)
· slot 1: TPM-sealed under PCR 7 (7+11if UKI)
· slot 2: recovery key printed at install, stored in a password manager - Measured boot records the actual boot path into PCRs.
tpm2_eventlogavailable for incident response. - Boot behaviour: clean boot → TPM unseals silently → root mounts, no console interaction. Tampered chain or post-update PCR change → initramfs falls back to passphrase prompt → admin investigates or re-enrolls.
- Optional: Clevis + Tang with SSS threshold "TPM AND tang" for on-prem racks — disks only unlock while plugged into the internal network.
- Operational hygiene: document which hosts are enrolled against which PCR selections; automate re-seal in the post-update hook; test the passphrase fallback after every major kernel or firmware bump.
Tip
End state: a server that boots unattended with full-disk encryption, refuses to give up its root disk if its firmware or bootloader is tampered with, and still has a documented path back to recovery if hardware fails or updates invalidate the TPM seal. That is the capstone — LUKS from Storage 06, plus everything on this page, composing into a single operational posture.