@blamejs/pki 0.8.10 → 0.8.12
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 +33 -0
- package/README.md +6 -6
- package/lib/attrcert-sign.js +14 -15
- package/lib/constants.js +5 -0
- package/lib/guard-parsed.js +1 -1
- package/lib/inspect.js +59 -0
- package/lib/lint.js +3 -3
- package/lib/path-validate.js +227 -97
- package/lib/schema-all.js +10 -5
- package/lib/schema-attrcert.js +108 -13
- package/lib/schema-crl.js +136 -38
- package/lib/schema-crmf.js +91 -6
- package/lib/schema-csr.js +116 -5
- package/lib/schema-pkix.js +494 -4
- package/lib/schema-x509.js +97 -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
|
-
var idx = node.parent.children.indexOf(node);
|
|
652
|
-
if (idx !== -1) node.parent.children.splice(idx, 1);
|
|
653
|
-
}
|
|
696
|
+
nodesAt(state, d).forEach(function (node) {
|
|
697
|
+
if (node.children.length === 0) removePolicyNode(state, node);
|
|
654
698
|
});
|
|
655
699
|
}
|
|
656
700
|
}
|
|
657
|
-
function
|
|
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),
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
return { nodes: nodes };
|
|
736
|
+
}
|
|
658
737
|
|
|
659
738
|
|
|
660
739
|
function isSubtreeBaseValid(tag, base) {
|
|
@@ -727,8 +806,17 @@ function _anchorSubtreeSeeds(anchor) {
|
|
|
727
806
|
|
|
728
807
|
function initialize(certs, params, seeds, anchor) {
|
|
729
808
|
var n = certs.length;
|
|
809
|
+
var state = _initialState(certs, params, seeds, anchor, n);
|
|
810
|
+
/** @internal The root is the depth-zero anyPolicy node, and it is indexed like any other, so the
|
|
811
|
+
* "is there a node at this depth for this policy" lookup answers for depth zero too. */
|
|
812
|
+
policyLayer(state, 0)[OID.anyPolicy] = state.validPolicyGraph;
|
|
813
|
+
return state;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function _initialState(certs, params, seeds, anchor, n) {
|
|
730
817
|
return {
|
|
731
|
-
|
|
818
|
+
validPolicyGraph: graphRoot(),
|
|
819
|
+
policyIndex: [],
|
|
732
820
|
policyNodeCount: 1,
|
|
733
821
|
maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
|
|
734
822
|
minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
|
|
@@ -757,55 +845,61 @@ function processPolicies(state, cert, i, checks) {
|
|
|
757
845
|
try { cp = decodeExt(cert, OID.certificatePolicies); }
|
|
758
846
|
catch (e) { guard.list.append(checks,{ name: "policies", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
759
847
|
|
|
760
|
-
if (cp && state.
|
|
848
|
+
if (cp && state.validPolicyGraph) {
|
|
761
849
|
var policies = cp.value;
|
|
762
|
-
var
|
|
850
|
+
var prev = i - 1;
|
|
763
851
|
var anyPolicyActive = state.inhibitAnyPolicy > 0 || (i < state._n && selfIssued(cert));
|
|
764
852
|
var anyPolicyPresent = false;
|
|
765
853
|
var anyPolicyQualifiers = null;
|
|
854
|
+
|
|
855
|
+
/** @internal RFC 9618 sec. 5.3 step (d)(1): ONE node per policy, carrying every depth i-1 node
|
|
856
|
+
* that expected it. Creating one per parent instead is what made the structure exponential. */
|
|
766
857
|
policies.forEach(function (p) {
|
|
767
858
|
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
|
-
}
|
|
859
|
+
var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
|
|
860
|
+
return contains(node.expectedPolicySet, p.policyIdentifier);
|
|
774
861
|
});
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
862
|
+
if (parents.length === 0) {
|
|
863
|
+
/** @internal (d)(1)(ii): no node expected it, so it hangs off the anyPolicy node if there
|
|
864
|
+
* is one. That single anyPolicy parent is what step (g)(2) later reads. */
|
|
865
|
+
var anyPrev = nodeAt(state, prev, OID.anyPolicy);
|
|
866
|
+
if (anyPrev === null) return;
|
|
867
|
+
parents = [anyPrev];
|
|
779
868
|
}
|
|
869
|
+
addPolicyNode(state, i, p.policyIdentifier, p.qualifiersBytes, [p.policyIdentifier], parents);
|
|
780
870
|
});
|
|
871
|
+
|
|
872
|
+
/** @internal (d)(2): for each policy some depth i-1 node expects, if depth i has no node for it
|
|
873
|
+
* yet, one node carrying every parent that expected it. */
|
|
781
874
|
if (anyPolicyPresent && anyPolicyActive) {
|
|
782
|
-
|
|
875
|
+
var expected = [];
|
|
876
|
+
nodesAt(state, prev).forEach(function (node) {
|
|
783
877
|
node.expectedPolicySet.forEach(function (ep) {
|
|
784
|
-
|
|
785
|
-
|
|
878
|
+
if (expected.indexOf(ep) === -1) guard.list.append(expected, ep);
|
|
879
|
+
});
|
|
880
|
+
});
|
|
881
|
+
expected.forEach(function (ep) {
|
|
882
|
+
if (nodeAt(state, i, ep) !== null) return;
|
|
883
|
+
var parents = intrinsic.filter(nodesAt(state, prev), function (node) {
|
|
884
|
+
return contains(node.expectedPolicySet, ep);
|
|
786
885
|
});
|
|
886
|
+
addPolicyNode(state, i, ep, anyPolicyQualifiers, [ep], parents);
|
|
787
887
|
});
|
|
788
888
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
889
|
+
|
|
890
|
+
if (state._capHit) return { fatal: true, error: E("path/policy-tree-cap", "policy graph exceeded the node cap") };
|
|
891
|
+
prunePolicyChildless(state, prev);
|
|
892
|
+
if (graphIsEmpty(state)) state.validPolicyGraph = null;
|
|
792
893
|
} else if (!cp) {
|
|
793
|
-
state.
|
|
894
|
+
state.validPolicyGraph = null;
|
|
794
895
|
}
|
|
795
896
|
|
|
796
|
-
if (!(state.explicitPolicy > 0 || state.
|
|
897
|
+
if (!(state.explicitPolicy > 0 || state.validPolicyGraph !== null)) {
|
|
797
898
|
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
|
|
899
|
+
return { fatal: true, error: E("path/policy-required", "explicit policy required but the valid-policy graph is empty") };
|
|
799
900
|
}
|
|
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
901
|
void checks;
|
|
902
|
+
return { fatal: false };
|
|
809
903
|
}
|
|
810
904
|
|
|
811
905
|
function isNullOrAbsentParams(p) {
|
|
@@ -859,7 +953,7 @@ function prepareNext(state, cert, i, checks) {
|
|
|
859
953
|
if (pm) {
|
|
860
954
|
var badAny = mapsAnyPolicy(pm.value);
|
|
861
955
|
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.
|
|
956
|
+
if (state.validPolicyGraph) applyPolicyMappings(state, pm.value, i);
|
|
863
957
|
}
|
|
864
958
|
|
|
865
959
|
state.workingIssuerName = cert.subject;
|
|
@@ -919,36 +1013,49 @@ function prepareNext(state, cert, i, checks) {
|
|
|
919
1013
|
return { fatal: false };
|
|
920
1014
|
}
|
|
921
1015
|
|
|
1016
|
+
/** @internal RFC 9618 sec. 5.4 rewrites RFC 5280 sec. 6.1.4 step (b) over the graph. Each clause is
|
|
1017
|
+
* singular where the tree's was plural, because at most one node at a depth carries a given policy.
|
|
1018
|
+
*
|
|
1019
|
+
* A mapping naming anyPolicy on either side is skipped here as well as refused by the caller
|
|
1020
|
+
* (sec. 6.1.4(a)), because mapping one in would rewrite the anyPolicy node's expected policy set and
|
|
1021
|
+
* the graph's invariant is that it is always {anyPolicy}. The caller refusing it keeps that from
|
|
1022
|
+
* happening today; skipping it here is what keeps it true of this function. */
|
|
922
1023
|
function applyPolicyMappings(state, mappings, i) {
|
|
923
|
-
var depth = i;
|
|
924
1024
|
if (state.policyMapping > 0) {
|
|
925
1025
|
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
|
-
}
|
|
1026
|
+
mappings.forEach(function (m) {
|
|
1027
|
+
if (m.issuerDomainPolicy === OID.anyPolicy || m.subjectDomainPolicy === OID.anyPolicy) return;
|
|
1028
|
+
guard.list.append(mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || [], m.subjectDomainPolicy);
|
|
938
1029
|
});
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
var idx = node.parent.children.indexOf(node);
|
|
946
|
-
if (idx !== -1) node.parent.children.splice(idx, 1);
|
|
1030
|
+
intrinsic.forEach(intrinsic.keys(mappedFrom), function (idp) {
|
|
1031
|
+
var node = nodeAt(state, i, idp);
|
|
1032
|
+
if (node !== null) {
|
|
1033
|
+
/** @internal (b)(1) */
|
|
1034
|
+
node.expectedPolicySet = mappedFrom[idp].slice();
|
|
1035
|
+
return;
|
|
947
1036
|
}
|
|
1037
|
+
/** @internal (b)(2): no node for the issuer domain policy, so one is generated under the
|
|
1038
|
+
* anyPolicy node at depth i-1, taking the depth-i anyPolicy node's qualifiers. */
|
|
1039
|
+
var anyHere = nodeAt(state, i, OID.anyPolicy);
|
|
1040
|
+
if (anyHere === null) return;
|
|
1041
|
+
var anyPrev = nodeAt(state, i - 1, OID.anyPolicy);
|
|
1042
|
+
if (anyPrev === null) return;
|
|
1043
|
+
addPolicyNode(state, i, idp, anyHere.qualifierSet[0] || null, mappedFrom[idp].slice(), [anyPrev]);
|
|
948
1044
|
});
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
/** @internal (b)(3): mapping is inhibited, so the node for each issuer domain policy is deleted
|
|
1048
|
+
* and every ancestor it leaves childless goes with it. */
|
|
1049
|
+
if (!state.validPolicyGraph) return;
|
|
1050
|
+
var deleted = false;
|
|
1051
|
+
mappings.forEach(function (m) {
|
|
1052
|
+
if (m.issuerDomainPolicy === OID.anyPolicy) return;
|
|
1053
|
+
var node = nodeAt(state, i, m.issuerDomainPolicy);
|
|
1054
|
+
if (node !== null) { removePolicyNode(state, node); deleted = true; }
|
|
1055
|
+
});
|
|
1056
|
+
if (!deleted) return;
|
|
1057
|
+
prunePolicyChildless(state, i - 1);
|
|
1058
|
+
if (graphIsEmpty(state)) state.validPolicyGraph = null;
|
|
952
1059
|
}
|
|
953
1060
|
|
|
954
1061
|
var TARGET_UNPROCESSED_IF_CRITICAL = intrinsic.create(null);
|
|
@@ -1028,11 +1135,20 @@ function _revocationRow(row, rv) {
|
|
|
1028
1135
|
* are validated at the entry point. A mis-shaped value throws `path/bad-input`,
|
|
1029
1136
|
* so it cannot silently go unapplied. Returns `{ valid, revocationChecked, anchorConstraints,
|
|
1030
1137
|
* path, results, workingPublicKey, workingPublicKeyAlgorithm,
|
|
1031
|
-
* workingPublicKeyParameters,
|
|
1138
|
+
* workingPublicKeyParameters, validPolicyGraph, userConstrainedPolicySet }` where `results[i].checks`
|
|
1032
1139
|
* carries a per-check reason code (`path/*`) for every step. Pure and
|
|
1033
1140
|
* re-entrant: no input object is mutated. An empty path or a missing anchor
|
|
1034
1141
|
* throws a typed `PathError`.
|
|
1035
1142
|
*
|
|
1143
|
+
* Certificate policies are processed as the RFC 9618 `valid_policy_graph`, which replaces the
|
|
1144
|
+
* RFC 5280 sec. 6.1 `valid_policy_tree`. `validPolicyGraph` is a directed acyclic graph holding at
|
|
1145
|
+
* most one node per depth and policy identifier; a node reached from several policies carries them
|
|
1146
|
+
* all in `parents`, where the tree held a separate node under each. The graph is linear in the
|
|
1147
|
+
* number of policies and mappings on the path, so a chain whose tree would not fit inside
|
|
1148
|
+
* `maxPolicyNodes` validates. It is `{ nodes }`, a flat list linked by index, each entry
|
|
1149
|
+
* `{ id, depth, validPolicy, qualifierSet, expectedPolicySet, parents, children }` where `parents`
|
|
1150
|
+
* and `children` hold the ids of other entries and the root is id 0.
|
|
1151
|
+
*
|
|
1036
1152
|
* A certificate signed with the RFC 9802 stateful hash-based algorithm
|
|
1037
1153
|
* `id-alg-hss-lms-hashsig` is verified like any other, through the toolkit's own
|
|
1038
1154
|
* HSS engine rather than the crypto engine. The signature covers the whole
|
|
@@ -1488,28 +1604,42 @@ async function _validate(path, opts, run) {
|
|
|
1488
1604
|
workingPublicKey: state.workingPublicKey,
|
|
1489
1605
|
workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
|
|
1490
1606
|
workingPublicKeyParameters: state.workingPublicKeyParameters,
|
|
1491
|
-
|
|
1607
|
+
validPolicyGraph: graphSnapshot(state.validPolicyGraph),
|
|
1492
1608
|
userConstrainedPolicySet: ucps,
|
|
1493
1609
|
});
|
|
1494
1610
|
}
|
|
1495
1611
|
|
|
1612
|
+
/**
|
|
1613
|
+
* @internal RFC 9618 sec. 5.5 rewrites RFC 5280 sec. 6.1.5 step (g) over the graph. The set is read
|
|
1614
|
+
* from the nodes whose only parent is the anyPolicy node, at ANY depth rather than at the deepest
|
|
1615
|
+
* one, plus the anyPolicy node at depth n. Policy qualifiers are not collected: sec. 5.5 step
|
|
1616
|
+
* (g)(4)(ii) lets an application that does not use them skip it, and this returns identifiers.
|
|
1617
|
+
*/
|
|
1496
1618
|
function userConstrainedPolicies(state, n) {
|
|
1497
|
-
if (!state.
|
|
1619
|
+
if (!state.validPolicyGraph) return [];
|
|
1620
|
+
var authority = [];
|
|
1621
|
+
function note(node) {
|
|
1622
|
+
if (authority.indexOf(node.validPolicy) === -1) guard.list.append(authority, node.validPolicy);
|
|
1623
|
+
}
|
|
1624
|
+
/** @internal (g)(2) */
|
|
1625
|
+
for (var d = 1; d <= n; d++) {
|
|
1626
|
+
nodesAt(state, d).forEach(function (node) {
|
|
1627
|
+
if (node.validPolicy === OID.anyPolicy) return;
|
|
1628
|
+
if (node.parents.length === 1 && node.parents[0].validPolicy === OID.anyPolicy) note(node);
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
/** @internal (g)(3) */
|
|
1632
|
+
var anyAtN = nodeAt(state, n, OID.anyPolicy);
|
|
1633
|
+
if (anyAtN !== null) note(anyAtN);
|
|
1634
|
+
|
|
1635
|
+
/** @internal (g)(5) and (g)(6) */
|
|
1498
1636
|
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; });
|
|
1637
|
+
if (contains(uips, OID.anyPolicy)) return authority;
|
|
1638
|
+
var out = intrinsic.filter(authority, function (p) { return contains(uips, p); });
|
|
1639
|
+
if (contains(authority, OID.anyPolicy)) {
|
|
1640
|
+
uips.forEach(function (p) { if (out.indexOf(p) === -1) guard.list.append(out, p); });
|
|
1511
1641
|
}
|
|
1512
|
-
return
|
|
1642
|
+
return out;
|
|
1513
1643
|
}
|
|
1514
1644
|
|
|
1515
1645
|
|
package/lib/schema-all.js
CHANGED
|
@@ -345,10 +345,13 @@ function _detectOrNull(der, label) {
|
|
|
345
345
|
|
|
346
346
|
module.exports = {
|
|
347
347
|
engine: engine,
|
|
348
|
-
x509: { parse: x509.parse, pemDecode: x509.pemDecode, pemEncode: x509.pemEncode
|
|
348
|
+
x509: { parse: x509.parse, pemDecode: x509.pemDecode, pemEncode: x509.pemEncode,
|
|
349
|
+
decodeExtensions: x509.decodeExtensions, decodeExtension: x509.decodeExtension },
|
|
349
350
|
c509: { parse: c509.parse, encode: c509.encode },
|
|
350
|
-
crl: { parse: crl.parse, pemDecode: crl.pemDecode, pemEncode: crl.pemEncode
|
|
351
|
-
|
|
351
|
+
crl: { parse: crl.parse, pemDecode: crl.pemDecode, pemEncode: crl.pemEncode,
|
|
352
|
+
decodeExtensions: crl.decodeExtensions, decodeExtension: crl.decodeExtension },
|
|
353
|
+
csr: { parse: csr.parse, pemDecode: csr.pemDecode, pemEncode: csr.pemEncode,
|
|
354
|
+
decodeExtensions: csr.decodeExtensions, decodeExtension: csr.decodeExtension },
|
|
352
355
|
pkcs8: { parse: pkcs8.parse, parseEncrypted: pkcs8.parseEncrypted, pemDecode: pkcs8.pemDecode, pemEncode: pkcs8.pemEncode },
|
|
353
356
|
pkcs1: {
|
|
354
357
|
parse: pkcs1.parse, parsePublic: pkcs1.parsePublic,
|
|
@@ -361,10 +364,12 @@ module.exports = {
|
|
|
361
364
|
cms: { parse: cms.parse, pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
|
|
362
365
|
ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode, pemEncode: ocsp.pemEncode },
|
|
363
366
|
tsp: { parse: tsp.parse, parseResponse: tsp.parseResponse, parseRequest: tsp.parseRequest, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode, pemEncode: tsp.pemEncode },
|
|
364
|
-
crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode, pemEncode: crmf.pemEncode
|
|
367
|
+
crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode, pemEncode: crmf.pemEncode,
|
|
368
|
+
decodeExtensions: crmf.decodeExtensions, decodeExtension: crmf.decodeExtension },
|
|
365
369
|
cmp: { parse: cmp.parse, pemDecode: cmp.pemDecode, pemEncode: cmp.pemEncode },
|
|
366
370
|
csrattrs: { parse: csrattrs.parse },
|
|
367
|
-
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode
|
|
371
|
+
attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode,
|
|
372
|
+
decodeExtensions: attrcert.decodeExtensions, decodeExtension: attrcert.decodeExtension },
|
|
368
373
|
smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
|
|
369
374
|
cmc: { parse: cmc.parse, parsePkiData: cmc.parsePkiData, parsePkiResponse: cmc.parsePkiResponse },
|
|
370
375
|
pem: {
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -590,19 +590,9 @@ _AC_EXT_DECODERS[_Oav("acProxying")] = _acProxying;
|
|
|
590
590
|
/** @internal The three RFC 5280 extensions RFC 5755 sec. 4.3.3 to 4.3.5 profile for an AC are read
|
|
591
591
|
* through the certificate decoders, so a relying party running the sec. 6 "pointer in AC" scheme
|
|
592
592
|
* reads the distribution point and the OCSP location off the parse rather than off raw bytes. */
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
* figure. A caller outside a parse passes no context and reuses the module's table. */
|
|
597
|
-
function _certExtDecoder(name) {
|
|
598
|
-
var key = _Oav(name);
|
|
599
|
-
return function (buf, ctx) {
|
|
600
|
-
return (ctx ? pkix.certExtensionDecoders(schema.withResources(NS, ctx)).byOid : _CERT_EXT_DECODERS)[key](buf);
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
_AC_EXT_DECODERS[_Oav("authorityKeyIdentifier")] = _certExtDecoder("authorityKeyIdentifier");
|
|
604
|
-
_AC_EXT_DECODERS[_Oav("authorityInfoAccess")] = _certExtDecoder("authorityInfoAccess");
|
|
605
|
-
_AC_EXT_DECODERS[_Oav("cRLDistributionPoints")] = _certExtDecoder("cRLDistributionPoints");
|
|
593
|
+
_AC_EXT_DECODERS[_Oav("authorityKeyIdentifier")] = pkix.scopedCertDecoder(NS, "authorityKeyIdentifier");
|
|
594
|
+
_AC_EXT_DECODERS[_Oav("authorityInfoAccess")] = pkix.scopedCertDecoder(NS, "authorityInfoAccess");
|
|
595
|
+
_AC_EXT_DECODERS[_Oav("cRLDistributionPoints")] = pkix.scopedCertDecoder(NS, "cRLDistributionPoints");
|
|
606
596
|
|
|
607
597
|
// @internal
|
|
608
598
|
function validateAttributeValue(type, valueDer) {
|
|
@@ -614,6 +604,108 @@ function validateExtensionValue(extnOid, extnValueDer) {
|
|
|
614
604
|
return dec ? dec(extnValueDer) : null;
|
|
615
605
|
}
|
|
616
606
|
|
|
607
|
+
/** @internal The criticality RFC 5755 fixes for each extension it profiles for an attribute
|
|
608
|
+
* certificate. The AC signer refuses to write an extension against this table and the extension
|
|
609
|
+
* door reports one that arrived against it, from this one row set. */
|
|
610
|
+
var AC_FIXED_CRITICALITY_ROWS = [
|
|
611
|
+
["acAuditIdentity", true, "RFC 5755 sec. 4.3.1"], ["targetInformation", true, "RFC 5755 sec. 4.3.2"],
|
|
612
|
+
["authorityKeyIdentifier", false, "RFC 5755 sec. 4.3.3"], ["authorityInfoAccess", false, "RFC 5755 sec. 4.3.4"],
|
|
613
|
+
["cRLDistributionPoints", false, "RFC 5755 sec. 4.3.5"], ["noRevAvail", false, "RFC 5755 sec. 4.3.6"],
|
|
614
|
+
["acProxying", true, "RFC 5755 sec. 7.3"], ["aaControls", true, "RFC 5755 sec. 7.4"],
|
|
615
|
+
];
|
|
616
|
+
function extensionCriticality(oidRegistry) {
|
|
617
|
+
var table = intrinsic.create(null);
|
|
618
|
+
intrinsic.forEach(AC_FIXED_CRITICALITY_ROWS, function (r) {
|
|
619
|
+
table[oidRegistry.byName(r[0])] = { critical: r[1], citation: r[2] };
|
|
620
|
+
});
|
|
621
|
+
return table;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** @internal The attribute-certificate scope. The table is this module's own, passed to the shared
|
|
625
|
+
* door rather than reached for from it, because a registry in `schema-pkix.js` that required this
|
|
626
|
+
* module would invert the dependency. */
|
|
627
|
+
var AC_SCOPES = intrinsic.assign(intrinsic.create(null), {
|
|
628
|
+
"attribute-certificate": {
|
|
629
|
+
/** @internal Every decoder in this table takes the walk context and threads it into its own
|
|
630
|
+
* decode, so the one table serves every call and the caller's caps still reach the value. */
|
|
631
|
+
decoders: function () { return _AC_EXT_DECODERS; },
|
|
632
|
+
criticality: extensionCriticality(oid),
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
var DOOR = pkix.extensionDoor({
|
|
637
|
+
ns: NS, ErrorClass: AttrCertError, key: "attrcert", kind: "attributeCertificate",
|
|
638
|
+
what: "the attribute certificate", parse: parse, scopes: AC_SCOPES,
|
|
639
|
+
defaultScope: "attribute-certificate",
|
|
640
|
+
segments: function (ac) { return [{ list: ac.extensions || [], scope: "attribute-certificate" }]; },
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* @primitive pki.schema.attrcert.decodeExtensions
|
|
645
|
+
* @signature pki.schema.attrcert.decodeExtensions(acOrBytes, opts?) -> rows
|
|
646
|
+
* @since 0.8.11
|
|
647
|
+
* @status stable
|
|
648
|
+
* @spec RFC 5755 sec. 4.2.9, RFC 5755 sec. 4.3
|
|
649
|
+
* @related pki.schema.attrcert.parse, pki.schema.x509.decodeExtensions
|
|
650
|
+
*
|
|
651
|
+
* Read an attribute certificate's extensions as decoded records. The argument is the object `parse`
|
|
652
|
+
* returned or the bytes `parse` takes.
|
|
653
|
+
*
|
|
654
|
+
* Each row is `{ oid, name, critical, value, scope, index, containerIndex, state, decoded, code,
|
|
655
|
+
* profile }`, with `scope` `"attribute-certificate"` and `containerIndex` `null`. `state` is the
|
|
656
|
+
* discriminator, and `profile` reports the criticality RFC 5755 fixes for the extension.
|
|
657
|
+
*
|
|
658
|
+
* RFC 5755 sec. 5 item 7 has a verifier reject an AC carrying a critical extension it does not
|
|
659
|
+
* recognize. That is the caller's rule to apply, and the two facts it needs, `critical` and
|
|
660
|
+
* `state`, are on the row.
|
|
661
|
+
*
|
|
662
|
+
* @opts
|
|
663
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
664
|
+
* undecodable value. It does not turn an unrecognized OID into a throw.
|
|
665
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
666
|
+
* @example
|
|
667
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
668
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
669
|
+
* var aaCert = await pki.x509.sign({ subject: "Example AA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
670
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
671
|
+
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "CN=Holder" }] },
|
|
672
|
+
* attributes: { role: { roleName: "https://example.test/role/admin" } }, serialNumber: 1n,
|
|
673
|
+
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
674
|
+
* extensions: { noRevAvail: true } }, { cert: aaCert, key: key });
|
|
675
|
+
* var rows = pki.schema.attrcert.decodeExtensions(acDer);
|
|
676
|
+
* rows.filter(function (r) { return r.critical && r.state === "unrecognized"; }).length; // -> 0
|
|
677
|
+
*/
|
|
678
|
+
var decodeExtensions = DOOR.decodeExtensions;
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* @primitive pki.schema.attrcert.decodeExtension
|
|
682
|
+
* @signature pki.schema.attrcert.decodeExtension(ext, opts?) -> row
|
|
683
|
+
* @since 0.8.11
|
|
684
|
+
* @status stable
|
|
685
|
+
* @spec RFC 5755 sec. 4.3
|
|
686
|
+
* @related pki.schema.attrcert.decodeExtensions
|
|
687
|
+
*
|
|
688
|
+
* Read one attribute-certificate extension record as a decoded row, at scope
|
|
689
|
+
* `"attribute-certificate"`. The argument is `{ oid, name, critical, value }`.
|
|
690
|
+
*
|
|
691
|
+
* @opts
|
|
692
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
693
|
+
* undecodable value.
|
|
694
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
695
|
+
* @example
|
|
696
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
697
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
698
|
+
* var aaCert = await pki.x509.sign({ subject: "Example AA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
699
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
700
|
+
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "CN=Holder" }] },
|
|
701
|
+
* attributes: { role: { roleName: "https://example.test/role/admin" } }, serialNumber: 1n,
|
|
702
|
+
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
703
|
+
* extensions: { noRevAvail: true } }, { cert: aaCert, key: key });
|
|
704
|
+
* var ac = pki.schema.attrcert.parse(acDer);
|
|
705
|
+
* pki.schema.attrcert.decodeExtension(ac.extensions[0]).profile.citation; // -> "RFC 5755 sec. 4.3.6"
|
|
706
|
+
*/
|
|
707
|
+
var decodeExtension = DOOR.decodeExtension;
|
|
708
|
+
|
|
617
709
|
module.exports = {
|
|
618
710
|
parse: parse,
|
|
619
711
|
parseV1: parseV1,
|
|
@@ -621,6 +713,9 @@ module.exports = {
|
|
|
621
713
|
pemEncode: pemEncode,
|
|
622
714
|
matches: matches,
|
|
623
715
|
matchesV1: matchesV1,
|
|
716
|
+
decodeExtensions: decodeExtensions,
|
|
717
|
+
decodeExtension: decodeExtension,
|
|
718
|
+
extensionCriticality: extensionCriticality,
|
|
624
719
|
readGeneralName: readGeneralName,
|
|
625
720
|
validateAttributeValue: validateAttributeValue,
|
|
626
721
|
validateExtensionValue: validateExtensionValue,
|