Files
suaveplan/openspec/archive/2026-05-11-enterprise-tpm-struct/tasks.md
2026-05-27 23:29:53 +01:00

11 KiB

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

  • 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)
  • 1.2 Read TCG TPM 2.0 Library Part 1 — Architecture §16 (Names)
  • 1.3 Read W3C WebAuthn Level 3 §8.3 TPM Attestation Statement Format end-to-end
  • 1.4 Read TCG Credential Profile EK 2.0 v2.3 (informational — AIK / EK context)
  • 1.5 Read openspec/AGENTS.md §0, §0a, §0b, §2b, §6a, §7, §13, §14, §14a
  • 1.6 Confirm proposal.md covers every public-surface item, every subpath, and every dependency
  • 1.7 Confirm specs/tpm-struct/spec.md carries one requirement per logical capability with ≥2 scenarios each
  • 1.8 Confirm every requirement has a **Verified by:** line naming concrete it(...) titles in concrete test file paths
  • 1.9 Source-mapping table cites every TCG / W3C section consumed

2. Package scaffold

  • 2.1 Create packages/security/tpm-struct/ with src/{buffer,alg,public,attest,name,extra-data,schemas,errors,observability,diagnostic,__fixtures__,__integration__,__benchmarks__}/
  • 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)
  • 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
  • 2.4 tsconfig.lib.json extends @suaveplan/typescript-config/library.json
  • 2.5 vite.config.ts multi-entry build (one entry per subpath) + vitest.config.ts calling createTestConfig() from @suaveplan/testing/config
  • 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

  • 3.1 src/buffer/buffer.ts — TpmBufferReader (readUint8, readUint16Be, readUint32Be, readUint64Be: bigint, readBytes(n), readTpm2b(): Uint8Array, peek(n), remaining(), assertEnd())
  • 3.2 src/buffer/buffer.ts — TpmBufferWriter (mirror writes + writeTpm2b(bytes) + toUint8Array())
  • 3.3 Out-of-bounds read raises TPM_STRUCT.TRUNCATED with cursor + requested-length in cause
  • 3.4 src/buffer/buffer.types.ts — TpmReaderOptions, TpmWriterOptions, Tpm2bReadResult
  • 3.5 Sibling src/buffer/buffer.md ≥200 words with basic + advanced examples

4. Implementation — algorithm registry

  • 4.1 src/alg/alg.ts — TPM_ALG frozen 31-entry registry per TCG Part 2 §11.1
  • 4.2 src/alg/alg.ts — TPM_ECC_CURVE frozen 6-entry registry per TCG Part 2 §11.2.5
  • 4.3 algIdToName(id) returns symbolic name; unknown raises ALG_UNSUPPORTED
  • 4.4 eccCurveToName(id) returns symbolic name; unknown raises CURVE_UNSUPPORTED
  • 4.5 src/alg/alg.types.ts — TpmAlgId, TpmEccCurve literal unions
  • 4.6 Sibling src/alg/alg.md ≥200 words

5. Implementation — TPMT_PUBLIC parser + serialiser

  • 5.1 src/public/public.ts — parseTpmtPublic(bytes): TpmtPublic per TCG Part 2 §12.2 header (type, nameAlg, objectAttributes, authPolicy)
  • 5.2 RSA branch: TPMS_RSA_PARMS per §12.2.3.5 + TPM2B_PUBLIC_KEY_RSA unique
  • 5.3 ECC branch: TPMS_ECC_PARMS per §12.2.3.6 + TPMS_ECC_POINT unique with curveID lookup
  • 5.4 KEYEDHASH branch: TPMS_KEYEDHASH_PARMS per §12.2.3.3 + TPM2B_DIGEST unique
  • 5.5 SYMCIPHER branch: TPMS_SYMCIPHER_PARMS per §12.2.3.4 + TPM2B_DIGEST unique
  • 5.6 objectAttributes reserved bits non-zero raises OBJECT_ATTRIBUTES_RESERVED_NONZERO
  • 5.7 src/public/serialize.ts — serializeTpmtPublic(pub): Uint8Array byte-identical round-trip
  • 5.8 src/public/public.types.ts — TpmtPublic discriminator + parameter / point / attribute types
  • 5.9 Sibling .md for public.ts and serialize.ts (≥200 words each)

6. Implementation — TPMS_ATTEST parser + serialiser

  • 6.1 src/attest/attest.ts — magic validation: 0xff544347 mismatch raises MAGIC_INVALID
  • 6.2 parseTpmsAttest reads type, qualifiedSigner (TPM2B_NAME), extraData (TPM2B_DATA), clockInfo (17 bytes), firmwareVersion (uint64 BE)
  • 6.3 Eight TPMU_ATTEST branches: TPM_ST_ATTEST_CERTIFY ⇒ TPMS_CERTIFY_INFO; the other seven per TCG Part 2 §10.11
  • 6.4 Unknown type raises ATTEST_TYPE_UNSUPPORTED
  • 6.5 src/attest/serialize.ts — serializeTpmsAttest(att): Uint8Array byte-identical round-trip
  • 6.6 src/attest/attest.types.ts — TpmsAttest discriminator + every TPMU_ATTEST member type + ClockInfo
  • 6.7 Sibling .md for attest.ts and serialize.ts (≥200 words each)

7. Implementation — name digest

  • 7.1 src/name/name.ts — computeTpmtPublicName(pub, hash): Uint8Array per TCG Part 1 §16
  • 7.2 Output = nameAlg (2 BE) || hash.digest(serializeTpmtPublic(pub))
  • 7.3 nameAlg === TPM_ALG_NULL raises NAME_ALG_NULL
  • 7.4 src/name/name.types.ts — HashLike interface
  • 7.5 Sibling src/name/name.md ≥200 words

8. Implementation — extra-data helper

  • 8.1 src/extra-data/extra-data.ts — extraDataMatches(actual, expected, equal): boolean
  • 8.2 Length mismatch returns false without calling equal
  • 8.3 src/extra-data/extra-data.types.ts — ConstantTimeEqual signature
  • 8.4 Sibling .md ≥200 words

9. Implementation — Zod schemas

  • 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)
  • 9.2 Every public parser validates its output through the matching schema before returning
  • 9.3 Schema failure raises MALFORMED with Zod issues on cause.issues
  • 9.4 Sibling .md ≥200 words

10. Implementation — error registry

  • 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
  • 10.2 TpmStructError extends SuaveplanError<TpmStructErrorCode>
  • 10.3 Every code thrown by ≥1 production path (registry-coverage test pins this)
  • 10.4 Sibling .md ≥200 words

11. Implementation — OTel surface

  • 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}
  • 11.2 Counters tpm_struct.parse_total{kind,outcome}, tpm_struct.parse_errors_total{code}, tpm_struct.alg_total{alg}
  • 11.3 Every attribute is a bounded enum from the registries (no free-form strings)
  • 11.4 Hot paths emit under a real in-memory MeterProvider (no mocks)
  • 11.5 Sibling .md ≥200 words

12. Implementation — diagnostic emitter

  • 12.1 src/diagnostic/diagnostic.ts — toDiagnostic(value: TpmsAttest | TpmtPublic): string
  • 12.2 Symbolic TPM_ALG and TPM_ECC_CURVE names inlined
  • 12.3 Never invokes parser hot paths; never emits telemetry
  • 12.4 Sibling .md ≥200 words

13. Fixtures + integration test

  • 13.1 src/__fixtures__/webauthn-tpm-vector.ts — real-world WebAuthn tpm attestation byte strings + expected parsed structures + expected name digest
  • 13.2 src/__integration__/webauthn-vector.test.ts — parseTpmsAttest(attestationData) + parseTpmtPublic(pubArea) + computeTpmtPublicName round-trip matches expected digest
  • 13.3 Test uses a real SHA-256 implementation injected via HashLike (no mocks)

14. Tests

  • 14.1 Unit tests at each src/<feature>/<feature>.test.ts covering every scenario in the spec delta
  • 14.2 Property tests at src/public/property.test.ts and src/attest/property.test.ts (fast-check round-trip + single-bit-flip)
  • 14.3 Spec-conformance test at src/index.test.ts resolves every subpath export
  • 14.4 Every it(...) carries { timeout: 5000 } (or 30000 for genuinely async)
  • 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

  • 15.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference, Implementation Notes)
  • 15.2 README links every co-located .md under ## Modules
  • 15.3 Update CLAUDE.md package listing under §Security / supply chain to add tpm-struct

16. Benchmarks

  • 16.1 src/__benchmarks__/parse-tpms-attest.bench.ts — ~1 KiB attest buffer
  • 16.2 src/__benchmarks__/parse-tpmt-public-rsa2048.bench.ts — RSA-2048 public area
  • 16.3 src/__benchmarks__/parse-tpmt-public-ecc-p256.bench.ts — ECC P-256 public area
  • 16.4 src/__benchmarks__/compute-name.bench.ts — name digest with injected SHA-256
  • 16.5 Baseline numbers published in README

17. Pre-archive verification (the 5-gate triple)

  • 17.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
  • 17.2 bunx turbo lint typecheck test --filter=@suaveplan/tpm-struct — green
  • 17.3 bunx turbo test:coverage --filter=@suaveplan/tpm-struct — 100/100/100/100
  • 17.4 bun scripts/openspec-conformance.ts @suaveplan/tpm-struct — exit 0, no error findings
  • 17.5 bunx turbo build --filter=@suaveplan/tpm-struct — green

18. Pre-archive 7-row checklist (AGENTS.md §13)

  • 18.1 Generics threaded; expectTypeOf assertions on the literal unions (TpmAlgId, TpmEccCurve, TpmStructErrorCode, every discriminator)
  • 18.2 Zod schemas wired at every declared boundary
  • 18.3 Every error code in error-codes.ts thrown by at least one production path
  • 18.4 objectAttributes reserved-bits-strict mode tested both on (raise) and off (default raise; documented invariant — no off-mode)
  • 18.5 OTel instruments emitted from production paths and asserted with a real in-memory MeterProvider
  • 18.6 No I/O imports — grep -rnE "from ['\"](node:|tpm-js|node-tss|tss.js)" packages/security/tpm-struct/src/ returns zero
  • 18.7 No retroactive tick — every box above reflects work done in this change

19. Archive

  • 19.1 Fold spec delta into openspec/specs/tpm-struct/spec.md
  • 19.2 Move openspec/changes/enterprise-tpm-struct/ → openspec/archive/2026-MM-DD-enterprise-tpm-struct/
  • 19.3 Refresh MANIFEST.md via bun scripts/folder-reorg/generate-manifest.ts
  • 19.4 Refresh MISSING.yml via bun scripts/generate-missing.ts
  • 19.5 Update STATUS.md to move @suaveplan/tpm-struct to Completed
  • 19.6 Update SUMMARY.md with the per-package paragraph
  • 19.7 bunx markdownlint-cli2 --fix on touched files; re-stage; verify clean