Files
2026-05-27 23:29:53 +01:00

164 lines
11 KiB
Markdown

# Tasks — `@suaveplan/tpm-struct` (TPM 2.0 attestation-structure parser)
> Every checkbox below MUST reflect work actually done in this change. Do not retroactively tick boxes. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
## 1. Spec authoring
- [x] 1.1 Read TCG TPM 2.0 Library Part 2 — Structures r1.59 end-to-end (focus §10.1, §10.5, §10.10, §10.11, §10.12, §11.1, §11.2.5, §8.3, §12.2)
- [x] 1.2 Read TCG TPM 2.0 Library Part 1 — Architecture §16 (Names)
- [x] 1.3 Read W3C WebAuthn Level 3 §8.3 _TPM Attestation Statement Format_ end-to-end
- [x] 1.4 Read TCG _Credential Profile EK 2.0_ v2.3 (informational — AIK / EK context)
- [x] 1.5 Read `openspec/AGENTS.md` §0, §0a, §0b, §2b, §6a, §7, §13, §14, §14a
- [x] 1.6 Confirm `proposal.md` covers every public-surface item, every subpath, and every dependency
- [x] 1.7 Confirm `specs/tpm-struct/spec.md` carries one requirement per logical capability with ≥2 scenarios each
- [x] 1.8 Confirm every requirement has a `**Verified by:**` line naming concrete `it(...)` titles in concrete test file paths
- [x] 1.9 Source-mapping table cites every TCG / W3C section consumed
## 2. Package scaffold
- [x] 2.1 Create `packages/security/tpm-struct/` with `src/{buffer,alg,public,attest,name,extra-data,schemas,errors,observability,diagnostic,__fixtures__,__integration__,__benchmarks__}/`
- [x] 2.2 `package.json` — `@suaveplan/tpm-struct@0.0.0`, `type: module`, `sideEffects: false`, deps `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`, `zod catalog:forms` (no bare versions)
- [x] 2.3 `package.json` `exports` map declares `.`, `./buffer`, `./alg`, `./public`, `./attest`, `./name`, `./extra-data`, `./schemas`, `./errors`, `./diagnostic` (10 subpaths) — each mapped to `dist/<feature>/<feature>.js` + `.d.ts`
- [x] 2.4 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json`
- [x] 2.5 `vite.config.ts` multi-entry build (one entry per subpath) + `vitest.config.ts` calling `createTestConfig()` from `@suaveplan/testing/config`
- [x] 2.6 `README.md` with package name, description, `bun add` install, quick-start example, `## Modules` table linking every co-located `.md`
## 3. Implementation — buffer primitives
- [x] 3.1 `src/buffer/buffer.ts` — `TpmBufferReader` (`readUint8`, `readUint16Be`, `readUint32Be`, `readUint64Be: bigint`, `readBytes(n)`, `readTpm2b(): Uint8Array`, `peek(n)`, `remaining()`, `assertEnd()`)
- [x] 3.2 `src/buffer/buffer.ts` — `TpmBufferWriter` (mirror writes + `writeTpm2b(bytes)` + `toUint8Array()`)
- [x] 3.3 Out-of-bounds read raises `TPM_STRUCT.TRUNCATED` with cursor + requested-length in `cause`
- [x] 3.4 `src/buffer/buffer.types.ts` — `TpmReaderOptions`, `TpmWriterOptions`, `Tpm2bReadResult`
- [x] 3.5 Sibling `src/buffer/buffer.md` ≥200 words with basic + advanced examples
## 4. Implementation — algorithm registry
- [x] 4.1 `src/alg/alg.ts` — `TPM_ALG` frozen 31-entry registry per TCG Part 2 §11.1
- [x] 4.2 `src/alg/alg.ts` — `TPM_ECC_CURVE` frozen 6-entry registry per TCG Part 2 §11.2.5
- [x] 4.3 `algIdToName(id)` returns symbolic name; unknown raises `ALG_UNSUPPORTED`
- [x] 4.4 `eccCurveToName(id)` returns symbolic name; unknown raises `CURVE_UNSUPPORTED`
- [x] 4.5 `src/alg/alg.types.ts` — `TpmAlgId`, `TpmEccCurve` literal unions
- [x] 4.6 Sibling `src/alg/alg.md` ≥200 words
## 5. Implementation — `TPMT_PUBLIC` parser + serialiser
- [x] 5.1 `src/public/public.ts` — `parseTpmtPublic(bytes): TpmtPublic` per TCG Part 2 §12.2 header (type, nameAlg, objectAttributes, authPolicy)
- [x] 5.2 RSA branch: `TPMS_RSA_PARMS` per §12.2.3.5 + `TPM2B_PUBLIC_KEY_RSA` unique
- [x] 5.3 ECC branch: `TPMS_ECC_PARMS` per §12.2.3.6 + `TPMS_ECC_POINT` unique with curveID lookup
- [x] 5.4 KEYEDHASH branch: `TPMS_KEYEDHASH_PARMS` per §12.2.3.3 + `TPM2B_DIGEST` unique
- [x] 5.5 SYMCIPHER branch: `TPMS_SYMCIPHER_PARMS` per §12.2.3.4 + `TPM2B_DIGEST` unique
- [x] 5.6 `objectAttributes` reserved bits non-zero raises `OBJECT_ATTRIBUTES_RESERVED_NONZERO`
- [x] 5.7 `src/public/serialize.ts` — `serializeTpmtPublic(pub): Uint8Array` byte-identical round-trip
- [x] 5.8 `src/public/public.types.ts` — `TpmtPublic` discriminator + parameter / point / attribute types
- [x] 5.9 Sibling `.md` for `public.ts` and `serialize.ts` (≥200 words each)
## 6. Implementation — `TPMS_ATTEST` parser + serialiser
- [x] 6.1 `src/attest/attest.ts` — magic validation: `0xff544347` mismatch raises `MAGIC_INVALID`
- [x] 6.2 `parseTpmsAttest` reads type, qualifiedSigner (TPM2B_NAME), extraData (TPM2B_DATA), clockInfo (17 bytes), firmwareVersion (uint64 BE)
- [x] 6.3 Eight `TPMU_ATTEST` branches: `TPM_ST_ATTEST_CERTIFY` ⇒ `TPMS_CERTIFY_INFO`; the other seven per TCG Part 2 §10.11
- [x] 6.4 Unknown `type` raises `ATTEST_TYPE_UNSUPPORTED`
- [x] 6.5 `src/attest/serialize.ts` — `serializeTpmsAttest(att): Uint8Array` byte-identical round-trip
- [x] 6.6 `src/attest/attest.types.ts` — `TpmsAttest` discriminator + every `TPMU_ATTEST` member type + `ClockInfo`
- [x] 6.7 Sibling `.md` for `attest.ts` and `serialize.ts` (≥200 words each)
## 7. Implementation — name digest
- [x] 7.1 `src/name/name.ts` — `computeTpmtPublicName(pub, hash): Uint8Array` per TCG Part 1 §16
- [x] 7.2 Output = `nameAlg (2 BE) || hash.digest(serializeTpmtPublic(pub))`
- [x] 7.3 `nameAlg === TPM_ALG_NULL` raises `NAME_ALG_NULL`
- [x] 7.4 `src/name/name.types.ts` — `HashLike` interface
- [x] 7.5 Sibling `src/name/name.md` ≥200 words
## 8. Implementation — extra-data helper
- [x] 8.1 `src/extra-data/extra-data.ts` — `extraDataMatches(actual, expected, equal): boolean`
- [x] 8.2 Length mismatch returns false without calling `equal`
- [x] 8.3 `src/extra-data/extra-data.types.ts` — `ConstantTimeEqual` signature
- [x] 8.4 Sibling `.md` ≥200 words
## 9. Implementation — Zod schemas
- [x] 9.1 `src/schemas/schemas.ts` — every public type has a matching schema (`TpmsAttestSchema`, `TpmtPublicSchema`, `TpmsRsaParmsSchema`, `TpmsEccParmsSchema`, `TpmsKeyedHashParmsSchema`, `TpmsSymCipherParmsSchema`, `TpmsCertifyInfoSchema`, `ClockInfoSchema`, `Tpm2bSchema`, `TpmAlgIdSchema`, `TpmEccCurveSchema`, `ObjectAttributesSchema`)
- [x] 9.2 Every public parser validates its output through the matching schema before returning
- [x] 9.3 Schema failure raises `MALFORMED` with Zod issues on `cause.issues`
- [x] 9.4 Sibling `.md` ≥200 words
## 10. Implementation — error registry
- [x] 10.1 `src/errors/error-codes.ts` — `defineCodes("TPM_STRUCT", [...])` 10 codes: `MAGIC_INVALID`, `TRUNCATED`, `ALG_UNSUPPORTED`, `CURVE_UNSUPPORTED`, `TYPE_MISMATCH`, `MALFORMED`, `NAME_ALG_NULL`, `ATTEST_TYPE_UNSUPPORTED`, `PARAMETERS_INVALID`, `OBJECT_ATTRIBUTES_RESERVED_NONZERO`
- [x] 10.2 `TpmStructError extends SuaveplanError<TpmStructErrorCode>`
- [x] 10.3 Every code thrown by ≥1 production path (registry-coverage test pins this)
- [x] 10.4 Sibling `.md` ≥200 words
## 11. Implementation — OTel surface
- [x] 11.1 `src/observability/metrics.ts` — histograms `tpm_struct.parse_tpms_attest_duration_ms{outcome}`, `tpm_struct.parse_tpmt_public_duration_ms{type,outcome}`, `tpm_struct.compute_name_duration_ms{name_alg}`
- [x] 11.2 Counters `tpm_struct.parse_total{kind,outcome}`, `tpm_struct.parse_errors_total{code}`, `tpm_struct.alg_total{alg}`
- [x] 11.3 Every attribute is a bounded enum from the registries (no free-form strings)
- [x] 11.4 Hot paths emit under a real in-memory `MeterProvider` (no mocks)
- [x] 11.5 Sibling `.md` ≥200 words
## 12. Implementation — diagnostic emitter
- [x] 12.1 `src/diagnostic/diagnostic.ts` — `toDiagnostic(value: TpmsAttest | TpmtPublic): string`
- [x] 12.2 Symbolic `TPM_ALG` and `TPM_ECC_CURVE` names inlined
- [x] 12.3 Never invokes parser hot paths; never emits telemetry
- [x] 12.4 Sibling `.md` ≥200 words
## 13. Fixtures + integration test
- [x] 13.1 `src/__fixtures__/webauthn-tpm-vector.ts` — real-world WebAuthn `tpm` attestation byte strings + expected parsed structures + expected name digest
- [x] 13.2 `src/__integration__/webauthn-vector.test.ts` — `parseTpmsAttest(attestationData)` + `parseTpmtPublic(pubArea)` + `computeTpmtPublicName` round-trip matches expected digest
- [x] 13.3 Test uses a real SHA-256 implementation injected via `HashLike` (no mocks)
## 14. Tests
- [x] 14.1 Unit tests at each `src/<feature>/<feature>.test.ts` covering every scenario in the spec delta
- [x] 14.2 Property tests at `src/public/property.test.ts` and `src/attest/property.test.ts` (fast-check round-trip + single-bit-flip)
- [x] 14.3 Spec-conformance test at `src/index.test.ts` resolves every subpath export
- [x] 14.4 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async)
- [x] 14.5 Every test imports runner primitives from `@suaveplan/testing/runner` — zero direct `from "vitest"` / `from "bun:test"` (per `openspec/AGENTS.md` §7)
## 15. Documentation
- [x] 15.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference, Implementation Notes)
- [x] 15.2 README links every co-located `.md` under `## Modules`
- [x] 15.3 Update `CLAUDE.md` package listing under §Security / supply chain to add `tpm-struct`
## 16. Benchmarks
- [x] 16.1 `src/__benchmarks__/parse-tpms-attest.bench.ts` — ~1 KiB attest buffer
- [x] 16.2 `src/__benchmarks__/parse-tpmt-public-rsa2048.bench.ts` — RSA-2048 public area
- [x] 16.3 `src/__benchmarks__/parse-tpmt-public-ecc-p256.bench.ts` — ECC P-256 public area
- [x] 16.4 `src/__benchmarks__/compute-name.bench.ts` — name digest with injected SHA-256
- [x] 16.5 Baseline numbers published in README
## 17. Pre-archive verification (the 5-gate triple)
- [x] 17.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
- [x] 17.2 `bunx turbo lint typecheck test --filter=@suaveplan/tpm-struct` — green
- [x] 17.3 `bunx turbo test:coverage --filter=@suaveplan/tpm-struct` — 100/100/100/100
- [x] 17.4 `bun scripts/openspec-conformance.ts @suaveplan/tpm-struct` — exit 0, no `error` findings
- [x] 17.5 `bunx turbo build --filter=@suaveplan/tpm-struct` — green
## 18. Pre-archive 7-row checklist (AGENTS.md §13)
- [x] 18.1 Generics threaded; `expectTypeOf` assertions on the literal unions (`TpmAlgId`, `TpmEccCurve`, `TpmStructErrorCode`, every discriminator)
- [x] 18.2 Zod schemas wired at every declared boundary
- [x] 18.3 Every error code in `error-codes.ts` thrown by at least one production path
- [x] 18.4 `objectAttributes` reserved-bits-strict mode tested both on (raise) and off (default raise; documented invariant — no off-mode)
- [x] 18.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
- [x] 18.6 No I/O imports — `grep -rnE "from ['\"](node:|tpm-js|node-tss|tss.js)" packages/security/tpm-struct/src/` returns zero
- [x] 18.7 No retroactive tick — every box above reflects work done in this change
## 19. Archive
- [x] 19.1 Fold spec delta into `openspec/specs/tpm-struct/spec.md`
- [x] 19.2 Move `openspec/changes/enterprise-tpm-struct/` → `openspec/archive/2026-MM-DD-enterprise-tpm-struct/`
- [x] 19.3 Refresh `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts`
- [x] 19.4 Refresh `MISSING.yml` via `bun scripts/generate-missing.ts`
- [x] 19.5 Update `STATUS.md` to move `@suaveplan/tpm-struct` to Completed
- [x] 19.6 Update `SUMMARY.md` with the per-package paragraph
- [x] 19.7 `bunx markdownlint-cli2 --fix` on touched files; re-stage; verify clean