fleet-enrol: content-aware NV check (-check-only), not blind "index defined"
The enrol trigger keyed on "is NV 0x01800001 defined/readable", so leftover state (an earlier tpm-provision run — bench-seen on the 820: "cert present — normal boot" with no real identity) or random bytes (tpm-selftest) silently suppressed enrolment. Now content-aware: - tpmkey.ReadNVCert reads the full NV contents (chunked). - classifyNVCert (TPM-agnostic, unit-tested): parses X.509, checks validity window, and when a real fleet CA bundle is present verifies the cert chains to it. Only a valid fleet cert = enrolled. - New `fleet-enrol -check-only` (no network/no key-gen) exits 0=valid cert, 20=enrol needed, 1=error. The full enrol path uses the same classification instead of NVPresent. - The dracut hook calls -check-only as the single gate, replacing the blind tpm2_nvread (and its now-dead TPM2TOOLS_TCTI export). tpm2_nvread stays in the initramfs for the secure path (dot1x-macsec-up.sh). Lab fallback: the shipped radius-ca.pem is a placeholder (not a cert) -> loadCAPool returns nil -> chain-less "parseable + unexpired" acceptance, so reboot-converge doesn't loop; production replaces radius-ca.pem with the real fleet CA for strict chaining. Tests: classifyNVCert table (valid/expired/garbage/foreign/no-CA) + simtest ReadNVCert roundtrip across the 512B chunk. All pass; packer validate clean.
This commit is contained in:
+15
-7
@@ -1077,13 +1077,21 @@ have their EK there), so enrolment could never create our key.
|
||||
(`avc: denied … tpm_device_t`); the faithful path is the **initramfs** run
|
||||
(reboot, unconfined), or `enforcing=0` for hand-run TPM inspection.
|
||||
|
||||
**FOLLOW-UP HARDENING (from the two findings above, not yet built):**
|
||||
- **Content-aware NV presence check** — read NV `0x01800001`, parse as X.509, verify
|
||||
it **chains to the fleet CA** (bundled `radius-ca.pem`); only then treat as enrolled,
|
||||
else enrol + overwrite (`WriteNVCert` already undefine+redefines). Cleanest as a
|
||||
`fleet-enrol -check-only` mode (no network) the hook calls — one content-aware source
|
||||
of truth, replacing the hook's blind `tpm2_nvread` and the binary's `NVPresent`
|
||||
(defined-only). Stops leftover/foreign NV from suppressing enrolment.
|
||||
**FOLLOW-UP HARDENING:**
|
||||
- **Content-aware NV presence check — BUILT 2026-07-03.** `fleet-enrol -check-only`
|
||||
(no network / no key-gen) reads NV `0x01800001` via `tpmkey.ReadNVCert`, parses it as
|
||||
X.509, checks the validity window, and — when a real fleet CA is bundled (`-cacert`,
|
||||
default `radius-ca.pem`) — verifies it **chains** to it. Only a valid fleet cert counts
|
||||
as enrolled (rc 0); empty/garbage/foreign/expired → rc 20 (enrol + overwrite;
|
||||
`WriteNVCert` undefine+redefines). The dracut hook now calls it as the single gate,
|
||||
replacing the blind `tpm2_nvread` (removed from `tpm-enrol.sh`; the binary's `NVPresent`
|
||||
stays for cheap existence checks). **Lab fallback:** the shipped `radius-ca.pem` is a
|
||||
*placeholder* (comments, not a cert) → `loadCAPool` returns nil → chain-less "parseable
|
||||
+ unexpired" acceptance, so the reboot-converge doesn't loop on a bench-CA cert;
|
||||
**production must replace `radius-ca.pem` with the real fleet CA** for strict chaining.
|
||||
Tests: `classifyNVCert` unit table (valid/expired/garbage/foreign/no-CA) + `simtest`
|
||||
ReadNVCert roundtrip across the 512B chunk. **BENCH-VERIFY** on a real boot: a garbage/
|
||||
foreign NV now enrols instead of "normal boot".
|
||||
- **De-enrol / TPM-reset procedure** for re-testing: `tpm2_nvundefine 0x01800001 -C o`
|
||||
(+ `tpm2_evictcontrol -C o -c 0x81000100` for a stale *app* key — never the EK), or
|
||||
BIOS "Clear TPM". Document in the bench README.
|
||||
|
||||
Reference in New Issue
Block a user