summaryrefslogtreecommitdiffstatshomepage
path: root/scripts/tpm_nvpcr.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/tpm_nvpcr.py')
-rw-r--r--scripts/tpm_nvpcr.py49
1 files changed, 48 insertions, 1 deletions
diff --git a/scripts/tpm_nvpcr.py b/scripts/tpm_nvpcr.py
index ddf9fd2..93f090e 100644
--- a/scripts/tpm_nvpcr.py
+++ b/scripts/tpm_nvpcr.py
@@ -15,6 +15,8 @@ from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
BOOT = Path("/boot/EFI/Linux")
+PCRLOCK_POLICY = Path("/var/lib/systemd/pcrlock.json")
+GPT_COMPONENT = Path("/var/lib/pcrlock.d/600-gpt.pcrlock.d/generated.pcrlock")
PUBLIC_KEY = Path("/etc/systemd/tpm2-pcr-public-key.pem")
SB_CERT = Path("/var/lib/sbctl/keys/db/db.pem")
ROOT_UUID = "81520bbc-1e7a-45e6-9465-cfc2e8b18945"
@@ -217,6 +219,49 @@ def check_image(path: Path, public_key: bytes) -> None:
print(f"verified: {path}")
+def check_pcrlock_policy(policy: dict, boot_policy: dict) -> None:
+ values = policy.get("pcrValues")
+ if (
+ policy.get("pcrBank") != "sha256"
+ or not isinstance(values, list)
+ or len(values) != 2
+ or any(
+ not isinstance(entry, dict)
+ or entry.get("pcr") not in (5, 7)
+ or not isinstance(entry.get("values"), list)
+ or not entry["values"]
+ for entry in values
+ )
+ or {entry["pcr"] for entry in values} != {5, 7}
+ ):
+ raise ValueError("Stored pcrlock policy does not require PCR 5 and PCR 7")
+ if policy != boot_policy:
+ raise ValueError("EFI pcrlock credential differs from the stored policy")
+
+
+def check_boot_policy() -> None:
+ if not GPT_COMPONENT.is_file():
+ raise ValueError("Approved GPT component is missing")
+ policy = json.loads(PCRLOCK_POLICY.read_text())
+ credential_dir = Path(run("bootctl", "-x").strip()) / "loader/credentials"
+ credentials = list(credential_dir.glob("pcrlock.*.cred"))
+ if len(credentials) != 1:
+ raise ValueError("Expected one EFI pcrlock credential")
+ credential = credentials[0]
+ name = credential.name.removesuffix(".cred").lower()
+ boot_policy = json.loads(
+ run(
+ "systemd-creds",
+ "--allow-null",
+ f"--name={name}",
+ "decrypt",
+ str(credential),
+ "-",
+ )
+ )
+ check_pcrlock_policy(policy, boot_policy)
+
+
def check_boot() -> None:
active = Path("/proc/cmdline").read_text()
if not all(option in active for option in MEASUREMENT_OPTIONS):
@@ -225,9 +270,11 @@ def check_boot() -> None:
for name in ("cryptsetup", "hardware", "login", "verity"):
if name not in nvpcrs:
raise ValueError(f"NvPCR {name} is not available")
+ check_boot_policy()
run(
"/usr/lib/systemd/systemd-pcrlock",
"--strict=yes",
+ "--pcr=5",
"--pcr=7",
"--location=770",
"predict",
@@ -236,7 +283,7 @@ def check_boot() -> None:
if failed.strip():
raise ValueError("Systemd has failed units; inspect systemctl --failed")
print(
- "verified: current boot options, NvPCR availability, and strict PCR 7 prediction"
+ "verified: current boot options, NvPCR availability, and strict PCR 5+7 policy"
)