tpm-enrol: move NV cert index off OEM-occupied 0x01800001 → 0x01800100
The Dell 820 kept "finding a cert" and bypassing enrolment despite repeated BIOS TPM clears. tpm2_nvreadpublic revealed 0x01800001 on that hardware is a FIRMWARE platform index (platformcreate|policydelete|writelocked, 70 bytes — not our cert; siblings at 0x01800003/4 + 0x01810000). platformcreate survives TPM2_Clear and policydelete needs the platform's secret policy, so it's permanent and unusable by us — the blind check false-positived on it, and enrolment could never write there anyway. Same class of bug as the EK-handle collision. Move the NV cert index default 0x01800001 -> 0x01800100 (free on the 820, owner range, overridable via rd.dot1x.nvindex): cmdline (extract-artifacts.sh), fleet-enrol -nvindex, the dracut hooks, bench scripts, CLAUDE.md invariant #1, and design/SSP docs. The signing-key handle (0x81000100) is unchanged. Harden WriteNVCert: refuse a platformcreate index with a clear "pick a free owner index via rd.dot1x.nvindex" error instead of a confusing undefine failure. Regression simtest/TestWriteNVCertRefusesPlatformIndex; NV roundtrip + ReadNVCert tests moved to 0x01800100. All go tests + packer validate pass.
This commit is contained in:
@@ -8,7 +8,7 @@ end-to-end *minus* the human-approval queue; the only unbuilt piece is the
|
||||
**enrolment broker**.
|
||||
|
||||
How a diskless host acquires — and keeps — its **one cryptographic identity**: a
|
||||
signing key at TPM handle `0x81000100` and a cert at NV index `0x01800001`.
|
||||
signing key at TPM handle `0x81000100` and a cert at NV index `0x01800100`.
|
||||
Invariant #1 forbids baking any per-host state into the image, so this identity
|
||||
**must** be minted at enrolment, at runtime, from the TPM. That single identity is
|
||||
then reused for everything: **802.1X EAP-TLS**, the **NVMe hostnqn** (derived from
|
||||
@@ -29,7 +29,7 @@ separate box — is what makes the two designs one coherent identity story.
|
||||
## Design philosophy (forced by the architecture invariants)
|
||||
|
||||
1. **One identity, minted once, reused everywhere.** Key `0x81000100` + cert
|
||||
NV `0x01800001` = 802.1X identity = NVMe hostnqn (`…:host-<cert-serial>`) =
|
||||
NV `0x01800100` = 802.1X identity = NVMe hostnqn (`…:host-<cert-serial>`) =
|
||||
mTLS client identity. There is no second secret. (Invariant #1.)
|
||||
2. **The private key never leaves the TPM.** Generated *in* the TPM; only a CSR
|
||||
and the issued cert ever cross the wire — both are public. This is why the
|
||||
@@ -50,7 +50,7 @@ separate box — is what makes the two designs one coherent identity story.
|
||||
|
||||
## The identity lifecycle (the load-bearing state machine)
|
||||
|
||||
Triggered by the state of NV `0x01800001` and the cert within it. The three events
|
||||
Triggered by the state of NV `0x01800100` and the cert within it. The three events
|
||||
are deliberately **split** — conflating them is a security regression:
|
||||
|
||||
```
|
||||
@@ -76,7 +76,7 @@ are deliberately **split** — conflating them is a security regression:
|
||||
|
||||
Key generated in the TPM, persisted at `0x81000100`; CSR built with it via the
|
||||
openssl tpm2 provider (key never leaves the TPM); issued cert written DER to NV
|
||||
`0x01800001`. At boot, `dot1x-macsec-up.sh` reads the cert from NV, wpa_supplicant
|
||||
`0x01800100`. At boot, `dot1x-macsec-up.sh` reads the cert from NV, wpa_supplicant
|
||||
signs EAP-TLS with the handle key, and the hostnqn is derived from the cert serial.
|
||||
All of this is built and bench-proven; enrolment just replaces the *manual* sign
|
||||
step with a brokered, human-gated one.
|
||||
@@ -174,7 +174,7 @@ pinned for mTLS — one trust anchor across 802.1X, NVMe, and management.
|
||||
store cert ◀───────────────────────────────
|
||||
8. GET /enrol/status/{id} (poll) ───▶
|
||||
◀── APPROVED + cert ────────────────
|
||||
9. write cert DER → NV 0x01800001
|
||||
9. write cert DER → NV 0x01800100
|
||||
10. reboot into production UKI → 802.1X EAP-TLS → mTLS mgmt
|
||||
```
|
||||
|
||||
@@ -221,7 +221,7 @@ on the existing `disable_dot1x` / `boot_splash` cmdline toggles):
|
||||
auto-detects empty-NV and branches into enrolment in early boot. Picked because it
|
||||
needs **no separate provisioning boot path** — everything stays purely
|
||||
network-booted (no USB / no provisioning media to manage). The empty-NV gate is the
|
||||
safety: NV-write at `0x01800001` needs TPM owner auth, so empty-NV is hard to
|
||||
safety: NV-write at `0x01800100` needs TPM owner auth, so empty-NV is hard to
|
||||
coerce. Accepted trade-offs: production carries the enrolment + pre-auth broker code
|
||||
permanently, and there is no PCR-11 discrimination. The empty-NV branch is mutually
|
||||
exclusive with the secure path, so it does not endanger the load-bearing hook
|
||||
@@ -346,7 +346,7 @@ exists. Enrolment is where it comes into being.
|
||||
trigger** as a static **Go + go-tpm** binary (`files/enrol/`, `fleet-enrol`)
|
||||
talking to `/dev/tpmrm0` directly — ensures the key at `0x81000100`, builds the
|
||||
CSR, POSTs to the broker, shows the Plymouth fingerprint, polls, writes the leaf
|
||||
cert to NV `0x01800001`. A thin dracut GATE hook (`95dot1x-macsec/tpm-enrol.sh`,
|
||||
cert to NV `0x01800100`. A thin dracut GATE hook (`95dot1x-macsec/tpm-enrol.sh`,
|
||||
initqueue/settled prio 25) does the empty-NV check + pre-auth DHCP on the
|
||||
physical NIC + reboot-to-converge. No tpm2-tools key-gen / openssl provider /
|
||||
curl needed in the image (go-tpm does it all). Built **out of the VM** on the CI
|
||||
|
||||
Reference in New Issue
Block a user