# `validate/trust-anchor` — SKI-indexed trust-anchor store ## Purpose `validate/trust-anchor` is the core of the certificate path-validation pipeline. `validateChain` walks the certificate chain leaf-first up to the trust anchor, re-hashing each TBSCertificate, dispatching the signature verify through `@suaveplan/crypto-rsa` (for RSA-PKCS1v1.5 and RSA-PSS) or `@suaveplan/crypto-curve` (for ECDSA-P{256,384,521} and Ed25519), and checking validity, basic constraints, key usage, EKU, name chaining, name constraints, OCSP status (when an `OcspResponderLike` is injected), and critical-extension recognition per RFC 5280 paragraph 6.1. ## Features - Full 12-step algorithm per RFC 5280 paragraph 6.1 with the spec-pinned set of failure codes. - Injectable `Clock` so tests can probe expiry boundaries without time travel. - Injectable `AbortSignal` so callers can interrupt slow chains (e.g. when an OCSP responder is unreachable). - Bounded chain depth (default 16) with caller override. - `TrustAnchorStore` value object with O(1) SKI lookup and PEM-bundle constructor. - Configurable duplicate-SKI policy (`last-wins` default, `first-wins`, or `throw`). - OpenTelemetry surface emitting `x509.validate_duration_ms`, `x509.validate_total`, `x509.chain_length`, and `x509.error_total` on every call. ## Usage: Basic Example ```ts import { validateChain, TrustAnchorStore } from "@suaveplan/x509/validate"; import { parseCertificate } from "@suaveplan/x509/certificate"; const result = await validateChain({ leaf: parseCertificate(leafBytes), intermediates: [parseCertificate(intermediateBytes)], trustAnchors: [parseCertificate(rootBytes)], }); console.log(result.trustedPath.length); // 3 ``` ## Usage: Advanced Example ```ts import { validateChain, TrustAnchorStore } from "@suaveplan/x509/validate"; import { parseCertificate } from "@suaveplan/x509/certificate"; const store = new TrustAnchorStore(pemBundleString, { onDuplicate: "throw" }); const ac = new AbortController(); const result = await validateChain({ leaf: parseCertificate(leafBytes), intermediates: intermediates.map(parseCertificate), trustAnchors: store.toArray(), clock: () => Date.UTC(2025, 0, 1), signal: ac.signal, maxChainDepth: 8, ekuRequired: "serverAuth", }); ``` ## API Reference - `validateChain(opts: ValidateChainOptions): Promise` — async; throws `X509Error` on every failure path. - `TrustAnchorStore` — `findBySki`, `findIssuer`, `contains`, `toArray`, `size`. - `DEFAULT_MAX_CHAIN_DEPTH` — `16`. - `OcspResponderLike` — `check(cert, issuer, signal?) => Promise`. ## Implementation Notes - The chain is ordered leaf-first by walking from the leaf upward: at each step the next certificate is chosen by canonical-name equality between the current cert's issuer and a candidate's subject. The algorithm terminates when a trust anchor is reached. - The OCSP boundary is interface-only — no built-in responder ships. Implementation lives in `@suaveplan/x509-ocsp` (follow-up). - Policy mapping / policy constraints are out of scope; the validator does not enforce them and rejects them as unknown critical extensions per RFC 5280 paragraph 6.1.4(f). - Self-signed leaf rejection happens before the chain walk; the leaf is accepted only when it is byte-identical to a trust anchor. - Pre-aborted `AbortSignal` short-circuits before any work; aborts between per-cert iterations are honoured.