Files
skills/docs/security-stack/security/x509/src/validate/trust-anchor.md
2026-05-29 08:20:56 +01:00

3.4 KiB

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

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

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<ValidateChainResult> — async; throws X509Error on every failure path.
  • TrustAnchorStore — findBySki, findIssuer, contains, toArray, size.
  • DEFAULT_MAX_CHAIN_DEPTH — 16.
  • OcspResponderLike — check(cert, issuer, signal?) => Promise<OcspStatus>.

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.