summaryrefslogtreecommitdiffstatshomepage
path: root/docs/tpm-unlock.md
diff options
context:
space:
mode:
authorsommerfeld <sommerfeld@sommerfeld.dev>2026-09-30 08:54:59 +0100
committersommerfeld <sommerfeld@sommerfeld.dev>2026-09-30 08:54:59 +0100
commit6739045b173cc1be1f46bdf4b6167cbf9a883089 (patch)
tree955161ee64eba8d06cf1492b8c903bae76b967b6 /docs/tpm-unlock.md
parentc6cf3bc7e20e43c61b48d417b9291e5d7d115b1d (diff)
downloaddotfiles-6739045b173cc1be1f46bdf4b6167cbf9a883089.tar.gz
dotfiles-6739045b173cc1be1f46bdf4b6167cbf9a883089.tar.bz2
dotfiles-6739045b173cc1be1f46bdf4b6167cbf9a883089.zip
Require signed PCR 11 policy for TPM root unlock
Diffstat (limited to 'docs/tpm-unlock.md')
-rw-r--r--docs/tpm-unlock.md429
1 files changed, 128 insertions, 301 deletions
diff --git a/docs/tpm-unlock.md b/docs/tpm-unlock.md
index 8815639..d60b5e7 100644
--- a/docs/tpm-unlock.md
+++ b/docs/tpm-unlock.md
@@ -1,351 +1,178 @@
-# TPM Unlock on Arch
+# TPM Root Unlock on Halley2
-The root volume uses a systemd TPM enrollment with a `pcrlock` policy that
-protects PCR 7. Keep a working disk passphrase and the policy recovery PIN in
-a secure location accessible without unlocking this laptop.
+Before this migration, the root token requires the approved PCR 7 pcrlock
+policy. The new enrollment also requires an RSA signed PCR 11 policy at
+`enter-initrd`. The `luks-root` policy reference is for root unlock; the
+existing `initrd` reference remains for NvPCR setup. The root volume is
+`/dev/nvme0n1p2`. Keep the tested disk passphrase and pcrlock recovery PIN
+available away from this laptop.
-The policy, enrollment, and generated component files are host state. Do not
-copy them into this repo. Deploy this configuration only after policy creation,
-enrollment, and an automatic unlock test have succeeded.
+The signing key, pcrlock policy, and LUKS metadata are host state. Do not put
+them in this repo. Each kernel update must build and Secure Boot sign a UKI
+with both PCR 11 signatures. A signed policy approves older UKIs that use the
+same signing key and `luks-root` reference; it does not prevent rollback to
+an older approved version.
-## Deploy
+## Prepare
-Run on the Arch host, outside aibox:
+Run these steps on Halley2, outside aibox. Stop if a check fails. No step in
+this section changes the LUKS header.
```sh
+pacman -Q systemd systemd-ukify mkinitcpio
+sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
+sudo /usr/lib/systemd/systemd-pcrlock --strict=yes --pcr=7 --location=770 predict
+df -h /boot
just apply
-sudo systemctl daemon-reload
-sudo systemctl enable systemd-pcrlock-make-policy.service
-sudo systemctl restart systemd-pcrlock-make-policy.service
-sudo journalctl -b -u systemd-pcrlock-make-policy.service --no-pager
+sudo mkinitcpio -P
+sudo sbctl verify
+just tpm-nvpcr-check
```
-The journal must show PCR 7 in the protection mask, or report that the policy
-has not changed. Check for errors and unmet conditions. A successful command
-exit alone does not prove that a conditional service ran.
-
-The service runs after root is unlocked. It updates the existing TPM policy
-and boot credential from the approved component files. It selects only PCR 7
-and requires the policy files and the mounted EFI System Partition at `/boot`.
-On a host without an existing policy, the service skips execution.
-
-Leave `systemd-pcrlock-secureboot-policy.service` and
-`systemd-pcrlock-secureboot-authority.service` disabled. These services generate
-component files from the current system. This setup retains the approved
-components instead of automatically accepting changes to Secure Boot settings.
-
-## Updates
+`systemd` and `systemd-ukify` must have the same version. The normal
+`mkinitcpio -P` build replaces all four maintained UKIs. The checker verifies
+their Secure Boot signatures, initrd services, and both PCR 11 signatures
+against the measured contents of each image. It does not claim that the LUKS
+token has changed. Before enrollment, check that the initrd has no separate
+root key file or credential that could bypass the TPM test.
-Normal kernel and initramfs updates do not require LUKS re-enrollment. Keep the
-existing mkinitcpio and Secure Boot signing hooks. Policy maintenance does not
-build or sign UKIs.
-
-After a systemd update, check the prediction before rebooting:
+Back up the header and old TPM token in the encrypted root file system. These
+files must stay root owned and must not go to the ESP or the repo. Check the
+current LUKS metadata before using the token and slot numbers below. On this
+host the existing TPM token is 0, its keyslot is 1, and the passphrase uses
+slot 0. Stop if these facts have changed.
```sh
-sudo /usr/lib/systemd/systemd-pcrlock --pcr=7 --location=770 predict
+sudo cryptsetup luksDump --dump-json-metadata /dev/nvme0n1p2 | jq '{keyslots: (.keyslots|keys), tokens: (.tokens|with_entries(.value |= {type, keyslots, "tpm2-pcrs", tpm2_pcrlock, tpm2_pubkey_pcrs, tpm2_pubkey_ref}))}'
+sudo install -d -m 0700 /var/lib/dotfiles/tpm-root
+sudo cryptsetup luksHeaderBackup /dev/nvme0n1p2 \
+ --header-backup-file /var/lib/dotfiles/tpm-root/before.header
+sudo install -m 0600 /dev/null /var/lib/dotfiles/tpm-root/old-token.json
+sudo cryptsetup token export --token-id 0 \
+ --json-file /var/lib/dotfiles/tpm-root/old-token.json /dev/nvme0n1p2
```
-If PCR 7 is included and the prediction completes without errors, refresh the
-policy and check the journal:
+## Enroll
+
+Enroll a new keyslot with two required TPM policy shards. Do not request a
+direct PCR value or a TPM PIN. Enrollment on the running system cannot test
+the `enter-initrd` signature because PCR 11 has changed since that phase.
```sh
-sudo systemctl restart systemd-pcrlock-make-policy.service
-sudo journalctl -b -u systemd-pcrlock-make-policy.service --no-pager
+sudo systemd-cryptenroll /dev/nvme0n1p2 \
+ --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-pcrs= \
+ --tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
+ --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
+ --tpm2-public-key-pcrs=11 \
+ --tpm2-public-key-policyref=luks-root --tpm2-with-pin=no
+sudo cryptsetup luksDump --dump-json-metadata /dev/nvme0n1p2 | jq '{keyslots: (.keyslots|keys), tokens: (.tokens|with_entries(.value |= {type, keyslots, "tpm2-pcrs", "tpm2-pcr-bank", tpm2_pcrlock, tpm2_pubkey_pcrs, tpm2_pubkey_ref}))}'
```
-Prediction uses the current boot log. It cannot guarantee that changed firmware
-or boot software will produce the same measurements on the next boot. Keep the
-disk passphrase available. No unattended hook relaxes protection or replaces
-LUKS enrollments.
-
-## NvPCR Measurements
-
-This setup initializes the four systemd NvPCRs. It records hardware identity,
-the logged-in user, and the LUKS root unlock method and keyslot. It also records
-the root volume key in ordinary PCR 15. The verity NvPCR has no event on this
-host because it does not use dm-verity. Root unlock still uses only the existing
-PCR 7 `pcrlock` policy. The NvPCR indices are separate from that policy's index.
-
-Ukify signs an initrd PCR 11 policy with reference `initrd` and embeds its public
-key in each UKI. The `sbctl` mkinitcpio post hook signs the complete UKI for
-Secure Boot. Keep the PCR private key outside the repo and EFI System Partition
-(ESP). Do not regenerate it during updates.
-
-### First Activation
-
-Run these steps on Halley2. Keep the LUKS passphrase and pcrlock recovery PIN
-available without unlocking this laptop. Do not reboot until the requested test
-is agreed. Stop if any command fails.
-
-1. Install the declared Arch packages. A full Arch update may rebuild UKIs. If
- it does, verify Secure Boot signing and automatic unlock on a separate,
- approved reboot before taking the recovery copy below.
-
- ```sh
- sudo pacman -Syu --needed systemd-ukify efibootmgr sbsigntools
- pacman -Q systemd systemd-ukify mkinitcpio
- sudo sbctl verify
- sudo /usr/lib/systemd/systemd-pcrlock --strict=yes --pcr=7 --location=770 predict
- df -h /boot
- ```
-
- `systemd` and `systemd-ukify` must have the same package version. Check that
- `/boot` can hold one more complete hardened UKI and temporary replacement
- files. Leave enough free space for normal kernel updates.
-
-2. Save the current boot images and affected `/etc` files in a root-only backup.
- Do this once, before `just apply`. Do not overwrite this backup on a retry.
-
- ```sh
- sudo bash -euo pipefail -c '
- backup=/var/lib/dotfiles/tpm-nvpcr/backup
- test ! -e "$backup"
- install -d -m 0700 "$backup"
- cp -a /boot/EFI/Linux/arch-linux-hardened*.efi \
- /boot/EFI/Linux/arch-linux-lts*.efi "$backup/"
- tar -C / -cpf "$backup/etc.tar" \
- etc/nvpcr/cryptsetup.nvpcr etc/nvpcr/hardware.nvpcr \
- etc/nvpcr/login.nvpcr etc/nvpcr/verity.nvpcr \
- etc/systemd/system/systemd-pcrlogin@.service \
- etc/systemd/system/systemd-pcrproduct.service \
- etc/mkinitcpio.conf.d/60-no-nvpcr.conf \
- etc/mkinitcpio.conf etc/kernel/cmdline \
- etc/kernel/cmdline-linux-hardened
- '
- ```
-
-3. Copy and check a working recovery UKI. Use `--create-only` so this entry does
- not change the normal EFI boot order. Read its entry number from `efibootmgr -v`.
-
- ```sh
- sudo cp -a /boot/EFI/Linux/arch-linux-hardened.efi \
- /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi
- sudo sbverify --cert /var/lib/sbctl/keys/db/db.pem \
- /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi
- sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
- --label 'Arch NvPCR Recovery' \
- --loader '\EFI\Linux\arch-linux-hardened-nvpcr-recovery.efi'
- sudo efibootmgr -v
- ```
-
- On an approved reboot, select this entry once with `efibootmgr --bootnext`
- or the firmware menu. Confirm automatic unlock. Return to the normal entry.
- The recovery copy is outside all mkinitcpio presets.
-
-4. Create a dedicated PCR signing key pair if neither file exists. Ukify refuses
- to replace existing key files. Check permissions and that the keys match.
-
- ```sh
- sudo ukify --pcr-private-key=/etc/systemd/tpm2-pcr-private-key.pem \
- --pcr-public-key=/etc/systemd/tpm2-pcr-public-key.pem genkey
- sudo stat -c '%U:%G:%a %n' /etc/systemd/tpm2-pcr-private-key.pem
- sudo openssl pkey -in /etc/systemd/tpm2-pcr-private-key.pem -pubout |
- sudo cmp - /etc/systemd/tpm2-pcr-public-key.pem
- ```
-
- The private file must be `root:root:600`. If either file already exists,
- verify the pair and do not run `genkey` again. Never copy the private key
- into the repo, an initramfs, or the ESP.
+Confirm that the new TPM token has a different token ID and keyslot, and
+requires `tpm2_pcrlock`, `tpm2_pubkey_pcrs: [11]`, and
+`tpm2_pubkey_ref: "luks-root"`. Keep slot 0. If there is no new token, stop.
-5. Apply the repo files and remove only the prior masks. The deployment checks
- the key pair, ukify version, and recovery image before it changes `/etc`.
- Chezmoi does not delete files that were removed from its source directory;
- `tpm-nvpcr-unmask` checks their exact old contents before removing them.
+Remove only the old token metadata. Its keyslot remains for reversible
+rollback until the boot tests pass. The new token is now the only automatic
+unlock path. This command assumes the old token is still ID 0.
- ```sh
- just apply
- just tpm-nvpcr-unmask
- ```
-
-6. Build and sign all four candidate UKIs on the root filesystem. These
- commands do not replace the active boot images.
-
- ```sh
- sudo install -d -m 0700 /var/lib/dotfiles/tpm-nvpcr/stage
- sudo mkinitcpio -k /boot/vmlinuz-linux-hardened \
- -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
- --cmdline /etc/kernel/cmdline-linux-hardened \
- --ukiconfig /etc/kernel/uki.conf
- sudo mkinitcpio -k /boot/vmlinuz-linux-hardened \
- -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened-fallback.efi \
- --cmdline /etc/kernel/cmdline-linux-hardened \
- --ukiconfig /etc/kernel/uki.conf -S autodetect
- sudo mkinitcpio -k /boot/vmlinuz-linux-lts \
- -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts.efi \
- --cmdline /etc/kernel/cmdline --ukiconfig /etc/kernel/uki.conf
- sudo mkinitcpio -k /boot/vmlinuz-linux-lts \
- -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts-fallback.efi \
- --cmdline /etc/kernel/cmdline --ukiconfig /etc/kernel/uki.conf \
- -S autodetect
- just tpm-nvpcr-check \
- /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
- /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened-fallback.efi \
- /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts.efi \
- /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts-fallback.efi
- ```
-
- The check reads `.pcrpkey`, `.pcrsig`, and `.cmdline` using ukify. It uses
- `lsinitcpio` to check the four required initrd units and verifies each
- Secure Boot signature with the enrolled `sbctl` certificate. Stop on any
- mismatch.
-
-7. Copy only the checked hardened default image to a separate ESP path and
- create a one-time test entry. Leave the normal and recovery images intact.
- Read the new entry number from `efibootmgr -v`; do not assume it is `0002`.
-
- ```sh
- sudo install -m 0644 \
- /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
- /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
- just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
- sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
- --label 'Arch NvPCR Test' \
- --loader '\EFI\Linux\arch-linux-hardened-nvpcr-test.efi'
- sudo efibootmgr -v
- ```
-
- Only after these commands succeed, set `BootNext` to that entry and perform
- one approved reboot. Keep the disk passphrase available. Confirm automatic
- unlock, `BootCurrent` equal to the test entry, and the checks below. The
- normal boot order remains unchanged. Do not publish the other images until
- the test passes. If the test fails, boot the normal or recovery entry and
- inspect the journal before trying again.
-
-8. After the one-time test passes, copy each checked image to the ESP under a
- temporary name, then rename it
- into place before copying the next one. This needs space for only one extra
- image at a time. Do not reboot after a partial copy. Restore from the backup
- if a copy or verification fails.
-
- ```sh
- sudo bash -euo pipefail -c '
- for name in arch-linux-hardened.efi arch-linux-hardened-fallback.efi \
- arch-linux-lts.efi arch-linux-lts-fallback.efi; do
- install -m 0644 "/var/lib/dotfiles/tpm-nvpcr/stage/$name" \
- "/boot/EFI/Linux/$name.next"
- mv -f "/boot/EFI/Linux/$name.next" "/boot/EFI/Linux/$name"
- done
- '
- sudo sbctl verify
- just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened.efi \
- /boot/EFI/Linux/arch-linux-hardened-fallback.efi \
- /boot/EFI/Linux/arch-linux-lts.efi \
- /boot/EFI/Linux/arch-linux-lts-fallback.efi
- ```
+```sh
+sudo cryptsetup token remove --token-id 0 /dev/nvme0n1p2
+just tpm-unlock-check --trial
+```
-### Boot Checks
+## Test
-After the one-time test boot, confirm automatic unlock and run:
+Boot the normal Hardened entry once. Confirm automatic unlock, then run:
```sh
-just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
-sudo env SYSTEMD_PAGER=cat /usr/lib/systemd/systemd-pcrlock \
- --strict=yes --pcr=7 --location=770 predict
-sudo journalctl -b --no-pager \
- -u systemd-tpm2-setup-early.service \
- -u systemd-pcrnvdone.service \
- -u systemd-pcrproduct.service \
- -u systemd-pcrlogin@1000.service \
- -u systemd-cryptsetup@root.service \
- -u systemd-pcrlock-make-policy.service
+efibootmgr | rg 'BootCurrent|BootOrder'
+just tpm-nvpcr-check
+just tpm-unlock-check --trial
systemctl --failed --no-pager
-systemd-analyze nvpcrs
```
-The early setup and NvPCR separator must succeed. The `hardware`, `login`, and
-`cryptsetup` records must appear after their events; `verity` can stay at its
-initial value. Check that PCR 7 is still the only disk-unlock policy PCR. After
-publishing all four images, reboot into the normal hardened entry and run
-`just tpm-nvpcr-check` with no image arguments. Boot-test hardened and LTS
-default and fallback UKIs with explicit approval.
-Then rebuild with `sudo mkinitcpio -P` and repeat a normal boot test. Keep the
-recovery UKI until these checks pass.
+The image check verifies all four maintained UKIs without booting each one.
+The token check verifies header metadata. The main boot proves that the
+combined token can unlock the root volume.
-After the final boot test, verify the disk passphrase without activating a new
-mapping. Keyslot 0 is the passphrase slot on Halley2; check the LUKS header if
-this test fails. Do not type the passphrase on the command line.
+For a refusal test, make a copy of the main UKI without the `luks-root`
+approval. The helper keeps the `initrd` approval and the measured sections.
+Re-sign the modified image for Secure Boot, then check the new signature. The
+unsigned intermediate file remains on the encrypted root file system.
```sh
-sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
+sudo python3 -m scripts.tpm_negative_uki \
+ /boot/EFI/Linux/arch-linux-hardened.efi \
+ /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
+sudo sbsign --key /var/lib/sbctl/keys/db/db.key \
+ --cert /var/lib/sbctl/keys/db/db.pem \
+ --output /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi \
+ /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
+sudo sbverify --cert /var/lib/sbctl/keys/db/db.pem \
+ /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi
+sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
+ --label 'Arch Root Policy Refusal Test' \
+ --loader '\EFI\Linux\arch-linux-hardened-no-root-approval.efi'
+sudo efibootmgr
```
-If the test succeeds, remove the temporary recovery and test boot entries and
-UKIs. Check that `BootCurrent` is 0000 and that entries 0001 and 0002 still
-refer to these exact files before running the removal commands. The normal
-hardened and LTS entries must stay in `BootOrder`.
+Read the new EFI entry number. Set it for one boot with
+`sudo efibootmgr --bootnext NNNN`. The test must request the disk passphrase.
+Enter it and check the boot journal for the TPM policy failure. If it unlocks
+automatically, stop: another automatic path still exists. The normal boot
+order must stay unchanged.
+
+After the refusal test, remove only that test entry and UKI, using its actual
+entry number. Boot the normal Hardened entry again if needed. Keep the
+passphrase available.
```sh
-efibootmgr -v
-sudo efibootmgr -b 0001 -B
-sudo efibootmgr -b 0002 -B
-sudo rm -- /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi \
- /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
-efibootmgr
-sudo sbctl verify
+sudo efibootmgr -b NNNN -B
+sudo rm -- /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi
+sudo rm -- /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
just tpm-nvpcr-check
+just tpm-unlock-check --trial
```
-The recovery copy is not updated by `mkinitcpio`. Keeping it on the ESP leaves
-an old signed boot path that can unlock under the current PCR 7 policy. The
-NvPCRs record boot events, but the root TPM enrollment does not require their
-values. Removal of these two copies does not prevent booting another old UKI
-signed by a trusted Secure Boot key. Keep the disk passphrase and the root-only
-backup for recovery from external media. Audit the LUKS TPM tokens separately
-before claiming that boot-image measurements are required for unlock.
-
-### Rollback
+## Finish
-If a new image does not boot or automatic unlock fails, select the recovery
-entry and use the disk passphrase if needed. On the running host, restore the
-saved images and exact `/etc` files. Do not remove any TPM indices or keyslots.
+When both tests pass, remove only the old, unbound TPM keyslot. This host used
+slot 1. Confirm the slot number again before running the command. Do not use a
+type-wide `--wipe-slot=tpm2` operation.
```sh
-sudo install -d -m 0700 /var/lib/dotfiles/tpm-nvpcr
-sudo touch /var/lib/dotfiles/tpm-nvpcr/rollback
-sudo bash -euo pipefail -c '
- cp -a /var/lib/dotfiles/tpm-nvpcr/backup/arch-linux-hardened*.efi \
- /var/lib/dotfiles/tpm-nvpcr/backup/arch-linux-lts*.efi /boot/EFI/Linux/
-'
-sudo tar -C / -xpf /var/lib/dotfiles/tpm-nvpcr/backup/etc.tar
-sudo rm -f /etc/kernel/uki.conf /etc/initcpio/install/nvpcr
-sudo systemctl daemon-reload
-sudo sbctl verify
+sudo cryptsetup luksDump /dev/nvme0n1p2
+sudo cryptsetup luksKillSlot /dev/nvme0n1p2 1
+just tpm-unlock-check
+sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
+sudo cryptsetup luksHeaderBackup /dev/nvme0n1p2 \
+ --header-backup-file /var/lib/dotfiles/tpm-root/final.header
+sudo mkinitcpio -P
+just tpm-nvpcr-check
```
-Revert this task's repo commit before another `just apply`. The rollback marker
-blocks redeployment until then. Keep the key pair and root-only backup for
-diagnosis; the temporary recovery UKI is only for first activation.
-
-Sources: [systemd 262 release notes](https://github.com/systemd/systemd/releases/tag/v262),
-[ukify](https://github.com/systemd/systemd/blob/v262/man/ukify.xml), and
-[systemd TPM measurement guide](https://github.com/systemd/systemd/blob/v262/docs/TPM2_PCR_MEASUREMENTS.md).
+On a later approved reboot, confirm normal automatic unlock. Keep the final
+header backup and the passphrase. Remove the pre-migration backup and token
+export only after that boot succeeds.
## Recovery
-If automatic unlock fails, use the disk passphrase. Inspect the boot journal
-and `systemd-pcrlock log` before changing policy. Do not clear the TPM, remove
-the policy, or remove password slots.
-
-After confirming that a Secure Boot change was intended, regenerate its
-component files:
-
-```sh
-sudo /usr/lib/systemd/systemd-pcrlock lock-secureboot-policy
-sudo /usr/lib/systemd/systemd-pcrlock lock-secureboot-authority
-sudo /usr/lib/systemd/systemd-pcrlock --pcr=7 --location=770 predict
-```
-
-Proceed only if PCR 7 is included. Update the existing policy with its recovery
-PIN when the old policy no longer permits updates:
+If automatic unlock fails, use the disk passphrase. During the trial, restore
+the old token if needed, after checking that token ID 0 is free. This restores
+the previous, weaker automatic unlock policy. The old keyslot must still exist.
```sh
-sudo /usr/lib/systemd/systemd-pcrlock \
- --pcr=7 --location=770 --recovery-pin=query make-policy
+sudo cryptsetup token import --token-id 0 \
+ --json-file /var/lib/dotfiles/tpm-root/old-token.json /dev/nvme0n1p2
```
-Enter the policy recovery PIN, not the disk passphrase. Do not record this
-session or share the PIN. Reboot to test automatic unlock. An update to the
-existing policy normally needs neither re-enrollment nor a UKI rebuild.
+If the LUKS header is damaged, use the saved header and passphrase from an
+external recovery system. Header restore replaces all current token and
+keyslot changes; do not use it for an ordinary policy failure. Do not clear
+the TPM or its NV indices.
-Reference: [systemd-pcrlock manual](https://man.archlinux.org/man/systemd-pcrlock.8.en).
+For an intended Secure Boot change, review the PCR 7 prediction before
+updating its policy. See [systemd-pcrlock](https://man.archlinux.org/man/systemd-pcrlock.8.en),
+[systemd-cryptenroll](https://man.archlinux.org/man/systemd-cryptenroll.1.en),
+and [cryptsetup-token](https://man.archlinux.org/man/cryptsetup-token.8.en).