@blamejs/pki 0.8.11 → 0.8.14
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 +47 -0
- package/README.md +3 -2
- package/index.js +6 -0
- package/lib/acme.js +13 -5
- package/lib/attrcert-sign.js +3 -3
- package/lib/cmc-build.js +9 -9
- package/lib/cmc-verify.js +5 -5
- package/lib/cmp-build.js +3 -4
- package/lib/cmp-session.js +6 -6
- package/lib/cms-verify.js +4 -4
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +2 -2
- package/lib/est.js +12 -13
- package/lib/framework-error.js +3 -0
- package/lib/guard-header.js +15 -1
- package/lib/guard-identifier.js +34 -0
- package/lib/hpke.js +7 -2
- package/lib/http-transport.js +5 -6
- package/lib/identity-match.js +4 -3
- package/lib/ocsp.js +5 -3
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +380 -111
- package/lib/pki-build.js +1 -1
- package/lib/revocation-fetch.js +1 -1
- package/lib/scep.js +21 -9
- package/lib/schema-all.js +10 -1
- package/lib/schema-engine.js +7 -2
- package/lib/schema-pkix.js +40 -0
- package/lib/schema-trustanchor.js +469 -0
- package/lib/schema-x509.js +66 -33
- package/lib/smime.js +5 -1
- package/lib/trust.js +192 -11
- package/lib/trustanchor-build.js +278 -0
- package/lib/validator-tpm.js +1 -1
- package/lib/webauthn-mds.js +1 -1
- package/lib/webauthn.js +9 -10
- package/lib/webcrypto.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/path-validate.js
CHANGED
|
@@ -622,39 +622,118 @@ function absorbNameConstraints(state, decoded) {
|
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
|
|
625
|
+
/**
|
|
626
|
+
* @internal The RFC 9618 valid_policy_graph. RFC 5280 sec. 6.1.3 step (d)(1)(i) creates one child per
|
|
627
|
+
* matching parent, so a chain where several issuer policies map to one subject policy multiplies the
|
|
628
|
+
* structure at every level: measured on a conforming chain carrying four policies mapped to each
|
|
629
|
+
* other, the tree wants 5461 nodes at six certificates against this graph's 25, and the node cap
|
|
630
|
+
* refuses the path. RFC 9618 sec. 5.2 keeps at most ONE node per (depth, policy identifier) and
|
|
631
|
+
* gives it several parents instead, which holds the same information in linear size.
|
|
632
|
+
*
|
|
633
|
+
* `state.policyIndex[depth]` maps a policy identifier to the one node at that depth, so the "is there
|
|
634
|
+
* already a node here for this policy" question every step asks is a lookup rather than a walk.
|
|
635
|
+
*/
|
|
636
|
+
function graphRoot() {
|
|
637
|
+
return { depth: 0, validPolicy: OID.anyPolicy, qualifierSet: [], expectedPolicySet: [OID.anyPolicy], children: [], parents: [] };
|
|
638
|
+
}
|
|
639
|
+
function policyLayer(state, depth) {
|
|
640
|
+
var layer = state.policyIndex[depth];
|
|
641
|
+
if (layer === undefined) { layer = intrinsic.create(null); state.policyIndex[depth] = layer; }
|
|
642
|
+
return layer;
|
|
643
|
+
}
|
|
644
|
+
function nodeAt(state, depth, policyOid) {
|
|
645
|
+
var layer = state.policyIndex[depth];
|
|
646
|
+
if (layer === undefined || !intrinsic.hasOwn(layer, policyOid)) return null;
|
|
647
|
+
return layer[policyOid];
|
|
648
|
+
}
|
|
649
|
+
function nodesAt(state, depth) {
|
|
650
|
+
var layer = state.policyIndex[depth];
|
|
651
|
+
if (layer === undefined) return [];
|
|
639
652
|
var out = [];
|
|
640
|
-
|
|
641
|
-
(function walk(node) {
|
|
642
|
-
if (node.depth === depth) { guard.list.append(out, node); return; }
|
|
643
|
-
node.children.forEach(walk);
|
|
644
|
-
})(tree);
|
|
653
|
+
intrinsic.forEach(intrinsic.keys(layer), function (k) { guard.list.append(out, layer[k]); });
|
|
645
654
|
return out;
|
|
646
655
|
}
|
|
647
|
-
|
|
656
|
+
/** @internal One node per (depth, policy). Meeting the same pair twice joins the parents onto the
|
|
657
|
+
* node that is already there rather than making a second one, which is the invariant the wrap-up
|
|
658
|
+
* procedure's single-parent test depends on. */
|
|
659
|
+
function addPolicyNode(state, depth, validPolicy, qualifiers, expectedPolicySet, parents) {
|
|
660
|
+
if (parents.length === 0) return null;
|
|
661
|
+
var node = nodeAt(state, depth, validPolicy);
|
|
662
|
+
if (node === null) {
|
|
663
|
+
if (state.policyNodeCount >= state.maxPolicyNodes) { state._capHit = true; return null; }
|
|
664
|
+
node = { depth: depth, validPolicy: validPolicy, qualifierSet: qualifiers ? [qualifiers] : [],
|
|
665
|
+
expectedPolicySet: expectedPolicySet, children: [], parents: [] };
|
|
666
|
+
policyLayer(state, depth)[validPolicy] = node;
|
|
667
|
+
state.policyNodeCount++;
|
|
668
|
+
}
|
|
669
|
+
for (var i = 0; i < parents.length; i++) {
|
|
670
|
+
if (node.parents.indexOf(parents[i]) !== -1) continue;
|
|
671
|
+
guard.list.append(node.parents, parents[i]);
|
|
672
|
+
guard.list.append(parents[i].children, node);
|
|
673
|
+
}
|
|
674
|
+
return node;
|
|
675
|
+
}
|
|
676
|
+
function removePolicyNode(state, node) {
|
|
677
|
+
node.parents.forEach(function (p) {
|
|
678
|
+
var idx = p.children.indexOf(node);
|
|
679
|
+
if (idx !== -1) p.children.splice(idx, 1);
|
|
680
|
+
});
|
|
681
|
+
node.children.forEach(function (c) {
|
|
682
|
+
var idx = c.parents.indexOf(node);
|
|
683
|
+
if (idx !== -1) c.parents.splice(idx, 1);
|
|
684
|
+
});
|
|
685
|
+
node.parents = [];
|
|
686
|
+
var layer = state.policyIndex[node.depth];
|
|
687
|
+
if (layer !== undefined && layer[node.validPolicy] === node) delete layer[node.validPolicy];
|
|
688
|
+
state.policyNodeCount--;
|
|
689
|
+
}
|
|
690
|
+
/** @internal RFC 9618 sec. 5.3 step (d)(3): delete every node of depth `depth` or less that has no
|
|
691
|
+
* children, and repeat, since deleting one can leave its own parents childless. Depth zero is the
|
|
692
|
+
* root, which is never deleted: an empty graph is a root with no children, which the caller reads
|
|
693
|
+
* as NULL. */
|
|
694
|
+
function prunePolicyChildless(state, depth) {
|
|
648
695
|
for (var d = depth; d > 0; d--) {
|
|
649
|
-
|
|
650
|
-
if (node.children.length === 0
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
696
|
+
nodesAt(state, d).forEach(function (node) {
|
|
697
|
+
if (node.children.length === 0) removePolicyNode(state, node);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function graphIsEmpty(state) { return state.validPolicyGraph.children.length === 0; }
|
|
702
|
+
/**
|
|
703
|
+
* @internal The graph a caller reads, as a flat list of nodes linked by index. A graph is not a
|
|
704
|
+
* tree, and handing back a root to walk would invite the caller to recurse into every child and
|
|
705
|
+
* rebuild the exponential structure this replaced: `JSON.stringify` on such a value re-expands a
|
|
706
|
+
* shared node once per path that reaches it. A node list is linear however it is read, holds the
|
|
707
|
+
* parent relation the wrap-up procedure needs, and carries no cycle to serialize. The root is
|
|
708
|
+
* index 0.
|
|
709
|
+
*/
|
|
710
|
+
function graphSnapshot(root) {
|
|
711
|
+
if (!root) return null;
|
|
712
|
+
var originals = [];
|
|
713
|
+
var indexOfNode = new intrinsic.Map();
|
|
714
|
+
(function walk(node) {
|
|
715
|
+
if (intrinsic.mapHas(indexOfNode, node)) return;
|
|
716
|
+
intrinsic.mapSet(indexOfNode, node, originals.length);
|
|
717
|
+
guard.list.append(originals, node);
|
|
718
|
+
node.children.forEach(walk);
|
|
719
|
+
})(root);
|
|
720
|
+
function idsOf(list) {
|
|
721
|
+
var out = [];
|
|
722
|
+
for (var j = 0; j < list.length; j++) {
|
|
723
|
+
if (intrinsic.mapHas(indexOfNode, list[j])) guard.list.append(out, intrinsic.mapGet(indexOfNode, list[j]));
|
|
724
|
+
}
|
|
725
|
+
return out;
|
|
726
|
+
}
|
|
727
|
+
var nodes = [];
|
|
728
|
+
for (var i = 0; i < originals.length; i++) {
|
|
729
|
+
var o = originals[i];
|
|
730
|
+
guard.list.append(nodes, {
|
|
731
|
+
id: i, depth: o.depth, validPolicy: o.validPolicy, qualifierSet: o.qualifierSet,
|
|
732
|
+
expectedPolicySet: o.expectedPolicySet, parents: idsOf(o.parents), children: idsOf(o.children),
|
|
654
733
|
});
|
|
655
734
|
}
|
|
735
|
+
return { nodes: nodes };
|
|
656
736
|
}
|
|
657
|
-
function treeIsEmpty(tree) { return tree.children.length === 0; }
|
|
658
737
|
|
|
659
738
|
|
|
660
739
|
function isSubtreeBaseValid(tag, base) {
|
|
@@ -725,24 +804,90 @@ function _anchorSubtreeSeeds(anchor) {
|
|
|
725
804
|
};
|
|
726
805
|
}
|
|
727
806
|
|
|
728
|
-
|
|
807
|
+
/**
|
|
808
|
+
* @internal An RFC 5914 trust anchor's `certPath` constraints, read off the anchor `pki.trust`
|
|
809
|
+
* produced. Absent on an anchor from any other source, so every reader below sees empty lists and
|
|
810
|
+
* nothing changes for a caller who does not use the format.
|
|
811
|
+
*/
|
|
812
|
+
function _anchorRfc5914Constraints(anchor) {
|
|
813
|
+
var empty = { permitted: [], excluded: [], policySet: null, policyFlags: null, pathLenConstraint: null };
|
|
814
|
+
var c = anchor === null || anchor === undefined ? undefined : anchor.constraints;
|
|
815
|
+
if (!c || typeof c !== "object") return empty;
|
|
816
|
+
var nc = c.nameConstraints;
|
|
817
|
+
return {
|
|
818
|
+
permitted: nc && intrinsic.isArray(nc.permitted) ? checkedSubtreeSeeds(nc.permitted, "trustAnchors[].constraints.nameConstraints.permitted") : [],
|
|
819
|
+
excluded: nc && intrinsic.isArray(nc.excluded) ? checkedSubtreeSeeds(nc.excluded, "trustAnchors[].constraints.nameConstraints.excluded") : [],
|
|
820
|
+
policySet: intrinsic.isArray(c.policySet) ? c.policySet : null,
|
|
821
|
+
policyFlags: c.policyFlags && typeof c.policyFlags === "object" ? c.policyFlags : null,
|
|
822
|
+
pathLenConstraint: intrinsic.isInteger(c.pathLenConstraint) ? c.pathLenConstraint : null,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* @internal RFC 5937 sec. 3.2, the policy set. anyPolicy on either side means "any policy satisfies
|
|
828
|
+
* me", so intersecting with it leaves the other side's set untouched: filtering it literally would
|
|
829
|
+
* compare a concrete identifier against the wildcard identifier, find no match, and refuse a path
|
|
830
|
+
* both sides accept. Only where BOTH name concrete policies is the intersection a comparison, and
|
|
831
|
+
* an empty result there is a real answer rather than "unconstrained": the two name disjoint policy
|
|
832
|
+
* spaces, so no policy satisfies both.
|
|
833
|
+
*/
|
|
834
|
+
function _mergedPolicySet(callerSet, anchorSet) {
|
|
835
|
+
if (anchorSet === null) return callerSet;
|
|
836
|
+
var callerAny = callerSet === undefined || callerSet === null || contains(callerSet, OID.anyPolicy);
|
|
837
|
+
var anchorAny = contains(anchorSet, OID.anyPolicy);
|
|
838
|
+
if (anchorAny && callerAny) return [OID.anyPolicy];
|
|
839
|
+
if (anchorAny) return intrinsic.map(callerSet, function (p) { return p; });
|
|
840
|
+
if (callerAny) return intrinsic.map(anchorSet, function (p) { return p; });
|
|
841
|
+
return intrinsic.filter(anchorSet, function (p) { return contains(callerSet, p); });
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* @internal RFC 5937 sec. 3.2, the flags. Each is one-directional: a flag the anchor asserts turns
|
|
846
|
+
* the matching initial value on where it was off and never off where it was on, so an anchor can
|
|
847
|
+
* only narrow what a caller asked for. That is what makes RFC 5914's "always enforced" safe.
|
|
848
|
+
*/
|
|
849
|
+
function _flagSet(callerValue, anchorFlags, name) {
|
|
850
|
+
if (callerValue) return true;
|
|
851
|
+
return anchorFlags !== null && anchorFlags[name] === true;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function initialize(certs, params, seeds, anchor, ta) {
|
|
729
855
|
var n = certs.length;
|
|
856
|
+
var state = _initialState(certs, params, seeds, anchor, n, ta);
|
|
857
|
+
/** @internal The root is the depth-zero anyPolicy node, and it is indexed like any other, so the
|
|
858
|
+
* "is there a node at this depth for this policy" lookup answers for depth zero too. */
|
|
859
|
+
policyLayer(state, 0)[OID.anyPolicy] = state.validPolicyGraph;
|
|
860
|
+
return state;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function _initialState(certs, params, seeds, anchor, n, ta) {
|
|
864
|
+
/** @internal The anchor's constraints are read HERE rather than merged into a copy of `params`,
|
|
865
|
+
* because a copy answers only for the own properties the copy loop enumerated. A caller option
|
|
866
|
+
* defined non-enumerably would be dropped by the copy and the anchor would then WIDEN the
|
|
867
|
+
* validation, which is the one thing sec. 3.2 does not allow. Reading the caller's object
|
|
868
|
+
* directly keeps every option live whatever its descriptor. */
|
|
869
|
+
var anchorPathLen = ta.pathLenConstraint;
|
|
730
870
|
return {
|
|
731
|
-
|
|
871
|
+
validPolicyGraph: graphRoot(),
|
|
872
|
+
policyIndex: [],
|
|
732
873
|
policyNodeCount: 1,
|
|
733
874
|
maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
|
|
734
875
|
minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
|
|
735
876
|
permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
|
|
736
877
|
excludedSubtrees: seeds.excluded,
|
|
737
|
-
explicitPolicy: params.initialExplicitPolicy ? 0 : n + 1,
|
|
738
|
-
inhibitAnyPolicy: params.initialAnyPolicyInhibit ? 0 : n + 1,
|
|
739
|
-
policyMapping: params.initialPolicyMappingInhibit ? 0 : n + 1,
|
|
878
|
+
explicitPolicy: _flagSet(params.initialExplicitPolicy, ta.policyFlags, "requireExplicitPolicy") ? 0 : n + 1,
|
|
879
|
+
inhibitAnyPolicy: _flagSet(params.initialAnyPolicyInhibit, ta.policyFlags, "inhibitAnyPolicy") ? 0 : n + 1,
|
|
880
|
+
policyMapping: _flagSet(params.initialPolicyMappingInhibit, ta.policyFlags, "inhibitPolicyMapping") ? 0 : n + 1,
|
|
740
881
|
workingPublicKeyAlgorithm: anchor.algorithm,
|
|
741
882
|
workingPublicKey: anchor.publicKey,
|
|
742
883
|
workingPublicKeyParameters: anchor.parameters || null,
|
|
743
884
|
workingIssuerName: anchor.name,
|
|
744
|
-
|
|
745
|
-
|
|
885
|
+
/** @internal RFC 5937 sec. 3.2: "If a basic constraints extension is associated with the trust
|
|
886
|
+
* anchor and contains a pathLenConstraint value, set the max_path_length state variable equal
|
|
887
|
+
* to the pathLenConstraint value". Taken as the lower of the two, since sec. 6.1.2's own
|
|
888
|
+
* initial value is the path length and an anchor may only narrow. */
|
|
889
|
+
maxPathLength: intrinsic.isInteger(anchorPathLen) && anchorPathLen < n ? anchorPathLen : n,
|
|
890
|
+
userInitialPolicySet: _mergedPolicySet(params.userInitialPolicySet, ta.policySet) || [OID.anyPolicy],
|
|
746
891
|
results: [],
|
|
747
892
|
};
|
|
748
893
|
}
|
|
@@ -757,55 +902,61 @@ function processPolicies(state, cert, i, checks) {
|
|
|
757
902
|
try { cp = decodeExt(cert, OID.certificatePolicies); }
|
|
758
903
|
catch (e) { guard.list.append(checks,{ name: "policies", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
759
904
|
|
|
760
|
-
if (cp && state.
|
|
905
|
+
if (cp && state.validPolicyGraph) {
|
|
761
906
|
var policies = cp.value;
|
|
762
|
-
var
|
|
907
|
+
var prev = i - 1;
|
|
763
908
|
var anyPolicyActive = state.inhibitAnyPolicy > 0 || (i < state._n && selfIssued(cert));
|
|
764
909
|
var anyPolicyPresent = false;
|
|
765
910
|
var anyPolicyQualifiers = null;
|
|
911
|
+
|
|
912
|
+
/** @internal RFC 9618 sec. 5.3 step (d)(1): ONE node per policy, carrying every depth i-1 node
|
|
913
|
+
* that expected it. Creating one per parent instead is what made the structure exponential. */
|
|
766
914
|
policies.forEach(function (p) {
|
|
767
915
|
if (p.policyIdentifier === OID.anyPolicy) { anyPolicyPresent = true; anyPolicyQualifiers = p.qualifiersBytes; return; }
|
|
768
|
-
var
|
|
769
|
-
|
|
770
|
-
if (contains(node.expectedPolicySet, p.policyIdentifier)) {
|
|
771
|
-
addChild(state, node, p.policyIdentifier, p.qualifiersBytes, [p.policyIdentifier], checks);
|
|
772
|
-
matched = true;
|
|
773
|
-
}
|
|
916
|
+
var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
|
|
917
|
+
return contains(node.expectedPolicySet, p.policyIdentifier);
|
|
774
918
|
});
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
919
|
+
if (parents.length === 0) {
|
|
920
|
+
/** @internal (d)(1)(ii): no node expected it, so it hangs off the anyPolicy node if there
|
|
921
|
+
* is one. That single anyPolicy parent is what step (g)(2) later reads. */
|
|
922
|
+
var anyPrev = nodeAt(state, prev, OID.anyPolicy);
|
|
923
|
+
if (anyPrev === null) return;
|
|
924
|
+
parents = [anyPrev];
|
|
779
925
|
}
|
|
926
|
+
addPolicyNode(state, i, p.policyIdentifier, p.qualifiersBytes, [p.policyIdentifier], parents);
|
|
780
927
|
});
|
|
928
|
+
|
|
929
|
+
/** @internal (d)(2): for each policy some depth i-1 node expects, if depth i has no node for it
|
|
930
|
+
* yet, one node carrying every parent that expected it. */
|
|
781
931
|
if (anyPolicyPresent && anyPolicyActive) {
|
|
782
|
-
|
|
932
|
+
var expected = [];
|
|
933
|
+
nodesAt(state, prev).forEach(function (node) {
|
|
783
934
|
node.expectedPolicySet.forEach(function (ep) {
|
|
784
|
-
|
|
785
|
-
if (!already) addChild(state, node, ep, anyPolicyQualifiers, [ep], checks);
|
|
935
|
+
if (expected.indexOf(ep) === -1) guard.list.append(expected, ep);
|
|
786
936
|
});
|
|
787
937
|
});
|
|
938
|
+
expected.forEach(function (ep) {
|
|
939
|
+
if (nodeAt(state, i, ep) !== null) return;
|
|
940
|
+
var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
|
|
941
|
+
return contains(node.expectedPolicySet, ep);
|
|
942
|
+
});
|
|
943
|
+
addPolicyNode(state, i, ep, anyPolicyQualifiers, [ep], parents);
|
|
944
|
+
});
|
|
788
945
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
946
|
+
|
|
947
|
+
if (state._capHit) return { fatal: true, error: E("path/policy-tree-cap", "policy graph exceeded the node cap") };
|
|
948
|
+
prunePolicyChildless(state, prev);
|
|
949
|
+
if (graphIsEmpty(state)) state.validPolicyGraph = null;
|
|
792
950
|
} else if (!cp) {
|
|
793
|
-
state.
|
|
951
|
+
state.validPolicyGraph = null;
|
|
794
952
|
}
|
|
795
953
|
|
|
796
|
-
if (!(state.explicitPolicy > 0 || state.
|
|
954
|
+
if (!(state.explicitPolicy > 0 || state.validPolicyGraph !== null)) {
|
|
797
955
|
guard.list.append(checks,{ name: "policy", ok: false, code: "path/policy-required" });
|
|
798
|
-
return { fatal: true, error: E("path/policy-required", "explicit policy required but the valid-policy
|
|
956
|
+
return { fatal: true, error: E("path/policy-required", "explicit policy required but the valid-policy graph is empty") };
|
|
799
957
|
}
|
|
800
|
-
return { fatal: false };
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function addChild(state, parent, validPolicy, qualifiers, expectedPolicySet, checks) {
|
|
804
|
-
if (state.policyNodeCount >= state.maxPolicyNodes) { state._capHit = true; return; }
|
|
805
|
-
var node = { depth: parent.depth + 1, validPolicy: validPolicy, qualifierSet: qualifiers ? [qualifiers] : [], expectedPolicySet: expectedPolicySet, children: [], parent: parent };
|
|
806
|
-
guard.list.append(parent.children, node);
|
|
807
|
-
state.policyNodeCount++;
|
|
808
958
|
void checks;
|
|
959
|
+
return { fatal: false };
|
|
809
960
|
}
|
|
810
961
|
|
|
811
962
|
function isNullOrAbsentParams(p) {
|
|
@@ -859,7 +1010,7 @@ function prepareNext(state, cert, i, checks) {
|
|
|
859
1010
|
if (pm) {
|
|
860
1011
|
var badAny = mapsAnyPolicy(pm.value);
|
|
861
1012
|
if (badAny) { guard.list.append(checks,{ name: "policyMappings", ok: false, code: "path/bad-policy" }); return { fatal: true, error: E("path/bad-policy", "policyMappings must not map to or from anyPolicy (RFC 5280 6.1.4(a))") }; }
|
|
862
|
-
if (state.
|
|
1013
|
+
if (state.validPolicyGraph) applyPolicyMappings(state, pm.value, i);
|
|
863
1014
|
}
|
|
864
1015
|
|
|
865
1016
|
state.workingIssuerName = cert.subject;
|
|
@@ -919,36 +1070,49 @@ function prepareNext(state, cert, i, checks) {
|
|
|
919
1070
|
return { fatal: false };
|
|
920
1071
|
}
|
|
921
1072
|
|
|
1073
|
+
/** @internal RFC 9618 sec. 5.4 rewrites RFC 5280 sec. 6.1.4 step (b) over the graph. Each clause is
|
|
1074
|
+
* singular where the tree's was plural, because at most one node at a depth carries a given policy.
|
|
1075
|
+
*
|
|
1076
|
+
* A mapping naming anyPolicy on either side is skipped here as well as refused by the caller
|
|
1077
|
+
* (sec. 6.1.4(a)), because mapping one in would rewrite the anyPolicy node's expected policy set and
|
|
1078
|
+
* the graph's invariant is that it is always {anyPolicy}. The caller refusing it keeps that from
|
|
1079
|
+
* happening today; skipping it here is what keeps it true of this function. */
|
|
922
1080
|
function applyPolicyMappings(state, mappings, i) {
|
|
923
|
-
var depth = i;
|
|
924
1081
|
if (state.policyMapping > 0) {
|
|
925
1082
|
var mappedFrom = intrinsic.create(null);
|
|
926
|
-
mappings.forEach(function (m) {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
Object.keys(mappedFrom).forEach(function (idp) {
|
|
930
|
-
var idpNodes = depthI.filter(function (nd) { return nd.validPolicy === idp; });
|
|
931
|
-
if (idpNodes.length) {
|
|
932
|
-
idpNodes.forEach(function (nd) { nd.expectedPolicySet = mappedFrom[idp].slice(); });
|
|
933
|
-
} else {
|
|
934
|
-
anyNodes.forEach(function (anyNode) {
|
|
935
|
-
if (anyNode.parent) addChild(state, anyNode.parent, idp, anyNode.qualifierSet[0] || null, mappedFrom[idp].slice(), []);
|
|
936
|
-
});
|
|
937
|
-
}
|
|
1083
|
+
mappings.forEach(function (m) {
|
|
1084
|
+
if (m.issuerDomainPolicy === OID.anyPolicy || m.subjectDomainPolicy === OID.anyPolicy) return;
|
|
1085
|
+
guard.list.append(mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || [], m.subjectDomainPolicy);
|
|
938
1086
|
});
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
var idx = node.parent.children.indexOf(node);
|
|
946
|
-
if (idx !== -1) node.parent.children.splice(idx, 1);
|
|
1087
|
+
intrinsic.forEach(intrinsic.keys(mappedFrom), function (idp) {
|
|
1088
|
+
var node = nodeAt(state, i, idp);
|
|
1089
|
+
if (node !== null) {
|
|
1090
|
+
/** @internal (b)(1) */
|
|
1091
|
+
node.expectedPolicySet = mappedFrom[idp].slice();
|
|
1092
|
+
return;
|
|
947
1093
|
}
|
|
1094
|
+
/** @internal (b)(2): no node for the issuer domain policy, so one is generated under the
|
|
1095
|
+
* anyPolicy node at depth i-1, taking the depth-i anyPolicy node's qualifiers. */
|
|
1096
|
+
var anyHere = nodeAt(state, i, OID.anyPolicy);
|
|
1097
|
+
if (anyHere === null) return;
|
|
1098
|
+
var anyPrev = nodeAt(state, i - 1, OID.anyPolicy);
|
|
1099
|
+
if (anyPrev === null) return;
|
|
1100
|
+
addPolicyNode(state, i, idp, anyHere.qualifierSet[0] || null, mappedFrom[idp].slice(), [anyPrev]);
|
|
948
1101
|
});
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
/** @internal (b)(3): mapping is inhibited, so the node for each issuer domain policy is deleted
|
|
1105
|
+
* and every ancestor it leaves childless goes with it. */
|
|
1106
|
+
if (!state.validPolicyGraph) return;
|
|
1107
|
+
var deleted = false;
|
|
1108
|
+
mappings.forEach(function (m) {
|
|
1109
|
+
if (m.issuerDomainPolicy === OID.anyPolicy) return;
|
|
1110
|
+
var node = nodeAt(state, i, m.issuerDomainPolicy);
|
|
1111
|
+
if (node !== null) { removePolicyNode(state, node); deleted = true; }
|
|
1112
|
+
});
|
|
1113
|
+
if (!deleted) return;
|
|
1114
|
+
prunePolicyChildless(state, i - 1);
|
|
1115
|
+
if (graphIsEmpty(state)) state.validPolicyGraph = null;
|
|
952
1116
|
}
|
|
953
1117
|
|
|
954
1118
|
var TARGET_UNPROCESSED_IF_CRITICAL = intrinsic.create(null);
|
|
@@ -1028,11 +1192,20 @@ function _revocationRow(row, rv) {
|
|
|
1028
1192
|
* are validated at the entry point. A mis-shaped value throws `path/bad-input`,
|
|
1029
1193
|
* so it cannot silently go unapplied. Returns `{ valid, revocationChecked, anchorConstraints,
|
|
1030
1194
|
* path, results, workingPublicKey, workingPublicKeyAlgorithm,
|
|
1031
|
-
* workingPublicKeyParameters,
|
|
1195
|
+
* workingPublicKeyParameters, validPolicyGraph, userConstrainedPolicySet }` where `results[i].checks`
|
|
1032
1196
|
* carries a per-check reason code (`path/*`) for every step. Pure and
|
|
1033
1197
|
* re-entrant: no input object is mutated. An empty path or a missing anchor
|
|
1034
1198
|
* throws a typed `PathError`.
|
|
1035
1199
|
*
|
|
1200
|
+
* Certificate policies are processed as the RFC 9618 `valid_policy_graph`, which replaces the
|
|
1201
|
+
* RFC 5280 sec. 6.1 `valid_policy_tree`. `validPolicyGraph` is a directed acyclic graph holding at
|
|
1202
|
+
* most one node per depth and policy identifier; a node reached from several policies carries them
|
|
1203
|
+
* all in `parents`, where the tree held a separate node under each. The graph is linear in the
|
|
1204
|
+
* number of policies and mappings on the path, so a chain whose tree would not fit inside
|
|
1205
|
+
* `maxPolicyNodes` validates. It is `{ nodes }`, a flat list linked by index, each entry
|
|
1206
|
+
* `{ id, depth, validPolicy, qualifierSet, expectedPolicySet, parents, children }` where `parents`
|
|
1207
|
+
* and `children` hold the ids of other entries and the root is id 0.
|
|
1208
|
+
*
|
|
1036
1209
|
* A certificate signed with the RFC 9802 stateful hash-based algorithm
|
|
1037
1210
|
* `id-alg-hss-lms-hashsig` is verified like any other, through the toolkit's own
|
|
1038
1211
|
* HSS engine rather than the crypto engine. The signature covers the whole
|
|
@@ -1240,16 +1413,22 @@ async function _validate(path, opts, run) {
|
|
|
1240
1413
|
guard.limits.cap(opts.maxPolicyNodes, "validate: opts.maxPolicyNodes", undefined, { E: E, code: "path/bad-input", min: 1 });
|
|
1241
1414
|
_assertUserInitialPolicySet(opts.userInitialPolicySet, "validate");
|
|
1242
1415
|
var anchorNc = _anchorSubtreeSeeds(anchor);
|
|
1416
|
+
/** @internal RFC 5937 sec. 3.2 says how an RFC 5914 anchor's own constraints reach this
|
|
1417
|
+
* algorithm, and the rule is uniform: the stricter of the anchor's value and the caller's wins.
|
|
1418
|
+
* Its name constraints are a SEPARATE generation from the caller's and from the `nameConstraints`
|
|
1419
|
+
* overlay, because generations intersect and a replacement would let one loosen the other. */
|
|
1420
|
+
var taConstraints = _anchorRfc5914Constraints(anchor);
|
|
1243
1421
|
var seeds = {
|
|
1244
|
-
permittedGenerations: [callerPermitted, anchorNc.permitted],
|
|
1245
|
-
excluded: intrinsic.concat(callerExcluded, anchorNc.excluded),
|
|
1422
|
+
permittedGenerations: [callerPermitted, anchorNc.permitted, taConstraints.permitted],
|
|
1423
|
+
excluded: intrinsic.concat(intrinsic.concat(callerExcluded, anchorNc.excluded), taConstraints.excluded),
|
|
1246
1424
|
};
|
|
1247
|
-
var anchorNameConstraintsApplied = anchorNc.permitted.length > 0 || anchorNc.excluded.length > 0
|
|
1425
|
+
var anchorNameConstraintsApplied = anchorNc.permitted.length > 0 || anchorNc.excluded.length > 0 ||
|
|
1426
|
+
taConstraints.permitted.length > 0 || taConstraints.excluded.length > 0;
|
|
1248
1427
|
var purposeOpts = resolvePurposeOpts(opts);
|
|
1249
1428
|
var requiredEku = purposeOpts.requiredEku;
|
|
1250
1429
|
var checkPurpose = purposeOpts.checkPurpose;
|
|
1251
1430
|
|
|
1252
|
-
var state = initialize(certs, opts, seeds, anchor);
|
|
1431
|
+
var state = initialize(certs, opts, seeds, anchor, taConstraints);
|
|
1253
1432
|
state._n = n;
|
|
1254
1433
|
state.requiredEku = requiredEku;
|
|
1255
1434
|
var verifier = opts.verifier || null;
|
|
@@ -1488,28 +1667,42 @@ async function _validate(path, opts, run) {
|
|
|
1488
1667
|
workingPublicKey: state.workingPublicKey,
|
|
1489
1668
|
workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
|
|
1490
1669
|
workingPublicKeyParameters: state.workingPublicKeyParameters,
|
|
1491
|
-
|
|
1670
|
+
validPolicyGraph: graphSnapshot(state.validPolicyGraph),
|
|
1492
1671
|
userConstrainedPolicySet: ucps,
|
|
1493
1672
|
});
|
|
1494
1673
|
}
|
|
1495
1674
|
|
|
1675
|
+
/**
|
|
1676
|
+
* @internal RFC 9618 sec. 5.5 rewrites RFC 5280 sec. 6.1.5 step (g) over the graph. The set is read
|
|
1677
|
+
* from the nodes whose only parent is the anyPolicy node, at ANY depth rather than at the deepest
|
|
1678
|
+
* one, plus the anyPolicy node at depth n. Policy qualifiers are not collected: sec. 5.5 step
|
|
1679
|
+
* (g)(4)(ii) lets an application that does not use them skip it, and this returns identifiers.
|
|
1680
|
+
*/
|
|
1496
1681
|
function userConstrainedPolicies(state, n) {
|
|
1497
|
-
if (!state.
|
|
1682
|
+
if (!state.validPolicyGraph) return [];
|
|
1683
|
+
var authority = [];
|
|
1684
|
+
function note(node) {
|
|
1685
|
+
if (authority.indexOf(node.validPolicy) === -1) guard.list.append(authority, node.validPolicy);
|
|
1686
|
+
}
|
|
1687
|
+
/** @internal (g)(2) */
|
|
1688
|
+
for (var d = 1; d <= n; d++) {
|
|
1689
|
+
nodesAt(state, d).forEach(function (node) {
|
|
1690
|
+
if (node.validPolicy === OID.anyPolicy) return;
|
|
1691
|
+
if (node.parents.length === 1 && node.parents[0].validPolicy === OID.anyPolicy) note(node);
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
/** @internal (g)(3) */
|
|
1695
|
+
var anyAtN = nodeAt(state, n, OID.anyPolicy);
|
|
1696
|
+
if (anyAtN !== null) note(anyAtN);
|
|
1697
|
+
|
|
1698
|
+
/** @internal (g)(5) and (g)(6) */
|
|
1498
1699
|
var uips = state.userInitialPolicySet;
|
|
1499
|
-
|
|
1500
|
-
var
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
if (node.validPolicy === OID.anyPolicy) hasAnyLeaf = true;
|
|
1504
|
-
else explicit[node.validPolicy] = true;
|
|
1505
|
-
});
|
|
1506
|
-
var set = intrinsic.create(null);
|
|
1507
|
-
Object.keys(explicit).forEach(function (p) { if (anyUser || contains(uips, p)) set[p] = true; });
|
|
1508
|
-
if (hasAnyLeaf) {
|
|
1509
|
-
if (anyUser) set[OID.anyPolicy] = true;
|
|
1510
|
-
else uips.forEach(function (p) { set[p] = true; });
|
|
1700
|
+
if (contains(uips, OID.anyPolicy)) return authority;
|
|
1701
|
+
var out = intrinsic.filter(authority, function (p) { return contains(uips, p); });
|
|
1702
|
+
if (contains(authority, OID.anyPolicy)) {
|
|
1703
|
+
uips.forEach(function (p) { if (out.indexOf(p) === -1) guard.list.append(out, p); });
|
|
1511
1704
|
}
|
|
1512
|
-
return
|
|
1705
|
+
return out;
|
|
1513
1706
|
}
|
|
1514
1707
|
|
|
1515
1708
|
|
|
@@ -2367,6 +2560,70 @@ function _precaptureAnchorNameConstraints(list, who) {
|
|
|
2367
2560
|
}
|
|
2368
2561
|
return out;
|
|
2369
2562
|
}
|
|
2563
|
+
var _TA_CONSTRAINT_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
2564
|
+
nameConstraints: 1, policySet: 1, policyFlags: 1, pathLenConstraint: 1 });
|
|
2565
|
+
var _TA_FLAG_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
2566
|
+
inhibitPolicyMapping: 1, requireExplicitPolicy: 1, inhibitAnyPolicy: 1 });
|
|
2567
|
+
/**
|
|
2568
|
+
* @internal An RFC 5914 anchor's own constraints, snapshotted the way every other anchor field is:
|
|
2569
|
+
* read once from its own data property, copied, and never read again. A caller object that answered
|
|
2570
|
+
* one value to the check and another to the enforcement would otherwise decide the namespace and
|
|
2571
|
+
* the policy set a path is held to.
|
|
2572
|
+
*/
|
|
2573
|
+
function _copyAnchorRfc5914Constraints(c, who) {
|
|
2574
|
+
var label = who + ": a trustAnchor constraints";
|
|
2575
|
+
if (c === null || typeof c !== "object" || intrinsic.isArray(c)) {
|
|
2576
|
+
throw E("path/bad-input", label + " must be a { nameConstraints, policySet, policyFlags, pathLenConstraint } object");
|
|
2577
|
+
}
|
|
2578
|
+
guard.identifier.assertPlainRecord(c, E, "path/bad-input", label);
|
|
2579
|
+
guard.identifier.assertKnownKeys(c, _TA_CONSTRAINT_KEYS, E, "path/bad-input",
|
|
2580
|
+
label + " names nameConstraints, policySet, policyFlags and pathLenConstraint. The unknown key was: ");
|
|
2581
|
+
var out = {};
|
|
2582
|
+
var ncD = intrinsic.getOwnPropertyDescriptor(c, "nameConstraints");
|
|
2583
|
+
if (ncD !== undefined) {
|
|
2584
|
+
if (!intrinsic.hasOwn(ncD, "value")) throw E("path/bad-input", label + ".nameConstraints must be a data property, not an accessor");
|
|
2585
|
+
intrinsic.defineProperty(out, "nameConstraints", { value: _copyAnchorNameConstraints(ncD.value, who + ": a trustAnchor constraints"), enumerable: true, configurable: true, writable: true });
|
|
2586
|
+
}
|
|
2587
|
+
var psD = intrinsic.getOwnPropertyDescriptor(c, "policySet");
|
|
2588
|
+
if (psD !== undefined) {
|
|
2589
|
+
if (!intrinsic.hasOwn(psD, "value")) throw E("path/bad-input", label + ".policySet must be a data property, not an accessor");
|
|
2590
|
+
var ps = psD.value;
|
|
2591
|
+
if (!intrinsic.isArray(ps) || intrinsic.types.isProxy(ps)) throw E("path/bad-input", label + ".policySet must be a plain array of policy identifiers");
|
|
2592
|
+
var psOut = [];
|
|
2593
|
+
for (var i = 0; i < ps.length; i++) {
|
|
2594
|
+
var pd = intrinsic.getOwnPropertyDescriptor(ps, i);
|
|
2595
|
+
if (pd === undefined || !intrinsic.hasOwn(pd, "value")) throw E("path/bad-input", label + ".policySet entry " + i + " must be a data property, not a hole or an accessor");
|
|
2596
|
+
if (typeof pd.value !== "string" || pd.value.length === 0) throw E("path/bad-input", label + ".policySet entries must be dotted-decimal policy identifiers");
|
|
2597
|
+
guard.list.append(psOut, guard.identifier.assertCanonicalOid(pd.value, E, "path/bad-input", label + ".policySet entry " + pd.value));
|
|
2598
|
+
}
|
|
2599
|
+
intrinsic.defineProperty(out, "policySet", { value: psOut, enumerable: true, configurable: true, writable: true });
|
|
2600
|
+
}
|
|
2601
|
+
var pfD = intrinsic.getOwnPropertyDescriptor(c, "policyFlags");
|
|
2602
|
+
if (pfD !== undefined) {
|
|
2603
|
+
if (!intrinsic.hasOwn(pfD, "value")) throw E("path/bad-input", label + ".policyFlags must be a data property, not an accessor");
|
|
2604
|
+
var pf = pfD.value;
|
|
2605
|
+
if (pf === null || typeof pf !== "object" || intrinsic.isArray(pf)) throw E("path/bad-input", label + ".policyFlags must be an object of boolean flags");
|
|
2606
|
+
guard.identifier.assertPlainRecord(pf, E, "path/bad-input", label + ".policyFlags");
|
|
2607
|
+
guard.identifier.assertKnownKeys(pf, _TA_FLAG_KEYS, E, "path/bad-input", label + ".policyFlags names the three RFC 5914 flags. The unknown key was: ");
|
|
2608
|
+
var fOut = {};
|
|
2609
|
+
intrinsic.forEach(intrinsic.keys(_TA_FLAG_KEYS), function (k) {
|
|
2610
|
+
var fd = intrinsic.getOwnPropertyDescriptor(pf, k);
|
|
2611
|
+
if (fd === undefined) return;
|
|
2612
|
+
if (!intrinsic.hasOwn(fd, "value")) throw E("path/bad-input", label + ".policyFlags." + k + " must be a data property, not an accessor");
|
|
2613
|
+
if (typeof fd.value !== "boolean") throw E("path/bad-input", label + ".policyFlags." + k + " must be a boolean");
|
|
2614
|
+
intrinsic.defineProperty(fOut, k, { value: fd.value, enumerable: true, configurable: true, writable: true });
|
|
2615
|
+
});
|
|
2616
|
+
intrinsic.defineProperty(out, "policyFlags", { value: fOut, enumerable: true, configurable: true, writable: true });
|
|
2617
|
+
}
|
|
2618
|
+
var plD = intrinsic.getOwnPropertyDescriptor(c, "pathLenConstraint");
|
|
2619
|
+
if (plD !== undefined) {
|
|
2620
|
+
if (!intrinsic.hasOwn(plD, "value")) throw E("path/bad-input", label + ".pathLenConstraint must be a data property, not an accessor");
|
|
2621
|
+
if (!intrinsic.isInteger(plD.value) || plD.value < 0) throw E("path/bad-input", label + ".pathLenConstraint must be a non-negative integer");
|
|
2622
|
+
intrinsic.defineProperty(out, "pathLenConstraint", { value: plD.value, enumerable: true, configurable: true, writable: true });
|
|
2623
|
+
}
|
|
2624
|
+
return out;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2370
2627
|
function _copyAnchorNameConstraints(nc, who) {
|
|
2371
2628
|
if (nc === null || typeof nc !== "object" || intrinsic.isArray(nc)) {
|
|
2372
2629
|
throw E("path/bad-input", who + ": a trustAnchor nameConstraints must be a { permitted, excluded } object of { tag, base } subtree entries");
|
|
@@ -2435,7 +2692,7 @@ function _copyRdnsDeep(rdns) {
|
|
|
2435
2692
|
function toAnchor(entry, verb) {
|
|
2436
2693
|
var who = verb || "build";
|
|
2437
2694
|
var isTuple = false, alg = null, pubRaw, nameRaw = null, pk = null, purposesSnap, distrustSnap;
|
|
2438
|
-
var subjectDerRaw, labelRaw, mozRaw, ncSnap;
|
|
2695
|
+
var subjectDerRaw, labelRaw, mozRaw, ncSnap, taSnap;
|
|
2439
2696
|
if (intrinsic.types.isProxy(entry)) {
|
|
2440
2697
|
throw E("path/bad-input", who + ": a trustAnchor must not be a Proxy");
|
|
2441
2698
|
}
|
|
@@ -2454,6 +2711,8 @@ function toAnchor(entry, verb) {
|
|
|
2454
2711
|
distrustSnap = _normalizeConstraintField(dRaw, E, "path/bad-input", who);
|
|
2455
2712
|
var ncRaw = _captureConstraintMap(entry, "nameConstraints", E, "path/bad-input", who);
|
|
2456
2713
|
ncSnap = ncRaw == null ? undefined : _copyAnchorNameConstraints(ncRaw, who);
|
|
2714
|
+
var taRaw = _captureConstraintMap(entry, "constraints", E, "path/bad-input", who);
|
|
2715
|
+
taSnap = taRaw == null ? undefined : _copyAnchorRfc5914Constraints(taRaw, who);
|
|
2457
2716
|
subjectDerRaw = _captureOwnData(entry, "subjectDer");
|
|
2458
2717
|
labelRaw = _captureOwnData(entry, "label");
|
|
2459
2718
|
mozRaw = _captureOwnData(entry, "mozillaCaPolicy");
|
|
@@ -2522,6 +2781,7 @@ function toAnchor(entry, verb) {
|
|
|
2522
2781
|
if (purposesSnap !== undefined) intrinsic.defineProperty(flat, "purposes", { value: purposesSnap, enumerable: true, configurable: true, writable: true });
|
|
2523
2782
|
if (distrustSnap !== undefined) intrinsic.defineProperty(flat, "distrustAfter", { value: distrustSnap, enumerable: true, configurable: true, writable: true });
|
|
2524
2783
|
if (ncSnap !== undefined) intrinsic.defineProperty(flat, "nameConstraints", { value: ncSnap, enumerable: true, configurable: true, writable: true });
|
|
2784
|
+
if (taSnap !== undefined) intrinsic.defineProperty(flat, "constraints", { value: taSnap, enumerable: true, configurable: true, writable: true });
|
|
2525
2785
|
if (subjectDerRaw !== undefined) intrinsic.defineProperty(flat, "subjectDer", { value: subjectDerRaw, enumerable: true, configurable: true, writable: true });
|
|
2526
2786
|
if (labelRaw !== undefined) intrinsic.defineProperty(flat, "label", { value: labelRaw, enumerable: true, configurable: true, writable: true });
|
|
2527
2787
|
if (mozRaw !== undefined) intrinsic.defineProperty(flat, "mozillaCaPolicy", { value: mozRaw, enumerable: true, configurable: true, writable: true });
|
|
@@ -2537,7 +2797,17 @@ function toAnchor(entry, verb) {
|
|
|
2537
2797
|
try { cert = coerceCert(entry); }
|
|
2538
2798
|
catch (e) { throw E("path/bad-input", who + ": a trustAnchor entry must be a { name, publicKey, algorithm } tuple or a certificate", e); }
|
|
2539
2799
|
var spki = cert.subjectPublicKeyInfo;
|
|
2540
|
-
|
|
2800
|
+
var out = { name: cert.subject, publicKey: spki.bytes, algorithm: spki.algorithm.oid, parameters: spki.algorithm.parameters, subjectDer: cert.subject.bytes };
|
|
2801
|
+
/** @internal The certificate's own basic constraints are associated with the anchor, so its
|
|
2802
|
+
* pathLenConstraint bounds the path below it (RFC 5937 sec. 3.2). Defined rather than assigned,
|
|
2803
|
+
* for the reason every other anchor field is. */
|
|
2804
|
+
var certPathLen = pkix.anchorPathLenFromExtensions(cert.extensions, NS, who + ": the trustAnchor certificate");
|
|
2805
|
+
if (certPathLen !== null) {
|
|
2806
|
+
var c = intrinsic.create(null);
|
|
2807
|
+
c.pathLenConstraint = certPathLen;
|
|
2808
|
+
guard.verdict.set(out, "constraints", c);
|
|
2809
|
+
}
|
|
2810
|
+
return out;
|
|
2541
2811
|
}
|
|
2542
2812
|
|
|
2543
2813
|
/**
|
|
@@ -2773,8 +3043,7 @@ async function _aiaFetchOne(uri, aia) {
|
|
|
2773
3043
|
: Buffer.from(res.body == null ? "" : String(res.body), "latin1");
|
|
2774
3044
|
if (body.length === 0) throw E("path/aia-empty", "an AIA fetch returned an empty body");
|
|
2775
3045
|
if (body.length > aia.maxResponseBytes) throw E("path/aia-too-large", "an AIA response exceeds the " + aia.maxResponseBytes + "-byte cap");
|
|
2776
|
-
var headers =
|
|
2777
|
-
Object.keys(res.headers || {}).forEach(function (k) { headers[k.toLowerCase()] = res.headers[k]; });
|
|
3046
|
+
var headers = guard.header.lowerCased(res.headers);
|
|
2778
3047
|
return _aiaParseBody(body, headers["content-type"], aia.maxCertsPerResponse);
|
|
2779
3048
|
}
|
|
2780
3049
|
|