1 21 Electra Secure Boot
Nimmo edited this page 2026-07-01 19:41:48 +01:00

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+7 token for IGPU auto-unlock and a PCR 7 + 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/sbctl with 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-enrol restores managed tokens and just tpm2-enrol-pin adds 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 4 covers the bootloader and Lanzaboote stub; the signed stub validates the kernel, initrd, and embedded command line
  • PCR 7 binds 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
  • sbctl available 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 status reports Secure Boot enabled in user mode.
  • sbctl verify validates every bootable generation.
  • Normal, NVIDIA, specialisation, and previous-generation boots succeed.
  • Interactive bootloader parameter editing is unavailable.
  • The managed pcrlock policy covers PCRs 4 and 7.
  • 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).