OCSP (Online Certificate Status Protocol) revocation checking

The producing + client-facing half of RFC 6960 Online Certificate Status Protocol: build a status request over a certificate (pki.ocsp.buildRequest), build and sign a status response as an authorized responder (pki.ocsp.sign), emit an unsigned error response (pki.ocsp.buildErrorResponse), and verify a returned response as a relying party (pki.ocsp.verify). Parsing lives in pki.schema.ocsp; revocation during path validation is pki.path.ocspChecker. Signing rides the shared sign-scheme registry (the same classical + post-quantum set pki.cms.sign uses), so a response is signed under RSA / ECDSA / EdDSA / ML-DSA / SLH-DSA per the responder key. Verification composes the same hardened responder- authorization, signature and currency gates pki.path.ocspChecker runs; there is no weaker second verify path. Fail-closed: verify returns a "unknown" verdict (never a silent accept) for any unmet gate, with one scoped exception: a request-nonce mismatch downgrades only a good to "unknown", leaving a signed, current, authorized revoked reported as revoked with nonceMatched: false (see verify). Malformed input throws a typed OcspError.

pki.ocsp.buildRequest

since 0.2.22 stable
pki.ocsp.buildRequest(query, opts?) -> Buffer | string

Build an OCSPRequest for the status of one or more certificates. query is a { cert, issuer } pair (or an array of them), each certificate given parsed or as DER/PEM; the CertID is derived by hashing the issuer name and key under opts.hashAlgorithm (SHA-1 by default, the RFC 5019 interop choice). The version DEFAULT (v1) is omitted from the DER. Returns the request DER, or PEM when opts.pem is set.

Options

hashAlgorithm  `"sha1"` (default) / `"sha256"` / `"sha384"` / `"sha512"`; the CertID identity hash.
nonce          `true` for a fresh 32-octet CSPRNG nonce (RFC 9654), or a caller Buffer of 32..128
               octets (RFC 9654 sec. 2.1: a requester MUST use at least 32).
requestorName  a Name (DER `BufferSource`, or a parsed Name) placed in the [1] requestorName as a
               directoryName. With `signer`, the signer certificate's subject is taken when none
               is stated (RFC 6960 sec. 4.1.2: a requestor that signs SHALL specify its name).
signer         `{ cert, key }` to sign the request.
profile        `"lightweight"`: one Request, SHA-1 CertID, nonce-only extensions (RFC 5019).
pem            emit a PEM `OCSP REQUEST` string instead of DER.

Example

async function example() {
  var ca = await pki.key.generate("Ed25519");
  var caKey = await pki.key.export(ca.privateKey);
  var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } }, { key: caKey });
  var leaf = await pki.key.generate("Ed25519");
  var leafDer = await pki.x509.sign({ subject: "leaf.example", subjectPublicKey: await pki.key.export(leaf.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { cert: caDer, key: caKey });
  var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, { nonce: true });
}
example();

References

pki.ocsp.sign

since 0.2.22 stable
pki.ocsp.sign(responseData, responder, opts?) -> Promise<Buffer | string>

Build and sign a successful OCSPResponse wrapping a BasicOCSPResponse. responseData names the responderID, an optional producedAt, and one or more per-certificate responses; responder is the { cert, key } signing the response (the issuing CA directly, or a CA-issued delegate bearing id-kp-OCSPSigning + id-pkix-ocsp-nocheck). The signature is computed over the exact ResponseData DER (RFC 6960 sec. 4.2.1: no CMS wrapper, no signed attributes). The responder certificate is embedded in certs [0] so a relying party can find it. Returns the response DER, or PEM.

Each response may carry singleExtensions, as an object or as an array of pre-encoded Extension DER. The object form takes archiveCutoff, the Date before which the responder no longer holds status (RFC 6960 sec. 4.4.4), and crlReferences, a { crlUrl, crlNum, crlTime } naming the CRL the status was drawn from (sec. 4.4.2), any subset of the three. A pre-encoded entry is read with the parser's own singleExtensions reader before it is emitted, so a response this verb signs is one pki.schema.ocsp.parseResponse reads back, and a malformed archiveCutoff or CrlID is refused here with the code the parser would have raised. A pre-encoded entry is also held to where RFC 6960 places it: the request-only extensions (sec. 4.4.3, 4.4.6, 4.4.7) and the two a response carries in responseExtensions alone (the extended revoked definition, sec. 4.4.8, and the RFC 9654 nonce) are refused in singleExtensions, and a CRL entry extension carried here (sec. 4.4.5) keeps the criticality RFC 5280 sec. 5.3 fixes for it.

A "revoked" status for a non-issued certificate (revocationReason certificateHold at 1970-01-01T00:00:00Z, sec. 2.2) places the extended revoked definition in responseExtensions whether or not opts.extendedRevoke names it, refuses extendedRevoke: false, and refuses CRL references or any CRL entry extension on that SingleResponse.

Options

nonce           a request nonce Buffer to echo back in responseExtensions (RFC 9654).
extendedRevoke  emit the id-pkix-ocsp-extended-revoke extension (RFC 6960 sec. 4.4.8); implied
                by a non-issued response, where `false` is refused.
embedCert       `false` to omit certs [0] (a direct-CA response the client already trusts).
pem             emit a PEM `OCSP RESPONSE` string instead of DER.

Example

async function example() {
  var ca = await pki.key.generate("Ed25519");
  var responderPkcs8 = await pki.key.export(ca.privateKey);
  // the issuing CA responds directly here, so its own certificate is the responder's
  var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } },
    { key: responderPkcs8 });
  var responderCertDer = caDer;
  var leaf = await pki.key.generate("Ed25519");
  var leafDer = await pki.x509.sign({ subject: "leaf.example", subjectPublicKey: await pki.key.export(leaf.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { cert: caDer, key: responderPkcs8 });
  var resp = await pki.ocsp.sign(
    { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
    { cert: responderCertDer, key: responderPkcs8 });
}
example();

References

pki.ocsp.buildErrorResponse

since 0.2.22 stable
pki.ocsp.buildErrorResponse(status) -> Buffer

Build an UNSIGNED error OCSPResponse, one of malformedRequest / internalError / tryLater / sigRequired / unauthorized, carrying only the responseStatus and no responseBytes (RFC 6960 sec. 2.3: an error message conveys no certificate status and is not signed).

Example

var der = pki.ocsp.buildErrorResponse("tryLater");

References

pki.ocsp.verify

since 0.2.22 stable
pki.ocsp.verify(response, opts) -> Promise<{ valid, status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, nonceMatched?, reason }>

Verify a returned OCSP response as a relying party (RFC 6960 sec. 3.2 client acceptance). Resolves an AUTHORIZED responder (the issuing CA directly, or a CA-issued delegate bearing id-kp-OCSPSigning + id-pkix-ocsp-nocheck), verifies the signature over tbsResponseData, matches the CertID triple to the target certificate under the CertID's own hashAlgorithm, checks currency (thisUpdate/nextUpdate), and, when opts.requestNonce is supplied, confirms the response nonce echoes it. This runs the same hardened gates pki.path.ocspChecker does. Fail-closed: an unauthorized, stale, or CertID-mismatched response is a "unknown" verdict (never a silent accept); a malformed response's parse fault surfaces as the parser's ocsp/* / asn1/*.

valid is the toolkit-wide verdict alias the other verify verbs carry, and it is true only when status is "good". Because every failure this verb detects already collapses to "unknown", valid states exactly what status === "good" does and hides no unrun check. A revoked certificate is valid: false; which of the two it was stays in status.

The request-nonce check is reported, and downgrades good alone. Every verdict carries nonceMatched (true / false / null when the client sent no nonce). An unmatched nonce turns a good into "unknown", because a response that is not an answer to this request cannot be relied on to say the certificate is still fine. It does not touch revoked: revocation does not go stale the way non-revocation does, so discarding a signed, current, authorized revoked because it was replayed would hand a soft-failing caller the very certificate the responder refused. A replayed revoked is therefore reported as revoked with nonceMatched: false.

Options

cert            the target certificate (parsed, DER, or PEM) -- REQUIRED.
issuer          its issuer certificate (parsed, DER, or PEM) -- REQUIRED.
time            the validation instant (default: now).
requestNonce    the nonce the client sent; when given, the response MUST echo it (constant-time).
historicalMode  defer a strictly-future revocation (report good) instead of revoking on skew.

Example

async function example() {
  var ca = await pki.key.generate("Ed25519");
  var caKey = await pki.key.export(ca.privateKey);
  var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } },
    { key: caKey });
  var leaf = await pki.key.generate("Ed25519");
  var leafDer = await pki.x509.sign({ subject: "leaf.example", subjectPublicKey: await pki.key.export(leaf.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { cert: caDer, key: caKey });
  var responseDer = await pki.ocsp.sign(
    { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
    { cert: caDer, key: caKey });
  var res = await pki.ocsp.verify(responseDer, { cert: leafDer, issuer: caDer });
  res.status;   // "good" | "revoked" | "unknown"
}
example();

References

pki.ocsp.verifyRequest

since 0.6.0 stable
pki.ocsp.verifyRequest(request, opts?) -> Promise<{ valid, signed, signatureValid, requestorNamed, signerCert, signerCerts, certs, signerSubject, requestorName, requestList, requestExtensions, version, reason }>

The responder-side counterpart to pki.ocsp.buildRequest: verify a client's signed OCSP request (RFC 6960 sec. 4.1.1). Signing a request is OPTIONAL, so an unsigned request is not an error; it is reported with signed: false and authenticates no requestor. For a signed request the requestor's signature over the exact tbsRequest is checked under the public key of the requestor certificate the request carries, through the same certification-path signature engine pki.ocsp.verify uses for a response (with its EdDSA low-order-point and algorithm-confusion gates). request is DER Buffer / Uint8Array or a PEM OCSP REQUEST string, parsed from bytes so the signature, its algorithm and the covered tbsRequest are one byte string and cannot be split.

signatureValid means only that a certificate's key made this signature over this request. It says nothing about whether that certificate is trusted: signerCerts is every certificate the request carries whose key verified the signature AND whose subject is the requestorName the request states (RFC 6960 sec. 4.1.1 certs is unordered, and a key may appear under an expired certificate beside its renewal; a same-key certificate under another name is not the requestor), so the responder builds a trusted path to one of them rather than depend on ordering. signerCert and the decoded signerSubject are the first of those, the common single-signer case; when no verifying certificate carries the name, they fall back to the verifying ones and valid is false. certs is every parseable certificate the request carried (or opts.certs supplied), including intermediates that do not sign and so are absent from signerCerts: the responder passes it as the opts.candidates pool to pki.path.build, which discovers and validates the ordered path from a signerCerts entry (leaf) up to a trust anchor. The responder then confirms that certificate is authorized to sign the request: its keyUsage, where present, must assert digitalSignature (RFC 5280 sec. 4.2.1.3, since the certificate signed the request). The requestorName a signed request carries (RFC 6960 sec. 4.1.2: a requestor that signs SHALL specify its name) is held to the subject of a certificate that verified the signature, compared as a distinguished name (RFC 5280 sec. 7.1): requestorNamed is true when it is, valid requires it, and a name given as a GeneralName form other than directoryName is not compared and never passes. The decoded requestorName (with its value for a directoryName), requestList, requestExtensions and version are returned so the CertIDs being asked about need no re-parse.

Options

- `certs`  An array of certificates (DER `Buffer` / `Uint8Array` or PEM strings) supplying the
           requestor certificate when the request omits its own `certs` (RFC 6960 lets them
           travel out of band); every entry whose key verifies is surfaced in `signerCerts`,
           and is ignored when the request embeds its own certificates.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var key = await pki.key.export(pair.privateKey), spki = await pki.key.export(pair.publicKey);
  var caDer = await pki.x509.sign({ subject: "OCSP CA", subjectPublicKey: spki,
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2027-01-01T00:00:00Z") }, { key: key });
  var request = await pki.ocsp.buildRequest({ cert: caDer, issuer: caDer },
    { signer: { cert: caDer, key: key }, requestorName: pki.schema.x509.parse(caDer).subject.bytes });
  var v = await pki.ocsp.verifyRequest(request);
  // v.signed && v.signatureValid -> for a signerCerts entry, pki.path.build(entry, { candidates:
  // v.certs, trustAnchors, time }) discovers and validates the path (several signerCerts may share
  // the key, e.g. an expired one beside its renewal); confirm that entry's keyUsage asserts
  // digitalSignature, then bind its subject to the requestor you expect before honoring v.requestList.
}
example();

References