OCSP (Online Certificate Status Protocol) revocation checking
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
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
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
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
- spec RFC 6960
pki.ocsp.verify
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
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
- spec RFC 6960
pki.ocsp.httpRequest
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
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
- spec RFC 6960
pki.schema.ocsp.parseResponse
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
- spec RFC 6960
pki.schema.ocsp.pemDecode
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
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();