# TPM Root Unlock on Halley2 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 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. ## Prepare 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 mkinitcpio -P sudo sbctl verify just tpm-nvpcr-check ``` `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. 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 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 ``` ## 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 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}))}' ``` 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. 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 sudo cryptsetup token remove --token-id 0 /dev/nvme0n1p2 just tpm-unlock-check --trial ``` ## Test Boot the normal Hardened entry once. Confirm automatic unlock, then run: ```sh efibootmgr | rg 'BootCurrent|BootOrder' just tpm-nvpcr-check just tpm-unlock-check --trial systemctl --failed --no-pager ``` 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. 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 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 ``` 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 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 ``` ## Finish 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 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 ``` 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. 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 cryptsetup token import --token-id 0 \ --json-file /var/lib/dotfiles/tpm-root/old-token.json /dev/nvme0n1p2 ``` 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. 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).