@blamejs/pki 0.6.13 → 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.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
package/lib/cms-verify.js CHANGED
@@ -39,10 +39,8 @@ var cmsEncrypt = require("./cms-encrypt");
39
39
  var cmsDecrypt = require("./cms-decrypt");
40
40
  var cmsCompress = require("./cms-compress");
41
41
  var signScheme = require("./sign-scheme");
42
- // The digest-strength question is asked of sign-scheme, which owns the policy table, so the signer
43
- // and this verifier cannot drift (RFC 9882 sec. 3.3).
44
42
  var _mldsaDigestSuitable = signScheme.mldsaDigestSuitable;
45
- var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID; // shared SLH-DSA set -> { wc, digest } (RFC 9814 sec. 4 pinned digest)
43
+ var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID;
46
44
  var validator = require("./validator-all");
47
45
  var compositeSig = require("./composite-sig");
48
46
  var guard = require("./guard-all");
@@ -52,11 +50,6 @@ var pkix = require("./schema-pkix");
52
50
 
53
51
  var CmsError = frameworkError.CmsError;
54
52
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
55
- // Promise.resolve captured at load and invoked through the captured Reflect.apply, so a streamed
56
- // opts.content -- whose iterator runs during the digest await and can replace Promise.resolve -- cannot
57
- // turn a resolved { ok: false } verdict into acceptance. The verify path resolves every value through
58
- // this and awaits every step: Promise.all is deliberately NOT used, since its spec-internal element
59
- // handling dispatches through the caller-replaceable Promise.prototype.then, which await does not.
60
53
  var _Promise = Promise, _promiseResolveRaw = Promise.resolve;
61
54
  function _pResolve(v) { return intrinsic.apply(_promiseResolveRaw, _Promise, [v]); }
62
55
 
@@ -64,41 +57,19 @@ var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
64
57
  var OID_CONTENT_TYPE = oid.byName("contentType");
65
58
  var OID_COUNTERSIGNATURE = oid.byName("countersignature");
66
59
 
67
- // A digest-algorithm name -> the engine's hash name, for computing a MESSAGE DIGEST (the
68
- // message-digest attribute and the content digest). The FIPS 202 extendable-output functions
69
- // belong here at the lengths RFC 8702 sec. 4 fixes for that use (SHAKE128 32 bytes, SHAKE256 64).
70
60
  var DIGEST_HASH = {
71
61
  sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512",
72
62
  shake128: "SHAKE128", shake256: "SHAKE256",
73
63
  };
74
- // The SIGNATURE hash a bare-key-OID scheme (rsaEncryption, ecPublicKey) inherits from the
75
- // SignerInfo digestAlgorithm. Deliberately a SEPARATE table from DIGEST_HASH, and deliberately
76
- // without the extendable-output functions: RFC 8702 sec. 3.2 gives RSASSA-PKCS1-v1_5-with-SHAKE
77
- // and ECDSA-with-SHAKE their own signature OIDs and never pairs a bare key OID with a SHAKE
78
- // digestAlgorithm, so that combination is non-conformant and must keep its precise refusal.
79
- // Folding the two roles into one table would resolve a signature hash for it instead, letting
80
- // it past the fail-closed gate below and into the engine, where the caller gets a relabeled
81
- // foreign fault and loses this module's own unsupported-algorithm verdict.
82
64
  var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
83
- // Is `name` a message-digest algorithm this verifier supports?
84
65
  function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
85
- // The digest of `content` under the named algorithm, resolved to a Buffer.
86
66
  async function _computeDigest(name, content) {
87
67
  return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
88
68
  }
89
- // A signatureAlgorithm name -> its verify scheme. A combined OID (sha256WithRSAEncryption,
90
- // ecdsaWithSHA256) carries its own hash; a bare key OID (rsaEncryption, ecPublicKey) takes the
91
- // hash from the SignerInfo digestAlgorithm.
92
- // `params` pins the AlgorithmIdentifier parameters shape RFC 5754 requires: "null" for the
93
- // RSASSA-PKCS1-v1_5 family (RFC 4055), "absent" for ECDSA (RFC 5758) and EdDSA (RFC 8410).
94
- // RSASSA-PSS omits it -- its parameters ARE the RSASSA-PSS-params, resolved by _resolvePss.
95
69
  var SIG_SCHEME = {
96
70
  rsaEncryption: { kind: "rsa", params: "null" },
97
71
  rsassaPss: { kind: "rsapss" },
98
72
  ecPublicKey: { kind: "ec", params: "absent" },
99
- // One-shot families (EdDSA, ML-DSA): the same OID identifies the key and the signature, so the
100
- // signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID. `sameKeyOid` enables
101
- // that agreement check (RFC 8410 / RFC 9882; enforced in _verifyAgainstCandidates).
102
73
  Ed25519: { kind: "eddsa", name: "Ed25519", params: "absent", sameKeyOid: true },
103
74
  Ed448: { kind: "eddsa", name: "Ed448", params: "absent", sameKeyOid: true },
104
75
  "id-ml-dsa-44": { kind: "mldsa", name: "ML-DSA-44", params: "absent", sameKeyOid: true },
@@ -113,17 +84,11 @@ var SIG_SCHEME = {
113
84
  ecdsaWithSHA384: { kind: "ec", hash: "SHA-384", params: "absent" },
114
85
  ecdsaWithSHA512: { kind: "ec", hash: "SHA-512", params: "absent" },
115
86
  };
116
- // SLH-DSA (RFC 9814): the twelve pure FIPS 205 sets, seeded like webcrypto's SLH_DSA_NODE. Each is
117
- // a one-shot family (kind "slhdsa"), params absent, sig-OID == key-OID (sameKeyOid). The WebCrypto
118
- // name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5: one name map).
119
87
  ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
120
88
  "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
121
89
  ].forEach(function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + s.toUpperCase(), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
122
- // The signatureAlgorithm parameters match the algorithm's fixed shape (RFC 5754). A DER NULL
123
- // is exactly `05 00`; "absent" is the parameters field omitted entirely.
124
90
  function _isDerNull(p) { return Buffer.isBuffer(p) && p.length === 2 && p[0] === 0x05 && p[1] === 0x00; }
125
91
  function _algParamsOk(shape, p) { return shape === "null" ? _isDerNull(p) : (p === null || p === undefined); }
126
- // An EC named-curve OID (in the signer cert SPKI) -> the WebCrypto curve + r/s coordinate width.
127
92
  var EC_CURVE = {};
128
93
  EC_CURVE[oid.byName("prime256v1")] = { curve: "P-256", coordLen: 32 };
129
94
  EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
@@ -134,17 +99,11 @@ function _toBuf(v, what) {
134
99
  throw _err("cms/bad-input", what + " must be a Buffer");
135
100
  }
136
101
 
137
- // Every certificate a SignerInfo's signer identifier matches, among the SignedData's embedded
138
- // certificates plus any the caller supplies. issuerAndSerialNumber matches on the canonical
139
- // DN + serial; subjectKeyIdentifier matches the cert's SKI extension. ALL matches are returned
140
- // (in candidate order) so a colliding certificate placed before the real signer cannot hide it.
141
102
  function _findSignerCerts(sid, parsedCerts) {
142
103
  var out = [];
143
104
  for (var i = 0; i < parsedCerts.length; i++) {
144
105
  var c = parsedCerts[i];
145
106
  if (sid.subjectKeyIdentifier != null) {
146
- // Captured equality: over a streamed content the untrusted iterator ran during the digest await
147
- // and could replace Buffer.prototype.equals to match the wrong certificate to a signer.
148
107
  if (c.ski && intrinsic.bufferEquals(c.ski, _toBuf(sid.subjectKeyIdentifier, "sid.subjectKeyIdentifier"))) intrinsic.push(out, c);
149
108
  } else if (sid.issuer && sid.serialNumberHex != null) {
150
109
  if (c.cert.serialNumberHex === sid.serialNumberHex && guard.name.dnEqual(c.cert.issuer.rdns, sid.issuer.rdns, _err, "cms/bad-name", "the signer certificate issuer")) intrinsic.push(out, c);
@@ -153,26 +112,14 @@ function _findSignerCerts(sid, parsedCerts) {
153
112
  return out;
154
113
  }
155
114
 
156
- // Verify `sigBytes` over `signedBytes` against each matching candidate certificate in turn; the
157
- // signer is valid iff one candidate verifies. A candidate whose key is structurally unusable (a
158
- // low-order EdDSA point, an unsupported curve) or whose signature simply does not verify falls
159
- // through to the next -- so a colliding certificate cannot make a valid signature read invalid.
160
- // When no candidate verifies, a plain false is a code-less verdict; the last structural fault's
161
- // code is surfaced if one occurred (so the diagnostic is not lost).
162
115
  async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, candidates, pssSalt, expectedKeyOid) {
163
116
  var lastErr = null;
164
117
  for (var idx = 0; idx < candidates.length; idx++) {
165
118
  var c = candidates[idx];
166
- // One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
167
- // SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
168
- // verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
169
119
  if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
170
120
  lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
171
121
  continue;
172
122
  }
173
- // `await`, not `.then`: over a streamed content the untrusted iterator ran during the digest await
174
- // and could replace Promise.prototype.then to return a fabricated `true`; await is immune (it does
175
- // not dispatch through the caller-replaceable prototype method).
176
123
  try {
177
124
  var ok = await _verifySignature(scheme, sigHash, sigBytes, c.cert.subjectPublicKeyInfo.bytes, signedBytes, _certCurveOid(c.cert), pssSalt);
178
125
  if (ok === true) return { ok: true, sid: sid, cert: c.der };
@@ -184,7 +131,6 @@ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes,
184
131
  : { ok: false, sid: sid, cert: candidates[0].der };
185
132
  }
186
133
 
187
- // The cert's subjectKeyIdentifier extension value (the raw key id), or null.
188
134
  function _certSki(cert) {
189
135
  var ext = (cert.extensions || []).filter(function (e) { return e.oid === oid.byName("subjectKeyIdentifier"); })[0];
190
136
  if (!ext) return null;
@@ -193,17 +139,12 @@ function _certSki(cert) {
193
139
  } catch (_e) { return null; }
194
140
  }
195
141
 
196
- // RSASSA-PSS-params resolution (RFC 4055). A SHA-2 hashAlgorithm name -> its WebCrypto hash
197
- // and the salt length WebCrypto verifies (the hash-length salt of the supported profile).
198
142
  var OID_MGF1 = oid.byName("mgf1");
199
143
  var PSS_HASH = {};
200
144
  PSS_HASH[oid.byName("sha256")] = "SHA-256";
201
145
  PSS_HASH[oid.byName("sha384")] = "SHA-384";
202
146
  PSS_HASH[oid.byName("sha512")] = "SHA-512";
203
147
 
204
- // A hash AlgorithmIdentifier { OID, parameters? } whose parameters, when present, MUST be a
205
- // DER NULL with empty content (RFC 4055 sec. 2.1 / RFC 5754). Returns the hash OID, or null
206
- // on any malformed shape.
207
148
  function _hashAlgOid(seq) {
208
149
  if (!seq || seq.tagClass !== "universal" || seq.tagNumber !== asn1.TAGS.SEQUENCE || !seq.children || seq.children.length < 1 || seq.children.length > 2) return null;
209
150
  var o;
@@ -220,12 +161,6 @@ function _hashAlgOid(seq) {
220
161
  return o;
221
162
  }
222
163
 
223
- // Resolve RSASSA-PSS-params (RFC 4055) to { hash, saltLength }, or null if the parameters
224
- // deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the same
225
- // hash, saltLength equal to the hash length, and trailerField 1. The SHA-1 DEFAULTs are
226
- // rejected (params must be explicit), matching the certification-path validator, so a
227
- // non-conformant PSS AlgorithmIdentifier is a fail-closed verdict. Falling back to WebCrypto's
228
- // own defaults would be a signatureAlgorithm bypass.
229
164
  function _resolvePss(paramsBytes) {
230
165
  if (!paramsBytes) return null;
231
166
  var n;
@@ -236,8 +171,6 @@ function _resolvePss(paramsBytes) {
236
171
  var hash = null, saltLen = null, mgfNode = null, trailer = 1n, last = -1;
237
172
  for (var i = 0; i < n.children.length; i++) {
238
173
  var f = n.children[i];
239
- // Each field is an EXPLICIT context [0..3] wrapper carrying exactly one value, at most
240
- // once and in ascending order; anything else is malformed.
241
174
  if (f.tagClass !== "context" || f.tagNumber > 3 || f.tagNumber <= last || !f.children || f.children.length !== 1) return null;
242
175
  last = f.tagNumber;
243
176
  try {
@@ -247,7 +180,7 @@ function _resolvePss(paramsBytes) {
247
180
  else { trailer = asn1.read.integer(f.children[0]); }
248
181
  } catch (_e3) { return null; }
249
182
  }
250
- if (hash === null || mgfNode === null) return null; // both MUST be explicit (SHA-1 default rejected)
183
+ if (hash === null || mgfNode === null) return null;
251
184
  if (mgfNode.tagClass !== "universal" || mgfNode.tagNumber !== asn1.TAGS.SEQUENCE || !mgfNode.children || mgfNode.children.length !== 2) return null;
252
185
  var mgfOid;
253
186
  try {
@@ -255,21 +188,11 @@ function _resolvePss(paramsBytes) {
255
188
  } catch (_e4) { return null; }
256
189
  if (mgfOid !== OID_MGF1 || PSS_HASH[_hashAlgOid(mgfNode.children[1])] !== hash) return null;
257
190
  if (trailer !== 1n) return null;
258
- // saltLength: an absent field is the RFC 4055 DEFAULT of 20; a declared value is honored and
259
- // passed through to WebCrypto (RSASSA-PSS is not pinned to hash-length salt). It is bounded to
260
- // a non-negative int -- a negative value maps to OpenSSL's RSA_PSS_SALTLEN_DIGEST/AUTO/MAX
261
- // magic, and AUTO accepts any salt length, defeating the salt-length binding.
262
191
  var saltLength = saltLen === null ? 20 : guard.range.uint31(saltLen, _err, "cms/unsupported-algorithm", "RSASSA-PSS saltLength");
263
192
  return { hash: hash, saltLength: saltLength };
264
193
  }
265
194
 
266
- // Verify the signature over `signedBytes` with the signer certificate's public key under the
267
- // resolved scheme + hash. Resolves true/false (a false verdict is a verdict); a structural
268
- // fault throws a typed CmsError.
269
195
  async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOid, pssSalt) {
270
- // `await` throughout, never `.then`: a streamed content's untrusted iterator ran during the digest
271
- // await and could replace Promise.prototype.then to answer a fabricated `true`; await does not
272
- // dispatch through it.
273
196
  if (scheme.kind === "rsa") {
274
197
  var kRsa = await subtle.importKey("spki", spki, { name: "RSASSA-PKCS1-v1_5", hash: hashName }, false, ["verify"]);
275
198
  return subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, kRsa, sigBytes, signedBytes);
@@ -281,36 +204,23 @@ async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, c
281
204
  if (scheme.kind === "ec") {
282
205
  var ec = EC_CURVE[curveOid];
283
206
  if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
284
- // The ORDER-AWARE gate: r,s in [1, n-1] per FIPS 186-5 sec. 6.4.2 (rejecting an out-of-range r/s >= the
285
- // curve order n, not only the r=s=0 forgery). A verifier that knows the curve MUST use it.
286
207
  var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
287
208
  var kEc = await subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"]);
288
209
  return subtle.verify({ name: "ECDSA", hash: hashName }, kEc, raw, signedBytes);
289
210
  }
290
211
  if (scheme.kind === "mldsa" || scheme.kind === "slhdsa") {
291
- // ML-DSA (RFC 9882) / SLH-DSA (RFC 9814): a one-shot post-quantum verify, pure mode, empty
292
- // context -- no external signature hash and no Edwards-point validation (FIPS 204 sec. 5.3 /
293
- // FIPS 205 sec. 10.3; node structurally validates the SPKI on import). The signature is raw and
294
- // fixed-length (not ECDSA-DER).
295
212
  var kPq = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
296
213
  return subtle.verify({ name: scheme.name }, kPq, sigBytes, signedBytes);
297
214
  }
298
- // EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
299
- // OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
300
- // all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
301
215
  _requireValidEdPoint(spki, scheme.name);
302
216
  var kEd = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
303
217
  return subtle.verify({ name: scheme.name }, kEd, sigBytes, signedBytes);
304
218
  }
305
219
 
306
- // The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point: reject an
307
- // off-curve or low-order key before it verifies a signature (WebCrypto import does not check it).
308
- // Routed through the shared edwards-point gate every EdDSA verify path uses; curve from the name.
309
220
  function _requireValidEdPoint(spkiBytes, name) {
310
221
  edwardsPoint.validateSpki(spkiBytes, name === "Ed25519" ? 6 : 7, CmsError, "cms/bad-signature");
311
222
  }
312
223
 
313
- // The EC named-curve OID carried in a signer cert's SubjectPublicKeyInfo, or null (non-EC).
314
224
  function _certCurveOid(cert) {
315
225
  var p = cert.subjectPublicKeyInfo.algorithm.parameters;
316
226
  if (cert.subjectPublicKeyInfo.algorithm.oid !== oid.byName("ecPublicKey") || !Buffer.isBuffer(p)) return null;
@@ -319,11 +229,6 @@ function _certCurveOid(cert) {
319
229
  } catch (_e) { return null; }
320
230
  }
321
231
 
322
- // Decode the SignedAttributes SET OF Attribute from the exact bytes the signature covers
323
- // (the re-tagged signedAttrsBytes), returning [{ type, values: [valueNode] }]. Binding the
324
- // content-type / message-digest checks to these decoded attributes means a parsed-object input
325
- // cannot desync the checked attributes from the verified preimage (RFC 5652 sec. 5.4). The
326
- // caller-mutable parsed si.signedAttrs is deliberately left out of that decision.
327
232
  function _decodeSignedAttrs(setOfBytes) {
328
233
  var set = asn1.decode(setOfBytes);
329
234
  if (set.tagClass !== "universal" || set.tagNumber !== asn1.TAGS.SET || !set.children) throw _err("cms/bad-signed-attrs", "signedAttrs is not a SET OF Attribute");
@@ -335,90 +240,40 @@ function _decodeSignedAttrs(setOfBytes) {
335
240
  });
336
241
  }
337
242
 
338
- // Verify one SignerInfo. Returns { ok, sid, cert, ... }, where a resolved false is a verdict and
339
- // a structural failure carries a code.
340
- // `csTarget`, when set (a Buffer), makes this a COUNTERSIGNATURE verify (RFC 5652 sec. 11.4): the
341
- // preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
342
- // content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
343
- // unused in that mode.
344
243
  async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, digestByName) {
345
- // A composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs) is keyed by its composite
346
- // OID, not a SIG_SCHEME name; intercept it before the classical dispatch (its verify has no
347
- // single external signature hash, so it never routes through the SIG_SCHEME/sigHash gate below).
348
244
  var composite = compositeSig.COMPOSITE_ALGS[si.signatureAlgorithm.oid];
349
245
  if (composite) return _verifyComposite(si, composite, content, eContentType, parsedCerts, csTarget, digestByName);
350
246
  var scheme = SIG_SCHEME[si.signatureAlgorithm.name];
351
247
  if (!scheme) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported signature algorithm " + JSON.stringify(si.signatureAlgorithm.name) });
352
248
  var digestHash = SIG_HASH[si.digestAlgorithm.name];
353
- // RSASSA-PSS takes its signature hash and salt length from the RSASSA-PSS-params, not the
354
- // digestAlgorithm; a non-conformant parameter set is a fail-closed unsupported verdict.
355
249
  var pss = scheme.kind === "rsapss" ? _resolvePss(si.signatureAlgorithm.parameters) : null;
356
250
  if (scheme.kind === "rsapss" && !pss) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported or non-conformant RSASSA-PSS parameters (RFC 4055)" });
357
- // signatureAlgorithm is outside the signed preimage, so a forbidden/missing parameters field
358
- // (a present parameter on ECDSA/EdDSA, a non-NULL or absent one on RSA) must fail closed --
359
- // otherwise it is a parser differential a caller trusting the verdict inherits (RFC 5754).
360
251
  if (scheme.params && !_algParamsOk(scheme.params, si.signatureAlgorithm.parameters)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.signatureAlgorithm.name + " signature algorithm parameters must be " + (scheme.params === "null" ? "DER NULL (RFC 4055)" : "absent (RFC 5758/8410)") });
361
- // The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
362
- // absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
363
- // must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
364
- // SignerInfo carrying no signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
365
- // has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
366
252
  var dp = si.digestAlgorithm.parameters;
367
253
  var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
368
254
  if (!mldsaNoAttrs && dp !== null && dp !== undefined && !_isDerNull(dp)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " digest algorithm parameters must be absent or DER NULL (RFC 5754 sec. 2)" });
369
- // A SHAKE digest algorithm identifier (id-shake128 / id-shake256) carries NO parameters at all --
370
- // not even a DER NULL (RFC 8702 sec. 3.1) -- so for a one-shot PQC SignerInfo (ML-DSA per RFC 9882,
371
- // SLH-DSA per RFC 9814 sec. 4, both citing RFC 8702) with signed attributes a present SHAKE
372
- // parameter fails closed. The SHA-2 digests (id-sha256/384/512) follow RFC 5754, which requires a
373
- // verifier to accept absent OR NULL (the generic rule above); the RFCs' omit-on-encode rule binds
374
- // the signer, not the verifier, so a SHA-2 NULL is not tightened here.
375
255
  if ((scheme.kind === "mldsa" || scheme.kind === "slhdsa") && si.signedAttrsBytes && (si.digestAlgorithm.name === "shake128" || si.digestAlgorithm.name === "shake256") && dp !== null && dp !== undefined) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "a SHAKE digestAlgorithm carries no parameters (RFC 8702 sec. 3.1)" });
376
256
  var sigHash = pss ? pss.hash : (scheme.hash || digestHash);
377
- // The signature hash is required for every non-EdDSA scheme (EdDSA hashes internally);
378
- // the content digest is required whenever signed attributes are present, for every scheme
379
- // (the message-digest attribute is computed under digestAlgorithm). Either gap is a
380
- // fail-closed unsupported-algorithm verdict, never a foreign-domain throw from the digest.
381
257
  if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
382
- // ML-DSA (RFC 9882 sec. 3.3): with signed attributes present, the message-digest algorithm MUST
383
- // meet the parameter set's security strength -- a below-strength digest is the weaker link and is
384
- // rejected fail-closed (the RFC's "verifiers MAY reject", taken by the strict-verifier posture).
385
- // With signed attributes absent the digestAlgorithm has no meaning (sec. 3.3) and is not checked.
386
258
  if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
387
- // RFC 9814 sec. 4: an SLH-DSA SignerInfo's message-digest algorithm is the one paired with the
388
- // parameter set (the sec. 4 list, always at least twice the tree-hash size); the signer emits it
389
- // and this strict verifier requires it, so a digest that does not match the set's paired hash fails
390
- // closed -- otherwise a signer could compute the message-digest attribute under a weaker hash the
391
- // set does not pair. Gated on signed attributes (absent -> the digest is not consulted, sec. 4).
392
259
  if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
393
260
  var signers = _findSignerCerts(si.sid, parsedCerts);
394
261
  if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
395
262
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
396
263
 
397
- // `await`, not `.then`: see _verifyAgainstCandidates -- a streamed content's iterator could replace
398
- // Promise.prototype.then to skip the signed-bytes computation and fabricate a passing verdict.
399
264
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
400
265
  if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
401
266
  return _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, si.sid, signers, pss ? pss.saltLength : 0, scheme.sameKeyOid ? oid.byName(si.signatureAlgorithm.name) : null);
402
267
  }
403
268
 
404
- // The exact bytes a countersignature covers (RFC 5652 sec. 11.4): the countersigned SignerInfo's
405
- // signature value octets (`csTarget`). With signed attributes present, the message-digest attribute
406
- // MUST equal digest(csTarget) under the countersignature's own digestAlgorithm and the signature
407
- // covers the re-tagged SignedAttributes ([0] -> universal SET OF) -- the identical transform the
408
- // top-level signer uses, minus the content-type attribute (which sec. 11.4 FORBIDS). Resolves the
409
- // signed bytes, or { mismatch } when the message-digest disagrees.
410
269
  async function _computeCountersigBytes(si, csTarget) {
411
- // async, not `_pResolve().then`: a synchronous throw still becomes a rejection, and the digest await
412
- // below dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
413
- if (!si.signedAttrsBytes) return csTarget; // no signed attributes: sign over the target signature octets directly
270
+ if (!si.signedAttrsBytes) return csTarget;
414
271
  var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
415
- reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
272
+ reTagged[0] = 0x31;
416
273
  var attrs;
417
274
  try {
418
275
  attrs = _decodeSignedAttrs(reTagged);
419
276
  } catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "the countersignature signedAttrs is not a valid SET OF Attribute", e); }
420
- // sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
421
- // verify never relies on the parse alone: a parsed-object input could carry one).
422
277
  if (intrinsic.filter(attrs, function (a) { return a.type === OID_CONTENT_TYPE; }).length) throw _err("cms/misplaced-attr", "a countersignature must not carry a content-type attribute (RFC 5652 sec. 11.4)");
423
278
  var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
424
279
  if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "a countersignature's signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 11.4)");
@@ -431,19 +286,8 @@ async function _computeCountersigBytes(si, csTarget) {
431
286
  return reTagged;
432
287
  }
433
288
 
434
- // The exact bytes the signature covers (RFC 5652 sec. 5.4), shared by the classical and composite
435
- // verify paths so both bind the message-digest / content-type checks to the VERIFIED preimage (not
436
- // the caller-mutable parsed si.signedAttrs). Resolves the signed bytes, or { mismatch } when a
437
- // content-type / message-digest attribute disagrees.
438
289
  async function _computeSignedBytes(si, content, eContentType, digestByName) {
439
- // async, not `_pResolve().then`: a synchronous throw still becomes a rejection, and every digest step
440
- // dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
441
290
  if (!si.signedAttrsBytes) {
442
- // No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). A streamed
443
- // content is not retained, so there are no content bytes to verify that signature against; this
444
- // is a per-signer verdict, not a whole-message abort, so the other signers of a mixed message
445
- // still verify (the same shape the buffered path gives every per-signer condition). `digestByName`
446
- // is the streaming marker: an object (possibly empty) when streamed, null when buffered.
447
291
  if (digestByName) {
448
292
  return { mismatch: {
449
293
  code: "cms/streamed-content-unverifiable",
@@ -451,12 +295,6 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
451
295
  "verified against a streamed content, whose bytes are not retained; pass opts.content as a Buffer",
452
296
  } };
453
297
  }
454
- // Signing content directly is also exactly what a stripped-attributes forgery looks like, because
455
- // a CMS signature does not commit to whether attributes were present -- so a signature made over a
456
- // SignedAttributes block re-presented as one made over content verifies, and with no attributes
457
- // there is no message-digest or content-type attribute left to disagree. Refuse the shape: content
458
- // that parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
459
- // the verifier's place to guess which one it is holding.
460
298
  if (cms.looksLikeSignedAttributes(content)) {
461
299
  return { mismatch: {
462
300
  code: "cms/ambiguous-content",
@@ -467,19 +305,12 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
467
305
  }
468
306
  return content;
469
307
  }
470
- // With signed attributes: decode them from the EXACT bytes the signature covers, namely the
471
- // SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
472
- // (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
473
- // that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
474
- // parsed-object input desync the checked attributes from the verified preimage.
475
308
  var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
476
- reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
309
+ reTagged[0] = 0x31;
477
310
  var attrs;
478
311
  try {
479
312
  attrs = _decodeSignedAttrs(reTagged);
480
313
  } catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "signedAttrs is not a valid SET OF Attribute", e); }
481
- // The content-type attribute MUST be present, single-valued, and equal the eContentType
482
- // (RFC 5652 sec. 5.3).
483
314
  var ctAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_CONTENT_TYPE; });
484
315
  if (ctAttr.length !== 1 || ctAttr[0].values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one content-type attribute (RFC 5652 sec. 5.3)");
485
316
  var ctOid;
@@ -487,72 +318,39 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
487
318
  ctOid = asn1.read.oid(ctAttr[0].values[0]);
488
319
  } catch (e) { throw _err("cms/bad-signed-attrs", "the content-type attribute value is not an OBJECT IDENTIFIER", e); }
489
320
  if (ctOid !== eContentType) return { mismatch: { code: "cms/content-type-mismatch", message: "the content-type signed attribute does not match the SignedData eContentType (RFC 5652 sec. 5.3)" } };
490
- // The message-digest attribute MUST be present, single-valued, and equal the digest of the
491
- // content (RFC 5652 sec. 5.4).
492
321
  var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
493
322
  if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 5.4)");
494
323
  var declared;
495
324
  try {
496
325
  declared = asn1.read.octetString(mdAttr.values[0]);
497
326
  } catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
498
- // Over a buffered content the digest is computed here; over a streamed content it was computed once,
499
- // incrementally, before any signer was verified, and looked up by algorithm.
500
327
  var d = digestByName ? digestByName[si.digestAlgorithm.name] : await _computeDigest(si.digestAlgorithm.name, content);
501
- // Captured equality, not d.equals: over a streamed content the untrusted iterator's next() ran during
502
- // the digest await and could have replaced Buffer.prototype.equals with one that always answers true,
503
- // passing this message-digest check for content that was never signed (CWE-347).
504
328
  if (!intrinsic.bufferEquals(d, declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the message-digest attribute does not match the content digest" } };
505
329
  return reTagged;
506
330
  }
507
331
 
508
- // Verify a composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs). The composite
509
- // verify engine lives in composite-sig.js (shared with certification-path validation); this branch
510
- // enforces the CMS-specific rules the X.509 path does not: params-absent on the signatureAlgorithm
511
- // (sec. 3.4, defense-in-depth over the parse-time _PARAMS_ABSENT gate), the unsupported-arm refusal,
512
- // and the sec. 3.4/sec. 5 coherence gate binding the SignerInfo digestAlgorithm to the arm's Table-1
513
- // pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
514
- // compositeVerify per candidate certificate (both components must verify, with no AND->OR
515
- // downgrade).
516
332
  async function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget, digestByName) {
517
333
  if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
518
334
  return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite signatureAlgorithm parameters must be absent (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
519
335
  }
520
- // The 2 arms Node's WebCrypto surface cannot verify (the brainpool curves) fail closed --
521
- // never a silent single-component accept (the AND->OR downgrade this feature prevents).
522
336
  if (comp.trad.unsupported) {
523
337
  return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
524
338
  }
525
- // draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
526
- // (id-sha256/id-sha512 and id-shake256). That is stricter than the generic RFC 5754 absent-OR-NULL
527
- // rule (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
528
- // field omitted. A present parameter -- even a DER NULL -- is non-conformant and fails closed
529
- // (it is outside the signed preimage, so a parser differential must not verify).
530
339
  var dp = si.digestAlgorithm.parameters;
531
340
  if (dp !== null && dp !== undefined) {
532
341
  return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite " + si.digestAlgorithm.name + " digestAlgorithm parameters must be omitted (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
533
342
  }
534
- // sec. 3.4 / sec. 5 (the one CMS-specific MUST the X.509 path does not cover): the SignerInfo
535
- // digestAlgorithm MUST equal the arm's pre-hash (Table 1) -- compositeVerify takes the pre-hash
536
- // from the OID-keyed descriptor, so a disagreeing digestAlgorithm would recompute the
537
- // message-digest attribute under the WRONG algorithm. The sec. 5 SHOULD-reject is taken
538
- // fail-closed; the MAY-verify-anyway leniency is deliberately not taken.
539
343
  if (si.digestAlgorithm.name !== comp.phCms) {
540
344
  return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the SignerInfo digestAlgorithm " + JSON.stringify(si.digestAlgorithm.name) + " is not the composite " + comp.name + " pre-hash " + JSON.stringify(comp.phCms) + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
541
345
  }
542
346
  var signers = _findSignerCerts(si.sid, parsedCerts);
543
347
  if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
544
348
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
545
- // `await`, not `.then`: see _verifyAgainstCandidates.
546
349
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
547
350
  if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
548
351
  return _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si.sid, signers, si.signatureAlgorithm.oid);
549
352
  }
550
353
 
551
- // Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
552
- // verifies both components. A candidate whose SPKI composite OID != the SignerInfo signatureAlgorithm
553
- // OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
554
- // returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
555
- // none verifies. A clean both-components-checked failure (the AND-downgrade) is a code-less false.
556
354
  async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, candidates, expectedKeyOid) {
557
355
  var lastErr = null;
558
356
  for (var idx = 0; idx < candidates.length; idx++) {
@@ -561,7 +359,6 @@ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si
561
359
  lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
562
360
  continue;
563
361
  }
564
- // `await`, not `.then`: see _verifyAgainstCandidates.
565
362
  var r = await compositeSig.compositeVerify(c.cert.subjectPublicKeyInfo.bytes, sigBytes, signedBytes, comp, CmsError, "cms/unsupported-algorithm", "cms/bad-signature");
566
363
  if (r.ok === true) return { ok: true, sid: sid, cert: c.der };
567
364
  if (r.code) lastErr = (r.error instanceof CmsError) ? r.error : _err(r.code, r.error && r.error.message ? r.error.message : "the composite signature could not be evaluated");
@@ -570,51 +367,25 @@ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si
570
367
  : { ok: false, sid: sid, cert: candidates[0].der };
571
368
  }
572
369
 
573
- // The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
574
- // the signature) with each OID resolved to a name, so a caller can read an attached RFC 3161
575
- // timestamp token or inspect a countersignature attribute. They are never counted toward a
576
- // signer's ok / res.valid.
577
370
  function _surfaceUnsignedAttrs(si) {
578
371
  return intrinsic.map(si.unsignedAttrs || [], function (a) {
579
372
  return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
580
373
  });
581
374
  }
582
375
 
583
- // The AUTHENTICATED attributes, surfaced per signer so a caller reads a signed attribute -- a content
584
- // type, a signing time, a protocol's own transaction state (SCEP's messageType / nonces) -- BOUND to
585
- // the verified signature rather than re-parsed from the untrusted message, where an attacker could
586
- // present attributes the signature never covered. The signature is over the SignedAttributes these
587
- // decode from, so read them only where the signer's `ok` is true.
588
376
  function _surfaceSignedAttrs(si) {
589
377
  return intrinsic.map(si.signedAttrs || [], function (a) {
590
378
  return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
591
379
  });
592
380
  }
593
381
 
594
- // Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
595
- // value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
596
- // countersignature's own countersignatures verify over its signature octets (recursive). A
597
- // countersignature that fails to verify is surfaced ok:false. It is never silently dropped, and
598
- // never allowed to change the primary verdict. A countersignature value that is not a well-formed
599
- // SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
600
- // its content, so such a message is refused whole. The recursion terminates because it only walks
601
- // the FINITE parsed structure: each nested countersignature value is a sub-encoding of its parent,
602
- // and the strict decoder already bounds total nesting by C.LIMITS.DER_MAX_DEPTH at parse
603
- // (CWE-834/770), so a hostile deep chain fails closed before verify.
604
382
  async function _verifyCountersignatures(si, parsedCerts) {
605
- // si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
606
- // recursive path), so _toBuf is a pass-through here. The SignerInfo is already well-formed.
607
383
  var targetSig = _toBuf(si.signature, "the countersigned signature");
608
384
  var values = [];
609
- // Captured enumeration: over a streamed content the untrusted iterator's next() ran during the
610
- // digest await and could replace Array.prototype.forEach / push to drop a failing countersignature.
611
385
  intrinsic.forEach(si.unsignedAttrs || [], function (a) {
612
386
  if (a.type !== OID_COUNTERSIGNATURE) return;
613
387
  intrinsic.forEach(a.values, function (v) { intrinsic.push(values, v && v.bytes ? v.bytes : v); });
614
388
  });
615
- // Sequential, started only as awaited (see _verify): not Promise.all (its element handling dispatches
616
- // through the replaceable Promise.prototype.then), and not started-all-then-awaited (an early
617
- // rejection would leave a later one unobserved).
618
389
  var out = [];
619
390
  for (var _cp = 0; _cp < values.length; _cp++) intrinsic.push(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts));
620
391
  return out;
@@ -622,17 +393,8 @@ async function _verifyCountersignatures(si, parsedCerts) {
622
393
 
623
394
  async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
624
395
  var csSi;
625
- // Coverage residual, and deliberately kept. Every value reaching here was already walked by the
626
- // decoder, which validates an id-countersignature by content and not by the attribute type. A
627
- // malformed one therefore refused the whole message before verify was entered, so this catch
628
- // cannot be reached through the public path. That is settled behavior: a message the decoder
629
- // has found to be malformed has no sound remainder to report a verdict over, so it is refused
630
- // whole instead of surfaced as one failed countersignature.
631
- // cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
632
- // reachable recursively and a backstop that returns a NEGATIVE verdict costs nothing.
633
396
  try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
634
397
  catch (e) { return { ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }; }
635
- // `await`, not `.then`: see _verifyAgainstCandidates.
636
398
  var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig);
637
399
  var nested = await _verifyCountersignatures(csSi, parsedCerts);
638
400
  var node = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
@@ -715,29 +477,13 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
715
477
  * res.valid; // boolean
716
478
  * res.signers[0].ok; // per-signer verdict
717
479
  */
718
- // A private copy of the input when -- and only when -- it is memory the caller can
719
- // still write to. Anything else (a PEM string, and whatever else the parser accepts)
720
- // is returned untouched so this cannot narrow the input contract of the parse it
721
- // feeds; the parser stays the one place that decides what an acceptable input is.
722
480
  function _snapshotIfBytes(input, label) {
723
- // Every byte form the parser accepts -- an ArrayBuffer or a DataView reaches the
724
- // decoder as readily as a Buffer, and leaving those aliased would reopen the
725
- // window for exactly the inputs that came in by the wider door.
726
481
  if (guard.bytes.isByteSource(input)) {
727
482
  return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", label);
728
483
  }
729
484
  return input;
730
485
  }
731
486
 
732
- // For a streamed (async-iterable) detached content: hash the stream ONCE, incrementally, under every
733
- // distinct SUPPORTED SignerInfo digest algorithm, returning { <digestName>: Buffer } (streaming), or
734
- // null (not streaming; digest per-signer over the buffered content). No per-signer condition aborts
735
- // the whole message here, matching the buffered path's per-signer verdicts: a content-only signer
736
- // (no signed attributes, whose signature is over content a stream does not retain) and an unsupported
737
- // digest each contribute no name and are verdicted per-signer by _verifyOne / _computeSignedBytes.
738
- // An empty map is still the streaming marker, so those per-signer verdicts are produced. `streamSource`
739
- // is the lazy stream classified at the door (null when not streaming, buffered per-signer); its
740
- // iterator is acquired only if a digest is actually needed, so an all-content-only message never opens it.
741
487
  async function _resolveContentDigests(parsed, streamSource) {
742
488
  if (streamSource == null) return null;
743
489
  var names = [], seen = {};
@@ -745,18 +491,11 @@ async function _resolveContentDigests(parsed, streamSource) {
745
491
  var dn = si.digestAlgorithm.name;
746
492
  if (si.signedAttrsBytes && _supportedDigest(dn) && !seen[dn]) { seen[dn] = 1; intrinsic.push(names, dn); }
747
493
  });
748
- // No signer needs a streamed digest (all content-only or unsupported): nothing to hash, but the
749
- // empty map still marks streaming mode so each signer gets its per-signer verdict.
750
494
  if (!names.length) return {};
751
- // `await`, not `.then`: this is where the UNTRUSTED stream is consumed, so the very next promise step
752
- // must not dispatch through a Promise.prototype.then the stream may have replaced.
753
495
  var ds;
754
496
  try {
755
497
  ds = await subtle.digestStream(intrinsic.map(names, function (n) { return DIGEST_HASH[n]; }), streamSource);
756
498
  } catch (e) {
757
- // The engine reports malformed streamed content in its own domain; report it in this verb's via the
758
- // shared guard, rather than leaking the engine code. The digest algorithm names are this module's own,
759
- // so a webcrypto/syntax here is never an algorithm fault.
760
499
  guard.bytes.translateStreamError(e, _err, "cms/bad-input");
761
500
  }
762
501
  var byName = {};
@@ -764,56 +503,20 @@ async function _resolveContentDigests(parsed, streamSource) {
764
503
  return byName;
765
504
  }
766
505
 
767
- // Every option pki.cms.verify reads. Adding one here is the only way to make it accepted, so a
768
- // capability cannot arrive with its option silently ignored at this boundary.
769
506
  var _VERIFY_OPTS = { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1 };
770
507
 
771
- // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async). The checks below stay
772
- // synchronous -- they read a caller's mutable options and bytes, and resolving those before any turn
773
- // passes is what stops a value being swapped between the check and the use.
774
508
  function verify(input, opts) {
775
509
  return guard.async.deferred(function () { return _verify(input, opts); });
776
510
  }
777
511
 
778
512
  async function _verify(input, opts) {
779
513
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
780
- // An unrecognized option is refused, not swallowed. This is what kept the missing trust seam
781
- // silent: a caller writing `trustAnchors` before it existed -- or `trustAnchor` now -- got a
782
- // verdict that looked anchored and was not. The guard rejects through a (code, message) FACTORY
783
- // and tests membership with hasOwnProperty, so the permitted set is a lookup object.
784
514
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "cms/bad-input", "pki.cms.verify has an unknown option ");
785
- // Handed MUTABLE BYTES, parse a private copy. This function decodes synchronously
786
- // and checks signatures in a later promise turn, so without the copy every range
787
- // the parse surfaced -- the signed content above all -- stays a view into the
788
- // caller's memory across that await, and a buffer rewritten in the gap would leave
789
- // the result describing one message while the signature was checked over another.
790
- // Re-derived from the bytes the parser read, never trusted as the object it arrives as. A
791
- // SignedData's meaning is a signature over a byte range, but a parsed one presents that range
792
- // (`signedAttrsBytes`, or the encapsulated `eContent` when there are no signed attributes), the
793
- // signature, the algorithms and the certificates as SEPARATE properties. Keep a genuine signer's
794
- // signature and signed attributes and put different content beside them and every part of this
795
- // check passes for a message that signer never signed -- the forgery this verb's own block claims
796
- // to defend (CWE-347). A duck-type test on `signerInfos` cannot see that, because every field is
797
- // individually well-formed; only re-deriving them all from one byte string can.
798
515
  var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
799
516
  return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
800
517
  }, _err, "cms/bad-input", "the SignedData");
801
518
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
802
- // The trust configuration is snapshotted HERE, before opts.content is read at all. A streamed
803
- // content's Symbol.asyncIterator accessor runs the moment asyncStreamOf reads it, one turn before a
804
- // snapshot taken further down would run; from that accessor a caller can replace a global the snapshot
805
- // itself relies on -- the Date it defaults the validation instant from, or the Array.isArray /
806
- // Array.prototype.map / slice it copies the anchors and requiredEku with -- and steer the trust
807
- // decision. Taken before the content object is touched, every value the snapshot reads comes from a
808
- // pristine global. It also stays caller-owned across the later await: the array cannot be re-pointed,
809
- // an anchor's DER rewritten, or the instant moved, once copied -- the same defense the input and the
810
- // certificates already have.
811
519
  var trustCfg = _snapshotTrust(opts);
812
- // Pre-parse every candidate certificate (embedded + caller-supplied) once, BEFORE opts.content is
813
- // touched. The caller-supplied set (opts.certs) is read here; a streamed content's accessor -- run the
814
- // moment asyncStreamOf reads it below -- could otherwise mutate that array, or replace the
815
- // Array.prototype.forEach this loop uses, and change which certificates the signer match and the trust
816
- // build see. Taken first, the candidate pool is the one the caller passed.
817
520
  var parsedCerts = [];
818
521
  (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
819
522
  (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
@@ -821,74 +524,31 @@ async function _verify(input, opts) {
821
524
  var streamSource = null;
822
525
  if (content == null) {
823
526
  if (opts.content == null) throw _err("cms/detached-content-required", "this SignedData is detached; opts.content (the external content) is required");
824
- // A streamed (async-iterable) detached content is classified by asyncStreamOf, which reads
825
- // Symbol.asyncIterator a single time and returns a LAZY stream whose iterator is acquired only when
826
- // it is driven below, after the per-signer digest algorithms are known, so a single-use stream
827
- // serves every signer in one pass and a message needing no streamed digest never acquires it. It is
828
- // not snapshotted: a stream has no bytes to copy, and it is consumed exactly once. A byte source (or
829
- // any non-iterable) yields null and is buffered.
830
527
  streamSource = guard.bytes.asyncStreamOf(opts.content);
831
528
  if (streamSource == null) {
832
- // Copied, not aliased: the digest over this content is computed in a later promise turn,
833
- // and a detached signature's content is the whole of what the signature speaks about. A
834
- // caller who rewrote the buffer in that gap would have the signature checked against the
835
- // original bytes while holding the replacement, and the verdict would report a valid
836
- // signature over content that was never signed.
837
529
  content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
838
530
  }
839
531
  } else if (opts.content != null) {
840
- // A caller supplied external content AND the SignedData is attached. A redundant COPY that equals
841
- // the encapsulated content is fine; content that DIFFERS is a substitution trap -- silently
842
- // preferring the embedded copy would let the caller believe it verified opts.content when the
843
- // signature covers different embedded bytes. Fail closed on a mismatch.
844
532
  if (Buffer.compare(_toBuf(opts.content, "opts.content"), Buffer.from(content)) !== 0) {
845
533
  throw _err("cms/content-conflict", "opts.content disagrees with this SignedData's own encapsulated content (a detached signature must omit eContent)");
846
534
  }
847
535
  }
848
536
  var eContentType = parsed.encapContentInfo.eContentType;
849
- // `await` throughout the aggregation, never `.then`: the streamed content is consumed inside
850
- // _resolveContentDigests, so from here on every promise step must dispatch through no
851
- // caller-replaceable Promise.prototype.then. Captured enumeration (intrinsic.map) guards
852
- // Array.prototype.map; awaiting each promise (never Promise.all) guards the chaining (CWE-347).
853
537
  var digestByName = await _resolveContentDigests(parsed, streamSource);
854
- // Each signer is verified SEQUENTIALLY -- started only as it is awaited. NOT Promise.all: its
855
- // spec-internal element handling calls the caller-replaceable Promise.prototype.then, so a streamed
856
- // content that replaced it could fabricate a resolved `{ ok: true }` for every signer; await
857
- // dispatches through no such method. And NOT started-all-then-awaited-each: an earlier signer's
858
- // rejection would leave a later one's rejection unobserved, which Node can escalate to a crash.
859
- // Per-signer work is small, so the sequential cost is negligible.
860
538
  var signers = [];
861
539
  for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
862
540
  var siOne = parsed.signerInfos[_sp];
863
- // res.valid reflects only the PRIMARY signerInfos; a countersignature / unsigned attribute is
864
- // outside the signature and NEVER flips the top-level verdict. Both are surfaced per signer.
865
541
  var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName);
866
542
  verdict.countersignatures = await _verifyCountersignatures(siOne, parsedCerts);
867
543
  verdict.unsignedAttrs = _surfaceUnsignedAttrs(siOne);
868
- // Whether THIS signer signed attributes or signed the content directly. The two are different
869
- // claims -- attributes bind a content type and a signing time alongside the digest, and content-only
870
- // binds nothing but the bytes -- and RFC 5652 lets a message carry one signer of each. A caller
871
- // whose profile requires attributes (RFC 8551 S/MIME does) can only enforce it if the verdict says
872
- // which they got.
873
544
  verdict.signedAttributesPresent = !!siOne.signedAttrsBytes;
874
- // The authenticated attributes themselves, bound to this signer's verdict (read only when ok).
875
545
  verdict.signedAttributes = _surfaceSignedAttrs(siOne);
876
- // The SignerInfo digestAlgorithm on the primary signer node (the countersignature node already
877
- // carries it), so a caller reading a signer's verdict learns the digest without re-parsing.
878
546
  verdict.digestAlgorithm = siOne.digestAlgorithm.name;
879
547
  intrinsic.push(signers, verdict);
880
548
  }
881
- // The content type travels with the verdict. An operator applying a policy of their own -- "I only
882
- // accept id-data", or a profile that names its own type -- otherwise had to parse the message a
883
- // second time to learn it, and a check that needs a second parse is a check most callers will not write.
884
549
  var res = {
885
- // Signature soundness is decided by comparison for the same reason the trust verdict below is:
886
- // `every` replaced after load reports the whole set sound without reading one signer.
887
550
  valid: signers.length > 0 && guard.list.allMatch(signers, function (s) { return s.ok === true; }),
888
551
  eContentType: parsed.encapContentInfo.eContentType,
889
- // The attached encapsulated content (null for a detached signature). A signer's message-digest
890
- // attribute binds these exact bytes, so a `valid` verdict lets a caller consume the authenticated
891
- // eContent -- SCEP's pkcsPKIEnvelope -- without a second parse of the untrusted message.
892
552
  eContent: parsed.encapContentInfo.eContent != null ? parsed.encapContentInfo.eContent : null,
893
553
  signers: signers,
894
554
  };
@@ -896,47 +556,16 @@ async function _verify(input, opts) {
896
556
  return res;
897
557
  }
898
558
 
899
- // The full path build + validate, injected by path-validate the way it injects into crl-verify and
900
- // cmp-verify. It is a seam rather than a require because path-validate is the higher layer: taking
901
- // the dependency the other way round would be a cycle, and re-implementing a weaker chain walk here
902
- // is exactly how a second, divergent notion of trust gets into a toolkit.
903
559
  var _engine = null;
904
560
  function setEngine(engine) { _engine = engine; }
905
561
 
906
- // `valid` and `trusted` are DIFFERENT claims and neither implies the other. `valid` says every
907
- // signature is sound under a certificate the message or the caller supplied -- a message carries its
908
- // own certificates, so that establishes internal consistency and nothing about who signed. `trusted`
909
- // says the signer chained to a root the CALLER named. Without anchors there is no one to chain to,
910
- // so it is false: not null, because "nobody anchored this" is a definite answer, and the same one a
911
- // caller gets from pki.cmp.verify.
912
- // A private copy of the trust configuration, taken at the entry point. The array is copied so it
913
- // cannot be re-pointed or grown; each anchor's BYTES are copied because rewriting those in place is
914
- // the same substitution one level down; the instant is copied because a Date is mutable. An anchor
915
- // TUPLE is copied too, field by field: `{ name, publicKey, algorithm }` is an ordinary object whose
916
- // key bytes and name can be rewritten just as readily as a DER buffer's, and copying only the DER
917
- // form would leave the documented tuple form aliased across the same gap -- the window closed for
918
- // one spelling of an anchor and left open for the other. A PEM string needs no copy: it cannot be
919
- // rewritten. `requiredEku` is copied for the same reason as the array.
920
562
  var _ANCHOR_CLONE_DEPTH = 8;
921
563
  function _cloneAnchorValue(v, depth) {
922
564
  if (guard.bytes.isByteSource(v)) {
923
565
  return _snapshotIfBytes(v, "opts.trustAnchors[]");
924
566
  }
925
567
  if (v === null || typeof v !== "object") return v;
926
- // A Date BEFORE the generic object walk. It has no enumerable own properties, so copying it
927
- // field by field yields `{}` -- an anchor's `distrustAfter: { emailProtection: <Date> }` would
928
- // survive the snapshot as an empty object and be rejected as an invalid date, disabling the very
929
- // policy it encodes. Copied by value, like the validation instant.
930
- // The instant comes through the guard's intrinsic read, so a caller's `getTime` override can
931
- // neither throw out of the snapshot nor put a different instant in the copy than the original
932
- // holds -- which would leave the anchor policy that was checked and the one that is applied two
933
- // different values.
934
568
  if (guard.time.isDate(v)) return new intrinsic.Date(guard.time.instantOf(v));
935
- // A structure too deep to copy is REFUSED, not shared. Returning it by reference at the cap
936
- // would leave part of the anchor caller-mutable across the chain walk while the rest was
937
- // snapshotted -- a guarantee that holds for the shallow fields and quietly lapses for the deep
938
- // ones, which is worse than not offering it. An anchor tuple is a handful of levels deep; a
939
- // deeper one is a caller's mistake, and it fails closed at the entry point.
940
569
  if (depth >= _ANCHOR_CLONE_DEPTH) {
941
570
  throw _err("cms/bad-input", "an opts.trustAnchors entry nests deeper than " + _ANCHOR_CLONE_DEPTH +
942
571
  " levels, so it cannot be copied before the chain is walked; pass the anchor as certificate DER or a { name, publicKey, algorithm } tuple");
@@ -959,148 +588,64 @@ function _snapshotTrust(opts) {
959
588
  }
960
589
  return {
961
590
  trustAnchors: anchors,
962
- // The validation instant, read from the captured Date intrinsic, never the live global Date. The
963
- // snapshot is taken before opts.content is touched (see _verify), so no content accessor has run to
964
- // replace a global yet; reading the instant through the captured constructor holds even if that
965
- // ordering regresses. A caller Date is cloned; undefined becomes now.
966
591
  time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time === undefined ? new intrinsic.Date() : opts.time),
967
592
  requiredEku: Array.isArray(opts.requiredEku) ? opts.requiredEku.slice() : opts.requiredEku,
968
593
  checkPurpose: opts.checkPurpose,
969
594
  };
970
595
  }
971
596
 
972
- // A signer certificate whose keyUsage FORBIDS signing has not been permitted to sign, however well
973
- // it chains. RFC 5280 sec. 4.2.1.3: when the extension is present it is binding, and a leaf
974
- // asserting keyEncipherment alone must not verify a signature. Path validation checks the CA's
975
- // keyCertSign, not the target's own usage, so the format that knows a signature was made asks the
976
- // question -- the same format-local gate on top of path.validate that pki.cmp.verify applies.
977
- // contentCommitment (nonRepudiation) counts: RFC 5652 signatures are made under either bit.
978
- // The value is read through the ONE strict decoder, which enforces the X.690 sec. 11.2.2 minimal
979
- // NamedBitList form, so a malformed keyUsage fails the gate rather than a hand-rolled bit test
980
- // authorizing it.
981
- // The shared RFC 5280 sec. 4.2.1 extension-value decoders, the same set pki.cmp.verify reads its
982
- // signer keyUsage through -- one structurally-strict decoder rather than a second, weaker bit test.
983
597
  var _CERT_EXT_DECODERS = pkix.certExtensionDecoders(pkix.makeNS("cms", CmsError, oid)).byOid;
984
598
 
985
599
  function _keyUsagePermitsSigning(parsedCerts, der) {
986
- // Captured filter + equality: this runs after the streamed content was consumed, and the fail-open
987
- // `if (!entry) return true` below would otherwise be reachable by a hostile iterator that replaced
988
- // Buffer.prototype.equals to return false, making the signer certificate un-findable and its
989
- // keyUsage un-read -- a keyEncipherment-only certificate would then be reported trusted (CWE-347).
990
600
  var entry = intrinsic.filter(parsedCerts, function (c) { return intrinsic.bufferEquals(c.der, der); })[0];
991
- if (!entry) return true; // not among the candidates: nothing to read
601
+ if (!entry) return true;
992
602
  var kuOid = oid.byName("keyUsage");
993
603
  var exts = entry.cert.extensions || [];
994
604
  for (var i = 0; i < exts.length; i++) {
995
605
  if (exts[i].oid !== kuOid) continue;
996
606
  var ku;
997
607
  try { ku = _CERT_EXT_DECODERS[kuOid](exts[i].value); }
998
- catch (_e) { return false; } // unreadable usage is not permission
608
+ catch (_e) { return false; }
999
609
  return ku.digitalSignature === true || ku.contentCommitment === true || ku.nonRepudiation === true;
1000
610
  }
1001
- return true; // absent: unconstrained (sec. 4.2.1.3)
611
+ return true;
1002
612
  }
1003
613
 
1004
- // Every signer trusted? The answer IS the message-level trust verdict, so it is decided by
1005
- // comparison instead of by an `every`. See guard-list.
1006
614
  function _allTrusted(signers) {
1007
615
  return guard.list.allMatch(signers, function (s) { return s.trusted === true; });
1008
616
  }
1009
617
 
1010
- // `cfg` is verify's SNAPSHOT, never the caller's options object -- the parameter is named for that
1011
- // so a future caller cannot hand it the live one without noticing.
1012
618
  async function _applyTrust(res, parsedCerts, cfg) {
1013
619
  res.trusted = false;
1014
- // Every signer carries the field too, whether or not anchors were supplied. A verdict whose
1015
- // per-signer shape depends on which options were passed makes a caller iterating `signers` write
1016
- // a different loop for each case -- and the absent field reads as "unknown" where the answer is
1017
- // a definite "nothing anchored this".
1018
- // async + captured enumeration + await (see _verify): the trust verdict is decided after the streamed
1019
- // content was consumed, so a hostile iterator must not steer it through a replaced Array.prototype or
1020
- // Promise.prototype.then.
1021
620
  intrinsic.forEach(res.signers, function (s) { s.trusted = false; });
1022
621
  if (cfg.trustAnchors == null) return;
1023
622
  if (!_engine) {
1024
623
  throw _err("cms/bad-input", "opts.trustAnchors requires the path validator; load pki.path before verifying (require the toolkit through its index)");
1025
624
  }
1026
- // Every signer, not merely one: a multi-signer message's signers are independent claims, and
1027
- // reporting the whole as trusted because one of them anchored would let an unanchored signer ride
1028
- // out on another's chain -- the same rule the per-signer `ok` already follows for signatures.
1029
625
  var pool = intrinsic.map(parsedCerts, function (c) { return c.der; });
1030
- // The default instant was captured at snapshot time (see _snapshotTrust), before the stream ran, so
1031
- // no live `new Date()` is read here where a replaced Date could steer the validation instant.
1032
626
  var at = cfg.time;
1033
627
  var anchors = cfg.trustAnchors;
1034
- // The configuration is checked HERE, once, before any signer is looked at. Leaving it to the
1035
- // chain walk would make a caller's mistake depend on the message: a SignedData whose every signer
1036
- // failed to verify never reaches a build call, so unusable anchors would be accepted in silence
1037
- // and reported as `trusted: false` -- the config fault dressed as a verdict, which is exactly the
1038
- // conflation this seam exists to remove. An empty anchor list is refused for the same reason a
1039
- // policy that constrains nothing is: it cannot make anything trusted, so asking for it is a
1040
- // mistake rather than a request.
1041
628
  if (!anchors.length) {
1042
629
  throw _err("cms/bad-input", "opts.trustAnchors is empty -- name at least one root, or omit it to state that the signer is not being anchored");
1043
630
  }
1044
- // The INSTANT and the key purposes, before the anchors -- the anchor check reads the NORMALIZED
1045
- // purpose the resolver returns, so it has to run first.
1046
631
  if (cfg.time !== undefined) guard.time.assertValid(cfg.time, _err, "cms/bad-input", "opts.time");
1047
- // The resolver's RETURN is what the walk goes on to use: it normalizes a dotted purpose OID to
1048
- // its registered name, which is the key an anchor's per-purpose metadata is stored under.
1049
- // Preflighting with the caller's raw spelling would look up `distrustAfter["1.3.6.1.5.5.7.3.4"]`
1050
- // where the walk reads `distrustAfter.emailProtection` -- the same value checked under two
1051
- // different keys, so the early check would pass on metadata the walk then rejects.
1052
632
  var purposes = _engine.resolvePurposeOpts({ requiredEku: cfg.requiredEku, checkPurpose: cfg.checkPurpose });
1053
- // The WHOLE anchor, not only its identity: `toAnchor` settles the name / key / algorithm tuple,
1054
- // and the constraint metadata beside it -- the per-purpose distrustAfter dates -- is validated
1055
- // through the same definition the walk uses. Checking one and not the other is how this rule has
1056
- // repeatedly come back: each new part of the configuration has to join the preflight, not wait
1057
- // to be caught where it is consumed.
1058
633
  intrinsic.forEach(anchors, function (a) {
1059
634
  _engine.toAnchor(a);
1060
635
  if (purposes.checkPurpose != null) _engine.assertAnchorConstraints(a, purposes.checkPurpose);
1061
636
  });
1062
- // The INSTANT is checked here for the same reason the anchors are, and in the same place: a
1063
- // Everything above is the preflight: the anchor list, the instant, the key purposes, and each
1064
- // anchor's own constraint metadata -- all judged before a single signer is looked at, and all
1065
- // through the definitions the walk itself uses. A new option joins it here; validating one only
1066
- // where it is consumed is what made a caller's mistake depend on the message.
1067
637
  for (var _si = 0; _si < res.signers.length; _si++) {
1068
638
  var s = res.signers[_si];
1069
639
  {
1070
640
  if (s.ok !== true || !s.cert) { s.trusted = false; continue; }
1071
641
  var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at };
1072
- // A key purpose, when the caller names one. "Trusted" is not a property of a chain alone: a
1073
- // certificate restricted to serverAuth chains perfectly well and is still the wrong key to
1074
- // have signed an email. The verb that KNOWS the purpose supplies it -- pki.smime.verify asks
1075
- // for emailProtection -- rather than this layer guessing one for every CMS use.
1076
642
  if (cfg.requiredEku != null) buildOpts.requiredEku = cfg.requiredEku;
1077
- // The ANCHOR's own trust metadata, which is a separate question from the leaf's EKU. A root
1078
- // distributed with NSS trust bits can be marked untrusted for email while remaining a
1079
- // perfectly good TLS root, and `pki.path` consults those bits -- and distrustAfter -- only
1080
- // when a purpose is named. Requiring the leaf's EKU without naming the purpose checks one
1081
- // end of the chain and not the other, so a root explicitly distrusted for the very purpose
1082
- // being asked about would still answer "trusted".
1083
643
  if (cfg.checkPurpose != null) buildOpts.checkPurpose = cfg.checkPurpose;
1084
- // THE certificate this SignerInfo selected, and only that one -- the same certificate the
1085
- // verdict reports as `cert`. When a subjectKeyIdentifier is used, several certificates can
1086
- // hold the signing key, and it is tempting to let any of them answer: the embedded one may
1087
- // be self-signed while the caller supplied a CA-issued twin. Doing so would decide trust
1088
- // from a certificate the message did not present, carrying a different validity window, key
1089
- // usage and policy set -- so an expired or wrong-purpose signer certificate could be
1090
- // reported trusted through its sibling. `trusted` describes the certificate named in the
1091
- // same verdict; a caller who wants a particular one used supplies that one.
1092
- // Permitted to have signed, as well as chained. Checked BEFORE the chain walk because a
1093
- // certificate that forbids signing cannot become trusted by chaining, so there is nothing to
1094
- // learn from walking it.
1095
644
  if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; continue; }
1096
- // `await`, not `.then`: see _verify.
1097
645
  try {
1098
646
  var r = await _engine.build(s.cert, buildOpts);
1099
647
  s.trusted = !!(r && r.valid);
1100
648
  } catch (e) {
1101
- // A CONFIG fault -- unusable anchors, an invalid time -- is the caller's mistake and must
1102
- // not be absorbed into "untrusted", which would read as a verdict about the message. A
1103
- // chain that simply does not reach an anchor is not that: it is the answer.
1104
649
  if (e && e.code) {
1105
650
  var code = intrinsic.String(e.code);
1106
651
  if (intrinsic.stringIndexOf(code, "bad-input") !== -1 ||
@@ -1111,18 +656,8 @@ async function _applyTrust(res, parsedCerts, cfg) {
1111
656
  }
1112
657
  }
1113
658
  }
1114
- // The trust verdict is decided by comparison, not by a prototype method: `every` replaced after
1115
- // load answers true without consulting a single signer, and a message no anchor covers reports
1116
- // trusted:true.
1117
659
  res.trusted = res.valid && res.signers.length > 0 && _allTrusted(res.signers);
1118
660
  }
1119
- // Parse a candidate cert DER and index its SKI; a cert that will not parse is skipped (it
1120
- // simply cannot be a signer match, and a malformed embedded cert must not fail the verify).
1121
- // The certificate bytes are copied BEFORE they are parsed, not after: the parse surfaces the
1122
- // signed portion and the public key as VIEWS into this buffer, and both are read in a later
1123
- // promise turn to check the signer's signature and walk the chain. Copying afterwards would
1124
- // leave those views pointing at bytes the caller can still rewrite -- a signer certificate
1125
- // swapped for another in that gap, with the verdict still naming the first.
1126
661
  function _addCert(out, der) {
1127
662
  var buf;
1128
663
  try {
@@ -1135,18 +670,6 @@ function _addCert(out, der) {
1135
670
  out.push({ cert: cert, der: buf, ski: _certSki(cert) });
1136
671
  }
1137
672
 
1138
- // Coverage residual -- these defensive branches are unreachable through the shipped path
1139
- // because an upstream contract already narrows the shape:
1140
- // * `cert.extensions || []` -- x509.parse always surfaces `extensions` as an array (empty
1141
- // when absent), so the `|| []` fallback never fires.
1142
- // * `_decodeSignedAttrs`'s `tagNumber !== SET` / `!children` guard -- the caller forces the
1143
- // leading byte to a universal SET OF before decoding, so `asn1.decode` yields a SET whose
1144
- // `children` is always an array; the guard is belt-and-suspenders against a future caller.
1145
- // * `if (!Array.isArray(parsed.signerInfos))` -- the object-input branch is only taken when
1146
- // `signerInfos` is already an array, and cms.parse yields a SignedData whose `signerInfos`
1147
- // is always an array (or it throws), so this belt-and-suspenders throw never fires.
1148
- // * `c && c.bytes ? c.bytes : c` -- schema-cms surfaces every embedded certificate as an
1149
- // object carrying a `bytes` Buffer, so the raw-value fallback never fires.
1150
673
  /**
1151
674
  * @primitive pki.cms.sign
1152
675
  * @signature pki.cms.sign(content, signers, opts?) -> Promise<Buffer|string>
@@ -1443,9 +966,6 @@ var compress = cmsCompress.compress;
1443
966
  */
1444
967
  var decompress = cmsCompress.decompress;
1445
968
 
1446
- // A certs-only reader/recognizer input: a DER Buffer (snapshotted at the door so a later mutation
1447
- // cannot swap the bytes the verdict describes) or a PEM CMS/PKCS7 block. An undecodable input is a
1448
- // typed cms/bad-input, never a raw throw.
1449
969
  var KNOWN_PARSE_CERTS_ONLY_OPTS = { maxCerts: 1 };
1450
970
  function _certsOnlyInputDer(input, verb) {
1451
971
  if (guard.bytes.isByteSource(input)) {
@@ -1518,10 +1038,6 @@ var certsOnly = cmsSign.certsOnly;
1518
1038
  function parseCertsOnly(input, opts) {
1519
1039
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.parseCertsOnly options");
1520
1040
  guard.identifier.assertKnownKeys(opts, KNOWN_PARSE_CERTS_ONLY_OPTS, _err, "cms/bad-input", "pki.cms.parseCertsOnly has an unknown option ");
1521
- // maxCerts bounds the parse of an untrusted bundle, so the bound itself is validated at the door: a
1522
- // negative, fractional, NaN, or non-numeric cap that slice would silently treat as no cap (or, for
1523
- // -1, as "all but the last") is refused rather than forwarded (guard.limits.cap). Absent or null
1524
- // leaves the count uncapped.
1525
1041
  var maxCerts = guard.limits.cap(opts.maxCerts == null ? undefined : opts.maxCerts, "maxCerts", undefined, { E: _err, code: "cms/bad-input" });
1526
1042
  return cms.parseCertsOnly(_certsOnlyInputDer(input, "pki.cms.parseCertsOnly"), _err, "cms", maxCerts, false);
1527
1043
  }
@@ -1546,18 +1062,11 @@ function parseCertsOnly(input, opts) {
1546
1062
  * pki.cms.isCertsOnly(pki.cms.certsOnly([certDer])); // true
1547
1063
  */
1548
1064
  function isCertsOnly(input) {
1549
- // A certs-only message is a SignedData with an id-data content, absent eContent, and no signer.
1550
- // digestAlgorithms is deliberately NOT constrained: RFC 5652 sec. 5.1 permits a non-empty set even
1551
- // with no signer, so a valid but non-canonical bundle (an EST or AIA response) is still recognized.
1552
1065
  var der = _certsOnlyInputDer(input, "pki.cms.isCertsOnly");
1553
1066
  var r;
1554
1067
  try {
1555
1068
  r = cms.parse(der);
1556
1069
  } catch (e) {
1557
- // A well-formed CMS ContentInfo whose content type this toolkit does not model (a bare id-data,
1558
- // a DigestedData) is definitively not a certs-only SignedData, so the predicate reports false
1559
- // rather than making a caller catch an exception for a valid non-SignedData CMS. Truly
1560
- // undecodable bytes (not a ContentInfo at all) still throw.
1561
1070
  if (e && e.code === "cms/unsupported-content-type") return false;
1562
1071
  throw e;
1563
1072
  }
@@ -1567,5 +1076,5 @@ function isCertsOnly(input) {
1567
1076
  r.signerInfos.length === 0;
1568
1077
  }
1569
1078
 
1570
- // `setEngine` is @internal -- the path-validate injection seam, never part of pki.cms.
1079
+ // @internal
1571
1080
  module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine };