@blamejs/pki 0.8.55 → 0.8.56

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 (50) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/lib/acme.js +2 -2
  3. package/lib/asn1-der.js +17 -5
  4. package/lib/attrcert-verify.js +1 -1
  5. package/lib/cmc-verify.js +1 -1
  6. package/lib/cmp-session.js +2 -2
  7. package/lib/cmp-verify.js +2 -2
  8. package/lib/cms-decrypt.js +4 -1
  9. package/lib/cms-encrypt.js +4 -1
  10. package/lib/cms-sign.js +26 -12
  11. package/lib/cms-verify.js +1 -1
  12. package/lib/composite-kem.js +30 -12
  13. package/lib/composite-sig.js +42 -23
  14. package/lib/constants.js +7 -2
  15. package/lib/crl-verify.js +1 -1
  16. package/lib/crmf-sign.js +72 -43
  17. package/lib/crmf-verify.js +1 -1
  18. package/lib/csr-verify.js +1 -1
  19. package/lib/ct.js +2 -2
  20. package/lib/edwards-point.js +1 -1
  21. package/lib/est.js +3 -3
  22. package/lib/guard-async.js +22 -1
  23. package/lib/guard-bytes.js +6 -3
  24. package/lib/guard-identifier.js +1 -1
  25. package/lib/guard-intrinsic.js +474 -0
  26. package/lib/http-digest.js +146 -62
  27. package/lib/http-transport.js +82 -17
  28. package/lib/identity-match.js +2 -2
  29. package/lib/merkle.js +2 -2
  30. package/lib/ocsp-request-verify.js +1 -1
  31. package/lib/ocsp.js +46 -22
  32. package/lib/path-validate.js +169 -76
  33. package/lib/pbes2.js +14 -7
  34. package/lib/schema-attrcert.js +2 -2
  35. package/lib/schema-cmc.js +2 -2
  36. package/lib/schema-cmp.js +2 -2
  37. package/lib/schema-cms.js +2 -2
  38. package/lib/schema-crmf.js +2 -2
  39. package/lib/schema-csr.js +2 -2
  40. package/lib/schema-pkix.js +3 -3
  41. package/lib/schema-x509.js +2 -2
  42. package/lib/sigstore.js +1 -1
  43. package/lib/tlog.js +7 -4
  44. package/lib/trust.js +187 -104
  45. package/lib/tsp-sign.js +1 -1
  46. package/lib/webauthn-mds.js +257 -121
  47. package/lib/webauthn.js +327 -164
  48. package/lib/webcrypto.js +10 -3
  49. package/package.json +1 -1
  50. package/sbom.cdx.json +6 -6
@@ -60,6 +60,13 @@ var _pop = intrinsic.pop;
60
60
  var _splice = intrinsic.splice;
61
61
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
62
62
  var _strIndexOf = intrinsic.uncurry(String.prototype.indexOf);
63
+ /** @internal The case fold a name constraint is compared under. Read off the live prototype, it is
64
+ * the replaceable operation that decides whether a name falls inside an EXCLUDED subtree, and a
65
+ * replacement that mangles the constraint alone while leaving the host as it is makes the exclusion
66
+ * stop matching: measured, a leaf naming `www.bad.example` under an `excludedSubtrees` of
67
+ * `bad.example` was ADMITTED with no failure code at all. Appending to both sides preserves the
68
+ * suffix relation and hides this, so only the asymmetric replacement shows it. */
69
+ var _strToLowerCase = intrinsic.toLowerCase;
63
70
  var _resolve = intrinsic.uncurry(intrinsic.promiseResolve);
64
71
  var _reject = intrinsic.uncurry(intrinsic.promiseReject);
65
72
  /** @internal The promise constructor, captured at load. `promiseResolve` builds its promise from the
@@ -81,6 +88,12 @@ var validator = require("./validator-all");
81
88
  var cms = require("./schema-cms");
82
89
  var httpTransport = require("./http-transport");
83
90
  var net = require("net");
91
+ /** @internal The address-family classification, captured at load. It decides the SSRF question for an
92
+ * AIA URL: a host the classifier calls an IP LITERAL is fetched without requiring the transport to
93
+ * declare that it filters resolved addresses, so a replacement answering that a DNS name is a
94
+ * literal fetches it under a guarantee nothing made. The export is writable, and this runs before
95
+ * the blocklist it feeds. */
96
+ var _netIsIP = net.isIP;
84
97
  var compositeSig = require("./composite-sig");
85
98
  /** @internal Captured at load: `pki.shbs` is the same module object the public surface exposes, and its
86
99
  * `verify` is a writable property, so reading it when a signature is checked would let whoever wrote it
@@ -92,7 +105,7 @@ var PathError = errors.PathError;
92
105
  function E(code, message, cause) { return new PathError(code, message, cause); }
93
106
  function pathCode(e, fallback) {
94
107
  if (!e || typeof e.code !== "string") return fallback;
95
- if (e.code.indexOf("path/") === 0) return e.code;
108
+ if (_strIndexOf(e.code, "path/") === 0) return e.code;
96
109
  if (e.code === "webcrypto/not-supported" || e.code === "webcrypto/data") return "path/unsupported-algorithm";
97
110
  return fallback;
98
111
  }
@@ -218,7 +231,7 @@ function resolveRsaPss(paramsBytes) {
218
231
  throw E("path/unsupported-algorithm", "RSASSA-PSS parameters must be an RSASSA-PSS-params SEQUENCE (RFC 4055)");
219
232
  }
220
233
  var pssLastTag = -1;
221
- n.children.forEach(function (f) {
234
+ intrinsic.forEach(n.children, function (f) {
222
235
  if (f.tagClass !== "context") throw E("path/unsupported-algorithm", "RSASSA-PSS-params fields must be context-tagged (RFC 4055)");
223
236
  if (f.tagNumber > 3 || f.tagNumber <= pssLastTag) throw E("path/unsupported-algorithm", "RSASSA-PSS-params has an unexpected, duplicate, or out-of-order field [" + f.tagNumber + "]");
224
237
  pssLastTag = f.tagNumber;
@@ -492,12 +505,18 @@ function builtinVerify(state, cert) {
492
505
  if (d.composite) return compositeSig.compositeVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature");
493
506
  if (d.shbs) return _resolve(_Promise, _shbsVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes));
494
507
  var key;
495
- return _importVerifyKey(state.workingPublicKey, d).then(function (k) {
508
+ /** @internal Both continuations come from the capture. A continuation is read off the promise at
509
+ * the call, and the one below IS where the signature verdict is decided: a replacement settling
510
+ * the import step hands this a key nothing imported, and one settling the verify step reports
511
+ * `ok: true` for a signature that was never checked, after which a chain whose issuer name matches
512
+ * a supplied root is accepted on a signature that does not verify. */
513
+ var verified = intrinsic.chain(_importVerifyKey(state.workingPublicKey, d), function (k) {
496
514
  key = k;
497
515
  var sig = cert.signatureValue.bytes;
498
516
  if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
499
517
  return _subtleVerify(subtle, d.verify, key, sig, cert.tbsBytes);
500
- }).then(function (ok) {
518
+ });
519
+ return intrinsic.chain(verified, function (ok) {
501
520
  return guard.verdict.of({ ok: ok === true });
502
521
  }, function (e) {
503
522
  return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-signature"), error: e });
@@ -542,7 +561,7 @@ var splitMailbox = guard.name.splitMailbox;
542
561
  function emailMatch(constraint, mailbox) {
543
562
  var mb = splitMailbox(mailbox);
544
563
  if (mb === "ambiguous") return "unsupported";
545
- if (constraint.indexOf("@") !== -1) {
564
+ if (_strIndexOf(constraint, "@") !== -1) {
546
565
  var cb = splitMailbox(constraint);
547
566
  if (cb === "ambiguous" || cb === null || mb === null) return "unsupported";
548
567
  /** @internal A constraint naming a whole mailbox asks the RFC 5280 sec. 7.5 question, so it is
@@ -555,10 +574,10 @@ function emailMatch(constraint, mailbox) {
555
574
  return "unsupported";
556
575
  }
557
576
  if (mb === null) return "unsupported";
558
- var host = stripTrailingDot(mb[1].toLowerCase());
577
+ var host = stripTrailingDot(_strToLowerCase(mb[1]));
559
578
  if (host === "") return "unsupported";
560
- var c = stripTrailingDot(constraint.toLowerCase());
561
- if (c.charAt(0) === ".") return host.length > c.length && host.slice(-c.length) === c;
579
+ var c = stripTrailingDot(_strToLowerCase(constraint));
580
+ if (_charCodeAt(c, 0) === 46) return host.length > c.length && _strSlice(host, -c.length) === c;
562
581
  return host === c;
563
582
  }
564
583
 
@@ -575,10 +594,10 @@ var stripTrailingDot = guard.name.stripTrailingDot;
575
594
  * fail-open mirror, so it is asked for only where the caller wants reach. A wildcard reaches exactly
576
595
  * one label, so a base below the wildcard's parent is reached only when it is one label below it. */
577
596
  function hostConstraintMatch(constraint, host, reachIntoBase) {
578
- var c = stripTrailingDot(constraint.toLowerCase()), h = stripTrailingDot(host.toLowerCase());
597
+ var c = stripTrailingDot(_strToLowerCase(constraint)), h = stripTrailingDot(_strToLowerCase(host));
579
598
  if (c === "") return true;
580
- if (c.charAt(0) === ".") return h.length > c.length && h.slice(-c.length) === c;
581
- if (h === c || (h.length > c.length && h.slice(-(c.length + 1)) === "." + c)) return true;
599
+ if (_charCodeAt(c, 0) === 46) return h.length > c.length && _strSlice(h, -c.length) === c;
600
+ if (h === c || (h.length > c.length && _strSlice(h, -(c.length + 1)) === "." + c)) return true;
582
601
  if (reachIntoBase === true && h.length > 2 && _charCodeAt(h, 0) === 42 && _charCodeAt(h, 1) === 46) {
583
602
  var parent = _strSlice(h, 2);
584
603
  if (parent.length > 0 && c.length > parent.length && _strSlice(c, -(parent.length + 1)) === "." + parent) {
@@ -596,16 +615,16 @@ var isFqdnHost = guard.name.isFqdnHost;
596
615
  * without them. */
597
616
  function _stripBrackets(s) {
598
617
  var n = s.length;
599
- return (n >= 2 && _charCodeAt(s, 0) === 91 && _charCodeAt(s, n - 1) === 93) ? s.slice(1, n - 1) : s;
618
+ return (n >= 2 && _charCodeAt(s, 0) === 91 && _charCodeAt(s, n - 1) === 93) ? _strSlice(s, 1, n - 1) : s;
600
619
  }
601
620
 
602
621
  function uriMatch(constraint, uri) {
603
622
  var host = uriHost(uri);
604
623
  if (host === null) return "unsupported";
605
624
  if (!isFqdnHost(host)) return "unsupported";
606
- var c = stripTrailingDot(constraint.toLowerCase()), h = stripTrailingDot(host.toLowerCase());
607
- if (!isFqdnHost(c.charAt(0) === "." ? c.slice(1) : c)) return "unsupported";
608
- if (c.charAt(0) === ".") return h.length > c.length && h.slice(-c.length) === c;
625
+ var c = stripTrailingDot(_strToLowerCase(constraint)), h = stripTrailingDot(_strToLowerCase(host));
626
+ if (!isFqdnHost(_charCodeAt(c, 0) === 46 ? _strSlice(c, 1) : c)) return "unsupported";
627
+ if (_charCodeAt(c, 0) === 46) return h.length > c.length && _strSlice(h, -c.length) === c;
609
628
  return h === c;
610
629
  }
611
630
 
@@ -660,15 +679,20 @@ function certNameForms(cert) {
660
679
  var forms = [];
661
680
  var san = decodeExt(cert, OID.subjectAltName);
662
681
  var hasRfc822San = false;
682
+ /** @internal The walks that collect the certificate's OWN names come from the captures. A replaced
683
+ * `Array.prototype.forEach` that skips an entry here leaves the name out of `forms`, and a name
684
+ * that was never collected is a name no constraint can match: measured, skipping the leaf's
685
+ * `subjectAltName` entry admitted it under an exclusion that named it exactly. The comparison
686
+ * operations and the subtree collection were converted first and neither covers this. */
663
687
  if (san) {
664
- san.value.names.forEach(function (nm) {
688
+ intrinsic.forEach(san.value.names, function (nm) {
665
689
  if (nm.tagNumber === 1) hasRfc822San = true;
666
690
  guard.list.append(forms, { tag: nm.tagNumber, value: nm.value === undefined ? null : nm.value });
667
691
  });
668
692
  }
669
693
  if (!hasRfc822San) {
670
- cert.subject.rdns.forEach(function (rdn) {
671
- rdn.forEach(function (atv) {
694
+ intrinsic.forEach(cert.subject.rdns, function (rdn) {
695
+ intrinsic.forEach(rdn, function (atv) {
672
696
  if (atv.type === OID.emailAddress && typeof atv.value === "string") guard.list.append(forms, { tag: 1, value: atv.value });
673
697
  });
674
698
  });
@@ -690,10 +714,15 @@ function checkNameConstraints(state, cert) {
690
714
  for (var f = 0; f < forms.length; f++) {
691
715
  var nf = forms[f];
692
716
  for (var g = 0; g < state.permittedGenerations.length; g++) {
693
- var formSubtrees = state.permittedGenerations[g].filter(function (s) { return s.tag === nf.tag; });
717
+ /** @internal The filter and the walk come from the captures. An EMPTY result here means "this
718
+ * generation constrains no name of this form", which SKIPS the permitted check for it, so a
719
+ * replaced `filter` answering with an empty array admits a name no subtree permits. The
720
+ * excluded direction refuses on a match and the permitted direction refuses on the absence of
721
+ * one, so this is the arm where a dropped entry opens rather than closes. */
722
+ var formSubtrees = intrinsic.selectList(state.permittedGenerations[g], function (s) { return s.tag === nf.tag; });
694
723
  if (!formSubtrees.length) continue;
695
724
  var permitted = false, unsupported = false;
696
- formSubtrees.forEach(function (s) {
725
+ intrinsic.forEach(formSubtrees, function (s) {
697
726
  var r = nameMatchesConstraint(nf.tag, nf.value, { tagNumber: s.tag, value: s.base });
698
727
  if (r === true) permitted = true;
699
728
  else if (r === "unsupported") unsupported = true;
@@ -704,13 +733,18 @@ function checkNameConstraints(state, cert) {
704
733
  return guard.verdict.of({ ok: true });
705
734
  }
706
735
 
736
+ /** @internal The walks that COLLECT the constraints come from the captures, not the live prototypes.
737
+ * Converting the comparisons alone does not cover the collection that feeds them: a replaced
738
+ * `Array.prototype.forEach` that skips an excluded-subtree entry means the exclusion is never
739
+ * recorded, so there is nothing left to compare against. Measured, a leaf naming `www.bad.example`
740
+ * was ADMITTED with no failure code when the entry was skipped on its way in. */
707
741
  function absorbNameConstraints(state, decoded) {
708
742
  if (decoded.permittedSubtrees.length) {
709
- guard.list.append(state.permittedGenerations, decoded.permittedSubtrees.map(function (st) {
743
+ guard.list.append(state.permittedGenerations, intrinsic.mapList(decoded.permittedSubtrees, function (st) {
710
744
  return { tag: st.base.tagNumber, base: st.base.value };
711
745
  }));
712
746
  }
713
- decoded.excludedSubtrees.forEach(function (st) {
747
+ intrinsic.forEach(decoded.excludedSubtrees, function (st) {
714
748
  guard.list.append(state.excludedSubtrees, { tag: st.base.tagNumber, base: st.base.value });
715
749
  });
716
750
  }
@@ -761,19 +795,22 @@ function addPolicyNode(state, depth, validPolicy, qualifiers, expectedPolicySet,
761
795
  state.policyNodeCount++;
762
796
  }
763
797
  for (var i = 0; i < parents.length; i++) {
764
- if (node.parents.indexOf(parents[i]) !== -1) continue;
798
+ if (intrinsic.indexOf(node.parents, parents[i]) !== -1) continue;
765
799
  guard.list.append(node.parents, parents[i]);
766
800
  guard.list.append(parents[i].children, node);
767
801
  }
768
802
  return node;
769
803
  }
770
804
  function removePolicyNode(state, node) {
771
- node.parents.forEach(function (p) {
772
- var idx = p.children.indexOf(node);
805
+ /** @internal The unlinking walks come from the captures. Removing a node is how a policy stops
806
+ * being valid for the chain, so a walk that skips an edge leaves the node reachable and the policy
807
+ * still counted after the step that disqualified it. */
808
+ intrinsic.forEach(node.parents, function (p) {
809
+ var idx = intrinsic.indexOf(p.children, node);
773
810
  if (idx !== -1) _splice(p.children, idx, 1);
774
811
  });
775
- node.children.forEach(function (c) {
776
- var idx = c.parents.indexOf(node);
812
+ intrinsic.forEach(node.children, function (c) {
813
+ var idx = intrinsic.indexOf(c.parents, node);
777
814
  if (idx !== -1) _splice(c.parents, idx, 1);
778
815
  });
779
816
  node.parents = [];
@@ -787,7 +824,10 @@ function removePolicyNode(state, node) {
787
824
  * as NULL. */
788
825
  function prunePolicyChildless(state, depth) {
789
826
  for (var d = depth; d > 0; d--) {
790
- nodesAt(state, d).forEach(function (node) {
827
+ /** @internal The pruning walk is captured. A node with no children is one no policy below it
828
+ * depends on, and leaving it in place is what makes an emptied graph read as non-empty, which is
829
+ * the test `initialExplicitPolicy` fails a chain on. */
830
+ intrinsic.forEach(nodesAt(state, d), function (node) {
791
831
  if (node.children.length === 0) removePolicyNode(state, node);
792
832
  });
793
833
  }
@@ -809,7 +849,7 @@ function graphSnapshot(root) {
809
849
  if (intrinsic.mapHas(indexOfNode, node)) return;
810
850
  intrinsic.mapSet(indexOfNode, node, originals.length);
811
851
  guard.list.append(originals, node);
812
- node.children.forEach(walk);
852
+ intrinsic.forEach(node.children, walk);
813
853
  })(root);
814
854
  function idsOf(list) {
815
855
  var out = [];
@@ -930,9 +970,9 @@ function _mergedPolicySet(callerSet, anchorSet) {
930
970
  var callerAny = callerSet === undefined || callerSet === null || contains(callerSet, OID.anyPolicy);
931
971
  var anchorAny = contains(anchorSet, OID.anyPolicy);
932
972
  if (anchorAny && callerAny) return [OID.anyPolicy];
933
- if (anchorAny) return intrinsic.map(callerSet, function (p) { return p; });
934
- if (callerAny) return intrinsic.map(anchorSet, function (p) { return p; });
935
- return intrinsic.filter(anchorSet, function (p) { return contains(callerSet, p); });
973
+ if (anchorAny) return intrinsic.mapList(callerSet, function (p) { return p; });
974
+ if (callerAny) return intrinsic.mapList(anchorSet, function (p) { return p; });
975
+ return intrinsic.selectList(anchorSet, function (p) { return contains(callerSet, p); });
936
976
  }
937
977
 
938
978
  /**
@@ -971,7 +1011,7 @@ function _initialState(certs, params, seeds, anchor, n, ta, policySwitches) {
971
1011
  policyNodeCount: 1,
972
1012
  maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
973
1013
  minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
974
- permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
1014
+ permittedGenerations: intrinsic.selectList(seeds.permittedGenerations, function (g) { return g.length > 0; }),
975
1015
  excludedSubtrees: seeds.excluded,
976
1016
  explicitPolicy: _flagSet(policySwitches.initialExplicitPolicy, ta.policyFlags, "requireExplicitPolicy") ? 0 : n + 1,
977
1017
  inhibitAnyPolicy: _flagSet(policySwitches.initialAnyPolicyInhibit, ta.policyFlags, "inhibitAnyPolicy") ? 0 : n + 1,
@@ -1009,9 +1049,14 @@ function processPolicies(state, cert, i, checks) {
1009
1049
 
1010
1050
  /** @internal RFC 9618 sec. 5.3 step (d)(1): ONE node per policy, carrying every depth i-1 node
1011
1051
  * that expected it. Creating one per parent instead is what made the structure exponential. */
1012
- policies.forEach(function (p) {
1052
+ /** @internal The walk comes from the capture. It decides which of the certificate's SIGNED
1053
+ * policy entries reach the graph, so a replacement calling the callback with a fabricated
1054
+ * `policyIdentifier` builds a node for an OID the certificate never asserted, and under
1055
+ * `initialExplicitPolicy` with a stated `userInitialPolicySet` that is what makes an otherwise
1056
+ * policy-invalid chain acceptable. */
1057
+ intrinsic.forEach(policies, function (p) {
1013
1058
  if (p.policyIdentifier === OID.anyPolicy) { anyPolicyPresent = true; anyPolicyQualifiers = p.qualifiersBytes; return; }
1014
- var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
1059
+ var parents = intrinsic.selectList(nodesAt(state, prev), function (node) {
1015
1060
  return contains(node.expectedPolicySet, p.policyIdentifier);
1016
1061
  });
1017
1062
  if (parents.length === 0) {
@@ -1028,14 +1073,16 @@ function processPolicies(state, cert, i, checks) {
1028
1073
  * yet, one node carrying every parent that expected it. */
1029
1074
  if (anyPolicyPresent && anyPolicyActive) {
1030
1075
  var expected = [];
1031
- nodesAt(state, prev).forEach(function (node) {
1032
- node.expectedPolicySet.forEach(function (ep) {
1033
- if (expected.indexOf(ep) === -1) guard.list.append(expected, ep);
1076
+ /** @internal The anyPolicy arm's three walks and its membership test come from the captures
1077
+ * for the same reason: each decides which policies the graph carries at this depth. */
1078
+ intrinsic.forEach(nodesAt(state, prev), function (node) {
1079
+ intrinsic.forEach(node.expectedPolicySet, function (ep) {
1080
+ if (intrinsic.indexOf(expected, ep) === -1) guard.list.append(expected, ep);
1034
1081
  });
1035
1082
  });
1036
- expected.forEach(function (ep) {
1083
+ intrinsic.forEach(expected, function (ep) {
1037
1084
  if (nodeAt(state, i, ep) !== null) return;
1038
- var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
1085
+ var parents = intrinsic.selectList(nodesAt(state, prev), function (node) {
1039
1086
  return contains(node.expectedPolicySet, ep);
1040
1087
  });
1041
1088
  addPolicyNode(state, i, ep, anyPolicyQualifiers, [ep], parents);
@@ -1178,15 +1225,23 @@ function prepareNext(state, cert, i, checks) {
1178
1225
  function applyPolicyMappings(state, mappings, i) {
1179
1226
  if (state.policyMapping > 0) {
1180
1227
  var mappedFrom = intrinsic.create(null);
1181
- mappings.forEach(function (m) {
1228
+ /** @internal Both mapping walks are captured. The pair a mapping carries is what substitutes one
1229
+ * policy for another down the chain, so a walk handing back a fabricated pair maps a policy the
1230
+ * certificate does not state, and one that skips a pair leaves the issuer's policy in place
1231
+ * where the step requires it to be replaced. */
1232
+ intrinsic.forEach(mappings, function (m) {
1182
1233
  if (m.issuerDomainPolicy === OID.anyPolicy || m.subjectDomainPolicy === OID.anyPolicy) return;
1183
1234
  guard.list.append(mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || [], m.subjectDomainPolicy);
1184
1235
  });
1185
1236
  intrinsic.forEach(intrinsic.keys(mappedFrom), function (idp) {
1186
1237
  var node = nodeAt(state, i, idp);
1187
1238
  if (node !== null) {
1188
- /** @internal (b)(1) */
1189
- node.expectedPolicySet = mappedFrom[idp].slice();
1239
+ /** @internal (b)(1). The copy is built through a value this toolkit constructs itself. A
1240
+ * captured `slice` still builds its result through the constructor it reads off the
1241
+ * receiver, and this copy BECOMES the node's expectedPolicySet, so a construction that
1242
+ * substitutes an element maps an issuer domain policy to a subject policy the certificate
1243
+ * never authorized and the path reports valid. */
1244
+ node.expectedPolicySet = intrinsic.copyList(mappedFrom[idp]);
1190
1245
  return;
1191
1246
  }
1192
1247
  /** @internal (b)(2): no node for the issuer domain policy, so one is generated under the
@@ -1195,7 +1250,7 @@ function applyPolicyMappings(state, mappings, i) {
1195
1250
  if (anyHere === null) return;
1196
1251
  var anyPrev = nodeAt(state, i - 1, OID.anyPolicy);
1197
1252
  if (anyPrev === null) return;
1198
- addPolicyNode(state, i, idp, anyHere.qualifierSet[0] || null, mappedFrom[idp].slice(), [anyPrev]);
1253
+ addPolicyNode(state, i, idp, anyHere.qualifierSet[0] || null, intrinsic.copyList(mappedFrom[idp]), [anyPrev]);
1199
1254
  });
1200
1255
  return;
1201
1256
  }
@@ -1203,7 +1258,7 @@ function applyPolicyMappings(state, mappings, i) {
1203
1258
  * and every ancestor it leaves childless goes with it. */
1204
1259
  if (!state.validPolicyGraph) return;
1205
1260
  var deleted = false;
1206
- mappings.forEach(function (m) {
1261
+ intrinsic.forEach(mappings, function (m) {
1207
1262
  if (m.issuerDomainPolicy === OID.anyPolicy) return;
1208
1263
  var node = nodeAt(state, i, m.issuerDomainPolicy);
1209
1264
  if (node !== null) { removePolicyNode(state, node); deleted = true; }
@@ -1479,7 +1534,13 @@ async function _validate(path, opts, run, scope) {
1479
1534
  throw E("path/bad-input", "validate: opts.minRsaModulusBits may only raise the " + constants.LIMITS.RSA_MIN_MODULUS_BITS + "-bit minimum, not lower it");
1480
1535
  }
1481
1536
  }
1482
- var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
1537
+ /** @internal The certificate list is built through a value this toolkit constructs itself, because
1538
+ * its LENGTH is what bounds the validation loop below. A built-in projection builds its result
1539
+ * through the constructor it reads off the receiver, and one answering with a shorter object drops
1540
+ * certificates from the middle of the path: the loop then never reaches the intermediate that
1541
+ * carried a name constraint or a basicConstraints path-length bound, and the shortened path is
1542
+ * what gets validated. */
1543
+ var certs = intrinsic.mapList(path, function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
1483
1544
  var n = certs.length;
1484
1545
  if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
1485
1546
  /** @internal The four switches that turn a requirement on, each read as the boolean it documents
@@ -1564,7 +1625,7 @@ async function _validate(path, opts, run, scope) {
1564
1625
  var taConstraints = _anchorRfc5914Constraints(anchor);
1565
1626
  var seeds = {
1566
1627
  permittedGenerations: [callerPermitted, anchorNc.permitted, taConstraints.permitted],
1567
- excluded: intrinsic.concat(intrinsic.concat(callerExcluded, anchorNc.excluded), taConstraints.excluded),
1628
+ excluded: intrinsic.concatList(intrinsic.concatList(callerExcluded, anchorNc.excluded), taConstraints.excluded),
1568
1629
  };
1569
1630
  var anchorNameConstraintsApplied = anchorNc.permitted.length > 0 || anchorNc.excluded.length > 0 ||
1570
1631
  taConstraints.permitted.length > 0 || taConstraints.excluded.length > 0;
@@ -1855,12 +1916,16 @@ function shieldResults(results) {
1855
1916
  function userConstrainedPolicies(state, n) {
1856
1917
  if (!state.validPolicyGraph) return [];
1857
1918
  var authority = [];
1919
+ /** @internal The walks and membership tests that build this set are captured. This IS the policy
1920
+ * answer the caller reads and the one `initialExplicitPolicy` requires to be non-empty, so a walk
1921
+ * that adds a policy no node carries reports a chain as valid for it, and one that drops a policy
1922
+ * fails a chain that satisfies the stated set. */
1858
1923
  function note(node) {
1859
- if (authority.indexOf(node.validPolicy) === -1) guard.list.append(authority, node.validPolicy);
1924
+ if (intrinsic.indexOf(authority, node.validPolicy) === -1) guard.list.append(authority, node.validPolicy);
1860
1925
  }
1861
1926
  /** @internal (g)(2) */
1862
1927
  for (var d = 1; d <= n; d++) {
1863
- nodesAt(state, d).forEach(function (node) {
1928
+ intrinsic.forEach(nodesAt(state, d), function (node) {
1864
1929
  if (node.validPolicy === OID.anyPolicy) return;
1865
1930
  if (node.parents.length === 1 && node.parents[0].validPolicy === OID.anyPolicy) note(node);
1866
1931
  });
@@ -1872,9 +1937,9 @@ function userConstrainedPolicies(state, n) {
1872
1937
  /** @internal (g)(5) and (g)(6) */
1873
1938
  var uips = state.userInitialPolicySet;
1874
1939
  if (contains(uips, OID.anyPolicy)) return authority;
1875
- var out = intrinsic.filter(authority, function (p) { return contains(uips, p); });
1940
+ var out = intrinsic.selectList(authority, function (p) { return contains(uips, p); });
1876
1941
  if (contains(authority, OID.anyPolicy)) {
1877
- uips.forEach(function (p) { if (out.indexOf(p) === -1) guard.list.append(out, p); });
1942
+ intrinsic.forEach(uips, function (p) { if (intrinsic.indexOf(out, p) === -1) guard.list.append(out, p); });
1878
1943
  }
1879
1944
  return out;
1880
1945
  }
@@ -2077,7 +2142,10 @@ function crlChecker(crls, opts) {
2077
2142
  if (crls !== undefined && crls !== null && !intrinsic.isArray(crls)) {
2078
2143
  throw E("path/bad-input", "crlChecker: crls must be an array of CRLs");
2079
2144
  }
2080
- var parsed = (crls || []).map(function (c, ci) {
2145
+ /** @internal Mapped with the protocol-free verb: `map` builds its result through a constructor read
2146
+ * off the receiver and a species read off that, and a caller's array is where that constructor comes
2147
+ * from. A result that reads as empty is a checker with no CRLs, which answers "nothing revoked". */
2148
+ var parsed = intrinsic.mapList(crls || [], function (c, ci) {
2081
2149
  return guard.parsed.acceptDerived(c, "crl", crl.parse, E, "path/bad-input",
2082
2150
  "crlChecker: crls[" + ci + "]");
2083
2151
  });
@@ -2277,15 +2345,22 @@ function _verifyWithSpki(sigAlg, rawSig, spkiBytes, tbsBytes) {
2277
2345
  d = resolveDescriptor(sigAlg);
2278
2346
  assertKeyMatchesSigAlg(spkiBytes, sigAlg.oid, d);
2279
2347
  } catch (_e) { return _resolve(_Promise, false); }
2280
- if (d.composite) return compositeSig.compositeVerify(spkiBytes, rawSig, tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature").then(function (r) { return r.ok === true; });
2348
+ if (d.composite) {
2349
+ return intrinsic.chain(compositeSig.compositeVerify(spkiBytes, rawSig, tbsBytes, d.composite,
2350
+ PathError, "path/unsupported-algorithm", "path/bad-signature"), function (r) { return r.ok === true; });
2351
+ }
2281
2352
  /** @internal RFC 9802 sec. 6 names the CertificateList as well as the Certificate, so a revocation list
2282
2353
  * signed with a stateful hash-based key reaches the same engine the certificate path uses. */
2283
2354
  if (d.shbs) return _resolve(_Promise, _shbsVerify(spkiBytes, rawSig, tbsBytes).ok === true);
2284
- return _importVerifyKey(spkiBytes, d).then(function (key) {
2355
+ /** @internal The same two continuations, for the engine every other verifier in this toolkit is
2356
+ * wired to: this is what tells a CRL, a CSR, a CRMF proof and an OCSP response's signature from
2357
+ * one that does not verify, and a `true` resolved past the callback is that verdict. */
2358
+ var checked = intrinsic.chain(_importVerifyKey(spkiBytes, d), function (key) {
2285
2359
  var sig = rawSig;
2286
2360
  if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
2287
2361
  return _subtleVerify(subtle, d.verify, key, sig, tbsBytes);
2288
- }).then(function (ok) { return ok === true; }, function () { return false; });
2362
+ });
2363
+ return intrinsic.chain(checked, function (ok) { return ok === true; }, function () { return false; });
2289
2364
  }
2290
2365
 
2291
2366
  crlVerify.setEngine(_verifyWithSpki);
@@ -2364,7 +2439,9 @@ function ocspChecker(responses) {
2364
2439
  if (responses !== undefined && responses !== null && !intrinsic.isArray(responses)) {
2365
2440
  throw E("path/bad-input", "ocspChecker: responses must be an array of OCSP responses");
2366
2441
  }
2367
- var parsed = (responses || []).map(function (r) { return _ocspFromBytes(r); });
2442
+ /** @internal Mapped with the protocol-free verb, for the reason at `crlChecker`: a result reading as
2443
+ * empty is a checker holding no responses, and every certificate it is asked about is "unknown". */
2444
+ var parsed = intrinsic.mapList(responses || [], function (r) { return _ocspFromBytes(r); });
2368
2445
  return {
2369
2446
  check: async function (cert, issuer, ctx) {
2370
2447
  var time = ctx.time;
@@ -2501,7 +2578,10 @@ function _ocspFromBytes(response) {
2501
2578
 
2502
2579
  function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2503
2580
  opts = opts || {};
2504
- if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
2581
+ /** @internal The captured finite test rather than a live `isNaN`: a valid Date's instant is always
2582
+ * finite and an invalid one's is not, so the two answer the same question, and the instant is read
2583
+ * ONCE into the value the test decides on. */
2584
+ if (!guard.time.isDate(time) || !intrinsic.numberIsFinite(guard.time.instantOf(time))) {
2505
2585
  return _reject(_Promise, E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
2506
2586
  }
2507
2587
  /** @internal Snapshotted in the synchronous prologue: the bytes compared are the bytes the caller
@@ -2525,7 +2605,11 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2525
2605
  if (!boundName) {
2526
2606
  return _resolve(_Promise, unbound("the supplied issuer certificate's subject does not match the target certificate's issuer"));
2527
2607
  }
2528
- return builtinVerify({ workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes }, cert).then(function (sig) {
2608
+ /** @internal Both continuations are taken from the capture, for the reason the verifier's own are:
2609
+ * the first reads the signature verdict that binds this certificate to the supplied issuer, and
2610
+ * the second reads the revocation status the response carries. A settled value in place of either
2611
+ * reports a status for a certificate whose issuer was never confirmed. */
2612
+ return intrinsic.chain(builtinVerify({ workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes }, cert), function (sig) {
2529
2613
  if (!sig.ok) return unbound("the target certificate's signature does not verify under the supplied issuer certificate's key");
2530
2614
  var issuerCtx = { workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes, workingIssuerName: issuerCert.subject, issuerCert: issuerCert };
2531
2615
  var issuerNameCandidates = [cert.issuer.bytes];
@@ -2534,7 +2618,8 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2534
2618
  var issuerKeyBits;
2535
2619
  try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
2536
2620
  catch (_e) { return unbound("the issuer public key could not be read to recompute the OCSP CertID"); }
2537
- return ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits, issuerNameCandidates, time, opts.historicalMode === true).then(function (v) {
2621
+ return intrinsic.chain(ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits,
2622
+ issuerNameCandidates, time, opts.historicalMode === true), function (v) {
2538
2623
  if (v.revoked) return _withNonceRule(guard.verdict.of({ status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason }), parsedResponse, wantNonce, nonce);
2539
2624
  if (v.sawGood) return _withNonceRule(guard.verdict.of({ status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" }), parsedResponse, wantNonce, nonce);
2540
2625
  return _withNonceRule(guard.verdict.of({ status: "unknown", responderAuthorized: v.responderAuthorized === true, signatureValid: v.signatureValid === true, matched: v.matched === true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: v.reason }), parsedResponse, wantNonce, nonce);
@@ -2606,7 +2691,7 @@ function resolvePurposeOpts(opts) {
2606
2691
  if (!intrinsic.isArray(opts.requiredEku) || opts.requiredEku.length === 0) {
2607
2692
  throw E("path/bad-input", "validate: opts.requiredEku must be a non-empty array of key-purpose OID names or dotted OID strings");
2608
2693
  }
2609
- requiredEku = opts.requiredEku.map(function (p) {
2694
+ requiredEku = intrinsic.mapList(opts.requiredEku, function (p) {
2610
2695
  if (typeof p !== "string" || p.length === 0) throw E("path/bad-input", "validate: opts.requiredEku entries must be non-empty strings");
2611
2696
  if (_startsWithDigit(p)) return guard.identifier.assertCanonicalOid(p, E, "path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p));
2612
2697
  var dotted = oid.byName(p);
@@ -3197,7 +3282,7 @@ function _assertUserInitialPolicySet(uips, prefix) {
3197
3282
  * A candidate whose path length constraint cannot cover the certificates below it sorts under every candidate
3198
3283
  * that can, whatever the heuristics score it, and the score orders within each of the two groups. */
3199
3284
  function _pushCandidates(frame, scored, stack, counter, down) {
3200
- scored.sort(function (a, b) {
3285
+ intrinsic.sort(scored, function (a, b) {
3201
3286
  if (!a.ineligible !== !b.ineligible) return a.ineligible ? -1 : 1;
3202
3287
  return a.score - b.score;
3203
3288
  });
@@ -3210,21 +3295,23 @@ function _pushCandidates(frame, scored, stack, counter, down) {
3210
3295
  var childKeys = new Set(frame.keys);
3211
3296
  intrinsic.setAdd(childKeys, candKey);
3212
3297
  guard.list.append(stack, down
3213
- ? { chain: intrinsic.concat(frame.chain, [cand]), hop: frame.hop + 1, keys: childKeys, anchor: frame.anchor }
3214
- : { chain: intrinsic.concat([cand], frame.chain), hop: frame.hop + 1, keys: childKeys });
3298
+ ? { chain: intrinsic.concatList(frame.chain, [cand]), hop: frame.hop + 1, keys: childKeys, anchor: frame.anchor }
3299
+ : { chain: intrinsic.concatList([cand], frame.chain), hop: frame.hop + 1, keys: childKeys });
3215
3300
  }
3216
3301
  return n;
3217
3302
  }
3218
3303
 
3219
3304
 
3220
3305
  function _isBlockedAiaHost(host) {
3221
- if (host.charAt(0) === "[" && host.charAt(host.length - 1) === "]") host = host.slice(1, -1);
3222
- if (net.isIP(host) === 0) return false;
3306
+ if (_charCodeAt(host, 0) === 91 && _charCodeAt(host, host.length - 1) === 93) host = _strSlice(host, 1, -1);
3307
+ if (_netIsIP(host) === 0) return false;
3223
3308
  return httpTransport.isBlockedIp(host);
3224
3309
  }
3225
3310
 
3226
3311
  function _aiaParseBody(body, contentType, maxCerts) {
3227
- var certsFirst = String(contentType || "").toLowerCase().indexOf("pkcs7") >= 0;
3312
+ /** @internal The conversion and the fold are captured: together they decide HOW a fetched AIA
3313
+ * response body is parsed, and therefore which certificates are taken out of it. */
3314
+ var certsFirst = _strIndexOf(_strToLowerCase(intrinsic.String(contentType || "")), "pkcs7") >= 0;
3228
3315
  var order = certsFirst ? ["certs", "cert"] : ["cert", "certs"];
3229
3316
  for (var i = 0; i < order.length; i++) {
3230
3317
  try {
@@ -3267,7 +3354,7 @@ async function _fetchAiaIssuers(current, aia) {
3267
3354
  * and the literal-versus-name question have to be answered about the same host. */
3268
3355
  var uHost = _urlHostname(u);
3269
3356
  if (_isBlockedAiaHost(uHost)) continue;
3270
- if (net.isIP(_stripBrackets(uHost)) === 0 && !aia.transportGuardsAddresses) continue;
3357
+ if (_netIsIP(_stripBrackets(uHost)) === 0 && !aia.transportGuardsAddresses) continue;
3271
3358
  _urlSetHash(u, "");
3272
3359
  var uHref = _urlHref(u);
3273
3360
  if (intrinsic.setHas(aia.fetchedUrls, uHref) || intrinsic.setHas(seenThisCert, uHref)) continue;
@@ -3397,7 +3484,7 @@ var _BUILD_OPTS = (function () {
3397
3484
  maxAiaPerCert: 1, maxCandidatesConsidered: 1, maxDepth: 1, maxPathCerts: 1,
3398
3485
  maxResponseBytes: 1, time: 1, tls: 1, transport: 1, trustAnchors: 1, validate: 1
3399
3486
  });
3400
- Object.keys(_VALIDATE_OPTS).forEach(function (k) { o[k] = 1; });
3487
+ intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) { o[k] = 1; });
3401
3488
  return o;
3402
3489
  })();
3403
3490
 
@@ -3453,7 +3540,7 @@ async function build(leaf, opts) {
3453
3540
  var poolCeiling = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES;
3454
3541
  if (poolInput.length > poolCeiling) throw E("path/bad-input", "build: the candidate pool has " + poolInput.length + " certificates, exceeding the " + poolCeiling + " ceiling");
3455
3542
  var pool;
3456
- try { pool = intrinsic.map(poolInput, coerceCert); }
3543
+ try { pool = intrinsic.mapList(poolInput, coerceCert); }
3457
3544
  catch (e) { throw E("path/bad-input", "build: a candidate certificate did not parse", e); }
3458
3545
  var poolDerKeys = new Set();
3459
3546
  for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
@@ -3539,7 +3626,7 @@ async function build(leaf, opts) {
3539
3626
  if (buildExclRaw != null) forwarded.initialExcludedSubtrees = buildExclSeeds;
3540
3627
  function validateOpts(anchor) {
3541
3628
  var vo = Object.create(null);
3542
- Object.keys(forwarded).forEach(function (f) { vo[f] = forwarded[f]; });
3629
+ intrinsic.forEach(intrinsic.keys(forwarded), function (f) { vo[f] = forwarded[f]; });
3543
3630
  vo.trustAnchors = anchor;
3544
3631
  return vo;
3545
3632
  }
@@ -3578,7 +3665,7 @@ async function build(leaf, opts) {
3578
3665
  var frontierCert = rframe.chain.length ? rframe.chain[rframe.chain.length - 1] : null;
3579
3666
  var frontierName = frontierCert ? frontierCert.subject.rdns : rframe.anchor.name.rdns;
3580
3667
  if (nameMatchSoft(leafCert.issuer.rdns, frontierName)) {
3581
- var rpath = intrinsic.concat(rframe.chain, [leafCert]);
3668
+ var rpath = intrinsic.concatList(rframe.chain, [leafCert]);
3582
3669
  if (!doValidate) { success = { path: rpath, trustAnchor: rframe.anchor }; break; }
3583
3670
  anyChainAssembled = true;
3584
3671
  var rres = await validate(rpath, validateOpts(rframe.anchor));
@@ -3650,15 +3737,17 @@ async function build(leaf, opts) {
3650
3737
 
3651
3738
  for (var ai = 0; ai < anchors.length; ai++) {
3652
3739
  if (!nameMatchSoft(current.issuer.rdns, anchors[ai].name.rdns)) continue;
3653
- if (!doValidate) { success = { path: frame.chain.slice(), trustAnchor: anchors[ai] }; break; }
3740
+ /** @internal The REPORTED path is copied the same way the validated one is read, so the chain
3741
+ * an operator sees in the result cannot differ from the chain that was checked. */
3742
+ if (!doValidate) { success = { path: intrinsic.copyList(frame.chain), trustAnchor: anchors[ai] }; break; }
3654
3743
  anyChainAssembled = true;
3655
3744
  var res = await validate(frame.chain, validateOpts(anchors[ai]));
3656
3745
  if (res.valid) {
3657
- var acc = { valid: true, path: frame.chain.slice(), trustAnchor: anchors[ai], result: res };
3746
+ var acc = { valid: true, path: intrinsic.copyList(frame.chain), trustAnchor: anchors[ai], result: res };
3658
3747
  if (!selectByPolicy) { success = acc; break; }
3659
3748
  if (_policyScore(res, uipsDistinct) >= uipsDistinct.length) { success = acc; break; }
3660
3749
  guard.list.append(accepted, acc);
3661
- } else if (!bestFail) bestFail = { path: frame.chain.slice(), trustAnchor: anchors[ai], result: res };
3750
+ } else if (!bestFail) bestFail = { path: intrinsic.copyList(frame.chain), trustAnchor: anchors[ai], result: res };
3662
3751
  }
3663
3752
  if (success) continue;
3664
3753
 
@@ -3729,7 +3818,11 @@ function _anyExpandable(scored, frame) {
3729
3818
  * the documented reason an inline require is permitted. */
3730
3819
  function fetchingChecker(opts) { return require("./revocation-fetch").fetchingChecker(opts); }
3731
3820
 
3732
- module.exports = {
3821
+ /** @internal Frozen, because a consumer reads a verb off this object at the call and the verb IS the
3822
+ * check at that point. The metadata anchoring fold reaches `validate` this way, so a replacement
3823
+ * resolving `{ valid: true }` makes an x5c leaf unrelated to every supplied root pass, and the
3824
+ * catalogue that leaf signed is then branded verified. */
3825
+ module.exports = intrinsic.freeze({
3733
3826
  validate: validate,
3734
3827
  build: build,
3735
3828
  anchorFromCert: anchorFromCert,
@@ -3739,4 +3832,4 @@ module.exports = {
3739
3832
  verifyOcspResponse: verifyOcspResponse,
3740
3833
  PROCESSED_EXTENSIONS: PROCESSED_EXTENSIONS,
3741
3834
  TARGET_UNPROCESSED_IF_CRITICAL: TARGET_UNPROCESSED_IF_CRITICAL,
3742
- };
3835
+ });
package/lib/pbes2.js CHANGED
@@ -165,16 +165,23 @@ function pbmac1AlgId(desc) {
165
165
 
166
166
  function pbmac1(pwBytes, salt, iterationCount, keyLength, prfHash, macHash, message) {
167
167
  var subtle = webcrypto.webcrypto.subtle;
168
- return subtle.importKey("raw", pwBytes, { name: "PBKDF2" }, false, ["deriveBits"]).then(function (baseKey) {
169
- return subtle.deriveBits({ name: "PBKDF2", salt: salt, iterations: iterationCount, hash: prfHash }, baseKey, keyLength * 8);
170
- }).then(function (bits) {
168
+ /** @internal Every step is continued through the capture, and the wipe is attached on BOTH outcomes
169
+ * rather than through `finally`, which calls the receiver's own `then`. These steps derive the key
170
+ * a PBMAC1 integrity tag is computed under, so a replacement settling one hands back a tag computed
171
+ * under bytes nobody derived, and skips the wipe that keeps the derived key from outliving it. */
172
+ var base = guard.async.chain(subtle.importKey("raw", pwBytes, { name: "PBKDF2" }, false, ["deriveBits"]),
173
+ function (baseKey) {
174
+ return subtle.deriveBits({ name: "PBKDF2", salt: salt, iterations: iterationCount, hash: prfHash }, baseKey, keyLength * 8);
175
+ });
176
+ var tagged = guard.async.chain(base, function (bits) {
171
177
  var mk = Buffer.from(bits);
172
- return subtle.importKey("raw", mk, { name: "HMAC", hash: macHash }, false, ["sign"]).then(function (hmacKey) {
173
- return subtle.sign({ name: "HMAC" }, hmacKey, message);
174
- }).finally(function () {
178
+ var signed = guard.async.chain(subtle.importKey("raw", mk, { name: "HMAC", hash: macHash }, false, ["sign"]),
179
+ function (hmacKey) { return subtle.sign({ name: "HMAC" }, hmacKey, message); });
180
+ return guard.async.cleanup(signed, function () {
175
181
  guard.secret.zeroize(mk, webcrypto.WebCryptoError, "webcrypto/operation", "the PBMAC1 key");
176
182
  });
177
- }).then(function (sig) { return Buffer.from(sig); });
183
+ });
184
+ return guard.async.chain(tagged, function (sig) { return Buffer.from(sig); });
178
185
  }
179
186
 
180
187
  module.exports = {