summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--docs/tpm-unlock.md8
-rw-r--r--scripts/tpm_nvpcr.py14
-rw-r--r--tests/test_nvpcr.py15
3 files changed, 33 insertions, 4 deletions
diff --git a/docs/tpm-unlock.md b/docs/tpm-unlock.md
index 6a95ac7..653b099 100644
--- a/docs/tpm-unlock.md
+++ b/docs/tpm-unlock.md
@@ -185,10 +185,10 @@ is agreed. Stop if any command fails.
/var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts-fallback.efi
```
- The check reads `.pcrpkey`, `.pcrsig`, and `.cmdline` using ukify, then
- verifies each Secure Boot signature with the enrolled `sbctl` certificate.
- Stop on any mismatch. The `systemd-pcrextend` socket and early setup units
- must be present in both initramfs variants.
+ 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 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
diff --git a/scripts/tpm_nvpcr.py b/scripts/tpm_nvpcr.py
index 4f0f0a7..14a250c 100644
--- a/scripts/tpm_nvpcr.py
+++ b/scripts/tpm_nvpcr.py
@@ -21,6 +21,12 @@ MEASUREMENT_OPTIONS = (
"tpm2-measure-pcr=15",
"tpm2-measure-keyslot-nvpcr=cryptsetup",
)
+INITRD_UNITS = (
+ "systemd-tpm2-setup-early.service",
+ "systemd-pcrnvdone.service",
+ "systemd-pcrextend.socket",
+ "systemd-pcrextend@.service",
+)
def run(*command: str) -> str:
@@ -69,6 +75,13 @@ def check_sections(sections: dict, public_key: bytes) -> None:
raise ValueError("Root measurement or TPM unlock option is missing")
+def check_initrd_listing(listing: str) -> None:
+ members = {line.strip().lstrip("./") for line in listing.splitlines()}
+ for unit in INITRD_UNITS:
+ if f"usr/lib/systemd/system/{unit}" not in members:
+ raise ValueError(f"UKI initrd lacks {unit}")
+
+
def check_image(path: Path, public_key: bytes) -> None:
sections = json.loads(
run(
@@ -84,6 +97,7 @@ def check_image(path: Path, public_key: bytes) -> None:
if not isinstance(sections, dict) or "_profiles" in sections:
raise ValueError(f"Unsupported multi-profile UKI: {path}")
check_sections(sections, public_key)
+ check_initrd_listing(run("lsinitcpio", "--list", str(path)))
run("sbverify", "--cert", str(SB_CERT), str(path))
print(f"verified: {path}")
diff --git a/tests/test_nvpcr.py b/tests/test_nvpcr.py
index f5765e4..5f36bb7 100644
--- a/tests/test_nvpcr.py
+++ b/tests/test_nvpcr.py
@@ -83,6 +83,21 @@ def sections(public_key, *, policies=None, options=None):
class NvPCRImageTests(unittest.TestCase):
+ def test_initrd_has_required_units(self):
+ listing = "\n".join(
+ f"usr/lib/systemd/system/{unit}" for unit in tpm_nvpcr.INITRD_UNITS
+ )
+ tpm_nvpcr.check_initrd_listing(listing)
+
+ def test_rejects_missing_initrd_service(self):
+ listing = "\n".join(
+ f"usr/lib/systemd/system/{unit}"
+ for unit in tpm_nvpcr.INITRD_UNITS
+ if unit != "systemd-pcrnvdone.service"
+ )
+ with self.assertRaisesRegex(ValueError, "systemd-pcrnvdone.service"):
+ tpm_nvpcr.check_initrd_listing(listing)
+
def test_valid_initrd_signature_and_root_options(self):
tpm_nvpcr.check_sections(sections(b"public key"), b"public key")