15 KiB
15 KiB
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.mdend-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.mdSections 13, 14, 14a - 1.4 Confirm
proposal.mdcovers every public-surface item, every subpath, every dependency, and a complete Parity Checklist - 1.5 Confirm
specs/x509/spec.mdcarries one requirement per logical capability with ≥2 scenarios each - 1.6 Confirm every requirement has a
**Verified by:**line naming concreteit(...)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/withsrc/{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,zodvia catalog refs only (no bare versions) - 2.3
package.jsonexportsmap declares.,./asn1,./pem,./certificate,./extensions,./signature,./validate,./helpers,./errors(9 subpaths) — each mapped to its builtdist/<feature>/<feature>.js+.d.tspair - 2.4
tsconfig.lib.jsonextends@suaveplan/typescript-config/library.json - 2.5
vite.config.tswith multi-entry build (one entry per subpath) +vitest.config.tscallingcreateTestConfig()from@suaveplan/testing/config - 2.6
README.mdwith package name, description,bun addinstall, 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/encodeOidper X.690 §8.19 - 3.4
src/asn1/strings.ts—UTF8String,PrintableString,IA5String,TeletexString,BMPString,UniversalStringdecoders; encoder picks most-restrictive form per RFC 5280 §7 - 3.5
src/asn1/time.ts—UTCTime(50/49 split) +GeneralizedTime(mandatoryZ) decode + encode - 3.6
src/asn1/asn1.types.ts—Asn1Tag,Asn1Class,Asn1Nodediscriminated union,Asn1DecodeMode
4. Implementation — PEM framing
- 4.1
src/pem/pem.ts—decodePem(text): PemBlock[]+encodePem(label, bytes): stringper RFC 7468; multi-cert bundles - 4.2
src/pem/pem.types.ts—PemBlock - 4.3
\r\nand\nline endings tolerated; label whitelist (CERTIFICATE, others rejected at boundary)
5. Implementation — certificate parser
- 5.1
src/certificate/parse.ts—parseCertificate(bytes): X509Certificatefor full TBSCertificate (RFC 5280 §4.1); accepts DER and bare PEM (viadecodePem); captures canonical TBS DER bytes for signature verify - 5.2
src/certificate/name.ts— RFC 5280 §4.1.2.4Namedecode + 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-bitBIT 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 — fullGeneralNamediscriminated 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 forvalidateChainfailure
7. Implementation — signature verify
- 7.1
src/signature/alg-map.ts— OID →@suaveplan/crypto-curvealgorithm 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 ECDSASEQUENCE{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—TrustAnchorStorewith O(1)findBySki+findIssuer; PEM-bundle constructor - 8.4
src/validate/ocsp.types.ts—OcspResponderLikeinterface 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
AbortSignalhonoured — 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 forParseCertificateOptions,ValidateChainOptions,X509Certificate,GeneralName,KeyUsage,ExtendedKeyUsage,BasicConstraints,NameConstraints,CrlDistributionPoint,AuthorityKeyIdentifier,SubjectKeyIdentifier - 10.3
src/observability/metrics.ts—x509.parse_duration_ms+x509.validate_duration_mshistograms;x509.parse_total{outcome},x509.validate_total{outcome},x509.extension_total{oid,critical},x509.error_total{code}counters;x509.chain_lengthhistogram - 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 (noexport *); namespace re-exports allowed for sub-namespaces (per CLAUDE.md §10) - 11.2 Every
<feature>/index.tsuses explicit named re-exports per AGENTS.md §6a - 11.3
package.jsonexportsmap 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—UTCTime50/49 split;GeneralizedTimemandatoryZ - 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— canonicalNameencode/decode; equality predicate - 12.11
src/extensions/basic-constraints.test.ts—cA: truewithpathLenConstraint; end-entity rejected as issuer - 12.12
src/extensions/key-usage.test.ts— every bit position;keyCertSignenforced on issuers - 12.13
src/extensions/extended-key-usage.test.ts— every recognised KeyPurposeId;anyExtendedKeyUsagepermissive - 12.14
src/extensions/subject-alt-name.test.ts— everyGeneralNamevariant; 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—keyIdentifierdecode; matches issuer SKI - 12.17
src/extensions/subject-key-id.test.ts—keyIdentifierdecode - 12.18
src/extensions/crl-distribution-points.test.ts—fullNameURIs 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;expectTypeOfpins literal union - 12.29
src/observability/metrics.test.ts— histograms + counters under in-memoryMeterProvider - 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
.mdfor 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
.mdunder## Modules - 13.3 Update root
CLAUDE.mdpackage listing — add thex509paragraph under the existing security plugin grouping (aftermacos-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—decodePemover 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, noerrorfindings - 15.5
bunx turbo build --filter=@suaveplan/x509— green
16. Pre-archive 7-row checklist (AGENTS.md §13)
- 16.1 Generics threaded —
Asn1Nodediscriminator,GeneralNamediscriminator,Extension<T>;expectTypeOfassertions onX509ErrorCode,GeneralNameKind,KeyUsageBit - 16.2 Zod schemas wired at every declared boundary (
parseCertificate,validateChain,TrustAnchorStoreconstructor) - 16.3 Every error code in
error-codes.tsthrown 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;
TrustAnchorStoreis 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-
.mdscan — 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— refreshMANIFEST.md - 18.2
bun scripts/generate-missing.ts— refreshMISSING.yml - 18.3
STATUS.md— movex509row toCompleted - 18.4
SUMMARY.md— add per-package paragraph - 18.5
bunx markdownlint-cli2 --fixthen a secondbunx markdownlint-cli2pass with exit 0 - 18.6
git statusshows 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.mdlists@suaveplan/x509after refresh