diff --git a/CLAUDE.md b/CLAUDE.md index a78f253..5a1aed0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,10 +106,13 @@ instead of working around it. All host identity derives from the TPM at runtime (signing key at persistent handle `0x81000100` — a **non-reserved owner handle**, deliberately NOT the EK slot `0x81010001` nor the SRK `0x81000001`, which firmware/swtpm - provisioning already occupy; cert at NV index `0x01800001`, hostnqn derived - from the cert serial). If a change needs per-host data, it belongs in the - enrolment flow, not the image. (The handle is overridable via - `rd.dot1x.handle=` / `fleet-enrol -handle`.) + provisioning already occupy; cert at NV index `0x01800100` — also chosen to + avoid **OEM platform NV**: a Dell 820 firmware squats `0x01800001`–`0x01800004` + + `0x01810000` as `platformcreate|policydelete` indices that no owner/BIOS + clear can remove, so the original `0x01800001` collided; hostnqn derived from + the cert serial). If a change needs per-host data, it belongs in the enrolment + flow, not the image. (Both are overridable: `rd.dot1x.handle=` / + `rd.dot1x.nvindex=` / `fleet-enrol -handle -nvindex`.) 1. **The hook ordering in `dot1x-macsec-up.sh` is load-bearing.** NV-cert extraction → supplicant start → wait for `Authorized` + macsec0 → DHCP **on macsec0** → hostnqn derivation. Never DHCP before auth diff --git a/README.md b/README.md index 612c960..6dfa41f 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ release only changes which artifact image feeds them: ``` rd.neednet=1 systemd.volatile=overlay \ -rd.dot1x.nvindex=0x01800001 rd.dot1x.timeout=60 \ +rd.dot1x.nvindex=0x01800100 rd.dot1x.timeout=60 \ rd.nvmf.discover=tcp,NVMET_TRADDR,4420 \ root=/dev/mapper/root rootfstype=erofs rootflags=ro \ systemd.verity=yes roothash= \ diff --git a/docs/TODO.md b/docs/TODO.md index 3ab4651..a0c251d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -783,7 +783,7 @@ smoke test. returns MSK); a `hostapd` wired authenticator does 802.1X + MACsec on an **isolated bridge per workstation** (flat VLAN can't auth — EAPOL dropped + MACsec is P2P). `enrol/tpm-provision.sh` creates the key in the TPM at - `0x81000100` + writes the cert to NV `0x01800001`. Starting solution **signs a + `0x81000100` + writes the cert to NV `0x01800100`. Starting solution **signs a CSR** (key stays in TPM); **TPM attestation** (ACME `device-attest-01`) is the documented upgrade on the same CA. **BENCH-VERIFY** items flagged in the files: hostapd MACsec build, openssl tpm2 provider vs pkcs11 key ref, EAP-TLS version. @@ -1026,7 +1026,7 @@ path needs **no tpm2-tools key-gen / no openssl tpm2 provider / no curl** in the image (removes the biggest bench risk — the provider `handle:` key-ref). It ensures the ECDSA key at `0x81000100`, builds the CSR, POSTs CSR+context to `rd.fleet.enrol_url`, shows the broker fingerprint on Plymouth, polls `/status`, -and writes the leaf cert to NV `0x01800001`. The dracut hook `tpm-enrol.sh` +and writes the leaf cert to NV `0x01800100`. The dracut hook `tpm-enrol.sh` (initqueue/settled prio 25) is now a thin GATE: empty-NV check (`tpm2_nvread`, still needed for the secure-path read) → pre-auth DHCP on `en*` (nmcli override of the secure-mode unmanaged drop-in) → `exec /usr/sbin/fleet-enrol` → reboot to @@ -1071,7 +1071,7 @@ have their EK there), so enrolment could never create our key. **`0x81000100`** (non-reserved owner handle; threaded through `fleet-enrol -handle` default, `rd.dot1x.handle=` cmdline knob baked by `extract-artifacts.sh`, `wpa_supplicant-dot1x.conf`, the bench scripts, invariant #1, and the design docs). - NV `0x01800001` (owner range) is unchanged. + NV index was `0x01800001` here (owner range); it MOVED to `0x01800100` in a later finding (OEM platform-NV collision on the 820 — see below). - **EnsureKey hardening:** (i) evicts a genuine *stale signing key* at the handle and recreates (self-heal for old provisioning leftovers); (ii) **refuses to evict any restricted-decrypt object (EK/SRK)** as a safety net — never clobber the TPM's @@ -1096,7 +1096,7 @@ have their EK there), so enrolment could never create our key. **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 + (no network / no key-gen) reads NV `0x01800100` 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; @@ -1109,10 +1109,30 @@ have their EK there), so enrolment could never create our key. 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` +- **De-enrol / TPM-reset procedure** for re-testing: `tpm2_nvundefine 0x01800100 -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. +**BENCH FINDING 2026-07-03 (FIXED) — NV index `0x01800001` collided with OEM +platform NV; moved to `0x01800100`.** The Dell 820 kept "finding a cert" and +bypassing enrolment even after repeated BIOS TPM clears. `tpm2_nvreadpublic` +showed the truth: `0x01800001` on that hardware is a **firmware/platform** index +(`platformcreate|policydelete|writelocked`, 70 bytes — NOT our cert; siblings at +`0x01800003/4` + `0x01810000` share it), which **no owner/BIOS clear can remove** +(`TPM2_Clear` spares `platformcreate`; `policydelete` needs the platform's secret +policy). So our chosen index was permanently occupied by Dell — the blind check +false-positived on it, and even the content-aware check would have failed at the +NV **write** (can't undefine a platform index). Same class as the EK-handle +collision. Fix: **moved the NV cert index default `0x01800001` → `0x01800100`** +(free on the 820; owner range; overridable via `rd.dot1x.nvindex=`), threaded +through the cmdline (`extract-artifacts.sh`), `fleet-enrol -nvindex`, the dracut +hooks, bench scripts, invariant #1, and docs. Also **hardened `WriteNVCert`** to +refuse a `platformcreate` index with a clear "pick a free owner index" error +instead of a confusing undefine failure (regression `simtest/ +TestWriteNVCertRefusesPlatformIndex`). **BENCH-VERIFY:** 820 now enrols at +`0x01800100`; and check `tpm2_nvreadpublic` on any OTHER hardware model in the +fleet — if a vendor also squats `0x018001xx`, pick a value free on all of them. + **BENCH-VERIFY (host side, before trusting):** (a) go-tpm against a REAL TPM + swtpm on the target (the simulator proves the ceremony, not the device) — **the stale-key fix (above) is the first real-TPM result; re-boot to confirm it now @@ -1159,7 +1179,7 @@ Verification found + fixed two things the `mock` path had hidden: FreeRADIUS (next, before host enrolment). How a host gets its **802.1X EAP-TLS client identity** the first time — the key at -TPM handle `0x81000100` + cert at NV `0x01800001` that invariant #1 says must come +TPM handle `0x81000100` + cert at NV `0x01800100` that invariant #1 says must come from enrolment, never the image. Distinct from #10 (that enrols *Secure Boot* PK/KEK/db); this enrols the *network identity*. They share the provisioning UKI + isolated-environment vehicle. Pairs with #8 + the CA decision (start **step-ca**, @@ -1170,7 +1190,7 @@ on an empty TPM the host generates a key *in* the TPM, builds a CSR, submits it an enrolment **broker**, and shows a **fingerprint** on the Plymouth splash; an admin visually cross-checks that fingerprint against the broker's pending-list and approves; the broker has step-ca sign the CSR and returns the cert; the host writes -it to NV `0x01800001` and proceeds to authenticate. Sound, recognised pattern +it to NV `0x01800100` and proceeds to authenticate. Sound, recognised pattern (SSH/WireGuard/Signal-style OOB verification); good fit for invariant #1. **Key design decisions (settled 2026-06-22):** diff --git a/docs/design-v2-errata.md b/docs/design-v2-errata.md index 0cf02ae..9d6a495 100644 --- a/docs/design-v2-errata.md +++ b/docs/design-v2-errata.md @@ -85,7 +85,7 @@ approval** — on an empty TPM the host generates a key *in* the TPM (`0x81000100`), submits a CSR to an enrolment **broker** (reachable via the same pre-auth permit-set, e.g. an `/enrol/` location on the serving nginx → broker → step-ca signer), and shows a fingerprint on the Plymouth splash; an admin -cross-checks it and approves; the signed cert is written to NV `0x01800001` and +cross-checks it and approves; the signed cert is written to NV `0x01800100` and authentication proceeds. Enrol triggers on **empty-NV only** (expired cert → automatic renew; auth-failure-with-cert → alert, not re-enrol). The fingerprint authorises *which device*; **TPM attestation** (step-ca ACME `device-attest-01`) diff --git a/docs/fleet-management-design.md b/docs/fleet-management-design.md index 5bcbeae..63cc389 100644 --- a/docs/fleet-management-design.md +++ b/docs/fleet-management-design.md @@ -20,7 +20,7 @@ These are consequences of the CLAUDE.md invariants, not preferences: stateless and buffers only in RAM (best-effort). 2. **Identity from the TPM, never baked.** No per-host state in any artifact (invariant #1). The agent derives identity at runtime from the **TPM-held - cert** (key `0x81000100`, cert NV `0x01800001`) — the same identity used for + cert** (key `0x81000100`, cert NV `0x01800100`) — the same identity used for 802.1X EAP-TLS and the NVMe hostnqn. No enrollment token in the image. 3. **Pull-based control, no inbound surface.** The boot LAN is locked down and segmented; we do not open an always-on inbound control port on hosts. Hosts diff --git a/docs/ssp.md b/docs/ssp.md index 999a309..fb5e584 100644 --- a/docs/ssp.md +++ b/docs/ssp.md @@ -91,7 +91,7 @@ manifest. One commit = one release = one digest = one manifest entry. 3. **Initramfs (dracut)** brings up the NIC and performs **802.1X EAP-TLS** network authentication using a **private key held in the TPM** (persistent handle `0x81000100`); the client certificate is read from TPM NV index - `0x01800001`. + `0x01800100`. 4. On authorisation, **MACsec** is established on the access link (link-layer encryption/integrity); the host then obtains an IP **on the MACsec interface** via DHCP. diff --git a/docs/tpm-enrolment-design.md b/docs/tpm-enrolment-design.md index 4930ab0..891c6a9 100644 --- a/docs/tpm-enrolment-design.md +++ b/docs/tpm-enrolment-design.md @@ -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-`) = + NV `0x01800100` = 802.1X identity = NVMe hostnqn (`…:host-`) = 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 diff --git a/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh b/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh index 3ab4416..5f55cc4 100755 --- a/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh +++ b/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh @@ -31,7 +31,7 @@ if [ -z "$NIC" ]; then NIC=$_name; break done fi -NVINDEX=$(getarg rd.dot1x.nvindex=); [ -n "$NVINDEX" ] || NVINDEX=0x01800001 +NVINDEX=$(getarg rd.dot1x.nvindex=); [ -n "$NVINDEX" ] || NVINDEX=0x01800100 TIMEOUT=$(getarg rd.dot1x.timeout=); [ -n "$TIMEOUT" ] || TIMEOUT=60 info "dot1x-macsec: using interface $NIC, NV index $NVINDEX" diff --git a/files/dracut/95dot1x-macsec/tpm-enrol.sh b/files/dracut/95dot1x-macsec/tpm-enrol.sh index 99b67bf..10a8de9 100644 --- a/files/dracut/95dot1x-macsec/tpm-enrol.sh +++ b/files/dracut/95dot1x-macsec/tpm-enrol.sh @@ -2,7 +2,7 @@ # tpm-enrol.sh — initqueue/settled hook, runs BEFORE dot1x-macsec-up.sh. # # INLINE empty-NV enrolment GATE (docs/tpm-enrolment-design.md). On a secure -# build whose TPM NV 0x01800001 is empty, it brings up a pre-auth network on the +# build whose TPM NV 0x01800100 is empty, it brings up a pre-auth network on the # physical NIC and hands off to the out-of-VM `fleet-enrol` binary (Go + go-tpm), # which generates the key/CSR, talks to the broker, shows the Plymouth # fingerprint, and writes the issued cert to NV. On success this hook REBOOTS. @@ -37,7 +37,7 @@ TESTMODE=0 [ "$(getarg rd.dot1x=)" = "0" ] && TESTMODE=1 # --- knobs (override on the kernel cmdline) --------------------------------- -NVINDEX=$(getarg rd.dot1x.nvindex=); [ -n "$NVINDEX" ] || NVINDEX=0x01800001 +NVINDEX=$(getarg rd.dot1x.nvindex=); [ -n "$NVINDEX" ] || NVINDEX=0x01800100 # Persistent handle for the signing key: a NON-reserved owner handle. NOT the EK # slot 0x81010001 (which most firmware/swtpm provisioning already occupies) nor # the SRK 0x81000001 — see EnsureKey / CLAUDE.md invariant #1. diff --git a/files/enrol/main.go b/files/enrol/main.go index 66a609f..3bbd3f8 100644 --- a/files/enrol/main.go +++ b/files/enrol/main.go @@ -1,13 +1,13 @@ // fleet-enrol — inline empty-NV first-boot TPM enrolment client. // // Invoked from the dracut hook (95dot1x-macsec/tpm-enrol.sh) ONLY when the -// secure build boots a host whose TPM NV 0x01800001 is empty. It: +// secure build boots a host whose TPM NV 0x01800100 is empty. It: // 1. ensures an ECDSA signing key exists at a non-reserved owner handle // (default 0x81000100 — deliberately NOT the EK slot 0x81010001), // 2. builds a CSR with it (key never leaves the TPM), // 3. submits CSR + device context to the enrolment broker, shows the returned // fingerprint on the Plymouth splash, and polls until an admin approves, -// 4. writes the issued cert (DER) to NV 0x01800001. +// 4. writes the issued cert (DER) to NV 0x01800100. // // The hook then reboots into the normal secure path. Everything host-specific // (key, cert, hostnqn) is created here at runtime — nothing is baked (invariant #1). @@ -49,7 +49,7 @@ func main() { var ( url = flag.String("url", "http://boot.fleet.lab/enrol", "enrolment broker base URL") tpmPath = flag.String("tpm", "/dev/tpmrm0", "TPM device path") - nvHex = flag.String("nvindex", "0x01800001", "TPM NV index for the client cert") + nvHex = flag.String("nvindex", "0x01800100", "TPM NV index for the client cert") handleHex = flag.String("handle", "0x81000100", "TPM persistent handle for the signing key (non-reserved owner handle; NOT the EK slot 0x81010001)") cn = flag.String("cn", "fleet-host", "CSR CommonName (generic; identity is the cert serial)") iface = flag.String("iface", "", "network interface for the device-context MAC") diff --git a/files/enrol/simtest/tpmsim_test.go b/files/enrol/simtest/tpmsim_test.go index 5c94fde..919f460 100644 --- a/files/enrol/simtest/tpmsim_test.go +++ b/files/enrol/simtest/tpmsim_test.go @@ -10,6 +10,7 @@ import ( "crypto/sha256" "crypto/x509" "crypto/x509/pkix" + "strings" "testing" "fleet-enrol/tpmkey" @@ -81,7 +82,7 @@ func TestEnsureKeyAndSignCSR(t *testing.T) { func TestNVWriteAndPresence(t *testing.T) { tpm := openSim(t) - const nv = tpm2.TPMHandle(0x01800001) + const nv = tpm2.TPMHandle(0x01800100) if tpmkey.NVPresent(tpm, nv) { t.Fatalf("NV should be empty on a fresh TPM") @@ -229,3 +230,32 @@ func TestEnsureKeyRefusesToEvictProtectedKey(t *testing.T) { t.Fatalf("protected object was evicted (should have been left intact): %v", err) } } + +// Regression for the Dell 820 finding: our NV index collided with an OEM +// PLATFORM-created index (platformcreate|policydelete) that no owner clear can +// remove. WriteNVCert must REFUSE such an index with a clear error (the remedy +// is a different rd.dot1x.nvindex), not a confusing undefine failure. +func TestWriteNVCertRefusesPlatformIndex(t *testing.T) { + tpm := openSim(t) + const nv = tpm2.TPMHandle(0x01800100) + + def := tpm2.NVDefineSpace{ + AuthHandle: tpm2.TPMRHPlatform, + PublicInfo: tpm2.New2B(tpm2.TPMSNVPublic{ + NVIndex: nv, + NameAlg: tpm2.TPMAlgSHA256, + Attributes: tpm2.TPMANV{ + PlatformCreate: true, AuthRead: true, AuthWrite: true, + NoDA: true, NT: tpm2.TPMNTOrdinary, + }, + DataSize: 8, + }), + } + if _, err := def.Execute(tpm); err != nil { + t.Fatalf("define platform NV: %v", err) + } + err := tpmkey.WriteNVCert(tpm, nv, make([]byte, 64)) + if err == nil || !strings.Contains(err.Error(), "platform-created") { + t.Fatalf("WriteNVCert should refuse a platform-created index; got: %v", err) + } +} diff --git a/files/enrol/tpmkey/tpmkey.go b/files/enrol/tpmkey/tpmkey.go index da30bf2..569f447 100644 --- a/files/enrol/tpmkey/tpmkey.go +++ b/files/enrol/tpmkey/tpmkey.go @@ -2,7 +2,7 @@ // / no openssl tpm2 provider). Provides: ensure a persistent ECDSA signing key // at a non-reserved owner handle (default 0x81000100 — NOT the EK slot // 0x81010001, see EnsureKey), a crypto.Signer backed by it (for CSR signing), -// and NV cert read-presence + write at 0x01800001. Talks to any transport.TPM, +// and NV cert read-presence + write at 0x01800100. Talks to any transport.TPM, // so the same // code runs against /dev/tpmrm0 (production) and the software simulator (tests). // @@ -224,6 +224,14 @@ func ReadNVCert(tpm transport.TPM, index tpm2.TPMHandle) ([]byte, error) { func WriteNVCert(tpm transport.TPM, index tpm2.TPMHandle, der []byte) error { // Undefine a stale index first (re-enrolment / resized cert). if rp, err := (tpm2.NVReadPublic{NVIndex: index}).Execute(tpm); err == nil { + // A PLATFORM-created index at our handle is OEM firmware NV (bench-seen on + // a Dell 820: 0x01800001 was platformcreate|policydelete|writelocked). We + // cannot undefine it with owner auth, and TPM2_Clear/BIOS won't remove it — + // so fail LOUDLY with guidance rather than a confusing "nv undefine" error. + // The remedy is a different, free owner index via rd.dot1x.nvindex. + if pub, e := rp.NVPublic.Contents(); e == nil && pub.Attributes.PlatformCreate { + return fmt.Errorf("NV index %#x is a platform-created (firmware-owned) index — cannot be written by enrolment; pick a free owner index via rd.dot1x.nvindex / -nvindex", uint32(index)) + } if _, err := (tpm2.NVUndefineSpace{ AuthHandle: tpm2.TPMRHOwner, NVIndex: tpm2.NamedHandle{Handle: index, Name: rp.NVName}, diff --git a/scripts/extract-artifacts.sh b/scripts/extract-artifacts.sh index befa0b2..c781f8b 100755 --- a/scripts/extract-artifacts.sh +++ b/scripts/extract-artifacts.sh @@ -223,7 +223,7 @@ printf '%s' \ "$STIG_FRAGMENT" \ "$DOT1X_FRAGMENT" \ "rd.neednet=1 systemd.volatile=overlay " \ - "rd.dot1x.nvindex=0x01800001 rd.dot1x.handle=0x81000100 rd.dot1x.timeout=60 " \ + "rd.dot1x.nvindex=0x01800100 rd.dot1x.handle=0x81000100 rd.dot1x.timeout=60 " \ "rd.fleet.enrol_url=${ENROL_URL} " \ "rd.nvmf.discover=tcp,${NVMET_TRADDR},4420 " \ "root=/dev/mapper/root rootfstype=erofs rootflags=ro " \ diff --git a/serving/dot1x-bench/README.md b/serving/dot1x-bench/README.md index 1db4c37..b28c5c5 100644 --- a/serving/dot1x-bench/README.md +++ b/serving/dot1x-bench/README.md @@ -84,7 +84,7 @@ qm set --tpmstate0 :1,version=v2.0 3. **Enrol each workstation's TPM** (one-shot, on a provisioning network that can reach step-ca) — see `enrol/tpm-provision.sh`: generates the key **in** the TPM at `0x81000100`, builds a CSR with it, has step-ca sign it, writes the cert to - NV `0x01800001`. Each host gets a unique cert → unique hostnqn. + NV `0x01800100`. Each host gets a unique cert → unique hostnqn. 4. **Set up the authenticator** — `authenticator/setup-link.sh` (bridge + macsec plumbing per link) then run `hostapd authenticator/hostapd-wired-.conf`. diff --git a/serving/dot1x-bench/enrol/tpm-provision.sh b/serving/dot1x-bench/enrol/tpm-provision.sh index 29ee814..a5a658b 100755 --- a/serving/dot1x-bench/enrol/tpm-provision.sh +++ b/serving/dot1x-bench/enrol/tpm-provision.sh @@ -7,7 +7,7 @@ # (non-reserved owner handle; NOT the EK slot 0x81010001) # 2. build a CSR using that TPM key (private key never leaves the TPM) # 3. have step-ca sign it (token-gated 'enrol' provisioner) -# 4. write the issued cert (DER) to TPM NV index 0x01800001 +# 4. write the issued cert (DER) to TPM NV index 0x01800100 # At boot the dracut hook reads the cert from NV and wpa_supplicant signs EAP-TLS # with the persistent-handle key. hostnqn is later derived from the cert serial. # @@ -27,7 +27,7 @@ set -euo pipefail HOSTID=${1:?usage: tpm-provision.sh e.g. ws-vm-a} HANDLE=${HANDLE:-0x81000100} -NVINDEX=${NVINDEX:-0x01800001} +NVINDEX=${NVINDEX:-0x01800100} export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI:-device:/dev/tpmrm0}" CA_DNS=${CA_DNS:-ca.fleet.lab}; CA_PORT=${CA_PORT:-8443} CA_URL="https://${CA_DNS}:${CA_PORT}" diff --git a/serving/dot1x-bench/enrol/tpm-selftest.sh b/serving/dot1x-bench/enrol/tpm-selftest.sh index f63d5bb..9463a9b 100755 --- a/serving/dot1x-bench/enrol/tpm-selftest.sh +++ b/serving/dot1x-bench/enrol/tpm-selftest.sh @@ -4,7 +4,7 @@ # # Run as root on a booted workstation (the open-port test image is fine — it # ships the same tooling). Uses SCRATCH handle/NV and cleans them up, so it never -# touches the real 0x81000100 / 0x01800001. Pass --use-real-handles to exercise +# touches the real 0x81000100 / 0x01800100. Pass --use-real-handles to exercise # the actual locations instead (still cleaned up unless --keep). # # It validates, in order: @@ -24,7 +24,7 @@ for a in "$@"; do case "$a" in esac; done export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI:-device:/dev/tpmrm0}" -if [ "$USE_REAL" = 1 ]; then HANDLE=0x81000100; NV=0x01800001 +if [ "$USE_REAL" = 1 ]; then HANDLE=0x81000100; NV=0x01800100 else HANDLE=0x81008000; NV=0x01500000; fi WORK=$(mktemp -d); PASS=0; FAIL=0; SKIP=0