Trust-store ingestion

Mozilla / CCADB root-program ingestion into constraint-carrying trust anchors. The bare root list (tls.rootCertificates) throws away exactly the metadata that decides WHICH roots may vouch for WHAT: the per-purpose trust bits (a root trusted for TLS is not thereby trusted for S/MIME) and the per-purpose distrust-after dates (a sunsetting root keeps validating already-issued certificates while certificates issued after the cutoff are rejected). parseCertdata reads the NSS certdata.txt object stream and parseCcadbCsv the CCADB CSV export into one identical Anchor shape, so enforcement downstream is source-agnostic; anchor() hands an entry to pki.path.validate({ trustAnchor, checkPurpose }).

Everything is fail-closed and offline: the caller supplies the text (no network fetch); every malformed or oversized input throws a typed trust/* error before the offending allocation; a certificate object and its trust object are paired by byte-exact (CKA_ISSUER, CKA_SERIAL_NUMBER) -- never by adjacency -- and cross-checked against the parsed DER, so trust metadata can never attach to the wrong root; only CKT_NSS_TRUSTED_DELEGATOR grants a purpose (everything else, including an absent bit, is untrusted).

pki.trust.parseCertdata

since 0.2.0 experimental
pki.trust.parseCertdata(text) -> { anchors }

Parse the Mozilla/NSS certdata.txt root-store object stream into constraint-carrying trust anchors. Each CKO_CERTIFICATE object's MULTILINE_OCTAL CKA_VALUE is decoded and parsed as a DER certificate; the paired CKO_NSS_TRUST object -- joined by byte-exact CKA_ISSUER + CKA_SERIAL_NUMBER, never adjacency, and cross-checked against the parsed DER -- contributes the purpose trust bits (only CKT_NSS_TRUSTED_DELEGATOR grants a purpose); the per-purpose distrust-after dates ride in the certificate object as bare ASCII times routed through the strict DER time reader. Every anchor carries the exact { name, publicKey, algorithm, parameters } shape pki.path.validate consumes plus distrustAfter, purposes, subjectDer, label, mozillaCaPolicy. A certificate with no trust object becomes an anchor trusted for nothing (never silently dropped); a trust object with no certificate grants nothing. Malformed octal, an oversized block, an unrecognized trust value, a mispaired or ambiguous-duplicate block, or an undecodable distrust-after time throws a typed trust/* error -- never a silently truncated or misattributed root.

Example

// Real input is the NSS certdata.txt read from disk; a one-root stream is
// synthesized here from a DER certificate to show the object shape.
var cert = pki.schema.x509.parse(der);
var oct = function (buf) { return Array.prototype.map.call(buf, function (b) { return "\\" + ("000" + b.toString(8)).slice(-3); }).join(""); };
var blk = function (n, v) { return n + " MULTILINE_OCTAL\n" + oct(v) + "\nEND\n"; };
var store = pki.trust.parseCertdata("BEGINDATA\n\n" +
  "CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE\n" +
  blk("CKA_ISSUER", cert.issuer.bytes) +
  blk("CKA_SERIAL_NUMBER", pki.asn1.build.integer(cert.serialNumber)) +
  blk("CKA_VALUE", der));
store.anchors[0].purposes.serverAuth;   // -> false (no trust object: trusted for nothing)

References

pki.trust.parseCcadbCsv

since 0.2.0 experimental
pki.trust.parseCcadbCsv(text) -> { anchors }

Parse a CCADB certificate-records CSV export into the SAME Anchor shape parseCertdata produces, so downstream enforcement is source-agnostic. Columns are located by header NAME -- never by position -- and unknown, reordered, or extra columns are tolerated; a MISSING required column (Common Name or Certificate Name, Trust Bits, Distrust for TLS After Date, Distrust for S/MIME After Date, PEM Info) fails closed with trust/bad-csv. Fields follow RFC 4180 quoting (embedded commas, newlines, doubled-quote escapes -- the PEM Info column depends on it). Trust Bits is set-valued (Websites -> serverAuth, Email -> emailProtection, Code -> codeSigning; absent -> untrusted). A DATE-only distrust cell expands to the end-of-day ...T23:59:59Z instant, matching the NSS ...235959Z encoding, through the same strict time reader.

Example

var csv = "Common Name or Certificate Name,Trust Bits," +
          "Distrust for TLS After Date,Distrust for S/MIME After Date,PEM Info\n" +
          'Example Root,"Websites; Email",2027.06.01,,"' + pemText + '"';
var store = pki.trust.parseCcadbCsv(csv);
store.anchors[0].purposes.serverAuth;                    // -> true
store.anchors[0].distrustAfter.serverAuth.toISOString(); // -> "2027-06-01T23:59:59.000Z"

References

pki.trust.anchor

since 0.2.0 experimental
pki.trust.anchor(entry, opts?) -> trustAnchor

Turn a parsed trust-store entry into the trustAnchor object pki.path.validate consumes: { name, publicKey, algorithm, parameters, distrustAfter, purposes } -- a straight hand-off (validate reads distrustAfter as a per-purpose map and purposes as the delegator set, selected by its own opts.checkPurpose). With opts.purpose it fail-fasts: an entry that is not a trusted delegator for that purpose throws trust/purpose-not-trusted at build time, so an operator wiring a store catches the wrong root before a single validation runs (the authoritative gate stays inside validate).

Options

purpose: string   // "serverAuth" | "emailProtection" | "codeSigning" -- fail-fast purpose check

Example

async function example() {
  var csv = "Common Name or Certificate Name,Trust Bits," +
            "Distrust for TLS After Date,Distrust for S/MIME After Date,PEM Info\n" +
            'Example Root,Websites,,,"' + pemText + '"';
  var entry = pki.trust.parseCcadbCsv(csv).anchors[0];
  var anchor = pki.trust.anchor(entry, { purpose: "serverAuth" });
  await pki.path.validate([pki.schema.x509.parse(der)], { time: new Date(), trustAnchor: anchor, checkPurpose: "serverAuth" });
}
example();

References