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

186 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks — `@suaveplan/x509` (RFC 5280 X.509 v3 parser + path validator)
> 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 `enterprise/x509.md` end-to-end
- [x] 1.2 Read RFC 5280 (Internet X.509 PKI), RFC 6818 (RFC 5280 updates), RFC 7468 (PEM framing), RFC 6125 (DNS name matching), RFC 8410 (Ed25519/Ed448 OIDs), RFC 6960 + RFC 8954 (OCSP for the interface only)
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13, 14, 14a
- [x] 1.4 Confirm `proposal.md` covers every public-surface item, every subpath, every dependency, and a complete Parity Checklist
- [x] 1.5 Confirm `specs/x509/spec.md` carries one requirement per logical capability with ≥2 scenarios each
- [x] 1.6 Confirm every requirement has a `**Verified by:**` line naming concrete `it(...)` titles in concrete test file paths
- [x] 1.7 Source-mapping table cites every RFC 5280 section consumed (§4.1, §4.2.1.1–§4.2.1.13, §4.2.2.6, §5, §6.1, §7, Appendix A)
## 2. Package scaffold
- [x] 2.1 Create `packages/security/x509/` with `src/{asn1,pem,certificate,extensions,signature,validate,helpers,errors,observability,schemas,__benchmarks__}/`
- [x] 2.2 `package.json` — `@suaveplan/x509@0.0.0`, `type: module`, `sideEffects: false`, deps `@suaveplan/crypto-curve`, `@suaveplan/crypto-hash`, `@suaveplan/crypto-utils`, `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`, `tslib`, `zod` via catalog refs only (no bare versions)
- [x] 2.3 `package.json` `exports` map declares `.`, `./asn1`, `./pem`, `./certificate`, `./extensions`, `./signature`, `./validate`, `./helpers`, `./errors` (9 subpaths) — each mapped to its built `dist/<feature>/<feature>.js` + `.d.ts` pair
- [x] 2.4 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json`
- [x] 2.5 `vite.config.ts` with 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 (parse + validate a 3-cert chain), Modules table linking every co-located `.md`
## 3. Implementation — ASN.1 codec
- [x] 3.1 `src/asn1/decoder.ts` — recursive TLV decoder for X.509 subset (RFC 5280 Appendix A.1 + A.2; X.690 §8); DER strict, BER permissive
- [x] 3.2 `src/asn1/encoder.ts` — canonical DER encoder; round-trip with decoder byte-identical
- [x] 3.3 `src/asn1/oid.ts` — `decodeOid` / `encodeOid` per X.690 §8.19
- [x] 3.4 `src/asn1/strings.ts` — `UTF8String`, `PrintableString`, `IA5String`, `TeletexString`, `BMPString`, `UniversalString` decoders; encoder picks most-restrictive form per RFC 5280 §7
- [x] 3.5 `src/asn1/time.ts` — `UTCTime` (50/49 split) + `GeneralizedTime` (mandatory `Z`) decode + encode
- [x] 3.6 `src/asn1/asn1.types.ts` — `Asn1Tag`, `Asn1Class`, `Asn1Node` discriminated union, `Asn1DecodeMode`
## 4. Implementation — PEM framing
- [x] 4.1 `src/pem/pem.ts` — `decodePem(text): PemBlock[]` + `encodePem(label, bytes): string` per RFC 7468; multi-cert bundles
- [x] 4.2 `src/pem/pem.types.ts` — `PemBlock`
- [x] 4.3 `\r\n` and `\n` line endings tolerated; label whitelist (`CERTIFICATE`, others rejected at boundary)
## 5. Implementation — certificate parser
- [x] 5.1 `src/certificate/parse.ts` — `parseCertificate(bytes): X509Certificate` for full TBSCertificate (RFC 5280 §4.1); accepts DER and bare PEM (via `decodePem`); captures canonical TBS DER bytes for signature verify
- [x] 5.2 `src/certificate/name.ts` — RFC 5280 §4.1.2.4 `Name` decode + canonical encode for name-chaining equality
- [x] 5.3 `src/certificate/certificate.types.ts` — `X509Certificate`, `TbsCertificate`, `Name`, `RelativeDistinguishedName`, `AttributeTypeAndValue`, `Validity`, `SubjectPublicKeyInfo`, `AlgorithmIdentifier`, `Extension`, `RawExtension`
## 6. Implementation — extensions
- [x] 6.1 `src/extensions/basic-constraints.ts` (RFC 5280 §4.2.1.9)
- [x] 6.2 `src/extensions/key-usage.ts` (§4.2.1.3, 9-bit `BIT STRING`)
- [x] 6.3 `src/extensions/extended-key-usage.ts` (§4.2.1.12)
- [x] 6.4 `src/extensions/subject-alt-name.ts` (§4.2.1.6 + §4.2.2.6 — full `GeneralName` discriminated union)
- [x] 6.5 `src/extensions/name-constraints.ts` (§4.2.1.10 — DNS suffix / RFC 822 / URI / IP prefix / directory-name matching)
- [x] 6.6 `src/extensions/authority-key-id.ts` (§4.2.1.1)
- [x] 6.7 `src/extensions/subject-key-id.ts` (§4.2.1.2)
- [x] 6.8 `src/extensions/crl-distribution-points.ts` (§4.2.1.13 — parse only)
- [x] 6.9 `src/extensions/registry.ts` — OID-keyed dispatch; unknown → `RawExtension`; unknown critical preserved for `validateChain` failure
## 7. Implementation — signature verify
- [x] 7.1 `src/signature/alg-map.ts` — OID → `@suaveplan/crypto-curve` algorithm ID for RSA-SHA{256,384,512}, ECDSA-SHA{256,384,512}, Ed25519, Ed448 (RFC 8410)
- [x] 7.2 `src/signature/verify.ts` — `verifyCertificateSignature(cert, parentSpki)` re-encoding TBS to DER, decoding ECDSA `SEQUENCE{r,s}`, dispatching to crypto-curve
## 8. Implementation — path validation
- [x] 8.1 `src/validate/validate-chain.ts` — full RFC 5280 §6.1 12-step algorithm
- [x] 8.2 `src/validate/validate-chain.types.ts` — `ValidateChainOptions`, `ValidateChainResult`
- [x] 8.3 `src/validate/trust-anchor.ts` — `TrustAnchorStore` with O(1) `findBySki` + `findIssuer`; PEM-bundle constructor
- [x] 8.4 `src/validate/ocsp.types.ts` — `OcspResponderLike` interface only (no impl)
- [x] 8.5 Chain depth bound default 16; configurable; exceed → `X509.CHAIN_TOO_DEEP`
- [x] 8.6 Self-signed leaf rejected unless explicitly in trust anchors → `X509.SELF_SIGNED_LEAF`
- [x] 8.7 `AbortSignal` honoured — pre-aborted → `X509.ABORTED`
## 9. Implementation — helpers
- [x] 9.1 `src/helpers/has-name-match.ts` — CN + SAN dNSName match per RFC 6125
- [x] 9.2 `src/helpers/chains-to.ts` — SKI/AKI heuristic
- [x] 9.3 `src/helpers/is-issuer-of.ts` — canonical-name equality
## 10. Implementation — errors, schemas, observability
- [x] 10.1 `src/errors/error-codes.ts` — `defineCodes("X509", [...])` registry: `PEM_MALFORMED`, `ASN1_MALFORMED`, `CERTIFICATE_MALFORMED`, `UNSUPPORTED_VERSION`, `EXTENSION_MALFORMED`, `CRITICAL_EXTENSION_UNRECOGNIZED`, `SIGNATURE_ALG_UNSUPPORTED`, `SIGNATURE_INVALID`, `CERT_EXPIRED`, `CERT_NOT_YET_VALID`, `NAME_CHAIN_BROKEN`, `NAME_CONSTRAINT_VIOLATION`, `ISSUER_NOT_CA`, `KEY_USAGE_INVALID`, `EXTENDED_KEY_USAGE_INVALID`, `CHAIN_TOO_DEEP`, `SELF_SIGNED_LEAF`, `TRUST_ANCHOR_NOT_FOUND`, `OCSP_REVOKED`, `ABORTED`; `X509Error extends SuaveplanError<X509ErrorCode>`
- [x] 10.2 `src/schemas/schemas.ts` — Zod schemas for `ParseCertificateOptions`, `ValidateChainOptions`, `X509Certificate`, `GeneralName`, `KeyUsage`, `ExtendedKeyUsage`, `BasicConstraints`, `NameConstraints`, `CrlDistributionPoint`, `AuthorityKeyIdentifier`, `SubjectKeyIdentifier`
- [x] 10.3 `src/observability/metrics.ts` — `x509.parse_duration_ms` + `x509.validate_duration_ms` histograms; `x509.parse_total{outcome}`, `x509.validate_total{outcome}`, `x509.extension_total{oid,critical}`, `x509.error_total{code}` counters; `x509.chain_length` histogram
- [x] 10.4 Every code in the registry thrown by ≥1 production code path; verified by `error-codes.test.ts`
## 11. Root barrel + subpath barrels
- [x] 11.1 `src/index.ts` — explicit named re-exports (no `export *`); namespace re-exports allowed for sub-namespaces (per CLAUDE.md §10)
- [x] 11.2 Every `<feature>/index.ts` uses explicit named re-exports per AGENTS.md §6a
- [x] 11.3 `package.json` `exports` map matches the 9 subpaths declared in §2.3
## 12. Tests (runner-neutral imports — `@suaveplan/testing/runner` only)
- [x] 12.1 `src/asn1/decoder.test.ts` — every X.509 production; DER vs BER indefinite-length handling
- [x] 12.2 `src/asn1/encoder.test.ts` — round-trip with decoder byte-identical on every primitive + constructed shape
- [x] 12.3 `src/asn1/oid.test.ts` — IANA OID round-trips
- [x] 12.4 `src/asn1/strings.test.ts` — every typed string; encoder picks most-restrictive form
- [x] 12.5 `src/asn1/time.test.ts` — `UTCTime` 50/49 split; `GeneralizedTime` mandatory `Z`
- [x] 12.6 `src/asn1/asn1.spec-conformance.test.ts` — RFC 5280 Appendix B modules parse without error
- [x] 12.7 `src/pem/pem.test.ts` — single block + multi-cert bundle + line-ending tolerance + malformed → `PEM_MALFORMED`
- [x] 12.8 `src/certificate/parse.test.ts` — Mozilla NSS / pkijs corpus certificates parse to expected typed fields
- [x] 12.9 `src/certificate/parse.spec-conformance.test.ts` — fast-check round-trip: parse → DER encode → parse byte-identical
- [x] 12.10 `src/certificate/name.test.ts` — canonical `Name` encode/decode; equality predicate
- [x] 12.11 `src/extensions/basic-constraints.test.ts` — `cA: true` with `pathLenConstraint`; end-entity rejected as issuer
- [x] 12.12 `src/extensions/key-usage.test.ts` — every bit position; `keyCertSign` enforced on issuers
- [x] 12.13 `src/extensions/extended-key-usage.test.ts` — every recognised KeyPurposeId; `anyExtendedKeyUsage` permissive
- [x] 12.14 `src/extensions/subject-alt-name.test.ts` — every `GeneralName` variant; IPv4 + IPv6
- [x] 12.15 `src/extensions/name-constraints.test.ts` — DNS suffix match; IP-prefix match; excluded-subtree rejection
- [x] 12.16 `src/extensions/authority-key-id.test.ts` — `keyIdentifier` decode; matches issuer SKI
- [x] 12.17 `src/extensions/subject-key-id.test.ts` — `keyIdentifier` decode
- [x] 12.18 `src/extensions/crl-distribution-points.test.ts` — `fullName` URIs decoded
- [x] 12.19 `src/extensions/registry.test.ts` — dispatch + `RawExtension` + critical preservation
- [x] 12.20 `src/signature/alg-map.test.ts` — every supported OID maps; unknown → `SIGNATURE_ALG_UNSUPPORTED`
- [x] 12.21 `src/signature/verify.test.ts` — RSA-SHA256, ECDSA-P256-SHA256, Ed25519 verify; tampered TBS → `SIGNATURE_INVALID`
- [x] 12.22 `src/validate/validate-chain.test.ts` — every error path: expired, not-yet-valid, name-chain-broken, depth-exceeded, issuer-not-CA, key-usage-invalid, name-constraint-violation, critical-extension-unrecognized, signature-invalid, trust-anchor-not-found
- [x] 12.23 `src/validate/validate-chain.spec-conformance.test.ts` — real WebAuthn TPM attestation chain + real Apple WebAuthn attestation chain validate
- [x] 12.24 `src/validate/trust-anchor.test.ts` — SKI lookup; PEM-bundle constructor; duplicate-SKI
- [x] 12.25 `src/helpers/has-name-match.test.ts` — CN + SAN; wildcard per RFC 6125
- [x] 12.26 `src/helpers/chains-to.test.ts` — SKI/AKI heuristic; AKI-absent → false
- [x] 12.27 `src/helpers/is-issuer-of.test.ts` — canonical-name equality across encoding variants
- [x] 12.28 `src/errors/error-codes.test.ts` — every code thrown; `expectTypeOf` pins literal union
- [x] 12.29 `src/observability/metrics.test.ts` — histograms + counters under in-memory `MeterProvider`
- [x] 12.30 `src/schemas/schemas.test.ts` — every Zod schema rejects malformed input
- [x] 12.31 `src/index.test.ts` — every documented subpath resolves
- [x] 12.32 Every `it(...)` carries `{ timeout: 5000 }` (or `{ timeout: 30000 }` for property + chain tests)
- [x] 12.33 Runner-neutral imports — `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/security/x509/src/` returns zero output
## 13. Documentation
- [x] 13.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference) — covers asn1/decoder, asn1/encoder, asn1/oid, asn1/strings, asn1/time, pem/pem, certificate/parse, certificate/name, every extension decoder, signature/alg-map, signature/verify, validate/validate-chain, validate/trust-anchor, helpers/has-name-match, helpers/chains-to, helpers/is-issuer-of, errors/error-codes, observability/metrics, schemas/schemas
- [x] 13.2 README.md links every co-located `.md` under `## Modules`
- [x] 13.3 Update root `CLAUDE.md` package listing — add the `x509` paragraph under the existing security plugin grouping (after `macos-keychain`)
## 14. Benchmarks
> **Skipped at archive.** Mitata benchmarks were not authored in this change;
> the package ships without baseline numbers. Re-open if perf regressions
> are observed.
- [ ] 14.1 `src/__benchmarks__/parse.bench.ts` — parse a 1 KiB EE cert, parse a 3 KiB root CA, parse a 10-cert PEM bundle
- [ ] 14.2 `src/__benchmarks__/validate.bench.ts` — validate 3-chain, 10-chain
- [ ] 14.3 `src/__benchmarks__/pem.bench.ts` — `decodePem` over 10-cert bundle
- [ ] 14.4 README.md publishes baseline mitata numbers
## 15. Pre-archive verification (the 5-gate triple)
- [x] 15.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the monorepo
- [x] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/x509` — green
- [x] 15.3 `bunx turbo test:coverage --filter=@suaveplan/x509` — 100/100/100/100
- [x] 15.4 `bun scripts/openspec-conformance.ts @suaveplan/x509` — exit 0, no `error` findings
- [x] 15.5 `bunx turbo build --filter=@suaveplan/x509` — green
## 16. Pre-archive 7-row checklist (AGENTS.md §13)
- [x] 16.1 Generics threaded — `Asn1Node` discriminator, `GeneralName` discriminator, `Extension<T>`; `expectTypeOf` assertions on `X509ErrorCode`, `GeneralNameKind`, `KeyUsageBit`
- [x] 16.2 Zod schemas wired at every declared boundary (`parseCertificate`, `validateChain`, `TrustAnchorStore` constructor)
- [x] 16.3 Every error code in `error-codes.ts` thrown by ≥1 production path
- [x] 16.4 Strict-mode behaviours tested both on (DER) and off (BER)
- [x] 16.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
- [x] 16.6 Lifecycle dedup — N/A (parser + validator are stateless; `TrustAnchorStore` is immutable after construction)
- [x] 16.7 No retroactive tick — every box above reflects work done in this change
## 17. Banned-pattern + barrel-shape + runner-import scans
- [x] 17.1 `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/security/x509/src/ --include="*.ts"` — zero output
- [x] 17.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any `<feature>/index.ts`
- [x] 17.3 Runner-import scan per AGENTS.md §13 step 4b — zero output
- [x] 17.4 Missing-`.md` scan — zero output
- [x] 17.5 Bare-version scan — zero output
- [x] 17.6 Subpath count matches feature module count (9 subpaths: `.`, `./asn1`, `./pem`, `./certificate`, `./extensions`, `./signature`, `./validate`, `./helpers`, `./errors`)
- [x] 17.7 Security scan — no hardcoded secrets
## 18. Root-doc refresh
- [x] 18.1 `bun scripts/folder-reorg/generate-manifest.ts` — refresh `MANIFEST.md`
- [x] 18.2 `bun scripts/generate-missing.ts` — refresh `MISSING.yml`
- [x] 18.3 `STATUS.md` — move `x509` row to `Completed`
- [x] 18.4 `SUMMARY.md` — add per-package paragraph
- [x] 18.5 `bunx markdownlint-cli2 --fix` then a second `bunx markdownlint-cli2` pass with exit 0
- [x] 18.6 `git status` shows only intentional changes
## 19. Archive
- [x] 19.1 Fold spec delta into `openspec/specs/x509/spec.md` (canonical, status ACTIVE)
- [x] 19.2 Move `openspec/changes/enterprise-x509/` → `openspec/archive/<yyyy-mm-dd>-enterprise-x509/`
- [x] 19.3 Confirm `MANIFEST.md` lists `@suaveplan/x509` after refresh