@blamejs/pki 0.2.21 → 0.2.23

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/cms-sign.js CHANGED
@@ -17,101 +17,25 @@ var nodeCrypto = require("crypto");
17
17
  var asn1 = require("./asn1-der");
18
18
  var oid = require("./oid");
19
19
  var x509 = require("./schema-x509");
20
- var pkcs8 = require("./schema-pkcs8");
21
20
  var pkix = require("./schema-pkix");
22
- var webcrypto = require("./webcrypto");
23
- var subtle = webcrypto.webcrypto.subtle;
24
- var validator = require("./validator-all");
25
- var compositeSig = require("./composite-sig");
26
21
  var frameworkError = require("./framework-error");
27
22
 
23
+ var signScheme = require("./sign-scheme");
28
24
  var CmsError = frameworkError.CmsError;
29
25
  var b = asn1.build;
30
26
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
27
+ // The domain error factory the shared sign-scheme resolver/signer throws through (kind ->
28
+ // cms/<kind>), so its faults keep the cms/* codes.
29
+ function _signE(kind, message, cause) { return new CmsError("cms/" + kind, message, cause); }
31
30
  function O(name) { return oid.byName(name); }
32
31
 
33
- // A digest-algorithm name -> the WebCrypto hash (sign path). SHAKE256 has no WebCrypto hash, so
34
- // the message digest is always computed with node:crypto (below), uniform across the family.
35
- var HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
36
32
  var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", shake128: "shake128", shake256: "shake256" };
37
33
  var SHAKE_OUT = { shake128: 32, shake256: 64 }; // SHAKE output lengths: SHAKE128 256-bit (RFC 9814 sec. 4), SHAKE256 512-bit (RFC 8419 sec. 2.3 / RFC 9814)
38
- var PSS_SALT = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
39
- var ECDSA_ALG = { sha256: "ecdsaWithSHA256", sha384: "ecdsaWithSHA384", sha512: "ecdsaWithSHA512" };
40
- var HASH_NAME_BY_OID = {};
41
- HASH_NAME_BY_OID[O("sha256")] = "sha256";
42
- HASH_NAME_BY_OID[O("sha384")] = "sha384";
43
- HASH_NAME_BY_OID[O("sha512")] = "sha512";
44
- var EC_BY_CURVE_OID = {};
45
- EC_BY_CURVE_OID[O("prime256v1")] = { curve: "P-256", coordLen: 32 };
46
- EC_BY_CURVE_OID[O("secp384r1")] = { curve: "P-384", coordLen: 48 };
47
- EC_BY_CURVE_OID[O("secp521r1")] = { curve: "P-521", coordLen: 66 };
48
- // ML-DSA (RFC 9882): the signer cert SPKI algorithm OID -> its WebCrypto name. Registry-keyed
49
- // (Hard rule #2, no dotted-decimal literal). The same OID identifies both the key and the signature.
50
- var MLDSA_BY_OID = {};
51
- MLDSA_BY_OID[O("id-ml-dsa-44")] = "ML-DSA-44";
52
- MLDSA_BY_OID[O("id-ml-dsa-65")] = "ML-DSA-65";
53
- MLDSA_BY_OID[O("id-ml-dsa-87")] = "ML-DSA-87";
54
- // The message-digest algorithms suitable for each ML-DSA parameter set (RFC 9882 sec. 3.3 /
55
- // Table 1): a digest MUST offer at least the parameter set's collision strength (lambda) and at
56
- // least 2*lambda bits of output. SHA-512 is suitable for all sets; SHAKE256 (512-bit output) too.
57
- // Below-strength digests downgrade the signature to the weaker of the two (the whichever-is-lower
58
- // rule), so they are refused fail-closed on sign and verify -- the RFC's "verifiers MAY reject".
59
- var MLDSA_SUITABLE_DIGEST = {
60
- "ML-DSA-44": { sha256: 1, sha384: 1, sha512: 1, shake256: 1 },
61
- "ML-DSA-65": { sha384: 1, sha512: 1, shake256: 1 },
62
- "ML-DSA-87": { sha512: 1, shake256: 1 },
63
- };
64
- // SLH-DSA (RFC 9814): the twelve pure FIPS 205 sets. The signer cert SPKI algorithm OID -> its
65
- // WebCrypto set name ("SLH-DSA-"+SET, mirroring webcrypto's SLH_DSA_NODE / path-validate) + the
66
- // RFC 9814 sec. 4 pinned message digest for that set. The sig and key share one OID; the digest is
67
- // FIXED per set (no caller choice, unlike ML-DSA's suitable-set). Registry-keyed (Hard rule #2).
68
- var SLHDSA_BY_OID = {};
69
- [["sha2-128s", "sha256"], ["sha2-128f", "sha256"], ["sha2-192s", "sha512"], ["sha2-192f", "sha512"],
70
- ["sha2-256s", "sha512"], ["sha2-256f", "sha512"], ["shake-128s", "shake128"], ["shake-128f", "shake128"],
71
- ["shake-192s", "shake256"], ["shake-192f", "shake256"], ["shake-256s", "shake256"], ["shake-256f", "shake256"]
72
- ].forEach(function (r) { SLHDSA_BY_OID[O("id-slh-dsa-" + r[0])] = { wc: "SLH-DSA-" + r[0].toUpperCase(), digest: r[1] }; });
73
34
 
74
35
  var OID_DATA = O("data");
75
36
  var OID_SIGNED_DATA = O("signedData");
76
37
  var OID_SKI = O("subjectKeyIdentifier");
77
38
 
78
- // An AlgorithmIdentifier { OID } (absent parameters) or { OID, NULL }.
79
- function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(name)), b.nullValue()]) : b.sequence([b.oid(O(name))]); }
80
- // The RSASSA-PSS AlgorithmIdentifier with the params SEQUENCE cms.verify's _resolvePss accepts:
81
- // an explicit SHA-2 hashAlgorithm, MGF1 keyed to the same hash, the hash-length saltLength, and
82
- // the default trailerField (omitted). RFC 4055.
83
- function _pssAlgId(digestName) {
84
- var hashAlg = b.sequence([b.oid(O(digestName)), b.nullValue()]);
85
- var mgf = b.sequence([b.oid(O("mgf1")), hashAlg]);
86
- var params = b.sequence([b.explicit(0, hashAlg), b.explicit(1, mgf), b.explicit(2, b.integer(BigInt(PSS_SALT[HASH[digestName]])))]);
87
- return b.sequence([b.oid(O("rsassaPss")), params]);
88
- }
89
-
90
- // An id-RSASSA-PSS SubjectPublicKeyInfo MAY pin the permitted hash in its RSASSA-PSS-params
91
- // (RFC 4055 sec. 1.2 / sec. 3.1): a key generated for SHA-384 cannot sign under SHA-256. Read the
92
- // pinned digest name from the SPKI algorithm parameters so signing honors the key's restriction
93
- // instead of hard-coding SHA-256. Absent params (an unrestricted key) or an unrecognized hash
94
- // returns null -- the caller then falls back to its opts.digestAlgorithm or the SHA-256 default.
95
- function _pssHashFromSpki(cert) {
96
- var params = cert.subjectPublicKeyInfo.algorithm.parameters;
97
- if (params == null) return null; // an unrestricted id-RSASSA-PSS key
98
- // params is the raw parameters TLV the strict codec already validated when it parsed the cert,
99
- // so re-decoding a single valid TLV cannot throw. Read the pinned hash structurally, defaulting
100
- // to null (caller falls back) whenever the shape is not the expected RSASSA-PSS-params.
101
- var node = asn1.decode(params);
102
- if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children) return null;
103
- var hashField = node.children.filter(function (c) { return c.tagClass === "context" && c.tagNumber === 0; })[0];
104
- if (!hashField || !hashField.children || !hashField.children[0] || !hashField.children[0].children) return null;
105
- var oidNode = hashField.children[0].children[0]; // [0] EXPLICIT AlgorithmIdentifier { hash OID, ... }
106
- if (!oidNode || oidNode.tagClass !== "universal" || oidNode.tagNumber !== asn1.TAGS.OBJECT_IDENTIFIER) return null;
107
- var pinnedOid = asn1.read.oid(oidNode);
108
- var name = HASH_NAME_BY_OID[pinnedOid];
109
- // A recognized SHA-2 pin resolves to its name; an unsupported pinned hash (e.g. SHA-3) fails
110
- // closed -- we cannot honor the key's restriction, so we never silently sign under a different
111
- // digest the key forbids.
112
- if (!name) throw _err("cms/unsupported-algorithm", "the id-RSASSA-PSS signer key pins an unsupported hash algorithm (" + pinnedOid + ")");
113
- return name;
114
- }
115
39
 
116
40
  // The message digest of the content under the digest algorithm (SHA-2 or SHAKE256).
117
41
  function _digest(digestName, content) {
@@ -121,83 +45,6 @@ function _digest(digestName, content) {
121
45
  return h.update(content).digest();
122
46
  }
123
47
 
124
- // Resolve the sign scheme from the signer certificate's public-key algorithm + per-signer opts:
125
- // the digest, the digestAlgorithm and signatureAlgorithm AlgorithmIdentifiers (with the exact
126
- // parameter shape cms.verify requires), and the WebCrypto import + sign algorithms.
127
- function _scheme(cert, so, noSignedAttrs) {
128
- var alg = cert.subjectPublicKeyInfo.algorithm;
129
- var keyOid = alg.oid;
130
- var comp = compositeSig.COMPOSITE_ALGS[keyOid];
131
- if (comp) {
132
- // Composite ML-DSA (draft-ietf-lamps-cms-composite-sigs): the arm DICTATES the CMS
133
- // digestAlgorithm (its pre-hash, Table 1) -- unlike RSA/ECDSA the digest is not a caller
134
- // choice, so a conflicting so.digestAlgorithm is rejected. The 3 arms Node's WebCrypto surface
135
- // cannot sign (brainpool curves; the SHAKE256/64 pre-hash) fail closed at config time -- never
136
- // a partial single-component signature.
137
- if (comp.trad.unsupported) throw _err("cms/unsupported-algorithm", "composite " + comp.name + ": " + comp.trad.unsupported);
138
- if (so.digestAlgorithm && so.digestAlgorithm !== comp.phCms) throw _err("cms/bad-input", "composite " + comp.name + " fixes the digestAlgorithm to " + comp.phCms + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4); " + JSON.stringify(so.digestAlgorithm) + " conflicts");
139
- return { composite: comp, digest: comp.phCms, digestAlgId: _algId(comp.phCms, "absent"), sigAlgId: _algId(comp.name, "absent") };
140
- }
141
- if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) {
142
- var isPssKey = keyOid === O("rsassaPss");
143
- // An id-RSASSA-PSS key MAY pin its permitted hash in the SPKI params (RFC 4055 sec. 1.2): honor
144
- // that over the SHA-256 default so Node/OpenSSL does not reject the otherwise-valid signer with
145
- // ERR_OSSL_DIGEST_NOT_ALLOWED. An explicit digestAlgorithm that contradicts the pin is rejected
146
- // fail-closed rather than silently signed under a digest the key forbids.
147
- var pinned = isPssKey ? _pssHashFromSpki(cert) : null;
148
- if (pinned && so.digestAlgorithm && so.digestAlgorithm !== pinned) throw _err("cms/bad-input", "the signer key restricts the RSASSA-PSS digest to " + pinned + ", but digestAlgorithm " + JSON.stringify(so.digestAlgorithm) + " was requested");
149
- var d = so.digestAlgorithm || pinned || "sha256";
150
- if (!HASH[d]) throw _err("cms/unsupported-algorithm", "unsupported RSA digest algorithm " + JSON.stringify(d));
151
- // A general rsaEncryption key signs PKCS#1 v1.5 by default, or RSASSA-PSS when opts.pss is set.
152
- if (so.pss || isPssKey) return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: _pssAlgId(d), imp: { name: "RSA-PSS", hash: HASH[d] }, sign: { name: "RSA-PSS", saltLength: PSS_SALT[HASH[d]] }, ecdsaDer: false };
153
- return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: _algId("rsaEncryption", "null"), imp: { name: "RSASSA-PKCS1-v1_5", hash: HASH[d] }, sign: { name: "RSASSA-PKCS1-v1_5" }, ecdsaDer: false };
154
- }
155
- if (keyOid === O("ecPublicKey")) {
156
- var curveOid;
157
- try { curveOid = asn1.read.oid(asn1.decode(alg.parameters)); }
158
- catch (e) { throw _err("cms/unsupported-algorithm", "the signer EC key parameters are not a named-curve OID", e); }
159
- var ec = EC_BY_CURVE_OID[curveOid];
160
- if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
161
- var de = so.digestAlgorithm || "sha256";
162
- if (!HASH[de]) throw _err("cms/unsupported-algorithm", "unsupported ECDSA digest algorithm " + JSON.stringify(de));
163
- return { digest: de, digestAlgId: _algId(de, "absent"), sigAlgId: _algId(ECDSA_ALG[de], "absent"), imp: { name: "ECDSA", namedCurve: ec.curve }, sign: { name: "ECDSA", hash: HASH[de] }, ecdsaDer: true, coordLen: ec.coordLen };
164
- }
165
- if (keyOid === O("Ed25519") || keyOid === O("Ed448")) {
166
- var name = keyOid === O("Ed25519") ? "Ed25519" : "Ed448";
167
- var dd = so.digestAlgorithm || (name === "Ed25519" ? "sha512" : "shake256");
168
- if (!NODE_DIGEST[dd]) throw _err("cms/unsupported-algorithm", "unsupported " + name + " digest algorithm " + JSON.stringify(dd));
169
- return { digest: dd, digestAlgId: _algId(dd, "absent"), sigAlgId: _algId(name, "absent"), imp: { name: name }, sign: { name: name }, ecdsaDer: false };
170
- }
171
- if (MLDSA_BY_OID[keyOid]) {
172
- // ML-DSA (RFC 9882): a one-shot post-quantum signature, pure mode, empty context. The signature
173
- // covers the same sec. 5.4 preimage as every other scheme; digestAlgorithm names the message-
174
- // digest algorithm (SHA-512 by default -- the sec. 3.3 MUST / interop default), which must be
175
- // suitable for the parameter set (Q1 -- enforced on sign, refusing a below-strength digest).
176
- var mlName = MLDSA_BY_OID[keyOid];
177
- // RFC 9882 sec. 3.3: without signed attributes the digestAlgorithm has no meaning, but the
178
- // signer MUST still emit SHA-512 to avoid an interoperability failure -- so force it (ignoring
179
- // any caller digestAlgorithm) rather than carry a value a strict peer would reject. With signed
180
- // attributes the digest must be suitable for the parameter set (Q1 -- enforced on sign).
181
- var md;
182
- if (noSignedAttrs) {
183
- md = "sha512";
184
- } else {
185
- md = so.digestAlgorithm || "sha512";
186
- if (!NODE_DIGEST[md]) throw _err("cms/unsupported-algorithm", "unsupported ML-DSA message digest " + JSON.stringify(md));
187
- if (!MLDSA_SUITABLE_DIGEST[mlName][md]) throw _err("cms/unsupported-algorithm", "the " + md + " message digest is below the security strength of " + mlName + " (RFC 9882 sec. 3.3)");
188
- }
189
- return { digest: md, digestAlgId: _algId(md, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: mlName }, sign: { name: mlName }, ecdsaDer: false };
190
- }
191
- if (SLHDSA_BY_OID[keyOid]) {
192
- // SLH-DSA (RFC 9814): a one-shot post-quantum signature, pure mode, empty context. The message
193
- // digest is FIXED per parameter set (sec. 4) -- emit it, and reject a contradicting caller
194
- // digestAlgorithm fail-closed rather than carry a non-conformant digest.
195
- var slh = SLHDSA_BY_OID[keyOid];
196
- if (so.digestAlgorithm && so.digestAlgorithm !== slh.digest) throw _err("cms/bad-input", "SLH-DSA " + slh.wc + " requires the " + slh.digest + " message digest (RFC 9814 sec. 4); digestAlgorithm " + JSON.stringify(so.digestAlgorithm) + " conflicts");
197
- return { digest: slh.digest, digestAlgId: _algId(slh.digest, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: slh.wc }, sign: { name: slh.wc }, ecdsaDer: false };
198
- }
199
- throw _err("cms/unsupported-algorithm", "unsupported signer key algorithm " + keyOid);
200
- }
201
48
 
202
49
  // The raw issuer Name TLV from a parsed certificate (byte-identical to the cert, so the sid the
203
50
  // verifier canonically compares matches exactly). The issuer is the Name after the optional
@@ -215,30 +62,6 @@ function _skiValue(cert) {
215
62
  catch (e) { throw _err("cms/no-ski", "the signer certificate's subjectKeyIdentifier extension value is not an OCTET STRING", e); }
216
63
  }
217
64
 
218
- // A caller-supplied CryptoKey carries its OWN algorithm (name, and for RSA the baked-in hash,
219
- // for ECDSA the curve). A PKCS#8 key is imported under the resolved scheme, so it always agrees;
220
- // but a pre-imported CryptoKey whose algorithm disagrees with the certificate's scheme would
221
- // silently sign under a different hash than the digestAlgorithm advertises -- an inconsistent,
222
- // non-verifiable CMS. Reject the mismatch fail-closed.
223
- function _assertKeyMatchesScheme(key, imp) {
224
- var ka = key.algorithm || {};
225
- if (ka.name !== imp.name) throw _err("cms/bad-input", "the signer CryptoKey algorithm (" + ka.name + ") does not match the certificate's key algorithm (" + imp.name + ")");
226
- if (imp.hash && (!ka.hash || ka.hash.name !== imp.hash)) throw _err("cms/bad-input", "the signer CryptoKey hash (" + (ka.hash && ka.hash.name) + ") does not match the signing digest (" + imp.hash + ")");
227
- if (imp.namedCurve && ka.namedCurve !== imp.namedCurve) throw _err("cms/bad-input", "the signer CryptoKey curve (" + ka.namedCurve + ") does not match the certificate curve (" + imp.namedCurve + ")");
228
- }
229
- // Import the signer private key: a CryptoKey passed through, or a PKCS#8 DER Buffer / PEM string.
230
- function _importKey(key, imp) {
231
- if (key && typeof key === "object" && !Buffer.isBuffer(key) && !(key instanceof Uint8Array) && key.type === "private") {
232
- _assertKeyMatchesScheme(key, imp);
233
- return Promise.resolve(key);
234
- }
235
- var der;
236
- if (Buffer.isBuffer(key)) der = key;
237
- else if (key instanceof Uint8Array) der = Buffer.from(key);
238
- else if (typeof key === "string") { try { der = pkcs8.pemDecode(key); } catch (e) { throw _err("cms/bad-input", "the signer PEM private key could not be decoded", e); } }
239
- else throw _err("cms/bad-input", "a signer key must be a CryptoKey, a PKCS#8 DER Buffer, or a PKCS#8 PEM string");
240
- return subtle.importKey("pkcs8", der, imp, false, ["sign"]);
241
- }
242
65
 
243
66
  // Build one SignerInfo (RFC 5652 sec. 5.3) and sign it. Resolves the SignerInfo (as a build
244
67
  // node) plus its digestAlgorithm AlgorithmIdentifier and the signer certificate DER (for the
@@ -247,7 +70,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
247
70
  var so = signer || {};
248
71
  var certDer = _normCertDer(so.cert);
249
72
  var cert = x509.parse(certDer);
250
- var scheme = _scheme(cert, so, opts.signedAttributes === false);
73
+ var scheme = signScheme.resolveSignScheme(cert, so, opts.signedAttributes === false, _signE);
251
74
  var useSki = opts.sid === "ski";
252
75
  var sid = useSki
253
76
  ? b.contextPrimitive(0, _skiValue(cert)) // [0] IMPLICIT SubjectKeyIdentifier
@@ -282,7 +105,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
282
105
  return { setOf: setOf, wire: wire };
283
106
  }).then(function (toSign) {
284
107
  var signedBytes = toSign.setOf ? toSign.setOf : toSign; // SET-OF form for signing (sec. 5.4)
285
- return _signScheme(scheme, so, signedBytes).then(function (sig) {
108
+ return signScheme.signOverTbs(scheme, so.key, signedBytes, _signE).then(function (sig) {
286
109
  var fields = [b.integer(BigInt(version)), sid, scheme.digestAlgId];
287
110
  if (toSign.wire) fields.push(toSign.wire); // [0] IMPLICIT signedAttrs
288
111
  fields.push(scheme.sigAlgId, b.octetString(sig));
@@ -291,38 +114,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
291
114
  });
292
115
  }
293
116
 
294
- // Sign the sec. 5.4 preimage under the resolved scheme -> the raw signature bytes for the
295
- // SignerInfo signature OCTET STRING. The classical path imports the single signer key and signs
296
- // (re-encoding ECDSA to canonical DER); the composite path signs BOTH component keys over the
297
- // domain-separated M' and returns mldsaSig || tradSig (composite-sig.js owns the construction).
298
- function _signScheme(scheme, so, signedBytes) {
299
- if (scheme.composite) {
300
- return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(so.key, scheme.composite), signedBytes).then(function (sig) { return Buffer.from(sig); });
301
- }
302
- return _importKey(so.key, scheme.imp).then(function (priv) {
303
- return subtle.sign(scheme.sign, priv, signedBytes).then(function (sigRaw) {
304
- var sig = Buffer.from(sigRaw);
305
- if (scheme.ecdsaDer) sig = validator.sig.rawToEcdsaDer(sig, scheme.coordLen);
306
- return sig;
307
- });
308
- });
309
- }
310
117
 
311
- // A composite signer key is the two component private keys { mldsa, trad } as PKCS#8 (Node has no
312
- // single composite key type; the CMS draft is silent on key import). A missing/wrong-typed
313
- // component fails closed at config time -- never a partial single-component signature.
314
- function _normCompositeKeys(key, comp) {
315
- if (!key || typeof key !== "object" || Buffer.isBuffer(key) || key instanceof Uint8Array || key.mldsa == null || key.trad == null) {
316
- throw _err("cms/bad-input", "a composite " + comp.name + " signer key must be { mldsa: <PKCS#8>, trad: <PKCS#8> }");
317
- }
318
- return { mldsa: _normPkcs8(key.mldsa, "the composite ML-DSA component key"), trad: _normPkcs8(key.trad, "the composite traditional component key") };
319
- }
320
- function _normPkcs8(k, label) {
321
- if (Buffer.isBuffer(k)) return k;
322
- if (k instanceof Uint8Array) return Buffer.from(k);
323
- if (typeof k === "string") { try { return pkcs8.pemDecode(k); } catch (e) { throw _err("cms/bad-input", label + " PEM could not be decoded", e); } }
324
- throw _err("cms/bad-input", label + " must be a PKCS#8 DER Buffer, Uint8Array, or PEM string");
325
- }
326
118
 
327
119
  // A signing-time Time value: UTCTime before 2050, GeneralizedTime from 2050 (RFC 5652 sec. 11.3 /
328
120
  // RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above).
@@ -415,7 +207,4 @@ function _toBuf(v, what) {
415
207
  // * `_assertKeyMatchesScheme`'s `!ka.hash` guard -- an `imp.hash` is set only for an RSA
416
208
  // scheme, which requires `ka.name` to already equal the RSA name (else the earlier name
417
209
  // check throws); an RSA CryptoKey always carries a `hash`, so `!ka.hash` never fires.
418
- // MLDSA_SUITABLE_DIGEST is shared with cms-verify (which requires this module) so the per-
419
- // parameter-set digest-strength policy has ONE home -- a digest accepted on sign is exactly the
420
- // set accepted on verify, and neither side can drift from the other (RFC 9882 sec. 3.3).
421
- module.exports = { sign: sign, MLDSA_SUITABLE_DIGEST: MLDSA_SUITABLE_DIGEST, SLHDSA_BY_OID: SLHDSA_BY_OID };
210
+ module.exports = { sign: sign };
package/lib/cms-verify.js CHANGED
@@ -35,8 +35,11 @@ var webcrypto = require("./webcrypto");
35
35
  var subtle = webcrypto.webcrypto.subtle;
36
36
  var edwardsPoint = require("./edwards-point");
37
37
  var cmsSign = require("./cms-sign");
38
- var MLDSA_SUITABLE_DIGEST = cmsSign.MLDSA_SUITABLE_DIGEST; // shared sign/verify digest-strength policy (RFC 9882 sec. 3.3)
39
- var SLHDSA_BY_OID = cmsSign.SLHDSA_BY_OID; // shared SLH-DSA set -> { wc, digest } (RFC 9814 sec. 4 pinned digest)
38
+ var cmsEncrypt = require("./cms-encrypt");
39
+ var cmsDecrypt = require("./cms-decrypt");
40
+ var signScheme = require("./sign-scheme");
41
+ var MLDSA_SUITABLE_DIGEST = signScheme.MLDSA_SUITABLE_DIGEST; // shared sign/verify digest-strength policy (RFC 9882 sec. 3.3)
42
+ var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID; // shared SLH-DSA set -> { wc, digest } (RFC 9814 sec. 4 pinned digest)
40
43
  var validator = require("./validator-all");
41
44
  var compositeSig = require("./composite-sig");
42
45
  var guard = require("./guard-all");
@@ -587,4 +590,64 @@ function _addCert(out, der) {
587
590
  */
588
591
  var sign = cmsSign.sign;
589
592
 
590
- module.exports = { verify: verify, sign: sign };
593
+ /**
594
+ * @primitive pki.cms.encrypt
595
+ * @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
596
+ * @since 0.2.23
597
+ * @status experimental
598
+ * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
599
+ * @related pki.cms.decrypt, pki.schema.cms.parse
600
+ *
601
+ * Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content -- the
602
+ * default), or EncryptedData. `recipients` is an ARRAY of recipient descriptors for the enveloped
603
+ * family, each wrapping the SAME fresh content-encryption key: `{ cert }` auto-dispatches off the
604
+ * recipient certificate's public-key algorithm -- RSA yields a ktri with RSAES-OAEP-SHA256 (PKCS#1
605
+ * v1.5 is NEVER emitted); EC P-256/384/521 a kari with ephemeral-static ECDH and the X9.63 KDF;
606
+ * X25519/X448 a kari per RFC 8418 (HKDF); ML-KEM-512/768/1024 an ori/KEMRecipientInfo per RFC
607
+ * 9629 + 9936. `{ password }` yields a pwri (PBKDF2 + RFC 3211 PWRI-KEK); `{ kek, kekId }` a kekri
608
+ * (AES key wrap). For EncryptedData (no RecipientInfos), pass a single non-array `{ cek }` or
609
+ * `{ password }` descriptor. The default AES-256-GCM content encryption produces an
610
+ * authenticated AuthEnvelopedData; a CBC choice produces an unauthenticated EnvelopedData.
611
+ * Malformed input throws a typed `CmsError`.
612
+ *
613
+ * @opts contentEncryptionAlgorithm `"aes-256-gcm"` (default, AuthEnvelopedData) / `"aes-128-gcm"` / `"aes-256-cbc"` / `"aes-128-cbc"` (EnvelopedData).
614
+ * @opts contentType The encapsulated content type (an OID name). Default `data`.
615
+ * @opts oaepHash The RSAES-OAEP hash for ktri recipients: `"sha256"` (default) / `"sha384"` / `"sha512"`.
616
+ * @opts keyIdentifier The recipient identifier form: `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
617
+ * @opts ukm User keying material (a Buffer) for kari / kemri recipients.
618
+ * @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData.
619
+ * @opts pem Return a PEM string instead of a DER Buffer.
620
+ * @example
621
+ * var env = await pki.cms.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
622
+ */
623
+ var encrypt = cmsEncrypt.encrypt;
624
+
625
+ /**
626
+ * @primitive pki.cms.decrypt
627
+ * @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated }>
628
+ * @since 0.2.23
629
+ * @status experimental
630
+ * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018, RFC 3218
631
+ * @related pki.cms.encrypt, pki.schema.cms.parse
632
+ *
633
+ * Decrypt a CMS EnvelopedData / AuthEnvelopedData / EncryptedData (DER Buffer or PEM). It selects
634
+ * the recipient the key material targets, acquires the content-encryption key through the matching
635
+ * arm (ktri OAEP or PKCS#1 v1.5 decrypt-only; kari ECDH / X25519 / X448; kekri; pwri; ori/ML-KEM),
636
+ * and decrypts + authenticates the content. `keyMaterial` is `{ key, cert }` (the recipient
637
+ * private key + its certificate, which drives recipient matching), `{ password }`, `{ kek, kekId? }`,
638
+ * or `{ cek }` (EncryptedData raw-key mode). Fail-closed and oracle-free: every secret-dependent
639
+ * failure -- a bad key-wrap, a padding fault, a GCM tag mismatch, a PWRI check-byte mismatch --
640
+ * collapses to the SINGLE uniform `cms/decrypt-failed` verdict (Bleichenbacher / EFAIL oracle
641
+ * freedom), and the PKCS#1 v1.5 arm applies the RFC 3218 implicit-rejection countermeasure so its
642
+ * failure is indistinguishable. `authenticated` is true only for AuthEnvelopedData; a CBC
643
+ * EnvelopedData surfaces `authenticated: false` (the EFAIL caveat in the verdict itself).
644
+ *
645
+ * @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
646
+ * @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
647
+ * @example
648
+ * var res = await pki.cms.decrypt(envDer, { key: recipientKeyPkcs8, cert: recipientCertDer });
649
+ * res.content; // the recovered plaintext Buffer
650
+ */
651
+ var decrypt = cmsDecrypt.decrypt;
652
+
653
+ module.exports = { verify: verify, sign: sign, encrypt: encrypt, decrypt: decrypt };
package/lib/constants.js CHANGED
@@ -227,6 +227,12 @@ var LIMITS = {
227
227
  // enforced BEFORE parsing so a hostile document is refused up front.
228
228
  JSON_MAX_BYTES: BYTES.mib(1),
229
229
  JSON_MAX_DEPTH: 32,
230
+ // PBKDF2 iteration ceiling on the DECRYPT side (RFC 8018 pwri / PBES2). An attacker-supplied
231
+ // iterationCount is attacker-controlled work; a message above this is refused before any
232
+ // derivation (typed cms/iteration-limit), and a caller may cap it lower via opts.maxIterations.
233
+ // 10M clears the authoring default (600k) with generous headroom for high-security policies.
234
+ PBKDF2_MAX_ITERATIONS: 10000000,
235
+ PBKDF2_MAX_SALT: 1024,
230
236
  // ACME challenge token entropy floor (RFC 8555 sec. 8, errata 6950): >= 128
231
237
  // bits of base64url is >= 22 characters. A shorter token is refused before use.
232
238
  ACME_TOKEN_MIN_CHARS: 22,
@@ -0,0 +1,226 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- no operator-facing namespace. The documented surfaces are pki.path.ocspChecker
6
+ // (revocation during path validation) and pki.ocsp.verify (standalone RFC 6960 sec. 3.2 client
7
+ // acceptance), which BOTH compose this ONE verify core. There is deliberately no second, weaker
8
+ // OCSP response-verification path: a standalone verify that re-derived the responder-authorization
9
+ // gates would be the exact fail-open the out-of-path-signer-cert-full-validation discipline exists
10
+ // to prevent.
11
+ //
12
+ // The core is a factory over an INJECTED `deps` seam so it has no back-edge into path-validate:
13
+ // the signature engine (`verifyWithSpki`) and the RFC 5280 cert-profile gates
14
+ // (`decodeExt`/`findExt`/`unrecognizedCriticalExtension`/`validateCriticalExtensionStructure`/
15
+ // `compositeKeyUsageCheck`/`isNullOrAbsentParams`/`spliceSpkiParameters`/`dnEqual`) are supplied by
16
+ // the caller that owns them; everything OCSP-specific (CertID hash binding, responder
17
+ // authorization, currency, status) lives here.
18
+ //
19
+ // Rule set (gap-checked verbatim against RFC 6960 sec. 3.2 / 4.1.1 / 4.2.2.2 / 4.2.2.2.1):
20
+ // - authorizeResponder: the issuing CA directly (responderID identifies the issuer) OR a
21
+ // CA-issued delegate valid at `time` bearing id-kp-OCSPSigning (anyEKU / absent EKU do NOT
22
+ // authorize), keyUsage-permits-signing, no unknown/malformed critical extension, and
23
+ // id-pkix-ocsp-nocheck (a transport-free verify cannot otherwise confirm the responder is
24
+ // unrevoked); the delegate's own issuance signature is verified under the issuer key.
25
+ // - certIdMatches: serial + issuerNameHash + issuerKeyHash under the CertID's OWN hashAlgorithm
26
+ // (a serial-only match is a cross-CA substitution and is rejected).
27
+ // - currency: thisUpdate <= time and a bounded nextUpdate > time (a nextUpdate-less response is
28
+ // unusable per the lightweight profile).
29
+
30
+ var asn1 = require("./asn1-der");
31
+ var oid = require("./oid");
32
+ var x509 = require("./schema-x509");
33
+ var compositeSig = require("./composite-sig");
34
+ var webcrypto = require("./webcrypto");
35
+ var subtle = webcrypto.webcrypto.subtle;
36
+
37
+ // CertID identity-hash algorithms (RFC 6960 sec. 4.1.1). SEPARATE from the signature HASH set
38
+ // (which omits SHA-1, SHAttered): a CertID hash is an identity binding of an already-known issuer,
39
+ // not a signature, so RFC 6960's default SHA-1 CertID MUST interoperate. A hash outside this set
40
+ // cannot be reproduced -> no CertID match (fail closed).
41
+ var OCSP_CERTID_HASHES = {};
42
+ OCSP_CERTID_HASHES[oid.byName("sha1")] = "SHA-1";
43
+ OCSP_CERTID_HASHES[oid.byName("sha256")] = "SHA-256";
44
+ OCSP_CERTID_HASHES[oid.byName("sha384")] = "SHA-384";
45
+ OCSP_CERTID_HASHES[oid.byName("sha512")] = "SHA-512";
46
+ var OID_OCSP_SIGNING = oid.byName("ocspSigning");
47
+ var OID_OCSP_NOCHECK = oid.byName("ocspNoCheck");
48
+ var OID_EKU = oid.byName("extKeyUsage");
49
+ var OID_KEY_USAGE = oid.byName("keyUsage");
50
+
51
+ function ocspDigest(alg, buf) { return subtle.digest(alg, buf).then(function (h) { return Buffer.from(h); }); }
52
+
53
+ // The subjectPublicKey BIT STRING VALUE (past the unused-bits octet) of an SPKI DER -- the exact
54
+ // bytes an OCSP CertID issuerKeyHash / byKey KeyHash hash over (RFC 6960 sec. 4.1.1). Throws on a
55
+ // malformed SPKI; the caller fails closed.
56
+ function ocspKeyValue(spkiDer) {
57
+ return asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
58
+ }
59
+
60
+ // makeOcspVerify(deps) -> the bound OCSP verify core. deps = { verifyWithSpki, decodeExt, findExt,
61
+ // unrecognizedCriticalExtension, validateCriticalExtensionStructure, compositeKeyUsageCheck,
62
+ // isNullOrAbsentParams, spliceSpkiParameters, dnEqual } -- the signature engine + RFC 5280
63
+ // cert-profile gates owned by the caller.
64
+ function makeOcspVerify(deps) {
65
+ var verifyWithSpki = deps.verifyWithSpki;
66
+ var decodeExt = deps.decodeExt;
67
+ var findExt = deps.findExt;
68
+ var unrecognizedCriticalExtension = deps.unrecognizedCriticalExtension;
69
+ var validateCriticalExtensionStructure = deps.validateCriticalExtensionStructure;
70
+ var compositeKeyUsageCheck = deps.compositeKeyUsageCheck;
71
+ var isNullOrAbsentParams = deps.isNullOrAbsentParams;
72
+ var spliceSpkiParameters = deps.spliceSpkiParameters;
73
+ var dnEqual = deps.dnEqual;
74
+
75
+ // The delegate responder's importable SPKI, splicing the issuer's algorithm parameters in when
76
+ // the delegate key inherits them (an EC SPKI that omits the namedCurve, RFC 5280 sec. 4.1.2.7).
77
+ function ocspResponderSpki(rc, issuer) {
78
+ var keyAlg = rc.subjectPublicKeyInfo.algorithm;
79
+ if (!isNullOrAbsentParams(keyAlg.parameters)) return rc.subjectPublicKeyInfo.bytes;
80
+ var issuerOid, issuerParams;
81
+ // Coverage residual -- the catch is unreachable: this runs only after the delegate's signature
82
+ // verified under issuer.workingPublicKey, so that SPKI already imported and decodes cleanly.
83
+ try {
84
+ var alg = asn1.decode(issuer.workingPublicKey).children[0];
85
+ issuerOid = asn1.read.oid(alg.children[0]);
86
+ issuerParams = alg.children[1] ? alg.children[1].bytes : null;
87
+ } catch (_e) { return rc.subjectPublicKeyInfo.bytes; }
88
+ if (issuerOid === keyAlg.oid && !isNullOrAbsentParams(issuerParams)) {
89
+ return spliceSpkiParameters(rc.subjectPublicKeyInfo, keyAlg.oid, issuerParams);
90
+ }
91
+ return rc.subjectPublicKeyInfo.bytes;
92
+ }
93
+
94
+ // Returns true if `extList` carries any critical extension (this code processes no OCSP
95
+ // response/single extension semantics, so a critical one makes the response unusable).
96
+ function ocspHasCriticalExtension(extList) {
97
+ if (!extList) return false;
98
+ for (var i = 0; i < extList.length; i++) if (extList[i].critical) return true;
99
+ return false;
100
+ }
101
+
102
+ // A SingleResponse's CertID names the target cert IFF serial AND issuerNameHash + issuerKeyHash
103
+ // (under the CertID's OWN hashAlgorithm) match the issuer. `issuerNameCandidates` is every RFC
104
+ // 5280 sec. 7.1-equal byte encoding of the validated issuer DN to try.
105
+ async function ocspCertIdMatches(certID, cert, issuerNameCandidates, issuerKeyBits) {
106
+ if (certID.serialNumberHex !== cert.serialNumberHex) return false;
107
+ var hashName = OCSP_CERTID_HASHES[certID.hashAlgorithm.oid];
108
+ if (!hashName) return false;
109
+ var keyHash = await ocspDigest(hashName, issuerKeyBits);
110
+ if (!certID.issuerKeyHash.equals(keyHash)) return false;
111
+ for (var i = 0; i < issuerNameCandidates.length; i++) {
112
+ if (certID.issuerNameHash.equals(await ocspDigest(hashName, issuerNameCandidates[i]))) return true;
113
+ }
114
+ return false;
115
+ }
116
+
117
+ // Resolve the signer of a BasicOCSPResponse to an AUTHORIZED responder SPKI DER, or null. Two
118
+ // models: the issuing CA directly, or a CA-delegated responder (RFC 6960 sec. 4.2.2.2). Fails
119
+ // closed at every branch.
120
+ async function ocspAuthorizeResponder(basicResponse, cert, issuer, issuerKeyBits, time) {
121
+ var rid = basicResponse.responderID;
122
+ var matchesIssuer = false;
123
+ try {
124
+ if (rid.byName) matchesIssuer = dnEqual(rid.byName.rdns, cert.issuer.rdns);
125
+ else if (rid.byKey) matchesIssuer = rid.byKey.equals(await ocspDigest("SHA-1", issuerKeyBits));
126
+ } catch (_e) { matchesIssuer = false; }
127
+ if (matchesIssuer) return issuer.workingPublicKey;
128
+
129
+ for (var i = 0; i < basicResponse.certs.length; i++) {
130
+ var rc;
131
+ try { rc = x509.parse(basicResponse.certs[i]); }
132
+ catch (_e) { continue; }
133
+ var identifies = false;
134
+ try {
135
+ if (rid.byName) identifies = dnEqual(rid.byName.rdns, rc.subject.rdns);
136
+ else if (rid.byKey) identifies = rid.byKey.equals(await ocspDigest("SHA-1", ocspKeyValue(rc.subjectPublicKeyInfo.bytes)));
137
+ } catch (_e) { identifies = false; }
138
+ if (!identifies) continue;
139
+ // The delegate MUST be issued directly by the CA that issued the target.
140
+ var issuedByCa;
141
+ try { issuedByCa = dnEqual(rc.issuer.rdns, cert.issuer.rdns); }
142
+ catch (_e) { continue; }
143
+ if (!issuedByCa) continue;
144
+ if (!isOctetAligned(rc.signatureValue)) continue;
145
+ if (!(await verifyWithSpki(rc.signatureAlgorithm, rc.signatureValue.bytes, issuer.workingPublicKey, rc.tbsBytes))) continue;
146
+ // The delegate cert MUST itself be valid at the validation instant.
147
+ if (time < rc.validity.notBefore || time > rc.validity.notAfter) continue;
148
+ // The delegate MUST assert id-kp-OCSPSigning; anyEKU / an absent EKU do not.
149
+ var eku;
150
+ try { eku = decodeExt(rc, OID_EKU); }
151
+ catch (_e) { continue; }
152
+ if (!eku || eku.value.indexOf(OID_OCSP_SIGNING) === -1) continue;
153
+ // A delegate asserting keyUsage MUST permit digitalSignature; an absent keyUsage is
154
+ // unrestricted, an unreadable one is not authoritative.
155
+ var ku;
156
+ try { ku = decodeExt(rc, OID_KEY_USAGE); }
157
+ catch (_e) { continue; }
158
+ if (ku && ku.value.digitalSignature !== true) continue;
159
+ // An unknown critical extension (or a recognized-but-malformed one) makes the delegate
160
+ // unusable, the same fail-closed rule the path validator applies.
161
+ if (unrecognizedCriticalExtension(rc, false)) continue;
162
+ if (validateCriticalExtensionStructure(rc)) continue;
163
+ // The delegate MUST carry id-pkix-ocsp-nocheck (RFC 6960 sec. 4.2.2.2.1): a transport-free
164
+ // checker cannot otherwise confirm the responder cert is unrevoked.
165
+ if (!findExt(rc, OID_OCSP_NOCHECK)) continue;
166
+ // A composite-keyed delegate gets the same composite keyUsage gate the path certs do.
167
+ if (compositeSig.COMPOSITE_ALGS[rc.subjectPublicKeyInfo.algorithm.oid] && !compositeKeyUsageCheck(rc).ok) continue;
168
+ return ocspResponderSpki(rc, issuer);
169
+ }
170
+ return null;
171
+ }
172
+
173
+ // A BIT STRING is octet-aligned iff its unused-bits count is 0 -- reused from the caller's
174
+ // guard so a non-octet-aligned responder signature is never verified.
175
+ function isOctetAligned(bitString) { return !!bitString && bitString.unusedBits === 0; }
176
+
177
+ // evaluateResponse(resp, cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical) ->
178
+ // a granular per-response SUMMARY for ONE parsed OCSPResponse, aggregating over ALL of its
179
+ // matching SingleResponses (a revoked SingleResponse shadows a good one WITHIN the response, the
180
+ // same fail-closed law the multi-response aggregator applies). Never throws (fail-closed):
181
+ // { applicable, responderAuthorized, signatureValid, matched, revoked:{reason,revocationReason}?,
182
+ // sawGood, sawUnknownStatus, thisUpdate?, nextUpdate?, reason }
183
+ async function evaluateResponse(resp, cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical) {
184
+ if (resp.responseStatus.code !== 0) return { applicable: false, matched: false, reason: "non-successful OCSP responseStatus (" + resp.responseStatus.code + ")" };
185
+ var br = resp.basicResponse;
186
+ // Coverage residual -- unreachable for a parsed response (schema-ocsp enforces the
187
+ // successful<->responseBytes biconditional); backstop for a hand-built object.
188
+ if (!br) return { applicable: false, matched: false, reason: "successful OCSP response carries no BasicOCSPResponse" };
189
+ var signerSpki = await ocspAuthorizeResponder(br, cert, issuer, issuerKeyBits, time);
190
+ if (!signerSpki) return { applicable: true, matched: false, responderAuthorized: false, reason: "no authorized OCSP responder signs this response (RFC 6960 sec. 4.2.2.2)" };
191
+ if (!(await verifyWithSpki(br.signatureAlgorithm, br.signature, signerSpki, br.tbsResponseDataBytes))) {
192
+ return { applicable: true, matched: false, responderAuthorized: true, signatureValid: false, reason: "the OCSP response signature does not verify over tbsResponseData" };
193
+ }
194
+ if (ocspHasCriticalExtension(br.responseExtensions)) {
195
+ return { applicable: true, matched: false, responderAuthorized: true, signatureValid: true, reason: "the OCSP response carries an unrecognized critical extension" };
196
+ }
197
+ var out = { applicable: true, matched: false, responderAuthorized: true, signatureValid: true, revoked: null, sawGood: false, sawUnknownStatus: false, reason: "no current SingleResponse names this certificate" };
198
+ for (var s = 0; s < br.responses.length; s++) {
199
+ var sr = br.responses[s];
200
+ if (!(await ocspCertIdMatches(sr.certID, cert, issuerNameCandidates, issuerKeyBits))) continue;
201
+ if (ocspHasCriticalExtension(sr.singleExtensions)) continue;
202
+ if (sr.thisUpdate > time) continue;
203
+ if (!sr.nextUpdate || sr.nextUpdate < time) continue;
204
+ out.matched = true; out.thisUpdate = sr.thisUpdate; out.nextUpdate = sr.nextUpdate;
205
+ var st = sr.certStatus;
206
+ if (st.type === "revoked") {
207
+ // Present-time validation revokes regardless of a future revocationTime (skew/post-dating);
208
+ // only explicit historical validation defers a strictly-future revocation (reports good).
209
+ if (historical && st.revocationTime instanceof Date && st.revocationTime.getTime() > time.getTime()) { out.sawGood = true; }
210
+ else if (!out.revoked) { out.revoked = { revocationReason: st.revocationReason || null, reason: "certificate reported revoked by an authorized OCSP responder" + (st.revocationReason ? " (" + st.revocationReason + ")" : "") }; }
211
+ } else if (st.type === "good") { out.sawGood = true; }
212
+ else { out.sawUnknownStatus = true; }
213
+ }
214
+ return out;
215
+ }
216
+
217
+ return {
218
+ evaluateResponse: evaluateResponse,
219
+ authorizeResponder: ocspAuthorizeResponder,
220
+ certIdMatches: ocspCertIdMatches,
221
+ hasCriticalExtension: ocspHasCriticalExtension,
222
+ responderSpki: ocspResponderSpki,
223
+ };
224
+ }
225
+
226
+ module.exports = { makeOcspVerify: makeOcspVerify, ocspKeyValue: ocspKeyValue, ocspDigest: ocspDigest, OCSP_CERTID_HASHES: OCSP_CERTID_HASHES };