Format detection: parse DER without knowing the format first

The schema family: a declarative ASN.1 structure-schema engine and the per-format parsers built on it. Every format, from X.509 certificates and CRLs through CMS, OCSP, timestamps, and PKCS#12 stores (all() enumerates the registered set), is a member that composes the shared engine and the shared PKIX sub-schemas (AlgorithmIdentifier, Name, Extension), so a structural rule (bounds-checked positional reads, optional / tagged field ordering, SET-OF uniqueness, fail-closed typed errors) is defined once in the engine and no format can reintroduce the class of bug it prevents.

parse is the orchestrator: hand it DER (or PEM) and it detects which format the bytes encode and routes to that member's parser. Each member is also reachable directly (pki.schema.x509.parse, pki.schema.crl.parse), and all() enumerates the registered formats.

pki.schema.all

since 0.1.7 stable
pki.schema.all() -> string[]

The names of every registered format, in detection order.

Example

pki.schema.all();  // -> ["cms", "tsp", "crmf", "cmp", "csrattrs", "trustanchor", "ocsp-request", "ocsp-response", "pkcs12", "pkcs8", "csr", "attrcert", "attrcert-v1", "crl", "x509"]

References

pki.schema.parse

since 0.1.7 stable
pki.schema.parse(input, caps?) -> parsed

Detect which PKI format input (a DER Buffer or a PEM string) encodes and route to that format's parser, returning the same structured object the format's own parse returns. Throws SchemaError("schema/unknown-format") when the bytes match no registered format; the underlying decode / structural errors of the matched format propagate unchanged.

Options

- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
  defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
  above it, or an option outside this set, is refused. They bound the detection decode
  and the decode of whichever format matches, and a parse that exceeds one is refused
  with the `/too-large` code of that format.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(pair.privateKey) });
  var parsed = pki.schema.parse(der);  // cert -> the pki.schema.x509 shape
}
example();

References

pki.schema.detectFormat

since 0.3.8 stable
pki.schema.detectFormat(input) -> string | null

Detect which registered PKI format input (a DER Buffer or PEM string) encodes and return its name, one of pki.schema.all(), without parsing it, or null when the decoded bytes match no registered format. This is the detection half of pki.schema.parse, running the same authoritative FORMATS ordering, exposed for a caller (e.g. pki.inspect.any) that needs the format name instead of the parsed result. Input that does not decode as DER throws the same coercion / decode error parse throws.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(pair.privateKey) });
  pki.schema.detectFormat(der);  // "x509" | "crl" | "csr" | "cms" | ... | null
}
example();

References

pki.schema.pem.decodeBundle

since 0.8.8 stable
pki.schema.pem.decodeBundle(text, opts) -> [{ index, offset, label, der }]

Read every object in a PEM file: a fullchain.pem of a leaf and its intermediates, a ca-certificates.crt of hundreds of anchors, a file holding a key beside its certificate. Each row carries the label that named the object, the DER it decoded to, its position in the file and the offset into the text its boundary started at, so a fault in a file of hundreds names the one object it is about. offset counts into the text this verb read: a BufferSource is read as latin1, so one character is one byte and the offset is the byte offset into the file, while a string a caller decoded itself is counted in that string's own characters.

Explanatory text before, between and after the blocks is text, which is what a bundle written by a real tool carries. A label the toolkit has no parser for is a row like any other: the label is data, not a filter.

This is the verb for a file of several objects. Every other door reads one: a parse door, a pemDecode, and a verb taking a certificate or a message as PEM each refuse a file holding more than one with pem/multiple-blocks, naming how many it holds.

Fail-closed on the file rather than on the object: a boundary opened and never closed, a block closed under a different label, a body carrying RFC 1421 encryption headers, a body outside the base64 alphabet, more objects than opts.maxBlocks, and objects decoding to more than opts.maxDecodedBytes are each refused with their own pem/* code. Either cap may be tightened by a caller and neither may be raised above the toolkit's own.

Options

route            check each label's claim against the structure its bytes carry, and add
                 `format` to every row (`null` when the label names no parser). A label
                 whose claim the bytes do not support is `pem/label-structure-mismatch`.
maxBlocks        how many objects the file may hold. Default `C.LIMITS.PEM_MAX_BLOCKS`.
maxDecodedBytes  how many bytes those objects may decode to in total, across the file
                 rather than per object. Default `C.LIMITS.PEM_MAX_DECODED_BYTES`.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var cert = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(pair.privateKey) });
  var text = "Some Root CA\n============\n" + pki.schema.x509.pemEncode(cert, "CERTIFICATE");
  var rows = pki.schema.pem.decodeBundle(text);
  rows.length;           // 1, the two prose lines being text
  rows[0].label;         // "CERTIFICATE"
  rows[0].offset;        // 26, where its boundary starts
  pki.schema.x509.parse(rows[0].der);
}
example();

References

pki.schema.pem.encodeBundle

since 0.8.8 stable
pki.schema.pem.encodeBundle(objects) -> string

Write an ordered list of { label, der } objects to one PEM text, each block wrapped at 64 characters and closed under the label it was opened with. What decodeBundle reads back is the list that went in, and writing that list again produces the same text.

A label is held to the uppercase form this toolkit writes, so a label carrying a boundary cannot put a block into the file that the caller never named. The reader is wider than the writer: it takes every label RFC 7468 sec. 3 admits, including lowercase and the empty one.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var cert = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(pair.privateKey) });
  var text = pki.schema.pem.encodeBundle([{ label: "CERTIFICATE", der: cert }, { label: "CERTIFICATE", der: cert }]);
  pki.schema.pem.decodeBundle(text).length;   // 2
}
example();

References