Initial Commit
This commit is contained in:
242
openspec/archive/2026-05-11-enterprise-x509/proposal.md
Normal file
242
openspec/archive/2026-05-11-enterprise-x509/proposal.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# `@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).
|
||||
Reference in New Issue
Block a user