Table of contents
Electra Secure Boot and TPM Hardening
Status: reference implementation and recovery record. Implementation and validation completed on 2026-06-29. The final post-change encrypted archive and status capture are tracked by issue 42.
Outcome: firmware Secure Boot is enforced in user mode with Microsoft compatibility certificates retained. Lanzaboote 1.1.0 signs Type #2 UKIs, interactive boot-entry editing is disabled, and recovery media, signed rollback, NVIDIA, hibernation, LUKS passphrases, firmware access, and encrypted key restoration were tested.
IGPU unattended unlock uses a managed PCR 4+7 policy. DGPU uses a separate
PCR 7 + PIN token because its firmware adds a PCR 4 event that
systemd-pcrlock cannot model unambiguously; policy regeneration is masked in
that specialisation. Root, data, and swap retain independent passphrase slots.
Goal
Protect electra against a physical-access attacker modifying the boot chain or kernel command line to obtain root access while the TPM automatically unlocks the encrypted disks.
The completed system should:
- boot only trusted, signed NixOS generations
- prevent interactive editing of systemd-boot entries
- protect Secure Boot settings with a firmware administrator password
- release TPM-backed LUKS keys only for the approved verified boot state
- retain the LUKS passphrase as an independent recovery path
- preserve normal NixOS generations, rollbacks, electra specialisations, and NVIDIA driver updates
Current state
- electra's root, data, and swap devices are LUKS encrypted
- each LUKS device has a managed PCR
4+7token for IGPU auto-unlock and a PCR7+ PIN token for DGPU unlock - Lanzaboote 1.1.0 is active and generates Type #2 UKI entries
- Secure Boot signing keys exist locally under
/var/lib/sbctlwith an encrypted off-host backup - systemd-boot interactive entry editing is disabled
- Lanzaboote refuses to install unsigned boot artifacts
- firmware Secure Boot is enabled in user mode
just tpm2-enrolrestores managed tokens andjust tpm2-enrol-pinadds the DGPU PIN fallback
Secure Boot signing and TPM measured boot are separate controls. Secure Boot
prevents untrusted EFI artifacts from executing. Lanzaboote's managed measured
boot uses systemd-pcrlock to maintain the set of approved measurements that
may release the LUKS key:
- PCR
4covers the bootloader and Lanzaboote stub; the signed stub validates the kernel, initrd, and embedded command line - PCR
7binds the policy to the Secure Boot state and authorities
The target managed policy is therefore [ 4 7 ]. Lanzaboote documents PCR 0
as an optional firmware-code binding, but Electra's removable DGPU changes its
PCR 0 value. Including it prevents the IGPU and DGPU hardware states from
sharing one policy. Static PCR 7+11+12 enrolment is not used: it bypasses
Lanzaboote's policy management and would make routine generation changes
brittle.
Safety requirements
Complete these before changing firmware keys or TPM enrolment:
- Confirm the LUKS passphrase unlocks every encrypted electra device.
- Store the LUKS passphrase independently of electra.
- Prepare bootable recovery media and confirm electra can reach it.
- Confirm the firmware administrator password is stored in the password manager.
- Back up the EFI System Partition.
- Ensure a previous known-good NixOS generation remains available.
- Archive current
bootctl,sbctl, pcrlock, signature, and LUKS-token status with the post-change recovery backup (tracked by issue 42).
Do not continue after any failed check. Secure Boot keys must never be committed to git, added to SOPS, or copied into the Nix store.
Phase 1: Prepare Lanzaboote
Status: deployed and reboot-tested successfully.
Add a pinned Lanzaboote flake input and import its NixOS module for electra. Replace direct systemd-boot installation with Lanzaboote while preserving the current loader behavior and electra's specialisation-selection hook.
Configure:
- Lanzaboote enabled for electra
- the PKI bundle at
/var/lib/sbctl sbctlavailable as an administrative package- systemd-boot entry editing disabled
Build the normal configuration and every electra specialisation before activation. Confirm that the current kernel and NVIDIA module build without errors.
Stop condition: do not change firmware settings unless the new configuration builds and the existing unsigned generation remains available as a fallback.
Rollback: boot the previous generation and revert the Lanzaboote configuration. Secure Boot is still disabled at this stage, so the previous systemd-boot generation remains usable.
Phase 2: Generate and protect keys
Status: complete. Keys and their encrypted off-host backup were verified on 2026-06-29.
Generate electra's Secure Boot keys locally:
sudo sbctl create-keys
sudo sbctl status
sudo ls -la /var/lib/sbctl
sbctl creates the Platform Key (PK), Key Exchange Key (KEK), and signature
database key (db). Their private portions remain root-only in
/var/lib/sbctl; the public certificates will later be enrolled in firmware.
Create an encrypted offline backup of the complete /var/lib/sbctl directory.
Store the backup separately from electra and record its location in the
password manager. Test that the archive can be decrypted and that it contains
the key bundle before continuing.
Stop condition: do not enrol keys without a tested encrypted backup and working LUKS recovery passphrase.
Recovery if keys are lost before enrolment: generate a new set. Recovery after enrolment requires firmware access, disabling Secure Boot or entering Setup Mode, restoring or replacing the keys, and reinstalling signed boot artifacts.
Phase 3: Install and verify signed generations
Status: complete. Generation 497 passed battery, IGPU, and DGPU reboot tests; generation 496 passed the signed rollback test, and the specialisation selector restored generation 497 as the default.
Activate Lanzaboote while firmware enforcement is still disabled. Lanzaboote must automatically construct and sign boot artifacts during each NixOS activation; routine kernel and NVIDIA updates must not require manual signing.
Verify the EFI System Partition:
sudo sbctl verify
bootctl status
Test, with Secure Boot still disabled:
- the normal electra generation
- every electra specialisation
- a previous Lanzaboote-managed generation
- return to the normal generation after each test
All bootable EFI artifacts reported by sbctl verify must be trusted. Record
and investigate any unsigned bootable artifact rather than treating it as an
expected exception.
Stop condition: do not enrol firmware keys unless all required generations boot and signature verification passes.
Rollback: boot the retained pre-Lanzaboote generation and restore the previous NixOS configuration.
Phase 4: Enrol and enable Secure Boot
Status: keys are enrolled with Microsoft compatibility certificates and generation 497 has booted successfully under enforcement in battery, IGPU, and DGPU modes. The NVIDIA driver and DGPU hibernation/resume are validated. The enforced-mode rollback test passed with generation 496, followed by a successful return to generation 497.
Set a firmware administrator password before enabling enforcement. This prevents a physical attacker from simply disabling Secure Boot or replacing its trust policy.
Enter the firmware's Secure Boot Setup Mode using the procedure appropriate to
electra's motherboard. Do not erase the forbidden-signature database (dbx).
Boot NixOS and enrol the generated keys while retaining Microsoft certificates
for Option ROM and recovery-media compatibility:
sudo sbctl enroll-keys --microsoft
sudo sbctl status
Enable Secure Boot in firmware if enrolment does not enable enforcement automatically, then boot the normal generation.
Verify:
bootctl status
sudo sbctl status
sudo sbctl verify
Secure Boot must report enabled (user), signatures must verify, and the
systemd-boot editor must be unavailable. Reboot and test the normal generation,
each specialisation, and a previous signed generation.
Stop condition: do not modify TPM enrolment until every signed boot path has worked under Secure Boot and the LUKS passphrase has been tested again.
Rollback: use the firmware administrator password to disable Secure Boot, then boot the retained unsigned generation or recovery media. Restore the key backup or revert Lanzaboote as required.
Phase 5: Validate recovery
Before tightening TPM policy:
- Boot recovery media successfully.
- Unlock each LUKS device using its passphrase.
- Confirm the encrypted Secure Boot key backup can be accessed.
- Confirm firmware settings can be entered with the stored administrator password.
- Confirm a signed previous generation boots.
- Confirm the bootloader does not permit command-line editing.
Do not proceed if any recovery route depends solely on TPM auto-unlock.
Phase 6: Bind TPM unlock to verified boot
Status: systemd-pcrlock is-supported returned yes on 2026-06-29. Managed
policy generation was initially configured for PCRs 0, 4, and 7, and the
battery, IGPU, and DGPU configurations built successfully.
The first policy-generation attempt exposed systemd's limit of eight
alternatives per PCR. Electra's five retained generations each produced
battery, IGPU, and DGPU PCR 4 measurements, for 15 alternatives. Boot
retention is therefore limited to two generations: six alternatives and one
complete rollback generation. The incomplete policy from that failed attempt
must not be enrolled.
After reducing retention, generation 499 booted successfully and
systemd-pcrlock-make-policy.service generated six alternatives for
generations 498 and 499. The policy protection mask is exactly PCRs 0, 4,
and 7, and the policy was written to the TPM NV index, policy file, and boot
credential. The static tpm2-pcrs=0+2 crypttab overrides were then removed;
root, data, and swap continued to auto-unlock successfully after reboot using
their existing TPM tokens. Root, data, and swap were subsequently migrated one
at a time to the managed policy, with their passphrase slots preserved. A full
reboot then unlocked and mounted all three volumes without a LUKS prompt.
The first DGPU boot after migration required the LUKS passphrase. Its removable
hardware changed PCR 0, and systemd-pcrlock-make-policy could not authorize
an NV policy update from the changed state. The managed policy is therefore
being corrected to PCRs 4 and 7; the signed DGPU UKI is already one of the
PCR 4 alternatives.
The DGPU firmware also measures a firmware-volume EFI application into PCR 4
before systemd-boot. Attempts to represent this as IGPU and DGPU component
variants were rejected: the shorter IGPU event sequence also matches the
suffix of the DGPU sequence, so systemd-pcrlock cannot select one variant
unambiguously. A policy generated while DGPU was active consequently contained
only PCR 7, which did not satisfy the project's boot-tampering threat model.
The selected fallback design keeps the managed PCR 4+7 policy generated in
IGPU mode and disables automatic policy regeneration in the DGPU
specialisation. Each LUKS volume receives a second TPM token bound to PCR 7
and protected by a TPM PIN. IGPU boots use unattended managed unlock; DGPU
boots require the PIN and cannot downgrade the managed policy. The independent
LUKS passphrase remains the final recovery path.
The corrected configuration was deployed from IGPU mode and the managed policy
was restored to PCRs 4 and 7. Separate PCR 7 + PIN TPM tokens were then
added to root, data, and swap without removing their managed tokens. DGPU
booted successfully using the PIN fallback, the policy-update service was
masked as designed, the stored managed policy remained PCR 4+7, and the
NVIDIA driver loaded successfully. DGPU hibernation and resume then completed
successfully with the dual-token design active.
First verify that electra supports systemd-pcrlock:
/run/current-system/systemd/lib/systemd/systemd-pcrlock is-supported
Do not proceed unless this prints yes.
Enable Lanzaboote's managed measured-boot integration:
boot.lanzaboote.measuredBoot = {
enable = true;
pcrs = [
4
7
];
};
Keep /var/lib/pcrlock.d and /var/lib/systemd/pcrlock.json persistent. Build,
boot, and confirm that Lanzaboote created the managed policy before changing
any LUKS token.
Remove the static tpm2-pcrs=0+2 crypttab option from root, data, and swap,
leaving TPM device discovery enabled. Update just tpm2-enrol to enrol each
device against the managed policy:
sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
DEVICE
For root, data, and swap, remove only the existing TPM2 enrolment and reenrol against the managed policy; preserve the passphrase slot. Inspect every LUKS header before and after the operation and migrate one device at a time.
Reboot in IGPU mode and confirm all devices auto-unlock. In DGPU mode, confirm
the managed token does not release automatically and the separate PCR 7 +
PIN token unlocks the devices.
After migration, normal Lanzaboote rebuilds update the managed policy for approved generations. Routine kernel, initrd, command-line, NVIDIA, and specialisation changes must not require manual LUKS reenrolment.
Stop condition: if normal auto-unlock fails, use the LUKS passphrase, return to the last signed working generation, inspect the generated pcrlock policy and TPM event log, and do not remove or alter the passphrase slot.
Rollback: unlock with the LUKS passphrase and repair the managed policy in IGPU
mode. Reenrol the managed token and PCR 7 + PIN token one volume at a time if
needed. Do not restore the obsolete static 0+2 policy. Secure Boot can remain
enabled while TPM policy is repaired.
Acceptance tests
bootctl statusreports Secure Boot enabled in user mode.sbctl verifyvalidates every bootable generation.- Normal, NVIDIA, specialisation, and previous-generation boots succeed.
- Interactive bootloader parameter editing is unavailable.
- The managed pcrlock policy covers PCRs
4and7. - IGPU auto-unlock and DGPU PCR
7+ PIN unlock succeed. - A routine Lanzaboote rebuild updates the policy without LUKS reenrolment.
- The DGPU measurement does not receive unattended TPM auto-unlock.
- LUKS passphrase recovery succeeds when TPM auto-unlock is unavailable.
- The encrypted key backup has been restored and inspected in a test location.
- Firmware recovery access and recovery media have been tested.
Completion work
- Mark the security-review finding resolved.
- Document Secure Boot, firmware, LUKS-header, pcrlock, and token recovery.
- Record Lanzaboote 1.1.0 and electra's IGPU/DGPU firmware behavior.
- Commit configuration and documentation without private key material.
- Create, verify, and store the post-change encrypted recovery archives off-host (tracked by issue 42).