Initial Commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# `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<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.
|
||||
Reference in New Issue
Block a user