@blamejs/pki 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ocsp.js ADDED
@@ -0,0 +1,378 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.ocsp
6
+ * @nav Revocation
7
+ * @title OCSP
8
+ * @intro The producing + client-facing half of RFC 6960 Online Certificate Status Protocol:
9
+ * build a status request over a certificate (`pki.ocsp.buildRequest`), build and sign a status
10
+ * response as an authorized responder (`pki.ocsp.sign`), emit an unsigned error response
11
+ * (`pki.ocsp.buildErrorResponse`), and verify a returned response as a relying party
12
+ * (`pki.ocsp.verify`). Parsing lives in `pki.schema.ocsp`; revocation during path validation is
13
+ * `pki.path.ocspChecker`. Signing rides the shared sign-scheme registry (the same classical +
14
+ * post-quantum set `pki.cms.sign` uses), so a response is signed under RSA / ECDSA / EdDSA /
15
+ * ML-DSA / SLH-DSA per the responder key. Verification composes the SAME hardened responder-
16
+ * authorization + signature + currency gates `pki.path.ocspChecker` runs -- there is no weaker
17
+ * second verify path. Fail-closed: `verify` returns a `"unknown"` verdict (never a silent accept)
18
+ * for any unmet gate; malformed input throws a typed `OcspError`.
19
+ * @spec RFC 6960, RFC 9654, RFC 5019
20
+ * @card Build, sign, and verify RFC 6960 OCSP requests + responses (a responder + a relying party).
21
+ */
22
+
23
+ var nodeCrypto = require("crypto");
24
+ var asn1 = require("./asn1-der");
25
+ var oid = require("./oid");
26
+ var x509 = require("./schema-x509");
27
+ var ocspSchema = require("./schema-ocsp");
28
+ var pathValidate = require("./path-validate");
29
+ var signScheme = require("./sign-scheme");
30
+ var ocspVerify = require("./ocsp-verify");
31
+ var webcrypto = require("./webcrypto");
32
+ var subtle = webcrypto.webcrypto.subtle;
33
+ var guard = require("./guard-all");
34
+ var constants = require("./constants");
35
+ var frameworkError = require("./framework-error");
36
+
37
+ var OcspError = frameworkError.OcspError;
38
+ var b = asn1.build;
39
+ function O(name) { return oid.byName(name); }
40
+ function _err(code, message, cause) { return new OcspError(code, message, cause); }
41
+ // The domain error factory the shared sign-scheme resolver/signer throws through (kind ->
42
+ // ocsp/<kind>), so its faults keep the ocsp/* codes.
43
+ function _signE(kind, message, cause) { return new OcspError("ocsp/" + kind, message, cause); }
44
+
45
+ // CertID / responder-ID hash algorithm name -> WebCrypto digest name. SHA-1 is the RFC 5019 interop
46
+ // default for the CertID IDENTITY hash (not a signature; collision resistance is irrelevant to the
47
+ // lookup, so SHAttered does not bar it -- the deliberate split path-validate makes too).
48
+ var HASH_WC = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
49
+ // A CRLReason name -> its enumerated value (RFC 5280 sec. 5.3.1), reverse of pki.C.NAMES.CRL_REASON.
50
+ var REASON_CODE = {};
51
+ Object.keys(constants.NAMES.CRL_REASON).forEach(function (k) { REASON_CODE[constants.NAMES.CRL_REASON[k]] = parseInt(k, 10); });
52
+ var OID_OCSP_NONCE = O("ocspNonce");
53
+ var OID_OCSP_BASIC = O("ocspBasic");
54
+ var OID_EXTENDED_REVOKE = O("ocspExtendedRevoke");
55
+
56
+ function _digest(wcHash, buf) { return subtle.digest(wcHash, buf).then(function (h) { return Buffer.from(h); }); }
57
+ function _certOf(arg, what) {
58
+ if (arg && arg.subjectPublicKeyInfo && arg.tbsBytes) return arg; // already parsed
59
+ var der;
60
+ if (Buffer.isBuffer(arg)) der = arg;
61
+ else if (arg instanceof Uint8Array) der = Buffer.from(arg);
62
+ else if (typeof arg === "string") { try { der = x509.pemDecode(arg); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
63
+ else throw _err("ocsp/bad-input", (what || "a certificate") + " must be a parsed certificate, a DER Buffer, or a PEM string");
64
+ try { return x509.parse(der); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " is not a well-formed X.509 certificate", e); }
65
+ }
66
+ function _toDer(input, what) {
67
+ if (Buffer.isBuffer(input)) return input;
68
+ if (input instanceof Uint8Array) return Buffer.from(input);
69
+ if (typeof input === "string") { try { return ocspSchema.pemDecode(input); } catch (e) { throw _err("ocsp/bad-input", (what || "input") + " PEM could not be decoded", e); } }
70
+ throw _err("ocsp/bad-input", (what || "input") + " must be a DER Buffer, Uint8Array, or PEM string");
71
+ }
72
+ // The subjectPublicKey BIT STRING value an issuerKeyHash / byKey KeyHash hashes over.
73
+ function _keyValue(spkiDer) { return ocspVerify.ocspKeyValue(spkiDer); }
74
+ // A non-critical id-pkix-ocsp-nonce Extension carrying the raw nonce (RFC 9654): the extnValue
75
+ // OCTET STRING wraps the Nonce ::= OCTET STRING.
76
+ function _nonceExt(nonceBytes) { return b.sequence([b.oid(OID_OCSP_NONCE), b.octetString(b.octetString(nonceBytes))]); }
77
+ function _assertNonce(nonceBytes) {
78
+ if (!Buffer.isBuffer(nonceBytes) || nonceBytes.length < 1 || nonceBytes.length > 128) {
79
+ throw _err("ocsp/bad-input", "a nonce must be a Buffer of 1..128 octets (RFC 9654 sec. 2.1)");
80
+ }
81
+ }
82
+ // The CertID SEQUENCE for target `cert` under `issuer`, hashing under `hashName` (RFC 6960 sec. 4.1.1).
83
+ function _buildCertID(cert, issuer, hashName) {
84
+ var wc = HASH_WC[hashName];
85
+ if (!wc) throw _err("ocsp/bad-input", "unsupported CertID hashAlgorithm " + JSON.stringify(hashName) + " (sha1 / sha256 / sha384 / sha512)");
86
+ return Promise.all([
87
+ _digest(wc, issuer.subject.bytes),
88
+ _digest(wc, _keyValue(issuer.subjectPublicKeyInfo.bytes)),
89
+ ]).then(function (hashes) {
90
+ // SHA-1 carries NULL parameters (RFC 3279); a SHA-2 digest AlgorithmIdentifier omits them
91
+ // (RFC 5754 sec. 2). The CertID matcher keys on the OID and ignores parameters either way.
92
+ var hashAlgId = hashName === "sha1" ? b.sequence([b.oid(O(hashName)), b.nullValue()]) : b.sequence([b.oid(O(hashName))]);
93
+ return b.sequence([
94
+ hashAlgId,
95
+ b.octetString(hashes[0]),
96
+ b.octetString(hashes[1]),
97
+ b.integer(cert.serialNumber),
98
+ ]);
99
+ });
100
+ }
101
+
102
+ /**
103
+ * @primitive pki.ocsp.buildRequest
104
+ * @signature pki.ocsp.buildRequest(query, opts?) -> Buffer | string
105
+ * @since 0.2.22
106
+ * @status experimental
107
+ * @spec RFC 6960, RFC 9654, RFC 5019
108
+ * @related pki.ocsp.verify, pki.schema.ocsp.parseRequest
109
+ *
110
+ * Build an OCSPRequest for the status of one or more certificates. `query` is a `{ cert, issuer }`
111
+ * pair (or an array of them), each certificate given parsed or as DER/PEM; the CertID is derived by
112
+ * hashing the issuer name and key under `opts.hashAlgorithm` (SHA-1 by default, the RFC 5019 interop
113
+ * choice). The version DEFAULT (v1) is omitted from the DER. Returns the request DER, or PEM when
114
+ * `opts.pem` is set.
115
+ *
116
+ * @opts
117
+ * hashAlgorithm `"sha1"` (default) / `"sha256"` / `"sha384"` / `"sha512"` -- the CertID identity hash.
118
+ * nonce `true` for a fresh 32-octet CSPRNG nonce (RFC 9654), or a caller Buffer (1..128 octets).
119
+ * requestorName a Name (RDN array) placed in the [1] requestorName as a directoryName.
120
+ * signer `{ cert, key }` to sign the request (requires requestorName).
121
+ * profile `"lightweight"` -- one Request, SHA-1 CertID, nonce-only extensions (RFC 5019).
122
+ * pem emit a PEM `OCSP REQUEST` string instead of DER.
123
+ * @example
124
+ * var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, { nonce: true });
125
+ */
126
+ function buildRequest(query, opts) {
127
+ opts = opts || {};
128
+ var lightweight = opts.profile === "lightweight";
129
+ var hashName = opts.hashAlgorithm || "sha1";
130
+ if (lightweight && hashName !== "sha1") throw _err("ocsp/bad-input", "the lightweight profile requires a SHA-1 CertID (RFC 5019 sec. 2.1.1)");
131
+ var queries = Array.isArray(query) ? query : [query];
132
+ if (!queries.length) throw _err("ocsp/bad-input", "buildRequest needs at least one { cert, issuer } query");
133
+ if (lightweight && queries.length !== 1) throw _err("ocsp/bad-input", "the lightweight profile permits exactly one Request (RFC 5019 sec. 2.1.1)");
134
+ if (opts.signer && !opts.requestorName) throw _err("ocsp/bad-input", "a signed OCSP request MUST carry requestorName (RFC 6960 sec. 4.1.2)");
135
+
136
+ return Promise.all(queries.map(function (q) {
137
+ if (!q || q.cert == null || q.issuer == null) throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
138
+ var cert = _certOf(q.cert, "the query certificate");
139
+ var issuer = _certOf(q.issuer, "the issuer certificate");
140
+ return _buildCertID(cert, issuer, hashName).then(function (certID) {
141
+ var reqChildren = [certID];
142
+ if (q.singleRequestExtensions && q.singleRequestExtensions.length) {
143
+ if (lightweight) throw _err("ocsp/bad-input", "the lightweight profile permits no singleRequestExtensions (RFC 5019 sec. 2.1.1)");
144
+ reqChildren.push(b.explicit(0, b.sequence(q.singleRequestExtensions)));
145
+ }
146
+ return b.sequence(reqChildren);
147
+ });
148
+ })).then(function (requests) {
149
+ var tbsChildren = [];
150
+ if (opts.requestorName) {
151
+ // requestorName [1] EXPLICIT GeneralName; a Name is carried as directoryName [4].
152
+ var nameDer = _nameDer(opts.requestorName);
153
+ tbsChildren.push(b.explicit(1, b.contextConstructed(4, nameDer)));
154
+ }
155
+ tbsChildren.push(b.sequence(requests));
156
+ var reqExts = [];
157
+ if (opts.nonce) {
158
+ var nonceBytes = opts.nonce === true ? nodeCrypto.randomBytes(32) : opts.nonce; // >= 32 octets, CSPRNG (RFC 9654 sec. 2.1)
159
+ _assertNonce(nonceBytes);
160
+ reqExts.push(_nonceExt(nonceBytes));
161
+ }
162
+ if (reqExts.length) tbsChildren.push(b.explicit(2, b.sequence(reqExts)));
163
+ var tbsRequest = b.sequence(tbsChildren);
164
+ if (!opts.signer) return _emitReq(b.sequence([tbsRequest]), opts);
165
+ var signerCertDer = _normCertDer(opts.signer.cert, "the request signer certificate");
166
+ var signerCert = _certOf(signerCertDer, "the request signer certificate");
167
+ var scheme = signScheme.resolveSignScheme(signerCert, { combinedRsaSig: true }, true, _signE);
168
+ return signScheme.signOverTbs(scheme, opts.signer.key, tbsRequest, _signE).then(function (sig) {
169
+ var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
170
+ return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
171
+ });
172
+ });
173
+ }
174
+ function _emitReq(der, opts) { return opts.pem ? ocspSchema.pemEncode(der, "OCSP REQUEST") : der; }
175
+ function _nameDer(name) {
176
+ if (Buffer.isBuffer(name)) return name;
177
+ if (name && name.bytes) return name.bytes; // a parsed Name
178
+ throw _err("ocsp/bad-input", "requestorName must be a DER Name Buffer or a parsed Name");
179
+ }
180
+ // The RAW DER of a certificate that will be embedded verbatim (the responder cert in a
181
+ // BasicOCSPResponse, the signer cert in a signed OCSPRequest). It must be supplied as bytes:
182
+ // a parsed certificate does not retain its full DER encoding, and re-encoding it would risk
183
+ // byte drift from the signed original, so a parsed cert is rejected rather than reconstructed.
184
+ function _normCertDer(cert, what) {
185
+ if (Buffer.isBuffer(cert)) return cert;
186
+ if (cert instanceof Uint8Array) return Buffer.from(cert);
187
+ if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
188
+ if (cert && cert.tbsBytes && cert.subjectPublicKeyInfo) {
189
+ throw _err("ocsp/bad-input", (what || "a certificate") + " to embed must be supplied as DER bytes or a PEM string, not a parsed certificate (the parser does not retain the full DER encoding needed to embed it verbatim)");
190
+ }
191
+ throw _err("ocsp/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
192
+ }
193
+
194
+ /**
195
+ * @primitive pki.ocsp.sign
196
+ * @signature pki.ocsp.sign(responseData, responder, opts?) -> Promise<Buffer | string>
197
+ * @since 0.2.22
198
+ * @status experimental
199
+ * @spec RFC 6960, RFC 9654
200
+ * @related pki.ocsp.verify, pki.ocsp.buildErrorResponse
201
+ *
202
+ * Build and sign a `successful` OCSPResponse wrapping a BasicOCSPResponse. `responseData` names the
203
+ * responderID, an optional producedAt, and one or more per-certificate responses; `responder` is the
204
+ * `{ cert, key }` signing the response (the issuing CA directly, or a CA-issued delegate bearing
205
+ * id-kp-OCSPSigning + id-pkix-ocsp-nocheck). The signature is computed over the exact ResponseData
206
+ * DER (RFC 6960 sec. 4.2.1 -- no CMS wrapper, no signed attributes). The responder certificate is
207
+ * embedded in `certs [0]` so a relying party can find it. Returns the response DER, or PEM.
208
+ *
209
+ * @opts
210
+ * nonce a request nonce Buffer to echo back in responseExtensions (RFC 9654).
211
+ * extendedRevoke emit the id-pkix-ocsp-extended-revoke extension (RFC 6960 sec. 4.4.8).
212
+ * embedCert `false` to omit certs [0] (a direct-CA response the client already trusts).
213
+ * pem emit a PEM `OCSP RESPONSE` string instead of DER.
214
+ * @example
215
+ * var resp = await pki.ocsp.sign(
216
+ * { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
217
+ * { cert: responderCertDer, key: responderPkcs8 });
218
+ */
219
+ function sign(responseData, responder, opts) {
220
+ opts = opts || {};
221
+ responseData = responseData || {};
222
+ if (!responder || responder.cert == null || responder.key == null) throw _err("ocsp/bad-input", "a responder must be { cert, key }");
223
+ var respCertDer = _normCertDer(responder.cert, "the responder certificate");
224
+ var respCert = _certOf(respCertDer, "the responder certificate");
225
+ var responses = responseData.responses || [];
226
+ if (!responses.length) throw _err("ocsp/bad-input", "a response MUST include at least one SingleResponse (RFC 6960 sec. 4.2.1)");
227
+
228
+ return _responderID(responseData.responderID, respCert).then(function (ridNode) {
229
+ return Promise.all(responses.map(function (r) { return _buildSingleResponse(r, opts); })).then(function (srNodes) {
230
+ var rdChildren = [ridNode, b.generalizedTime(_asDate(responseData.producedAt) || new Date()), b.sequence(srNodes)];
231
+ var respExts = [];
232
+ if (opts.nonce != null) { _assertNonce(opts.nonce); respExts.push(_nonceExt(opts.nonce)); }
233
+ if (opts.extendedRevoke) respExts.push(b.sequence([b.oid(OID_EXTENDED_REVOKE), b.octetString(b.nullValue())]));
234
+ if (respExts.length) rdChildren.push(b.explicit(1, b.sequence(respExts)));
235
+ var responseDataDer = b.sequence(rdChildren);
236
+ var scheme = signScheme.resolveSignScheme(respCert, { combinedRsaSig: true }, true, _signE);
237
+ return signScheme.signOverTbs(scheme, responder.key, responseDataDer, _signE).then(function (sig) {
238
+ var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
239
+ if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
240
+ var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
241
+ var der = b.sequence([b.enumerated(0n), b.explicit(0, responseBytes)]);
242
+ return opts.pem ? ocspSchema.pemEncode(der, "OCSP RESPONSE") : der;
243
+ });
244
+ });
245
+ });
246
+ }
247
+ // A date value -> Date, or null when absent (the caller defaults). An unparseable value is a
248
+ // config-time error, not a silently-emitted Invalid Date: a NaN date would encode as a malformed
249
+ // GeneralizedTime in a SIGNED response, and compare false against every currency bound on verify.
250
+ function _asDate(d) {
251
+ if (d == null) return null;
252
+ var dt = d instanceof Date ? d : new Date(d);
253
+ if (isNaN(dt.getTime())) throw _err("ocsp/bad-input", "an invalid date value " + JSON.stringify(d));
254
+ return dt;
255
+ }
256
+ function _responderID(rid, respCert) {
257
+ if (rid == null || rid === "byName") return Promise.resolve(b.explicit(1, b.raw(respCert.subject.bytes))); // byName [1] EXPLICIT Name
258
+ if (rid === "byKey") return _digest("SHA-1", _keyValue(respCert.subjectPublicKeyInfo.bytes)).then(function (kh) { return b.explicit(2, b.octetString(kh)); });
259
+ throw _err("ocsp/bad-input", "responderID must be \"byName\" or \"byKey\"");
260
+ }
261
+ function _buildSingleResponse(r, opts) {
262
+ r = r || {};
263
+ var certIdP;
264
+ if (r.certID != null) certIdP = Promise.resolve(b.raw(Buffer.isBuffer(r.certID) ? r.certID : Buffer.from(r.certID)));
265
+ else if (r.cert != null && r.issuer != null) certIdP = _buildCertID(_certOf(r.cert, "a response certificate"), _certOf(r.issuer, "a response issuer"), r.hashAlgorithm || "sha1");
266
+ else return Promise.reject(_err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
267
+ return certIdP.then(function (certID) {
268
+ var statusNode = _certStatusNode(r.status, opts);
269
+ var srChildren = [certID, statusNode, b.generalizedTime(_asDate(r.thisUpdate) || new Date())];
270
+ if (r.nextUpdate !== null) srChildren.push(b.explicit(0, b.generalizedTime(_asDate(r.nextUpdate) || _defaultNextUpdate())));
271
+ if (r.singleExtensions && r.singleExtensions.length) srChildren.push(b.explicit(1, b.sequence(r.singleExtensions)));
272
+ return b.sequence(srChildren);
273
+ });
274
+ }
275
+ function _defaultNextUpdate() { return new Date(Date.now() + constants.TIME.days(7)); }
276
+ function _certStatusNode(status, opts) {
277
+ if (status == null || status === "good") return b.contextPrimitive(0, Buffer.alloc(0)); // good [0] IMPLICIT NULL
278
+ if (status === "unknown") return b.contextPrimitive(2, Buffer.alloc(0)); // unknown [2] IMPLICIT NULL
279
+ if (typeof status === "object" && status.revoked != null) {
280
+ var ri = [b.generalizedTime(_asDate(status.revoked))]; // revoked != null is guaranteed above
281
+ if (status.revocationReason != null) {
282
+ var code = typeof status.revocationReason === "number" ? status.revocationReason : REASON_CODE[status.revocationReason];
283
+ if (code == null) throw _err("ocsp/bad-input", "unknown revocationReason " + JSON.stringify(status.revocationReason));
284
+ ri.push(b.explicit(0, b.enumerated(BigInt(code)))); // revocationReason [0] EXPLICIT CRLReason
285
+ }
286
+ return b.contextConstructed(1, Buffer.concat(ri)); // revoked [1] IMPLICIT RevokedInfo
287
+ }
288
+ throw _err("ocsp/bad-input", "a response status must be \"good\", \"unknown\", or { revoked: <Date>, revocationReason? }");
289
+ }
290
+
291
+ /**
292
+ * @primitive pki.ocsp.buildErrorResponse
293
+ * @signature pki.ocsp.buildErrorResponse(status) -> Buffer | string
294
+ * @since 0.2.22
295
+ * @status experimental
296
+ * @spec RFC 6960
297
+ * @related pki.ocsp.sign
298
+ *
299
+ * Build an UNSIGNED error OCSPResponse -- `malformedRequest` / `internalError` / `tryLater` /
300
+ * `sigRequired` / `unauthorized` -- carrying only the responseStatus and no responseBytes (RFC 6960
301
+ * sec. 2.3: an error message conveys no certificate status and is not signed).
302
+ *
303
+ * @example
304
+ * var der = pki.ocsp.buildErrorResponse("tryLater");
305
+ */
306
+ var ERROR_STATUS = { malformedRequest: 1, internalError: 2, tryLater: 3, sigRequired: 5, unauthorized: 6 };
307
+ function buildErrorResponse(status) {
308
+ var code = ERROR_STATUS[status];
309
+ if (code == null) throw _err("ocsp/bad-input", "an error responseStatus must be one of " + Object.keys(ERROR_STATUS).join(" / "));
310
+ return b.sequence([b.enumerated(BigInt(code))]);
311
+ }
312
+
313
+ /**
314
+ * @primitive pki.ocsp.verify
315
+ * @signature pki.ocsp.verify(response, opts) -> Promise<{ status, responderAuthorized, signatureValid, thisUpdate, nextUpdate, revocationReason?, nonceMatched?, reason }>
316
+ * @since 0.2.22
317
+ * @status experimental
318
+ * @spec RFC 6960, RFC 9654, RFC 5019
319
+ * @related pki.path.ocspChecker, pki.ocsp.buildRequest
320
+ *
321
+ * Verify a returned OCSP response as a relying party (RFC 6960 sec. 3.2 client acceptance). Resolves
322
+ * an AUTHORIZED responder (the issuing CA directly, or a CA-issued delegate bearing id-kp-OCSPSigning
323
+ * + id-pkix-ocsp-nocheck), verifies the signature over `tbsResponseData`, matches the CertID triple
324
+ * to the target certificate under the CertID's own hashAlgorithm, checks currency
325
+ * (`thisUpdate`/`nextUpdate`), and -- when `opts.requestNonce` is supplied -- confirms the response
326
+ * nonce echoes it. This runs the SAME hardened gates `pki.path.ocspChecker` does. Fail-closed: an
327
+ * unauthorized, stale, mismatched, or nonce-mismatched response is a `"unknown"` verdict (never a
328
+ * silent accept); a malformed response's parse fault surfaces as the parser's `ocsp/*` / `asn1/*`.
329
+ *
330
+ * @opts
331
+ * cert the target certificate (parsed, DER, or PEM) -- REQUIRED.
332
+ * issuer its issuer certificate (parsed, DER, or PEM) -- REQUIRED.
333
+ * time the validation instant (default: now).
334
+ * requestNonce the nonce the client sent; when given, the response MUST echo it (constant-time).
335
+ * historicalMode defer a strictly-future revocation (report good) instead of revoking on skew.
336
+ * @example
337
+ * var res = await pki.ocsp.verify(responseDer, { cert: leafDer, issuer: caDer });
338
+ * res.status; // "good" | "revoked" | "unknown"
339
+ */
340
+ function verify(response, opts) {
341
+ opts = opts || {};
342
+ if (opts.cert == null || opts.issuer == null) return Promise.reject(_err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
343
+ var parsed, cert, issuerCert, time;
344
+ try {
345
+ parsed = (response && response.responseStatus) ? response : ocspSchema.parseResponse(_toDer(response, "the OCSP response"));
346
+ cert = _certOf(opts.cert, "the target certificate");
347
+ issuerCert = _certOf(opts.issuer, "the issuer certificate");
348
+ // The time drives the currency + responder-cert validity windows; an invalid Date fails closed
349
+ // via _asDate (a NaN compares false against every bound, silently disabling both), never defaults.
350
+ time = opts.time == null ? new Date() : _asDate(opts.time);
351
+ } catch (e) { return Promise.reject(e); }
352
+ return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time, { historicalMode: opts.historicalMode === true }).then(function (verdict) {
353
+ if (opts.requestNonce == null) return verdict;
354
+ // A client that sent a nonce binds it (RFC 9654 / RFC 5019 sec. 4): a missing or mismatched
355
+ // response nonce fails the verdict closed, even if the status/signature were otherwise good.
356
+ var respNonce = _responseNonce(parsed);
357
+ var reqNonce = Buffer.isBuffer(opts.requestNonce) ? opts.requestNonce : (opts.requestNonce instanceof Uint8Array ? Buffer.from(opts.requestNonce) : null);
358
+ var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
359
+ var out = Object.assign({}, verdict, { nonceMatched: matched });
360
+ if (!matched && verdict.status !== "unknown") {
361
+ return Object.assign(out, { status: "unknown", reason: "the OCSP response nonce does not echo the request nonce (RFC 9654)" });
362
+ }
363
+ return out;
364
+ });
365
+ }
366
+ function _responseNonce(parsed) {
367
+ var br = parsed && parsed.basicResponse;
368
+ var exts = (br && br.responseExtensions) || [];
369
+ for (var i = 0; i < exts.length; i++) if (exts[i].oid === OID_OCSP_NONCE) return exts[i].nonce || null;
370
+ return null;
371
+ }
372
+
373
+ module.exports = {
374
+ buildRequest: buildRequest,
375
+ sign: sign,
376
+ buildErrorResponse: buildErrorResponse,
377
+ verify: verify,
378
+ };