hardware firmware security

Validating TPM Measured Boot and Attestation

Verify TPM PCRs and measured-boot and remote-attestation integrity.

hardware-firmware-securityintegrity-verificationmeasured-bootpcrremote-attestationtpmtpm2-toolstrusted-computing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Legal Notice: Perform TPM operations only on systems you own or are authorized to assess. Some operations (clearing the TPM, taking ownership, defining NV indices) are destructive or can lock the device. This skill is for defensive integrity verification and authorized assessment.

Overview

A Trusted Platform Module (TPM 2.0) is a hardware root of trust that supports measured boot: each stage of the boot chain hashes ("measures") the next stage and extends that measurement into a Platform Configuration Register (PCR) before handing off control. PCRs cannot be set arbitrarily — they can only be extended (new = hash(old || measurement)), so the final PCR value is a tamper-evident summary of everything that executed. The TCG firmware profile assigns specific meanings: UEFI firmware code/config measure into PCR 0–7 (PCR 7 specifically captures Secure Boot policy), bootloaders measure the kernel into PCR 8–9, and Linux IMA measures executables into PCR 10.

Two complementary verifications matter. Local validation reads current PCRs (tpm2_pcrread) and replays the TPM event log (tpm2_eventlog on /sys/kernel/security/tpm0/binary_bios_measurements) to confirm the recorded measurements reproduce the live PCR values — proving the log is authentic and revealing exactly what changed if a value drifts from baseline. Remote attestation has the TPM produce a signed quote (tpm2_quote) over selected PCRs using an Attestation Key (AK), with a fresh nonce to prevent replay; a verifier then independently checks the signature and PCR digest (tpm2_checkquote) and compares against a golden/expected value. This lets a server cryptographically establish that a remote machine booted approved firmware and kernel before granting access, sealing secrets, or admitting it to a Zero Trust network.

This skill provides the full tpm2-tools workflow for enrolling an AK, capturing and verifying quotes, replaying event logs, sealing/unsealing data to a PCR policy, and building a golden-value baseline for fleet attestation.

When to Use

  • Establishing that endpoints/servers booted a known-good firmware and kernel before granting access (Zero Trust device posture).
  • Detecting boot-chain tampering (bootkits, unauthorized firmware/kernel changes) via PCR drift from a baseline.
  • Verifying measured-boot integrity after a Secure Boot or firmware incident.
  • Sealing secrets (disk keys, credentials) to a measured-boot state so they only release on a trusted boot.
  • Building or auditing a remote-attestation service (e.g., Keylime, custom verifier).

Prerequisites

  • A system with a TPM 2.0 device and the resource manager:
    sudo apt install tpm2-tools tpm2-abrmd        # Debian/Ubuntu
    sudo dnf install tpm2-tools tpm2-abrmd        # Fedora/RHEL
  • Access to the TPM (/dev/tpm0 / /dev/tpmrm0) and the kernel measurement log at /sys/kernel/security/tpm0/binary_bios_measurements.
  • Root for reading some sysfs entries and for NV/AK operations.
  • For remote attestation: a verifier host and a transport for the quote/nonce exchange.

Objectives

  • Read and interpret PCR banks and their TCG-assigned meanings.
  • Verify the TPM event log reproduces live PCR values (event-log replay).
  • Create an Attestation Key and produce a nonce-bound signed quote.
  • Independently verify the quote signature and PCR digest on a verifier.
  • Establish golden PCR baselines and detect drift across a fleet.
  • Optionally seal/unseal a secret to a measured-boot PCR policy.

MITRE ATT&CK Mapping

Technique ID Technique Name Relevance
T1542 Pre-OS Boot Measured boot detects pre-OS tampering this technique relies on.
T1542.001 Pre-OS Boot: System Firmware PCR 0–7 drift reveals unauthorized firmware modification.
T1542.003 Pre-OS Boot: Bootkit Bootloader/kernel measurements (PCR 8–10) expose bootkit changes.
T1014 Rootkit IMA measurements (PCR 10) and quote verification surface concealed tampering.
T1601.001 Modify System Image: Patch System Image Attestation against golden values flags unauthorized image patches.

Workflow

1. Confirm the TPM is present and read its properties

tpm2_getcap properties-fixed | grep -i manufacturer
tpm2_getcap pcrs                 # list supported PCR banks (sha1, sha256, ...)

2. Read current PCR values

PCR 7 = Secure Boot policy; PCR 0–7 = firmware; PCR 8–9 = bootloader/kernel; PCR 10 = IMA.

tpm2_pcrread sha256                       # all sha256 PCRs
tpm2_pcrread sha256:0,1,2,3,4,5,6,7       # firmware + Secure Boot policy
tpm2_pcrread sha256:7                      # Secure Boot policy only

3. Replay the TPM event log against live PCRs

Confirm the recorded log reproduces the current PCR values (authenticity check).

tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements > eventlog.yaml
# The YAML includes a "pcrs" section with the calculated values — compare to step 2.
# Any mismatch means the event log and the live PCRs disagree (tampering or stale log).

4. Create a primary key and an Attestation Key (AK)

The AK signs quotes; its public part is shared with the verifier out-of-band.

tpm2_createprimary -C e -g sha256 -G rsa -c primary.ctx
tpm2_create -C primary.ctx -G rsa -u ak.pub -r ak.priv \
  -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign'
tpm2_load -C primary.ctx -u ak.pub -r ak.priv -c ak.ctx
tpm2_readpublic -c ak.ctx -o ak.pem -f pem      # export AK public key for the verifier

5. Produce a nonce-bound quote (attestor side)

The verifier supplies a fresh random nonce to defeat replay.

NONCE=$(openssl rand -hex 20)
tpm2_quote -c ak.ctx -l sha256:0,1,2,3,4,5,6,7,8,9 \
  -q "$NONCE" -m quote.msg -s quote.sig -o quote.pcrs -g sha256
# Send quote.msg, quote.sig, quote.pcrs (and the nonce) to the verifier.

6. Verify the quote (verifier side)

Independently validate the signature, nonce, and PCR digest with the AK public key.

tpm2_checkquote -u ak.pem -m quote.msg -s quote.sig -f quote.pcrs \
  -q "$NONCE" -g sha256
# Exit 0 + matching PCR digest == authentic, fresh, untampered quote.

7. Build and compare against a golden baseline

Compare attested PCRs to known-good values captured from a trusted reference build.

# Capture golden values on a trusted reference machine:
tpm2_pcrread sha256:0,7 > golden_pcrs.txt
# On each attested host, diff the quote's PCR section against golden_pcrs.txt.
diff <(grep -A8 'sha256' quote.pcrs) golden_pcrs.txt

8. Seal a secret to a measured-boot policy (optional)

Bind a secret so the TPM only releases it when PCRs match the trusted state.

tpm2_createpolicy --policy-pcr -l sha256:7 -L pcr7.policy -f pcr7.dat
echo -n "diskkey" | tpm2_create -C primary.ctx -L pcr7.policy \
  -i - -u sealed.pub -r sealed.priv
tpm2_load -C primary.ctx -u sealed.pub -r sealed.priv -c sealed.ctx
# Unseal succeeds only while PCR 7 matches the sealed policy:
tpm2_unseal -c sealed.ctx -p pcr:sha256:7

9. Inspect IMA runtime measurements (PCR 10)

If IMA is enabled, the runtime measurement list extends PCR 10.

tpm2_pcrread sha256:10
head -20 /sys/kernel/security/ima/ascii_runtime_measurements

10. Run the bundled attestation helper

agent.py reads PCRs, replays the event log, optionally produces+verifies a quote, and diffs a baseline.

sudo python scripts/agent.py --pcrs 0,1,2,3,4,5,6,7 --baseline golden_pcrs.json --output attest.json

Tools and Resources

Tool Purpose Source
tpm2-tools CLI for all TPM 2.0 operations https://github.com/tpm2-software/tpm2-tools
tpm2-tss TSS2 stack the tools build on https://github.com/tpm2-software/tpm2-tss
Keylime Scalable remote attestation framework https://github.com/keylime/keylime
TCG PC Client Platform Firmware Profile PCR usage specification https://trustedcomputinggroup.org/
Linux IMA docs Integrity Measurement Architecture https://sourceforge.net/p/linux-ima/wiki/Home/
RFC 9683 Remote integrity verification of TPM devices https://datatracker.ietf.org/doc/html/rfc9683

Validation Criteria

  • TPM 2.0 presence and supported PCR banks confirmed.
  • Current PCR values read for firmware and Secure Boot policy PCRs.
  • Event log replayed and confirmed to reproduce live PCR values.
  • Attestation Key created and its public key exported to the verifier.
  • Nonce-bound quote produced and independently verified with tpm2_checkquote.
  • Attested PCRs compared against a golden baseline; drift flagged.
  • (Optional) Secret sealed/unsealed against a PCR policy successfully.
  • IMA runtime measurements reviewed where enabled.
  • Results documented per host with pass/fail and any drift evidence.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.5 KB

tpm2-tools Command Reference

Discovery

Command Description
tpm2_getcap properties-fixed TPM manufacturer / fixed properties.
tpm2_getcap pcrs Supported PCR banks (sha1, sha256, ...).
tpm2_pcrread sha256 Read all sha256 PCRs.
tpm2_pcrread sha256:0,7 Read specific PCRs (here firmware + Secure Boot policy).

Event log

Command Description
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements Parse + replay the firmware event log; output includes calculated PCRs.

Attestation key

Command Description
tpm2_createprimary -C e -g sha256 -G rsa -c primary.ctx Create an endorsement-hierarchy primary key.
tpm2_create -C primary.ctx -G rsa -u ak.pub -r ak.priv -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign' Create a restricted signing AK.
tpm2_load -C primary.ctx -u ak.pub -r ak.priv -c ak.ctx Load the AK into the TPM.
tpm2_readpublic -c ak.ctx -o ak.pem -f pem Export the AK public key (PEM) for the verifier.

Quote / verify

Command Description
tpm2_quote -c ak.ctx -l sha256:0,1,...,9 -q <nonce> -m quote.msg -s quote.sig -o quote.pcrs -g sha256 Produce a nonce-bound signed quote.
tpm2_checkquote -u ak.pem -m quote.msg -s quote.sig -f quote.pcrs -q <nonce> -g sha256 Verify signature, nonce, and PCR digest (verifier side).

Sealing to PCR policy

Command Description
tpm2_createpolicy --policy-pcr -l sha256:7 -L pcr7.policy -f pcr7.dat Build a PCR-bound auth policy.
tpm2_create -C primary.ctx -L pcr7.policy -i - -u sealed.pub -r sealed.priv Seal a secret to the policy.
tpm2_unseal -c sealed.ctx -p pcr:sha256:7 Release the secret only if PCRs match.

IMA (PCR 10)

Command Description
tpm2_pcrread sha256:10 Read the IMA aggregate PCR.
cat /sys/kernel/security/ima/ascii_runtime_measurements View the IMA runtime measurement list.

TCG PCR index meanings (PC Client profile)

PCR Measures
0 UEFI firmware code (CRTM, boot block).
1 UEFI firmware configuration / host platform config.
2–3 Option ROM code and config.
4 Boot manager / MBR code.
5 Boot manager config / GPT.
6 Platform-specific events.
7 Secure Boot policy (PK/KEK/db/dbx state).
8–9 Bootloader-measured kernel/initrd.
10 Linux IMA runtime measurements.
standards.md1.5 KB

Standards and Framework Mapping

MITRE ATT&CK

ID Name Rationale
T1542 Pre-OS Boot Measured boot detects the pre-OS tampering this technique relies on.
T1542.001 Pre-OS Boot: System Firmware PCR 0–7 drift reveals unauthorized firmware modification.
T1542.003 Pre-OS Boot: Bootkit Bootloader/kernel measurements (PCR 8–10) expose bootkit changes.
T1014 Rootkit Quote verification and IMA measurements surface concealed tampering.
T1601.001 Modify System Image: Patch System Image Attestation against golden values flags unauthorized image patches.

NIST Cybersecurity Framework 2.0

ID Name Rationale
PR.PS-01 Configuration management practices are established and applied Measured-boot baselining and attestation enforce and verify the approved firmware/kernel configuration of platforms.

Supporting Standards and References

  • TCG TPM 2.0 Library Specification. Defines PCRs, extend semantics, quotes, and attestation keys.
  • TCG PC Client Platform Firmware Profile. Assigns PCR index meanings (PCR 0–7 firmware, PCR 7 Secure Boot, PCR 8–10 OS/IMA).
  • RFC 9683 — Remote Integrity Verification of Network Devices Containing TPMs. Standardizes TPM-based remote attestation.
  • NIST SP 800-155 — BIOS Integrity Measurement Guidelines. Measured-boot and integrity reporting guidance.
  • NSA UEFI/Boot Security guidance. Recommends measured boot + attestation alongside Secure Boot.

Scripts 1

agent.py6.2 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
TPM 2.0 measured-boot and attestation helper.

Reads PCRs via tpm2-tools, replays the firmware event log, optionally produces
and verifies a nonce-bound quote, and diffs measured PCRs against a golden
baseline JSON. Emits a structured report.

Requires tpm2-tools (tpm2_pcrread, tpm2_eventlog, tpm2_quote, tpm2_checkquote).
Authorized integrity-verification use only.
"""
import argparse
import json
import os
import re
import secrets
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

EVENTLOG = "/sys/kernel/security/tpm0/binary_bios_measurements"


def run(cmd: list[str], timeout: int = 120) -> tuple[int, str, str]:
    try:
        p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        return p.returncode, p.stdout, p.stderr
    except FileNotFoundError:
        return 127, "", f"not found: {cmd[0]}"
    except subprocess.SubprocessError as exc:
        return 1, "", str(exc)


def need(tool: str) -> bool:
    if not shutil.which(tool):
        print(f"[error] required tool missing: {tool}", file=sys.stderr)
        return False
    return True


def read_pcrs(selection: str) -> dict:
    if not need("tpm2_pcrread"):
        return {"error": "tpm2_pcrread missing"}
    rc, out, err = run(["tpm2_pcrread", f"sha256:{selection}"])
    if rc != 0:
        return {"error": err.strip() or "tpm2_pcrread failed", "returncode": rc}
    pcrs = {}
    for line in out.splitlines():
        m = re.match(r"\s*(\d+)\s*:\s*(0x[0-9A-Fa-f]+)", line)
        if m:
            pcrs[m.group(1)] = m.group(2).lower()
    return pcrs


def replay_eventlog() -> dict:
    if not need("tpm2_eventlog"):
        return {"error": "tpm2_eventlog missing"}
    if not os.path.exists(EVENTLOG):
        return {"error": f"event log not found at {EVENTLOG} (run as root?)"}
    rc, out, err = run(["tpm2_eventlog", EVENTLOG])
    if rc != 0:
        return {"error": err.strip() or "tpm2_eventlog failed"}
    # Pull the calculated pcrs section emitted by the tool
    calc = {}
    for line in out.splitlines():
        m = re.match(r"\s*(\d+)\s*:\s*(0x[0-9A-Fa-f]+)", line)
        if m:
            calc[m.group(1)] = m.group(2).lower()
    return {"calculated_pcrs": calc, "event_count": out.count("EventNum")}


def attest(selection: str, workdir: Path) -> dict:
    for tool in ("tpm2_createprimary", "tpm2_create", "tpm2_load", "tpm2_readpublic",
                 "tpm2_quote", "tpm2_checkquote"):
        if not need(tool):
            return {"error": f"{tool} missing"}
    primary = workdir / "primary.ctx"
    akpub, akpriv, akctx, akpem = (workdir / f for f in ("ak.pub", "ak.priv", "ak.ctx", "ak.pem"))
    qmsg, qsig, qpcrs = (workdir / f for f in ("quote.msg", "quote.sig", "quote.pcrs"))
    nonce = secrets.token_hex(20)

    steps = [
        ["tpm2_createprimary", "-C", "e", "-g", "sha256", "-G", "rsa", "-c", str(primary)],
        ["tpm2_create", "-C", str(primary), "-G", "rsa", "-u", str(akpub), "-r", str(akpriv),
         "-a", "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign"],
        ["tpm2_load", "-C", str(primary), "-u", str(akpub), "-r", str(akpriv), "-c", str(akctx)],
        ["tpm2_readpublic", "-c", str(akctx), "-o", str(akpem), "-f", "pem"],
        ["tpm2_quote", "-c", str(akctx), "-l", f"sha256:{selection}", "-q", nonce,
         "-m", str(qmsg), "-s", str(qsig), "-o", str(qpcrs), "-g", "sha256"],
    ]
    for cmd in steps:
        rc, out, err = run(cmd)
        if rc != 0:
            return {"error": f"{cmd[0]} failed: {err.strip()}"}

    rc, out, err = run(["tpm2_checkquote", "-u", str(akpem), "-m", str(qmsg),
                        "-s", str(qsig), "-f", str(qpcrs), "-q", nonce, "-g", "sha256"])
    return {"nonce": nonce, "verified": rc == 0,
            "checkquote_output": (out or err)[:800]}


def diff_baseline(pcrs: dict, baseline_path: str) -> dict:
    try:
        baseline = json.loads(Path(baseline_path).read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        return {"error": f"cannot read baseline: {exc}"}
    drift = {k: {"expected": baseline[k], "actual": pcrs.get(k)}
             for k in baseline if baseline.get(k) != pcrs.get(k)}
    return {"match": not drift, "drift": drift}


def main() -> int:
    ap = argparse.ArgumentParser(description="TPM measured-boot attestation helper")
    ap.add_argument("--pcrs", default="0,1,2,3,4,5,6,7", help="comma-separated PCR indices")
    ap.add_argument("--quote", action="store_true", help="produce + verify an AK quote")
    ap.add_argument("--baseline", help="golden PCR baseline JSON to diff against")
    ap.add_argument("--save-baseline", help="write current PCRs as a baseline JSON")
    ap.add_argument("--output", help="write JSON report")
    args = ap.parse_args()

    report = {"pcr_selection": args.pcrs}
    pcrs = read_pcrs(args.pcrs)
    report["pcrs"] = pcrs
    if "error" not in pcrs:
        for idx, val in sorted(pcrs.items(), key=lambda x: int(x[0])):
            print(f"PCR[{idx}] = {val}")

    report["eventlog"] = replay_eventlog()
    if isinstance(pcrs, dict) and "calculated_pcrs" in report["eventlog"]:
        calc = report["eventlog"]["calculated_pcrs"]
        mismatches = {k: (pcrs.get(k), calc.get(k)) for k in calc if pcrs.get(k) != calc.get(k)}
        report["eventlog_replay_match"] = not mismatches
        if mismatches:
            print(f"[!] event-log replay MISMATCH: {mismatches}")
        else:
            print("[+] event-log replay matches live PCRs")

    if args.save_baseline and "error" not in pcrs:
        Path(args.save_baseline).write_text(json.dumps(pcrs, indent=2), encoding="utf-8")
        print(f"[+] baseline saved to {args.save_baseline}")

    if args.baseline and "error" not in pcrs:
        report["baseline_diff"] = diff_baseline(pcrs, args.baseline)
        print(f"[+] baseline match: {report['baseline_diff'].get('match')}")

    if args.quote:
        with tempfile.TemporaryDirectory() as td:
            report["attestation"] = attest(args.pcrs, Path(td))
        print(f"[+] quote verified: {report['attestation'].get('verified')}")

    if args.output:
        Path(args.output).write_text(json.dumps(report, indent=2), encoding="utf-8")
        print(f"[+] report written to {args.output}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
Keep exploring