@blamejs/pki 0.8.55 → 0.8.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +1 -1
- package/lib/acme.js +2 -2
- package/lib/asn1-der.js +17 -5
- package/lib/attrcert-verify.js +1 -1
- package/lib/cmc-verify.js +1 -1
- package/lib/cmp-session.js +2 -2
- package/lib/cmp-verify.js +2 -2
- package/lib/cms-decrypt.js +4 -1
- package/lib/cms-encrypt.js +4 -1
- package/lib/cms-sign.js +26 -12
- package/lib/cms-verify.js +1 -1
- package/lib/composite-kem.js +30 -12
- package/lib/composite-sig.js +42 -23
- package/lib/constants.js +7 -2
- package/lib/crl-verify.js +1 -1
- package/lib/crmf-sign.js +72 -43
- package/lib/crmf-verify.js +1 -1
- package/lib/csr-verify.js +1 -1
- package/lib/ct.js +2 -2
- package/lib/edwards-point.js +1 -1
- package/lib/est.js +3 -3
- package/lib/guard-async.js +22 -1
- package/lib/guard-bytes.js +6 -3
- package/lib/guard-identifier.js +1 -1
- package/lib/guard-intrinsic.js +474 -0
- package/lib/guard-parsed.js +23 -7
- package/lib/http-digest.js +146 -62
- package/lib/http-transport.js +82 -17
- package/lib/identity-match.js +2 -2
- package/lib/inspect.js +1 -1
- package/lib/lint.js +51 -3
- package/lib/merkle.js +2 -2
- package/lib/ocsp-request-verify.js +1 -1
- package/lib/ocsp.js +53 -22
- package/lib/oid.js +7 -0
- package/lib/path-validate.js +169 -76
- package/lib/pbes2.js +14 -7
- package/lib/schema-attrcert.js +2 -2
- package/lib/schema-cmc.js +2 -2
- package/lib/schema-cmp.js +2 -2
- package/lib/schema-cms.js +115 -10
- package/lib/schema-crmf.js +2 -2
- package/lib/schema-csr.js +2 -2
- package/lib/schema-ocsp.js +41 -6
- package/lib/schema-pkix.js +3 -3
- package/lib/schema-x509.js +2 -2
- package/lib/sigstore.js +1 -1
- package/lib/tlog.js +7 -4
- package/lib/trust.js +187 -104
- package/lib/tsp-sign.js +1 -1
- package/lib/webauthn-mds.js +257 -121
- package/lib/webauthn.js +327 -164
- package/lib/webcrypto.js +10 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/path-validate.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
|
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]
|
|
577
|
+
var host = stripTrailingDot(_strToLowerCase(mb[1]));
|
|
559
578
|
if (host === "") return "unsupported";
|
|
560
|
-
var c = stripTrailingDot(constraint
|
|
561
|
-
if (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
|
|
597
|
+
var c = stripTrailingDot(_strToLowerCase(constraint)), h = stripTrailingDot(_strToLowerCase(host));
|
|
579
598
|
if (c === "") return true;
|
|
580
|
-
if (c
|
|
581
|
-
if (h === c || (h.length > c.length && h
|
|
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
|
|
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
|
|
607
|
-
if (!isFqdnHost(c
|
|
608
|
-
if (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
|
|
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
|
|
671
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
772
|
-
|
|
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
|
|
776
|
-
var idx = c.parents
|
|
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
|
-
|
|
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
|
|
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.
|
|
934
|
-
if (callerAny) return intrinsic.
|
|
935
|
-
return intrinsic.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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
|
-
|
|
1083
|
+
intrinsic.forEach(expected, function (ep) {
|
|
1037
1084
|
if (nodeAt(state, i, ep) !== null) return;
|
|
1038
|
-
var parents = intrinsic.
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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)
|
|
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.
|
|
1940
|
+
var out = intrinsic.selectList(authority, function (p) { return contains(uips, p); });
|
|
1876
1941
|
if (contains(authority, OID.anyPolicy)) {
|
|
1877
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
3214
|
-
: { chain: intrinsic.
|
|
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
|
|
3222
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
173
|
-
return subtle.sign({ name: "HMAC" }, hmacKey, message);
|
|
174
|
-
|
|
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
|
-
})
|
|
183
|
+
});
|
|
184
|
+
return guard.async.chain(tagged, function (sig) { return Buffer.from(sig); });
|
|
178
185
|
}
|
|
179
186
|
|
|
180
187
|
module.exports = {
|