@blamejs/pki 0.8.30 → 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 +411 -0
- package/README.md +67 -16
- 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 +28 -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 +448 -2
- 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 +267 -4
- 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/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++) {
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -89,9 +89,14 @@ var _ENCRYPT_KEYS = intrinsic.assign(intrinsic.create(null), { password: 1, ciph
|
|
|
89
89
|
var _MAC_KEYS = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 });
|
|
90
90
|
var _MAC_KEYS_HMAC = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, password: 1 });
|
|
91
91
|
var _MAC_KEYS_PBMAC1 = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 });
|
|
92
|
+
/** @internal `allowUnauthenticatedRsa15` reaches `pki.cms.decrypt` through the options this verb already
|
|
93
|
+
* forwards. A public-key-privacy safe IS a CMS EnvelopedData, and the one `openssl pkcs12` or
|
|
94
|
+
* `openssl cms -encrypt` produces uses RSAES-PKCS1-v1_5 key transport over an AES-CBC content, which that
|
|
95
|
+
* verb refuses by default. An operator opening such a file needs the option, and naming it here is what
|
|
96
|
+
* lets them pass it rather than meeting an unknown-option refusal first. */
|
|
92
97
|
var _OPEN_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
93
|
-
allowUnauthenticated: 1,
|
|
94
|
-
recipientKey: 1, recipientCert: 1, recipientIndex: 1,
|
|
98
|
+
allowUnauthenticated: 1, allowUnauthenticatedRsa15: 1, importAlgorithm: 1, keys: 1, maxIterations: 1,
|
|
99
|
+
signerCerts: 1, recipientKey: 1, recipientCert: 1, recipientIndex: 1,
|
|
95
100
|
});
|
|
96
101
|
var _VERIFY_MAC_OPTS = intrinsic.assign(intrinsic.create(null), { maxIterations: 1 });
|
|
97
102
|
|
package/lib/pki-build.js
CHANGED
|
@@ -29,6 +29,7 @@ var _stringify = intrinsic.stringify;
|
|
|
29
29
|
var _mapIntrinsic = intrinsic.map;
|
|
30
30
|
var _bufferConcatIntrinsic = intrinsic.bufferConcat;
|
|
31
31
|
var OID_SKI = oid.byName("subjectKeyIdentifier");
|
|
32
|
+
var OID_SAPKI = oid.byName("subjectAltPublicKeyInfo");
|
|
32
33
|
|
|
33
34
|
function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); }
|
|
34
35
|
function _isDigitCode(c) { return c >= 48 && c <= 57; }
|
|
@@ -1375,15 +1376,15 @@ function makeBuilder(ctx) {
|
|
|
1375
1376
|
});
|
|
1376
1377
|
}
|
|
1377
1378
|
var pub;
|
|
1378
|
-
try { pub =
|
|
1379
|
+
try { pub = guard.crypto.publicKey({ key: spki, format: "der", type: "spki" }); }
|
|
1379
1380
|
catch (e) { throw E("bad-input", "the public key could not be imported for the signature self-check", e); }
|
|
1380
1381
|
var s = scheme.sign, ok;
|
|
1381
1382
|
try {
|
|
1382
|
-
if (s.name === "ECDSA") ok =
|
|
1383
|
-
else if (s.name === "RSA-PSS") ok =
|
|
1384
|
-
else if (s.name === "RSASSA-PKCS1-v1_5") ok =
|
|
1383
|
+
if (s.name === "ECDSA") ok = guard.crypto.verify(scheme.digest, preimage, { key: pub, dsaEncoding: "der" }, sig);
|
|
1384
|
+
else if (s.name === "RSA-PSS") ok = guard.crypto.verify(scheme.digest, preimage, { key: pub, padding: guard.crypto.padding.RSA_PKCS1_PSS, saltLength: s.saltLength }, sig);
|
|
1385
|
+
else if (s.name === "RSASSA-PKCS1-v1_5") ok = guard.crypto.verify(scheme.digest, preimage, pub, sig);
|
|
1385
1386
|
// allow:eddsa-verify-without-loworder-gate -- a self-check that OUR just-produced signature verifies
|
|
1386
|
-
else ok =
|
|
1387
|
+
else ok = guard.crypto.verify(null, preimage, pub, sig);
|
|
1387
1388
|
} catch (e) { throw E("bad-input", "the signature self-check could not run against the public key", e); }
|
|
1388
1389
|
if (!ok) throw E("bad-input", "the signing key does not correspond to the public key -- the signature would not verify");
|
|
1389
1390
|
}
|
|
@@ -1404,6 +1405,7 @@ function makeBuilder(ctx) {
|
|
|
1404
1405
|
extMsCertificateTemplate: extMsCertificateTemplate, extMsCaVersion: extMsCaVersion,
|
|
1405
1406
|
extMsPreviousCertHash: extMsPreviousCertHash, extMsEnrollCertType: extMsEnrollCertType,
|
|
1406
1407
|
spkiKeyId: spkiKeyId, skiKeyId: skiKeyId, skiValueOf: skiValueOf,
|
|
1408
|
+
altPublicKeyValueOf: altPublicKeyValueOf,
|
|
1407
1409
|
reqDer: reqDer, reqDenseArray: reqDenseArray, reqDerSequence: reqDerSequence, assertValidSpki: assertValidSpki, assertValidExtension: assertValidExtension,
|
|
1408
1410
|
assertExtnValueIsDer: assertExtnValueIsDer,
|
|
1409
1411
|
assertCertCriticality: assertCertCriticality,
|
|
@@ -1435,6 +1437,28 @@ function skiValueOf(parsedCert) {
|
|
|
1435
1437
|
catch (_e) { return null; } // allow:swallow-unverified -- a malformed SKI is read as absent; a signer then derives the authorityKeyIdentifier keyIdentifier from the public key, and a verifier matches on nothing
|
|
1436
1438
|
}
|
|
1437
1439
|
|
|
1440
|
+
/** @internal The alternative public key a parsed certificate PUBLISHES, from its
|
|
1441
|
+
* subjectAltPublicKeyInfo extension (ITU-T X.509 (2019) clause 9.8.2), or null when it carries none.
|
|
1442
|
+
*
|
|
1443
|
+
* A signer needs this to bind an alternative signature to the issuer the same way the native signature
|
|
1444
|
+
* is already bound: the native key is read out of the issuer certificate, while the alternative key is
|
|
1445
|
+
* handed in beside it, so without this the two can be different keys. Clause 9.8.4 has a relying party
|
|
1446
|
+
* check an alternative signature with the issuer's alternative public key, which is the one in the
|
|
1447
|
+
* issuer's own certificate, so a signature made with any other key verifies for nobody. */
|
|
1448
|
+
function altPublicKeyValueOf(parsedCert) {
|
|
1449
|
+
var exts = intrinsic.filter(parsedCert.extensions, function (e) { return e.oid === OID_SAPKI; });
|
|
1450
|
+
if (!exts.length) return null;
|
|
1451
|
+
/** @internal The extension's value IS a SubjectPublicKeyInfo, so unlike subjectKeyIdentifier there is
|
|
1452
|
+
* no inner OCTET STRING to read through: the bytes are the SPKI. They are decoded only to confirm the
|
|
1453
|
+
* shape, and the original bytes returned rather than a re-encoding, so the comparison a signer makes is
|
|
1454
|
+
* against what the certificate actually carries. */
|
|
1455
|
+
try {
|
|
1456
|
+
var node = asn1.decode(exts[0].value);
|
|
1457
|
+
if (!node.constructed || node.tagNumber !== asn1.TAGS.SEQUENCE) return null;
|
|
1458
|
+
return _bufferFrom(exts[0].value);
|
|
1459
|
+
} catch (_e) { return null; } // allow:swallow-unverified -- an unreadable extension publishes no key, and a signer then refuses rather than binding to bytes it could not decode
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1438
1462
|
/** @internal Whether two SubjectPublicKeyInfo encodings carry the same public key: byte-equal, or
|
|
1439
1463
|
* equal under the runtime's key comparison once both import. */
|
|
1440
1464
|
function samePublicKey(spkiA, spkiB) {
|
|
@@ -1443,8 +1467,8 @@ function samePublicKey(spkiA, spkiB) {
|
|
|
1443
1467
|
var ka, kb;
|
|
1444
1468
|
// allow:swallow-unverified an SPKI the key layer cannot import matches nothing, which is the
|
|
1445
1469
|
try {
|
|
1446
|
-
ka =
|
|
1447
|
-
kb =
|
|
1470
|
+
ka = guard.crypto.publicKey({ key: a, format: "der", type: "spki" });
|
|
1471
|
+
kb = guard.crypto.publicKey({ key: bb, format: "der", type: "spki" });
|
|
1448
1472
|
} catch (_e) { return false; }
|
|
1449
1473
|
/** @internal Two keys of different types are never the same key, and comparing them through
|
|
1450
1474
|
* the runtime's equality leaves a fault queued in the crypto library that surfaces on the
|