@blamejs/pki 0.6.14 → 0.6.15
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/CHANGELOG.md +14 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +8 -252
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/ocsp.js
CHANGED
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
|
|
26
26
|
var nodeCrypto = require("crypto");
|
|
27
27
|
var asn1 = require("./asn1-der");
|
|
28
|
-
var pkiBuild = require("./pki-build");
|
|
28
|
+
var pkiBuild = require("./pki-build");
|
|
29
29
|
var oid = require("./oid");
|
|
30
30
|
var x509 = require("./schema-x509");
|
|
31
31
|
var ocspSchema = require("./schema-ocsp");
|
|
32
32
|
var pathValidate = require("./path-validate");
|
|
33
33
|
var signScheme = require("./sign-scheme");
|
|
34
34
|
var ocspVerify = require("./ocsp-verify");
|
|
35
|
-
var ocspRequestVerify = require("./ocsp-request-verify");
|
|
35
|
+
var ocspRequestVerify = require("./ocsp-request-verify");
|
|
36
36
|
var webcrypto = require("./webcrypto");
|
|
37
37
|
var subtle = webcrypto.webcrypto.subtle;
|
|
38
38
|
var guard = require("./guard-all");
|
|
@@ -43,15 +43,9 @@ var OcspError = frameworkError.OcspError;
|
|
|
43
43
|
var b = asn1.build;
|
|
44
44
|
function O(name) { return oid.byName(name); }
|
|
45
45
|
function _err(code, message, cause) { return new OcspError(code, message, cause); }
|
|
46
|
-
// The domain error factory the shared sign-scheme resolver/signer throws through (kind ->
|
|
47
|
-
// ocsp/<kind>), so its faults keep the ocsp/* codes.
|
|
48
46
|
function _signE(kind, message, cause) { return new OcspError("ocsp/" + kind, message, cause); }
|
|
49
47
|
|
|
50
|
-
// CertID / responder-ID hash algorithm name -> WebCrypto digest name. SHA-1 is the RFC 5019 interop
|
|
51
|
-
// default for the CertID identity hash (not a signature; collision resistance is irrelevant to the
|
|
52
|
-
// lookup, so SHAttered does not bar it, the same deliberate split path-validate makes).
|
|
53
48
|
var HASH_WC = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
54
|
-
// A CRLReason name -> its enumerated value (RFC 5280 sec. 5.3.1), reverse of pki.C.NAMES.CRL_REASON.
|
|
55
49
|
var REASON_CODE = {};
|
|
56
50
|
Object.keys(constants.NAMES.CRL_REASON).forEach(function (k) { REASON_CODE[constants.NAMES.CRL_REASON[k]] = parseInt(k, 10); });
|
|
57
51
|
var OID_OCSP_NONCE = O("ocspNonce");
|
|
@@ -61,10 +55,6 @@ var OID_EXTENDED_REVOKE = O("ocspExtendedRevoke");
|
|
|
61
55
|
function _digest(wcHash, buf) { return subtle.digest(wcHash, buf).then(function (h) { return Buffer.from(h); }); }
|
|
62
56
|
function _certOf(arg, what) {
|
|
63
57
|
var label = what || "a certificate";
|
|
64
|
-
// Re-derived from the bytes its parser read, like every other certificate door. A CertID is an
|
|
65
|
-
// IDENTITY -- the issuer's name and key hashed, plus the serial -- so a certificate that names one
|
|
66
|
-
// identity while carrying another's signed bytes would have this verb ask about, or answer for,
|
|
67
|
-
// a certificate nobody issued.
|
|
68
58
|
return guard.parsed.acceptDerived(arg, "certificate", function (bytes) {
|
|
69
59
|
var der = bytes;
|
|
70
60
|
if (typeof bytes === "string") {
|
|
@@ -73,10 +63,6 @@ function _certOf(arg, what) {
|
|
|
73
63
|
try { return x509.parse(der); } catch (e) { throw _err("ocsp/bad-input", label + " is not a well-formed X.509 certificate", e); }
|
|
74
64
|
}, _err, "ocsp/bad-input", label);
|
|
75
65
|
}
|
|
76
|
-
// The response, always parsed from the bytes the caller handed over. See verify's own comment for
|
|
77
|
-
// why an object cannot be accepted here: the three parts of a signature check would come from three
|
|
78
|
-
// independently-chosen properties. The claim is detected on any of the parsed-response fields, so a
|
|
79
|
-
// caller who passes one is told what happened, never given a byte-parse fault about its type.
|
|
80
66
|
var _RESPONSE_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
|
|
81
67
|
function _responseFromBytes(response) {
|
|
82
68
|
return guard.parsed.fromTrustedSource(response, "ocspResponse", _RESPONSE_CLAIM, function (bytes) {
|
|
@@ -90,17 +76,13 @@ function _toDer(input, what) {
|
|
|
90
76
|
if (typeof input === "string") { try { return ocspSchema.pemDecode(input); } catch (e) { throw _err("ocsp/bad-input", (what || "input") + " PEM could not be decoded", e); } }
|
|
91
77
|
throw _err("ocsp/bad-input", (what || "input") + " must be a DER Buffer, Uint8Array, or PEM string");
|
|
92
78
|
}
|
|
93
|
-
// The subjectPublicKey BIT STRING value an issuerKeyHash / byKey KeyHash hashes over.
|
|
94
79
|
function _keyValue(spkiDer) { return ocspVerify.ocspKeyValue(spkiDer); }
|
|
95
|
-
// A non-critical id-pkix-ocsp-nonce Extension carrying the raw nonce (RFC 9654): the extnValue
|
|
96
|
-
// OCTET STRING wraps the Nonce ::= OCTET STRING.
|
|
97
80
|
function _nonceExt(nonceBytes) { return b.sequence([b.oid(OID_OCSP_NONCE), b.octetString(b.octetString(nonceBytes))]); }
|
|
98
81
|
function _assertNonce(nonceBytes) {
|
|
99
82
|
if (!Buffer.isBuffer(nonceBytes) || nonceBytes.length < 1 || nonceBytes.length > 128) {
|
|
100
83
|
throw _err("ocsp/bad-input", "a nonce must be a Buffer of 1..128 octets (RFC 9654 sec. 2.1)");
|
|
101
84
|
}
|
|
102
85
|
}
|
|
103
|
-
// The CertID SEQUENCE for target `cert` under `issuer`, hashing under `hashName` (RFC 6960 sec. 4.1.1).
|
|
104
86
|
function _buildCertID(cert, issuer, hashName) {
|
|
105
87
|
var wc = HASH_WC[hashName];
|
|
106
88
|
if (!wc) throw _err("ocsp/bad-input", "unsupported CertID hashAlgorithm " + JSON.stringify(hashName) + " (sha1 / sha256 / sha384 / sha512)");
|
|
@@ -108,8 +90,6 @@ function _buildCertID(cert, issuer, hashName) {
|
|
|
108
90
|
_digest(wc, issuer.subject.bytes),
|
|
109
91
|
_digest(wc, _keyValue(issuer.subjectPublicKeyInfo.bytes)),
|
|
110
92
|
]).then(function (hashes) {
|
|
111
|
-
// SHA-1 carries NULL parameters (RFC 3279); a SHA-2 digest AlgorithmIdentifier omits them
|
|
112
|
-
// (RFC 5754 sec. 2). The CertID matcher keys on the OID and ignores parameters either way.
|
|
113
93
|
var hashAlgId = hashName === "sha1" ? b.sequence([b.oid(O(hashName)), b.nullValue()]) : b.sequence([b.oid(O(hashName))]);
|
|
114
94
|
return b.sequence([
|
|
115
95
|
hashAlgId,
|
|
@@ -154,19 +134,11 @@ function _buildCertID(cert, issuer, hashName) {
|
|
|
154
134
|
* var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, { nonce: true });
|
|
155
135
|
*/
|
|
156
136
|
function buildRequest(query, opts) {
|
|
157
|
-
// Both arguments copied at entry and released when the call settles -- see the note on the same
|
|
158
|
-
// call in x509-sign. The request is assembled across several promise turns (each CertID is a
|
|
159
|
-
// hash), and opts carries byte fields -- the nonce and requestorName -- that reach the encoding.
|
|
160
137
|
return guard.bytes.fixedCall(OcspError, "ocsp/bad-input", [
|
|
161
138
|
[query, "the OCSP request query"], [opts, "pki.ocsp.buildRequest options"],
|
|
162
139
|
], _buildRequest);
|
|
163
140
|
}
|
|
164
141
|
|
|
165
|
-
// Every option pki.ocsp.buildRequest reads. `nonce` earns the named refusal: it is the
|
|
166
|
-
// anti-replay binding, and a misspelling ships a request with no nonce at all, which the
|
|
167
|
-
// responder answers happily and the caller cannot later bind to.
|
|
168
|
-
// Taken from this verb's own @opts block, which is the published contract; a grep of the
|
|
169
|
-
// function body misses `pem`, read by the shared emitter rather than here.
|
|
170
142
|
var _BUILD_REQUEST_OPTS = {
|
|
171
143
|
hashAlgorithm: 1, nonce: 1, pem: 1, profile: 1, requestorName: 1, signer: 1
|
|
172
144
|
};
|
|
@@ -178,8 +150,6 @@ function _buildRequest(query, opts) {
|
|
|
178
150
|
var lightweight = opts.profile === "lightweight";
|
|
179
151
|
var hashName = opts.hashAlgorithm || "sha1";
|
|
180
152
|
if (lightweight && hashName !== "sha1") throw _err("ocsp/bad-input", "the lightweight profile requires a SHA-1 CertID (RFC 5019 sec. 2.1.1)");
|
|
181
|
-
// A batch array is density-checked before the map below (which would otherwise skip a hole and reach
|
|
182
|
-
// Promise.all/b.sequence with an undefined); a single {cert,issuer} wraps to a dense one-element array.
|
|
183
153
|
var queries = Array.isArray(query) ? pkiBuild.reqDenseArray(query, "queries", _err, "ocsp/bad-input") : [query];
|
|
184
154
|
if (!queries.length) throw _err("ocsp/bad-input", "buildRequest needs at least one { cert, issuer } query");
|
|
185
155
|
if (lightweight && queries.length !== 1) throw _err("ocsp/bad-input", "the lightweight profile permits exactly one Request (RFC 5019 sec. 2.1.1)");
|
|
@@ -200,14 +170,13 @@ function _buildRequest(query, opts) {
|
|
|
200
170
|
})).then(function (requests) {
|
|
201
171
|
var tbsChildren = [];
|
|
202
172
|
if (opts.requestorName) {
|
|
203
|
-
// requestorName [1] EXPLICIT GeneralName; a Name is carried as directoryName [4].
|
|
204
173
|
var nameDer = _nameDer(opts.requestorName);
|
|
205
174
|
tbsChildren.push(b.explicit(1, b.contextConstructed(4, nameDer)));
|
|
206
175
|
}
|
|
207
176
|
tbsChildren.push(b.sequence(requests));
|
|
208
177
|
var reqExts = [];
|
|
209
178
|
if (opts.nonce) {
|
|
210
|
-
var nonceBytes = opts.nonce === true ? nodeCrypto.randomBytes(32) : opts.nonce;
|
|
179
|
+
var nonceBytes = opts.nonce === true ? nodeCrypto.randomBytes(32) : opts.nonce;
|
|
211
180
|
_assertNonce(nonceBytes);
|
|
212
181
|
reqExts.push(_nonceExt(nonceBytes));
|
|
213
182
|
}
|
|
@@ -226,13 +195,9 @@ function _buildRequest(query, opts) {
|
|
|
226
195
|
function _emitReq(der, opts) { return opts.pem ? ocspSchema.pemEncode(der, "OCSP REQUEST") : der; }
|
|
227
196
|
function _nameDer(name) {
|
|
228
197
|
if (guard.bytes.isByteSource(name)) return guard.bytes.snapshotSource(name, OcspError, "ocsp/bad-input", "requestorName");
|
|
229
|
-
if (name && name.bytes) return name.bytes;
|
|
198
|
+
if (name && name.bytes) return name.bytes;
|
|
230
199
|
throw _err("ocsp/bad-input", "requestorName must be a DER Name BufferSource or a parsed Name");
|
|
231
200
|
}
|
|
232
|
-
// The RAW DER of a certificate that will be embedded verbatim (the responder cert in a
|
|
233
|
-
// BasicOCSPResponse, the signer cert in a signed OCSPRequest). It must be supplied as bytes:
|
|
234
|
-
// a parsed certificate does not retain its full DER encoding, and re-encoding it would risk
|
|
235
|
-
// byte drift from the signed original, so a parsed cert is rejected rather than reconstructed.
|
|
236
201
|
function _normCertDer(cert, what) {
|
|
237
202
|
if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, OcspError, "ocsp/bad-input", what || "a certificate");
|
|
238
203
|
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); } }
|
|
@@ -279,19 +244,12 @@ function _normCertDer(cert, what) {
|
|
|
279
244
|
* { responderID: "byName", responses: [{ cert: leafDer, issuer: caDer, status: "good" }] },
|
|
280
245
|
* { cert: responderCertDer, key: responderPkcs8 });
|
|
281
246
|
*/
|
|
282
|
-
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
|
|
283
|
-
// synchronous because they read the responder's mutable cert and key.
|
|
284
247
|
function sign(responseData, responder, opts) {
|
|
285
|
-
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
286
|
-
// on the same call in x509-sign.
|
|
287
248
|
return guard.bytes.fixedCall(OcspError, "ocsp/bad-input", [
|
|
288
249
|
[responseData, "the OCSP responseData"], [responder, "the responder"], [opts, "pki.ocsp.sign options"],
|
|
289
250
|
], _sign);
|
|
290
251
|
}
|
|
291
252
|
|
|
292
|
-
// Every option pki.ocsp.sign reads. `nonce` here is the request nonce this response echoes back.
|
|
293
|
-
// A misspelling emits a response that answers nobody's request, and the client that sent a nonce
|
|
294
|
-
// then reads it as unbound.
|
|
295
253
|
var _SIGN_OPTS = { embedCert: 1, extendedRevoke: 1, nonce: 1, pem: 1 };
|
|
296
254
|
|
|
297
255
|
function _sign(responseData, responder, opts) {
|
|
@@ -302,27 +260,8 @@ function _sign(responseData, responder, opts) {
|
|
|
302
260
|
if (!responder || responder.cert == null || responder.key == null) throw _err("ocsp/bad-input", "a responder must be { cert, key }");
|
|
303
261
|
var respCertDer = _normCertDer(responder.cert, "the responder certificate");
|
|
304
262
|
var respCert = _certOf(respCertDer, "the responder certificate");
|
|
305
|
-
// Read with the certificate, not after the awaits below. The ResponderID and the
|
|
306
|
-
// embedded certificate are both fixed from `respCert` here; the signature is made
|
|
307
|
-
// several promise turns later, so a `responder` whose key was replaced in between
|
|
308
|
-
// would produce a response naming one responder and signed by another key.
|
|
309
|
-
//
|
|
310
|
-
// Capturing the REFERENCE only closes half of that: it stops `responder.key = other`,
|
|
311
|
-
// but PKCS#8 arrives as caller-owned BYTES and a composite key as a caller-owned
|
|
312
|
-
// { mldsa, trad } object, either of which can be rewritten in place across the same
|
|
313
|
-
// gap with the same result -- a response carrying this responder's ID and certificate
|
|
314
|
-
// over a signature made by a different key, which no relying party can verify. So the
|
|
315
|
-
// material itself is snapshotted here, not just the binding to it. A CryptoKey is
|
|
316
|
-
// opaque and a PEM string is immutable, so both are already safe by reference.
|
|
317
263
|
var ownedKeyBytes = [];
|
|
318
264
|
var responderKey = _snapshotSignerKey(responder.key, ownedKeyBytes);
|
|
319
|
-
// The copy is cleared on the failing path as well as the succeeding one -- a wrong or
|
|
320
|
-
// malformed key is the case an attacker can force, so a success-only wipe would keep the
|
|
321
|
-
// secret exactly when it matters. The wipe covers the WHOLE window the copy exists, not
|
|
322
|
-
// just the signing call: the copy is taken before the response list, the responder ID, the
|
|
323
|
-
// SingleResponses, the dates, the nonce and the signature scheme are validated, and every
|
|
324
|
-
// one of those can fail. Attaching cleanup to signing alone leaves the secret in the heap
|
|
325
|
-
// on each of those earlier exits, which are the easiest ones for a caller to reach.
|
|
326
265
|
function _wipeOwnedKey() {
|
|
327
266
|
if (ownedKeyBytes.length) guard.secret.zeroizeAll(ownedKeyBytes, OcspError, "ocsp/bad-input", "the responder key copy");
|
|
328
267
|
ownedKeyBytes.length = 0;
|
|
@@ -335,9 +274,6 @@ function _sign(responseData, responder, opts) {
|
|
|
335
274
|
function (e) { _wipeOwnedKey(); throw e; });
|
|
336
275
|
}
|
|
337
276
|
|
|
338
|
-
// Everything from the key snapshot onward, so a single cleanup in the caller covers every
|
|
339
|
-
// exit -- sync throw and async rejection alike -- rather than each fallible step needing to
|
|
340
|
-
// remember. `responderKey` is the private copy; it is never the caller's object.
|
|
341
277
|
function _signResponse(responseData, responder, respCert, respCertDer, responderKey, opts) {
|
|
342
278
|
var responses = responseData.responses != null ? pkiBuild.reqDenseArray(responseData.responses, "responses", _err, "ocsp/bad-input") : [];
|
|
343
279
|
if (!responses.length) throw _err("ocsp/bad-input", "a response MUST include at least one SingleResponse (RFC 6960 sec. 4.2.1)");
|
|
@@ -361,39 +297,18 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
|
|
|
361
297
|
});
|
|
362
298
|
});
|
|
363
299
|
}
|
|
364
|
-
// A date value -> Date, or null when absent (the caller defaults). An unparseable value is a
|
|
365
|
-
// config-time error, not a silently-emitted Invalid Date: a NaN date would encode as a malformed
|
|
366
|
-
// GeneralizedTime in a SIGNED response, and compare false against every currency bound on verify.
|
|
367
300
|
function _asDate(d) {
|
|
368
301
|
if (d == null) return null;
|
|
369
302
|
var dt = guard.time.isDate(d) ? d : new Date(d);
|
|
370
303
|
if (isNaN(guard.time.instantOf(dt))) throw _err("ocsp/bad-input", "an invalid date value " + JSON.stringify(d));
|
|
371
304
|
return dt;
|
|
372
305
|
}
|
|
373
|
-
// A private copy of whatever signer-key material the caller owns, taken at the entry
|
|
374
|
-
// point because signing happens several promise turns later (see `sign`). Bytes are
|
|
375
|
-
// copied; a composite { mldsa, trad } is rebuilt with each component copied, since
|
|
376
|
-
// rewriting a component is the same attack one level down. Anything else -- a CryptoKey,
|
|
377
|
-
// a PEM string -- is not caller-mutable in a way that changes the key, so it rides as-is
|
|
378
|
-
// rather than being coerced into a shape this function does not understand.
|
|
379
|
-
// `owned` collects every buffer THIS function allocated, so the caller can clear them once
|
|
380
|
-
// signing is done. A copy of a private key is a second copy of a secret, which is precisely
|
|
381
|
-
// what this project's secret-lifetime discipline exists to avoid -- so the copy that closes
|
|
382
|
-
// the aliasing window is wiped rather than left to the garbage collector. Only our own
|
|
383
|
-
// allocations are listed: the caller's key is never written to.
|
|
384
306
|
function _snapshotSignerKey(key, owned) {
|
|
385
307
|
if (Buffer.isBuffer(key) || key instanceof Uint8Array) { // allow:byte-source-narrow -- responder private key on the key-ownership contract (Buffer / Uint8Array / PEM / CryptoKey); widening keys is a separate root decision
|
|
386
308
|
var copy = guard.bytes.snapshot(key, OcspError, "ocsp/bad-input", "the responder key");
|
|
387
309
|
owned.push(copy);
|
|
388
310
|
return copy;
|
|
389
311
|
}
|
|
390
|
-
// The test is "is this a composite DESCRIPTOR", not "does it currently hold bytes". A
|
|
391
|
-
// composite whose components are both PEM strings carries nothing mutable inside it, but
|
|
392
|
-
// the OBJECT is still the caller's: reassigning `key.mldsa` after the call reaches the
|
|
393
|
-
// deferred signing operation just as rewriting a buffer would, and yields a response whose
|
|
394
|
-
// responder ID and embedded certificate describe one responder over a signature made by
|
|
395
|
-
// another key. So the container is always rebuilt, and each component is snapshotted by
|
|
396
|
-
// its own type -- bytes copied, an immutable PEM string passed through.
|
|
397
312
|
if (key && typeof key === "object" && (key.mldsa != null || key.trad != null)) {
|
|
398
313
|
return Object.assign({}, key, { mldsa: _snapshotSignerKey(key.mldsa, owned), trad: _snapshotSignerKey(key.trad, owned) });
|
|
399
314
|
}
|
|
@@ -401,16 +316,13 @@ function _snapshotSignerKey(key, owned) {
|
|
|
401
316
|
}
|
|
402
317
|
|
|
403
318
|
function _responderID(rid, respCert) {
|
|
404
|
-
if (rid == null || rid === "byName") return Promise.resolve(b.explicit(1, b.raw(respCert.subject.bytes)));
|
|
319
|
+
if (rid == null || rid === "byName") return Promise.resolve(b.explicit(1, b.raw(respCert.subject.bytes)));
|
|
405
320
|
if (rid === "byKey") return _digest("SHA-1", _keyValue(respCert.subjectPublicKeyInfo.bytes)).then(function (kh) { return b.explicit(2, b.octetString(kh)); });
|
|
406
321
|
throw _err("ocsp/bad-input", "responderID must be \"byName\" or \"byKey\"");
|
|
407
322
|
}
|
|
408
323
|
function _buildSingleResponse(r, opts) {
|
|
409
324
|
r = r || {};
|
|
410
325
|
var certIdP;
|
|
411
|
-
// A pre-encoded CertID is spliced in RAW, so its type is checked rather than coerced:
|
|
412
|
-
// Buffer.from(20) would allocate twenty zero octets and emit a structurally broken
|
|
413
|
-
// CertID inside a response this responder then SIGNS.
|
|
414
326
|
if (r.certID != null) certIdP = Promise.resolve(b.raw(guard.bytes.view(r.certID, OcspError, "ocsp/bad-input", "a response entry's certID")));
|
|
415
327
|
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");
|
|
416
328
|
else return Promise.reject(_err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
|
|
@@ -424,16 +336,16 @@ function _buildSingleResponse(r, opts) {
|
|
|
424
336
|
}
|
|
425
337
|
function _defaultNextUpdate() { return new Date(Date.now() + constants.TIME.days(7)); }
|
|
426
338
|
function _certStatusNode(status, opts) {
|
|
427
|
-
if (status == null || status === "good") return b.contextPrimitive(0, Buffer.alloc(0));
|
|
428
|
-
if (status === "unknown") return b.contextPrimitive(2, Buffer.alloc(0));
|
|
339
|
+
if (status == null || status === "good") return b.contextPrimitive(0, Buffer.alloc(0));
|
|
340
|
+
if (status === "unknown") return b.contextPrimitive(2, Buffer.alloc(0));
|
|
429
341
|
if (typeof status === "object" && status.revoked != null) {
|
|
430
|
-
var ri = [b.generalizedTime(_asDate(status.revoked))];
|
|
342
|
+
var ri = [b.generalizedTime(_asDate(status.revoked))];
|
|
431
343
|
if (status.revocationReason != null) {
|
|
432
344
|
var code = typeof status.revocationReason === "number" ? status.revocationReason : REASON_CODE[status.revocationReason];
|
|
433
345
|
if (code == null) throw _err("ocsp/bad-input", "unknown revocationReason " + JSON.stringify(status.revocationReason));
|
|
434
|
-
ri.push(b.explicit(0, b.enumerated(BigInt(code))));
|
|
346
|
+
ri.push(b.explicit(0, b.enumerated(BigInt(code))));
|
|
435
347
|
}
|
|
436
|
-
return b.contextConstructed(1, Buffer.concat(ri));
|
|
348
|
+
return b.contextConstructed(1, Buffer.concat(ri));
|
|
437
349
|
}
|
|
438
350
|
throw _err("ocsp/bad-input", "a response status must be \"good\", \"unknown\", or { revoked: <Date>, revocationReason? }");
|
|
439
351
|
}
|
|
@@ -508,14 +420,9 @@ function buildErrorResponse(status) {
|
|
|
508
420
|
* var res = await pki.ocsp.verify(responseDer, { cert: leafDer, issuer: caDer });
|
|
509
421
|
* res.status; // "good" | "revoked" | "unknown"
|
|
510
422
|
*/
|
|
511
|
-
// Every option pki.ocsp.verify reads. `requestNonce` is the one worth refusing by name: it is
|
|
512
|
-
// what binds the response to this request. A misspelling leaves nonceMatched null on a call
|
|
513
|
-
// that did ask, and null is the value meaning "the client never asked".
|
|
514
423
|
var _VERIFY_OPTS = { cert: 1, historicalMode: 1, issuer: 1, requestNonce: 1, time: 1 };
|
|
515
424
|
|
|
516
425
|
function verify(response, opts) {
|
|
517
|
-
// This verb reports faults by rejecting, not throwing, so both entry guards run inside the
|
|
518
|
-
// same try and surface as a rejection like every other fault on this path.
|
|
519
426
|
try {
|
|
520
427
|
opts = guard.identifier.optionsObject(opts, _err, "ocsp/bad-input", "pki.ocsp.verify options");
|
|
521
428
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "ocsp/bad-input",
|
|
@@ -524,49 +431,18 @@ function verify(response, opts) {
|
|
|
524
431
|
if (opts.cert == null || opts.issuer == null) return Promise.reject(_err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
|
|
525
432
|
var parsed, cert, issuerCert, time;
|
|
526
433
|
try {
|
|
527
|
-
// The response is parsed from BYTES, always. A claimed-parsed response carries the signature,
|
|
528
|
-
// the algorithm that verifies it and the byte range it covers as three independent properties,
|
|
529
|
-
// so an object could pair one structure's tbsResponseDataBytes with another's signature -- and a
|
|
530
|
-
// signature the issuing CA made over a certificate it issued would verify as a ResponseData
|
|
531
|
-
// signature, returning status "good" for a certificate the responder never spoke about.
|
|
532
|
-
// Parsing here binds all three to one byte string. pki.schema.ocsp.parseResponse remains the
|
|
533
|
-
// parse-only route for a caller who wants the structure without a verdict.
|
|
534
434
|
parsed = _responseFromBytes(response);
|
|
535
435
|
cert = _certOf(opts.cert, "the target certificate");
|
|
536
436
|
issuerCert = _certOf(opts.issuer, "the issuer certificate");
|
|
537
|
-
// The time drives the currency + responder-cert validity windows; an invalid Date fails closed
|
|
538
|
-
// via _asDate (a NaN compares false against every bound, silently disabling both), never defaults.
|
|
539
437
|
time = opts.time == null ? new Date() : _asDate(opts.time);
|
|
540
438
|
} catch (e) { return Promise.reject(e); }
|
|
541
|
-
// The object parsed HERE goes to the verdict verb, not the caller's argument again. It carries the
|
|
542
|
-
// parser's record, so the verdict verb re-derives from the same recorded bytes -- one snapshot,
|
|
543
|
-
// read by both. Passing the caller's argument a second time would take a SECOND snapshot of it,
|
|
544
|
-
// and a shared-memory view can differ between the two: the nonce compared below would belong to
|
|
545
|
-
// one response and the signature verified to another, which is the split this whole mechanism
|
|
546
|
-
// exists to close.
|
|
547
439
|
return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time, { historicalMode: opts.historicalMode === true }).then(function (verdict) {
|
|
548
|
-
// A client that sent no nonce still gets the field, as null. Leaving it absent would make
|
|
549
|
-
// "not requested" indistinguishable from "the field is not there yet" for a consumer reading
|
|
550
|
-
// res.nonceMatched, and the three-state contract is the whole point: true bound, false not
|
|
551
|
-
// bound, null never asked. The verdict is copied rather than mutated, since it is the lower
|
|
552
|
-
// primitive's object and this layer does not own it.
|
|
553
440
|
if (opts.requestNonce == null) return Object.assign({}, verdict, { nonceMatched: null });
|
|
554
|
-
// A client that sent a nonce binds it (RFC 9654 / RFC 5019 sec. 4): a missing or mismatched
|
|
555
|
-
// response nonce fails the verdict closed, even if the status/signature were otherwise good.
|
|
556
441
|
var respNonce = _responseNonce(parsed);
|
|
557
442
|
var reqNonce = guard.bytes.isByteSource(opts.requestNonce)
|
|
558
443
|
? guard.bytes.snapshotSource(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
|
|
559
444
|
var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
|
|
560
445
|
var out = Object.assign({}, verdict, { nonceMatched: matched });
|
|
561
|
-
// The downgrade applies to `good` ONLY. `unknown` is the closed direction for a
|
|
562
|
-
// response claiming the certificate is fine, because an unmatched nonce means
|
|
563
|
-
// this is not an answer to this request and the "fine" may be stale. It is NOT
|
|
564
|
-
// the closed direction for `revoked`: revocation does not expire the way
|
|
565
|
-
// non-revocation does, so discarding a signed, current, authorized revoked
|
|
566
|
-
// verdict because it was replayed would hand a soft-fail caller the certificate
|
|
567
|
-
// the responder just refused -- turning the anti-replay defense into the thing
|
|
568
|
-
// that accepts a revoked certificate. `nonceMatched: false` still reports that
|
|
569
|
-
// this response was not bound to this request.
|
|
570
446
|
if (!matched && verdict.status === "good") {
|
|
571
447
|
return Object.assign(out, { status: "unknown", reason: "the OCSP response nonce does not echo the request nonce (RFC 9654)" });
|
|
572
448
|
}
|
|
@@ -581,19 +457,13 @@ function _responseNonce(parsed) {
|
|
|
581
457
|
}
|
|
582
458
|
|
|
583
459
|
var _VERIFY_REQUEST_OPTS = { certs: 1 };
|
|
584
|
-
// Parsed from BYTES so the signature, the algorithm that verifies it and the tbsRequest it covers
|
|
585
|
-
// are bound to one byte string, the same discipline pki.ocsp.verify follows for a response. A
|
|
586
|
-
// BufferSource is SNAPSHOT first: parseRequest surfaces tbsRequest / signature / embedded certs as
|
|
587
|
-
// views into it, and the signature check awaits, so without a copy a caller could mutate the buffer
|
|
588
|
-
// mid-verification and be judged on bytes other than the parser read. A PEM string decodes to a
|
|
589
|
-
// fresh buffer already; parseRequest owns that decode and a non-bytes input is its typed refusal.
|
|
590
460
|
function _requestFromBytes(request) {
|
|
591
461
|
if (guard.bytes.isByteSource(request)) request = guard.bytes.snapshotSource(request, OcspError, "ocsp/bad-input", "the OCSP request");
|
|
592
462
|
return ocspSchema.parseRequest(request);
|
|
593
463
|
}
|
|
594
464
|
function _reqVerdict(parsed, extra) {
|
|
595
465
|
return {
|
|
596
|
-
valid: extra.signed === true && extra.signatureValid === true,
|
|
466
|
+
valid: extra.signed === true && extra.signatureValid === true,
|
|
597
467
|
signed: extra.signed, signatureValid: extra.signatureValid,
|
|
598
468
|
signerCert: extra.signerCert != null ? extra.signerCert : null,
|
|
599
469
|
signerCerts: extra.signerCerts != null ? extra.signerCerts : [],
|
|
@@ -604,33 +474,13 @@ function _reqVerdict(parsed, extra) {
|
|
|
604
474
|
reason: extra.reason != null ? extra.reason : null,
|
|
605
475
|
};
|
|
606
476
|
}
|
|
607
|
-
// Collect EVERY candidate certificate whose key verified the request signature. RFC 6960 sec. 4.1.1
|
|
608
|
-
// certs is an unordered SEQUENCE OF Certificate, and several may share the signing key (an expired
|
|
609
|
-
// certificate carried beside its renewal), so all matches are surfaced rather than one
|
|
610
|
-
// position-dependent pick: the responder builds a trusted path to one of them to select a usable
|
|
611
|
-
// one, and no valid match is hidden behind an unusable one that happened to appear first. `strict` separates the
|
|
612
|
-
// responder's own opts.certs (a malformed entry is its config error, a typed throw) from the
|
|
613
|
-
// request's untrusted embedded certs (a malformed one is skipped, never fatal).
|
|
614
477
|
async function _findRequestSigners(candidates, strict, parsed, sig, sigBits) {
|
|
615
478
|
var parsedList = [];
|
|
616
479
|
for (var i = 0; i < candidates.length; i++) {
|
|
617
480
|
try { parsedList.push({ der: candidates[i], cert: x509.parse(candidates[i]) }); }
|
|
618
481
|
catch (e) { if (strict) throw _err("ocsp/bad-signer-cert", "an opts.certs entry does not parse as an X.509 certificate", e); }
|
|
619
482
|
}
|
|
620
|
-
// The certs surfaced for path building are the PARSEABLE ones: a malformed embedded entry (skipped
|
|
621
|
-
// above rather than fatal) is excluded so it is never handed to pki.path.build as a candidate.
|
|
622
483
|
var parseable = parsedList.map(function (p) { return p.der; });
|
|
623
|
-
// signerCerts is every candidate whose key VERIFIES the request signature, identified by the
|
|
624
|
-
// verification itself rather than by comparing encoded key bytes. A request carries one signature
|
|
625
|
-
// under one key, but that key can appear across several certificates in the unordered certs field
|
|
626
|
-
// (an expired certificate beside its renewal), and the one key can be encoded differently between
|
|
627
|
-
// them -- rsaEncryption with NULL versus absent parameters, an EC point compressed versus
|
|
628
|
-
// uncompressed -- so a byte comparison would drop a genuine match, while a coincidental byte match
|
|
629
|
-
// under a different algorithm would be a false one. Verifying each candidate answers both exactly,
|
|
630
|
-
// through the algorithm-confusion gate (RFC 9814 sec. 4) in the shared engine. The work is bounded:
|
|
631
|
-
// the parser already caps the certificate count at OCSP_MAX_CERTS, and the engine returns false
|
|
632
|
-
// (never throws) for a candidate whose key does not match the signature, so a non-signer is skipped
|
|
633
|
-
// rather than aborting the verdict.
|
|
634
484
|
var matches = [];
|
|
635
485
|
for (var j = 0; j < parsedList.length; j++) {
|
|
636
486
|
if (await ocspRequestVerify.verifyRequestSignature(sig.signatureAlgorithm, sigBits, parsedList[j].cert.subjectPublicKeyInfo.bytes, parsed.tbsRequestBytes)) {
|
|
@@ -639,11 +489,6 @@ async function _findRequestSigners(candidates, strict, parsed, sig, sigBits) {
|
|
|
639
489
|
}
|
|
640
490
|
return { matches: matches, parseable: parseable };
|
|
641
491
|
}
|
|
642
|
-
// The verdict from `_findRequestSigners`: signerCerts is every match (so the caller's trust decision
|
|
643
|
-
// does not depend on ordering), signerCert / signerSubject the first for the common single-signer
|
|
644
|
-
// case. `certs` is every PARSEABLE certificate the request carried (or opts.certs supplied), so the
|
|
645
|
-
// responder has the intermediates it needs to chain a signer to an anchor -- those do not sign the
|
|
646
|
-
// request, so they never appear in signerCerts. No match is signatureValid:false with the reason.
|
|
647
492
|
function _signerVerdict(parsed, found, failReason) {
|
|
648
493
|
if (!found.matches.length) return _reqVerdict(parsed, { signed: true, signatureValid: false, certs: found.parseable, reason: failReason });
|
|
649
494
|
return _reqVerdict(parsed, {
|
|
@@ -714,10 +559,7 @@ async function verifyRequest(request, opts) {
|
|
|
714
559
|
return _reqVerdict(parsed, { signed: false, signatureValid: false,
|
|
715
560
|
reason: "the OCSP request is unsigned (RFC 6960 sec. 4.1.1 makes optionalSignature OPTIONAL); no requestor is authenticated" });
|
|
716
561
|
}
|
|
717
|
-
// sig.signature is the raw signature the parser already refused unless octet-aligned; the seam
|
|
718
|
-
// re-checks alignment, so it is handed the BIT STRING shape the seam verifies.
|
|
719
562
|
var sigBits = { unusedBits: 0, bytes: sig.signature };
|
|
720
|
-
// Prefer the certificates the request carries; fall back to opts.certs only when it carries none.
|
|
721
563
|
if (sig.certs.length) {
|
|
722
564
|
return _signerVerdict(parsed, await _findRequestSigners(sig.certs, false, parsed, sig, sigBits),
|
|
723
565
|
"the OCSP request signature does not verify over tbsRequest under any certificate the request carries (RFC 6960 sec. 4.1.1)");
|