summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--docs/tpm-unlock.md51
-rw-r--r--etc/mkinitcpio.conf.d/60-no-nvpcr.conf9
-rw-r--r--etc/nvpcr/cryptsetup.nvpcr0
-rw-r--r--etc/nvpcr/hardware.nvpcr0
-rw-r--r--etc/nvpcr/login.nvpcr0
-rw-r--r--etc/nvpcr/verity.nvpcr0
-rw-r--r--etc/systemd/system/systemd-pcrlogin@.service0
-rw-r--r--etc/systemd/system/systemd-pcrproduct.service0
-rw-r--r--tests/test_nvpcr_masks.py43
9 files changed, 103 insertions, 0 deletions
diff --git a/docs/tpm-unlock.md b/docs/tpm-unlock.md
index d499928..0cd915b 100644
--- a/docs/tpm-unlock.md
+++ b/docs/tpm-unlock.md
@@ -59,6 +59,57 @@ 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.
+## Unused NvPCR Measurements
+
+This host uses PCR 7 for root unlock. It does not use the default `cryptsetup`,
+`hardware`, `login`, or `verity` NvPCR measurements. Systemd 262 requires a signed
+initrd PCR policy for NvPCR initialization, which this boot setup does not provide.
+This is separate from the NV index that stores the `pcrlock` policy.
+
+Empty files in `etc/nvpcr/` mask the four vendor definitions. Empty unit files
+mask only `systemd-pcrproduct.service` and `systemd-pcrlogin@.service`. Keep these
+files at zero bytes: adding comments removes the mask. SRK setup and PCR 7
+policy maintenance remain active. No TPM index or LUKS keyslot is deleted.
+The mkinitcpio drop-in includes the masks in each rebuilt initramfs.
+
+Deploy outside aibox, with the recovery passphrase available:
+
+```sh
+just apply
+sudo mkinitcpio -P
+sudo sbctl sign-all
+sudo sbctl verify
+```
+
+Stop if image generation or signing fails. Confirm that the rebuilt UKIs are
+signed before rebooting. No re-enrollment is required. The current boot's failed
+setup status can remain until the next boot; do not restart TPM setup merely
+to clear that status.
+
+After an approved reboot, confirm automatic unlock and inspect:
+
+```sh
+sudo journalctl -b --no-pager \
+ -u systemd-tpm2-setup-early.service \
+ -u systemd-tpm2-setup.service \
+ -u systemd-pcrlock-make-policy.service
+systemctl --failed --no-pager
+systemctl is-enabled systemd-pcrproduct.service systemd-pcrlogin@.service
+```
+
+The two measurement units must report `masked`. SRK setup must have no NvPCR
+initialization failures, and the policy must retain PCR 7 protection.
+
+To roll back, remove the four `/etc/nvpcr/*.nvpcr` masks listed above, the two
+empty unit files, and `/etc/mkinitcpio.conf.d/60-no-nvpcr.conf`. Remove the same
+files from the repo to prevent redeployment. Reload systemd, rebuild and sign
+the UKIs, and test at the next approved reboot. This restores the vendor
+behavior, including its errors if signed initrd PCR policies are still absent.
+
+Sources: [systemd 262 release notes](https://github.com/systemd/systemd/releases/tag/v262),
+[NvPCR setup](https://github.com/systemd/systemd/blob/v262/src/tpm2-setup/tpm2-setup.c),
+and [empty-file mask handling](https://github.com/systemd/systemd/blob/v262/src/basic/conf-files.c).
+
## Recovery
If automatic unlock fails, use the disk passphrase. Inspect the boot journal
diff --git a/etc/mkinitcpio.conf.d/60-no-nvpcr.conf b/etc/mkinitcpio.conf.d/60-no-nvpcr.conf
new file mode 100644
index 0000000..06b94ff
--- /dev/null
+++ b/etc/mkinitcpio.conf.d/60-no-nvpcr.conf
@@ -0,0 +1,9 @@
+# Keep the unused NvPCR definitions and consumers masked in the initramfs.
+FILES+=(
+ /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
+)
diff --git a/etc/nvpcr/cryptsetup.nvpcr b/etc/nvpcr/cryptsetup.nvpcr
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/nvpcr/cryptsetup.nvpcr
diff --git a/etc/nvpcr/hardware.nvpcr b/etc/nvpcr/hardware.nvpcr
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/nvpcr/hardware.nvpcr
diff --git a/etc/nvpcr/login.nvpcr b/etc/nvpcr/login.nvpcr
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/nvpcr/login.nvpcr
diff --git a/etc/nvpcr/verity.nvpcr b/etc/nvpcr/verity.nvpcr
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/nvpcr/verity.nvpcr
diff --git a/etc/systemd/system/systemd-pcrlogin@.service b/etc/systemd/system/systemd-pcrlogin@.service
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/systemd/system/systemd-pcrlogin@.service
diff --git a/etc/systemd/system/systemd-pcrproduct.service b/etc/systemd/system/systemd-pcrproduct.service
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/etc/systemd/system/systemd-pcrproduct.service
diff --git a/tests/test_nvpcr_masks.py b/tests/test_nvpcr_masks.py
new file mode 100644
index 0000000..b2ec429
--- /dev/null
+++ b/tests/test_nvpcr_masks.py
@@ -0,0 +1,43 @@
+import subprocess
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+MASKS = [
+ *(
+ f"etc/nvpcr/{name}.nvpcr"
+ for name in ("cryptsetup", "hardware", "login", "verity")
+ ),
+ "etc/systemd/system/systemd-pcrlogin@.service",
+ "etc/systemd/system/systemd-pcrproduct.service",
+]
+
+
+class NvPCRTests(unittest.TestCase):
+ def test_masks_are_empty_regular_files(self):
+ for name in MASKS:
+ path = ROOT / name
+ self.assertFalse(path.is_symlink())
+ self.assertEqual(path.read_bytes(), b"")
+
+ def test_initramfs_keeps_existing_files_and_includes_masks(self):
+ config = ROOT / "etc/mkinitcpio.conf.d/60-no-nvpcr.conf"
+ output = subprocess.check_output(
+ [
+ "bash",
+ "-c",
+ 'FILES=(existing); source "$1"; printf "%s\\n" "${FILES[@]}"',
+ "bash",
+ str(config),
+ ],
+ text=True,
+ ).splitlines()
+ self.assertEqual(output, ["existing", *(f"/{name}" for name in MASKS)])
+
+ def test_srk_setup_and_pcr7_policy_remain_enabled(self):
+ for name in ("systemd-tpm2-setup.service", "systemd-tpm2-setup-early.service"):
+ self.assertFalse((ROOT / "etc/systemd/system" / name).exists())
+ policy = (
+ ROOT / "etc/systemd/system/systemd-pcrlock-make-policy.service.d/pcr7.conf"
+ )
+ self.assertIn("--pcr=7 --location=770", policy.read_text())