CRLs

The X.509 CRL producing side. pki.crl.sign builds a TBSCertList, signs it, and emits a CertificateList (RFC 5280 sec. 5) that pki.schema.crl.parse, pki.path.crlChecker, and OpenSSL all accept -- over any signature algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS), ECDSA, EdDSA, ML-DSA, SLH-DSA, and the composite (hybrid) arms. pki.crl.verify checks a CRL signature through the one path-validation signature engine, and pki.crl.isRevoked looks a serial up in a parsed CRL. Parsing lives at pki.schema.crl.parse.

pki.crl.sign

since 0.3.9 stable
pki.crl.sign(spec, issuer, opts?) -> Promise<Buffer|string>

Build, sign, and DER-encode an X.509 certificate revocation list. spec describes the CRL -- thisUpdate / nextUpdate (Dates), an optional crlNumber, a revoked array (each entry a serialNumber + revocationDate with an optional reason or invalidityDate), and an optional extensions object (authorityKeyIdentifier, issuingDistributionPoint, deltaCRLIndicator, freshestCRL, authorityInfoAccess) or an array of pre-encoded Extension DER. issuer is the signing side: { cert, key } takes the issuer DN + SPKI from a CA certificate; { name, publicKey, key } (or spec.issuer + { publicKey, key }) supplies them explicitly. The signature algorithm is resolved from the signing key, so every algorithm the toolkit signs with (RSA PKCS#1 v1.5 / PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, composite) is available without a per-algorithm branch.

The version is derived from the field set (v2 when any CRL or entry extension is present, else v1). The outer signatureAlgorithm is emitted from the same source as tbsCertList.signature (sec. 5.1.1.2); an empty revocation list omits revokedCertificates instead of emitting an empty SEQUENCE (sec. 5.1.2.6); reasonCode is an ENUMERATED and invalidityDate is always GeneralizedTime (sec. 5.3.1/5.3.2); per-extension criticality is fixed by the RFC; and the produced signature is verified under the issuer key before return. A violation throws a typed CrlError; where the spec carries raw DER (an issuer Name Buffer or a pre-encoded Extension), a malformed leaf inside those bytes throws Asn1Error.

Options

- `pem` (boolean) -- return a PEM `X509 CRL` string instead of DER.
- `pss` (boolean) -- sign an RSA key with RSASSA-PSS instead of PKCS#1 v1.5.
- `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
  var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"], subjectKeyIdentifier: true } },
    { key: signerKeyPkcs8 });
  var der = await pki.crl.sign({
    thisUpdate: new Date("2026-01-01T00:00:00Z"), nextUpdate: new Date("2026-02-01T00:00:00Z"),
    crlNumber: 7n,
    revoked: [{ serialNumber: 0x1234n, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }],
    extensions: { authorityKeyIdentifier: true },
  }, { cert: signerCertDer, key: signerKeyPkcs8 });
  pki.schema.crl.parse(der).revokedCertificates[0].serialNumberHex;   // "1234"
}
example();

References

pki.crl.verify

since 0.3.9 stable
pki.crl.verify(crl, issuer) -> Promise<boolean>

Verify a CRL's signature over its exact parsed tbsCertList bytes under the issuer public key. crl is a DER Buffer, a PEM string, or a parsed CRL; issuer is { cert } (DER/PEM/parsed), { publicKey } (SPKI DER), or a raw SPKI Buffer. Verification composes the one path-validation signature engine pki.path.crlChecker uses -- the same algorithm-confusion (RFC 9814 sec. 4 key-OID == sig-OID) and EdDSA low-order-point gates -- so there is no second, weaker CRL verifier. It fails closed to false on any resolution, import, or verification fault; malformed input throws a typed CrlError.

Given a certificate in place of a bare key, it also asks what only a certificate can answer: that the certificate is the issuer this CRL names, and that its keyUsage, when it carries one, asserts cRLSign (RFC 5280 sec. 4.2.1.3, the same rule this module's signing side already enforces). Either failing is false: a statement about the CRL, not about the caller's input, so trying each candidate issuer in turn still works. A signature verifying says only that SOME key signed these bytes; without those two questions a CRL minted under an end-entity certificate of the same CA verified as that CA's. Handed a bare SPKI there is no certificate to carry either restriction, and the signature is all that is checked. Currency and distribution-point scope remain pki.path.crlChecker.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var signerSpki = await pki.key.export(pair.publicKey);
  var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
  var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: signerSpki,
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: signerKeyPkcs8 });
  var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
    { cert: signerCertDer, key: signerKeyPkcs8 });
  var ok = await pki.crl.verify(crlDer, { publicKey: signerSpki });   // true / false
}
example();

References

pki.crl.isRevoked

since 0.3.9 stable
pki.crl.isRevoked(crl, serialNumber) -> entry | null

Look a certificate serial number up in a CRL's revokedCertificates list. crl is a DER Buffer, a PEM string, or a parsed CRL; serialNumber is a BigInt, a safe integer, a decimal / 0x-hex string, or a magnitude Buffer. Returns the matching revoked-certificate entry ({ serialNumber, serialNumberHex, revocationDate, crlEntryExtensions }) or null when the serial is not listed. A structural lookup only -- it does not verify the CRL signature or its currency; call pki.crl.verify / pki.path.crlChecker for that.

It does check scope first, because a serial number means something only within the set of certificates a CRL speaks for, and this verb is given a serial and nothing else. So a CRL that speaks for part of its issuer's certificates is refused, never answered from:

- A DELTA CRL lists changes since a base, so a serial in it may be there to say the certificate was RELEASED; read alone, the entry meaning "no longer revoked" reads as "revoked" (crl/delta-not-authoritative). Merge it with its base through pki.path.crlChecker. - An INDIRECT CRL carries entries for other issuers, whose serials are unrelated to yours (crl/indirect-not-supported), as does any CRL carrying certificateIssuer on an entry while not declaring itself indirect, a contradiction about whose certificates it lists. - Any other issuingDistributionPoint narrows the CRL to one distribution point, one kind of certificate, or a subset of revocation reasons (crl/scope-not-authoritative). Which part applies is decided against fields of the CERTIFICATE, which this verb never sees, so an absent serial is not an unrevoked certificate. pki.path.crlChecker is handed the certificate and performs the RFC 5280 sec. 6.3.3 correspondence.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
  var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: signerKeyPkcs8 });
  var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n,
    revoked: [{ serialNumber: 0x1234n, revocationDate: new Date("2026-01-15T00:00:00Z") }] },
    { cert: signerCertDer, key: signerKeyPkcs8 });
  pki.crl.isRevoked(crlDer, 0x1234n) ? "revoked" : "not listed";
}
example();

References