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

25 KiB

@suaveplan/x509 — RFC 5280 X.509 v3 parser + certification-path validator

Package: @suaveplan/x509 (NEW) Status: PROPOSED Type: new-package (genesis-original; greenfield — no upstream port) Date: 2026-05-11 Path: packages/security/x509/

Why

@suaveplan/webauthn parses three attestation statement formats — tpm (TPM 2.0 attestation), android-key (Google Hardware Attestation), apple (Apple Anonymous Attestation) — each of which carries an X.509 certificate chain in its x5c array and requires that chain to validate against a pinned set of trust anchors (FIDO MDS root CAs for TPM, Google Hardware Attestation roots for android-key, the Apple WebAuthn Root CA for apple). Without a real RFC 5280 §6.1 path validator the WebAuthn attestation verifiers either trust the leaf blindly (security defect) or ship as half-baked stubs (banned per CLAUDE.md §3 and OpenSpec AGENTS.md §2b). Both options are unacceptable on genesis.

External alternatives (node-forge, pkijs, @peculiar/x509, asn1.js) drag in CommonJS interop hazards, oversize transitive deps, and behaviours that diverge from RFC 5280 strict-profile requirements (most notably around the §6.1.4(f) handling of unrecognised critical extensions, and around DER canonicalisation when re-hashing the TBSCertificate for signature verify). An in-house, RFC-cited, zero-runtime-dep package keeps webauthn universal-tier and lets every future PKI consumer (mTLS handshake validation, Sigstore Fulcio chains, code-signing verify, SCEP enrolment) share one canonical implementation with one error registry, one OTel surface, one ASN.1 codec, and one path-validation algorithm.

The package also unlocks the SLSA / Sigstore work already on genesis: @suaveplan/slsa currently verifies Rekor inclusion proofs but cannot verify the Fulcio-issued code-signing certificate chains those proofs reference, because no X.509 verifier exists. This proposal closes both gaps with one package.

What changes

New package: @suaveplan/x509

  • ADDED: packages/security/x509/ package scaffold (package.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts, README.md).

ASN.1 codec

  • ADDED: src/asn1/decoder.ts — decodeAsn1(bytes: Uint8Array, mode?: "der" | "ber"): Asn1Node recursive TLV decoder covering every production used by X.509 v3 (RFC 5280 Appendix A, A.1, A.2). Strict DER (default) rejects indefinite-length; permissive BER accepts it.
  • ADDED: src/asn1/encoder.ts — encodeAsn1(node: Asn1Node): Uint8Array canonical DER encoder; round-trip with the decoder is byte-identical for any input the decoder accepted.
  • ADDED: src/asn1/oid.ts — OID encode/decode (decodeOid(bytes): string, encodeOid(dotted: string): Uint8Array) per X.690 §8.19.
  • ADDED: src/asn1/strings.ts — typed-string decoders for UTF8String, PrintableString, IA5String, TeletexString, BMPString, UniversalString. Encoder picks the most-restrictive form that holds the input (RFC 5280 §4.1.2.4 + §7).
  • ADDED: src/asn1/time.ts — UTCTime (RFC 5280 §4.1.2.5.1, two-digit-year 50/49 split) + GeneralizedTime (§4.1.2.5.2, mandatory Z) decode + encode; returns numeric epoch-ms via injectable Clock-compatible interface.
  • ADDED: src/asn1/asn1.types.ts — Asn1Tag, Asn1Class, Asn1Node discriminated union, Asn1DecodeMode.

PEM framing

  • ADDED: src/pem/pem.ts — decodePem(text: string): PemBlock[] and encodePem(label: string, bytes: Uint8Array): string per RFC 7468. Multi-cert bundles supported via repeated BEGIN/END blocks; \r\n and \n line endings tolerated.
  • ADDED: src/pem/pem.types.ts — PemBlock { label: string; bytes: Uint8Array; }.

Certificate parser

  • ADDED: src/certificate/parse.ts — parseCertificate(bytes: Uint8Array): X509Certificate per RFC 5280 §4.1. Accepts DER and bare PEM (via decodePem); the result carries the parsed fields plus the canonical TBSCertificate DER bytes (for re-hashing during signature verify) AND the raw outer signature bytes.
  • ADDED: src/certificate/certificate.types.ts — X509Certificate, TbsCertificate, Name, RelativeDistinguishedName, AttributeTypeAndValue, Validity, SubjectPublicKeyInfo, AlgorithmIdentifier, Extension, RawExtension.
  • ADDED: src/certificate/name.ts — RFC 5280 §4.1.2.4 Name decode + canonical encode (used by name-chaining check during validation).

Extension decoders (RFC 5280 §4.2)

  • ADDED: src/extensions/basic-constraints.ts — RFC 5280 §4.2.1.9 cA, pathLenConstraint.
  • ADDED: src/extensions/key-usage.ts — RFC 5280 §4.2.1.3 nine-bit BIT STRING → KeyUsage flags.
  • ADDED: src/extensions/extended-key-usage.ts — RFC 5280 §4.2.1.12 SEQUENCE OF KeyPurposeId. Recognises serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning, anyExtendedKeyUsage.
  • ADDED: src/extensions/subject-alt-name.ts — RFC 5280 §4.2.1.6 + §4.2.2.6 GeneralName discriminated union: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress (4-byte v4 / 16-byte v6), directoryName, otherName, x400Address, ediPartyName, registeredID.
  • ADDED: src/extensions/name-constraints.ts — RFC 5280 §4.2.1.10 permittedSubtrees / excludedSubtrees with GeneralSubtree decode + matching helpers (DNS suffix, RFC 822, URI, IP prefix, directory name).
  • ADDED: src/extensions/authority-key-id.ts — RFC 5280 §4.2.1.1.
  • ADDED: src/extensions/subject-key-id.ts — RFC 5280 §4.2.1.2.
  • ADDED: src/extensions/crl-distribution-points.ts — RFC 5280 §4.2.1.13 SEQUENCE OF DistributionPoint decode (CRL fetching is out of scope; structure is exposed for sibling packages).
  • ADDED: src/extensions/registry.ts — OID-keyed dispatch table; unknown extension → RawExtension { oid, critical, value }.

Signature verify

  • ADDED: src/signature/alg-map.ts — OID → @suaveplan/crypto-curve algorithm ID table: sha256WithRSAEncryption (1.2.840.113549.1.1.11), sha384WithRSAEncryption (…1.12), sha512WithRSAEncryption (…1.13), ecdsa-with-SHA256 (1.2.840.10045.4.3.2), ecdsa-with-SHA384 (…4.3.3), ecdsa-with-SHA512 (…4.3.4), id-Ed25519 (1.3.101.112), id-Ed448 (1.3.101.113).
  • ADDED: src/signature/verify.ts — verifyCertificateSignature(cert: X509Certificate, parentSpki: SubjectPublicKeyInfo): Promise<boolean> re-encoding TBSCertificate to DER, decoding the outer signatureValue BIT STRING (handling the ECDSA SEQUENCE { r, s } shape), dispatching via alg-map to @suaveplan/crypto-curve.

Path validation

  • ADDED: src/validate/validate-chain.ts — validateChain(opts: ValidateChainOptions): Promise<ValidateChainResult> implementing RFC 5280 §6.1 12-step algorithm: initialise state (maxPathLength, permittedSubtrees, excludedSubtrees, validPolicyTree); for each cert verify signature, validity, name chaining, name constraints, basic constraints, key usage keyCertSign on issuers, path-length, critical-extension recognition; bound chain depth.
  • ADDED: src/validate/validate-chain.types.ts — ValidateChainOptions { leaf, intermediates, trustAnchors, clock?, signal?, maxChainDepth?, ocspResponder?, hostName?, ekuRequired? }, ValidateChainResult { trustedPath: X509Certificate[] }.
  • ADDED: src/validate/trust-anchor.ts — TrustAnchorStore value object with O(1) findBySki and findIssuer lookups; PEM-bundle constructor.
  • ADDED: src/validate/ocsp.types.ts — OcspResponderLike interface only; implementation lives in a follow-up @suaveplan/x509-ocsp package.

Helpers

  • ADDED: src/helpers/has-name-match.ts — hasNameMatch(cert: X509Certificate, name: string): boolean against CN + every SAN dNSName per RFC 6125.
  • ADDED: src/helpers/chains-to.ts — chainsTo(child: X509Certificate, anchor: X509Certificate): boolean heuristic via SKI/AKI.
  • ADDED: src/helpers/is-issuer-of.ts — canonical-name equality between child.issuer and parent.subject.

Errors, schemas, observability

  • ADDED: src/errors/error-codes.ts — defineCodes("X509", [...]) with ≥18 codes (full list below); X509Error extends SuaveplanError<X509ErrorCode>.
  • ADDED: src/schemas/schemas.ts — Zod schemas for ParseCertificateOptions, ValidateChainOptions, X509Certificate, GeneralName, KeyUsage, ExtendedKeyUsage, BasicConstraints, NameConstraints, CrlDistributionPoint, AuthorityKeyIdentifier, SubjectKeyIdentifier.
  • ADDED: 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} counters; x509.chain_length histogram on successful validations.

Benchmarks + root barrel

  • ADDED: src/__benchmarks__/parse.bench.ts, validate.bench.ts, pem.bench.ts — mitata.
  • ADDED: src/index.ts — explicit named re-exports (no export *).

Subpath exports

  • . — root barrel: parseCertificate, validateChain, decodePem, encodePem, TrustAnchorStore, error codes.
  • ./asn1 — decodeAsn1, encodeAsn1, decodeOid, encodeOid, string + time helpers, Asn1Node.
  • ./pem — decodePem, encodePem, PemBlock.
  • ./certificate — parseCertificate, X509Certificate, name codec.
  • ./extensions — every extension decoder + registry + types.
  • ./signature — verifyCertificateSignature, alg-map.
  • ./validate — validateChain, TrustAnchorStore, options/result/OCSP types.
  • ./helpers — hasNameMatch, chainsTo, isIssuerOf.
  • ./errors — X509_CODES, X509Error, X509ErrorCode.

Dependencies

  • @suaveplan/crypto-curve workspace:^ — RSA / ECDSA / Ed25519 / Ed448 verify (called by verifyCertificateSignature).
  • @suaveplan/crypto-hash workspace:^ — SHA-256/384/512 over the canonical TBSCertificate bytes.
  • @suaveplan/crypto-utils workspace:^ — constant-time equality, hex/base64 helpers.
  • @suaveplan/error workspace:^ — defineCodes, SuaveplanError base.
  • @suaveplan/types workspace:^ — Clock injection point.
  • @opentelemetry/api catalog:observability — histograms + counters.
  • tslib catalog:build.
  • zod catalog:forms — boundary schemas.
  • No node-forge, pkijs, @peculiar/x509, asn1.js, browser globals, or Node built-ins. Pure TypeScript over Uint8Array + DataView.

Out of scope

Every capability NOT listed in ## What changes is explicitly out of scope for this delta. The deliberate exclusions:

  • OCSP responder implementation — OcspResponderLike interface is exposed and validateChain honours it, but no responder is shipped. Reason: OCSP requires a fetch transport, response signature verify, nonce extension handling (RFC 8954), and AIA URL extraction — a meaningfully separate surface. A follow-up @suaveplan/x509-ocsp package owns it.
  • CRL fetching + CRL signature verify — CrlDistributionPoint parsing ships here so callers can extract distribution URLs; actual CRL retrieval, parsing of the CertificateList ASN.1 structure (RFC 5280 §5), and revocation-set checking live in a follow-up @suaveplan/x509-crl package. Reason: same scope-discipline justification as OCSP — CRL is a separate wire format with its own extension set and its own fetch concerns.
  • Certificate issuance / signing — no signCertificate, no createCertificateRequest. Reason: issuance is a CA function; @suaveplan/x509-ca would own it (also requires keypair generation + serial-number policy). Not yet requested by any consumer.
  • Smartcard / PKCS#11 / PKCS#12 interop — out of scope; not requested.
  • Policy mapping / policy constraints extensions (RFC 5280 §4.2.1.5 / §4.2.1.11) — these extensions are decoded as RawExtension if non-critical; if marked critical they trigger X509.CRITICAL_EXTENSION_UNRECOGNIZED per RFC 5280 §6.1.4(f). Path validation does NOT honour them. Reason: WebAuthn attestation chains do not use policy mapping; SLSA / Sigstore chains do not use policy mapping. Adding it would double the path-validation algorithm without a consumer requesting it; the §6.1.4(f) failure mode is RFC-compliant.

Source mapping

This package is genesis-original; no ../suaveplan/enterprise/x509.md line range exists for any feature. The companion enterprise doc at enterprise/x509.md is being authored in this same change. Source-of-record is RFC 5280 (plus RFC 6818, RFC 7468, and the IANA Object Identifier registry).

Requirement RFC source Notes
ASN.1 BER/DER decoder (X.509 subset) RFC 5280 Appendix A.1 + A.2; X.690 §8 TLV, primitive + constructed; tags + classes per X.690 §8.1.2
ASN.1 DER encoder RFC 5280 §4.1.2 + X.690 §10 (DER canonical) Round-trip with decoder
Object Identifier encode/decode X.690 §8.19 Subidentifier 7-bit encoding
PEM framing RFC 7468 §5 -----BEGIN/END CERTIFICATE-----; multi-cert bundles
TBSCertificate parser RFC 5280 §4.1.2 Every field; v1/v2/v3 version handling
Name / RDN / AttributeTypeAndValue RFC 5280 §4.1.2.4 + §7 Canonical encoding for name-chaining check
Validity (UTCTime / GeneralizedTime) RFC 5280 §4.1.2.5 50/49 split; mandatory Z suffix on GeneralizedTime
SubjectPublicKeyInfo + AlgorithmIdentifier RFC 5280 §4.1.2.7 RSA / EC / Ed25519 / Ed448 key shapes
Extension dispatch + unknown-critical handling RFC 5280 §4.2 + §6.1.4(f) Unknown critical → CRITICAL_EXTENSION_UNRECOGNIZED
Basic constraints RFC 5280 §4.2.1.9 cA, pathLenConstraint
Key usage RFC 5280 §4.2.1.3 9-bit BIT STRING
Extended key usage RFC 5280 §4.2.1.12 OID list; anyExtendedKeyUsage
Subject Alternative Name RFC 5280 §4.2.1.6 + §4.2.2.6 DNS/IP/URI/email/dirName/otherName
Name constraints RFC 5280 §4.2.1.10 permitted/excluded; DNS suffix matching
Authority key identifier RFC 5280 §4.2.1.1 keyIdentifier + optional issuer/serial
Subject key identifier RFC 5280 §4.2.1.2 keyIdentifier
CRL distribution points RFC 5280 §4.2.1.13 Parse only; CRL fetch out of scope
Signature OID → algorithm ID RFC 5280 §1.2 + IANA OID registry; RFC 8410 for Ed25519/Ed448 RSA / ECDSA / Ed25519 / Ed448
Signature verify dispatch RFC 5280 §4.1.1.3 + §6.1.3(a)(1) Re-encode TBS for hashing; decode ECDSA SEQUENCE { r, s }
Validity period check RFC 5280 §6.1.3(a)(2) Injectable Clock
Name chaining RFC 5280 §6.1.3(a)(4) Canonical Name equality
Path validation 12-step RFC 5280 §6.1 State per §6.1.2; per-cert per §6.1.3; wrap-up per §6.1.4
Chain depth bound RFC 5280 §6.1.4(h)/(k) + §6.1.2(j) Default 16; configurable
Self-signed leaf rejection RFC 5280 §6.1.1 (preconditions) Unless explicitly in trust anchors
Trust anchor store RFC 5280 §6.1.1(d) SKI lookup; PEM-bundle constructor
OCSP responder interface RFC 6960 + RFC 8954 (nonce) Interface only; impl in follow-up package

Requirements with no source line

Requirement Justification
defineCodes("X509", [...]) ≥18-code registry Genesis convention — every package surfaces typed errors via @suaveplan/error. Each code maps to a distinct RFC 5280 §6.1.4 abort condition or parser failure class so callers can branch on root cause.
Zod boundary schemas at every public option type Genesis convention (CLAUDE.md §2 / §11). Catches caller misuse at the boundary; option misuse never silently degrades into a wrong-trust-store outcome.
OTel surface (x509.parse_duration_ms, x509.validate_duration_ms, parse/validate/extension counters, x509.chain_length) Genesis convention — every hot-path package emits histograms + counters. The oid attribute on the extension counter is bounded by the IANA + registered-OID set the parser recognises plus a single "unknown" bucket for unrecognised non-critical extensions; cardinality stays low.
300-line file budget + 100/100/100/100 coverage + sibling .md per impl file CLAUDE.md global rules.
Property-based round-trip (fast-check) — parse → DER encode → parse byte-identical CLAUDE.md "no mocks"; round-trip is the only test that proves the parser + encoder agree on the wire format under arbitrary RFC 5280-valid structures.
TrustAnchorStore SKI lookup + findIssuer helper Path validation needs O(1) issuer lookup or every chain walk is O(n·m); the helper is required for validateChain to meet the perf bound the benchmarks publish.
OcspResponderLike interface (no impl) Allows validateChain to thread OCSP into RFC 5280 §6.1.3(a)(3) without coupling this package to a fetch transport. Implementation in @suaveplan/x509-ocsp (follow-up).
hasNameMatch / chainsTo / isIssuerOf helpers Pre-filtering / display helpers; not part of §6.1 but standard PKI library surface.
Mitata benchmarks under src/__benchmarks__/ CLAUDE.md global benchmark requirement.

Parity Checklist

This package is greenfield (no upstream in ../suaveplan/packages/). The parity check is therefore against external reference implementations rather than against an upstream package:

Capability Reference (pkijs / @peculiar/x509) Migrated Notes
ASN.1 BER/DER codec ✅ (via asn1js) ✅ Hand-rolled X.509 subset; sufficient for every TBSCertificate field
PEM framing ✅ ✅ RFC 7468
parseCertificate ✅ ✅ Full TBSCertificate per §4.1
Basic constraints ✅ ✅ §4.2.1.9
Key usage ✅ ✅ §4.2.1.3
Extended key usage ✅ ✅ §4.2.1.12
Subject Alternative Name ✅ ✅ §4.2.1.6 + §4.2.2.6
Name constraints ✅ ✅ §4.2.1.10
AKI / SKI ✅ ✅ §4.2.1.1 / §4.2.1.2
CRL distribution points ✅ ✅ Parse only — fetch in follow-up
Validity check ✅ ✅ Clock-injectable
Signature verify dispatch ✅ ✅ RSA / ECDSA / Ed25519 / Ed448 via @suaveplan/crypto-curve
RFC 5280 §6.1 path validation ✅ ✅ 12-step algorithm
Trust anchor store ✅ ✅ SKI O(1) lookup
OCSP responder integration ✅ (pkijs ships a client) 🚫 Scoped to follow-up @suaveplan/x509-ocsp. Reason: OCSP is a wire protocol with its own fetch transport, response signature verify, and RFC 8954 nonce; separable surface that does not block WebAuthn.
CRL fetch + verify ✅ (pkijs) 🚫 Scoped to follow-up @suaveplan/x509-crl. Reason: separate ASN.1 structure (CertificateList, RFC 5280 §5) with its own extension set; not required by WebAuthn (attestation chains don't carry CDPs that matter for the immediate consumer).
Certificate issuance / signing ✅ 🚫 Scoped to follow-up @suaveplan/x509-ca. Reason: CA function; no consumer has requested it.
Policy mapping / policy constraints ✅ (full) 🚫 Decoded as RawExtension; critical occurrence triggers CRITICAL_EXTENSION_UNRECOGNIZED per RFC 5280 §6.1.4(f). Reason: no consumer chain uses these extensions; full algorithm doubles validation surface without payoff. RFC-compliant degradation.
PKCS#12 / PKCS#11 / smartcard ✅ (pkijs) 🚫 Out of scope; not requested.

No 🚫 row uses "not needed yet" alone — every justification names the concrete reason and (where applicable) the follow-up package that owns it.

Verification approach

Each requirement carries a **Verified by:** line in the spec delta naming concrete it(...) titles inside the test files below.

  • packages/security/x509/src/asn1/decoder.test.ts — every X.509 production decoded; indefinite-length rejected in DER mode, accepted in BER mode.
  • packages/security/x509/src/asn1/encoder.test.ts — round-trip on every primitive + constructed shape; byte-identical with decoder input.
  • packages/security/x509/src/asn1/oid.test.ts — IANA OID round-trips: 1.2.840.113549.1.1.11, 1.2.840.10045.4.3.2, 1.3.101.112, 2.5.29.17, 2.5.29.19, …
  • packages/security/x509/src/asn1/strings.test.ts — every typed string round-trips; encoder picks the most-restrictive form.
  • packages/security/x509/src/asn1/time.test.ts — UTCTime 50/49 split (490101000000Z → 2049; 500101000000Z → 1950); GeneralizedTime mandatory Z enforcement.
  • packages/security/x509/src/asn1/asn1.spec-conformance.test.ts — RFC 5280 Appendix B explicit ASN.1 modules parse without error.
  • packages/security/x509/src/pem/pem.test.ts — single block + multi-cert bundle + \r\n vs \n + label whitelist; malformed input throws X509.PEM_MALFORMED.
  • packages/security/x509/src/certificate/parse.test.ts — Mozilla NSS / pkijs corpus certificates parse to expected typed fields.
  • packages/security/x509/src/certificate/parse.spec-conformance.test.ts — round-trip property (fast-check): generated valid certs parse → encode → parse byte-identical.
  • packages/security/x509/src/certificate/name.test.ts — canonical Name encode/decode; equality predicate.
  • packages/security/x509/src/extensions/basic-constraints.test.ts — cA: true with pathLenConstraint: 3; cA: false; end-entity rejected when used as issuer.
  • packages/security/x509/src/extensions/key-usage.test.ts — every bit position; keyCertSign requirement enforced for issuers in validateChain.
  • packages/security/x509/src/extensions/extended-key-usage.test.ts — every recognised KeyPurposeId; anyExtendedKeyUsage permissive.
  • packages/security/x509/src/extensions/subject-alt-name.test.ts — every GeneralName variant; IPv4 + IPv6 decode; URI parse.
  • packages/security/x509/src/extensions/name-constraints.test.ts — DNS suffix match (*.example.com); IP-prefix match (192.0.2.0/24); excluded-subtree rejection.
  • packages/security/x509/src/extensions/authority-key-id.test.ts — keyIdentifier decode; matches issuer's SKI.
  • packages/security/x509/src/extensions/subject-key-id.test.ts — keyIdentifier decode.
  • packages/security/x509/src/extensions/crl-distribution-points.test.ts — fullName URIs decoded; CRL fetch is out of scope.
  • packages/security/x509/src/extensions/registry.test.ts — OID-keyed dispatch; unknown extension yields RawExtension; unknown critical extension marked so the validator can fail.
  • packages/security/x509/src/signature/alg-map.test.ts — every supported OID maps; unknown OID throws X509.SIGNATURE_ALG_UNSUPPORTED.
  • packages/security/x509/src/signature/verify.test.ts — RSA-SHA256, ECDSA-P256-SHA256, Ed25519 verify against real intermediate-signed-by-root pairs from the Mozilla NSS root store; tampered TBS rejected with X509.SIGNATURE_INVALID.
  • packages/security/x509/src/validate/validate-chain.test.ts — happy path 3-chain; expired cert → X509.CERT_EXPIRED; not-yet-valid → X509.CERT_NOT_YET_VALID; broken name chaining → X509.NAME_CHAIN_BROKEN; depth bound exceeded → X509.CHAIN_TOO_DEEP; non-CA issuer → X509.ISSUER_NOT_CA; missing keyCertSign → X509.KEY_USAGE_INVALID; name-constraint violation → X509.NAME_CONSTRAINT_VIOLATION; unknown critical extension → X509.CRITICAL_EXTENSION_UNRECOGNIZED; signature invalid → X509.SIGNATURE_INVALID; trust anchor not found → X509.TRUST_ANCHOR_NOT_FOUND.
  • packages/security/x509/src/validate/validate-chain.spec-conformance.test.ts — real WebAuthn TPM attestation chain (Microsoft TPM Root CA → intermediate → leaf) validates; real Apple WebAuthn attestation chain validates.
  • packages/security/x509/src/validate/trust-anchor.test.ts — SKI lookup; PEM-bundle constructor; duplicate-SKI handling.
  • packages/security/x509/src/helpers/has-name-match.test.ts — CN + SAN dNSName matching; wildcard handling per RFC 6125.
  • packages/security/x509/src/helpers/chains-to.test.ts — SKI/AKI heuristic; returns false when AKI absent.
  • packages/security/x509/src/helpers/is-issuer-of.test.ts — canonical name equality across encoding variants.
  • packages/security/x509/src/errors/error-codes.test.ts — every code in registry thrown by ≥1 production path; expectTypeOf<X509ErrorCode>().toEqualTypeOf<…>() pins the literal union.
  • packages/security/x509/src/observability/metrics.test.ts — histograms + counters recorded under in-memory MeterProvider; x509.extension_total{oid,critical} increments per observed extension; x509.error_total{code} increments on every thrown X509Error.
  • packages/security/x509/src/schemas/schemas.test.ts — every Zod schema rejects malformed options at the boundary.
  • packages/security/x509/src/index.test.ts — every documented subpath export resolves.

Migration impact

None — additive only; this is the first delta of a new package. No existing code in genesis imports from @suaveplan/x509. @suaveplan/webauthn is the gating consumer and is itself pending migration; consumer wiring lands in the WebAuthn migration change once that proposal is opened.

Impact

Affected dependents

bunx turbo ls --filter=...@suaveplan/x509 returns no current dependents (the package does not yet exist on genesis). Pending consumers:

  • @suaveplan/webauthn — [ ] tests run / [ ] docs reviewed / [x] no action needed in THIS change (reason: webauthn is itself pending migration; consumer wiring lands in the webauthn migration change once that proposal is opened, per STATUS.md).
  • @suaveplan/slsa — [ ] tests run / [ ] docs reviewed / [x] no action needed in THIS change (reason: slsa currently lacks Fulcio cert-chain verification entirely; adding the @suaveplan/x509 dep + wiring lands in a separate slsa-fulcio-verification change after this package archives).

No foundation-package fan-out applies (this is a new universal-tier leaf package; foundation deps @suaveplan/error and @suaveplan/types are unchanged).