@blamejs/pki 0.6.39 → 0.6.41
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 +25 -0
- package/lib/acme.js +24 -13
- package/lib/asn1-der.js +5 -5
- package/lib/attrcert-sign.js +16 -16
- package/lib/cbor-det.js +2 -2
- package/lib/cmc-build.js +16 -16
- package/lib/cmc-verify.js +5 -5
- package/lib/cmp-build.js +29 -19
- package/lib/cmp-session.js +25 -20
- package/lib/cmp-verify.js +8 -12
- package/lib/cms-decrypt.js +8 -8
- package/lib/cms-encrypt.js +22 -22
- package/lib/cms-sign.js +17 -17
- package/lib/cms-verify.js +13 -13
- package/lib/composite-kem.js +1 -1
- package/lib/composite-sig.js +4 -4
- package/lib/constants.js +13 -9
- package/lib/crl-sign.js +25 -25
- package/lib/crmf-sign.js +11 -11
- package/lib/csr-sign.js +4 -4
- package/lib/ct.js +33 -25
- package/lib/edwards-point.js +2 -2
- package/lib/est.js +74 -43
- package/lib/guard-async.js +17 -1
- package/lib/guard-bytes.js +3 -3
- package/lib/guard-compress.js +5 -5
- package/lib/guard-identifier.js +46 -2
- package/lib/guard-json.js +1 -1
- package/lib/guard-list.js +16 -0
- package/lib/guard-name.js +1 -1
- package/lib/guard-parsed.js +1 -1
- package/lib/guard-time.js +3 -1
- package/lib/guard-verdict.js +7 -1
- package/lib/hpke.js +10 -10
- package/lib/http-digest.js +2 -2
- package/lib/http-transport.js +3 -3
- package/lib/inspect.js +9 -9
- package/lib/ip-utils.js +1 -1
- package/lib/jose.js +14 -14
- package/lib/key.js +7 -7
- package/lib/lint.js +6 -4
- package/lib/merkle.js +2 -2
- package/lib/mime.js +2 -2
- package/lib/ocsp-verify.js +1 -1
- package/lib/ocsp.js +5 -5
- package/lib/oid.js +3 -3
- package/lib/path-validate.js +127 -117
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +26 -24
- package/lib/pki-build.js +2 -2
- package/lib/scep.js +47 -16
- package/lib/schema-all.js +1 -1
- package/lib/schema-attrcert.js +4 -3
- package/lib/schema-c509.js +23 -23
- package/lib/schema-cmc.js +6 -6
- package/lib/schema-cmp.js +3 -3
- package/lib/schema-cms.js +15 -8
- package/lib/schema-crmf.js +1 -1
- package/lib/schema-csr.js +1 -1
- package/lib/schema-engine.js +2 -1
- package/lib/schema-pkcs12.js +1 -1
- package/lib/schema-pkix.js +3 -0
- package/lib/schema-tsp.js +2 -2
- package/lib/shbs.js +2 -2
- package/lib/sign-scheme.js +9 -9
- package/lib/sigstore.js +24 -14
- package/lib/smime.js +16 -16
- package/lib/tls-cert-compress.js +6 -6
- package/lib/trust.js +9 -9
- package/lib/tsp-sign.js +30 -30
- package/lib/validator-attcert.js +2 -2
- package/lib/validator-cose.js +5 -5
- package/lib/validator-sig.js +3 -3
- package/lib/validator-tpm.js +5 -4
- package/lib/webauthn.js +58 -54
- package/lib/webcrypto.js +22 -14
- package/lib/x509-sign.js +8 -8
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/path-validate.js
CHANGED
|
@@ -80,7 +80,7 @@ var ANCHOR_SPKI_SCHEMA = pkix.spki(NS);
|
|
|
80
80
|
|
|
81
81
|
var subtle = webcrypto.webcrypto.subtle;
|
|
82
82
|
|
|
83
|
-
var OID = {
|
|
83
|
+
var OID = intrinsic.assign(intrinsic.create(null), {
|
|
84
84
|
basicConstraints: oid.byName("basicConstraints"),
|
|
85
85
|
keyUsage: oid.byName("keyUsage"),
|
|
86
86
|
nameConstraints: oid.byName("nameConstraints"),
|
|
@@ -98,9 +98,9 @@ var OID = {
|
|
|
98
98
|
authorityKeyIdentifier: oid.byName("authorityKeyIdentifier"),
|
|
99
99
|
authorityInfoAccess: oid.byName("authorityInfoAccess"),
|
|
100
100
|
caIssuers: oid.byName("caIssuers"),
|
|
101
|
-
};
|
|
101
|
+
});
|
|
102
102
|
|
|
103
|
-
var PROCESSED_EXTENSIONS =
|
|
103
|
+
var PROCESSED_EXTENSIONS = intrinsic.create(null);
|
|
104
104
|
[OID.basicConstraints, OID.keyUsage, OID.nameConstraints, OID.certificatePolicies,
|
|
105
105
|
OID.policyMappings, OID.policyConstraints, OID.inhibitAnyPolicy, OID.subjectAltName,
|
|
106
106
|
OID.extKeyUsage, OID.cRLDistributionPoints].
|
|
@@ -137,7 +137,7 @@ _sig("id-ml-dsa-87", { name: "ML-DSA-87" }, { name: "ML-DSA-87" }, "absent", fal
|
|
|
137
137
|
|
|
138
138
|
var OID_RSA_PSS = oid.byName("rsassaPss");
|
|
139
139
|
var OID_MGF1 = oid.byName("mgf1");
|
|
140
|
-
var HASH_BY_OID =
|
|
140
|
+
var HASH_BY_OID = intrinsic.create(null);
|
|
141
141
|
HASH_BY_OID[oid.byName("sha256")] = "SHA-256";
|
|
142
142
|
HASH_BY_OID[oid.byName("sha384")] = "SHA-384";
|
|
143
143
|
HASH_BY_OID[oid.byName("sha512")] = "SHA-512";
|
|
@@ -244,7 +244,7 @@ function compositeKeyUsageCheck(cert) {
|
|
|
244
244
|
return { ok: true };
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
var ML_KEM_OIDS =
|
|
247
|
+
var ML_KEM_OIDS = intrinsic.create(null);
|
|
248
248
|
["id-ml-kem-512", "id-ml-kem-768", "id-ml-kem-1024"].forEach(function (n) { ML_KEM_OIDS[oid.byName(n)] = true; });
|
|
249
249
|
|
|
250
250
|
function kemKeyUsageCheck(cert) {
|
|
@@ -316,7 +316,7 @@ function decodeExt(cert, extOid) {
|
|
|
316
316
|
|
|
317
317
|
function requireCriticalExt(ext, name, checks) {
|
|
318
318
|
if (ext && ext.critical !== true) {
|
|
319
|
-
|
|
319
|
+
guard.list.append(checks,{ name: name, ok: false, code: "path/extension-not-critical" });
|
|
320
320
|
return E("path/extension-not-critical", name + " extension must be marked critical (RFC 5280 4.2.1)");
|
|
321
321
|
}
|
|
322
322
|
return null;
|
|
@@ -454,17 +454,17 @@ function certNameForms(cert) {
|
|
|
454
454
|
if (san) {
|
|
455
455
|
san.value.names.forEach(function (nm) {
|
|
456
456
|
if (nm.tagNumber === 1) hasRfc822San = true;
|
|
457
|
-
|
|
457
|
+
guard.list.append(forms, { tag: nm.tagNumber, value: nm.value === undefined ? null : nm.value });
|
|
458
458
|
});
|
|
459
459
|
}
|
|
460
460
|
if (!hasRfc822San) {
|
|
461
461
|
cert.subject.rdns.forEach(function (rdn) {
|
|
462
462
|
rdn.forEach(function (atv) {
|
|
463
|
-
if (atv.type === OID.emailAddress && typeof atv.value === "string")
|
|
463
|
+
if (atv.type === OID.emailAddress && typeof atv.value === "string") guard.list.append(forms, { tag: 1, value: atv.value });
|
|
464
464
|
});
|
|
465
465
|
});
|
|
466
466
|
}
|
|
467
|
-
if (cert.subject.rdns.length > 0)
|
|
467
|
+
if (cert.subject.rdns.length > 0) guard.list.append(forms, { tag: 4, value: cert.subject });
|
|
468
468
|
return forms;
|
|
469
469
|
}
|
|
470
470
|
|
|
@@ -497,12 +497,12 @@ function checkNameConstraints(state, cert) {
|
|
|
497
497
|
|
|
498
498
|
function absorbNameConstraints(state, decoded) {
|
|
499
499
|
if (decoded.permittedSubtrees.length) {
|
|
500
|
-
state.permittedGenerations
|
|
500
|
+
guard.list.append(state.permittedGenerations, decoded.permittedSubtrees.map(function (st) {
|
|
501
501
|
return { tag: st.base.tagNumber, base: st.base.value };
|
|
502
502
|
}));
|
|
503
503
|
}
|
|
504
504
|
decoded.excludedSubtrees.forEach(function (st) {
|
|
505
|
-
state.excludedSubtrees
|
|
505
|
+
guard.list.append(state.excludedSubtrees, { tag: st.base.tagNumber, base: st.base.value });
|
|
506
506
|
});
|
|
507
507
|
}
|
|
508
508
|
|
|
@@ -524,7 +524,7 @@ function leavesAt(tree, depth) {
|
|
|
524
524
|
var out = [];
|
|
525
525
|
if (!tree) return out;
|
|
526
526
|
(function walk(node) {
|
|
527
|
-
if (node.depth === depth) {
|
|
527
|
+
if (node.depth === depth) { guard.list.append(out, node); return; }
|
|
528
528
|
node.children.forEach(walk);
|
|
529
529
|
})(tree);
|
|
530
530
|
return out;
|
|
@@ -563,7 +563,7 @@ function checkedSubtreeSeeds(list, optName) {
|
|
|
563
563
|
* array's constructor and Symbol.species, which is more caller code running mid-copy. */
|
|
564
564
|
var seedsOut = [];
|
|
565
565
|
for (var si = 0; si < list.length; si++) {
|
|
566
|
-
|
|
566
|
+
guard.list.append(seedsOut, (function (st) {
|
|
567
567
|
var shapeMsg = "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }";
|
|
568
568
|
if (!st || typeof st !== "object" || intrinsic.types.isProxy(st)) throw E("path/bad-input", shapeMsg);
|
|
569
569
|
/** @internal Read tag and base once each, from their own data properties: an accessor could
|
|
@@ -639,7 +639,7 @@ function selfIssued(cert) {
|
|
|
639
639
|
function processPolicies(state, cert, i, checks) {
|
|
640
640
|
var cp;
|
|
641
641
|
try { cp = decodeExt(cert, OID.certificatePolicies); }
|
|
642
|
-
catch (e) {
|
|
642
|
+
catch (e) { guard.list.append(checks,{ name: "policies", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
643
643
|
|
|
644
644
|
if (cp && state.validPolicyTree) {
|
|
645
645
|
var policies = cp.value;
|
|
@@ -678,7 +678,7 @@ function processPolicies(state, cert, i, checks) {
|
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
if (!(state.explicitPolicy > 0 || state.validPolicyTree !== null)) {
|
|
681
|
-
|
|
681
|
+
guard.list.append(checks,{ name: "policy", ok: false, code: "path/policy-required" });
|
|
682
682
|
return { fatal: true, error: E("path/policy-required", "explicit policy required but the valid-policy tree is empty") };
|
|
683
683
|
}
|
|
684
684
|
return { fatal: false };
|
|
@@ -687,7 +687,7 @@ function processPolicies(state, cert, i, checks) {
|
|
|
687
687
|
function addChild(state, parent, validPolicy, qualifiers, expectedPolicySet, checks) {
|
|
688
688
|
if (state.policyNodeCount >= state.maxPolicyNodes) { state._capHit = true; return; }
|
|
689
689
|
var node = { depth: parent.depth + 1, validPolicy: validPolicy, qualifierSet: qualifiers ? [qualifiers] : [], expectedPolicySet: expectedPolicySet, children: [], parent: parent };
|
|
690
|
-
parent.children
|
|
690
|
+
guard.list.append(parent.children, node);
|
|
691
691
|
state.policyNodeCount++;
|
|
692
692
|
void checks;
|
|
693
693
|
}
|
|
@@ -722,11 +722,11 @@ function ekuPurposeFails(cert, requiredEku, checks) {
|
|
|
722
722
|
var eku;
|
|
723
723
|
try { eku = decodeExt(cert, OID.extKeyUsage); }
|
|
724
724
|
// allow:fail-open-verify -- ekuPurposeFails returns true to mean the EKU purpose FAILS (a rejecting verdict); a bad extension is recorded in checks and the purpose is treated as failed, which is fail-closed
|
|
725
|
-
catch (e) {
|
|
725
|
+
catch (e) { guard.list.append(checks,{ name: "extendedKeyUsage", ok: false, code: pathCode(e, "path/bad-extension-value") }); return true; }
|
|
726
726
|
if (!eku) return false;
|
|
727
727
|
var purposes = eku.value;
|
|
728
728
|
var ok = contains(purposes, OID.anyExtendedKeyUsage) || containsAll(purposes, requiredEku);
|
|
729
|
-
|
|
729
|
+
guard.list.append(checks,{ name: "extendedKeyUsage", ok: ok, code: ok ? undefined : "path/eku-not-permitted" });
|
|
730
730
|
return !ok;
|
|
731
731
|
}
|
|
732
732
|
|
|
@@ -739,10 +739,10 @@ function prepareNext(state, cert, i, checks) {
|
|
|
739
739
|
|
|
740
740
|
var pm;
|
|
741
741
|
try { pm = decodeExt(cert, OID.policyMappings); }
|
|
742
|
-
catch (e) {
|
|
742
|
+
catch (e) { guard.list.append(checks,{ name: "policyMappings", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
743
743
|
if (pm) {
|
|
744
744
|
var badAny = mapsAnyPolicy(pm.value);
|
|
745
|
-
if (badAny) {
|
|
745
|
+
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))") }; }
|
|
746
746
|
if (state.validPolicyTree) applyPolicyMappings(state, pm.value, i);
|
|
747
747
|
}
|
|
748
748
|
|
|
@@ -751,7 +751,7 @@ function prepareNext(state, cert, i, checks) {
|
|
|
751
751
|
|
|
752
752
|
var nc;
|
|
753
753
|
try { nc = decodeExt(cert, OID.nameConstraints); }
|
|
754
|
-
catch (e) {
|
|
754
|
+
catch (e) { guard.list.append(checks,{ name: "nameConstraints", ok: false, code: pathCode(e, "path/bad-name-constraints") }); return { fatal: true, error: e }; }
|
|
755
755
|
var ncCritErr = requireCriticalExt(nc, "nameConstraints", checks);
|
|
756
756
|
if (ncCritErr) return { fatal: true, error: ncCritErr };
|
|
757
757
|
if (nc) absorbNameConstraints(state, nc.value);
|
|
@@ -764,7 +764,7 @@ function prepareNext(state, cert, i, checks) {
|
|
|
764
764
|
|
|
765
765
|
var pc;
|
|
766
766
|
try { pc = decodeExt(cert, OID.policyConstraints); }
|
|
767
|
-
catch (e) {
|
|
767
|
+
catch (e) { guard.list.append(checks,{ name: "policyConstraints", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
768
768
|
var pcCritErr = requireCriticalExt(pc, "policyConstraints", checks);
|
|
769
769
|
if (pcCritErr) return { fatal: true, error: pcCritErr };
|
|
770
770
|
if (pc) {
|
|
@@ -773,31 +773,31 @@ function prepareNext(state, cert, i, checks) {
|
|
|
773
773
|
}
|
|
774
774
|
var iap;
|
|
775
775
|
try { iap = decodeExt(cert, OID.inhibitAnyPolicy); }
|
|
776
|
-
catch (e) {
|
|
776
|
+
catch (e) { guard.list.append(checks,{ name: "inhibitAnyPolicy", ok: false, code: "path/bad-policy" }); return { fatal: true, error: e }; }
|
|
777
777
|
var iapCritErr = requireCriticalExt(iap, "inhibitAnyPolicy", checks);
|
|
778
778
|
if (iapCritErr) return { fatal: true, error: iapCritErr };
|
|
779
779
|
if (iap && iap.value < state.inhibitAnyPolicy) state.inhibitAnyPolicy = iap.value;
|
|
780
780
|
|
|
781
781
|
var bc;
|
|
782
782
|
try { bc = decodeExt(cert, OID.basicConstraints); }
|
|
783
|
-
catch (e) {
|
|
783
|
+
catch (e) { guard.list.append(checks,{ name: "basicConstraints", ok: false, code: "path/bad-basic-constraints" }); return { fatal: true, error: e }; }
|
|
784
784
|
if (!bc || bc.value.cA !== true) {
|
|
785
|
-
|
|
785
|
+
guard.list.append(checks,{ name: "basicConstraints", ok: false, code: "path/not-a-ca" });
|
|
786
786
|
return { fatal: true, error: E("path/not-a-ca", "intermediate certificate is not a CA (basicConstraints cA is not TRUE, RFC 5280 6.1.4(k))") };
|
|
787
787
|
}
|
|
788
788
|
var bcCritErr = requireCriticalExt(bc, "basicConstraints", checks);
|
|
789
789
|
if (bcCritErr) return { fatal: true, error: bcCritErr };
|
|
790
790
|
if (!isSelfIssued) {
|
|
791
|
-
if (state.maxPathLength <= 0) {
|
|
791
|
+
if (state.maxPathLength <= 0) { guard.list.append(checks,{ name: "pathLength", ok: false, code: "path/path-length-exceeded" }); return { fatal: true, error: E("path/path-length-exceeded", "certification path is longer than the CA path-length constraint allows") }; }
|
|
792
792
|
state.maxPathLength--;
|
|
793
793
|
}
|
|
794
794
|
if (bc.value.pathLenConstraint !== null && bc.value.pathLenConstraint < state.maxPathLength) state.maxPathLength = bc.value.pathLenConstraint;
|
|
795
795
|
|
|
796
796
|
var ku;
|
|
797
797
|
try { ku = decodeExt(cert, OID.keyUsage); }
|
|
798
|
-
catch (e) {
|
|
798
|
+
catch (e) { guard.list.append(checks,{ name: "keyUsage", ok: false, code: "path/bad-key-usage" }); return { fatal: true, error: e }; }
|
|
799
799
|
if (ku && ku.value.keyCertSign !== true) {
|
|
800
|
-
|
|
800
|
+
guard.list.append(checks,{ name: "keyUsage", ok: false, code: "path/missing-key-cert-sign" });
|
|
801
801
|
return { fatal: true, error: E("path/missing-key-cert-sign", "CA certificate keyUsage does not assert keyCertSign (RFC 5280 6.1.4(n))") };
|
|
802
802
|
}
|
|
803
803
|
return { fatal: false };
|
|
@@ -806,8 +806,8 @@ function prepareNext(state, cert, i, checks) {
|
|
|
806
806
|
function applyPolicyMappings(state, mappings, i) {
|
|
807
807
|
var depth = i;
|
|
808
808
|
if (state.policyMapping > 0) {
|
|
809
|
-
var mappedFrom =
|
|
810
|
-
mappings.forEach(function (m) { (mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || []
|
|
809
|
+
var mappedFrom = intrinsic.create(null);
|
|
810
|
+
mappings.forEach(function (m) { guard.list.append(mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || [], m.subjectDomainPolicy); });
|
|
811
811
|
var depthI = leavesAt(state.validPolicyTree, depth);
|
|
812
812
|
var anyNodes = depthI.filter(function (nd) { return nd.validPolicy === OID.anyPolicy; });
|
|
813
813
|
Object.keys(mappedFrom).forEach(function (idp) {
|
|
@@ -821,7 +821,7 @@ function applyPolicyMappings(state, mappings, i) {
|
|
|
821
821
|
}
|
|
822
822
|
});
|
|
823
823
|
} else {
|
|
824
|
-
var mappedSet =
|
|
824
|
+
var mappedSet = intrinsic.create(null);
|
|
825
825
|
mappings.forEach(function (m) { mappedSet[m.issuerDomainPolicy] = true; });
|
|
826
826
|
if (!state.validPolicyTree) return;
|
|
827
827
|
leavesAt(state.validPolicyTree, depth).forEach(function (node) {
|
|
@@ -835,7 +835,7 @@ function applyPolicyMappings(state, mappings, i) {
|
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
var TARGET_UNPROCESSED_IF_CRITICAL =
|
|
838
|
+
var TARGET_UNPROCESSED_IF_CRITICAL = intrinsic.create(null);
|
|
839
839
|
TARGET_UNPROCESSED_IF_CRITICAL[OID.policyMappings] = true;
|
|
840
840
|
Object.freeze(TARGET_UNPROCESSED_IF_CRITICAL);
|
|
841
841
|
|
|
@@ -939,13 +939,13 @@ function validateCriticalExtensionStructure(cert) {
|
|
|
939
939
|
* });
|
|
940
940
|
* res.valid; // boolean; res.results[0].checks carries the per-check codes
|
|
941
941
|
*/
|
|
942
|
-
var _VALIDATE_OPTS = {
|
|
942
|
+
var _VALIDATE_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
943
943
|
checkPurpose: 1, ctLogList: 1, ctPolicy: 1, historicalMode: 1, initialAnyPolicyInhibit: 1,
|
|
944
944
|
initialExcludedSubtrees: 1, initialExplicitPolicy: 1, initialPermittedSubtrees: 1,
|
|
945
945
|
initialPolicyMappingInhibit: 1, maxPathCerts: 1, maxPolicyNodes: 1, requireRevocation: 1,
|
|
946
946
|
requiredEku: 1, revocationChecker: 1, softFail: 1, time: 1, trustAnchors: 1,
|
|
947
947
|
userInitialPolicySet: 1, verifier: 1
|
|
948
|
-
};
|
|
948
|
+
});
|
|
949
949
|
|
|
950
950
|
var OID_SCT_LIST = oid.byName("signedCertificateTimestampList");
|
|
951
951
|
async function ctGateCheck(leaf, certs, anchor, ctLogList, ctPolicy, n, at) {
|
|
@@ -953,14 +953,14 @@ async function ctGateCheck(leaf, certs, anchor, ctLogList, ctPolicy, n, at) {
|
|
|
953
953
|
for (var i = 0; i < exts.length; i++) {
|
|
954
954
|
if (exts[i] && (exts[i].oid === OID_SCT_LIST || exts[i].name === "signedCertificateTimestampList")) { sctExt = exts[i]; break; }
|
|
955
955
|
}
|
|
956
|
-
if (sctExt == null) return { name: "ct", ok: false, code: "path/ct-required" };
|
|
956
|
+
if (sctExt == null) return guard.verdict.of({ name: "ct", ok: false, code: "path/ct-required" });
|
|
957
957
|
var issuer = n >= 2 ? certs[n - 2] : { subjectPublicKeyInfo: { bytes: anchor.publicKey } };
|
|
958
958
|
var entry, list;
|
|
959
959
|
try {
|
|
960
960
|
entry = ct.x509CertEntry(leaf, issuer);
|
|
961
961
|
list = ct.parseSctList(sctExt.value);
|
|
962
962
|
} catch (e) {
|
|
963
|
-
return { name: "ct", ok: false, code: pathCode(e, "path/ct-required") };
|
|
963
|
+
return guard.verdict.of({ name: "ct", ok: false, code: pathCode(e, "path/ct-required") });
|
|
964
964
|
}
|
|
965
965
|
var verdict;
|
|
966
966
|
try {
|
|
@@ -970,7 +970,7 @@ async function ctGateCheck(leaf, certs, anchor, ctLogList, ctPolicy, n, at) {
|
|
|
970
970
|
if (e && e.isPkiError) throw E("path/bad-input", "validate: the CT policy or log-list is invalid (" + (e.code || "ct/bad-input") + "): " + e.message);
|
|
971
971
|
throw e;
|
|
972
972
|
}
|
|
973
|
-
return { name: "ct", ok: verdict.policyOk === true, code: verdict.policyOk ? undefined : "path/ct-policy-not-met" };
|
|
973
|
+
return guard.verdict.of({ name: "ct", ok: verdict.policyOk === true, code: verdict.policyOk ? undefined : "path/ct-policy-not-met" });
|
|
974
974
|
}
|
|
975
975
|
|
|
976
976
|
async function validate(path, opts) {
|
|
@@ -1015,7 +1015,7 @@ async function validate(path, opts) {
|
|
|
1015
1015
|
var exclRawOnce = opts.initialExcludedSubtrees;
|
|
1016
1016
|
var singleOpts = function (a) {
|
|
1017
1017
|
var keys = intrinsic.ownKeys(opts);
|
|
1018
|
-
var o =
|
|
1018
|
+
var o = intrinsic.create(null);
|
|
1019
1019
|
for (var ki = 0; ki < keys.length; ki++) { o[keys[ki]] = opts[keys[ki]]; }
|
|
1020
1020
|
o.trustAnchors = a;
|
|
1021
1021
|
if (permRawOnce != null) o.initialPermittedSubtrees = callerPermitted;
|
|
@@ -1080,18 +1080,18 @@ async function validate(path, opts) {
|
|
|
1080
1080
|
} else {
|
|
1081
1081
|
sigRes = await builtinVerify(state, cert);
|
|
1082
1082
|
}
|
|
1083
|
-
|
|
1083
|
+
guard.list.append(checks, { name: "signature", ok: sigRes.ok, code: sigRes.ok ? undefined : (sigRes.code || "path/bad-signature") });
|
|
1084
1084
|
if (!sigRes.ok) failed = true;
|
|
1085
1085
|
|
|
1086
1086
|
if (compositeSig.COMPOSITE_ALGS[cert.subjectPublicKeyInfo.algorithm.oid]) {
|
|
1087
1087
|
var cku = compositeKeyUsageCheck(cert);
|
|
1088
|
-
|
|
1088
|
+
guard.list.append(checks,{ name: "compositeKeyUsage", ok: cku.ok, code: cku.ok ? undefined : cku.code });
|
|
1089
1089
|
if (!cku.ok) failed = true;
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
1092
1092
|
if (ML_KEM_OIDS[cert.subjectPublicKeyInfo.algorithm.oid]) {
|
|
1093
1093
|
var kku = kemKeyUsageCheck(cert);
|
|
1094
|
-
|
|
1094
|
+
guard.list.append(checks,{ name: "kemKeyUsage", ok: kku.ok, code: kku.ok ? undefined : kku.code });
|
|
1095
1095
|
if (!kku.ok) failed = true;
|
|
1096
1096
|
}
|
|
1097
1097
|
|
|
@@ -1099,20 +1099,20 @@ async function validate(path, opts) {
|
|
|
1099
1099
|
var vOk = true, vCode;
|
|
1100
1100
|
if (t < guard.time.instantOf(cert.validity.notBefore)) { vOk = false; vCode = "path/not-yet-valid"; }
|
|
1101
1101
|
else if (t > guard.time.instantOf(cert.validity.notAfter)) { vOk = false; vCode = "path/expired"; }
|
|
1102
|
-
|
|
1102
|
+
guard.list.append(checks,{ name: "validity", ok: vOk, code: vCode });
|
|
1103
1103
|
if (!vOk) failed = true;
|
|
1104
1104
|
|
|
1105
1105
|
var chainOk;
|
|
1106
1106
|
try { chainOk = dnEqual(cert.issuer.rdns, state.workingIssuerName.rdns); }
|
|
1107
1107
|
catch (_e) { chainOk = false; }
|
|
1108
|
-
|
|
1108
|
+
guard.list.append(checks,{ name: "nameChaining", ok: chainOk === true, code: chainOk === true ? undefined : "path/name-chaining" });
|
|
1109
1109
|
if (chainOk !== true) failed = true;
|
|
1110
1110
|
|
|
1111
1111
|
if (!(selfIssued(cert) && i !== n)) {
|
|
1112
1112
|
var ncRes;
|
|
1113
1113
|
try { ncRes = checkNameConstraints(state, cert); }
|
|
1114
1114
|
catch (e) { ncRes = { ok: false, code: pathCode(e, "path/bad-name-constraints") }; }
|
|
1115
|
-
|
|
1115
|
+
guard.list.append(checks,{ name: "nameConstraints", ok: ncRes.ok, code: ncRes.ok ? undefined : ncRes.code });
|
|
1116
1116
|
if (!ncRes.ok) failed = true;
|
|
1117
1117
|
}
|
|
1118
1118
|
|
|
@@ -1122,33 +1122,33 @@ async function validate(path, opts) {
|
|
|
1122
1122
|
try { rv = await revocationChecker.check(cert, { workingIssuerName: state.workingIssuerName, workingPublicKey: state.workingPublicKey, workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm, issuerCert: issuerCert }, { time: opts.time, historicalMode: opts.historicalMode === true }); }
|
|
1123
1123
|
catch (e) { rv = { status: "error" }; rvError = e; }
|
|
1124
1124
|
var rvStatus = (rv && typeof rv.status === "string") ? rv.status : "unknown";
|
|
1125
|
-
if (rv && rv.status === "good") {
|
|
1126
|
-
else if (rv && rv.status === "revoked") {
|
|
1125
|
+
if (rv && rv.status === "good") { guard.list.append(checks,{ name: "revocation", ok: true, status: "good" }); }
|
|
1126
|
+
else if (rv && rv.status === "revoked") { guard.list.append(checks,{ name: "revocation", ok: false, status: "revoked", code: "path/revoked" }); failed = true; }
|
|
1127
1127
|
else if (rvError) {
|
|
1128
|
-
|
|
1128
|
+
guard.list.append(checks,{ name: "revocation", ok: false, status: "error", code: "path/revocation-checker-error", error: rvError });
|
|
1129
1129
|
revocationUndetermined = true;
|
|
1130
1130
|
failed = true;
|
|
1131
1131
|
}
|
|
1132
1132
|
else if (softFail) {
|
|
1133
|
-
|
|
1133
|
+
guard.list.append(checks,{ name: "revocation", ok: true, status: rvStatus, waived: true });
|
|
1134
1134
|
revocationWaived = true;
|
|
1135
1135
|
} else {
|
|
1136
|
-
|
|
1136
|
+
guard.list.append(checks,{ name: "revocation", ok: false, status: rvStatus, code: "path/revocation-undetermined" });
|
|
1137
1137
|
revocationUndetermined = true;
|
|
1138
1138
|
failed = true;
|
|
1139
1139
|
}
|
|
1140
1140
|
revocationRan = true;
|
|
1141
1141
|
} else if (requireRevocation) {
|
|
1142
|
-
|
|
1142
|
+
guard.list.append(checks,{ name: "revocation", ok: false, code: "path/revocation-undetermined" }); failed = true;
|
|
1143
1143
|
}
|
|
1144
1144
|
|
|
1145
1145
|
var polRes = processPolicies(state, cert, i, checks);
|
|
1146
|
-
if (state._capHit) {
|
|
1146
|
+
if (state._capHit) { guard.list.append(checks,{ name: "policyTree", ok: false, code: "path/policy-tree-cap" }); failed = true; }
|
|
1147
1147
|
else if (polRes.fatal) failed = true;
|
|
1148
1148
|
|
|
1149
1149
|
if (cert.subject.rdns.length === 0) {
|
|
1150
1150
|
var san = findExt(cert, OID.subjectAltName);
|
|
1151
|
-
if (!san || !san.critical) {
|
|
1151
|
+
if (!san || !san.critical) { guard.list.append(checks,{ name: "emptySubject", ok: false, code: "path/empty-subject-no-critical-san" }); failed = true; }
|
|
1152
1152
|
}
|
|
1153
1153
|
|
|
1154
1154
|
if (i !== n) {
|
|
@@ -1160,22 +1160,22 @@ async function validate(path, opts) {
|
|
|
1160
1160
|
if (state.explicitPolicy > 0) state.explicitPolicy--;
|
|
1161
1161
|
var lpc;
|
|
1162
1162
|
try { lpc = decodeExt(cert, OID.policyConstraints); }
|
|
1163
|
-
catch (_e) { lpc = null;
|
|
1163
|
+
catch (_e) { lpc = null; guard.list.append(checks,{ name: "policyConstraints", ok: false, code: "path/bad-policy" }); failed = true; }
|
|
1164
1164
|
if (requireCriticalExt(lpc, "policyConstraints", checks)) failed = true;
|
|
1165
1165
|
if (lpc && lpc.value.requireExplicitPolicy === 0) state.explicitPolicy = 0;
|
|
1166
1166
|
var lpm;
|
|
1167
1167
|
try { lpm = decodeExt(cert, OID.policyMappings); }
|
|
1168
|
-
catch (_e) { lpm = null;
|
|
1168
|
+
catch (_e) { lpm = null; guard.list.append(checks,{ name: "policyMappings", ok: false, code: "path/bad-policy" }); failed = true; }
|
|
1169
1169
|
if (lpm && mapsAnyPolicy(lpm.value)) {
|
|
1170
|
-
|
|
1170
|
+
guard.list.append(checks,{ name: "policyMappings", ok: false, code: "path/bad-policy" }); failed = true;
|
|
1171
1171
|
}
|
|
1172
1172
|
var lnc;
|
|
1173
1173
|
try { lnc = decodeExt(cert, OID.nameConstraints); }
|
|
1174
|
-
catch (e) { lnc = null;
|
|
1174
|
+
catch (e) { lnc = null; guard.list.append(checks,{ name: "nameConstraints", ok: false, code: pathCode(e, "path/bad-name-constraints") }); failed = true; }
|
|
1175
1175
|
if (requireCriticalExt(lnc, "nameConstraints", checks)) failed = true;
|
|
1176
1176
|
var liap;
|
|
1177
1177
|
try { liap = decodeExt(cert, OID.inhibitAnyPolicy); }
|
|
1178
|
-
catch (e) { liap = null;
|
|
1178
|
+
catch (e) { liap = null; guard.list.append(checks,{ name: "inhibitAnyPolicy", ok: false, code: pathCode(e, "path/bad-policy") }); failed = true; }
|
|
1179
1179
|
if (requireCriticalExt(liap, "inhibitAnyPolicy", checks)) failed = true;
|
|
1180
1180
|
if (requiredEku && ekuPurposeFails(cert, requiredEku, checks)) failed = true;
|
|
1181
1181
|
var ta = anchor;
|
|
@@ -1183,25 +1183,25 @@ async function validate(path, opts) {
|
|
|
1183
1183
|
if (distrustDate != null) {
|
|
1184
1184
|
anchorDistrustApplied = true;
|
|
1185
1185
|
if (guard.time.instantOf(cert.validity.notBefore) > guard.time.instantOf(distrustDate)) {
|
|
1186
|
-
|
|
1186
|
+
guard.list.append(checks,{ name: "distrustAfter", ok: false, code: "path/distrusted-after" }); failed = true;
|
|
1187
1187
|
}
|
|
1188
1188
|
}
|
|
1189
1189
|
if (checkPurpose && ta.purposes) {
|
|
1190
1190
|
anchorPurposeApplied = true;
|
|
1191
1191
|
if (!intrinsic.hasOwn(ta.purposes, checkPurpose) || ta.purposes[checkPurpose] !== true) {
|
|
1192
|
-
|
|
1192
|
+
guard.list.append(checks,{ name: "purposeTrust", ok: false, code: "path/purpose-not-trusted" }); failed = true;
|
|
1193
1193
|
}
|
|
1194
1194
|
}
|
|
1195
1195
|
updateWorkingKey(state, cert);
|
|
1196
1196
|
}
|
|
1197
1197
|
|
|
1198
1198
|
var unk = unrecognizedCriticalExtension(cert, i === n);
|
|
1199
|
-
if (unk) {
|
|
1199
|
+
if (unk) { guard.list.append(checks,{ name: "criticalExtensions", ok: false, code: "path/unrecognized-critical-extension" }); failed = true; }
|
|
1200
1200
|
|
|
1201
1201
|
var crit = validateCriticalExtensionStructure(cert);
|
|
1202
|
-
if (crit) {
|
|
1202
|
+
if (crit) { guard.list.append(checks,{ name: "criticalExtensionValue", ok: false, code: crit }); failed = true; }
|
|
1203
1203
|
|
|
1204
|
-
state.results
|
|
1204
|
+
guard.list.append(state.results, { index: idx, checks: checks });
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
1207
|
var ucps = userConstrainedPolicies(state, n);
|
|
@@ -1209,7 +1209,7 @@ async function validate(path, opts) {
|
|
|
1209
1209
|
if (!policyOk) {
|
|
1210
1210
|
var last = state.results[state.results.length - 1];
|
|
1211
1211
|
if (!guard.list.anyMatches(last.checks, function (c) { return c.code === "path/policy-required"; })) {
|
|
1212
|
-
last.checks
|
|
1212
|
+
guard.list.append(last.checks, { name: "policy", ok: false, code: "path/policy-required" });
|
|
1213
1213
|
}
|
|
1214
1214
|
failed = true;
|
|
1215
1215
|
}
|
|
@@ -1218,11 +1218,11 @@ async function validate(path, opts) {
|
|
|
1218
1218
|
var ctCheck = await ctGateCheck(certs[n - 1], certs, anchor, opts.ctLogList, opts.ctPolicy || {}, n, opts.time);
|
|
1219
1219
|
var target = null;
|
|
1220
1220
|
for (var tr = 0; tr < state.results.length; tr++) { if (state.results[tr].index === n - 1) { target = state.results[tr]; break; } }
|
|
1221
|
-
if (target)
|
|
1221
|
+
if (target) guard.list.append(target.checks, ctCheck);
|
|
1222
1222
|
if (!ctCheck.ok) failed = true;
|
|
1223
1223
|
}
|
|
1224
1224
|
|
|
1225
|
-
return {
|
|
1225
|
+
return guard.verdict.of({
|
|
1226
1226
|
valid: !failed,
|
|
1227
1227
|
revocationChecked: !revocationRan ? false
|
|
1228
1228
|
: revocationUndetermined ? "undetermined"
|
|
@@ -1240,7 +1240,7 @@ async function validate(path, opts) {
|
|
|
1240
1240
|
workingPublicKeyParameters: state.workingPublicKeyParameters,
|
|
1241
1241
|
validPolicyTree: treeWithoutParent(state.validPolicyTree),
|
|
1242
1242
|
userConstrainedPolicySet: ucps,
|
|
1243
|
-
};
|
|
1243
|
+
});
|
|
1244
1244
|
}
|
|
1245
1245
|
|
|
1246
1246
|
function userConstrainedPolicies(state, n) {
|
|
@@ -1248,12 +1248,12 @@ function userConstrainedPolicies(state, n) {
|
|
|
1248
1248
|
var uips = state.userInitialPolicySet;
|
|
1249
1249
|
var anyUser = contains(uips, OID.anyPolicy);
|
|
1250
1250
|
var leaves = leavesAt(state.validPolicyTree, n);
|
|
1251
|
-
var explicit =
|
|
1251
|
+
var explicit = intrinsic.create(null), hasAnyLeaf = false;
|
|
1252
1252
|
leaves.forEach(function (node) {
|
|
1253
1253
|
if (node.validPolicy === OID.anyPolicy) hasAnyLeaf = true;
|
|
1254
1254
|
else explicit[node.validPolicy] = true;
|
|
1255
1255
|
});
|
|
1256
|
-
var set =
|
|
1256
|
+
var set = intrinsic.create(null);
|
|
1257
1257
|
Object.keys(explicit).forEach(function (p) { if (anyUser || contains(uips, p)) set[p] = true; });
|
|
1258
1258
|
if (hasAnyLeaf) {
|
|
1259
1259
|
if (anyUser) set[OID.anyPolicy] = true;
|
|
@@ -1379,13 +1379,13 @@ function classifyCrls(parsed) {
|
|
|
1379
1379
|
baseCrlNumber: null,
|
|
1380
1380
|
mergeable: false,
|
|
1381
1381
|
};
|
|
1382
|
-
if (deltaRaw === null) {
|
|
1382
|
+
if (deltaRaw === null) { guard.list.append(completes, rec); continue; }
|
|
1383
1383
|
try {
|
|
1384
1384
|
var n = asn1.read.integer(asn1.decode(deltaRaw));
|
|
1385
1385
|
if (crlNumberWithinBound(n) && deltaCritical) { rec.baseCrlNumber = n; rec.mergeable = true; }
|
|
1386
1386
|
} catch (_e) {
|
|
1387
1387
|
}
|
|
1388
|
-
|
|
1388
|
+
guard.list.append(deltas, rec);
|
|
1389
1389
|
}
|
|
1390
1390
|
return { completes: completes, deltas: deltas };
|
|
1391
1391
|
}
|
|
@@ -1494,7 +1494,7 @@ function crlChecker(crls, opts) {
|
|
|
1494
1494
|
var iku;
|
|
1495
1495
|
try { iku = decodeExt(issuer.issuerCert, OID.keyUsage); }
|
|
1496
1496
|
catch (e) {
|
|
1497
|
-
return { status: "unknown", reason: "the CRL issuer's keyUsage extension is unreadable (" + pathCode(e, "path/bad-key-usage") + "), so its authorization to sign CRLs cannot be verified" };
|
|
1497
|
+
return guard.verdict.of({ status: "unknown", reason: "the CRL issuer's keyUsage extension is unreadable (" + pathCode(e, "path/bad-key-usage") + "), so its authorization to sign CRLs cannot be verified" });
|
|
1498
1498
|
}
|
|
1499
1499
|
if (iku && iku.value.cRLSign !== true) signerAuthorized = false;
|
|
1500
1500
|
}
|
|
@@ -1600,14 +1600,14 @@ function crlChecker(crls, opts) {
|
|
|
1600
1600
|
if (!deltaMergesWith(cand, rec)) continue;
|
|
1601
1601
|
if (!(await gateCrl(cand))) continue;
|
|
1602
1602
|
cand.accounted = true;
|
|
1603
|
-
|
|
1603
|
+
guard.list.append(candidates, cand);
|
|
1604
1604
|
}
|
|
1605
1605
|
chosenDelta = selectDelta(candidates);
|
|
1606
1606
|
}
|
|
1607
1607
|
|
|
1608
1608
|
var status;
|
|
1609
1609
|
if (chosenDelta) {
|
|
1610
|
-
|
|
1610
|
+
guard.list.append(consumedDeltas, chosenDelta);
|
|
1611
1611
|
status = scanCrl(chosenDelta.crl);
|
|
1612
1612
|
if (status === null) status = scanCrl(rec.crl);
|
|
1613
1613
|
} else {
|
|
@@ -1631,20 +1631,20 @@ function crlChecker(crls, opts) {
|
|
|
1631
1631
|
if (certStatus === null) certStatus = dStatus;
|
|
1632
1632
|
}
|
|
1633
1633
|
|
|
1634
|
-
if (releasedByUnmergedDelta) return { status: "unknown", reason: "a delta CRL released this serial from hold; without merging its base CRL the revocation status is undetermined" };
|
|
1634
|
+
if (releasedByUnmergedDelta) return guard.verdict.of({ status: "unknown", reason: "a delta CRL released this serial from hold; without merging its base CRL the revocation status is undetermined" });
|
|
1635
1635
|
if (certStatus !== null) {
|
|
1636
1636
|
var reasonName = constants.NAMES.CRL_REASON[String(certStatus)] || "unspecified";
|
|
1637
|
-
return { status: "revoked", reasonCode: certStatus, reason: "serial listed in a CRL (" + reasonName + ")" };
|
|
1637
|
+
return guard.verdict.of({ status: "revoked", reasonCode: certStatus, reason: "serial listed in a CRL (" + reasonName + ")" });
|
|
1638
1638
|
}
|
|
1639
|
-
if (sawUnmergedDelta) return { status: "unknown", reason: "a delta CRL cannot be combined with any complete CRL held here, so the revocation picture is incomplete" };
|
|
1640
|
-
if ((reasonsMask & ALL_REASONS) === ALL_REASONS) return { status: "good" };
|
|
1639
|
+
if (sawUnmergedDelta) return guard.verdict.of({ status: "unknown", reason: "a delta CRL cannot be combined with any complete CRL held here, so the revocation picture is incomplete" });
|
|
1640
|
+
if ((reasonsMask & ALL_REASONS) === ALL_REASONS) return guard.verdict.of({ status: "good" });
|
|
1641
1641
|
if (certScopeFault) {
|
|
1642
|
-
return { status: "unknown", reason: "no authoritative in-scope CRL covers this certificate; its basicConstraints extension is unreadable (" + certScopeFault + "), so scope-limited CRLs were skipped" };
|
|
1642
|
+
return guard.verdict.of({ status: "unknown", reason: "no authoritative in-scope CRL covers this certificate; its basicConstraints extension is unreadable (" + certScopeFault + "), so scope-limited CRLs were skipped" });
|
|
1643
1643
|
}
|
|
1644
1644
|
if (reasonsMask !== 0) {
|
|
1645
|
-
return { status: "unknown", reason: "the CRLs available cover only some revocation reasons for this certificate; no combination covers all of them" };
|
|
1645
|
+
return guard.verdict.of({ status: "unknown", reason: "the CRLs available cover only some revocation reasons for this certificate; no combination covers all of them" });
|
|
1646
1646
|
}
|
|
1647
|
-
return { status: "unknown", reason: "no authoritative in-scope CRL covers this certificate" };
|
|
1647
|
+
return guard.verdict.of({ status: "unknown", reason: "no authoritative in-scope CRL covers this certificate" });
|
|
1648
1648
|
},
|
|
1649
1649
|
};
|
|
1650
1650
|
}
|
|
@@ -1736,13 +1736,13 @@ function ocspChecker(responses) {
|
|
|
1736
1736
|
var historical = ctx.historicalMode === true;
|
|
1737
1737
|
var issuerNameCandidates = [cert.issuer.bytes];
|
|
1738
1738
|
function addNameCandidate(nm) {
|
|
1739
|
-
if (nm && nm.bytes && !guard.list.anyMatches(issuerNameCandidates, function (e) { return e.equals(nm.bytes); }))
|
|
1739
|
+
if (nm && nm.bytes && !guard.list.anyMatches(issuerNameCandidates, function (e) { return e.equals(nm.bytes); })) guard.list.append(issuerNameCandidates, nm.bytes);
|
|
1740
1740
|
}
|
|
1741
1741
|
if (issuer.issuerCert) addNameCandidate(issuer.issuerCert.subject);
|
|
1742
1742
|
addNameCandidate(issuer.workingIssuerName);
|
|
1743
1743
|
var issuerKeyBits;
|
|
1744
1744
|
try { issuerKeyBits = ocspVerify.ocspKeyValue(issuer.workingPublicKey); }
|
|
1745
|
-
catch (_e) { return { status: "unknown", reason: "the issuer public key could not be read to recompute the OCSP CertID" }; }
|
|
1745
|
+
catch (_e) { return guard.verdict.of({ status: "unknown", reason: "the issuer public key could not be read to recompute the OCSP CertID" }); }
|
|
1746
1746
|
|
|
1747
1747
|
var revokedResult = null;
|
|
1748
1748
|
var sawGood = false;
|
|
@@ -1751,19 +1751,19 @@ function ocspChecker(responses) {
|
|
|
1751
1751
|
for (var k = 0; k < parsed.length; k++) {
|
|
1752
1752
|
var v = await ocspCore.evaluateResponse(parsed[k], cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical);
|
|
1753
1753
|
if (v.revoked && !revokedResult) {
|
|
1754
|
-
revokedResult = { status: "revoked", revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason };
|
|
1754
|
+
revokedResult = guard.verdict.of({ status: "revoked", revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason });
|
|
1755
1755
|
}
|
|
1756
1756
|
if (v.sawGood) sawGood = true;
|
|
1757
1757
|
if (v.sawUnknownStatus) sawUnknownStatus = true;
|
|
1758
1758
|
}
|
|
1759
1759
|
if (revokedResult) return revokedResult;
|
|
1760
|
-
if (sawGood) return { status: "good" };
|
|
1761
|
-
return {
|
|
1760
|
+
if (sawGood) return guard.verdict.of({ status: "good" });
|
|
1761
|
+
return guard.verdict.of({
|
|
1762
1762
|
status: "unknown",
|
|
1763
1763
|
reason: sawUnknownStatus
|
|
1764
1764
|
? "the OCSP responder reported certStatus unknown for this certificate"
|
|
1765
1765
|
: "no authoritative, current, in-scope OCSP response covers this certificate",
|
|
1766
|
-
};
|
|
1766
|
+
});
|
|
1767
1767
|
},
|
|
1768
1768
|
};
|
|
1769
1769
|
}
|
|
@@ -1865,7 +1865,7 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
|
1865
1865
|
if (!sig.ok) return unbound("the target certificate's signature does not verify under the supplied issuer certificate's key");
|
|
1866
1866
|
var issuerCtx = { workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes, workingIssuerName: issuerCert.subject, issuerCert: issuerCert };
|
|
1867
1867
|
var issuerNameCandidates = [cert.issuer.bytes];
|
|
1868
|
-
function add(nm) { if (nm && nm.bytes && !guard.list.anyMatches(issuerNameCandidates, function (e) { return e.equals(nm.bytes); }))
|
|
1868
|
+
function add(nm) { if (nm && nm.bytes && !guard.list.anyMatches(issuerNameCandidates, function (e) { return e.equals(nm.bytes); })) guard.list.append(issuerNameCandidates, nm.bytes); }
|
|
1869
1869
|
add(issuerCert.subject);
|
|
1870
1870
|
var issuerKeyBits;
|
|
1871
1871
|
try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
|
|
@@ -1941,7 +1941,7 @@ function resolvePurposeOpts(opts) {
|
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
1943
|
function _snapshotConstraintMap(m, E, code, who) {
|
|
1944
|
-
var out =
|
|
1944
|
+
var out = intrinsic.create(null);
|
|
1945
1945
|
var proto = intrinsic.getPrototypeOf(m);
|
|
1946
1946
|
if (proto !== null && proto !== intrinsic.ObjectProto) throw E(code, who + ": a trustAnchor constraint map must be a plain object (its own entries only), not one with a custom or cross-realm prototype");
|
|
1947
1947
|
var keys = intrinsic.getOwnPropertyNames(m);
|
|
@@ -2019,9 +2019,9 @@ function _copyDnRdns(rdns, who) {
|
|
|
2019
2019
|
if (!intrinsic.hasOwn(atvOut, "type") || !intrinsic.hasOwn(atvOut, "value")) {
|
|
2020
2020
|
throw E("path/bad-input", who + "[" + i + "][" + j + "] must carry its type and value as own data properties");
|
|
2021
2021
|
}
|
|
2022
|
-
|
|
2022
|
+
guard.list.append(rdnOut, atvOut);
|
|
2023
2023
|
}
|
|
2024
|
-
|
|
2024
|
+
guard.list.append(out, rdnOut);
|
|
2025
2025
|
}
|
|
2026
2026
|
return out;
|
|
2027
2027
|
}
|
|
@@ -2107,7 +2107,7 @@ function _copyAnchorSubtreeList(list, key, who) {
|
|
|
2107
2107
|
}
|
|
2108
2108
|
intrinsic.defineProperty(out, field, { value: fv, enumerable: true, configurable: true, writable: true });
|
|
2109
2109
|
}
|
|
2110
|
-
|
|
2110
|
+
guard.list.append(copied, out);
|
|
2111
2111
|
}
|
|
2112
2112
|
return copied;
|
|
2113
2113
|
}
|
|
@@ -2128,7 +2128,7 @@ function _ownAnchorEntries(list, who) {
|
|
|
2128
2128
|
var d = intrinsic.getOwnPropertyDescriptor(list, i);
|
|
2129
2129
|
if (d === undefined) throw E("path/bad-input", who + ": trustAnchors index " + i + " is a hole or is inherited; every entry must be its own");
|
|
2130
2130
|
if (!intrinsic.hasOwn(d, "value")) throw E("path/bad-input", who + ": trustAnchors entry " + i + " must be a data property, not an accessor");
|
|
2131
|
-
|
|
2131
|
+
guard.list.append(out, d.value);
|
|
2132
2132
|
}
|
|
2133
2133
|
return out;
|
|
2134
2134
|
}
|
|
@@ -2141,7 +2141,7 @@ function _precaptureAnchorNameConstraints(list, who) {
|
|
|
2141
2141
|
var raw = _captureConstraintMap(entry, "nameConstraints", E, "path/bad-input", who);
|
|
2142
2142
|
captured = raw == null ? undefined : _copyAnchorNameConstraints(raw, who);
|
|
2143
2143
|
}
|
|
2144
|
-
|
|
2144
|
+
guard.list.append(out, captured);
|
|
2145
2145
|
}
|
|
2146
2146
|
return out;
|
|
2147
2147
|
}
|
|
@@ -2441,7 +2441,7 @@ function _pushCandidates(frame, scored, stack, counter, down) {
|
|
|
2441
2441
|
if (intrinsic.setHas(frame.keys, candKey)) continue;
|
|
2442
2442
|
var childKeys = new Set(frame.keys);
|
|
2443
2443
|
intrinsic.setAdd(childKeys, candKey);
|
|
2444
|
-
|
|
2444
|
+
guard.list.append(stack, down
|
|
2445
2445
|
? { chain: intrinsic.concat(frame.chain, [cand]), hop: frame.hop + 1, keys: childKeys, anchor: frame.anchor }
|
|
2446
2446
|
: { chain: [cand].concat(frame.chain), hop: frame.hop + 1, keys: childKeys });
|
|
2447
2447
|
}
|
|
@@ -2468,7 +2468,9 @@ function _aiaParseBody(body, contentType, maxCerts) {
|
|
|
2468
2468
|
}
|
|
2469
2469
|
|
|
2470
2470
|
async function _aiaFetchOne(uri, aia) {
|
|
2471
|
-
var res = await
|
|
2471
|
+
var res = await guard.async.invoked(aia.transport,
|
|
2472
|
+
[{ method: "GET", url: uri, tls: aia.tls, timeout: aia.timeout, maxResponseBytes: aia.maxResponseBytes, blockPrivateAddresses: true }],
|
|
2473
|
+
E, "path/bad-input", "opts.transport");
|
|
2472
2474
|
res = res || {};
|
|
2473
2475
|
if (res.status !== 200) throw E("path/aia-status", "an AIA fetch returned HTTP " + res.status + " (only 200 is a cert source; no redirect following)");
|
|
2474
2476
|
var body = guard.bytes.isByteSource(res.body)
|
|
@@ -2476,7 +2478,7 @@ async function _aiaFetchOne(uri, aia) {
|
|
|
2476
2478
|
: Buffer.from(res.body == null ? "" : String(res.body), "latin1");
|
|
2477
2479
|
if (body.length === 0) throw E("path/aia-empty", "an AIA fetch returned an empty body");
|
|
2478
2480
|
if (body.length > aia.maxResponseBytes) throw E("path/aia-too-large", "an AIA response exceeds the " + aia.maxResponseBytes + "-byte cap");
|
|
2479
|
-
var headers =
|
|
2481
|
+
var headers = intrinsic.create(null);
|
|
2480
2482
|
Object.keys(res.headers || {}).forEach(function (k) { headers[k.toLowerCase()] = res.headers[k]; });
|
|
2481
2483
|
return _aiaParseBody(body, headers["content-type"], aia.maxCertsPerResponse);
|
|
2482
2484
|
}
|
|
@@ -2500,7 +2502,7 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
2500
2502
|
if (intrinsic.setHas(aia.fetchedUrls, u.href) || intrinsic.setHas(seenThisCert, u.href)) continue;
|
|
2501
2503
|
if (seenThisCert.size >= aia.maxPerCert) break;
|
|
2502
2504
|
intrinsic.setAdd(seenThisCert, u.href);
|
|
2503
|
-
|
|
2505
|
+
guard.list.append(uris, u.href);
|
|
2504
2506
|
}
|
|
2505
2507
|
var out = [];
|
|
2506
2508
|
for (var k = 0; k < uris.length; k++) {
|
|
@@ -2509,13 +2511,18 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
2509
2511
|
intrinsic.setAdd(aia.fetchedUrls, uris[k]);
|
|
2510
2512
|
aia.fetches += 1;
|
|
2511
2513
|
var certs;
|
|
2514
|
+
/** @internal Every failure of a fetch is skipped and the next URL tried, because AIA is a
|
|
2515
|
+
* best-effort source of issuers and one unreachable host must not fail a build that another URL
|
|
2516
|
+
* can complete. A transport that throws where a rejecting one would only reject is the same
|
|
2517
|
+
* failure reported a different way, so the two are treated alike rather than one of them ending
|
|
2518
|
+
* the build. */
|
|
2512
2519
|
try { certs = await _aiaFetchOne(uris[k], aia); }
|
|
2513
2520
|
catch (_e2) { continue; }
|
|
2514
2521
|
for (var c = 0; c < certs.length; c++) {
|
|
2515
2522
|
var parsed;
|
|
2516
2523
|
try { parsed = coerceCert(certs[c]); }
|
|
2517
2524
|
catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defense-in-depth */ continue; }
|
|
2518
|
-
|
|
2525
|
+
guard.list.append(out, parsed);
|
|
2519
2526
|
}
|
|
2520
2527
|
}
|
|
2521
2528
|
return out;
|
|
@@ -2591,11 +2598,11 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
2591
2598
|
* result.valid; // true when a path to a trust anchor was found and validated
|
|
2592
2599
|
*/
|
|
2593
2600
|
var _BUILD_OPTS = (function () {
|
|
2594
|
-
var o = {
|
|
2601
|
+
var o = intrinsic.assign(intrinsic.create(null), {
|
|
2595
2602
|
aiaTimeout: 1, candidates: 1, direction: 1, fetchAia: 1, intermediates: 1, maxAiaFetches: 1,
|
|
2596
2603
|
maxAiaPerCert: 1, maxCandidatesConsidered: 1, maxDepth: 1, maxPathCerts: 1,
|
|
2597
2604
|
maxResponseBytes: 1, time: 1, tls: 1, transport: 1, trustAnchors: 1, validate: 1
|
|
2598
|
-
};
|
|
2605
|
+
});
|
|
2599
2606
|
Object.keys(_VALIDATE_OPTS).forEach(function (k) { o[k] = 1; });
|
|
2600
2607
|
return o;
|
|
2601
2608
|
})();
|
|
@@ -2604,6 +2611,12 @@ async function build(leaf, opts) {
|
|
|
2604
2611
|
opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "build: opts");
|
|
2605
2612
|
guard.identifier.assertKnownKeys(opts, _BUILD_OPTS, E, "path/bad-input",
|
|
2606
2613
|
"pki.path.build has an unknown option (the anchor option is `trustAnchors`). The unknown option was: ");
|
|
2614
|
+
/** @internal Checked at the door, before any certificate is parsed, and whether or not AIA
|
|
2615
|
+
* fetching is on: supplying a transport that cannot be called is a wiring fault at the moment it
|
|
2616
|
+
* is written. The checked value is the one used below, since reading the option again would ask a
|
|
2617
|
+
* second time and an accessor can answer differently on each read. */
|
|
2618
|
+
var checkedTransport = guard.identifier.assertCallableOption(opts, "transport", E, "path/bad-input",
|
|
2619
|
+
"build: opts.transport", "(request) -> Promise<{ status, headers, body }>");
|
|
2607
2620
|
var leafCert;
|
|
2608
2621
|
try { leafCert = coerceCert(leaf); }
|
|
2609
2622
|
catch (e) { throw E("path/bad-input", "build: the leaf certificate did not parse", e); }
|
|
@@ -2632,7 +2645,7 @@ async function build(leaf, opts) {
|
|
|
2632
2645
|
for (var bai = 0; bai < buildEntries.length; bai++) {
|
|
2633
2646
|
var bAnchor = toAnchor(buildEntries[bai], "build");
|
|
2634
2647
|
if (buildPreNc[bai] !== undefined) intrinsic.defineProperty(bAnchor, "nameConstraints", { value: buildPreNc[bai], enumerable: true, configurable: true, writable: true });
|
|
2635
|
-
|
|
2648
|
+
guard.list.append(anchors, bAnchor);
|
|
2636
2649
|
}
|
|
2637
2650
|
|
|
2638
2651
|
guard.time.assertValid(opts.time, E, "path/bad-input", "build: opts.time (the validity-window check date, forwarded to validate)");
|
|
@@ -2652,13 +2665,10 @@ async function build(leaf, opts) {
|
|
|
2652
2665
|
|
|
2653
2666
|
var aiaCtx = null;
|
|
2654
2667
|
if (opts.fetchAia === true) {
|
|
2655
|
-
if (opts.transport !== undefined && typeof opts.transport !== "function") {
|
|
2656
|
-
throw E("path/bad-input", "build: opts.transport must be a transport function (request) -> Promise<{ status, headers, body }>");
|
|
2657
|
-
}
|
|
2658
2668
|
if (opts.aiaTimeout !== undefined) {
|
|
2659
2669
|
guard.limits.cap(opts.aiaTimeout, "build: opts.aiaTimeout", 1, { E: E, code: "path/bad-input", min: 1, max: httpTransport.MAX_TIMEOUT });
|
|
2660
2670
|
}
|
|
2661
|
-
var aiaTransport =
|
|
2671
|
+
var aiaTransport = checkedTransport || httpTransport.https({ E: E, errPrefix: "path" });
|
|
2662
2672
|
aiaCtx = {
|
|
2663
2673
|
transport: aiaTransport,
|
|
2664
2674
|
transportGuardsAddresses: aiaTransport.blocksPrivateAddresses === true,
|
|
@@ -2704,7 +2714,7 @@ async function build(leaf, opts) {
|
|
|
2704
2714
|
if (uip === OID.anyPolicy) { containsAny = true; break; }
|
|
2705
2715
|
var dupUip = false;
|
|
2706
2716
|
for (var uj = 0; uj < uipsDistinct.length; uj++) { if (uipsDistinct[uj] === uip) { dupUip = true; break; } }
|
|
2707
|
-
if (!dupUip)
|
|
2717
|
+
if (!dupUip) guard.list.append(uipsDistinct, uip);
|
|
2708
2718
|
}
|
|
2709
2719
|
selectByPolicy = !containsAny;
|
|
2710
2720
|
}
|
|
@@ -2713,7 +2723,7 @@ async function build(leaf, opts) {
|
|
|
2713
2723
|
try {
|
|
2714
2724
|
if (direction === "reverse") {
|
|
2715
2725
|
var rstack = [];
|
|
2716
|
-
for (var rsi = 0; rsi < anchors.length; rsi++)
|
|
2726
|
+
for (var rsi = 0; rsi < anchors.length; rsi++) guard.list.append(rstack, { chain: [], hop: 0, keys: new Set([identityKey(leafCert)]), anchor: anchors[rsi] });
|
|
2717
2727
|
while (rstack.length) {
|
|
2718
2728
|
var rframe = rstack.pop();
|
|
2719
2729
|
var frontierCert = rframe.chain.length ? rframe.chain[rframe.chain.length - 1] : null;
|
|
@@ -2727,7 +2737,7 @@ async function build(leaf, opts) {
|
|
|
2727
2737
|
var racc = { valid: true, path: rpath, trustAnchor: rframe.anchor, result: rres };
|
|
2728
2738
|
if (!selectByPolicy) { success = racc; break; }
|
|
2729
2739
|
if (_policyScore(rres, uipsDistinct) >= uipsDistinct.length) { success = racc; break; }
|
|
2730
|
-
|
|
2740
|
+
guard.list.append(accepted, racc);
|
|
2731
2741
|
} else if (!bestFail) bestFail = { path: rpath, trustAnchor: rframe.anchor, result: rres };
|
|
2732
2742
|
}
|
|
2733
2743
|
if (rframe.hop >= maxDepth) continue;
|
|
@@ -2736,7 +2746,7 @@ async function build(leaf, opts) {
|
|
|
2736
2746
|
if (nameMatchSoft(pool[rpi].subject.rdns, rframe.anchor.name.rdns) &&
|
|
2737
2747
|
intrinsic.bufferEquals(pool[rpi].subjectPublicKeyInfo.bytes, rframe.anchor.publicKey)) continue;
|
|
2738
2748
|
if (nameMatchSoft(pool[rpi].issuer.rdns, frontierName)) {
|
|
2739
|
-
|
|
2749
|
+
guard.list.append(rscored, { cand: pool[rpi], score: scoreCandidateReverse(pool[rpi], frontierCert, leafCert, opts.time) });
|
|
2740
2750
|
}
|
|
2741
2751
|
}
|
|
2742
2752
|
considered += _pushCandidates(rframe, rscored, rstack, counter, true);
|
|
@@ -2764,7 +2774,7 @@ async function build(leaf, opts) {
|
|
|
2764
2774
|
var fdk = certDerKey(fetched[fj]);
|
|
2765
2775
|
if (intrinsic.setHas(poolDerKeys, fdk)) continue;
|
|
2766
2776
|
intrinsic.setAdd(poolDerKeys, fdk);
|
|
2767
|
-
if (pool.length < poolCeiling)
|
|
2777
|
+
if (pool.length < poolCeiling) guard.list.append(pool, fetched[fj]);
|
|
2768
2778
|
}
|
|
2769
2779
|
}
|
|
2770
2780
|
fb.fetched = true;
|
|
@@ -2772,7 +2782,7 @@ async function build(leaf, opts) {
|
|
|
2772
2782
|
var fbScored = [];
|
|
2773
2783
|
for (var pj = fb.poolMark; pj < pool.length; pj++) {
|
|
2774
2784
|
if (nameMatchSoft(pool[pj].subject.rdns, fbCur.issuer.rdns)) {
|
|
2775
|
-
|
|
2785
|
+
guard.list.append(fbScored, { cand: pool[pj], score: scoreCandidate(pool[pj], fbAki, anchors, opts.time) });
|
|
2776
2786
|
}
|
|
2777
2787
|
}
|
|
2778
2788
|
fb.poolMark = pool.length;
|
|
@@ -2791,7 +2801,7 @@ async function build(leaf, opts) {
|
|
|
2791
2801
|
var acc = { valid: true, path: frame.chain.slice(), trustAnchor: anchors[ai], result: res };
|
|
2792
2802
|
if (!selectByPolicy) { success = acc; break; }
|
|
2793
2803
|
if (_policyScore(res, uipsDistinct) >= uipsDistinct.length) { success = acc; break; }
|
|
2794
|
-
|
|
2804
|
+
guard.list.append(accepted, acc);
|
|
2795
2805
|
} else if (!bestFail) bestFail = { path: frame.chain.slice(), trustAnchor: anchors[ai], result: res };
|
|
2796
2806
|
}
|
|
2797
2807
|
if (success || frame.hop >= maxDepth) continue;
|
|
@@ -2800,11 +2810,11 @@ async function build(leaf, opts) {
|
|
|
2800
2810
|
var scored = [];
|
|
2801
2811
|
for (var pi = 0; pi < pool.length; pi++) {
|
|
2802
2812
|
if (nameMatchSoft(pool[pi].subject.rdns, current.issuer.rdns)) {
|
|
2803
|
-
|
|
2813
|
+
guard.list.append(scored, { cand: pool[pi], score: scoreCandidate(pool[pi], childAki, anchors, opts.time) });
|
|
2804
2814
|
}
|
|
2805
2815
|
}
|
|
2806
2816
|
if (aiaCtx && frame.hop < maxDepth && aiaCtx.fetches < aiaCtx.maxFetches) {
|
|
2807
|
-
|
|
2817
|
+
guard.list.append(deferredAia, { chain: frame.chain, hop: frame.hop, keys: frame.keys, poolMark: pool.length, seq: deferSeq, fetched: false });
|
|
2808
2818
|
deferSeq += 1;
|
|
2809
2819
|
}
|
|
2810
2820
|
considered += _pushCandidates(frame, scored, stack, counter);
|
|
@@ -2820,11 +2830,11 @@ async function build(leaf, opts) {
|
|
|
2820
2830
|
|
|
2821
2831
|
var aiaFetches = aiaCtx ? aiaCtx.fetches : 0;
|
|
2822
2832
|
if (success) {
|
|
2823
|
-
if (doValidate) return { valid: true, path: success.path, trustAnchor: success.trustAnchor, result: success.result, candidatesConsidered: considered, aiaFetches: aiaFetches };
|
|
2824
|
-
return { path: success.path, trustAnchor: success.trustAnchor, candidatesConsidered: considered, aiaFetches: aiaFetches };
|
|
2833
|
+
if (doValidate) return guard.verdict.of({ valid: true, path: success.path, trustAnchor: success.trustAnchor, result: success.result, candidatesConsidered: considered, aiaFetches: aiaFetches });
|
|
2834
|
+
return guard.verdict.of({ path: success.path, trustAnchor: success.trustAnchor, candidatesConsidered: considered, aiaFetches: aiaFetches });
|
|
2825
2835
|
}
|
|
2826
2836
|
if (anyChainAssembled) {
|
|
2827
|
-
return { valid: false, path: bestFail.path, trustAnchor: bestFail.trustAnchor, result: bestFail.result, candidatesConsidered: considered, aiaFetches: aiaFetches };
|
|
2837
|
+
return guard.verdict.of({ valid: false, path: bestFail.path, trustAnchor: bestFail.trustAnchor, result: bestFail.result, candidatesConsidered: considered, aiaFetches: aiaFetches });
|
|
2828
2838
|
}
|
|
2829
2839
|
throw E("path/no-path", "build: no certification path from the leaf to any configured trust anchor could be assembled");
|
|
2830
2840
|
}
|