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

pki.ocsp.httpRequest

since 0.8.8 stable
pki.ocsp.httpRequest(der, responderUrl, opts?) -> { method, url, headers, body }

Shape a DER OCSPRequest into the HTTP request RFC 6960 Appendix A.1 defines, without opening a socket: the result is the method, the URL, the headers and the body a caller hands to its own transport.

A GET carries the request in the URL, as the base64 of the DER percent-encoded into one path segment appended to the responder URI. RFC 5019 sec. 5 requires GET when the whole encoded URL is 255 bytes or fewer, counting the scheme, the delimiters, the server name and the encoded request, and that is what is measured; anything longer is a POST carrying Content-Type: application/ocsp-request and the DER as its body. The base64 carries no CR and no LF, which that section also requires.

The encoding escapes every byte outside RFC 3986 sec. 2.3's unreserved set, so the +, / and = of base64 become triplets and a request cannot reach past the segment it was placed in. A responder URL carrying a query or a fragment is refused, since a path segment appended after either would not be part of the path.

Options

method  `"GET"` or `"POST"` to override the sec. 5 measurement. A GET asked for on a
               request too long to carry that way is refused rather than truncated.

Example

async function example() {
  var ca = await pki.key.generate("Ed25519");
  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: await pki.key.export(ca.privateKey) });
  var req = await pki.ocsp.buildRequest({ cert: caDer, issuer: caDer }, { profile: "lightweight" });
  var http = pki.ocsp.httpRequest(req, "http://ocsp.example/");
  console.log(http.method, http.url.length <= 255);
}
example();

References

pki.schema.ocsp.parseRequest

since 0.1.11 stable
pki.schema.ocsp.parseRequest(input, caps?) -> ocspRequest

Parse a DER Buffer or a PEM (OCSP REQUEST) string into a structured OCSPRequest: { tbsRequestBytes, version, requestorName, requestList, requestExtensions, optionalSignature }. tbsRequestBytes is the exact on-wire tbsRequest TLV for external signature verification; each requestList entry carries its CertID (with the two issuer hashes raw). A malformed structure throws a typed OcspError (ocsp/*) and a leaf-level codec fault surfaces as asn1/*.

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. A parse that exceeds one is
  refused with the `/too-large` code of this format.

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),
    serialNumber: 0x1332n, 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 }, {});
  var req = pki.schema.ocsp.parseRequest(der);
  req.requestList[0].certID.serialNumberHex;   // -> "1332"
}
example();

References

pki.schema.ocsp.parseResponse

since 0.1.11 stable
pki.schema.ocsp.parseResponse(input, caps?) -> ocspResponse

Parse a DER Buffer or a PEM (OCSP RESPONSE) string into a structured OCSPResponse: { responseStatus, responseBytes, basicResponse }. For a successful basic response, basicResponse carries { tbsResponseDataBytes, responderID, producedAt, responses, signatureAlgorithm, signature, certs, certsPresent }; each responses[i].certStatus is { type: "good" | "revoked" | "unknown" }. certs is the embedded certificate list, empty when the field is absent, and certsPresent says whether the response carried the field at all. A non-successful status carries no responseBytes. A malformed structure throws a typed OcspError (ocsp/*); an unsupported responseType throws ocsp/unsupported-response-type.

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. A parse that exceeds one is
  refused with the `/too-large` code of this format.

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.sign({ responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
    { cert: caDer, key: caKey });
  var res = pki.schema.ocsp.parseResponse(der);
  res.responseStatus.name;                       // -> "successful"
  res.basicResponse.responses[0].certStatus.type; // -> "good" | "revoked" | "unknown"
}
example();

References

pki.schema.ocsp.pemDecode

since 0.1.11 stable
pki.schema.ocsp.pemDecode(text, label?) -> Buffer

Extract the DER bytes from a PEM OCSP block (default label OCSP RESPONSE; pass "OCSP REQUEST" for a request). Throws PemError on a missing / mismatched envelope or a non-base64 body.

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 reqDer = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, {});
  var pemText = pki.schema.ocsp.pemEncode(reqDer, "OCSP REQUEST");
  var der = pki.schema.ocsp.pemDecode(pemText, "OCSP REQUEST");
}
example();

References

pki.schema.ocsp.pemEncode

since 0.1.23 stable
pki.schema.ocsp.pemEncode(der, label?) -> string

Wrap DER bytes in a PEM envelope (default label OCSP RESPONSE, the same default pemDecode reads; pass "OCSP REQUEST" for a request). Throws PemError on a malformed label.

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.sign({ responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
    { cert: caDer, key: caKey });
  var pem = pki.schema.ocsp.pemEncode(der);
}
example();

References