From 242f87fcf057e10003bc020cbaa6de6ede09aece Mon Sep 17 00:00:00 2001 From: phaer Date: Wed, 18 Mar 2026 09:10:31 +0100 Subject: [PATCH] Remove expected-pcr11 mechanism entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the build-time PCR 11 pre-calculation and runtime verification against /boot/expected-pcr11. With auto-enrollment, this was only a configuration sanity check (catches forgotten set-pcr11 after signing) but not a security boundary — both the UKI and expected-pcr11 live on the same unencrypted ESP, and Secure Boot already verifies the UKI. Removed: - calculate-pcr11.py tool and its packaging - configure-disk-image set-pcr11 subcommand - show_pcr11_status() from configure-disk-image status - expectedPcr11 derivation from secure-boot.nix - set-pcr11 invocation from vm.nix prepareWritableDisk - --verify-pcr11 flag from read-firmware-pcrs - Expected value verification from report-pcrs - calculate-pcr11 from flake.nix exports and devShell - All references in docs Kept: - PCR 11 is still read from the TPM and included in attestation policy (read-firmware-pcrs always includes it now) - report-pcrs still sends PCR 11 to the auto-enrollment daemon - systemd-pcrphase services still extend PCR 11 at boot - All auto-enrollment test assertions about PCR 11 in policy --- docs/docs.md | 25 ++--- docs/user-guide.md | 36 ++------ flake.nix | 3 +- modules/secure-boot.nix | 21 ----- modules/vm.nix | 6 -- .../disk-installer/configure-disk-image.py | 92 ------------------- packages/disk-installer/default.nix | 5 - packages/pcr-policy/calculate-pcr11.py | 79 ---------------- packages/pcr-policy/default.nix | 24 ----- packages/pcr-policy/read-firmware-pcrs.py | 51 ++-------- packages/pcr-policy/report-pcrs.py | 32 +------ tests/keylime.nix | 2 +- 12 files changed, 30 insertions(+), 346 deletions(-) delete mode 100644 packages/pcr-policy/calculate-pcr11.py diff --git a/docs/docs.md b/docs/docs.md index ca2ccd6..55166aa 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -20,7 +20,7 @@ We created a modular proof‑of‑concept based on NixOS that fulfills most of t * **aarch64 support** could be added if needed. Only `x86_64` with `UEFI` is implemented at the moment. * **artifact uploads**: build artifacts are currently not automatically uploaded anywhere, but stay on the build machine. Integration of a Trusted Platform Module (TPM) could be useful here, to ease authentication to private repositories as well as destinations for artifact upload. -* **measured boot & attestation**: PCR 11 (UKI) is pre-calculated at build time and verified at runtime. Firmware PCRs (0–3, 7) and PCR 11 are automatically reported to the attestation server via `report-pcrs` for full-policy auto-enrollment. The keylime agent is enabled by default (using TPM EK-derived identity). Remaining work: an attestation-gated step in the unattended pipeline, network policy for attestation traffic, and replacing the `accept-all` measured boot policy with real UEFI event log validation. +* **measured boot & attestation**: Firmware PCRs (0–3, 7) and PCR 11 (UKI measurement) are automatically reported to the attestation server via `report-pcrs` for full-policy auto-enrollment. The keylime agent is enabled by default (using TPM EK-derived identity). Remaining work: an attestation-gated step in the unattended pipeline, network policy for attestation traffic, and replacing the `accept-all` measured boot policy with real UEFI event log validation. * **credential storage**: TPM-encrypted credentials (via `systemd-creds`) are currently bound to PCR 7 (Secure Boot policy) only, not PCR 11 (UKI). Since the Secure Boot signing keys are created specifically for this project, only images signed with our key can produce a matching PCR 7 — so the practical risk is low. Binding to PCR 11 as well would prevent a *different* image signed with the same key from decrypting credentials, at the cost of invalidating all stored credentials on every image update. A `systemd-measure sign` based approach could provide PCR 11 binding without this drawback. * **higher-level configuration**: Adapting the build environment to the needs of custom AOSP distributions might need extra work. Depending on the nature of those customizations, a good understanding of `nix` might be needed. We will ease those as far as possible, as we learn more about users customization needs. @@ -261,13 +261,13 @@ The daemon runs on the attestation server alongside the registrar and verifier. 2. Periodically polls the registrar for registered agent UUIDs. 3. When an agent is both registered AND has submitted its PCR report, enrolls it with the verifier using the full policy. -On the agent side, `report-pcrs` runs as a oneshot systemd service after the keylime agent registers. It reads all PCR values from the TPM, verifies PCR 11 against the expected value on the ESP, and POSTs the policy to the daemon. +On the agent side, `report-pcrs` runs as a oneshot systemd service after the keylime agent registers. It reads all PCR values (firmware PCRs 0–3, 7 and PCR 11) from the TPM and POSTs the policy to the daemon. #### Trust Model Firmware PCRs are accepted on a **trust-on-first-use (TOFU)** basis: the agent self-reports its PCR values before the first attestation. This is acceptable because: -- PCR 11 is verified locally against the build-time expected value before reporting — the agent must be running the correct image. +- PCR 11 (UKI measurement) is included in the reported policy, ensuring the verifier attests the specific image booted on the agent. - After enrollment, the verifier validates all PCR values against the TPM quote on every attestation cycle — any false report is caught immediately. - Once enrolled with the full policy, the agent cannot downgrade the policy — only an admin with verifier mTLS credentials can modify it. @@ -282,13 +282,12 @@ Attestation verifies the following Platform Configuration Registers (PCRs) by de - **PCR 7** – Secure Boot state (keys, policy, boot variables) - **PCR 11** – UKI components and boot phases -PCRs 0–3 and 7 are firmware-dependent and can only be read from the live TPM. PCR 11 is pre-calculated at build time from the UKI using `systemd-measure` and written to the ESP as `/boot/expected-pcr11` by `configure-disk-image set-pcr11`. +PCRs 0–3 and 7 are firmware-dependent and can only be read from the live TPM. PCR 11 is measured by systemd at boot from the UKI components and boot phase strings. Two tools are included for PCR management: -- `report-pcrs` – reads firmware PCR values (0–3, 7) and PCR 11 from the TPM sysfs, verifies PCR 11 against the expected value on the ESP, and enrolls on the keylime server by sending the PCRs to the auto-enrollment service. Runs automatically as a oneshot service after the keylime agent registers. -- `read-firmware-pcrs` – reads PCR values from the TPM sysfs and outputs a keylime `tpm_policy` JSON. Supports `--verify-pcr11` to include PCR 11 after verifying it against the expected value, `--save` to persist a baseline, and `--diff` to compare against a previously saved baseline. Useful for debugging and manual inspection. -- `calculate-pcr11` – offline tool for use on a local workstation that computes the expected PCR 11 value from a UKI file by extracting its PE sections and running `systemd-measure calculate` with the `sysinit:ready` phase. +- `report-pcrs` – reads firmware PCR values (0–3, 7) and PCR 11 from the TPM sysfs and sends them to the auto-enrollment service on the attestation server. Runs automatically as a oneshot service after the keylime agent registers. +- `read-firmware-pcrs` – reads PCR values (0–3, 7, 11) from the TPM sysfs and outputs a keylime `tpm_policy` JSON. Supports `--save` to persist a baseline and `--diff` to compare against a previously saved baseline. Useful for debugging and manual inspection. ## Credential Storage {#credential-storage} @@ -338,14 +337,8 @@ flowchart TB copy-auth["(7) Copy Secure Boot update bundles"] end - signing-script --> set-pcr11 - subgraph set-pcr11["configure-disk-image set-pcr11"] - direction TB - inject-pcr11["(8) Write expected PCR 11 hash to ESP"] - end - - set-pcr11 --> signed - signed["(9) Image is signed & ready to boot"] + signing-script --> signed + signed["(8) Image is signed & ready to boot"] ~~~ @@ -378,7 +371,7 @@ Usage is documented in [user-guide.pdf](user-guide.pdf). `configure-disk-image` - **(6)** The `UKI` is copied to a temporary file, signed, and copied back into the `esp` again. - **(7)** Secure Boot update bundles (`*.auth` files) are copied to the `esp` to ensure that `ensure-secure-boot-enrollment.service` can find them during boot. -- **(8)** The expected PCR 11 value is pre-calculated from the signed UKI and written to the ESP as `/boot/expected-pcr11`. At runtime, `report-pcrs` verifies the live TPM PCR 11 against this value before reporting the full policy to the auto-enrollment server. + - **(9)** We finally have a signed image, ready to flash & boot on a target machine. diff --git a/docs/user-guide.md b/docs/user-guide.md index 43af872..37c56c9 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -155,20 +155,6 @@ Copying keystore files for auto-enrollment... ✓ Keystore files copied to ESP ``` -To write the expected PCR 11 hash (UKI measurement) to the image, run: - -```shell-session -$ nix run .#configure-disk-image -- set-pcr11 --device android-builder_25.11pre-git.raw -``` - -``` text -Extracting UKI from image... -Computing expected PCR 11... -✓ Expected PCR 11 written to ESP: 00e9c94ef58cd0c569e2872b451fee0e30b322dffb38cf79415c9f478807dddf -``` - -This step must be run **after** signing, since signing changes the UKI and therefore its PCR 11 value. If omitted, `report-pcrs` will fail at runtime because it cannot verify PCR 11. - ## Configure Attestation Server If you are running a keylime attestation server (registrar & verifier), the builder image needs to know how to reach it. `configure-disk-image set-attestation-server` writes the server address and CA certificate to the ESP so the keylime agent can connect on boot. @@ -206,7 +192,7 @@ sequenceDiagram participant S as Attestation Server participant A as Agent - B->>B: nix build, sign, set-pcr11,
set-attestation-server + B->>B: nix build, sign,
set-attestation-server B->>A: Flash image to disk A->>A: Boot — agent starts @@ -461,14 +447,14 @@ $ cat /sys/class/tpm/tpm0/pcr-sha256/7 abc123... ``` -On boot, the `report-pcrs` service automatically reads firmware PCRs (0–3, 7) and PCR 11, verifies PCR 11 against the expected value on the ESP, and enrolls on the keylime server by sending the PCRs to the auto-enrollment service. No manual PCR inspection is normally needed. +On boot, the `report-pcrs` service automatically reads firmware PCRs (0–3, 7) and PCR 11 from the TPM, then sends them to the auto-enrollment service on the attestation server. No manual PCR inspection is normally needed. For debugging, `read-firmware-pcrs` is also available to inspect PCR values interactively: ```shell-session $ read-firmware-pcrs {"0": ["abc123..."], "1": ["def456..."], "2": ["..."], "3": ["..."], "7": ["..."]} -$ read-firmware-pcrs --verify-pcr11 +$ read-firmware-pcrs {"0": ["..."], "1": ["..."], "2": ["..."], "3": ["..."], "7": ["..."], "11": ["..."]} ``` @@ -603,9 +589,6 @@ Installation target: Storage target: Interactive menu (user will select artifact storage) -PCR 11 (UKI measurement): - ✓ Expected hash: 00e9c94ef58cd0c569e2872b451fee0e30b322dffb38cf79415c9f478807dddf - Attestation server: ✓ Server: 10.0.0.1 (registrar:8891, verifier:8881) ✓ CA cert: present @@ -714,12 +697,11 @@ This will: 1. Create a writable copy of the read-only disk image (e.g. `android-builder_25.11pre-git.raw`) in the current directory. 2. Sign the UKI with a pair of auto-generated test keys (stored in the nix store, so they persist across runs). -3. Inject the expected PCR 11 hash into the ESP. -4. Pre-configure artifact storage to use `/dev/vdb` (a second virtual disk is created automatically when `nixosAndroidBuilder.artifactStorage.enable` is set). -5. If an `attestation-server.json` file exists in the current directory, configure the keylime agent to use it. The file uses the same format as `/boot/attestation-server.json` (see [Configure Attestation Server](#configure-attestation-server) above). -6. Start a QEMU VM with Secure Boot, a TPM, and a graphical window. +3. Pre-configure artifact storage to use `/dev/vdb` (a second virtual disk is created automatically when `nixosAndroidBuilder.artifactStorage.enable` is set). +4. If an `attestation-server.json` file exists in the current directory, configure the keylime agent to use it. The file uses the same format as `/boot/attestation-server.json` (see [Configure Attestation Server](#configure-attestation-server) above). +5. Start a QEMU VM with Secure Boot, a TPM, and a graphical window. -If the writable disk image already exists from a previous run, steps 1–5 are skipped and the existing image is reused. Delete the `.raw` file to force a fresh image. +If the writable disk image already exists from a previous run, steps 1–4 are skipped and the existing image is reused. Delete the `.raw` file to force a fresh image. Use `systemctl poweroff` from within the VM, or close the QEMU window, to stop it. @@ -810,10 +792,6 @@ Check the daemon logs (`journalctl -u keylime-auto-enroll`) for enrollment error - mTLS certificate issues (expired, wrong CA). - Port 8893 not reachable from the agent. -**Problem: `report-pcrs` fails with "PCR 11 mismatch"** - -The agent's live PCR 11 does not match the expected value on the ESP. Ensure `set-pcr11` was run after signing the image. - **Problem: Agent is enrolled but fails attestation** The TPM quote does not match the enrolled policy. This can happen after a firmware update or BIOS settings change that alters firmware PCRs. Delete the enrollment and let the agent re-enroll: diff --git a/flake.nix b/flake.nix index 457298c..b5f4cdf 100644 --- a/flake.nix +++ b/flake.nix @@ -94,7 +94,6 @@ packages = with secureBootScripts; [ create-signing-keys diskInstaller.configure - pcrPolicy.calculate-pcr11 docs.build-docs docs.watch-docs pkgs.pam_u2f @@ -112,7 +111,7 @@ keylime-agent ; inherit (secureBootScripts) create-signing-keys; - inherit (pcrPolicy) calculate-pcr11 report-pcrs read-firmware-pcrs; + inherit (pcrPolicy) report-pcrs read-firmware-pcrs; configure-disk-image = diskInstaller.configure; default = image; }; diff --git a/modules/secure-boot.nix b/modules/secure-boot.nix index 5259e07..3b1e7b6 100644 --- a/modules/secure-boot.nix +++ b/modules/secure-boot.nix @@ -7,16 +7,6 @@ let pcrPolicy = pkgs.callPackage ../packages/pcr-policy { }; - # Pre-calculate the expected PCR 11 value from the UKI at build time. - # Placed on the ESP (not in /nix/store) to avoid a circular dependency: - # /etc → store partition → UKI → expectedPcr11 → /etc would be infinite. - # The ESP already contains the UKI so referencing it here is safe. - expectedPcr11 = pkgs.runCommand "expected-pcr11" { } '' - ${lib.getExe pcrPolicy.calculate-pcr11} \ - ${config.system.build.uki}/${config.system.build.uki.name} \ - > $out - ''; - enroll-secure-boot = pkgs.writeShellScriptBin "enroll-secure-boot" '' set -xeu # Allow modification of efivars @@ -67,17 +57,6 @@ in pcrPolicy.read-firmware-pcrs ]; - # Expose the expected PCR 11 hash as a build output. - # - # It can't be baked into the store partition or ESP at image build - # time because that would create a circular dependency - # (store → UKI → expectedPcr11 → store). Instead it is written to - # the ESP by configure-disk-image set-pcr11 (post-build). - # - # report-pcrs reads it from /boot/expected-pcr11 - # at runtime to compare against the running TPM state. - system.build.expectedPcr11 = expectedPcr11; - # Enable PCR phase measurements (systemd-pcrextend extends PCR 11 with boot # phase strings so that the final value is only reachable after a full, # successful boot of this exact UKI). diff --git a/modules/vm.nix b/modules/vm.nix index a22e407..6a7c2a2 100644 --- a/modules/vm.nix +++ b/modules/vm.nix @@ -10,7 +10,6 @@ let secureBootScripts = hostPkgs.callPackage ../packages/secure-boot-scripts { }; disk-installer = hostPkgs.callPackage ../packages/disk-installer { }; - pcrPolicy = hostPkgs.callPackage ../packages/pcr-policy { }; in { config = { @@ -84,11 +83,6 @@ in --target "/dev/vdb" \ --device "${cfg.diskImage}" - echo >&2 "Injecting expected PCR 11 hash onto ESP" - ${lib.getExe disk-installer.configure} set-pcr11 \ - --expected-pcr11 "${config.system.build.expectedPcr11}" \ - --device "${cfg.diskImage}" - # Configure attestation server from local attestation-server.json # (same format as /boot/attestation-server.json). if [ -f attestation-server.json ]; then diff --git a/packages/disk-installer/configure-disk-image.py b/packages/disk-installer/configure-disk-image.py index b52d808..07da954 100644 --- a/packages/disk-installer/configure-disk-image.py +++ b/packages/disk-installer/configure-disk-image.py @@ -227,19 +227,6 @@ def show_storage_target(img_spec): print() -def show_pcr11_status(img_spec): - result = subprocess.run( - ["mtype", "-i", img_spec, "::/expected-pcr11"], - capture_output=True, text=True, check=False, - ) - print("PCR 11 (UKI measurement):") - if result.returncode == 0: - pcr11_hash = result.stdout.strip().replace('\r', '').replace('\n', '') - print(f" ✓ Expected hash: {pcr11_hash}") - else: - print(f" ✗ Not configured (run: configure-disk-image set-pcr11 --device )") - print() - def cmd_status(args): """Check status of installer image.""" @@ -268,7 +255,6 @@ def cmd_status(args): raise InstallerError("Cannot access EFI partition (invalid FAT filesystem)") show_storage_target(esp_img_spec) - show_pcr11_status(esp_img_spec) show_attestation_server_status(esp_img_spec) extract_and_verify_uki(esp_img_spec, cert_path, "Payload") else: @@ -289,7 +275,6 @@ def cmd_status(args): show_install_target(installer_img_spec) show_storage_target(installer_img_spec) - show_pcr11_status(payload_img_spec) show_attestation_server_status(payload_img_spec) extract_and_verify_uki(installer_img_spec, cert_path, "Installer") @@ -377,78 +362,6 @@ def cmd_set_target(args): temp_path.unlink(missing_ok=True) -def cmd_set_pcr11(args): - """Compute expected PCR 11 from the UKI inside the image and write it to the ESP.""" - device = Path(args.device) - if not device.exists(): - raise InstallerError( - f"Device or image file not found: {device}") - - payload_only = is_payload_only(device) - if payload_only: - esp_offset = get_partition_offset( - device, UUID_EFI_SYSTEM) - else: - esp_offset = get_payload_esp_offset(device) - - esp_img_spec = f"{device}@@{esp_offset}" - if not verify_mtools_access(esp_img_spec): - raise InstallerError( - "Cannot access EFI partition") - - if args.expected_pcr11: - # Use a pre-computed hash file if provided - expected_pcr11 = Path(args.expected_pcr11) - if not expected_pcr11.is_file(): - raise InstallerError( - f"Expected PCR 11 file not found:" - f" {expected_pcr11}") - pcr11_hash = expected_pcr11.read_text().strip() - else: - # Extract the UKI from the image and compute PCR 11 - with tempfile.NamedTemporaryFile(suffix=".efi", delete=False) as temp_efi: - temp_uki = Path(temp_efi.name) - try: - print("Extracting UKI from image...") - if subprocess.run( - ["mcopy", "-n", "-i", esp_img_spec, - "::/EFI/BOOT/BOOTX64.EFI", str(temp_uki)], - check=False, capture_output=True - ).returncode != 0: - raise InstallerError( - "Failed to extract UKI from image") - - print("Computing expected PCR 11...") - result = subprocess.run( - ["calculate-pcr11", str(temp_uki)], - capture_output=True, text=True - ) - if result.returncode != 0: - raise InstallerError( - f"Failed to compute PCR 11: {result.stderr.strip()}") - pcr11_hash = result.stdout.strip() - finally: - temp_uki.unlink(missing_ok=True) - - with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_hash: - temp_hash.write(pcr11_hash) - temp_hash_path = Path(temp_hash.name) - - try: - if subprocess.run( - ["mcopy", "-n", "-o", "-i", esp_img_spec, - str(temp_hash_path), "::/expected-pcr11"], - check=False, capture_output=True - ).returncode != 0: - raise InstallerError( - "Failed to write expected-pcr11 to ESP") - finally: - temp_hash_path.unlink(missing_ok=True) - - print(f"✓ Expected PCR 11 written to ESP:" - f" {pcr11_hash}") - - def cmd_set_storage(args): """Configure target for artifact storage.""" device = Path(args.device) @@ -615,10 +528,6 @@ def main(): storage_parser.add_argument('--target', required=True, help='Target device (e.g., /dev/sda) or "select" for interactive') storage_parser.add_argument('--device', required=True, help='Block device or disk image file') - pcr11_parser = subparsers.add_parser('set-pcr11', help='Compute expected PCR 11 from UKI in image and write to ESP') - pcr11_parser.add_argument('--expected-pcr11', help='File containing pre-computed PCR 11 hash (default: compute from UKI in image)') - pcr11_parser.add_argument('--device', required=True, help='Block device or disk image file') - registrar_parser = subparsers.add_parser('set-attestation-server', help='Configure keylime registrar/verifier connection on ESP') registrar_parser.add_argument('--ip', required=True, help='Registrar/verifier server IP address') registrar_parser.add_argument('--ca-cert', required=True, help='Path to CA certificate PEM file') @@ -634,7 +543,6 @@ def main(): 'sign': cmd_sign, 'set-target': cmd_set_target, 'set-storage': cmd_set_storage, - 'set-pcr11': cmd_set_pcr11, 'set-attestation-server': cmd_set_attestation_server, }[args.command](args) except InstallerError as e: diff --git a/packages/disk-installer/default.nix b/packages/disk-installer/default.nix index 66e5fc0..ec5759c 100644 --- a/packages/disk-installer/default.nix +++ b/packages/disk-installer/default.nix @@ -1,6 +1,5 @@ { lib, - callPackage, writers, writeShellApplication, jq, @@ -9,9 +8,6 @@ sbsigntool, parted, }: -let - pcrPolicy = callPackage ../pcr-policy { }; -in { module = import ./installer.nix; @@ -27,7 +23,6 @@ in util-linux mtools sbsigntool - pcrPolicy.calculate-pcr11 ] }" ]; diff --git a/packages/pcr-policy/calculate-pcr11.py b/packages/pcr-policy/calculate-pcr11.py deleted file mode 100644 index 2d287cc..0000000 --- a/packages/pcr-policy/calculate-pcr11.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Calculate the expected PCR 11 value for a UKI image. - -systemd-stub measures each PE section of the UKI into PCR 11, then -systemd-pcrphase extends it with boot phase strings. This script -reproduces that calculation offline using systemd-measure so that -the expected value can be fed into a keylime TPM policy. - -Outputs the sha256 PCR 11 hash to stdout (64 hex characters, no newline). -""" - -import argparse -import json -import shutil -import subprocess -import sys -import tempfile -from pathlib import Path - -UKI_SECTIONS = [".linux", ".osrel", ".cmdline", ".initrd", ".uname", ".sbat"] - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Calculate the expected PCR 11 value for a UKI image.", - ) - parser.add_argument("uki", type=Path, help="Path to the UKI (.efi) file.") - args = parser.parse_args() - - if not args.uki.exists(): - print(f"Error: UKI file not found: {args.uki}", file=sys.stderr) - sys.exit(1) - - with tempfile.TemporaryDirectory() as workdir: - work = Path(workdir) - - local_uki = work / "uki.efi" - shutil.copy2(args.uki, local_uki) - local_uki.chmod(0o644) - - # Extract PE sections from the UKI - extracted: dict[str, Path] = {} - for section in UKI_SECTIONS: - name = section.lstrip(".") - out = work / name - result = subprocess.run( - [ - "objcopy", "--dump-section", - f"{section}={out}", str(local_uki), - ], - capture_output=True, - ) - if result.returncode == 0 and out.exists(): - extracted[name] = out - - # Build systemd-measure arguments - linux = extracted["linux"] - measure_args = [ - "systemd-measure", "calculate", - f"--linux={linux}", - ] - for name in ["osrel", "cmdline", "initrd", "uname", "sbat"]: - if name in extracted: - measure_args.append(f"--{name}={extracted[name]}") - measure_args += [ - "--phase=sysinit:ready", - "--bank=sha256", - "--json=short", - ] - - result = subprocess.run( - measure_args, - capture_output=True, text=True, check=True, - ) - data = json.loads(result.stdout) - print(data["sha256"][0]["hash"], end="") - - -if __name__ == "__main__": - main() diff --git a/packages/pcr-policy/default.nix b/packages/pcr-policy/default.nix index 47b6138..69e6fc0 100644 --- a/packages/pcr-policy/default.nix +++ b/packages/pcr-policy/default.nix @@ -1,31 +1,7 @@ { writers, - lib, - binutils, - systemd, - runCommand, }: -let - # systemd-measure lives in systemd's lib output, not in bin. - systemd-measure = runCommand "systemd-measure" { } '' - mkdir -p $out/bin - ln -s ${systemd}/lib/systemd/systemd-measure $out/bin/systemd-measure - ''; -in { - # Build-time tool: calculate expected PCR 11 from a UKI file. - # Used during `nix build` to pre-compute the hash for keylime policies. - calculate-pcr11 = writers.writePython3Bin "calculate-pcr11" { - makeWrapperArgs = [ - "--prefix PATH : ${ - lib.makeBinPath [ - binutils - systemd-measure - ] - }" - ]; - } (builtins.readFile ./calculate-pcr11.py); - # Run-time tool: read TPM PCRs and report them to the auto-enrollment # server. Used as a oneshot service on the agent side. report-pcrs = writers.writePython3Bin "report-pcrs" { diff --git a/packages/pcr-policy/read-firmware-pcrs.py b/packages/pcr-policy/read-firmware-pcrs.py index 26e1d69..e8de799 100644 --- a/packages/pcr-policy/read-firmware-pcrs.py +++ b/packages/pcr-policy/read-firmware-pcrs.py @@ -1,15 +1,12 @@ """Read firmware PCR values from the TPM and emit a keylime tpm_policy JSON. +Reads firmware PCRs (0–3, 7) and PCR 11 from the TPM and outputs +them as a keylime-compatible tpm_policy JSON object. + These PCRs are populated by UEFI firmware and cannot be pre-calculated at build time — they depend on the specific hardware, firmware version, BIOS settings, and Secure Boot key enrollment. -When --verify-pcr11 is given, the script also reads PCR 11 from the -TPM and compares it against the expected value baked into the image -at build time (see the secure-boot NixOS module). If they match, -PCR 11 is included in the output policy. If not, the script exits -with an error. - The --save flag writes the current PCR baseline to a JSON file for later comparison. The --diff flag compares the current PCR values against a previously saved baseline and reports any changes. @@ -22,7 +19,6 @@ FIRMWARE_PCRS = [0, 1, 2, 3, 7] TPM_SYSFS = Path("/sys/class/tpm/tpm0/pcr-sha256") -EXPECTED_PCR11 = Path("/boot/expected-pcr11") DEFAULT_BASELINE = Path("/var/lib/keylime/pcr-baseline.json") @@ -42,8 +38,8 @@ def read_pcr(pcr: int) -> str: sys.exit(1) -def read_policy(verify_pcr11: bool) -> dict: - """Read all firmware PCRs, optionally including PCR 11.""" +def read_policy() -> dict: + """Read all firmware PCRs and PCR 11.""" policy = {} for pcr in FIRMWARE_PCRS: digest = read_pcr(pcr) @@ -57,31 +53,7 @@ def read_policy(verify_pcr11: bool) -> dict: sys.exit(1) policy[str(pcr)] = [digest] - if verify_pcr11: - if not EXPECTED_PCR11.exists(): - print( - "Error: --verify-pcr11 requires" - f" {EXPECTED_PCR11} to exist.\n" - "Run: configure-disk-image set-pcr11" - " --device ", - file=sys.stderr, - ) - sys.exit(1) - - expected = EXPECTED_PCR11.read_text().strip().lower() - actual = read_pcr(11) - - if actual != expected: - print( - "Error: PCR 11 mismatch!\n" - f" expected: {expected}\n" - f" actual: {actual}", - file=sys.stderr, - ) - sys.exit(1) - - policy["11"] = [actual] - + policy["11"] = [read_pcr(11)] return policy @@ -146,15 +118,6 @@ def main() -> None: " and emit a keylime tpm_policy JSON." ), ) - parser.add_argument( - "--verify-pcr11", - action="store_true", - help=( - "Read PCR 11, verify it matches the expected" - " value baked into the image, and include it" - " in the output." - ), - ) parser.add_argument( "--output", metavar="FILE", @@ -183,7 +146,7 @@ def main() -> None: ) args = parser.parse_args() - policy = read_policy(args.verify_pcr11) + policy = read_policy() if args.save: save_baseline(policy, Path(args.save)) diff --git a/packages/pcr-policy/report-pcrs.py b/packages/pcr-policy/report-pcrs.py index 9c52184..a11ae5e 100644 --- a/packages/pcr-policy/report-pcrs.py +++ b/packages/pcr-policy/report-pcrs.py @@ -1,9 +1,8 @@ """Report TPM PCR values to the auto-enrollment server. -Reads firmware PCRs (0-3, 7) and PCR 11 from the TPM, verifies -PCR 11 against the expected value on the ESP, then POSTs the full -policy to the auto-enrollment HTTPS endpoint on the attestation -server. +Reads firmware PCRs (0-3, 7) and PCR 11 from the TPM, then POSTs +the full policy to the auto-enrollment HTTPS endpoint on the +attestation server. The agent UUID is read from the keylime agent's ``agent_data.json`` file, which stores the EK hash (== the UUID in ``hash_ek`` mode) @@ -25,7 +24,6 @@ FIRMWARE_PCRS = [0, 1, 2, 3, 7] TPM_SYSFS = Path("/sys/class/tpm/tpm0/pcr-sha256") -EXPECTED_PCR11 = Path("/boot/expected-pcr11") ATTESTATION_SERVER = Path("/boot/attestation-server.json") AGENT_DATA = Path("/var/lib/keylime/agent_data.json") @@ -41,7 +39,7 @@ def read_pcr(pcr: int) -> str: def read_policy() -> dict: - """Read firmware PCRs and verified PCR 11.""" + """Read firmware PCRs and PCR 11.""" policy: dict = {} for pcr in FIRMWARE_PCRS: @@ -55,27 +53,7 @@ def read_policy() -> dict: sys.exit(1) policy[str(pcr)] = [digest] - if not EXPECTED_PCR11.exists(): - print( - f"Error: {EXPECTED_PCR11} not found.\n" - "Run: configure-disk-image set-pcr11 --device ", - file=sys.stderr, - ) - sys.exit(1) - - expected = EXPECTED_PCR11.read_text().strip().lower() - actual = read_pcr(11) - - if actual != expected: - print( - "Error: PCR 11 mismatch!\n" - f" expected: {expected}\n" - f" actual: {actual}", - file=sys.stderr, - ) - sys.exit(1) - - policy["11"] = [actual] + policy["11"] = [read_pcr(11)] return policy diff --git a/tests/keylime.nix b/tests/keylime.nix index 4d167ba..05071ec 100644 --- a/tests/keylime.nix +++ b/tests/keylime.nix @@ -228,7 +228,7 @@ in with subtest("Agent can be added for attestation with PCR policy"): tpm_policy = json.loads( - agent.succeed("read-firmware-pcrs --verify-pcr11") + agent.succeed("read-firmware-pcrs") ) policy = json.dumps({"7": tpm_policy["7"], "11": tpm_policy["11"]})