@blamejs/pki 0.8.31 → 0.8.48
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 +393 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/ocsp.js
CHANGED
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
var nodeCrypto = require("crypto");
|
|
27
27
|
var asn1 = require("./asn1-der");
|
|
28
28
|
var pkiBuild = require("./pki-build");
|
|
29
|
+
var _ocspIntrinsic = require("./guard-intrinsic");
|
|
30
|
+
/** @internal The promise a signature self-check is awaited through, built from the constructor captured
|
|
31
|
+
* at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
|
|
32
|
+
* the construction to whatever that binding holds, and one settling without awaiting its argument runs
|
|
33
|
+
* the continuation before the check has finished. */
|
|
34
|
+
var _Promise = _ocspIntrinsic.Promise;
|
|
35
|
+
var _promiseResolve = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseResolve);
|
|
36
|
+
/** @internal A refusal returned as a REJECTED promise is a verdict too: read from the global binding, a
|
|
37
|
+
* replacement whose `reject` resolves instead turns each `ocsp/bad-input` below into a pass. */
|
|
38
|
+
var _promiseReject = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseReject);
|
|
39
|
+
/** @internal The CertID hashes bind a response to the certificate it answers for, and they are awaited
|
|
40
|
+
* as a pair, so a replaced `all` decides what that pair contains. */
|
|
41
|
+
var _promiseAll = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseAll);
|
|
29
42
|
var oid = require("./oid");
|
|
30
43
|
var x509 = require("./schema-x509");
|
|
31
44
|
var ocspSchema = require("./schema-ocsp");
|
|
@@ -100,7 +113,7 @@ var REQUESTER_NONCE_FLOOR = 32, RESPONDER_NONCE_FLOOR = 1;
|
|
|
100
113
|
function _buildCertID(cert, issuer, hashName) {
|
|
101
114
|
var wc = HASH_WC[guard.text.keyOf(hashName)];
|
|
102
115
|
if (!wc) throw _err("ocsp/bad-input", "unsupported CertID hashAlgorithm " + guard.text.showValue(hashName) + " (sha1 / sha256 / sha384 / sha512)");
|
|
103
|
-
return
|
|
116
|
+
return _promiseAll(_Promise, [
|
|
104
117
|
_digest(wc, issuer.subject.bytes),
|
|
105
118
|
_digest(wc, _keyValue(issuer.subjectPublicKeyInfo.bytes)),
|
|
106
119
|
]).then(function (hashes) {
|
|
@@ -189,7 +202,7 @@ function _buildRequest(query, opts) {
|
|
|
189
202
|
requestorNameDer = signerCert.subject.bytes;
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
return
|
|
205
|
+
return _promiseAll(_Promise, queries.map(function (q) {
|
|
193
206
|
if (!q || typeof q !== "object") throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
|
|
194
207
|
guard.identifier.assertKnownKeys(q, _QUERY_KEYS, _err, "ocsp/bad-input", "unknown query field (a query is { cert, issuer, singleRequestExtensions? }): ");
|
|
195
208
|
if (q.cert == null || q.issuer == null) throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
|
|
@@ -227,7 +240,7 @@ function _buildRequest(query, opts) {
|
|
|
227
240
|
return signScheme.signOverTbs(scheme, opts.signer.key, tbsRequest, _signE).then(function (sig) {
|
|
228
241
|
/** @internal The request is held to the same proof a certificate is: the signature verifies
|
|
229
242
|
* against the public key the certificate it carries declares. */
|
|
230
|
-
return
|
|
243
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(tbsRequest, sig, signerCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
|
|
231
244
|
var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
|
|
232
245
|
return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
|
|
233
246
|
});
|
|
@@ -347,7 +360,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
|
|
|
347
360
|
if (!responses.length) throw _err("ocsp/bad-input", "a response MUST include at least one SingleResponse (RFC 6960 sec. 4.2.1)");
|
|
348
361
|
|
|
349
362
|
return _responderID(responseData.responderID, respCert).then(function (ridNode) {
|
|
350
|
-
return
|
|
363
|
+
return _promiseAll(_Promise, responses.map(function (r) { return _buildSingleResponse(r, opts); })).then(function (built) {
|
|
351
364
|
var srNodes = built.map(function (x) { return x.node; });
|
|
352
365
|
var anyNonIssued = built.some(function (x) { return x.nonIssued; });
|
|
353
366
|
/** @internal Sec. 2.2 and 4.4.8: a "revoked" answer for a non-issued certificate MUST carry the
|
|
@@ -366,7 +379,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
|
|
|
366
379
|
return signScheme.signOverTbs(scheme, responderKey, responseDataDer, _signE).then(function (sig) {
|
|
367
380
|
/** @internal The response is held to the same proof a certificate is: the signature verifies
|
|
368
381
|
* against the public key the responder certificate declares. */
|
|
369
|
-
return
|
|
382
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(responseDataDer, sig, respCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
|
|
370
383
|
var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
|
|
371
384
|
if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
|
|
372
385
|
var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
|
|
@@ -406,19 +419,19 @@ function _snapshotSignerKey(key, owned) {
|
|
|
406
419
|
}
|
|
407
420
|
|
|
408
421
|
function _responderID(rid, respCert) {
|
|
409
|
-
if (rid == null || rid === "byName") return
|
|
422
|
+
if (rid == null || rid === "byName") return _promiseResolve(_Promise, b.explicit(1, b.raw(respCert.subject.bytes)));
|
|
410
423
|
if (rid === "byKey") return _digest("SHA-1", _keyValue(respCert.subjectPublicKeyInfo.bytes)).then(function (kh) { return b.explicit(2, b.octetString(kh)); });
|
|
411
424
|
throw _err("ocsp/bad-input", "responderID must be \"byName\" or \"byKey\"");
|
|
412
425
|
}
|
|
413
426
|
function _buildSingleResponse(r, opts) {
|
|
414
427
|
r = r || {};
|
|
415
428
|
var certIdP;
|
|
416
|
-
if (typeof r !== "object") return
|
|
429
|
+
if (typeof r !== "object") return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry must be an object"));
|
|
417
430
|
try { guard.identifier.assertKnownKeys(r, _SINGLE_RESPONSE_KEYS, _err, "ocsp/bad-input", "unknown response entry field (accepted: cert, issuer, certID, hashAlgorithm, status, thisUpdate, nextUpdate, singleExtensions): "); }
|
|
418
|
-
catch (e) { return
|
|
419
|
-
if (r.certID != null) certIdP =
|
|
431
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
432
|
+
if (r.certID != null) certIdP = _promiseResolve(_Promise, b.raw(guard.bytes.view(r.certID, OcspError, "ocsp/bad-input", "a response entry's certID")));
|
|
420
433
|
else if (r.cert != null && r.issuer != null) certIdP = _buildCertID(_certOf(r.cert, "a response certificate"), _certOf(r.issuer, "a response issuer"), r.hashAlgorithm || "sha1");
|
|
421
|
-
else return
|
|
434
|
+
else return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
|
|
422
435
|
/** @internal Every per-entry refusal stays INSIDE this chain. These entries are built through a
|
|
423
436
|
* map into Promise.all, so a synchronous throw here would abort the map before Promise.all
|
|
424
437
|
* attached handlers, and an earlier entry's rejection would be orphaned: the caller catches
|
|
@@ -653,15 +666,15 @@ function verify(response, opts) {
|
|
|
653
666
|
opts = guard.identifier.optionsObject(opts, _err, "ocsp/bad-input", "pki.ocsp.verify options");
|
|
654
667
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "ocsp/bad-input",
|
|
655
668
|
"pki.ocsp.verify has an unknown option (the request nonce to bind against is `requestNonce`): ");
|
|
656
|
-
} catch (e) { return
|
|
657
|
-
if (opts.cert == null || opts.issuer == null) return
|
|
669
|
+
} catch (e) { return _promiseReject(_Promise, e); }
|
|
670
|
+
if (opts.cert == null || opts.issuer == null) return _promiseReject(_Promise, _err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
|
|
658
671
|
var parsed, cert, issuerCert, time;
|
|
659
672
|
try {
|
|
660
673
|
parsed = _responseFromBytes(response);
|
|
661
674
|
cert = _certOf(opts.cert, "the target certificate");
|
|
662
675
|
issuerCert = _certOf(opts.issuer, "the issuer certificate");
|
|
663
676
|
time = opts.time == null ? new Date() : _asDate(opts.time);
|
|
664
|
-
} catch (e) { return
|
|
677
|
+
} catch (e) { return _promiseReject(_Promise, e); }
|
|
665
678
|
/** @internal The nonce rule is applied by the acceptance gate rather than restated here, so this
|
|
666
679
|
* door and every other consumer of that gate answer alike. The bytes are snapshotted here so a
|
|
667
680
|
* fault in them carries this door's own code; a value that is not bytes is passed through for the
|
|
@@ -670,7 +683,7 @@ function verify(response, opts) {
|
|
|
670
683
|
var reqNonce = opts.requestNonce;
|
|
671
684
|
if (guard.bytes.isByteSource(reqNonce)) {
|
|
672
685
|
try { reqNonce = guard.bytes.snapshotSource(reqNonce, OcspError, "ocsp/bad-input", "opts.requestNonce"); }
|
|
673
|
-
catch (e) { return
|
|
686
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
674
687
|
}
|
|
675
688
|
return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time,
|
|
676
689
|
{ historicalMode: opts.historicalMode === true, requestNonce: reqNonce }).then(function (verdict) {
|
package/lib/oid.js
CHANGED
|
@@ -65,11 +65,13 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
65
65
|
policyConstraints: 36, extKeyUsage: 37, freshestCRL: 46,
|
|
66
66
|
inhibitAnyPolicy: 54,
|
|
67
67
|
anyPolicy: [32, 0], anyExtendedKeyUsage: [37, 0],
|
|
68
|
-
targetInformation: 55, noRevAvail: 56
|
|
68
|
+
targetInformation: 55, noRevAvail: 56,
|
|
69
|
+
subjectAltPublicKeyInfo: 72, altSignatureAlgorithm: 73, altSignatureValue: 74 } },
|
|
69
70
|
|
|
70
71
|
pkixAccess: { base: [1, 3, 6, 1, 5, 5, 7, 1], of: {
|
|
71
72
|
authorityInfoAccess: 1, acAuditIdentity: 4, aaControls: 6, acProxying: 10,
|
|
72
73
|
acmeIdentifier: 31,
|
|
74
|
+
relatedCertificate: 36,
|
|
73
75
|
subjectInfoAccess: 11,
|
|
74
76
|
tlsFeature: 24,
|
|
75
77
|
qcStatements: 3,
|
|
@@ -187,6 +189,12 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
187
189
|
|
|
188
190
|
wisun: { base: [1, 3, 6, 1, 4, 1, 45605], of: { fanDevice: 1 } },
|
|
189
191
|
|
|
192
|
+
/** @internal RFC 9883 assigns its attribute on a private-enterprise arc rather than a PKIX one, and
|
|
193
|
+
* uses the SAME value as a PKCS#10 attribute and as a CRMF registration control, so the two names
|
|
194
|
+
* resolve to one dotted OID. `byName` is one-to-one in the other direction, so the attribute name is
|
|
195
|
+
* the one the registry returns for the dotted form. */
|
|
196
|
+
rfc9883: { base: [1, 3, 6, 1, 4, 1, 22112, 2], of: { statementOfPossession: 1 } },
|
|
197
|
+
|
|
190
198
|
ct: { base: [1, 3, 6, 1, 4, 1, 11129, 2, 4], of: {
|
|
191
199
|
signedCertificateTimestampList: 2, precertificatePoison: 3,
|
|
192
200
|
precertificateSigningCert: 4, ocspSignedCertificateTimestampList: 5 } },
|
|
@@ -314,7 +322,9 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
314
322
|
msgSigDigest: 5, contentIdentifier: 7, equivalentLabels: 9, contentReference: 10,
|
|
315
323
|
signingCertificate: 12, timeStampToken: 14, decryptKeyID: 37, signingCertificateV2: 47,
|
|
316
324
|
cmcUnsignedData: 34,
|
|
317
|
-
|
|
325
|
+
binarySigningTime: 46,
|
|
326
|
+
asymmDecryptKeyID: 54, relatedCertRequest: 60,
|
|
327
|
+
certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
|
|
318
328
|
|
|
319
329
|
ansiX962: { base: [1, 2, 840, 10045], of: {
|
|
320
330
|
ecPublicKey: [2, 1], prime192v1: [3, 1, 1], prime256v1: [3, 1, 7],
|
|
@@ -443,8 +453,8 @@ function _assertEncodableArcs(arcs, who) {
|
|
|
443
453
|
if (arcs.length < 2) {
|
|
444
454
|
throw new OidError("oid/bad-input", who + ": an OID must have at least 2 arcs");
|
|
445
455
|
}
|
|
446
|
-
var root =
|
|
447
|
-
var second =
|
|
456
|
+
var root = guard.range.authoredInteger(arcs[0], _oidError, "oid/bad-arc", who + ": the root arc");
|
|
457
|
+
var second = guard.range.authoredInteger(arcs[1], _oidError, "oid/bad-arc", who + ": the second arc");
|
|
448
458
|
if (root > 2n) throw new OidError("oid/bad-arc", who + ": the root arc must be 0, 1, or 2 (X.660)");
|
|
449
459
|
if (root < 2n && second > 39n) throw new OidError("oid/bad-arc", who + ": the second arc must be 0..39 under roots 0 and 1 (X.660)");
|
|
450
460
|
}
|
package/lib/path-validate.js
CHANGED
|
@@ -47,15 +47,26 @@ var csrVerify = require("./csr-verify");
|
|
|
47
47
|
var crmfVerify = require("./crmf-verify");
|
|
48
48
|
var attrcertVerify = require("./attrcert-verify");
|
|
49
49
|
var ocspRequestVerify = require("./ocsp-request-verify");
|
|
50
|
+
var relatedCert = require("./related-cert");
|
|
51
|
+
var altSig = require("./alt-sig");
|
|
52
|
+
var possession = require("./possession");
|
|
50
53
|
var cmpVerify = require("./cmp-verify");
|
|
51
54
|
var cmsVerify = require("./cms-verify");
|
|
52
55
|
var cmpSession = require("./cmp-session");
|
|
53
56
|
var guard = require("./guard-all");
|
|
54
57
|
var intrinsic = require("./guard-intrinsic");
|
|
55
58
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
59
|
+
var _pop = intrinsic.pop;
|
|
60
|
+
var _splice = intrinsic.splice;
|
|
56
61
|
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
57
62
|
var _strIndexOf = intrinsic.uncurry(String.prototype.indexOf);
|
|
58
63
|
var _resolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
64
|
+
var _reject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
65
|
+
/** @internal The promise constructor, captured at load. `promiseResolve` builds its promise from the
|
|
66
|
+
* RECEIVER it is called on, so reading the global binding at the call hands that construction to
|
|
67
|
+
* whatever the binding holds: a descriptor failure answering `false` through a replaced constructor is
|
|
68
|
+
* not a refusal, and this module's verdicts are what a caller acts on. */
|
|
69
|
+
var _Promise = intrinsic.Promise;
|
|
59
70
|
var constants = require("./constants");
|
|
60
71
|
var validator = require("./validator-all");
|
|
61
72
|
var cms = require("./schema-cms");
|
|
@@ -90,6 +101,11 @@ var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
|
|
|
90
101
|
var ANCHOR_SPKI_SCHEMA = pkix.spki(NS);
|
|
91
102
|
|
|
92
103
|
var subtle = webcrypto.webcrypto.subtle;
|
|
104
|
+
/** @internal The two SubtleCrypto operations a certificate signature verdict rests on, captured at
|
|
105
|
+
* load. The object was held and its METHODS fetched per call, which leaves them replaceable: a
|
|
106
|
+
* `verify` answering true accepts a certificate nothing in the path signed. */
|
|
107
|
+
var _subtleImportKey = intrinsic.uncurry(subtle.importKey);
|
|
108
|
+
var _subtleVerify = intrinsic.uncurry(subtle.verify);
|
|
93
109
|
|
|
94
110
|
var OID = intrinsic.assign(intrinsic.create(null), {
|
|
95
111
|
basicConstraints: oid.byName("basicConstraints"),
|
|
@@ -250,18 +266,18 @@ function assertKeyMatchesSigAlg(spkiBytes, sigOid, d) {
|
|
|
250
266
|
function compositeKeyUsageCheck(cert) {
|
|
251
267
|
var ku;
|
|
252
268
|
try { ku = decodeExt(cert, OID.keyUsage); }
|
|
253
|
-
catch (e) { return { ok: false, code: "path/composite-key-usage", error: e }; }
|
|
254
|
-
if (!ku) return { ok: true };
|
|
269
|
+
catch (e) { return guard.verdict.of({ ok: false, code: "path/composite-key-usage", error: e }); }
|
|
270
|
+
if (!ku) return guard.verdict.of({ ok: true });
|
|
255
271
|
var v = ku.value;
|
|
256
272
|
if (v.keyEncipherment || v.dataEncipherment || v.keyAgreement || v.encipherOnly || v.decipherOnly) {
|
|
257
|
-
return { ok: false, code: "path/composite-key-usage",
|
|
258
|
-
error: E("path/composite-key-usage", "a composite ML-DSA key asserts a forbidden encryption/key-establishment keyUsage bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") };
|
|
273
|
+
return guard.verdict.of({ ok: false, code: "path/composite-key-usage",
|
|
274
|
+
error: E("path/composite-key-usage", "a composite ML-DSA key asserts a forbidden encryption/key-establishment keyUsage bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") });
|
|
259
275
|
}
|
|
260
276
|
if (!(v.digitalSignature || v.nonRepudiation || v.keyCertSign || v.cRLSign)) {
|
|
261
|
-
return { ok: false, code: "path/composite-key-usage",
|
|
262
|
-
error: E("path/composite-key-usage", "a composite ML-DSA key's keyUsage asserts no signature bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") };
|
|
277
|
+
return guard.verdict.of({ ok: false, code: "path/composite-key-usage",
|
|
278
|
+
error: E("path/composite-key-usage", "a composite ML-DSA key's keyUsage asserts no signature bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") });
|
|
263
279
|
}
|
|
264
|
-
return { ok: true };
|
|
280
|
+
return guard.verdict.of({ ok: true });
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
var STATEFUL_HBS_OIDS = intrinsic.create(null);
|
|
@@ -284,24 +300,24 @@ function _isCaCert(cert) {
|
|
|
284
300
|
function statefulHbsKeyUsageCheck(cert, isCa) {
|
|
285
301
|
var ku;
|
|
286
302
|
try { ku = decodeExt(cert, OID.keyUsage); }
|
|
287
|
-
catch (e) { return { ok: false, code: "path/hbs-key-usage", error: e }; }
|
|
288
|
-
if (!ku) return { ok: true };
|
|
303
|
+
catch (e) { return guard.verdict.of({ ok: false, code: "path/hbs-key-usage", error: e }); }
|
|
304
|
+
if (!ku) return guard.verdict.of({ ok: true });
|
|
289
305
|
var v = ku.value;
|
|
290
306
|
var where = isCa ? "a certification authority" : "an end entity";
|
|
291
307
|
var allowed = isCa ? "digitalSignature, nonRepudiation, keyCertSign or cRLSign"
|
|
292
308
|
: "digitalSignature, nonRepudiation or cRLSign";
|
|
293
309
|
var asserted = v.digitalSignature || v.nonRepudiation || v.cRLSign || v.keyCertSign;
|
|
294
310
|
if (!asserted) {
|
|
295
|
-
return { ok: false, code: "path/hbs-key-usage",
|
|
296
|
-
error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert at least one of " + allowed + " (RFC 9802 sec. 6)") };
|
|
311
|
+
return guard.verdict.of({ ok: false, code: "path/hbs-key-usage",
|
|
312
|
+
error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert at least one of " + allowed + " (RFC 9802 sec. 6)") });
|
|
297
313
|
}
|
|
298
314
|
var forbidden = v.keyEncipherment || v.dataEncipherment || v.keyAgreement || v.encipherOnly ||
|
|
299
315
|
v.decipherOnly || (!isCa && v.keyCertSign) || (v.reservedBitsSet === true);
|
|
300
316
|
if (forbidden) {
|
|
301
|
-
return { ok: false, code: "path/hbs-key-usage",
|
|
302
|
-
error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert no keyUsage value outside " + allowed + " (RFC 9802 sec. 6)") };
|
|
317
|
+
return guard.verdict.of({ ok: false, code: "path/hbs-key-usage",
|
|
318
|
+
error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert no keyUsage value outside " + allowed + " (RFC 9802 sec. 6)") });
|
|
303
319
|
}
|
|
304
|
-
return { ok: true };
|
|
320
|
+
return guard.verdict.of({ ok: true });
|
|
305
321
|
}
|
|
306
322
|
|
|
307
323
|
var ML_KEM_OIDS = intrinsic.create(null);
|
|
@@ -310,23 +326,23 @@ var ML_KEM_OIDS = intrinsic.create(null);
|
|
|
310
326
|
function kemKeyUsageCheck(cert) {
|
|
311
327
|
var ku;
|
|
312
328
|
try { ku = decodeExt(cert, OID.keyUsage); }
|
|
313
|
-
catch (e) { return { ok: false, code: "path/kem-key-usage", error: e }; }
|
|
314
|
-
if (!ku) return { ok: true };
|
|
329
|
+
catch (e) { return guard.verdict.of({ ok: false, code: "path/kem-key-usage", error: e }); }
|
|
330
|
+
if (!ku) return guard.verdict.of({ ok: true });
|
|
315
331
|
var v = ku.value;
|
|
316
332
|
var others = v.digitalSignature || v.nonRepudiation || v.dataEncipherment || v.keyAgreement ||
|
|
317
333
|
v.keyCertSign || v.cRLSign || v.encipherOnly || v.decipherOnly || (v.reservedBitsSet === true);
|
|
318
334
|
if (!v.keyEncipherment || others) {
|
|
319
|
-
return { ok: false, code: "path/kem-key-usage",
|
|
320
|
-
error: E("path/kem-key-usage", "an ML-KEM key's keyUsage must assert keyEncipherment as the only bit (RFC 9935 sec. 5)") };
|
|
335
|
+
return guard.verdict.of({ ok: false, code: "path/kem-key-usage",
|
|
336
|
+
error: E("path/kem-key-usage", "an ML-KEM key's keyUsage must assert keyEncipherment as the only bit (RFC 9935 sec. 5)") });
|
|
321
337
|
}
|
|
322
|
-
return { ok: true };
|
|
338
|
+
return guard.verdict.of({ ok: true });
|
|
323
339
|
}
|
|
324
340
|
|
|
325
341
|
function _importVerifyKey(spkiBytes, d) {
|
|
326
342
|
try {
|
|
327
343
|
if (d.eddsa) edwardsPoint.validateSpki(spkiBytes, d.eddsa, PathError, "path/bad-signature");
|
|
328
|
-
} catch (e) { return
|
|
329
|
-
return subtle
|
|
344
|
+
} catch (e) { return _reject(_Promise, e); }
|
|
345
|
+
return _subtleImportKey(subtle, "spki", spkiBytes, d.imp, false, ["verify"]);
|
|
330
346
|
}
|
|
331
347
|
|
|
332
348
|
/** @internal The RFC 8554 public key the SubjectPublicKeyInfo carries, and the verdict on a signature over
|
|
@@ -397,23 +413,24 @@ function builtinVerify(state, cert) {
|
|
|
397
413
|
try {
|
|
398
414
|
d = resolveDescriptor(cert.signatureAlgorithm);
|
|
399
415
|
assertKeyMatchesSigAlg(state.workingPublicKey, cert.signatureAlgorithm.oid, d);
|
|
400
|
-
/** @internal Resolving reads `then` off the value, so
|
|
401
|
-
*
|
|
402
|
-
* different object (see guard-verdict).
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
/** @internal Resolving reads `then` off the value, so every verdict here is built with an own one,
|
|
417
|
+
* on the asynchronous arms as much as the synchronous ones: an inherited accessor would otherwise run
|
|
418
|
+
* with the verdict as its receiver and hand the caller a different object (see guard-verdict). The
|
|
419
|
+
* resolution of the `.then` callbacks below is the same window as the `_resolve` calls here. */
|
|
420
|
+
} catch (e) { return _resolve(_Promise, guard.verdict.of({ ok: false, code: pathCode(e, "path/unsupported-algorithm"), error: e })); }
|
|
421
|
+
if (!guard.crypto.isOctetAligned(cert.signatureValue)) return _resolve(_Promise, guard.verdict.of({ ok: false, code: "path/bad-signature" }));
|
|
405
422
|
if (d.composite) return compositeSig.compositeVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature");
|
|
406
|
-
if (d.shbs) return _resolve(
|
|
423
|
+
if (d.shbs) return _resolve(_Promise, _shbsVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes));
|
|
407
424
|
var key;
|
|
408
425
|
return _importVerifyKey(state.workingPublicKey, d).then(function (k) {
|
|
409
426
|
key = k;
|
|
410
427
|
var sig = cert.signatureValue.bytes;
|
|
411
428
|
if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
|
|
412
|
-
return subtle
|
|
429
|
+
return _subtleVerify(subtle, d.verify, key, sig, cert.tbsBytes);
|
|
413
430
|
}).then(function (ok) {
|
|
414
|
-
return { ok: ok === true };
|
|
431
|
+
return guard.verdict.of({ ok: ok === true });
|
|
415
432
|
}, function (e) {
|
|
416
|
-
return { ok: false, code: pathCode(e, "path/bad-signature"), error: e };
|
|
433
|
+
return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-signature"), error: e });
|
|
417
434
|
});
|
|
418
435
|
}
|
|
419
436
|
|
|
@@ -596,8 +613,8 @@ function checkNameConstraints(state, cert) {
|
|
|
596
613
|
var ex = state.excludedSubtrees[e];
|
|
597
614
|
for (var i = 0; i < forms.length; i++) {
|
|
598
615
|
var m = nameMatchesConstraint(forms[i].tag, forms[i].value, { tagNumber: ex.tag, value: ex.base }, true);
|
|
599
|
-
if (m === true) return { ok: false, code: "path/name-constraint-excluded" };
|
|
600
|
-
if (m === "unsupported") return { ok: false, code: "path/name-constraint-unsupported" };
|
|
616
|
+
if (m === true) return guard.verdict.of({ ok: false, code: "path/name-constraint-excluded" });
|
|
617
|
+
if (m === "unsupported") return guard.verdict.of({ ok: false, code: "path/name-constraint-unsupported" });
|
|
601
618
|
}
|
|
602
619
|
}
|
|
603
620
|
for (var f = 0; f < forms.length; f++) {
|
|
@@ -611,10 +628,10 @@ function checkNameConstraints(state, cert) {
|
|
|
611
628
|
if (r === true) permitted = true;
|
|
612
629
|
else if (r === "unsupported") unsupported = true;
|
|
613
630
|
});
|
|
614
|
-
if (!permitted) return { ok: false, code: unsupported ? "path/name-constraint-unsupported" : "path/name-constraint-not-permitted" };
|
|
631
|
+
if (!permitted) return guard.verdict.of({ ok: false, code: unsupported ? "path/name-constraint-unsupported" : "path/name-constraint-not-permitted" });
|
|
615
632
|
}
|
|
616
633
|
}
|
|
617
|
-
return { ok: true };
|
|
634
|
+
return guard.verdict.of({ ok: true });
|
|
618
635
|
}
|
|
619
636
|
|
|
620
637
|
function absorbNameConstraints(state, decoded) {
|
|
@@ -683,11 +700,11 @@ function addPolicyNode(state, depth, validPolicy, qualifiers, expectedPolicySet,
|
|
|
683
700
|
function removePolicyNode(state, node) {
|
|
684
701
|
node.parents.forEach(function (p) {
|
|
685
702
|
var idx = p.children.indexOf(node);
|
|
686
|
-
if (idx !== -1) p.children
|
|
703
|
+
if (idx !== -1) _splice(p.children, idx, 1);
|
|
687
704
|
});
|
|
688
705
|
node.children.forEach(function (c) {
|
|
689
706
|
var idx = c.parents.indexOf(node);
|
|
690
|
-
if (idx !== -1) c.parents
|
|
707
|
+
if (idx !== -1) _splice(c.parents, idx, 1);
|
|
691
708
|
});
|
|
692
709
|
node.parents = [];
|
|
693
710
|
var layer = state.policyIndex[node.depth];
|
|
@@ -1156,7 +1173,9 @@ function validateCriticalExtensionStructure(cert) {
|
|
|
1156
1173
|
* `crlChecker` gives the CRLReason integer and `revocationReason` the name `ocspChecker` gives it;
|
|
1157
1174
|
* both are read so the row does not depend on which checker answered. */
|
|
1158
1175
|
function _revocationRow(row, rv) {
|
|
1159
|
-
|
|
1176
|
+
/** @internal The row carries the sentinel from here rather than only from the walk that shields the
|
|
1177
|
+
* finished results, so a row returned to anything that awaits it is shielded on both arms. */
|
|
1178
|
+
if (rv == null) return guard.verdict.shield(row);
|
|
1160
1179
|
var reason = rv.reason;
|
|
1161
1180
|
if (typeof reason === "string" && reason.length > 0) row.reason = reason;
|
|
1162
1181
|
var code = rv.revocationReason;
|
|
@@ -1164,7 +1183,7 @@ function _revocationRow(row, rv) {
|
|
|
1164
1183
|
if (intrinsic.numberIsFinite(code)) row.revocationReason = code;
|
|
1165
1184
|
var at = rv.revocationTime;
|
|
1166
1185
|
if (guard.time.isDate(at)) row.revocationTime = new intrinsic.Date(guard.time.instantOf(at));
|
|
1167
|
-
return row;
|
|
1186
|
+
return guard.verdict.shield(row);
|
|
1168
1187
|
}
|
|
1169
1188
|
|
|
1170
1189
|
/**
|
|
@@ -1333,12 +1352,26 @@ async function validate(path, opts) {
|
|
|
1333
1352
|
* holds its state against this object; the object is unreachable once the validation returns, so
|
|
1334
1353
|
* a checker stays reusable and nothing accumulates between runs. The anchor recursion carries the
|
|
1335
1354
|
* same one, and a caller has no way to supply one, so two validations never share a budget. */
|
|
1336
|
-
|
|
1355
|
+
/** @internal The verifier's `verify` is read ONCE here, in the door, and carried through every anchor
|
|
1356
|
+
* attempt. Read inside `_validate` it was read once per attempt, because a list of trust anchors
|
|
1357
|
+
* whose names all match the first certificate's issuer retries the walk per anchor: measured, an
|
|
1358
|
+
* accessor whose first answer refused both certificates and whose second accepted them returned
|
|
1359
|
+
* `valid: true` over two matching anchors. It is the function that answers whether each signature in
|
|
1360
|
+
* the path verifies, so one reading of it governs the whole call. */
|
|
1361
|
+
var suppliedVerifier = checked.verifier || null;
|
|
1362
|
+
var suppliedVerify = suppliedVerifier === null ? null : suppliedVerifier.verify;
|
|
1363
|
+
if (suppliedVerifier !== null && typeof suppliedVerify !== "function") {
|
|
1364
|
+
throw E("path/bad-input", "validate: opts.verifier must carry a verify(args) function");
|
|
1365
|
+
}
|
|
1366
|
+
var scope = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), {
|
|
1367
|
+
verifier: suppliedVerifier, verify: suppliedVerify,
|
|
1368
|
+
}));
|
|
1369
|
+
return _validate(path, checked, intrinsic.freeze(intrinsic.create(null)), scope);
|
|
1337
1370
|
}
|
|
1338
1371
|
|
|
1339
1372
|
/** @internal The options are the ones the door above checked, and each anchor attempt is handed a
|
|
1340
1373
|
* copy this module built, so the door runs once per call rather than once per anchor. */
|
|
1341
|
-
async function _validate(path, opts, run) {
|
|
1374
|
+
async function _validate(path, opts, run, scope) {
|
|
1342
1375
|
if (opts.ctPolicy != null && opts.ctLogList == null) throw E("path/bad-input", "validate: opts.ctPolicy requires opts.ctLogList (the trusted CT log-list to verify the certificate's SCTs against)");
|
|
1343
1376
|
if (opts.ctLogList != null) {
|
|
1344
1377
|
if (typeof opts.ctLogList !== "object" || opts.ctLogList.byLogId == null) throw E("path/bad-input", "validate: opts.ctLogList must be a pki.ct.parseLogList result");
|
|
@@ -1409,11 +1442,11 @@ async function _validate(path, opts, run) {
|
|
|
1409
1442
|
for (var ai = 0; ai < tuples.length; ai++) {
|
|
1410
1443
|
if (!dnEqual(tuples[ai].name.rdns, certs[0].issuer.rdns)) continue;
|
|
1411
1444
|
anyMatched = true;
|
|
1412
|
-
var oneRes = await _validate(path, singleOpts(tuples[ai]), run);
|
|
1445
|
+
var oneRes = await _validate(path, singleOpts(tuples[ai]), run, scope);
|
|
1413
1446
|
if (oneRes && oneRes.valid) return oneRes;
|
|
1414
1447
|
lastMatched = oneRes;
|
|
1415
1448
|
}
|
|
1416
|
-
return anyMatched ? lastMatched : _validate(path, singleOpts(tuples[0]), run);
|
|
1449
|
+
return anyMatched ? lastMatched : _validate(path, singleOpts(tuples[0]), run, scope);
|
|
1417
1450
|
}
|
|
1418
1451
|
var anchor = toAnchor(anchorList[0], "validate");
|
|
1419
1452
|
guard.time.assertValid(opts.time, E, "path/bad-input", "validate: opts.time (the always-on validity-window check date)");
|
|
@@ -1438,7 +1471,11 @@ async function _validate(path, opts, run) {
|
|
|
1438
1471
|
var state = initialize(certs, opts, seeds, anchor, taConstraints);
|
|
1439
1472
|
state._n = n;
|
|
1440
1473
|
state.requiredEku = requiredEku;
|
|
1441
|
-
|
|
1474
|
+
/** @internal The verifier comes from the per-call scope the door built, never from `opts`: the option
|
|
1475
|
+
* bag is rebuilt per anchor attempt and reading it again would ask the caller's object once per
|
|
1476
|
+
* attempt (see the note in `validate`). */
|
|
1477
|
+
var verifier = scope.verifier;
|
|
1478
|
+
var verifierVerify = scope.verify;
|
|
1442
1479
|
var revocationChecker = opts.revocationChecker || null;
|
|
1443
1480
|
var softFail = opts.softFail === true;
|
|
1444
1481
|
var requireRevocation = opts.requireRevocation === true;
|
|
@@ -1458,18 +1495,18 @@ async function _validate(path, opts, run) {
|
|
|
1458
1495
|
var sigRes;
|
|
1459
1496
|
var weak = _weakIssuerKey(state);
|
|
1460
1497
|
if (weak) {
|
|
1461
|
-
sigRes = { ok: false, code: "path/weak-key", error: weak };
|
|
1498
|
+
sigRes = guard.verdict.of({ ok: false, code: "path/weak-key", error: weak });
|
|
1462
1499
|
} else if (verifier) {
|
|
1463
1500
|
var vv;
|
|
1464
1501
|
try {
|
|
1465
|
-
vv = await
|
|
1502
|
+
vv = await intrinsic.apply(verifierVerify, verifier, [{
|
|
1466
1503
|
cert: cert,
|
|
1467
1504
|
workingPublicKey: state.workingPublicKey,
|
|
1468
1505
|
workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
|
|
1469
1506
|
workingPublicKeyParameters: state.workingPublicKeyParameters,
|
|
1470
|
-
});
|
|
1507
|
+
}]);
|
|
1471
1508
|
} catch (_e) { vv = false; }
|
|
1472
|
-
sigRes = { ok: vv === true };
|
|
1509
|
+
sigRes = guard.verdict.of({ ok: vv === true });
|
|
1473
1510
|
} else {
|
|
1474
1511
|
sigRes = await builtinVerify(state, cert);
|
|
1475
1512
|
}
|
|
@@ -1670,7 +1707,7 @@ async function _validate(path, opts, run) {
|
|
|
1670
1707
|
nameConstraintsApplied: anchorNameConstraintsApplied,
|
|
1671
1708
|
},
|
|
1672
1709
|
path: certs,
|
|
1673
|
-
results: state.results,
|
|
1710
|
+
results: shieldResults(state.results),
|
|
1674
1711
|
workingPublicKey: state.workingPublicKey,
|
|
1675
1712
|
workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
|
|
1676
1713
|
workingPublicKeyParameters: state.workingPublicKeyParameters,
|
|
@@ -1679,6 +1716,22 @@ async function _validate(path, opts, run) {
|
|
|
1679
1716
|
});
|
|
1680
1717
|
}
|
|
1681
1718
|
|
|
1719
|
+
/**
|
|
1720
|
+
* @internal A per-certificate result and every check row inside it is a verdict a caller reads `ok`
|
|
1721
|
+
* off, and the rows are appended from some thirty places, two of them after the result itself is
|
|
1722
|
+
* recorded. The sentinel is applied once over the finished set rather than at each append, so a row
|
|
1723
|
+
* a check written later contributes carries it without being told to. `shield` adds the member in
|
|
1724
|
+
* place, which keeps the row identity the appends and the identity lookup above rely on.
|
|
1725
|
+
*/
|
|
1726
|
+
function shieldResults(results) {
|
|
1727
|
+
for (var r = 0; r < results.length; r++) {
|
|
1728
|
+
var row = results[r];
|
|
1729
|
+
for (var c = 0; c < row.checks.length; c++) guard.verdict.shield(row.checks[c]);
|
|
1730
|
+
guard.verdict.shield(row);
|
|
1731
|
+
}
|
|
1732
|
+
return results;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1682
1735
|
/**
|
|
1683
1736
|
* @internal RFC 9618 sec. 5.5 rewrites RFC 5280 sec. 6.1.5 step (g) over the graph. The set is read
|
|
1684
1737
|
* from the nodes whose only parent is the anyPolicy node, at ANY depth rather than at the deepest
|
|
@@ -1902,6 +1955,14 @@ function selectDelta(candidates) {
|
|
|
1902
1955
|
* typeof checker.check; // "function"
|
|
1903
1956
|
*/
|
|
1904
1957
|
function crlChecker(crls, opts) {
|
|
1958
|
+
/** @internal A LIST, held to being one before it is mapped. An omitted list is "nothing to check" and
|
|
1959
|
+
* stays accepted; anything else that is not an array fell through to `.map`, where an ArrayBuffer or a
|
|
1960
|
+
* plain object raised a bare TypeError naming an internal expression and a single CRL Buffer mapped
|
|
1961
|
+
* over its BYTES and reported the first byte as a malformed CRL. Passing one CRL where a list belongs
|
|
1962
|
+
* is the natural slip, and it is the one that read furthest before failing. */
|
|
1963
|
+
if (crls !== undefined && crls !== null && !intrinsic.isArray(crls)) {
|
|
1964
|
+
throw E("path/bad-input", "crlChecker: crls must be an array of CRLs");
|
|
1965
|
+
}
|
|
1905
1966
|
var parsed = (crls || []).map(function (c, ci) {
|
|
1906
1967
|
return guard.parsed.acceptDerived(c, "crl", crl.parse, E, "path/bad-input",
|
|
1907
1968
|
"crlChecker: crls[" + ci + "]");
|
|
@@ -2101,15 +2162,15 @@ function _verifyWithSpki(sigAlg, rawSig, spkiBytes, tbsBytes) {
|
|
|
2101
2162
|
try {
|
|
2102
2163
|
d = resolveDescriptor(sigAlg);
|
|
2103
2164
|
assertKeyMatchesSigAlg(spkiBytes, sigAlg.oid, d);
|
|
2104
|
-
} catch (_e) { return
|
|
2165
|
+
} catch (_e) { return _resolve(_Promise, false); }
|
|
2105
2166
|
if (d.composite) return compositeSig.compositeVerify(spkiBytes, rawSig, tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature").then(function (r) { return r.ok === true; });
|
|
2106
2167
|
/** @internal RFC 9802 sec. 6 names the CertificateList as well as the Certificate, so a revocation list
|
|
2107
2168
|
* signed with a stateful hash-based key reaches the same engine the certificate path uses. */
|
|
2108
|
-
if (d.shbs) return _resolve(
|
|
2169
|
+
if (d.shbs) return _resolve(_Promise, _shbsVerify(spkiBytes, rawSig, tbsBytes).ok === true);
|
|
2109
2170
|
return _importVerifyKey(spkiBytes, d).then(function (key) {
|
|
2110
2171
|
var sig = rawSig;
|
|
2111
2172
|
if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
|
|
2112
|
-
return subtle
|
|
2173
|
+
return _subtleVerify(subtle, d.verify, key, sig, tbsBytes);
|
|
2113
2174
|
}).then(function (ok) { return ok === true; }, function () { return false; });
|
|
2114
2175
|
}
|
|
2115
2176
|
|
|
@@ -2123,6 +2184,21 @@ attrcertVerify.setEngine(_verifyWithSpki);
|
|
|
2123
2184
|
|
|
2124
2185
|
ocspRequestVerify.setEngine(_verifyWithSpki);
|
|
2125
2186
|
|
|
2187
|
+
/** @internal RFC 9763 sec. 3.1's proof of possession is a signature by the key of a certificate the
|
|
2188
|
+
* request names, so it reaches the same engine the certificate path uses and the same binding of the
|
|
2189
|
+
* advertised algorithm to the key. */
|
|
2190
|
+
relatedCert.setEngine(_verifyWithSpki);
|
|
2191
|
+
|
|
2192
|
+
/** @internal An ITU-T X.509 (2019) clause 9.8 alternative signature is a signature over a certificate
|
|
2193
|
+
* or a CRL by a key in an extension, so it reaches the same engine and the same binding of the
|
|
2194
|
+
* advertised algorithm to the key. */
|
|
2195
|
+
altSig.setEngine(_verifyWithSpki);
|
|
2196
|
+
|
|
2197
|
+
/** @internal RFC 9883 sec. 4 makes certification path validation of the signature certificate a MUST
|
|
2198
|
+
* alongside the signature check, so the possession verb needs the validator as well as the engine and
|
|
2199
|
+
* runs the same `validate` a caller would call rather than holding a second copy of it. */
|
|
2200
|
+
possession.setEngine({ verifyWithSpki: _verifyWithSpki, validate: validate });
|
|
2201
|
+
|
|
2126
2202
|
|
|
2127
2203
|
var ocspCore = ocspVerify.makeOcspVerify({
|
|
2128
2204
|
verifyWithSpki: _verifyWithSpki,
|
|
@@ -2170,6 +2246,10 @@ cmsVerify.setEngine({ build: build, validate: validate, toAnchor: toAnchor,
|
|
|
2170
2246
|
* typeof checker.check; // "function"
|
|
2171
2247
|
*/
|
|
2172
2248
|
function ocspChecker(responses) {
|
|
2249
|
+
/** @internal A LIST, for the reason at `crlChecker`. */
|
|
2250
|
+
if (responses !== undefined && responses !== null && !intrinsic.isArray(responses)) {
|
|
2251
|
+
throw E("path/bad-input", "ocspChecker: responses must be an array of OCSP responses");
|
|
2252
|
+
}
|
|
2173
2253
|
var parsed = (responses || []).map(function (r) { return _ocspFromBytes(r); });
|
|
2174
2254
|
return {
|
|
2175
2255
|
check: async function (cert, issuer, ctx) {
|
|
@@ -2288,7 +2368,7 @@ async function verifyOcspResponse(response, cert, issuerCert, time, opts) {
|
|
|
2288
2368
|
parsedResponse = _ocspFromBytes(response);
|
|
2289
2369
|
subject = coerceCert(cert, "the certificate");
|
|
2290
2370
|
issuer = coerceCert(issuerCert, "the issuer certificate");
|
|
2291
|
-
} catch (e) { return
|
|
2371
|
+
} catch (e) { return _reject(_Promise, e); }
|
|
2292
2372
|
var v = await _verifyOcspParsed(parsedResponse, subject, issuer, time, checkedOpts);
|
|
2293
2373
|
var out = guard.verdict.of(v);
|
|
2294
2374
|
return guard.verdict.of(out, { valid: out.status === "good" });
|
|
@@ -2308,7 +2388,7 @@ function _ocspFromBytes(response) {
|
|
|
2308
2388
|
function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
2309
2389
|
opts = opts || {};
|
|
2310
2390
|
if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
|
|
2311
|
-
return
|
|
2391
|
+
return _reject(_Promise, E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
|
|
2312
2392
|
}
|
|
2313
2393
|
/** @internal Snapshotted in the synchronous prologue: the bytes compared are the bytes the caller
|
|
2314
2394
|
* passed, not what its BufferSource holds once the verify has awaited. A value that is not bytes
|
|
@@ -2327,9 +2407,9 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
|
2327
2407
|
}
|
|
2328
2408
|
var boundName;
|
|
2329
2409
|
try { boundName = dnEqual(cert.issuer.rdns, issuerCert.subject.rdns); }
|
|
2330
|
-
catch (e) { return
|
|
2410
|
+
catch (e) { return _reject(_Promise, e); }
|
|
2331
2411
|
if (!boundName) {
|
|
2332
|
-
return
|
|
2412
|
+
return _resolve(_Promise, unbound("the supplied issuer certificate's subject does not match the target certificate's issuer"));
|
|
2333
2413
|
}
|
|
2334
2414
|
return builtinVerify({ workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes }, cert).then(function (sig) {
|
|
2335
2415
|
if (!sig.ok) return unbound("the target certificate's signature does not verify under the supplied issuer certificate's key");
|
|
@@ -2984,7 +3064,9 @@ function _bestByPolicy(accepted, uips) {
|
|
|
2984
3064
|
var s = _policyScore(accepted[i].result, uips);
|
|
2985
3065
|
if (s > bestScore) { best = accepted[i]; bestScore = s; }
|
|
2986
3066
|
}
|
|
2987
|
-
|
|
3067
|
+
/** @internal The candidate carries a verdict in its `result`, so the wrapper carries the sentinel
|
|
3068
|
+
* too: whichever of the two a caller awaits, the lookup ends on the object itself. */
|
|
3069
|
+
return guard.verdict.shield(best);
|
|
2988
3070
|
}
|
|
2989
3071
|
|
|
2990
3072
|
function _assertUserInitialPolicySet(uips, prefix) {
|
|
@@ -3015,7 +3097,7 @@ function _pushCandidates(frame, scored, stack, counter, down) {
|
|
|
3015
3097
|
intrinsic.setAdd(childKeys, candKey);
|
|
3016
3098
|
guard.list.append(stack, down
|
|
3017
3099
|
? { chain: intrinsic.concat(frame.chain, [cand]), hop: frame.hop + 1, keys: childKeys, anchor: frame.anchor }
|
|
3018
|
-
: { chain: [cand]
|
|
3100
|
+
: { chain: intrinsic.concat([cand], frame.chain), hop: frame.hop + 1, keys: childKeys });
|
|
3019
3101
|
}
|
|
3020
3102
|
return n;
|
|
3021
3103
|
}
|
|
@@ -3216,7 +3298,7 @@ async function build(leaf, opts) {
|
|
|
3216
3298
|
var poolCeiling = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES;
|
|
3217
3299
|
if (poolInput.length > poolCeiling) throw E("path/bad-input", "build: the candidate pool has " + poolInput.length + " certificates, exceeding the " + poolCeiling + " ceiling");
|
|
3218
3300
|
var pool;
|
|
3219
|
-
try { pool =
|
|
3301
|
+
try { pool = intrinsic.map(poolInput, coerceCert); }
|
|
3220
3302
|
catch (e) { throw E("path/bad-input", "build: a candidate certificate did not parse", e); }
|
|
3221
3303
|
var poolDerKeys = new Set();
|
|
3222
3304
|
for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
|
|
@@ -3280,7 +3362,7 @@ async function build(leaf, opts) {
|
|
|
3280
3362
|
var BUILD_ONLY_OPT = { candidates: 1, intermediates: 1, trustAnchors: 1, maxDepth: 1, maxCandidatesConsidered: 1, validate: 1,
|
|
3281
3363
|
fetchAia: 1, transport: 1, tls: 1, maxAiaFetches: 1, maxAiaPerCert: 1, aiaTimeout: 1, maxResponseBytes: 1 };
|
|
3282
3364
|
var forwarded = Object.create(null);
|
|
3283
|
-
|
|
3365
|
+
intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) {
|
|
3284
3366
|
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
|
|
3285
3367
|
if (k in opts) forwarded[k] = opts[k];
|
|
3286
3368
|
});
|
|
@@ -3319,7 +3401,7 @@ async function build(leaf, opts) {
|
|
|
3319
3401
|
var rstack = [];
|
|
3320
3402
|
for (var rsi = 0; rsi < anchors.length; rsi++) guard.list.append(rstack, { chain: [], hop: 0, keys: new Set([identityKey(leafCert)]), anchor: anchors[rsi] });
|
|
3321
3403
|
while (rstack.length) {
|
|
3322
|
-
var rframe = rstack
|
|
3404
|
+
var rframe = _pop(rstack);
|
|
3323
3405
|
var frontierCert = rframe.chain.length ? rframe.chain[rframe.chain.length - 1] : null;
|
|
3324
3406
|
var frontierName = frontierCert ? frontierCert.subject.rdns : rframe.anchor.name.rdns;
|
|
3325
3407
|
if (nameMatchSoft(leafCert.issuer.rdns, frontierName)) {
|
|
@@ -3384,7 +3466,7 @@ async function build(leaf, opts) {
|
|
|
3384
3466
|
considered += _pushCandidates(fb, fbScored, stack, counter);
|
|
3385
3467
|
continue;
|
|
3386
3468
|
}
|
|
3387
|
-
var frame = stack
|
|
3469
|
+
var frame = _pop(stack);
|
|
3388
3470
|
var current = frame.chain[0];
|
|
3389
3471
|
|
|
3390
3472
|
for (var ai = 0; ai < anchors.length; ai++) {
|