@blamejs/pki 0.5.31 → 0.5.33

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 (52) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -3
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +93 -33
  5. package/lib/cmc-build.js +19 -8
  6. package/lib/cmc-verify.js +4 -4
  7. package/lib/cmp-build.js +36 -6
  8. package/lib/cmp-session.js +5 -5
  9. package/lib/cmp-verify.js +86 -16
  10. package/lib/cms-compress.js +2 -2
  11. package/lib/cms-decrypt.js +3 -3
  12. package/lib/cms-encrypt.js +1 -1
  13. package/lib/cms-sign.js +214 -93
  14. package/lib/cms-verify.js +363 -237
  15. package/lib/crl-sign.js +4 -4
  16. package/lib/crmf-sign.js +2 -1
  17. package/lib/csr-sign.js +1 -4
  18. package/lib/ct.js +8 -4
  19. package/lib/est.js +234 -34
  20. package/lib/framework-error.js +20 -6
  21. package/lib/guard-bytes.js +56 -4
  22. package/lib/guard-intrinsic.js +13 -0
  23. package/lib/http-digest.js +69 -16
  24. package/lib/http-retry-after.js +68 -24
  25. package/lib/http-transport.js +60 -6
  26. package/lib/inspect.js +13 -2
  27. package/lib/key.js +2 -2
  28. package/lib/lint.js +1 -1
  29. package/lib/mime.js +27 -4
  30. package/lib/ocsp.js +7 -7
  31. package/lib/oid.js +16 -0
  32. package/lib/path-validate.js +76 -18
  33. package/lib/pbes2.js +2 -2
  34. package/lib/pkcs12-build.js +2 -2
  35. package/lib/pki-build.js +6 -6
  36. package/lib/rfc3339.js +75 -17
  37. package/lib/schema-c509.js +36 -9
  38. package/lib/schema-cmc.js +3 -3
  39. package/lib/schema-pkix.js +174 -25
  40. package/lib/schema-tsp.js +10 -1
  41. package/lib/sign-scheme.js +1 -1
  42. package/lib/sigstore.js +52 -9
  43. package/lib/smime.js +86 -6
  44. package/lib/trust.js +114 -14
  45. package/lib/tsp-sign.js +39 -27
  46. package/lib/validator-tpm.js +2 -2
  47. package/lib/webauthn-mds.js +24 -6
  48. package/lib/webauthn.js +3 -3
  49. package/lib/webcrypto.js +80 -1
  50. package/lib/x509-sign.js +1 -1
  51. package/package.json +2 -2
  52. package/sbom.cdx.json +6 -6
@@ -129,7 +129,7 @@ function _parse(input) {
129
129
  return schemaCms.parse(_toDer(input));
130
130
  }
131
131
  function _toDer(input) {
132
- if (Buffer.isBuffer(input) || input instanceof Uint8Array) return guard.bytes.snapshot(input, CmsError, "cms/bad-input", "input");
132
+ if (guard.bytes.isByteSource(input)) return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", "input");
133
133
  if (typeof input === "string") { try { return schemaCms.pemDecode(input); } catch (e) { throw _err("cms/bad-input", "the CMS PEM could not be decoded", e); } }
134
134
  throw _err("cms/bad-input", "input must be a DER Buffer, Uint8Array, or PEM string");
135
135
  }
@@ -166,7 +166,7 @@ function _riKindForKey(km) {
166
166
  throw _err("cms/bad-input", "key material needs { key, cert }, { password }, { kek }, or { cek }");
167
167
  }
168
168
  function _assertSupported(ri, km) {
169
- if (ri.type === "kari" && ri.keyEncryptionAlgorithm && /mqv/i.test(ri.keyEncryptionAlgorithm.name || "")) throw _err("cms/unsupported-algorithm", "ECMQV kari is not supported");
169
+ if (ri.type === "kari" && ri.keyEncryptionAlgorithm && intrinsic.stringIndexOf(intrinsic.toLowerCase(ri.keyEncryptionAlgorithm.name || ""), "mqv") !== -1) throw _err("cms/unsupported-algorithm", "ECMQV kari is not supported"); // /mqv/i (ASCII name)
170
170
  if (ri.type === "ori") {
171
171
  if (ri.oriType !== O("kem")) throw _err("cms/unsupported-recipient-type", "unsupported OtherRecipientInfo type " + ri.oriType);
172
172
  if (ri.kemri && ri.kemri.kem && ri.kemri.kem.oid === O("id-kem-rsa")) throw _err("cms/unsupported-algorithm", "RSA-KEM is not supported");
@@ -801,7 +801,7 @@ function _releaseKeyDer(k) {
801
801
  if (k && k.owned) guard.secret.zeroize(k.der, CmsError, "cms/bad-input", "the recipient private-key copy");
802
802
  }
803
803
  function _normCertDer(cert) {
804
- if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, CmsError, "cms/bad-input", "the recipient certificate");
804
+ if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, CmsError, "cms/bad-input", "the recipient certificate");
805
805
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", "the recipient certificate PEM could not be decoded", e); } }
806
806
  throw _err("cms/bad-input", "the recipient certificate must be a DER Buffer or PEM string");
807
807
  }
@@ -41,7 +41,7 @@ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(nam
41
41
  // A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
42
42
  // supplies bytes, not a re-encoded parse).
43
43
  function _normCertDer(cert, what) {
44
- if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, CmsError, "cms/bad-input", what || "a certificate");
44
+ if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, CmsError, "cms/bad-input", what || "a certificate");
45
45
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
46
46
  throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
47
47
  }
package/lib/cms-sign.js CHANGED
@@ -117,13 +117,13 @@ function _buildSid(cert, useSki) {
117
117
  // signature octets + signing-time, content-type omitted per sec. 11.4).
118
118
  function _buildSignedAttrs(pairs) {
119
119
  var seenTypes = {};
120
- var attrs = pairs.map(function (p) {
120
+ var attrs = intrinsic.map(pairs, function (p) {
121
121
  if (intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
122
122
  seenTypes[p.type] = 1;
123
123
  return b.sequence([b.oid(p.type), b.set(p.values)]);
124
124
  });
125
125
  var setOf = b.set(attrs); // SET OF (tag 0x31) -- the exact bytes the signature covers (sec. 5.4)
126
- var wire = Buffer.from(setOf); wire[0] = 0xA0; // the on-wire [0] IMPLICIT tag
126
+ var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA0; // the on-wire [0] IMPLICIT tag
127
127
  return { setOf: setOf, wire: wire };
128
128
  }
129
129
 
@@ -131,10 +131,10 @@ function _buildSignedAttrs(pairs) {
131
131
  // into the pair shape _buildSignedAttrs consumes; each MUST carry >= 1 value (Attribute values is
132
132
  // SET SIZE (1..MAX)).
133
133
  function _resolveAttrPairs(list, what) {
134
- return (list || []).map(function (a) {
135
- var vals = (a.values || []).map(function (v) { return _toBuf(v, what); });
134
+ return intrinsic.map(list || [], function (a) {
135
+ var vals = intrinsic.map(a.values || [], function (v) { return _toBuf(v, what); });
136
136
  if (!vals.length) throw _err("cms/bad-input", "a signed attribute must carry at least one value (RFC 5652 -- Attribute values is SET SIZE (1..MAX))");
137
- return { type: /^\d+(\.\d+)+$/.test(a.type) ? a.type : O(a.type), values: vals };
137
+ return { type: oid.isDottedDecimal(a.type) ? a.type : O(a.type), values: vals };
138
138
  });
139
139
  }
140
140
 
@@ -151,24 +151,26 @@ UNSIGNED_FORBIDDEN[O("signingTime")] = "signing-time";
151
151
  // duplicate type (RFC 5652 sec. 11 / sec. 5.3) is a config-time cms/bad-input.
152
152
  function _buildUnsignedAttrs(list) {
153
153
  if (list == null) return null;
154
- if (!Array.isArray(list)) throw _err("cms/bad-input", "opts.unsignedAttributes must be an array of { type, values }");
154
+ if (!intrinsic.isArray(list)) throw _err("cms/bad-input", "opts.unsignedAttributes must be an array of { type, values }");
155
155
  if (!list.length) return null;
156
156
  var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
157
157
  var seen = {};
158
- pairs.forEach(function (p) {
158
+ intrinsic.forEach(pairs, function (p) {
159
159
  if (intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
160
160
  if (intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
161
161
  seen[p.type] = 1;
162
162
  });
163
- var setOf = b.set(pairs.map(function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
164
- var wire = Buffer.from(setOf); wire[0] = 0xA1; // SET OF -> [1] IMPLICIT UnsignedAttributes
163
+ var setOf = b.set(intrinsic.map(pairs, function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
164
+ var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA1; // SET OF -> [1] IMPLICIT UnsignedAttributes
165
165
  return wire;
166
166
  }
167
167
 
168
- // Build one SignerInfo (RFC 5652 sec. 5.3) and sign it. Resolves the SignerInfo (as a build
169
- // node) plus its digestAlgorithm AlgorithmIdentifier and the signer certificate DER (for the
170
- // SignedData digestAlgorithms + certificates sets).
171
- function _buildSignerInfo(signer, content, eContentType, opts) {
168
+ // Resolve, at entry and once, every field an emitted SignerInfo depends on (RFC 5652 sec. 5.3):
169
+ // the SignerIdentifier + version, the signature scheme, the private key reference, the declared
170
+ // public bytes, and the signer certificate DER. Nothing here defers, so the values are captured
171
+ // before any promise turn; the digest, the signature, and the self-check happen a turn later in
172
+ // _finishSignerInfo, off exactly these captured values. Buffered and streamed signing share this.
173
+ function _resolveSignerContext(signer, opts) {
172
174
  var so = signer || {};
173
175
  // A key-only signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
174
176
  // 5272 sec. 3.2 requires exactly this when a Full PKI Request is signed with the
@@ -199,36 +201,54 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
199
201
  var sidv = keyOnly
200
202
  ? { sid: b.contextPrimitive(0, _keyOnlyKeyId(so)), version: 3 } // [0] IMPLICIT SubjectKeyIdentifier
201
203
  : _buildSid(cert, opts.sid === "ski");
202
- var sid = sidv.sid, version = sidv.version;
204
+ return { keyOnly: keyOnly, soKey: soKey, soSpki: soSpki, certDer: certDer, cert: cert,
205
+ scheme: scheme, sid: sidv.sid, version: sidv.version };
206
+ }
203
207
 
204
- return Promise.resolve().then(function () {
205
- if (opts.signedAttributes === false) return content; // sign the content directly (no signed attributes)
208
+ // Finish a SignerInfo (RFC 5652 sec. 5.3) from a resolved context: build the SignedAttributes (or
209
+ // sign the content directly), sign them, self-check the signature against the declared key, and
210
+ // assemble the SignerInfo. `md` is the message digest of the content under `rc.scheme.digest`,
211
+ // already computed -- over a buffered content by _digest, or over a streamed content's chunks by the
212
+ // engine's single-pass digest. `content` is read ONLY on the signedAttributes:false path, where the
213
+ // signature covers the content directly and there is no digest attribute.
214
+ async function _finishSignerInfo(rc, md, content, eContentType, opts) {
215
+ // await throughout, never a live Promise.resolve()/.then lookup: on the streamed path this runs after
216
+ // the content's iterator has driven the digest, so a replaced global Promise.resolve or a replaced
217
+ // Promise.prototype.then would otherwise let it disrupt the signing that completes here. await hooks
218
+ // onto a native promise through the internal job, not the caller-replaceable prototype method.
219
+ var toSign;
220
+ if (opts.signedAttributes === false) {
221
+ toSign = content; // sign the content directly (no signed attributes)
222
+ } else {
206
223
  // Signed attributes (RFC 5652 sec. 5.3): content-type == eContentType, message-digest ==
207
224
  // digest(content), and (by default) signing-time, plus any caller-supplied attribute.
208
- return _digest(scheme.digest, content).then(function (md) {
209
- var pairs = [
210
- { type: O("contentType"), values: [b.oid(eContentType)] },
211
- { type: O("messageDigest"), values: [b.octetString(md)] },
212
- ];
213
- if (opts.signingTime !== false) pairs.push({ type: O("signingTime"), values: [_timeValue(opts.signingTime)] });
214
- pairs = pairs.concat(_resolveAttrPairs(opts.additionalSignedAttributes, "a signed attribute value"));
215
- return _buildSignedAttrs(pairs);
216
- });
217
- }).then(function (toSign) {
218
- var signedBytes = toSign.setOf ? toSign.setOf : toSign; // SET-OF form for signing (sec. 5.4)
219
- return signScheme.signOverTbs(scheme, soKey, signedBytes, _signE).then(function (sig) {
220
- return _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert).then(function () { return sig; });
221
- }).then(function (sig) {
222
- var fields = [b.integer(BigInt(version)), sid, scheme.digestAlgId];
223
- if (toSign.wire) fields.push(toSign.wire); // [0] IMPLICIT signedAttrs
224
- fields.push(scheme.sigAlgId, b.octetString(sig));
225
- var ua = _buildUnsignedAttrs(opts.unsignedAttributes);
226
- if (ua) fields.push(ua); // [1] IMPLICIT unsignedAttrs
227
- return { si: b.sequence(fields), digestAlgId: scheme.digestAlgId, version: version, certDer: certDer };
228
- // certDer is null for a key-only signer, and the certificates [0] embedding
229
- // below filters it out -- there is nothing to embed, which is the point.
230
- });
231
- });
225
+ var pairs = [
226
+ { type: O("contentType"), values: [b.oid(eContentType)] },
227
+ { type: O("messageDigest"), values: [b.octetString(md)] },
228
+ ];
229
+ if (opts.signingTime !== false) intrinsic.push(pairs, { type: O("signingTime"), values: [_timeValue(opts.signingTime)] });
230
+ pairs = intrinsic.concat(pairs, _resolveAttrPairs(opts.additionalSignedAttributes, "a signed attribute value"));
231
+ toSign = _buildSignedAttrs(pairs);
232
+ }
233
+ var signedBytes = toSign.setOf ? toSign.setOf : toSign; // SET-OF form for signing (sec. 5.4)
234
+ var sig = await signScheme.signOverTbs(rc.scheme, rc.soKey, signedBytes, _signE);
235
+ await _assertKeyMatchesSpki(rc.keyOnly, rc.soKey, rc.soSpki, rc.scheme, sig, signedBytes, rc.cert);
236
+ var fields = [b.integer(BigInt(rc.version)), rc.sid, rc.scheme.digestAlgId];
237
+ if (toSign.wire) intrinsic.push(fields, toSign.wire); // [0] IMPLICIT signedAttrs
238
+ intrinsic.push(fields, rc.scheme.sigAlgId, b.octetString(sig));
239
+ var ua = _buildUnsignedAttrs(opts.unsignedAttributes);
240
+ if (ua) intrinsic.push(fields, ua); // [1] IMPLICIT unsignedAttrs
241
+ // certDer is null for a key-only signer, and the certificates [0] embedding below filters it out --
242
+ // there is nothing to embed, which is the point.
243
+ return { si: b.sequence(fields), digestAlgId: rc.scheme.digestAlgId, version: rc.version, certDer: rc.certDer };
244
+ }
245
+
246
+ // Build one SignerInfo (RFC 5652 sec. 5.3) over a buffered content and sign it: resolve the context,
247
+ // digest the content (unless it is signed directly), then finish.
248
+ function _buildSignerInfo(signer, content, eContentType, opts) {
249
+ var rc = _resolveSignerContext(signer, opts);
250
+ var mdP = opts.signedAttributes === false ? Promise.resolve(null) : _digest(rc.scheme.digest, content);
251
+ return mdP.then(function (md) { return _finishSignerInfo(rc, md, content, eContentType, opts); });
232
252
  }
233
253
 
234
254
 
@@ -250,7 +270,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
250
270
  // This is the same post-sign self-check every other signer in the toolkit runs,
251
271
  // through the same shared helper. It costs one verification per SignerInfo, which
252
272
  // is what a certificate, a CRL, a CSR and a CMP message have always paid here.
253
- function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
273
+ async function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
254
274
  var declared = keyOnly ? soSpki : (cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.bytes);
255
275
  // A parsed certificate that does not retain its SPKI bytes cannot be checked this
256
276
  // way. That is not a shape x509.parse produces, and if it ever were, refusing is
@@ -260,20 +280,26 @@ function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes,
260
280
  "a signer certificate did not surface its subjectPublicKeyInfo, so the signature it produced could " +
261
281
  "not be checked against the key the SignerInfo declares");
262
282
  }
263
- return Promise.resolve()
264
- .then(function () { return _b.assertSignatureVerifies(signedBytes, sig, declared, scheme); })
265
- .then(null, function (e) {
266
- if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
267
- throw _signE("bad-input",
268
- "a signer's `key` does not match the public key its SignerInfo declares (" +
269
- (keyOnly ? "`spki`" : "its certificate") + "): the signature it produced does not verify under that key", e);
270
- });
283
+ // await, never Promise.resolve()/.then: reached from _finishSignerInfo after a streamed content's
284
+ // iterator ran, so a replaced global Promise.resolve would otherwise disrupt this self-check.
285
+ try {
286
+ await _b.assertSignatureVerifies(signedBytes, sig, declared, scheme);
287
+ } catch (e) {
288
+ if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
289
+ throw _signE("bad-input",
290
+ "a signer's `key` does not match the public key its SignerInfo declares (" +
291
+ (keyOnly ? "`spki`" : "its certificate") + "): the signature it produced does not verify under that key", e);
292
+ }
271
293
  }
272
294
 
273
295
  // A signing-time Time value: UTCTime before 2050, GeneralizedTime from 2050 (RFC 5652 sec. 11.3 /
274
- // RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above).
296
+ // RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above). The
297
+ // DEFAULT instant is read from the captured Date intrinsic, never the live global: a streamed content's
298
+ // iterator runs during the digest, one turn before this attribute is built, and a replaced global Date
299
+ // would otherwise let it stamp the signature with an attacker-chosen instant (byte-identical, under a
300
+ // deterministic scheme, to signing with that instant explicitly).
275
301
  function _timeValue(when) {
276
- var d = guard.time.isDate(when) ? when : new Date();
302
+ var d = guard.time.isDate(when) ? when : new intrinsic.Date();
277
303
  return d.getUTCFullYear() < 2050 ? b.utcTime(d) : b.generalizedTime(d);
278
304
  }
279
305
 
@@ -351,29 +377,114 @@ function _keyOnlyKeyId(so) {
351
377
 
352
378
  function _normCertDer(c) {
353
379
  if (c == null) throw _err("cms/bad-input", "each signer requires a certificate (cert)");
354
- if (c instanceof Uint8Array || Buffer.isBuffer(c)) {
355
- c = guard.bytes.snapshot(c, CmsError, "cms/bad-input", "a signer certificate");
380
+ if (guard.bytes.isByteSource(c)) {
381
+ c = guard.bytes.snapshotSource(c, CmsError, "cms/bad-input", "a signer certificate");
356
382
  return c[0] === 0x30 ? c : _pemToDer(c.toString("latin1")); // DER as-is, else PEM
357
383
  }
358
384
  if (typeof c === "string") return _pemToDer(c);
359
385
  throw _err("cms/bad-input", "a signer certificate must be a DER Buffer or a PEM string");
360
386
  }
361
387
  function _pemToDer(text) {
362
- var m = text.match(/-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/);
363
- if (!m) throw _err("cms/bad-input", "a signer certificate PEM is not a CERTIFICATE block");
364
- return Buffer.from(m[1].replace(/[^A-Za-z0-9+/=]/g, ""), "base64");
388
+ var der = pkix.pemDecodeLenient(text, "CERTIFICATE");
389
+ if (der === null) throw _err("cms/bad-input", "a signer certificate PEM is not a CERTIFICATE block");
390
+ return der;
365
391
  }
366
392
 
367
393
  // pki.cms.sign -- documented by the @primitive block in cms-verify.js (the @module pki.cms home).
368
- // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
369
- // synchronous because they read the caller's mutable content and signer list.
394
+ // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async). The stream/buffer routing
395
+ // classifies `content` at the door, which reads `content[Symbol.asyncIterator]` -- a caller accessor
396
+ // that can throw -- so the whole dispatch runs inside the rejection boundary rather than fixedCall's
397
+ // alone, which the classification precedes. The checks the impls run stay synchronous because they
398
+ // read the caller's mutable content and signer list.
370
399
  function sign(content, signers, opts) {
371
- // Every caller-owned argument copied at entry and released when the call settles; see the note
372
- // on the same call in x509-sign. Here it is what makes the attribute-shaped-content refusal below
373
- // hold: the value that decides it and the value that gets signed are now the same read.
400
+ return guard.async.deferred(function () { return _signDispatch(content, signers, opts); });
401
+ }
402
+
403
+ function _signDispatch(content, signers, opts) {
404
+ // The signer list and options are copied FIRST, before the content is classified. Classifying reads
405
+ // content[Symbol.asyncIterator] -- a caller accessor that runs in-realm -- and a probe run before the
406
+ // copy could mutate the still-uncopied signers/opts, or replace the Array.prototype method the copy
407
+ // uses, and steer the emitted signature. With the copies taken first, the probe cannot reach them.
374
408
  return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
375
- [content, "content"], [signers, "the signer list"], [opts, "pki.cms.sign options"],
376
- ], _sign);
409
+ [signers, "the signer list"], [opts, "pki.cms.sign options"],
410
+ ], function (copiedSigners, copiedOpts) {
411
+ // A streaming content is classified by guard.bytes.asyncStreamOf, which reads Symbol.asyncIterator a
412
+ // single time and returns a LAZY stream (its underlying iterator is acquired only when the digest
413
+ // engine drives it, so a pre-hash rejection never opens a resource to leak). The stream, consumed
414
+ // exactly once, is passed through; it CANNOT be deep-copied itself. A byte source (or any
415
+ // non-iterable) yields null here and takes the buffered path.
416
+ var stream = guard.bytes.asyncStreamOf(content);
417
+ if (stream) return _signStream(stream, copiedSigners, copiedOpts);
418
+ // Buffered: the content byte source is copied too, so the value that decides the attribute-shaped-
419
+ // content refusal and the value that gets signed are the same read.
420
+ return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
421
+ [content, "content"],
422
+ ], function (copiedContent) { return _sign(copiedContent, copiedSigners, copiedOpts); });
423
+ });
424
+ }
425
+
426
+ // Sign a streamed content (an async iterable of byte chunks) as a DETACHED SignedData. The payload
427
+ // is hashed once, incrementally, under every distinct SignerInfo digest algorithm (RFC 5652
428
+ // sec. 5.4), so an arbitrarily large content is never held whole and a single-use stream serves
429
+ // every signer in one pass. Detached is the only conformant streamed form: an attached SignedData
430
+ // embeds the content as a definite-length OCTET STRING, which needs the whole content buffered to
431
+ // state its length, and signed attributes are required (a stream cannot be signed directly, and the
432
+ // message-digest attribute is exactly what the streamed hash produces).
433
+ async function _signStream(stream, signers, opts) {
434
+ opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
435
+ guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
436
+ if (opts.detached !== true) {
437
+ throw _err("cms/bad-input", "a streaming (async-iterable) content requires opts.detached: true; an attached " +
438
+ "SignedData embeds the content as a definite-length OCTET STRING, which cannot be produced without buffering it");
439
+ }
440
+ if (opts.signedAttributes === false) {
441
+ throw _err("cms/bad-input", "a streaming content cannot be signed without signed attributes: the message-digest " +
442
+ "attribute is the streamed hash, and there is no content to sign directly (RFC 5652 sec. 5.4)");
443
+ }
444
+ var list = intrinsic.isArray(signers) ? signers : [signers];
445
+ if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
446
+ var eContentType = opts.eContentType ? O(opts.eContentType) : OID_DATA;
447
+ if (eContentType === OID_PKI_DATA && list.length > 1 &&
448
+ intrinsic.some(list, function (s) { return s && s.cert == null && s.spki != null; })) {
449
+ throw _err("cms/bad-input",
450
+ "a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
451
+ }
452
+ if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
453
+ // Resolve every signer's context first (no stream consumed yet): this yields each SignerInfo's
454
+ // digest algorithm, so the stream can be hashed once under the distinct set.
455
+ var rcs = intrinsic.map(list, function (s) { return _resolveSignerContext(s, opts); });
456
+ var digestNames = [], seenDigest = {};
457
+ intrinsic.forEach(rcs, function (r) { if (!seenDigest[r.scheme.digest]) { seenDigest[r.scheme.digest] = 1; intrinsic.push(digestNames, r.scheme.digest); } });
458
+ var wcNames = intrinsic.map(digestNames, function (n) { return DIGEST_HASH[n]; });
459
+ // `await`, not `.then`: the streamed content's iterator runs during digestStream and could replace
460
+ // Promise.prototype.then (or Promise.all, whose spec-internal element handling dispatches through it)
461
+ // to drop SignerInfos from the emitted DER or bypass the non-empty-signer check. await and a captured
462
+ // await-each aggregation dispatch through no such caller-replaceable method.
463
+ var digests;
464
+ try {
465
+ digests = await subtle.digestStream(wcNames, stream);
466
+ } catch (e) {
467
+ // The engine reports malformed streamed content in its own domain; report it in this verb's domain
468
+ // instead, exactly as a buffered content's bad bytes are, rather than leaking the engine code. A
469
+ // non-byte chunk is webcrypto/data; a source whose async-iterator protocol is malformed (a
470
+ // non-callable next, or a factory returning a non-iterator) is webcrypto/syntax. The digest
471
+ // algorithm names are this module's own, so webcrypto/syntax here is never an algorithm fault.
472
+ if (e && e.code === "webcrypto/data") throw _err("cms/bad-input", "a streamed content chunk is not a byte source", e);
473
+ if (e && e.code === "webcrypto/syntax") throw _err("cms/bad-input", "the streamed content is not a valid async iterable of byte chunks", e);
474
+ throw e;
475
+ }
476
+ var byName = {};
477
+ intrinsic.forEach(digestNames, function (n, i) { byName[n] = intrinsic.bufferFrom(digests[i]); });
478
+ // Build each SignerInfo sequentially -- START it only as it is awaited -- rather than starting them
479
+ // all and awaiting each: awaiting is the capture-safe combinator (no Promise.all/.then the streamed
480
+ // content could replace), but starting all first would leave a later signer's rejection unobserved
481
+ // if an earlier one rejects, which Node can escalate to an unhandled-rejection crash. Per-signer work
482
+ // is small, so the sequential cost is negligible.
483
+ var built = [];
484
+ for (var _ip = 0; _ip < rcs.length; _ip++) {
485
+ intrinsic.push(built, await _finishSignerInfo(rcs[_ip], byName[rcs[_ip].scheme.digest], null, eContentType, opts));
486
+ }
487
+ return _assembleSignedData(built, eContentType, opts, null);
377
488
  }
378
489
 
379
490
  function _sign(content, signers, opts) {
@@ -422,45 +533,55 @@ function _sign(content, signers, opts) {
422
533
  // silently-ignored non-Date or an Invalid Date that would encode a garbage Time.
423
534
  if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
424
535
 
425
- return Promise.all(list.map(function (s) { return _buildSignerInfo(s, contentBuf, eContentType, opts); })).then(function (built) {
426
- // digestAlgorithms: the distinct SignerInfo digestAlgorithm AlgorithmIdentifiers, deduped.
427
- var seen = {}, digestAlgs = [];
428
- built.forEach(function (x) { var k = x.digestAlgId.toString("hex"); if (!seen[k]) { seen[k] = 1; digestAlgs.push(x.digestAlgId); } });
429
- // CMSVersion (RFC 5652 sec. 5.1): 3 if any SignerInfo is v3 (ski) or eContentType != id-data;
430
- // otherwise 1 (v1 emits only X.509 certificates, so the v4/v5 attribute-certificate cases
431
- // do not arise).
432
- var v3 = built.some(function (x) { return x.version === 3; }) || eContentType !== OID_DATA;
433
- var version = v3 ? 3 : 1;
434
- // EncapsulatedContentInfo: eContentType + [0] EXPLICIT eContent (omitted when detached).
435
- var encapFields = [b.oid(eContentType)];
436
- if (!opts.detached) encapFields.push(b.explicit(0, b.octetString(contentBuf)));
437
- var encap = b.sequence(encapFields);
438
- // certificates [0] IMPLICIT SET OF (the signer certs), deduped + SET-OF-ordered, when embedded.
439
- var sdFields = [b.integer(BigInt(version)), b.set(digestAlgs), encap];
440
- if (opts.certificates !== false) {
441
- // A key-only signer contributes no certificate (certDer is null), so the
442
- // set can end up EMPTY -- and an empty certificates [0] is not the same as
443
- // an absent one, so the field is omitted entirely in that case.
444
- var certDers = _dedupe(built.map(function (x) { return x.certDer; })
445
- .filter(function (d) { return d != null; })).sort(Buffer.compare); // X.690 sec. 11.6
446
- if (certDers.length) sdFields.push(b.contextConstructed(0, Buffer.concat(certDers))); // [0] IMPLICIT SET OF
447
- }
448
- sdFields.push(b.set(built.map(function (x) { return x.si; }))); // signerInfos SET OF
449
- var signedData = b.sequence(sdFields);
450
- var contentInfo = b.sequence([b.oid(OID_SIGNED_DATA), b.explicit(0, signedData)]); // ContentInfo
451
- return opts.pem ? pkix.pemEncode(contentInfo, "CMS", frameworkError.PemError) : contentInfo;
452
- });
536
+ return Promise.all(list.map(function (s) { return _buildSignerInfo(s, contentBuf, eContentType, opts); }))
537
+ .then(function (built) { return _assembleSignedData(built, eContentType, opts, contentBuf); });
538
+ }
539
+
540
+ // Assemble the SignedData ContentInfo (RFC 5652 sec. 5.1) from the built SignerInfos: the deduped
541
+ // digestAlgorithms set, the coupled CMSVersion, the EncapsulatedContentInfo (eContent omitted when
542
+ // detached, the only form a streamed content takes), the deduped certificates [0], and the
543
+ // signerInfos SET OF. `contentBuf` is the content bytes for the attached form, or null when detached.
544
+ function _assembleSignedData(built, eContentType, opts, contentBuf) {
545
+ // digestAlgorithms: the distinct SignerInfo digestAlgorithm AlgorithmIdentifiers, deduped. Captured
546
+ // enumeration throughout: this assembly runs after a streamed content's iterator may have replaced
547
+ // Array.prototype.map / forEach / Buffer.prototype.toString, and a live one could drop SignerInfos
548
+ // from the emitted CMS or mis-dedup the algorithm / certificate sets.
549
+ var seen = {}, digestAlgs = [];
550
+ intrinsic.forEach(built, function (x) { var k = intrinsic.bufToString(x.digestAlgId, "hex"); if (!seen[k]) { seen[k] = 1; intrinsic.push(digestAlgs, x.digestAlgId); } });
551
+ // CMSVersion (RFC 5652 sec. 5.1): 3 if any SignerInfo is v3 (ski) or eContentType != id-data;
552
+ // otherwise 1 (v1 emits only X.509 certificates, so the v4/v5 attribute-certificate cases
553
+ // do not arise).
554
+ var v3 = intrinsic.some(built, function (x) { return x.version === 3; }) || eContentType !== OID_DATA;
555
+ var version = v3 ? 3 : 1;
556
+ // EncapsulatedContentInfo: eContentType + [0] EXPLICIT eContent (omitted when detached).
557
+ var encapFields = [b.oid(eContentType)];
558
+ if (!opts.detached) intrinsic.push(encapFields, b.explicit(0, b.octetString(contentBuf)));
559
+ var encap = b.sequence(encapFields);
560
+ // certificates [0] IMPLICIT SET OF (the signer certs), deduped + SET-OF-ordered, when embedded.
561
+ var sdFields = [b.integer(BigInt(version)), b.set(digestAlgs), encap];
562
+ if (opts.certificates !== false) {
563
+ // A key-only signer contributes no certificate (certDer is null), so the
564
+ // set can end up EMPTY -- and an empty certificates [0] is not the same as
565
+ // an absent one, so the field is omitted entirely in that case.
566
+ var certDers = intrinsic.sort(_dedupe(intrinsic.filter(intrinsic.map(built, function (x) { return x.certDer; }),
567
+ function (d) { return d != null; })), intrinsic.compare); // X.690 sec. 11.6
568
+ if (certDers.length) intrinsic.push(sdFields, b.contextConstructed(0, intrinsic.bufferConcat(certDers))); // [0] IMPLICIT SET OF
569
+ }
570
+ intrinsic.push(sdFields, b.set(intrinsic.map(built, function (x) { return x.si; }))); // signerInfos SET OF
571
+ var signedData = b.sequence(sdFields);
572
+ var contentInfo = b.sequence([b.oid(OID_SIGNED_DATA), b.explicit(0, signedData)]); // ContentInfo
573
+ return opts.pem ? pkix.pemEncode(contentInfo, "CMS", frameworkError.PemError) : contentInfo;
453
574
  }
454
575
 
455
576
  // Dedupe certificate DERs (two signers may share a cert, so embed it once).
456
577
  function _dedupe(ders) {
457
578
  var seen = {}, out = [];
458
- ders.forEach(function (d) { var k = d.toString("hex"); if (!seen[k]) { seen[k] = 1; out.push(d); } });
579
+ intrinsic.forEach(ders, function (d) { var k = intrinsic.bufToString(d, "hex"); if (!seen[k]) { seen[k] = 1; intrinsic.push(out, d); } });
459
580
  return out;
460
581
  }
461
582
 
462
583
  function _toBuf(v, what) {
463
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
584
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmsError, "cms/bad-input", what);
464
585
  throw _err("cms/bad-input", what + " must be a Buffer");
465
586
  }
466
587