@blamejs/pki 0.9.4 → 0.9.6
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 +60 -0
- package/README.md +7 -6
- package/bin/pki.js +110 -34
- package/lib/acme.js +91 -46
- package/lib/attrcert-sign.js +4 -3
- package/lib/cmp-build.js +16 -6
- package/lib/cmp-session.js +19 -3
- package/lib/cmp-verify.js +25 -2
- package/lib/cms-compress.js +3 -3
- package/lib/cms-digest.js +10 -5
- package/lib/cms-encrypt.js +6 -1
- package/lib/cms-sign.js +16 -0
- package/lib/cms-verify.js +32 -14
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +3 -3
- package/lib/csr-sign.js +6 -2
- package/lib/ct.js +16 -3
- package/lib/est.js +67 -31
- package/lib/guard-all.js +2 -0
- package/lib/guard-distrust.js +224 -0
- package/lib/guard-identifier.js +27 -1
- package/lib/guard-json.js +1 -1
- package/lib/http-transport.js +299 -36
- package/lib/jose.js +1 -1
- package/lib/lint.js +67 -7
- package/lib/ocsp-verify.js +9 -3
- package/lib/ocsp.js +16 -4
- package/lib/oid.js +4 -0
- package/lib/path-validate.js +119 -18
- package/lib/pkcs12-build.js +6 -1
- package/lib/pki-build.js +38 -6
- package/lib/possession.js +6 -2
- package/lib/related-cert.js +2 -2
- package/lib/revocation-fetch.js +46 -11
- package/lib/scep.js +39 -14
- package/lib/schema-pkix.js +3 -3
- package/lib/sigstore.js +11 -6
- package/lib/smime.js +32 -16
- package/lib/tls-cert-compress.js +6 -5
- package/lib/trust.js +121 -20
- package/lib/trustanchor-build.js +16 -1
- package/lib/tsp-sign.js +15 -2
- package/lib/webauthn-mds.js +29 -8
- package/lib/webauthn.js +46 -14
- package/lib/x509-sign.js +26 -10
- package/package.json +2 -2
- package/sbom.cdx.json +6 -6
package/lib/pki-build.js
CHANGED
|
@@ -648,6 +648,36 @@ function makeBuilder(ctx) {
|
|
|
648
648
|
if (bc.pathLen != null) pathLen(bc.pathLen);
|
|
649
649
|
guard.identifier.assertKnownKeys(bc, BC_KEYS, E, "bad-input", "unknown basicConstraints field ");
|
|
650
650
|
}
|
|
651
|
+
|
|
652
|
+
/** @internal The criticality a caller states for an extension, read as the boolean each flag
|
|
653
|
+
* documents and resolved to the default an omitted flag selects. RFC 5280 sec. 4.2 fixes
|
|
654
|
+
* keyUsage critical and leaves the other five non-critical, which is why the defaults differ.
|
|
655
|
+
* `basicConstraints` carries its own flag inside the value and is typed by `validateBcSpec`.
|
|
656
|
+
*
|
|
657
|
+
* The flags are RETURNED, so the emitters below read criticality from this record rather than
|
|
658
|
+
* from the caller's spec a second time. A flag read off the spec at the point of emission is a
|
|
659
|
+
* read of a value that was never typed: measured, `extendedKeyUsageCritical: "false"` emitted
|
|
660
|
+
* that extension critical, and an extension a relying party does not recognize as critical is
|
|
661
|
+
* one RFC 5280 sec. 6.1 requires it to reject. */
|
|
662
|
+
var CRITICALITY_FLAGS = [
|
|
663
|
+
["keyUsageCritical", true],
|
|
664
|
+
["extendedKeyUsageCritical", false],
|
|
665
|
+
["certificatePoliciesCritical", false],
|
|
666
|
+
["policyMappingsCritical", false],
|
|
667
|
+
["qcStatementsCritical", false],
|
|
668
|
+
["tlsFeatureCritical", false],
|
|
669
|
+
];
|
|
670
|
+
function criticality(extSpec, label) {
|
|
671
|
+
var out = intrinsic.create(null);
|
|
672
|
+
var where = label || "extensions";
|
|
673
|
+
for (var i = 0; i < CRITICALITY_FLAGS.length; i++) {
|
|
674
|
+
var k = CRITICALITY_FLAGS[i][0];
|
|
675
|
+
out[k] = CRITICALITY_FLAGS[i][1]
|
|
676
|
+
? guard.identifier.booleanOptionDefaultOn(extSpec[k], E, "bad-input", where + "." + k)
|
|
677
|
+
: guard.identifier.booleanOption(extSpec[k], E, "bad-input", where + "." + k);
|
|
678
|
+
}
|
|
679
|
+
return out;
|
|
680
|
+
}
|
|
651
681
|
function extBasicConstraints(spec) {
|
|
652
682
|
var children = [];
|
|
653
683
|
if (spec.cA === true) _push(children,b.boolean(true));
|
|
@@ -1368,16 +1398,17 @@ function makeBuilder(ctx) {
|
|
|
1368
1398
|
guard.identifier.assertKnownKeys(extSpec, REQ_EXT_KEYS, E, "bad-input", function (k) {
|
|
1369
1399
|
return "unknown requested extension " + JSON.stringify(k) + "; pass a pre-encoded Extension DER via the array form for a custom extension";
|
|
1370
1400
|
});
|
|
1401
|
+
var crit = criticality(extSpec, "requested extensions");
|
|
1371
1402
|
var out = [];
|
|
1372
1403
|
if (extSpec.subjectKeyIdentifier != null) {
|
|
1373
1404
|
if (extSpec.subjectKeyIdentifier === true && spki == null) throw E("bad-input", "subjectKeyIdentifier auto-derive (true) requires the public key -- supply a Buffer key id, or include the public key");
|
|
1374
1405
|
_push(out,ext(O("subjectKeyIdentifier"), false, extSki(skiKeyId(extSpec.subjectKeyIdentifier, spki))));
|
|
1375
1406
|
}
|
|
1376
|
-
if (extSpec.keyUsage != null) _push(out,ext(O("keyUsage"),
|
|
1377
|
-
if (extSpec.extendedKeyUsage != null) _push(out,ext(O("extKeyUsage"),
|
|
1407
|
+
if (extSpec.keyUsage != null) _push(out,ext(O("keyUsage"), crit.keyUsageCritical, extKeyUsage(extSpec.keyUsage)));
|
|
1408
|
+
if (extSpec.extendedKeyUsage != null) _push(out,ext(O("extKeyUsage"), crit.extendedKeyUsageCritical, extExtKeyUsage(extSpec.extendedKeyUsage)));
|
|
1378
1409
|
if (extSpec.basicConstraints != null) { validateBcSpec(extSpec.basicConstraints); _push(out,ext(O("basicConstraints"), extSpec.basicConstraints.critical !== false, extBasicConstraints(extSpec.basicConstraints))); }
|
|
1379
1410
|
if (extSpec.subjectAltName != null) _push(out,ext(O("subjectAltName"), false, extSan(extSpec.subjectAltName)));
|
|
1380
|
-
if (extSpec.certificatePolicies != null) _push(out,ext(O("certificatePolicies"),
|
|
1411
|
+
if (extSpec.certificatePolicies != null) _push(out,ext(O("certificatePolicies"), crit.certificatePoliciesCritical, extCertPolicies(extSpec.certificatePolicies)));
|
|
1381
1412
|
/** @internal RFC 5280 sec. 4.2.1.10 fixes this extension critical, in a request as in the certificate. */
|
|
1382
1413
|
if (extSpec.nameConstraints != null) guard.list.append(out, ext(O("nameConstraints"), true, extNameConstraints(extSpec.nameConstraints)));
|
|
1383
1414
|
if (extSpec.authorityInfoAccess != null) guard.list.append(out, ext(O("authorityInfoAccess"), false, extAuthorityInfoAccess(extSpec.authorityInfoAccess)));
|
|
@@ -1385,14 +1416,14 @@ function makeBuilder(ctx) {
|
|
|
1385
1416
|
if (extSpec.freshestCRL != null) guard.list.append(out, ext(O("freshestCRL"), false, extCrlDistributionPoints(extSpec.freshestCRL, "freshestCRL")));
|
|
1386
1417
|
if (extSpec.policyConstraints != null) guard.list.append(out, ext(O("policyConstraints"), true, extPolicyConstraints(extSpec.policyConstraints)));
|
|
1387
1418
|
if (extSpec.inhibitAnyPolicy != null) guard.list.append(out, ext(O("inhibitAnyPolicy"), true, extInhibitAnyPolicy(extSpec.inhibitAnyPolicy)));
|
|
1388
|
-
if (extSpec.policyMappings != null) guard.list.append(out, ext(O("policyMappings"),
|
|
1419
|
+
if (extSpec.policyMappings != null) guard.list.append(out, ext(O("policyMappings"), crit.policyMappingsCritical, extPolicyMappings(extSpec.policyMappings)));
|
|
1389
1420
|
if (extSpec.issuerAltName != null) guard.list.append(out, ext(O("issuerAltName"), false, encodeGeneralNames(reqDenseArray(extSpec.issuerAltName, "issuerAltName"))));
|
|
1390
|
-
if (extSpec.qcStatements != null) guard.list.append(out, ext(O("qcStatements"),
|
|
1421
|
+
if (extSpec.qcStatements != null) guard.list.append(out, ext(O("qcStatements"), crit.qcStatementsCritical, extQcStatements(extSpec.qcStatements)));
|
|
1391
1422
|
/** @internal RFC 7633 sec. 4.2.1 lets a request state the features the server supports, and
|
|
1392
1423
|
* sec. 4.3.2 asks a server to generate it, so a feature declaration is the requester's wish and
|
|
1393
1424
|
* belongs in this table. The criticality knob is here too, because a request carries the
|
|
1394
1425
|
* Extension shape whole and the CA decides what it issues. */
|
|
1395
|
-
if (extSpec.tlsFeature != null) guard.list.append(out, ext(O("tlsFeature"),
|
|
1426
|
+
if (extSpec.tlsFeature != null) guard.list.append(out, ext(O("tlsFeature"), crit.tlsFeatureCritical, extTlsFeature(extSpec.tlsFeature)));
|
|
1396
1427
|
if (extSpec.msCertificateTemplate != null) guard.list.append(out, ext(O("msCertificateTemplate"), false, extMsCertificateTemplate(extSpec.msCertificateTemplate)));
|
|
1397
1428
|
if (extSpec.msEnrollCertType != null) guard.list.append(out, ext(O("msEnrollCertType"), false, extMsEnrollCertType(extSpec.msEnrollCertType)));
|
|
1398
1429
|
if (extSpec.msApplicationPolicies != null) guard.list.append(out, ext(O("msApplicationPolicies"), false, extCertPolicies(extSpec.msApplicationPolicies)));
|
|
@@ -1457,6 +1488,7 @@ function makeBuilder(ctx) {
|
|
|
1457
1488
|
encodeGeneralNames: encodeGeneralNames, serialInteger: serialInteger, timeDer: timeDer,
|
|
1458
1489
|
requestedExtensions: requestedExtensions,
|
|
1459
1490
|
extKeyUsage: extKeyUsage, extExtKeyUsage: extExtKeyUsage, validateBcSpec: validateBcSpec,
|
|
1491
|
+
criticality: criticality,
|
|
1460
1492
|
extBasicConstraints: extBasicConstraints, pathLen: pathLen, extSki: extSki, extAki: extAki, AKI_KEYS: AKI_KEYS,
|
|
1461
1493
|
extSan: extSan, extCertPolicies: extCertPolicies, ext: ext,
|
|
1462
1494
|
extSubjectInfoAccess: extSubjectInfoAccess, extSubjectDirectoryAttributes: extSubjectDirectoryAttributes,
|
package/lib/possession.js
CHANGED
|
@@ -118,7 +118,7 @@ var STATEMENT = schema.seq([
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
var KNOWN_VERIFY_OPTS = _assign(_create(null), {
|
|
121
|
-
trustAnchors: 1, time: 1, signatureCertificate: 1, intermediates: 1,
|
|
121
|
+
trustAnchors: 1, time: 1, signatureCertificate: 1, intermediates: 1, distrustedKeys: 1,
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
/** @internal Whether a certificate is the one an IssuerAndSerialNumber names. Both halves are compared,
|
|
@@ -345,6 +345,7 @@ var _isSignatureOnlyKey = pkix.signatureOnlyKeyOid(NS);
|
|
|
345
345
|
* that is present and is not a usable `Date` is `possession/bad-input`.
|
|
346
346
|
* @opts signatureCertificate The signature certificate, for a statement that omits it; refused when it is not the one `signer` names.
|
|
347
347
|
* @opts intermediates The rest of the signature certificate's path, when an intermediate CA issued it rather than an anchor directly. Ordered as `pki.path.validate` takes a path, from the certificate nearest an anchor down to the issuer of the signature certificate, which this verb appends. Omitted, the signature certificate is validated on its own, which succeeds only when an anchor issued it.
|
|
348
|
+
* @opts distrustedKeys SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or 32 bytes each). A listed key on the anchor, an intermediate or the signature certificate makes `pathValidated` and `valid` false. A malformed list is `possession/bad-input`.
|
|
348
349
|
*
|
|
349
350
|
* @example
|
|
350
351
|
* var kem = await pki.key.generate({ name: "ML-KEM-768" });
|
|
@@ -382,6 +383,7 @@ function _verifyRequest(request, opts) {
|
|
|
382
383
|
guard.identifier.assertKnownKeys(opts, KNOWN_VERIFY_OPTS, _err, "possession/bad-input",
|
|
383
384
|
"pki.possession.verifyRequest has an unknown option: ");
|
|
384
385
|
var o = guard.identifier.snapshotOptions(opts, KNOWN_VERIFY_OPTS);
|
|
386
|
+
var distrusted = guard.distrust.keys(o.distrustedKeys, _err, "possession/bad-input", "opts.distrustedKeys");
|
|
385
387
|
if (o.trustAnchors == null || !_isArray(o.trustAnchors) || o.trustAnchors.length === 0) {
|
|
386
388
|
throw _err("possession/bad-input",
|
|
387
389
|
"opts.trustAnchors is required: RFC 9883 sec. 4 makes certification path validation of the signature certificate a MUST, and a verdict that skipped it would not answer the question asked");
|
|
@@ -522,8 +524,10 @@ function _verifyRequest(request, opts) {
|
|
|
522
524
|
"authorize a certification request (RFC 5280 sec. 4.2.1.3)");
|
|
523
525
|
}
|
|
524
526
|
|
|
527
|
+
var pathOpts = { trustAnchors: o.trustAnchors, time: at };
|
|
528
|
+
if (distrusted !== null) pathOpts.distrustedKeys = distrusted;
|
|
525
529
|
return intrinsic.chain(
|
|
526
|
-
_promiseResolve(_Promise, _validatePath(chain,
|
|
530
|
+
_promiseResolve(_Promise, _validatePath(chain, pathOpts)),
|
|
527
531
|
function (pathResult) {
|
|
528
532
|
var pathValidated = pathResult.valid === true;
|
|
529
533
|
if (!pathValidated) _push(reasons, "the signature certificate's certification path is not valid, and RFC 9883 sec. 4 makes that a MUST");
|
package/lib/related-cert.js
CHANGED
|
@@ -208,7 +208,7 @@ var KNOWN_VERIFY_OPTS = _assign(_create(null), { digestAlgorithm: 1, signatureAl
|
|
|
208
208
|
* an attacker's key verifies forever without it, and a verifier that cannot perform a MUST check must not
|
|
209
209
|
* answer true. `at` is the instant judged against, for a caller reading a request recorded earlier. */
|
|
210
210
|
function _assertFresh(requestTime, o) {
|
|
211
|
-
if (o.maxAge
|
|
211
|
+
if (o.maxAge == null) {
|
|
212
212
|
throw _err("relatedcert/no-freshness-policy",
|
|
213
213
|
"pki.relatedCert.verifyRequest needs opts.maxAge, the seconds a requestTime may be old: RFC 9763 " +
|
|
214
214
|
"sec. 3.2 requires the certification authority to check that it is sufficiently fresh and leaves " +
|
|
@@ -217,7 +217,7 @@ function _assertFresh(requestTime, o) {
|
|
|
217
217
|
}
|
|
218
218
|
var maxAge = guard.range.authoredInteger(o.maxAge, _err, "relatedcert/bad-input", "opts.maxAge");
|
|
219
219
|
if (maxAge < 0n) throw _err("relatedcert/bad-input", "opts.maxAge must not be negative");
|
|
220
|
-
var at = o.at
|
|
220
|
+
var at = o.at == null ? new intrinsic.Date() : o.at;
|
|
221
221
|
if (!guard.time.isDate(at)) throw _err("relatedcert/bad-input", "opts.at must be a Date");
|
|
222
222
|
guard.time.assertValid(at, _err, "relatedcert/bad-input", "opts.at");
|
|
223
223
|
var atMs = guard.time.instantOf(at);
|
package/lib/revocation-fetch.js
CHANGED
|
@@ -398,7 +398,8 @@ async function _tryOcsp(state, cert, issuer, ctx, candidate) {
|
|
|
398
398
|
hit = got.body;
|
|
399
399
|
}
|
|
400
400
|
var v = await state.verifyOcspResponse(hit, cert, issuerCert, ctx.time,
|
|
401
|
-
{ historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined
|
|
401
|
+
{ historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined,
|
|
402
|
+
distrustedKeys: state.distrusted });
|
|
402
403
|
/** @internal `valid` on that verdict means the certificate is good, not that the response was
|
|
403
404
|
* acceptable, so reading it as acceptance would turn a `revoked` answer into an undetermined
|
|
404
405
|
* one that `opts.softFail` could then waive. Acceptance is the three conditions the gate
|
|
@@ -670,7 +671,9 @@ function _deltaUrlsFor(parsedCrl, state) {
|
|
|
670
671
|
* of the same shape. A transport that does not declare that it blocks
|
|
671
672
|
* private addresses is refused a hostname.
|
|
672
673
|
* @opts allowPlaintextHttp Whether an `http` URL is fetched. Default `true`.
|
|
673
|
-
* @opts useDeltas Whether a freshestCRL delta is fetched and merged. Default `true`.
|
|
674
|
+
* @opts useDeltas Whether a freshestCRL delta is fetched and merged. Default `true`. A
|
|
675
|
+
* value other than `true`, `false`, `null` or an absent option is refused
|
|
676
|
+
* `path/bad-input`.
|
|
674
677
|
* @opts nonce Whether an OCSP request carries a 32-octet nonce. `true` or `false`,
|
|
675
678
|
* and anything else is `path/bad-input` rather than the default, since a
|
|
676
679
|
* control read as off is one an operator asked for and did not get.
|
|
@@ -693,6 +696,21 @@ function _deltaUrlsFor(parsedCrl, state) {
|
|
|
693
696
|
* var checker = pki.path.fetchingChecker({ transport: pki.transport.https({}) });
|
|
694
697
|
* console.log(typeof checker.check);
|
|
695
698
|
*/
|
|
699
|
+
/** @internal The TLS policy every fetch is sent with, copied once at the door, with its distrust list
|
|
700
|
+
* typed there. A fetch fault becomes an undetermined verdict, so a malformed list that reached the
|
|
701
|
+
* transport would be reported as a responder that did not answer. */
|
|
702
|
+
function _tlsSnapshot(tls) {
|
|
703
|
+
if (tls == null) return tls;
|
|
704
|
+
guard.identifier.assertKnownKeys(tls, httpTransport.TLS_KEYS, E, "path/bad-input", "fetchingChecker: opts.tls has an unknown option ");
|
|
705
|
+
/** @internal Each setting the transport reads is taken by value, whether the bag holds it or
|
|
706
|
+
* inherits it, which is how the transport itself reads the bag. */
|
|
707
|
+
var copy = guard.identifier.pickSettings(tls, intrinsic.keys(httpTransport.TLS_KEYS));
|
|
708
|
+
if (copy.distrustedKeys != null) {
|
|
709
|
+
copy.distrustedKeys = guard.distrust.keys(copy.distrustedKeys, E, "path/bad-input", "fetchingChecker: opts.tls.distrustedKeys");
|
|
710
|
+
}
|
|
711
|
+
return copy;
|
|
712
|
+
}
|
|
713
|
+
|
|
696
714
|
function fetchingChecker(opts) {
|
|
697
715
|
opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "fetchingChecker: opts");
|
|
698
716
|
guard.identifier.assertKnownKeys(opts, CHECKER_OPTS, E, "path/bad-input",
|
|
@@ -701,6 +719,7 @@ function fetchingChecker(opts) {
|
|
|
701
719
|
* accessor twice, so the transport that passed the door is the transport that gets called. */
|
|
702
720
|
var transport = guard.identifier.assertCallable(opts.transport, E, "path/bad-input",
|
|
703
721
|
"fetchingChecker: opts.transport", TRANSPORT_SHAPE);
|
|
722
|
+
// allow:caller-option-presence-tested-against-undefined -- absence selects the permissive default, so a null must not reach it
|
|
704
723
|
var allowPlaintextHttp = opts.allowPlaintextHttp === undefined ? true : opts.allowPlaintextHttp === true;
|
|
705
724
|
var cfg = {
|
|
706
725
|
transport: transport,
|
|
@@ -709,9 +728,13 @@ function fetchingChecker(opts) {
|
|
|
709
728
|
* door is the value the rule is applied on. */
|
|
710
729
|
net: intrinsic.freeze({ allowHttp: allowPlaintextHttp, guardsAddresses: _declaresAddressGuard(transport) }),
|
|
711
730
|
allowPlaintextHttp: allowPlaintextHttp,
|
|
712
|
-
|
|
731
|
+
/** @internal Read the way `nonce` on the next line has been read since it was written: the
|
|
732
|
+
* comparison this replaces answered for a value of any other type by fetching the deltas the
|
|
733
|
+
* caller declined. */
|
|
734
|
+
useDeltas: guard.identifier.booleanOptionDefaultOn(opts.useDeltas, E, "path/bad-input",
|
|
735
|
+
"fetchingChecker: opts.useDeltas"),
|
|
713
736
|
nonce: _booleanOption(opts.nonce, "fetchingChecker: opts.nonce"),
|
|
714
|
-
tls: opts.tls,
|
|
737
|
+
tls: _tlsSnapshot(opts.tls),
|
|
715
738
|
maxFetches: guard.limits.cap(opts.maxFetches, "fetchingChecker: opts.maxFetches", LIMITS.REVOCATION_MAX_FETCHES,
|
|
716
739
|
{ E: E, code: "path/bad-input", min: 1, max: LIMITS.REVOCATION_MAX_FETCHES }),
|
|
717
740
|
maxPerCert: guard.limits.cap(opts.maxPerCert, "fetchingChecker: opts.maxPerCert", LIMITS.REVOCATION_MAX_PER_CERT,
|
|
@@ -751,10 +774,17 @@ function fetchingChecker(opts) {
|
|
|
751
774
|
|
|
752
775
|
return {
|
|
753
776
|
check: async function (cert, issuer, ctx) {
|
|
777
|
+
/** @internal Typed before any location is decoded or fetched: a malformed list is the
|
|
778
|
+
* caller's fault and is thrown, where a fetch fault below becomes an undetermined verdict. */
|
|
779
|
+
var distrusted = guard.distrust.keys(ctx == null ? undefined : ctx.distrustedKeys, E, "path/bad-input",
|
|
780
|
+
"fetchingChecker: ctx.distrustedKeys");
|
|
781
|
+
if (guard.distrust.has(distrusted, issuer.workingPublicKey)) {
|
|
782
|
+
return _unknown("the issuer's key is listed in distrustedKeys, so nothing it signs is an answer", [], []);
|
|
783
|
+
}
|
|
754
784
|
var notes = [], skips = [];
|
|
755
785
|
var budget = budgetFor(ctx == null ? null : ctx.run);
|
|
756
786
|
var state = intrinsic.assign(intrinsic.create(null), cfg, {
|
|
757
|
-
notes: notes, cache: cache, crlCache: crlCache,
|
|
787
|
+
notes: notes, cache: cache, crlCache: crlCache, distrusted: distrusted,
|
|
758
788
|
deadline: budget.deadline,
|
|
759
789
|
fetches: budget.fetches,
|
|
760
790
|
urls: new intrinsic.Set(),
|
|
@@ -781,7 +811,9 @@ function fetchingChecker(opts) {
|
|
|
781
811
|
* number of retries. A responder that answers `revoked` or `good` ends it; anything else
|
|
782
812
|
* falls through, because sec. 5 of RFC 6960 makes the CRL the fallback and never an
|
|
783
813
|
* assumption of good standing. */
|
|
784
|
-
|
|
814
|
+
/** @internal Each source keeps its own last failure, so an undetermined verdict names the
|
|
815
|
+
* responder's fault and the CRL's. They usually have different causes. */
|
|
816
|
+
var ocspReason = null, crlReason = null;
|
|
785
817
|
for (var a = 0; a <= cfg.retries && ocspCands.length > 0; a++) {
|
|
786
818
|
for (var o = 0; o < ocspCands.length; o++) {
|
|
787
819
|
if (!_takeUrl(state, ocspCands[o].url)) {
|
|
@@ -791,18 +823,21 @@ function fetchingChecker(opts) {
|
|
|
791
823
|
try {
|
|
792
824
|
var got = await _tryOcsp(state, cert, issuer, ctx, ocspCands[o]);
|
|
793
825
|
if (got.status === "good" || got.status === "revoked") return _verdict(got, notes, skips);
|
|
794
|
-
|
|
795
|
-
} catch (e) {
|
|
826
|
+
ocspReason = got.reason + " reported " + got.status;
|
|
827
|
+
} catch (e) { ocspReason = _say(e); }
|
|
796
828
|
}
|
|
797
829
|
}
|
|
798
830
|
if (crlCands.length > 0) {
|
|
799
831
|
try {
|
|
800
832
|
var viaCrl = await _tryCrl(state, cert, issuer, ctx, crlCands, certDeltas);
|
|
801
833
|
if (viaCrl.status === "good" || viaCrl.status === "revoked") return _verdict(viaCrl, notes, skips);
|
|
802
|
-
|
|
803
|
-
} catch (e2) {
|
|
834
|
+
crlReason = viaCrl.reason;
|
|
835
|
+
} catch (e2) { crlReason = _say(e2); }
|
|
804
836
|
}
|
|
805
|
-
|
|
837
|
+
var why = [];
|
|
838
|
+
if (ocspReason !== null) _append(why, "OCSP: " + ocspReason);
|
|
839
|
+
if (crlReason !== null) _append(why, "CRL: " + crlReason);
|
|
840
|
+
return _unknown(why.length ? intrinsic.join(why, "; ") : "no revocation source answered", notes, skips);
|
|
806
841
|
},
|
|
807
842
|
};
|
|
808
843
|
}
|
package/lib/scep.js
CHANGED
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
* the message layer, transport-agnostic; a live SCEP CA is driven over
|
|
29
29
|
* pki.transport separately.
|
|
30
30
|
*
|
|
31
|
+
* Every verb that reaches a CA accepts the same transport bounds, listed on
|
|
32
|
+
* each of them. One of those bounds is a switch: `allowCrossOriginRedirect`
|
|
33
|
+
* opts in to following a cross-origin redirect on an unsafe method. It is a
|
|
34
|
+
* boolean, and a value other than `true`, `false`, `null` or an absent option
|
|
35
|
+
* is refused `scep/bad-input` before the first request goes out.
|
|
36
|
+
*
|
|
31
37
|
* @card
|
|
32
38
|
* Build and read SCEP (RFC 8894) messages: a PKCSReq, RenewalReq, CertPoll,
|
|
33
39
|
* GetCert, or GetCRL request, or a CA-side CertRep response, wrapped in the
|
|
@@ -683,10 +689,25 @@ function _responseLen(body) {
|
|
|
683
689
|
if (Buffer.isBuffer(body)) return guard.bytes.lengthOf(body);
|
|
684
690
|
return guard.bytes.lengthOf(guard.bytes.source(body, ScepError, "scep/bad-response", "the response body"));
|
|
685
691
|
}
|
|
686
|
-
/** @internal The settings this client forwards to its transport.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
692
|
+
/** @internal The settings this client forwards to its transport. The distrust list is typed here, and
|
|
693
|
+
* the frozen copy is the one every hop carries. A setting not on the list is refused. */
|
|
694
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
|
|
695
|
+
var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
|
|
696
|
+
function _distrustOf(value) {
|
|
697
|
+
return guard.distrust.keys(value, _err, "scep/bad-input", "opts.tls.distrustedKeys");
|
|
698
|
+
}
|
|
699
|
+
function _tlsForRequest(tls) {
|
|
700
|
+
guard.identifier.assertKnownKeys(tls, TLS_KNOWN, _err, "scep/bad-input", "unknown opts.tls setting ");
|
|
701
|
+
var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
|
|
702
|
+
t.distrustedKeys = _distrustOf(t.distrustedKeys);
|
|
703
|
+
return t;
|
|
704
|
+
}
|
|
705
|
+
/** @internal A verb that reaches the transport only after an await copies the caller's tls at its door,
|
|
706
|
+
* with the distrust list typed there, so a later change to the caller's array does not reach the request.
|
|
707
|
+
* Each setting is read the way `_tlsForRequest` reads it, a prototype included. */
|
|
708
|
+
function _snapshotTls(tls) {
|
|
709
|
+
if (tls == null) return tls;
|
|
710
|
+
return _tlsForRequest(tls);
|
|
690
711
|
}
|
|
691
712
|
function _redirectTarget(current, location, method, allowCrossOrigin) {
|
|
692
713
|
if (location == null || _trim(String(location)) === "") throw _err("scep/bad-response", "a redirect response carried no Location header");
|
|
@@ -730,8 +751,12 @@ function _pkiRequest(method, opUrl, reqMsg, clientOpts) {
|
|
|
730
751
|
function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
731
752
|
var redirects = 0, curMethod = method, curBody = body, initialOrigin = _urlOrigin(url);
|
|
732
753
|
var proxySnap = _snapshotProxy(opts.proxy);
|
|
733
|
-
var tlsBase = _tlsForRequest(opts);
|
|
734
|
-
|
|
754
|
+
var tlsBase = _tlsForRequest(opts.tls);
|
|
755
|
+
/** @internal Read as the boolean it documents, on the one path every network verb here takes and
|
|
756
|
+
* before the first request. Taken by truthiness, `allowCrossOriginRedirect: "false"` ALLOWED the
|
|
757
|
+
* cross-origin redirect on an unsafe method that the check below exists to refuse. */
|
|
758
|
+
var allowCrossOrigin = guard.identifier.booleanOption(opts.allowCrossOriginRedirect, _err,
|
|
759
|
+
"scep/bad-input", "opts.allowCrossOriginRedirect");
|
|
735
760
|
function _hopTls(current) {
|
|
736
761
|
var t = tlsBase;
|
|
737
762
|
if (_urlOrigin(current) !== initialOrigin) {
|
|
@@ -858,7 +883,7 @@ function _assertStrongProfile(caps, profile) {
|
|
|
858
883
|
* - `transport` -- an injected transport (defaults to `pki.transport.https`); the request is a GET with no body.
|
|
859
884
|
* - `expectSCEPStandard` -- refuse (`scep/weak-profile`) if the response does not advertise `SCEPStandard`. Takes `true`, `false`, `null` or an absent option; any other value is refused `scep/bad-input`.
|
|
860
885
|
* - `requireStrongProfile` -- refuse unless AES + SHA-256 + POSTPKIOperation are all advertised. Read the same way.
|
|
861
|
-
* - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
886
|
+
* - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
862
887
|
* @example
|
|
863
888
|
* // requires: scepTransport -- an injected transport (omit to use pki.transport.https)
|
|
864
889
|
* var caps = await pki.scep.getCACaps("http://ca.example/scep", { transport: scepTransport });
|
|
@@ -942,7 +967,7 @@ function _normalizeFingerprint(fp, algName) {
|
|
|
942
967
|
* @opts
|
|
943
968
|
* - `expectedFingerprint` -- a hex string or Buffer; a returned certificate must hash to it, or the response is refused.
|
|
944
969
|
* - `fingerprintAlgorithm` -- the hash for the fingerprint (default "sha256").
|
|
945
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
970
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
946
971
|
* @example
|
|
947
972
|
* // requires: scepCaCertTransport -- an injected transport returning a single CA certificate
|
|
948
973
|
* var ca = await pki.scep.getCACert("http://ca.example/scep", { transport: scepCaCertTransport });
|
|
@@ -998,7 +1023,7 @@ async function getCACert(baseUrl, opts) {
|
|
|
998
1023
|
*
|
|
999
1024
|
* @opts
|
|
1000
1025
|
* - `caCertificate` -- the current SCEP CA certificate DER; the response is authenticated against its key (required).
|
|
1001
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1026
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1002
1027
|
* @example
|
|
1003
1028
|
* // requires: caCertDer -- the current CA certificate (from getCACert), scepTransport
|
|
1004
1029
|
* var next = await pki.scep.getNextCACert("http://ca.example/scep", { caCertificate: caCertDer, transport: scepTransport });
|
|
@@ -1104,7 +1129,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1104
1129
|
var recipientKey = _captureRecipientKey(opts.recipientKey);
|
|
1105
1130
|
var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
|
|
1106
1131
|
var clientOpts = {
|
|
1107
|
-
tls:
|
|
1132
|
+
tls: _snapshotTls(opts.tls),
|
|
1108
1133
|
maxRedirects: opts.maxRedirects,
|
|
1109
1134
|
allowCrossOriginRedirect: opts.allowCrossOriginRedirect,
|
|
1110
1135
|
timeout: opts.timeout,
|
|
@@ -1177,7 +1202,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1177
1202
|
* - `sleep` -- an injectable sleeper `(ms) -> Promise` for the Retry-After wait (default a real timer).
|
|
1178
1203
|
* - `onRetryAfter` -- an optional observer `(delaySeconds) -> void` called before each poll sleep.
|
|
1179
1204
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1180
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1205
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1181
1206
|
* @example
|
|
1182
1207
|
* // requires: scepCsr, caCertDer, clientCertDer, clientKeyPkcs8, scepTransport
|
|
1183
1208
|
* var out = await pki.scep.enroll("http://ca.example/scep", { csr: scepCsr, caCert: caCertDer,
|
|
@@ -1255,7 +1280,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1255
1280
|
var recipientKey = _captureRecipientKey(opts.recipientKey);
|
|
1256
1281
|
var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
|
|
1257
1282
|
var want = _resolveQueryTarget(opts);
|
|
1258
|
-
var clientOpts = { tls:
|
|
1283
|
+
var clientOpts = { tls: _snapshotTls(opts.tls), maxRedirects: opts.maxRedirects, allowCrossOriginRedirect: opts.allowCrossOriginRedirect, timeout: opts.timeout, maxResponseBytes: opts.maxResponseBytes, proxy: _snapshotProxy(opts.proxy) };
|
|
1259
1284
|
if (checkedTransport !== undefined) clientOpts.transport = checkedTransport;
|
|
1260
1285
|
var senderNonce = guard.crypto.randomBytes(16);
|
|
1261
1286
|
var transactionId = opts.transactionId != null ? opts.transactionId : _defaultTransactionId();
|
|
@@ -1295,7 +1320,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
|
|
|
1295
1320
|
* - `responderCert` -- the certificate to authenticate the CA response signature against (defaults to `caCert`).
|
|
1296
1321
|
* - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
|
|
1297
1322
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1298
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1323
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1299
1324
|
* @example
|
|
1300
1325
|
* // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
|
|
1301
1326
|
* var out = await pki.scep.getCert("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
|
|
@@ -1344,7 +1369,7 @@ async function getCert(baseUrl, opts) {
|
|
|
1344
1369
|
* - `responderCert` -- the certificate to authenticate the CA response against (defaults to `caCert`).
|
|
1345
1370
|
* - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
|
|
1346
1371
|
* - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
|
|
1347
|
-
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
|
|
1372
|
+
* - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
|
|
1348
1373
|
* @example
|
|
1349
1374
|
* // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
|
|
1350
1375
|
* var out = await pki.scep.getCrl("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
|
package/lib/schema-pkix.js
CHANGED
|
@@ -1557,7 +1557,7 @@ function _extensionRow(ext, opts, index, state, decoded, code, profile) {
|
|
|
1557
1557
|
row.value = ext.value;
|
|
1558
1558
|
row.scope = opts.scope;
|
|
1559
1559
|
row.index = index;
|
|
1560
|
-
row.containerIndex = opts.containerIndex
|
|
1560
|
+
row.containerIndex = opts.containerIndex == null ? null : opts.containerIndex;
|
|
1561
1561
|
row.state = state;
|
|
1562
1562
|
row.decoded = decoded;
|
|
1563
1563
|
row.code = code;
|
|
@@ -1754,7 +1754,7 @@ function extensionDoor(spec) {
|
|
|
1754
1754
|
}
|
|
1755
1755
|
|
|
1756
1756
|
function decodeExtension(ext, callerOpts) {
|
|
1757
|
-
var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope
|
|
1757
|
+
var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope == null);
|
|
1758
1758
|
var key = read.scope === undefined ? spec.defaultScope : read.scope;
|
|
1759
1759
|
if (typeof key !== "string" || !_hasOwn(spec.scopes, key)) {
|
|
1760
1760
|
throw E(badInput, "pki.schema." + spec.key + ".decodeExtension needs opts.scope naming which " +
|
|
@@ -2617,7 +2617,7 @@ function spki(ns, opts) {
|
|
|
2617
2617
|
|
|
2618
2618
|
function attribute(ns, opts) {
|
|
2619
2619
|
opts = opts || _create(null);
|
|
2620
|
-
var minValues = opts.minValues
|
|
2620
|
+
var minValues = opts.minValues == null ? 1 : opts.minValues;
|
|
2621
2621
|
return schema.seq([
|
|
2622
2622
|
schema.field("type", schema.oidLeaf()),
|
|
2623
2623
|
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: minValues, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
package/lib/sigstore.js
CHANGED
|
@@ -1244,7 +1244,7 @@ async function _verifyRekorEntry(te, rekorKeys, leafDer, contentType, arm, paylo
|
|
|
1244
1244
|
* be checked. A token that does not verify is passed over rather than refused: it is a statement from
|
|
1245
1245
|
* an authority this caller did not pin, which says nothing either way. With no token verifying, the
|
|
1246
1246
|
* caller is left with no attested instant and the verdict says so. */
|
|
1247
|
-
async function _timestampInstants(vm, sigBytes, tsaRoots) {
|
|
1247
|
+
async function _timestampInstants(vm, sigBytes, tsaRoots, distrusted) {
|
|
1248
1248
|
if (!tsaRoots) return null;
|
|
1249
1249
|
var anchors = _normTsaAnchors(tsaRoots);
|
|
1250
1250
|
if (!anchors.length) {
|
|
@@ -1271,7 +1271,7 @@ async function _timestampInstants(vm, sigBytes, tsaRoots) {
|
|
|
1271
1271
|
? _b64(t.signedTimestamp, "rfc3161Timestamps[" + i + "].signedTimestamp") : null;
|
|
1272
1272
|
if (der === null) continue;
|
|
1273
1273
|
var res;
|
|
1274
|
-
try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors }); }
|
|
1274
|
+
try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors, distrustedKeys: distrusted }); }
|
|
1275
1275
|
catch (_e) { continue; } // allow:swallow-unverified a token this caller cannot check says nothing; the next is tried and a bundle with none verifying is refused for want of an instant
|
|
1276
1276
|
if (!res || res.valid !== true || res.trusted !== true) continue;
|
|
1277
1277
|
var ms = guard.time.instantOf(res.genTime);
|
|
@@ -1402,7 +1402,7 @@ function _ctLogFor(logs, logIdBytes, timeMs) {
|
|
|
1402
1402
|
* here read the caller's array after a yield, so a caller could add an anchor while verification was
|
|
1403
1403
|
* pending and decide which CA is trusted: an empty list that grows during the timestamp check would have
|
|
1404
1404
|
* passed a chain the call was made without any anchor for. */
|
|
1405
|
-
async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
1405
|
+
async function _verifyChain(leaf, chainDers, roots, timeMs, distrusted) {
|
|
1406
1406
|
var bundleLinks = _map(_arraySlice(chainDers, 1), function (d, i) { return _parseCert(d, "bundle intermediate [" + i + "]"); });
|
|
1407
1407
|
var inWindow = _filter(roots, function (r) { return _inWindow(timeMs, r.validFor); });
|
|
1408
1408
|
if (!inWindow.length) {
|
|
@@ -1449,6 +1449,7 @@ async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
|
1449
1449
|
time: new _Date(timeMs),
|
|
1450
1450
|
historicalMode: true,
|
|
1451
1451
|
requiredEku: ["codeSigning"],
|
|
1452
|
+
distrustedKeys: distrusted,
|
|
1452
1453
|
});
|
|
1453
1454
|
} catch (e) {
|
|
1454
1455
|
if (e && e.code === "path/no-path") {
|
|
@@ -1557,7 +1558,7 @@ function _fulcioExtValue(ext, leafArc) {
|
|
|
1557
1558
|
|
|
1558
1559
|
var IDENTITY_FIELDS = ["san", "issuer", "sourceRepositoryURI"];
|
|
1559
1560
|
var IDENTITY_KEYS = _assign(_create(null), { san: 1, issuer: 1, sourceRepositoryURI: 1 });
|
|
1560
|
-
var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1 });
|
|
1561
|
+
var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1, distrustedKeys: 1 });
|
|
1561
1562
|
|
|
1562
1563
|
/** @internal The bytes a message signature covers, taken once. There is no shape here in which a
|
|
1563
1564
|
* caller hands over a precomputed digest: the arm's own field comment forbids using one as a
|
|
@@ -1702,6 +1703,7 @@ function _statement(payload, payloadType, expectedPredicate) {
|
|
|
1702
1703
|
* predicateType: string, // optional: require this in-toto predicateType (e.g. the SLSA URI); dsse_envelope bundles only
|
|
1703
1704
|
* artifact: BufferSource, // the bytes a message_signature covers; required for that arm, refused for a dsse_envelope
|
|
1704
1705
|
* tsaRoots: Array, // optional timestamp-authority anchors: a DER Buffer or { der } each; required to date a Rekor v2 bundle, which carries no signed entry timestamp
|
|
1706
|
+
* distrustedKeys: Array, // optional SHA-256 digests of SubjectPublicKeyInfo DER (64 hex characters or 32 bytes); a listed key fails the Fulcio chain, or the timestamp authority's chain so its token dates nothing
|
|
1705
1707
|
* time: Date, // optional check-date override (default: the Rekor integratedTime, or an RFC 3161 token's genTime on a v2 entry)
|
|
1706
1708
|
*
|
|
1707
1709
|
* @example
|
|
@@ -1736,6 +1738,9 @@ async function verifyBundle(bundle, opts) {
|
|
|
1736
1738
|
* verification is pending would decide a check the call was made without it. */
|
|
1737
1739
|
var fulcioRoots = _normRoots(o.fulcioRoots || []);
|
|
1738
1740
|
var ctLogs = o.ctLogs === undefined || o.ctLogs === null ? o.ctLogs : _normCtLogs(o.ctLogs);
|
|
1741
|
+
/** @internal Typed here, before the timestamp leg, because that leg passes over a token whose
|
|
1742
|
+
* verification throws, and a malformed list must be reported as the caller's input fault. */
|
|
1743
|
+
var distrusted = guard.distrust.keys(o.distrustedKeys, _err, "sigstore/bad-input", "opts.distrustedKeys");
|
|
1739
1744
|
|
|
1740
1745
|
/** @internal Each option belongs to one arm, and one supplied for the other is refused rather than
|
|
1741
1746
|
* passed over. An ignored option reads as a check that ran: opts.artifact on a DSSE bundle names
|
|
@@ -1796,7 +1801,7 @@ async function verifyBundle(bundle, opts) {
|
|
|
1796
1801
|
* SIGNATURE bytes, which is what dates the ephemeral certificate that made them; a token over the
|
|
1797
1802
|
* payload would date the thing signed rather than the signing. */
|
|
1798
1803
|
var tsaMsList = null;
|
|
1799
|
-
if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots);
|
|
1804
|
+
if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots, distrusted);
|
|
1800
1805
|
var chainDers = _chainDers(vm);
|
|
1801
1806
|
/** @internal A caller-pinned instant is the same for every candidate, so the chain it decides is
|
|
1802
1807
|
* computed on the first candidate and reused rather than rebuilt per entry. Without one the instant
|
|
@@ -1873,7 +1878,7 @@ async function verifyBundle(bundle, opts) {
|
|
|
1873
1878
|
"timestamp authority, or supply opts.time");
|
|
1874
1879
|
}
|
|
1875
1880
|
var candidateChain = pinnedChain !== null ? pinnedChain
|
|
1876
|
-
: await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime);
|
|
1881
|
+
: await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime, distrusted);
|
|
1877
1882
|
if (haveCallerTime) pinnedChain = candidateChain;
|
|
1878
1883
|
var candidateScts = await _verifyEmbeddedScts(leaf, leafDer, candidateChain.issuer, ctLogs, candidateTime, candidateBound);
|
|
1879
1884
|
rekor = candidate;
|