@blamejs/pki 0.8.30 → 0.8.48

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 (87) hide show
  1. package/CHANGELOG.md +411 -0
  2. package/README.md +67 -16
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +28 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +939 -48
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +448 -2
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +267 -4
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
@@ -24,6 +24,12 @@ var x509 = require("./schema-x509");
24
24
  var signScheme = require("./sign-scheme");
25
25
  var guard = require("./guard-all");
26
26
  var intrinsic = require("./guard-intrinsic");
27
+ /** @internal The promise a signature verdict is awaited through, built from the constructor captured at
28
+ * load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands the
29
+ * construction to whatever that binding holds: one settling without awaiting its argument drops the
30
+ * self-check's rejection, and one settling truthy answers the verdict the caller reads. */
31
+ var _Promise = intrinsic.Promise;
32
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
27
33
  var _hasOwn = intrinsic.hasOwn, _bufferEquals = intrinsic.bufferEquals;
28
34
  var pkix = require("./schema-pkix");
29
35
  var schema = require("./schema-engine");
@@ -257,7 +263,7 @@ function _encodeClearance(cl) {
257
263
  try { parts = [b.oid(O(cl.policyId) || cl.policyId)]; } catch (e) { throw _err("attrcert/bad-input", "clearance.policyId is not a valid object identifier", e); }
258
264
  if (cl.classList != null) {
259
265
  if (!Array.isArray(cl.classList)) throw _err("attrcert/bad-input", "clearance.classList must be an array of class names");
260
- var positions = cl.classList.map(function (n) {
266
+ var positions = guard.list.copyMap(cl.classList, function (n) {
261
267
  var pos = CLASSLIST_BIT[guard.text.keyOf(n)];
262
268
  if (pos == null) throw _err("attrcert/bad-input", "unknown clearance class " + guard.text.showValue(n));
263
269
  return pos;
@@ -267,7 +273,7 @@ function _encodeClearance(cl) {
267
273
  }
268
274
  if (cl.securityCategories != null) {
269
275
  if (!Array.isArray(cl.securityCategories) || !cl.securityCategories.length) throw _err("attrcert/bad-input", "clearance.securityCategories must be a non-empty array");
270
- parts.push(b.set(cl.securityCategories.map(_encodeSecurityCategory)));
276
+ parts.push(b.set(guard.list.copyMap(cl.securityCategories, _encodeSecurityCategory)));
271
277
  }
272
278
  return b.sequence(parts);
273
279
  }
@@ -286,7 +292,7 @@ function _encodeIetfAttrSyntax(ia) {
286
292
  if (!Array.isArray(ia.values) || !ia.values.length) throw _err("attrcert/bad-input", "an IetfAttrSyntax attribute must be an object with a non-empty values array");
287
293
  var parts = [];
288
294
  if (ia.policyAuthority != null) parts.push(_b.encodeGeneralNames(_gnList(ia.policyAuthority), 0));
289
- parts.push(b.sequence(ia.values.map(function (v) {
295
+ parts.push(b.sequence(guard.list.copyMap(ia.values, function (v) {
290
296
  if (v == null || typeof v !== "object") throw _err("attrcert/bad-input", "each IetfAttrSyntax value must be { octets }, { oid }, or { string }");
291
297
  _assertFields(v, KNOWN_IETF_ATTR_VALUE_KEYS, "IetfAttrSyntax value");
292
298
  if (v.octets != null) { if (!Buffer.isBuffer(v.octets)) throw _err("attrcert/bad-input", "IetfAttrSyntax octets must be a Buffer"); return b.octetString(v.octets); }
@@ -328,11 +334,11 @@ function _encodeTarget(t) {
328
334
  }
329
335
  function _encodeSeqOfTargets(targets) {
330
336
  if (!Array.isArray(targets) || !targets.length) throw _err("attrcert/bad-input", "a targets value must be a non-empty array of targets");
331
- return b.sequence([b.sequence(targets.map(_encodeTarget))]);
337
+ return b.sequence([b.sequence(guard.list.copyMap(targets, _encodeTarget))]);
332
338
  }
333
339
  function _attrSpec(names, tag, label) {
334
340
  if (!Array.isArray(names)) throw _err("attrcert/bad-input", "aaControls." + label + " must be an array of attribute-type OIDs");
335
- var oids = names.map(function (n) {
341
+ var oids = guard.list.copyMap(names, function (n) {
336
342
  try { return b.oid(O(n) || n); } catch (e) { throw _err("attrcert/bad-input", "aaControls." + label + " contains an invalid object identifier " + guard.text.showValue(n), e); }
337
343
  });
338
344
  return b.contextConstructed(tag, Buffer.concat(oids));
@@ -385,7 +391,9 @@ function _buildAttributes(attrSpec) {
385
391
  attrs.push(b.sequence([b.oid(type), b.set([valueTlv])]));
386
392
  }
387
393
  if (Array.isArray(attrSpec)) {
388
- attrSpec.forEach(function (a, i) {
394
+ /** @internal Through the captured walk, never the caller's own `forEach`: these attributes are the
395
+ * content this verb signs, and a replaced walk hands the callback a list it never validated. */
396
+ guard.list.copyMap(attrSpec, function (a, i) {
389
397
  var der = _b.reqDer(a, "attribute [" + i + "]");
390
398
  var n;
391
399
  try { n = asn1.decode(der); } catch (e) { throw _err("attrcert/bad-input", "pre-encoded attribute [" + i + "] is not valid DER", e); }
@@ -441,7 +449,7 @@ function _buildExtensions(extSpec, ctx) {
441
449
  if (Array.isArray(extSpec)) {
442
450
  if (!extSpec.length) return null;
443
451
  var seenA = intrinsic.create(null);
444
- var exts = extSpec.map(function (e, i) {
452
+ var exts = guard.list.copyMap(extSpec, function (e, i) {
445
453
  var der = _b.reqDer(e, "extension");
446
454
  _b.assertValidExtension(der, i, EXT_DECODERS);
447
455
  var n = asn1.decode(der);
@@ -589,7 +597,7 @@ function _sign(spec, issuer, opts) {
589
597
  var acinfoDer = b.sequence(acinfoChildren);
590
598
 
591
599
  return signScheme.signOverTbs(scheme, issuer.key, acinfoDer, _signE).then(function (sig) {
592
- return Promise.resolve(_b.assertSignatureVerifies(acinfoDer, sig, aaSpki, scheme)).then(function () {
600
+ return _promiseResolve(_Promise, _b.assertSignatureVerifies(acinfoDer, sig, aaSpki, scheme)).then(function () {
593
601
  var acDer = b.sequence([acinfoDer, scheme.sigAlgId, b.bitString(sig, 0)]);
594
602
  return opts.pem ? attrcert.pemEncode(acDer, "ATTRIBUTE CERTIFICATE") : acDer;
595
603
  });
@@ -697,17 +705,17 @@ function _checkTargeting(ac, want) {
697
705
  for (var i = 0; i < ac.extensions.length; i++) {
698
706
  if (ac.extensions[i].name === "targetInformation") ext = ac.extensions[i];
699
707
  }
700
- if (!ext) return { checked: true, ok: true };
708
+ if (!ext) return guard.verdict.of({ checked: true, ok: true });
701
709
  if (want === undefined) {
702
- return { checked: false, ok: false,
703
- reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" };
710
+ return guard.verdict.of({ checked: false, ok: false,
711
+ reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" });
704
712
  }
705
713
  var groups = ext.decoded || [];
706
714
  for (var g0 = 0; g0 < groups.length; g0++) {
707
715
  for (var t0 = 0; t0 < groups[g0].length; t0++) {
708
716
  if (groups[g0][t0].kind !== "targetCert") continue;
709
- return { checked: false, ok: false,
710
- reason: "this attribute certificate carries a targetCert target, which RFC 5755 sec. 4.3.2 says MUST NOT be used, so the targeting check did not run" };
717
+ return guard.verdict.of({ checked: false, ok: false,
718
+ reason: "this attribute certificate carries a targetCert target, which RFC 5755 sec. 4.3.2 says MUST NOT be used, so the targeting check did not run" });
711
719
  }
712
720
  }
713
721
 
@@ -717,17 +725,17 @@ function _checkTargeting(ac, want) {
717
725
  var entry = groups[g][t];
718
726
  if (entry.kind !== "targetName" && entry.kind !== "targetGroup") continue;
719
727
  var m = _generalNameMatches(entry.name, want);
720
- if (m === true) return { checked: true, ok: true };
728
+ if (m === true) return guard.verdict.of({ checked: true, ok: true });
721
729
  if (m === null) uncomparable = entry.name && entry.name.tagNumber;
722
730
  }
723
731
  }
724
732
  if (uncomparable !== null) {
725
- return { checked: false, ok: false,
733
+ return guard.verdict.of({ checked: false, ok: false,
726
734
  reason: "this attribute certificate names a target in a GeneralName form ([" + uncomparable +
727
- "]) this verifier does not compare, so the RFC 5755 sec. 4.3.2 check did not run" };
735
+ "]) this verifier does not compare, so the RFC 5755 sec. 4.3.2 check did not run" });
728
736
  }
729
- return { checked: true, ok: false,
730
- reason: "this attribute certificate does not name this verifier among its targets (RFC 5755 sec. 4.3.2)" };
737
+ return guard.verdict.of({ checked: true, ok: false,
738
+ reason: "this attribute certificate does not name this verifier among its targets (RFC 5755 sec. 4.3.2)" });
731
739
  }
732
740
 
733
741
  var SUPPORTED_CRITICAL = intrinsic.create(null);
@@ -908,7 +916,7 @@ function _verify(ac, issuer, opts) {
908
916
  reason: undefined,
909
917
  });
910
918
 
911
- return Promise.resolve(attrcertVerify.verifyAcSignature(parsed, trust.spki)).then(function (sigOk) {
919
+ return _promiseResolve(_Promise, attrcertVerify.verifyAcSignature(parsed, trust.spki)).then(function (sigOk) {
912
920
  result.signatureValid = sigOk === true;
913
921
  var fail = function (reason) { result.reason = reason; return result; };
914
922
  if (!result.signatureValid) return fail("the signature does not verify under the issuer public key");
package/lib/cbor-det.js CHANGED
@@ -56,7 +56,7 @@ function _asBuffer(input, who) {
56
56
 
57
57
  function _profile(name) {
58
58
  var p = name === undefined ? "deterministic" : name;
59
- if (p === "deterministic") return { mapKeyCompare: Buffer.compare };
59
+ if (p === "deterministic") return { mapKeyCompare: guard.bytes.compare };
60
60
  throw new TypeError("decode: unknown profile " + JSON.stringify(p) + " (only \"deterministic\" is supported)");
61
61
  }
62
62
 
@@ -637,15 +637,21 @@ function _asText(s, who) {
637
637
  return s;
638
638
  }
639
639
 
640
+ /** @internal @guard-via guard\.text\.assertWellFormedUtf16\( */
640
641
  function _assertWellFormedText(str, who) {
641
- for (var i = 0; i < str.length; i++) {
642
- var c = str.charCodeAt(i);
643
- if (c >= 0xd800 && c <= 0xdbff) { var n = str.charCodeAt(i + 1); if (!(n >= 0xdc00 && n <= 0xdfff)) throw new CborError("cbor/bad-utf8", who + ": the string has an unpaired high surrogate"); i++; }
644
- else if (c >= 0xdc00 && c <= 0xdfff) throw new CborError("cbor/bad-utf8", who + ": the string has an unpaired low surrogate");
645
- }
642
+ return guard.text.assertWellFormedUtf16(str, CborError, "cbor/bad-utf8", who + ": the string");
643
+ }
644
+ /** @internal A builder's integer argument is authoring-tier input, so it is held to being an
645
+ * integer rather than converted into one. `BigInt(x)` accepts a string in any radix, a padded or
646
+ * empty string, and a boolean, each of which becomes a well-formed encoding of a number the caller
647
+ * never wrote; an empty string in particular encodes as zero. */
648
+ function _authoredInt(v, who) {
649
+ return guard.range.authoredInteger(v, function (c, m) { return new CborError(c, m); },
650
+ "cbor/bad-argument", who + ": the value");
646
651
  }
652
+
647
653
  function _head(mt, argument, who) {
648
- var a = typeof argument === "bigint" ? argument : BigInt(argument);
654
+ var a = _authoredInt(argument, who);
649
655
  if (a < 0n) throw new CborError("cbor/bad-argument", who + ": a CBOR head argument must be non-negative");
650
656
  if (a > 0xffffffffffffffffn) throw new CborError("cbor/bad-argument", who + ": a CBOR head argument exceeds 64 bits");
651
657
  var mtb = mt << 5, b;
@@ -656,9 +662,9 @@ function _head(mt, argument, who) {
656
662
  b = Buffer.alloc(9); b[0] = mtb | 27; b.writeBigUInt64BE(a, 1); return b;
657
663
  }
658
664
  var build = Object.assign(Object.create(null), {
659
- uint: function (n) { var a = typeof n === "bigint" ? n : BigInt(n); if (a < 0n) throw new CborError("cbor/bad-argument", "build.uint: value must be non-negative"); return _head(0, a, "build.uint"); },
660
- nint: function (n) { var a = typeof n === "bigint" ? n : BigInt(n); if (a >= 0n) throw new CborError("cbor/bad-argument", "build.nint: value must be negative"); return _head(1, -1n - a, "build.nint"); },
661
- int: function (n) { var a = typeof n === "bigint" ? n : BigInt(n); return a >= 0n ? _head(0, a, "build.int") : _head(1, -1n - a, "build.int"); },
665
+ uint: function (n) { var a = _authoredInt(n, "build.uint"); if (a < 0n) throw new CborError("cbor/bad-argument", "build.uint: value must be non-negative"); return _head(0, a, "build.uint"); },
666
+ nint: function (n) { var a = _authoredInt(n, "build.nint"); if (a >= 0n) throw new CborError("cbor/bad-argument", "build.nint: value must be negative"); return _head(1, -1n - a, "build.nint"); },
667
+ int: function (n) { var a = _authoredInt(n, "build.int"); return a >= 0n ? _head(0, a, "build.int") : _head(1, -1n - a, "build.int"); },
662
668
  byteString: function (buf) { var b = _asBuffer(buf, "build.byteString"); return _capString(Buffer.concat([_head(2, b.length, "build.byteString"), b]), "build.byteString"); },
663
669
  textString: function (s) { var str = _asText(s, "build.textString"); _assertWellFormedText(str, "build.textString"); var b = Buffer.from(str, "utf8"); return _capString(Buffer.concat([_head(3, b.length, "build.textString"), b]), "build.textString"); },
664
670
  array: function (items) {
@@ -669,8 +675,12 @@ var build = Object.assign(Object.create(null), {
669
675
  map: function (pairs) {
670
676
  if (!Array.isArray(pairs)) throw new CborError("cbor/bad-argument", "build.map: expected an array of [key, value] pairs");
671
677
  var enc = pairs.map(function (p, i) { if (!Array.isArray(p) || p.length !== 2) throw new CborError("cbor/bad-argument", "build.map[" + i + "]: each entry is a [key, value] pair"); return [_cbItem(p[0], "build.map key"), _cbItem(p[1], "build.map value")]; });
672
- enc.sort(function (a, b) { return Buffer.compare(a[0], b[0]); });
673
- for (var i = 1; i < enc.length; i++) { if (Buffer.compare(enc[i - 1][0], enc[i][0]) === 0) throw new CborError("cbor/duplicate-map-key", "build.map: duplicate map key"); }
678
+ /** @internal The map-key ordering and the duplicate test go through the captured comparator. A
679
+ * deterministic encoding IS its key order, so whatever decides "before" decides the bytes, and the
680
+ * duplicate test asked the same question: a replacement answering a fixed order would emit a map
681
+ * this decoder refuses and would let two equal keys through. */
682
+ enc = guard.list.sortedCopy(enc, function (a, b) { return guard.bytes.compare(a[0], b[0]); });
683
+ for (var i = 1; i < enc.length; i++) { if (guard.bytes.compare(enc[i - 1][0], enc[i][0]) === 0) throw new CborError("cbor/duplicate-map-key", "build.map: duplicate map key"); }
674
684
  var flat = [_head(5, pairs.length, "build.map")];
675
685
  enc.forEach(function (p) { flat.push(p[0], p[1]); });
676
686
  return _assertDecodes(Buffer.concat(flat), "build.map");
@@ -680,7 +690,7 @@ var build = Object.assign(Object.create(null), {
680
690
  nullValue: function () { return Buffer.from([0xf6]); },
681
691
  raw: function (buf) { return _assertDecodes(_asBuffer(buf, "build.raw"), "build.raw"); },
682
692
  biguint: function (n) {
683
- var a = typeof n === "bigint" ? n : BigInt(n);
693
+ var a = _authoredInt(n, "build.biguint");
684
694
  if (a < 0n) throw new CborError("cbor/bad-argument", "build.biguint: value must be non-negative");
685
695
  var hex = a.toString(16); if (hex.length % 2) hex = "0" + hex;
686
696
  var mag = a === 0n ? Buffer.alloc(0) : Buffer.from(hex, "hex");
@@ -695,7 +705,7 @@ var build = Object.assign(Object.create(null), {
695
705
  if (Number.isNaN(ms)) throw new CborError("cbor/bad-time", "build.time: an Invalid Date has no epoch value");
696
706
  if (ms % 1000 !== 0) throw new CborError("cbor/bad-time", "build.time: a Date with sub-second precision cannot be a second-granularity CBOR epoch; round to whole seconds");
697
707
  }
698
- var secs = guard.time.isDate(v) ? BigInt(Math.floor(ms / 1000)) : (typeof v === "bigint" ? v : BigInt(v));
708
+ var secs = guard.time.isDate(v) ? BigInt(Math.floor(ms / 1000)) : _authoredInt(v, "build.time");
699
709
  if (secs < -_MAX_EPOCH_SECONDS || secs > _MAX_EPOCH_SECONDS) throw new CborError("cbor/bad-time", "build.time: epoch seconds " + secs + " are outside the representable Date range");
700
710
  return build.tag(1, build.int(secs));
701
711
  },
package/lib/cmc-build.js CHANGED
@@ -590,7 +590,13 @@ async function _recoverProofValue(decoded, recipient, witnessHash) {
590
590
  for (var i = 0; i < count; i++) {
591
591
  var opened;
592
592
  try {
593
- opened = await cmsDecrypt.decrypt(decoded.cms, recipient, { recipientIndex: i });
593
+ /** @internal The refusal `pki.cms.decrypt` applies to RSAES-PKCS1-v1_5 key transport protects a
594
+ * content whose padding is the only integrity check. Here the witness digest below is that check:
595
+ * it decides the candidate, and a decrypt that fails and a candidate the witness rejects both
596
+ * continue to the next recipient and end in the one `cmc/pop-failed`, so a submitter who does not
597
+ * know the proof value observes one outcome whatever their ciphertext did. RFC 5272 sec. 6.7
598
+ * defines the position. */
599
+ opened = await cmsDecrypt.decrypt(decoded.cms, recipient, { recipientIndex: i, allowUnauthenticatedRsa15: true });
594
600
  } catch (_e) {
595
601
  continue; // allow:swallow-unverified this recipient is not addressed to the supplied key; the witness decides
596
602
  }
@@ -677,8 +683,9 @@ async function answerPopChallenge(popChallenge, ids) {
677
683
  function popLinkWitnessV2(secret, R) {
678
684
  _assertSecret(secret, "a POP Link Witness V2 control");
679
685
  var key = nodeCrypto.createHash("sha256").update(secret, "utf8").digest();
680
- var witness = nodeCrypto.createHmac("sha256", key).update(R).digest();
681
686
  try {
687
+ /** @internal Inside the try, so a MAC that faults on its message still wipes the key above it. */
688
+ var witness = nodeCrypto.createHmac("sha256", key).update(R).digest();
682
689
  return b.sequence([algId(OID_SHA256), algId(OID_HMAC_SHA256), b.octetString(witness)]);
683
690
  } finally {
684
691
  guard.secret.zeroize(key, CmcError, "cmc/bad-input", "the POP Link MAC key");
package/lib/cmc-verify.js CHANGED
@@ -27,6 +27,12 @@ var cmsDecrypt = require("./cms-decrypt");
27
27
  var guard = require("./guard-all");
28
28
  var intrinsic = require("./guard-intrinsic");
29
29
  var _hasOwn = intrinsic.hasOwn;
30
+ /** @internal The verify entry gates its body on a resolved promise and returns its refusals as
31
+ * rejected ones, and both build from the RECEIVER, so a replacement read at the call decides whether
32
+ * the body runs and whether a refusal stays a refusal. Taken at load. */
33
+ var _Promise = intrinsic.Promise;
34
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
35
+ var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
30
36
  var frameworkError = require("./framework-error");
31
37
 
32
38
  var CmcError = frameworkError.CmcError;
@@ -308,7 +314,7 @@ function verify(response, sent) {
308
314
  frozenResponse = _snapshotIfBytes(response);
309
315
  } catch (e) {
310
316
  _wipeOwnedSecret();
311
- return Promise.reject(e);
317
+ return _promiseReject(_Promise, e);
312
318
  }
313
319
  return guard.async.deferred(function () { return _verify(frozenResponse, frozenSent); })
314
320
  .then(function (v) { _wipeOwnedSecret(); return v; },
@@ -387,7 +393,7 @@ function _verify(response, sent) {
387
393
 
388
394
 
389
395
  function _assertAuthentic(body, sent, responseBytes) {
390
- return Promise.resolve().then(function () {
396
+ return _promiseResolve(_Promise, undefined).then(function () {
391
397
  var haveBytes = typeof responseBytes === "string" ||
392
398
  Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array; // allow:byte-source-narrow -- responseBytes is _snapshotIfBytes's return: any BufferSource is already a Buffer here, so this is Buffer-or-string by construction
393
399
  if (body.cms && body.cms.contentTypeName === "authData") {
@@ -413,7 +419,12 @@ function _assertAuthentic(body, sent, responseBytes) {
413
419
  derivedKey = cmcBuild.macPassword(recipient);
414
420
  recipient = { password: derivedKey };
415
421
  }
416
- return cmsDecrypt.decrypt(responseBytes, recipient).then(function (r) { _wipeDerived(); return r; },
422
+ /** @internal The refusal `pki.cms.decrypt` applies to RSAES-PKCS1-v1_5 key transport protects a
423
+ * content whose padding is the only integrity check. This is an AuthenticatedData, whose MAC is
424
+ * that check, so the premise does not hold here and the opt-in states it. The opt-in is inert on
425
+ * this path, because an AuthenticatedData is dispatched to the MAC verifier before the refusal is
426
+ * reached; it is passed so the premise stays recorded where the content type is decided. */
427
+ return cmsDecrypt.decrypt(responseBytes, recipient, { allowUnauthenticatedRsa15: true }).then(function (r) { _wipeDerived(); return r; },
417
428
  function (e) {
418
429
  _wipeDerived();
419
430
  throw E("cmc/unverified-response",
package/lib/cmp-build.js CHANGED
@@ -29,6 +29,12 @@ var cmp = require("./schema-cmp");
29
29
  var crmf = require("./crmf-sign");
30
30
  var schemaCrmf = require("./schema-crmf");
31
31
  var intrinsic = require("./guard-intrinsic");
32
+ /** @internal The promise a signature self-check is awaited through, built from the constructor captured
33
+ * at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
34
+ * the construction to whatever that binding holds, and one settling without awaiting its argument runs
35
+ * the continuation before the check has finished. */
36
+ var _Promise = intrinsic.Promise;
37
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
32
38
  var _hasOwn = intrinsic.hasOwn;
33
39
  var _isArray = intrinsic.isArray;
34
40
  var _isBuffer = intrinsic.isBuffer;
@@ -603,7 +609,7 @@ function _encodeBody(bodySpec, key, opts) {
603
609
  if (bodySpec.pkiconf !== null && bodySpec.pkiconf !== true) throw _err("cmp/bad-input", "pkiconf takes null or true (PKIConfirmContent is NULL)");
604
610
  inner = b.nullValue();
605
611
  }
606
- return Promise.resolve({ bodyTLV: b.explicit(tag, inner), usesCmp2021: state.usesCmp2021 });
612
+ return _promiseResolve(_Promise, { bodyTLV: b.explicit(tag, inner), usesCmp2021: state.usesCmp2021 });
607
613
  }
608
614
 
609
615
  async function _p10crBody(csrDer, parsedCsr, tag, state) {
@@ -786,8 +792,8 @@ function _resolveProtection(opts, message) {
786
792
  protectionAlgDer: scheme.sigAlgId,
787
793
  certDer: certDer,
788
794
  computeBits: function (protectedPartDer) {
789
- return Promise.resolve(signScheme.signOverTbs(scheme, opts.key, protectedPartDer, _signE)).then(function (sig) {
790
- return Promise.resolve(_b.assertSignatureVerifies(protectedPartDer, sig, senderSpki, scheme)).then(function () {
795
+ return _promiseResolve(_Promise, signScheme.signOverTbs(scheme, opts.key, protectedPartDer, _signE)).then(function (sig) {
796
+ return _promiseResolve(_Promise, _b.assertSignatureVerifies(protectedPartDer, sig, senderSpki, scheme)).then(function () {
791
797
  return b.bitString(sig, 0);
792
798
  });
793
799
  });
@@ -999,12 +1005,18 @@ function _tlsForTransfer(opts) {
999
1005
  function _ctypeToken(headers) { return String((headers || {})["content-type"] || "").split(";")[0].trim().toLowerCase(); }
1000
1006
  function _isPkixcmp(headers) { var t = _ctypeToken(headers); return t === PKIXCMP || t === PKIXCMP_POLL; }
1001
1007
 
1008
+ /** @internal The sentinel is added at the one exit rather than at each `return`, so a branch added
1009
+ * later carries it without being told to. */
1002
1010
  function _classifyCmpResponse(status, headers, body, tls) {
1011
+ return guard.verdict.shield(_classifyCmpResponseBody(status, headers, body, tls));
1012
+ }
1013
+
1014
+ function _classifyCmpResponseBody(status, headers, body, tls) {
1003
1015
  var ctype = _ctypeToken(headers);
1004
1016
  if (status === 200) {
1005
1017
  if (!_isPkixcmp(headers)) throw _err("cmp/bad-content-type", "a 200 CMP response must be application/pkixcmp (RFC 9811 sec. 3.2), got " + JSON.stringify(ctype || null));
1006
1018
  if (body.length === 0) throw _err("cmp/empty-response", "a 200 CMP response carried an empty body (RFC 9811 sec. 3.3)");
1007
- return { response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls };
1019
+ return guard.verdict.of({ response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls });
1008
1020
  }
1009
1021
  var httpStatus = (typeof status === "number" && Number.isSafeInteger(status) && status >= 100 && status <= 599) ? status : null;
1010
1022
  if (httpStatus !== null && httpStatus >= 300 && httpStatus < 400) throw _err("cmp/redirect-not-followed", "a CMP " + httpStatus + " redirect is not followed (RFC 9811 sec. 3.1, sec. 5); reconfigure the endpoint URL");
@@ -1014,7 +1026,7 @@ function _classifyCmpResponse(status, headers, body, tls) {
1014
1026
  try { forwarded = cmp.parse(body); }
1015
1027
  catch (e) { throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with an undecodable body", e); }
1016
1028
  if (forwarded.body.arm !== "error") throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with a non-error '" + forwarded.body.arm + "' body; an HTTP failure forwards only a CMP error message (RFC 9811 sec. 1.2/3.1)");
1017
- return { response: forwarded, responseBytes: body, status: httpStatus, contentType: ctype, tls: tls };
1029
+ return guard.verdict.of({ response: forwarded, responseBytes: body, status: httpStatus, contentType: ctype, tls: tls });
1018
1030
  }
1019
1031
  throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with no forwardable CMP error body");
1020
1032
  }
@@ -125,8 +125,8 @@ var PKI_STATUS_WAITING = 3;
125
125
  * single process. The authority's own diagnostic strings are not reproduced from stored state, since a
126
126
  * verdict must not present text the authority did not sign in this exchange as though it had. */
127
127
  function _waitingStatus() {
128
- return { status: { code: PKI_STATUS_WAITING, name: PKI_STATUS_NAMES[PKI_STATUS_WAITING] },
129
- statusString: null, failInfo: null };
128
+ return guard.verdict.of({ status: { code: PKI_STATUS_WAITING, name: PKI_STATUS_NAMES[PKI_STATUS_WAITING] },
129
+ statusString: null, failInfo: null });
130
130
  }
131
131
 
132
132
  function _certReqIdEq(a, b) { return a != null && b != null && BigInt(a) === BigInt(b); }
@@ -717,10 +717,10 @@ function session(opts) {
717
717
  if (code === 3) {
718
718
  if (txnKind === "enroll") throw _err("cmp/bad-error", "an error message answering an enrollment must not carry status waiting -- the profile places enrollment waiting in an ip/cp/kup (RFC 9483 sec. 4.4)");
719
719
  if (si.failInfo != null) throw _err("cmp/bad-error", "an error message with status waiting must not carry failInfo (RFC 9483 sec. 4.4)");
720
- return { state: "waiting", resp: { status: si } };
720
+ return guard.verdict.of({ state: "waiting", resp: guard.verdict.of({ status: si }) });
721
721
  }
722
722
  if (code !== 2) throw _err("cmp/bad-error", "an error message carries status rejection(2), or waiting(3) for a revoke/info delayed delivery (RFC 9483 sec. 4.2); got " + (code == null ? "no status" : code));
723
- return { state: "rejected", status: si };
723
+ return guard.verdict.of({ state: "rejected", status: si });
724
724
  }
725
725
 
726
726
  function _classifyRevRep(body) {
@@ -735,9 +735,9 @@ function session(opts) {
735
735
  if (si.failInfo != null) {
736
736
  return { state: "unexpected", reason: "an accepted rp must not carry failInfo (RFC 9483 sec. 4.2)" };
737
737
  }
738
- return { state: "granted", status: si, revCerts: d.revCerts, crls: d.crls };
738
+ return guard.verdict.of({ state: "granted", status: si, revCerts: d.revCerts, crls: d.crls });
739
739
  }
740
- if (code === 2) return { state: "rejected", status: si };
740
+ if (code === 2) return guard.verdict.of({ state: "rejected", status: si });
741
741
  return { state: "unexpected", reason: "an rp status code " + code + " has no transition in a revocation (RFC 9483 sec. 4.2 allows accepted or rejection)" };
742
742
  }
743
743
 
@@ -851,7 +851,7 @@ function session(opts) {
851
851
  async function _pollLoop(lastWaitingResp, alreadyWaited) {
852
852
  var polls = 0, waited = alreadyWaited || 0, lastStatus = _statusOf(lastWaitingResp);
853
853
  for (;;) {
854
- if (polls >= maxPolls || waited > maxTotalWait) return { timeout: true, polls: polls, status: lastStatus };
854
+ if (polls >= maxPolls || waited > maxTotalWait) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus });
855
855
  var verdict = await _send({ pollReq: [{ certReqId: activeCertReqId }] }, "pollReq");
856
856
  polls += 1;
857
857
  var t = _classify(verdict);
@@ -865,8 +865,8 @@ function session(opts) {
865
865
  /** @internal The authority asked to be polled again after `checkAfter`, and the budget ran out
866
866
  * before that wait was taken. Reporting it lets a resumed poll honor the interval rather than
867
867
  * poll immediately in a new process. */
868
- if (waited > maxTotalWait) return { timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter };
869
- if (polls >= maxPolls) return { timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter };
868
+ if (waited > maxTotalWait) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter });
869
+ if (polls >= maxPolls) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter });
870
870
  await sleep(checkAfter * constants.TIME.seconds(1));
871
871
  continue;
872
872
  }
@@ -920,7 +920,11 @@ function session(opts) {
920
920
  if (pk == null) return true;
921
921
  if (!guard.bytes.isByteSource(pk)) return false;
922
922
  var bits;
923
- try { bits = asn1.read.bitString(asn1.decode(guard.bytes.view(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey")).children[1]); }
923
+ /** @internal `source` rather than `view`, so the set read is the set the line above admits. `view`
924
+ * wants a Buffer or a Uint8Array and its refusal is caught below, so an ArrayBuffer or a DataView
925
+ * carrying a zero-length key was read as an ordinary request for a key the caller holds rather
926
+ * than as the request for a centrally generated one that it is. */
927
+ try { bits = asn1.read.bitString(asn1.decode(guard.bytes.source(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey")).children[1]); }
924
928
  catch (_e) { return false; }
925
929
  return bits.bytes.length === 0;
926
930
  }
@@ -1031,6 +1035,15 @@ function session(opts) {
1031
1035
  if (ckp.privateKey.bytes == null) {
1032
1036
  throw _err("cmp/bad-key-package", "a delivered key package is carried as an EnvelopedData, and this grant carries the legacy RFC 4211 EncryptedValue form (RFC 9483 sec. 4.1.6, RFC 9810 sec. 5.2.2)");
1033
1037
  }
1038
+ /** @internal `pki.cmp.openKeyPackage` leaves the RSAES-PKCS1-v1_5 decision to its caller, because
1039
+ * taking a container on its own it cannot establish that one arrived inside an authenticated
1040
+ * exchange. A session can: it reaches here only for a response whose protection verified AND whose
1041
+ * signer chained to a supplied trust anchor, so a container reaching this point came inside a
1042
+ * message the authority protected and an attacker cannot submit chosen ciphertexts to it. The
1043
+ * premise the refusal protects therefore holds, and the opt-in is the session's to make rather than
1044
+ * a hole the caller has to open. Deployed authorities deliver v1.5 over a CBC content, so withholding
1045
+ * it would refuse the key delivery this verb exists to complete. */
1046
+ open.allowUnauthenticatedRsa15 = true;
1034
1047
  return cmp.openKeyPackage(ckp.privateKey.bytes, open);
1035
1048
  }
1036
1049
 
@@ -1111,7 +1124,7 @@ function session(opts) {
1111
1124
  if (t.state === "waiting") {
1112
1125
  var polled = await _pollLoop(t.resp);
1113
1126
  polls = polled.polls;
1114
- if (polled.timeout) return { timeout: true, polls: polls, status: polled.status };
1127
+ if (polled.timeout) return guard.verdict.of({ timeout: true, polls: polls, status: polled.status });
1115
1128
  t = polled.done;
1116
1129
  }
1117
1130
  return { done: t, polls: polls };
package/lib/cmp-verify.js CHANGED
@@ -413,6 +413,11 @@ function _headerChecks(m, opts) {
413
413
  if (!guard.bytes.isByteSource(m.header.transactionID)) {
414
414
  return { code: "cmp/bad-transaction-id", reason: "the header transactionID is absent; a received PKIMessage MUST carry one (RFC 9483 sec. 3.5)" };
415
415
  }
416
+ /** @internal `lengthOf` wants a view and the check above admits every BufferSource, which elsewhere
417
+ * let an ArrayBuffer through a door and be refused by the line after it. Not reachable here: this
418
+ * verb takes DER bytes rather than a record, so `m` is always what the parser produced, and the
419
+ * parser reads this field as an OCTET STRING, whose value is a Buffer. The field cannot arrive as
420
+ * anything else, so there is nothing to normalize. */
416
421
  if (!guard.bytes.isByteSource(m.header.senderNonce) ||
417
422
  guard.bytes.lengthOf(m.header.senderNonce) < constants.LIMITS.CMP_MIN_NONCE_BYTES) {
418
423
  return { code: "cmp/bad-sender-nonce", reason: "the header senderNonce is absent or shorter than 128 bits (RFC 9483 sec. 3.5)" };
@@ -442,18 +447,9 @@ function _headerChecks(m, opts) {
442
447
  return null;
443
448
  }
444
449
 
445
- function _keyUsageAllowsSigning(parsed) {
446
- var kuOid = oid.byName("keyUsage");
447
- var exts = parsed.extensions;
448
- for (var i = 0; i < exts.length; i++) {
449
- if (exts[i].oid !== kuOid) continue;
450
- var ku;
451
- try { ku = _certExtDecoders[kuOid](exts[i].value); }
452
- catch (_e) { return false; }
453
- return ku.digitalSignature === true;
454
- }
455
- return true;
456
- }
450
+ /** @internal RFC 9483 sec. 3.2 names `digitalSignature` alone, which is stricter than the set a signature
451
+ * over content takes elsewhere; the shared helper carries the rest of the rule. */
452
+ var _keyUsageAllowsSigning = pkix.keyUsagePermits(NS, ["digitalSignature"]);
457
453
 
458
454
  /** @internal The verifying half of RFC 9810 sec. 5.1.3.4. The peer that encapsulated holds the same
459
455
  * shared secret, and derives the key from it through the derivation `pki.cmp.build` uses, so the two
@@ -634,7 +630,7 @@ function _certKey(c) {
634
630
 
635
631
  async function _chainSigner(signer, m, opts, extra) {
636
632
  if (!_keyUsageAllowsSigning(signer.parsed)) {
637
- return { trusted: false, reason: "the signer certificate keyUsage does not assert digitalSignature (RFC 9483 sec. 3.2)" };
633
+ return guard.verdict.of({ trusted: false, reason: "the signer certificate keyUsage does not assert digitalSignature (RFC 9483 sec. 3.2)" });
638
634
  }
639
635
  var time = opts.time != null ? opts.time : new _Date();
640
636
  var anchors = _certList(opts.trustAnchors);
@@ -657,7 +653,7 @@ async function _chainSigner(signer, m, opts, extra) {
657
653
  if (opts.revocationChecker != null) buildOpts.revocationChecker = opts.revocationChecker;
658
654
  try {
659
655
  var res = await _engine.build(signer.der, buildOpts);
660
- if (!res || res.valid !== true) return { trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" };
656
+ if (!res || res.valid !== true) return guard.verdict.of({ trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" });
661
657
  var byKey = _create(null);
662
658
  var skey = _certKey(signer.parsed); if (skey) byKey[skey] = signer.der;
663
659
  var ci, ck;
@@ -672,12 +668,12 @@ async function _chainSigner(signer, m, opts, extra) {
672
668
  var d = ck ? byKey[ck] : null;
673
669
  if (d) _append(chain, _bufferFrom(d));
674
670
  }
675
- return { trusted: true, reason: null, chain: chain };
671
+ return guard.verdict.of({ trusted: true, reason: null, chain: chain });
676
672
  } catch (e) {
677
673
  if (e && e.code === "path/bad-input") {
678
674
  throw _err("cmp/bad-input", "invalid trust / validation options for signer-certificate path validation: " + (e.message || e), e);
679
675
  }
680
- return { trusted: false, reason: "signer certificate path validation failed: " + (e.message || e) };
676
+ return guard.verdict.of({ trusted: false, reason: "signer certificate path validation failed: " + (e.message || e) });
681
677
  }
682
678
  }
683
679
 
@@ -878,7 +874,7 @@ async function _verify(message, opts) {
878
874
 
879
875
  var KNOWN_OPEN_KEY_PACKAGE_OPTS = intrinsic.assign(intrinsic.create(null), {
880
876
  key: 1, cert: 1, password: 1, trustAnchors: 1, intermediates: 1, time: 1,
881
- authorizedBySharedSecret: 1,
877
+ authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1,
882
878
  });
883
879
  var OID_A_KEY_PACKAGE = oid.byName("aKeyPackage");
884
880
  var OID_SIGNED_DATA = oid.byName("signedData");
@@ -953,6 +949,7 @@ function _fixOpenKeyPackageOptions(opts, made) {
953
949
  intermediates: _fixPoolCerts(opts.intermediates, "intermediates"),
954
950
  time: _fixTime(opts.time),
955
951
  exempt: opts.authorizedBySharedSecret === true,
952
+ allowRsa15: opts.allowUnauthenticatedRsa15 === true,
956
953
  };
957
954
  if (opts.authorizedBySharedSecret != null && typeof opts.authorizedBySharedSecret !== "boolean") {
958
955
  throw _err("cmp/bad-input", "opts.authorizedBySharedSecret is a boolean stating that this entity protected its request with the shared secret");
@@ -995,8 +992,16 @@ async function _openKeyPackage(container, opts) {
995
992
  * a single uniform result, and splitting that back out here would rebuild the oracle it removes. */
996
993
  var opened;
997
994
  try {
995
+ /** @internal The opt-in is the caller's, not this verb's. `pki.cms.decrypt` refuses RSAES-PKCS1-v1_5
996
+ * key transport over a content with no integrity tag because acceptance tells an attacker
997
+ * submitting chosen containers that theirs decoded. This verb takes the container itself, so it
998
+ * cannot establish that one arrived inside an authenticated exchange, and only the caller knows
999
+ * whether the containers it opens come from one. The password technique never reaches the refusal:
1000
+ * it is a `pwri` arm, not key transport. */
1001
+ var decOpts = { recipientIndex: 0 };
1002
+ if (opts.allowRsa15) decOpts.allowUnauthenticatedRsa15 = true;
998
1003
  opened = await cmsDecrypt.decrypt(bytes, opts.password != null ? { password: opts.password } : { key: opts.key, cert: opts.cert },
999
- { recipientIndex: 0 });
1004
+ decOpts);
1000
1005
  } catch (e) {
1001
1006
  throw _keyPackageErr("the delivered key package could not be opened under the supplied key material", e);
1002
1007
  }
@@ -1016,7 +1021,7 @@ async function _openKeyPackage(container, opts) {
1016
1021
  if (!signerCert) throw _keyPackageErr("the SignedData names no signer certificate to authorize as the key generation authority");
1017
1022
  _authorizeKga(verdict, signerCert, opts);
1018
1023
  if (verdict.eContent == null) throw _keyPackageErr("the SignedData carries no encapsulated content, so no key package was delivered");
1019
- return { keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true };
1024
+ return guard.verdict.of({ keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true });
1020
1025
  }
1021
1026
 
1022
1027
  /** @internal sec. 4.1.6: the entity "MUST use a certificate containing the additional extended key
@@ -1150,6 +1155,10 @@ function _coerceKeyPackageBytes(container) {
1150
1155
  * - `authorizedBySharedSecret` (boolean) -- authorize the authority by the secret that protected
1151
1156
  * the request rather than by a chain, which sec. 4.1.6 permits a MAC-protected exchange. Requires
1152
1157
  * `opts.password` and refuses `opts.trustAnchors`.
1158
+ * - `allowUnauthenticatedRsa15` (boolean) -- forwarded to `pki.cms.decrypt`: open a container that
1159
+ * uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag, such as AES-CBC. Set
1160
+ * it only where the containers this entity opens arrive inside an authenticated exchange, because
1161
+ * acceptance otherwise tells an attacker submitting chosen containers that theirs decoded.
1153
1162
  * @example
1154
1163
  * var nb = new Date("2026-01-01T00:00:00Z"), na = new Date("2036-01-01T00:00:00Z");
1155
1164
  * var kgaPair = await pki.key.generate("Ed25519"); // the key generation authority
@@ -52,7 +52,9 @@ async function decompress(input, opts) {
52
52
  if (parsed.contentTypeName !== "compressedData") throw _err("cms/unsupported-content-type", "input is not a CMS CompressedData (got " + parsed.contentTypeName + ")");
53
53
  var alg = parsed.compressionAlgorithm;
54
54
  if (alg.oid !== OID_ZLIB) throw _err("cms/unsupported-algorithm", "unsupported compressionAlgorithm " + (alg.name || alg.oid) + " (only id-alg-zlibCompress, RFC 3274 sec. 2)");
55
- if (alg.parameters != null && Buffer.compare(alg.parameters, NULL_PARAMS_DER) !== 0) throw _err("cms/bad-algorithm-parameters", "id-alg-zlibCompress parameters must be absent or NULL (RFC 3274 sec. 2)");
55
+ /** @internal Through the captured comparator, since this refusal is what a replacement answering
56
+ * zero would turn into an acceptance of parameters the specification forbids. */
57
+ if (alg.parameters != null && guard.bytes.compare(alg.parameters, NULL_PARAMS_DER) !== 0) throw _err("cms/bad-algorithm-parameters", "id-alg-zlibCompress parameters must be absent or NULL (RFC 3274 sec. 2)");
56
58
  var eci = parsed.encapContentInfo;
57
59
  if (eci.eContent == null) throw _err("cms/no-encapsulated-content", "the CompressedData carries no encapsulated content (a detached CompressedData cannot be decompressed)");
58
60
  var content = _inflateBounded(eci.eContent, cap);