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

15 KiB
Raw Permalink Blame History

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

  • 1.1 Read enterprise/x509.md end-to-end
  • 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)
  • 1.3 Read openspec/AGENTS.md Sections 13, 14, 14a
  • 1.4 Confirm proposal.md covers every public-surface item, every subpath, every dependency, and a complete Parity Checklist
  • 1.5 Confirm specs/x509/spec.md carries one requirement per logical capability with ≥2 scenarios each
  • 1.6 Confirm every requirement has a **Verified by:** line naming concrete it(...) titles in concrete test file paths
  • 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

  • 2.1 Create packages/security/x509/ with src/{asn1,pem,certificate,extensions,signature,validate,helpers,errors,observability,schemas,__benchmarks__}/
  • 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)
  • 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
  • 2.4 tsconfig.lib.json extends @suaveplan/typescript-config/library.json
  • 2.5 vite.config.ts with 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 (parse + validate a 3-cert chain), Modules table linking every co-located .md

3. Implementation — ASN.1 codec

  • 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
  • 3.2 src/asn1/encoder.ts — canonical DER encoder; round-trip with decoder byte-identical
  • 3.3 src/asn1/oid.ts — decodeOid / encodeOid per X.690 §8.19
  • 3.4 src/asn1/strings.ts — UTF8String, PrintableString, IA5String, TeletexString, BMPString, UniversalString decoders; encoder picks most-restrictive form per RFC 5280 §7
  • 3.5 src/asn1/time.ts — UTCTime (50/49 split) + GeneralizedTime (mandatory Z) decode + encode
  • 3.6 src/asn1/asn1.types.ts — Asn1Tag, Asn1Class, Asn1Node discriminated union, Asn1DecodeMode

4. Implementation — PEM framing

  • 4.1 src/pem/pem.ts — decodePem(text): PemBlock[] + encodePem(label, bytes): string per RFC 7468; multi-cert bundles
  • 4.2 src/pem/pem.types.ts — PemBlock
  • 4.3 \r\n and \n line endings tolerated; label whitelist (CERTIFICATE, others rejected at boundary)

5. Implementation — certificate parser

  • 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
  • 5.2 src/certificate/name.ts — RFC 5280 §4.1.2.4 Name decode + canonical encode for name-chaining equality
  • 5.3 src/certificate/certificate.types.ts — X509Certificate, TbsCertificate, Name, RelativeDistinguishedName, AttributeTypeAndValue, Validity, SubjectPublicKeyInfo, AlgorithmIdentifier, Extension, RawExtension

6. Implementation — extensions

  • 6.1 src/extensions/basic-constraints.ts (RFC 5280 §4.2.1.9)
  • 6.2 src/extensions/key-usage.ts (§4.2.1.3, 9-bit BIT STRING)
  • 6.3 src/extensions/extended-key-usage.ts (§4.2.1.12)
  • 6.4 src/extensions/subject-alt-name.ts (§4.2.1.6 + §4.2.2.6 — full GeneralName discriminated union)
  • 6.5 src/extensions/name-constraints.ts (§4.2.1.10 — DNS suffix / RFC 822 / URI / IP prefix / directory-name matching)
  • 6.6 src/extensions/authority-key-id.ts (§4.2.1.1)
  • 6.7 src/extensions/subject-key-id.ts (§4.2.1.2)
  • 6.8 src/extensions/crl-distribution-points.ts (§4.2.1.13 — parse only)
  • 6.9 src/extensions/registry.ts — OID-keyed dispatch; unknown → RawExtension; unknown critical preserved for validateChain failure

7. Implementation — signature verify

  • 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)
  • 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

  • 8.1 src/validate/validate-chain.ts — full RFC 5280 §6.1 12-step algorithm
  • 8.2 src/validate/validate-chain.types.ts — ValidateChainOptions, ValidateChainResult
  • 8.3 src/validate/trust-anchor.ts — TrustAnchorStore with O(1) findBySki + findIssuer; PEM-bundle constructor
  • 8.4 src/validate/ocsp.types.ts — OcspResponderLike interface only (no impl)
  • 8.5 Chain depth bound default 16; configurable; exceed → X509.CHAIN_TOO_DEEP
  • 8.6 Self-signed leaf rejected unless explicitly in trust anchors → X509.SELF_SIGNED_LEAF
  • 8.7 AbortSignal honoured — pre-aborted → X509.ABORTED

9. Implementation — helpers

  • 9.1 src/helpers/has-name-match.ts — CN + SAN dNSName match per RFC 6125
  • 9.2 src/helpers/chains-to.ts — SKI/AKI heuristic
  • 9.3 src/helpers/is-issuer-of.ts — canonical-name equality

10. Implementation — errors, schemas, observability

  • 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>
  • 10.2 src/schemas/schemas.ts — Zod schemas for ParseCertificateOptions, ValidateChainOptions, X509Certificate, GeneralName, KeyUsage, ExtendedKeyUsage, BasicConstraints, NameConstraints, CrlDistributionPoint, AuthorityKeyIdentifier, SubjectKeyIdentifier
  • 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
  • 10.4 Every code in the registry thrown by ≥1 production code path; verified by error-codes.test.ts

11. Root barrel + subpath barrels

  • 11.1 src/index.ts — explicit named re-exports (no export *); namespace re-exports allowed for sub-namespaces (per CLAUDE.md §10)
  • 11.2 Every <feature>/index.ts uses explicit named re-exports per AGENTS.md §6a
  • 11.3 package.json exports map matches the 9 subpaths declared in §2.3

12. Tests (runner-neutral imports — @suaveplan/testing/runner only)

  • 12.1 src/asn1/decoder.test.ts — every X.509 production; DER vs BER indefinite-length handling
  • 12.2 src/asn1/encoder.test.ts — round-trip with decoder byte-identical on every primitive + constructed shape
  • 12.3 src/asn1/oid.test.ts — IANA OID round-trips
  • 12.4 src/asn1/strings.test.ts — every typed string; encoder picks most-restrictive form
  • 12.5 src/asn1/time.test.ts — UTCTime 50/49 split; GeneralizedTime mandatory Z
  • 12.6 src/asn1/asn1.spec-conformance.test.ts — RFC 5280 Appendix B modules parse without error
  • 12.7 src/pem/pem.test.ts — single block + multi-cert bundle + line-ending tolerance + malformed → PEM_MALFORMED
  • 12.8 src/certificate/parse.test.ts — Mozilla NSS / pkijs corpus certificates parse to expected typed fields
  • 12.9 src/certificate/parse.spec-conformance.test.ts — fast-check round-trip: parse → DER encode → parse byte-identical
  • 12.10 src/certificate/name.test.ts — canonical Name encode/decode; equality predicate
  • 12.11 src/extensions/basic-constraints.test.ts — cA: true with pathLenConstraint; end-entity rejected as issuer
  • 12.12 src/extensions/key-usage.test.ts — every bit position; keyCertSign enforced on issuers
  • 12.13 src/extensions/extended-key-usage.test.ts — every recognised KeyPurposeId; anyExtendedKeyUsage permissive
  • 12.14 src/extensions/subject-alt-name.test.ts — every GeneralName variant; IPv4 + IPv6
  • 12.15 src/extensions/name-constraints.test.ts — DNS suffix match; IP-prefix match; excluded-subtree rejection
  • 12.16 src/extensions/authority-key-id.test.ts — keyIdentifier decode; matches issuer SKI
  • 12.17 src/extensions/subject-key-id.test.ts — keyIdentifier decode
  • 12.18 src/extensions/crl-distribution-points.test.ts — fullName URIs decoded
  • 12.19 src/extensions/registry.test.ts — dispatch + RawExtension + critical preservation
  • 12.20 src/signature/alg-map.test.ts — every supported OID maps; unknown → SIGNATURE_ALG_UNSUPPORTED
  • 12.21 src/signature/verify.test.ts — RSA-SHA256, ECDSA-P256-SHA256, Ed25519 verify; tampered TBS → SIGNATURE_INVALID
  • 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
  • 12.23 src/validate/validate-chain.spec-conformance.test.ts — real WebAuthn TPM attestation chain + real Apple WebAuthn attestation chain validate
  • 12.24 src/validate/trust-anchor.test.ts — SKI lookup; PEM-bundle constructor; duplicate-SKI
  • 12.25 src/helpers/has-name-match.test.ts — CN + SAN; wildcard per RFC 6125
  • 12.26 src/helpers/chains-to.test.ts — SKI/AKI heuristic; AKI-absent → false
  • 12.27 src/helpers/is-issuer-of.test.ts — canonical-name equality across encoding variants
  • 12.28 src/errors/error-codes.test.ts — every code thrown; expectTypeOf pins literal union
  • 12.29 src/observability/metrics.test.ts — histograms + counters under in-memory MeterProvider
  • 12.30 src/schemas/schemas.test.ts — every Zod schema rejects malformed input
  • 12.31 src/index.test.ts — every documented subpath resolves
  • 12.32 Every it(...) carries { timeout: 5000 } (or { timeout: 30000 } for property + chain tests)
  • 12.33 Runner-neutral imports — grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/security/x509/src/ returns zero output

13. Documentation

  • 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
  • 13.2 README.md links every co-located .md under ## Modules
  • 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)

  • 15.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the monorepo
  • 15.2 bunx turbo lint typecheck test --filter=@suaveplan/x509 — green
  • 15.3 bunx turbo test:coverage --filter=@suaveplan/x509 — 100/100/100/100
  • 15.4 bun scripts/openspec-conformance.ts @suaveplan/x509 — exit 0, no error findings
  • 15.5 bunx turbo build --filter=@suaveplan/x509 — green

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

  • 16.1 Generics threaded — Asn1Node discriminator, GeneralName discriminator, Extension<T>; expectTypeOf assertions on X509ErrorCode, GeneralNameKind, KeyUsageBit
  • 16.2 Zod schemas wired at every declared boundary (parseCertificate, validateChain, TrustAnchorStore constructor)
  • 16.3 Every error code in error-codes.ts thrown by ≥1 production path
  • 16.4 Strict-mode behaviours tested both on (DER) and off (BER)
  • 16.5 OTel instruments emitted from production paths and asserted with a real in-memory MeterProvider
  • 16.6 Lifecycle dedup — N/A (parser + validator are stateless; TrustAnchorStore is immutable after construction)
  • 16.7 No retroactive tick — every box above reflects work done in this change

17. Banned-pattern + barrel-shape + runner-import scans

  • 17.1 grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/security/x509/src/ --include="*.ts" — zero output
  • 17.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any <feature>/index.ts
  • 17.3 Runner-import scan per AGENTS.md §13 step 4b — zero output
  • 17.4 Missing-.md scan — zero output
  • 17.5 Bare-version scan — zero output
  • 17.6 Subpath count matches feature module count (9 subpaths: ., ./asn1, ./pem, ./certificate, ./extensions, ./signature, ./validate, ./helpers, ./errors)
  • 17.7 Security scan — no hardcoded secrets

18. Root-doc refresh

  • 18.1 bun scripts/folder-reorg/generate-manifest.ts — refresh MANIFEST.md
  • 18.2 bun scripts/generate-missing.ts — refresh MISSING.yml
  • 18.3 STATUS.md — move x509 row to Completed
  • 18.4 SUMMARY.md — add per-package paragraph
  • 18.5 bunx markdownlint-cli2 --fix then a second bunx markdownlint-cli2 pass with exit 0
  • 18.6 git status shows only intentional changes

19. Archive

  • 19.1 Fold spec delta into openspec/specs/x509/spec.md (canonical, status ACTIVE)
  • 19.2 Move openspec/changes/enterprise-x509/ → openspec/archive/<yyyy-mm-dd>-enterprise-x509/
  • 19.3 Confirm MANIFEST.md lists @suaveplan/x509 after refresh