# `certificate/name` — Name codec + canonical comparison ## Purpose Decodes RFC 5280 paragraph 4.1.2.4 `Name` (`RDNSequence`) structures into a typed shape carrying both the raw DER bytes (for byte-identical issuer/subject comparison when one CA always re-encodes identically) and a canonical byte-string suitable for RFC 5280 paragraph 7.1 case-folding / whitespace-normalising comparison. `namesEqual(a, b)` is the predicate `validate-chain` uses for the name-chaining check between an issuer's `subject` and a child's `issuer`. ## Features - Strict ASN.1 shape check — outer SEQUENCE, each child SET, each grand-child SEQUENCE of (OID, value). - Per-attribute value decoding via `decodeAsn1String`, supporting every typed-string variant RFC 5280 allows. - Canonical byte-string built with NFKD normalisation, lowercase fold, whitespace collapse, and SET-order sort (RFC 5280 paragraph 7.1 reference implementation). - `commonNameOf(name)` convenience for extracting the first CN attribute (used by `hasNameMatch`). ## Usage: Basic Example ```ts import { decodeName, namesEqual, commonNameOf } from "@suaveplan/x509/certificate"; import { decodeAsn1 } from "@suaveplan/x509/asn1"; const issuer = decodeName(decodeAsn1(issuerBytes)); const subject = decodeName(decodeAsn1(subjectBytes)); if (namesEqual(issuer, subject)) { console.log("self-issued"); } console.log(commonNameOf(subject)); // "example.com" ``` ## Usage: Advanced Example ```ts import { parseCertificate } from "@suaveplan/x509/certificate"; import { namesEqual } from "@suaveplan/x509/certificate"; const child = parseCertificate(childBytes); const parent = parseCertificate(parentBytes); const chained = namesEqual(child.tbs.issuer, parent.tbs.subject); ``` ## API Reference - `decodeName(node: Asn1Node): Name` — throws `X509Error(CERTIFICATE_MALFORMED)` on shape violations. - `namesEqual(a: Name, b: Name): boolean` — case-folded, whitespace-collapsed, sort-stable comparison. - `commonNameOf(name: Name): string | undefined` — returns the first CN attribute value found in any RDN. ## Implementation Notes - The canonical form is intentionally lossy — it discards attribute-encoding choice (PrintableString vs UTF8String) and SET ordering inside each RDN. This matches RFC 5280 paragraph 7.1 LDAP-style equality. - Raw DER bytes are retained alongside the canonical form so callers needing byte-identical comparison (rare; matches only when both parties use the same encoder) can use `name.raw` directly. - CN extraction uses OID `2.5.4.3`; the first CN in declaration order wins. This matches the heuristic browsers use when the SAN list is empty (legacy fallback). - Canonicalisation prepends each RDN with a `0x01` separator and inserts `0x00` between OID and value — these are non-base64 control bytes so the byte-comparison is unambiguous against any encoded attribute payload.