@blamejs/pki 0.8.31 → 0.8.49
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 +401 -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 +1008 -58
- 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/attrcert-sign.js
CHANGED
|
@@ -24,6 +24,12 @@ var x509 = require("./schema-x509");
|
|
|
24
24
|
var signScheme = require("./sign-scheme");
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
26
|
var intrinsic = require("./guard-intrinsic");
|
|
27
|
+
/** @internal The promise a signature verdict is awaited through, built from the constructor captured at
|
|
28
|
+
* load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands the
|
|
29
|
+
* construction to whatever that binding holds: one settling without awaiting its argument drops the
|
|
30
|
+
* self-check's rejection, and one settling truthy answers the verdict the caller reads. */
|
|
31
|
+
var _Promise = intrinsic.Promise;
|
|
32
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
27
33
|
var _hasOwn = intrinsic.hasOwn, _bufferEquals = intrinsic.bufferEquals;
|
|
28
34
|
var pkix = require("./schema-pkix");
|
|
29
35
|
var schema = require("./schema-engine");
|
|
@@ -257,7 +263,7 @@ function _encodeClearance(cl) {
|
|
|
257
263
|
try { parts = [b.oid(O(cl.policyId) || cl.policyId)]; } catch (e) { throw _err("attrcert/bad-input", "clearance.policyId is not a valid object identifier", e); }
|
|
258
264
|
if (cl.classList != null) {
|
|
259
265
|
if (!Array.isArray(cl.classList)) throw _err("attrcert/bad-input", "clearance.classList must be an array of class names");
|
|
260
|
-
var positions = cl.classList
|
|
266
|
+
var positions = guard.list.copyMap(cl.classList, function (n) {
|
|
261
267
|
var pos = CLASSLIST_BIT[guard.text.keyOf(n)];
|
|
262
268
|
if (pos == null) throw _err("attrcert/bad-input", "unknown clearance class " + guard.text.showValue(n));
|
|
263
269
|
return pos;
|
|
@@ -267,7 +273,7 @@ function _encodeClearance(cl) {
|
|
|
267
273
|
}
|
|
268
274
|
if (cl.securityCategories != null) {
|
|
269
275
|
if (!Array.isArray(cl.securityCategories) || !cl.securityCategories.length) throw _err("attrcert/bad-input", "clearance.securityCategories must be a non-empty array");
|
|
270
|
-
parts.push(b.set(cl.securityCategories
|
|
276
|
+
parts.push(b.set(guard.list.copyMap(cl.securityCategories, _encodeSecurityCategory)));
|
|
271
277
|
}
|
|
272
278
|
return b.sequence(parts);
|
|
273
279
|
}
|
|
@@ -286,7 +292,7 @@ function _encodeIetfAttrSyntax(ia) {
|
|
|
286
292
|
if (!Array.isArray(ia.values) || !ia.values.length) throw _err("attrcert/bad-input", "an IetfAttrSyntax attribute must be an object with a non-empty values array");
|
|
287
293
|
var parts = [];
|
|
288
294
|
if (ia.policyAuthority != null) parts.push(_b.encodeGeneralNames(_gnList(ia.policyAuthority), 0));
|
|
289
|
-
parts.push(b.sequence(ia.values
|
|
295
|
+
parts.push(b.sequence(guard.list.copyMap(ia.values, function (v) {
|
|
290
296
|
if (v == null || typeof v !== "object") throw _err("attrcert/bad-input", "each IetfAttrSyntax value must be { octets }, { oid }, or { string }");
|
|
291
297
|
_assertFields(v, KNOWN_IETF_ATTR_VALUE_KEYS, "IetfAttrSyntax value");
|
|
292
298
|
if (v.octets != null) { if (!Buffer.isBuffer(v.octets)) throw _err("attrcert/bad-input", "IetfAttrSyntax octets must be a Buffer"); return b.octetString(v.octets); }
|
|
@@ -328,11 +334,11 @@ function _encodeTarget(t) {
|
|
|
328
334
|
}
|
|
329
335
|
function _encodeSeqOfTargets(targets) {
|
|
330
336
|
if (!Array.isArray(targets) || !targets.length) throw _err("attrcert/bad-input", "a targets value must be a non-empty array of targets");
|
|
331
|
-
return b.sequence([b.sequence(
|
|
337
|
+
return b.sequence([b.sequence(guard.list.copyMap(targets, _encodeTarget))]);
|
|
332
338
|
}
|
|
333
339
|
function _attrSpec(names, tag, label) {
|
|
334
340
|
if (!Array.isArray(names)) throw _err("attrcert/bad-input", "aaControls." + label + " must be an array of attribute-type OIDs");
|
|
335
|
-
var oids =
|
|
341
|
+
var oids = guard.list.copyMap(names, function (n) {
|
|
336
342
|
try { return b.oid(O(n) || n); } catch (e) { throw _err("attrcert/bad-input", "aaControls." + label + " contains an invalid object identifier " + guard.text.showValue(n), e); }
|
|
337
343
|
});
|
|
338
344
|
return b.contextConstructed(tag, Buffer.concat(oids));
|
|
@@ -385,7 +391,9 @@ function _buildAttributes(attrSpec) {
|
|
|
385
391
|
attrs.push(b.sequence([b.oid(type), b.set([valueTlv])]));
|
|
386
392
|
}
|
|
387
393
|
if (Array.isArray(attrSpec)) {
|
|
388
|
-
|
|
394
|
+
/** @internal Through the captured walk, never the caller's own `forEach`: these attributes are the
|
|
395
|
+
* content this verb signs, and a replaced walk hands the callback a list it never validated. */
|
|
396
|
+
guard.list.copyMap(attrSpec, function (a, i) {
|
|
389
397
|
var der = _b.reqDer(a, "attribute [" + i + "]");
|
|
390
398
|
var n;
|
|
391
399
|
try { n = asn1.decode(der); } catch (e) { throw _err("attrcert/bad-input", "pre-encoded attribute [" + i + "] is not valid DER", e); }
|
|
@@ -441,7 +449,7 @@ function _buildExtensions(extSpec, ctx) {
|
|
|
441
449
|
if (Array.isArray(extSpec)) {
|
|
442
450
|
if (!extSpec.length) return null;
|
|
443
451
|
var seenA = intrinsic.create(null);
|
|
444
|
-
var exts =
|
|
452
|
+
var exts = guard.list.copyMap(extSpec, function (e, i) {
|
|
445
453
|
var der = _b.reqDer(e, "extension");
|
|
446
454
|
_b.assertValidExtension(der, i, EXT_DECODERS);
|
|
447
455
|
var n = asn1.decode(der);
|
|
@@ -589,7 +597,7 @@ function _sign(spec, issuer, opts) {
|
|
|
589
597
|
var acinfoDer = b.sequence(acinfoChildren);
|
|
590
598
|
|
|
591
599
|
return signScheme.signOverTbs(scheme, issuer.key, acinfoDer, _signE).then(function (sig) {
|
|
592
|
-
return
|
|
600
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(acinfoDer, sig, aaSpki, scheme)).then(function () {
|
|
593
601
|
var acDer = b.sequence([acinfoDer, scheme.sigAlgId, b.bitString(sig, 0)]);
|
|
594
602
|
return opts.pem ? attrcert.pemEncode(acDer, "ATTRIBUTE CERTIFICATE") : acDer;
|
|
595
603
|
});
|
|
@@ -697,17 +705,17 @@ function _checkTargeting(ac, want) {
|
|
|
697
705
|
for (var i = 0; i < ac.extensions.length; i++) {
|
|
698
706
|
if (ac.extensions[i].name === "targetInformation") ext = ac.extensions[i];
|
|
699
707
|
}
|
|
700
|
-
if (!ext) return { checked: true, ok: true };
|
|
708
|
+
if (!ext) return guard.verdict.of({ checked: true, ok: true });
|
|
701
709
|
if (want === undefined) {
|
|
702
|
-
return { checked: false, ok: false,
|
|
703
|
-
reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" };
|
|
710
|
+
return guard.verdict.of({ checked: false, ok: false,
|
|
711
|
+
reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" });
|
|
704
712
|
}
|
|
705
713
|
var groups = ext.decoded || [];
|
|
706
714
|
for (var g0 = 0; g0 < groups.length; g0++) {
|
|
707
715
|
for (var t0 = 0; t0 < groups[g0].length; t0++) {
|
|
708
716
|
if (groups[g0][t0].kind !== "targetCert") continue;
|
|
709
|
-
return { checked: false, ok: false,
|
|
710
|
-
reason: "this attribute certificate carries a targetCert target, which RFC 5755 sec. 4.3.2 says MUST NOT be used, so the targeting check did not run" };
|
|
717
|
+
return guard.verdict.of({ checked: false, ok: false,
|
|
718
|
+
reason: "this attribute certificate carries a targetCert target, which RFC 5755 sec. 4.3.2 says MUST NOT be used, so the targeting check did not run" });
|
|
711
719
|
}
|
|
712
720
|
}
|
|
713
721
|
|
|
@@ -717,17 +725,17 @@ function _checkTargeting(ac, want) {
|
|
|
717
725
|
var entry = groups[g][t];
|
|
718
726
|
if (entry.kind !== "targetName" && entry.kind !== "targetGroup") continue;
|
|
719
727
|
var m = _generalNameMatches(entry.name, want);
|
|
720
|
-
if (m === true) return { checked: true, ok: true };
|
|
728
|
+
if (m === true) return guard.verdict.of({ checked: true, ok: true });
|
|
721
729
|
if (m === null) uncomparable = entry.name && entry.name.tagNumber;
|
|
722
730
|
}
|
|
723
731
|
}
|
|
724
732
|
if (uncomparable !== null) {
|
|
725
|
-
return { checked: false, ok: false,
|
|
733
|
+
return guard.verdict.of({ checked: false, ok: false,
|
|
726
734
|
reason: "this attribute certificate names a target in a GeneralName form ([" + uncomparable +
|
|
727
|
-
"]) this verifier does not compare, so the RFC 5755 sec. 4.3.2 check did not run" };
|
|
735
|
+
"]) this verifier does not compare, so the RFC 5755 sec. 4.3.2 check did not run" });
|
|
728
736
|
}
|
|
729
|
-
return { checked: true, ok: false,
|
|
730
|
-
reason: "this attribute certificate does not name this verifier among its targets (RFC 5755 sec. 4.3.2)" };
|
|
737
|
+
return guard.verdict.of({ checked: true, ok: false,
|
|
738
|
+
reason: "this attribute certificate does not name this verifier among its targets (RFC 5755 sec. 4.3.2)" });
|
|
731
739
|
}
|
|
732
740
|
|
|
733
741
|
var SUPPORTED_CRITICAL = intrinsic.create(null);
|
|
@@ -908,7 +916,7 @@ function _verify(ac, issuer, opts) {
|
|
|
908
916
|
reason: undefined,
|
|
909
917
|
});
|
|
910
918
|
|
|
911
|
-
return
|
|
919
|
+
return _promiseResolve(_Promise, attrcertVerify.verifyAcSignature(parsed, trust.spki)).then(function (sigOk) {
|
|
912
920
|
result.signatureValid = sigOk === true;
|
|
913
921
|
var fail = function (reason) { result.reason = reason; return result; };
|
|
914
922
|
if (!result.signatureValid) return fail("the signature does not verify under the issuer public key");
|
package/lib/cbor-det.js
CHANGED
|
@@ -56,7 +56,7 @@ function _asBuffer(input, who) {
|
|
|
56
56
|
|
|
57
57
|
function _profile(name) {
|
|
58
58
|
var p = name === undefined ? "deterministic" : name;
|
|
59
|
-
if (p === "deterministic") return { mapKeyCompare:
|
|
59
|
+
if (p === "deterministic") return { mapKeyCompare: guard.bytes.compare };
|
|
60
60
|
throw new TypeError("decode: unknown profile " + JSON.stringify(p) + " (only \"deterministic\" is supported)");
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -637,15 +637,21 @@ function _asText(s, who) {
|
|
|
637
637
|
return s;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
+
/** @internal @guard-via guard\.text\.assertWellFormedUtf16\( */
|
|
640
641
|
function _assertWellFormedText(str, who) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
642
|
+
return guard.text.assertWellFormedUtf16(str, CborError, "cbor/bad-utf8", who + ": the string");
|
|
643
|
+
}
|
|
644
|
+
/** @internal A builder's integer argument is authoring-tier input, so it is held to being an
|
|
645
|
+
* integer rather than converted into one. `BigInt(x)` accepts a string in any radix, a padded or
|
|
646
|
+
* empty string, and a boolean, each of which becomes a well-formed encoding of a number the caller
|
|
647
|
+
* never wrote; an empty string in particular encodes as zero. */
|
|
648
|
+
function _authoredInt(v, who) {
|
|
649
|
+
return guard.range.authoredInteger(v, function (c, m) { return new CborError(c, m); },
|
|
650
|
+
"cbor/bad-argument", who + ": the value");
|
|
646
651
|
}
|
|
652
|
+
|
|
647
653
|
function _head(mt, argument, who) {
|
|
648
|
-
var a =
|
|
654
|
+
var a = _authoredInt(argument, who);
|
|
649
655
|
if (a < 0n) throw new CborError("cbor/bad-argument", who + ": a CBOR head argument must be non-negative");
|
|
650
656
|
if (a > 0xffffffffffffffffn) throw new CborError("cbor/bad-argument", who + ": a CBOR head argument exceeds 64 bits");
|
|
651
657
|
var mtb = mt << 5, b;
|
|
@@ -656,9 +662,9 @@ function _head(mt, argument, who) {
|
|
|
656
662
|
b = Buffer.alloc(9); b[0] = mtb | 27; b.writeBigUInt64BE(a, 1); return b;
|
|
657
663
|
}
|
|
658
664
|
var build = Object.assign(Object.create(null), {
|
|
659
|
-
uint: function (n) { var a =
|
|
660
|
-
nint: function (n) { var a =
|
|
661
|
-
int: function (n) { var a =
|
|
665
|
+
uint: function (n) { var a = _authoredInt(n, "build.uint"); if (a < 0n) throw new CborError("cbor/bad-argument", "build.uint: value must be non-negative"); return _head(0, a, "build.uint"); },
|
|
666
|
+
nint: function (n) { var a = _authoredInt(n, "build.nint"); if (a >= 0n) throw new CborError("cbor/bad-argument", "build.nint: value must be negative"); return _head(1, -1n - a, "build.nint"); },
|
|
667
|
+
int: function (n) { var a = _authoredInt(n, "build.int"); return a >= 0n ? _head(0, a, "build.int") : _head(1, -1n - a, "build.int"); },
|
|
662
668
|
byteString: function (buf) { var b = _asBuffer(buf, "build.byteString"); return _capString(Buffer.concat([_head(2, b.length, "build.byteString"), b]), "build.byteString"); },
|
|
663
669
|
textString: function (s) { var str = _asText(s, "build.textString"); _assertWellFormedText(str, "build.textString"); var b = Buffer.from(str, "utf8"); return _capString(Buffer.concat([_head(3, b.length, "build.textString"), b]), "build.textString"); },
|
|
664
670
|
array: function (items) {
|
|
@@ -669,8 +675,12 @@ var build = Object.assign(Object.create(null), {
|
|
|
669
675
|
map: function (pairs) {
|
|
670
676
|
if (!Array.isArray(pairs)) throw new CborError("cbor/bad-argument", "build.map: expected an array of [key, value] pairs");
|
|
671
677
|
var enc = pairs.map(function (p, i) { if (!Array.isArray(p) || p.length !== 2) throw new CborError("cbor/bad-argument", "build.map[" + i + "]: each entry is a [key, value] pair"); return [_cbItem(p[0], "build.map key"), _cbItem(p[1], "build.map value")]; });
|
|
672
|
-
|
|
673
|
-
|
|
678
|
+
/** @internal The map-key ordering and the duplicate test go through the captured comparator. A
|
|
679
|
+
* deterministic encoding IS its key order, so whatever decides "before" decides the bytes, and the
|
|
680
|
+
* duplicate test asked the same question: a replacement answering a fixed order would emit a map
|
|
681
|
+
* this decoder refuses and would let two equal keys through. */
|
|
682
|
+
enc = guard.list.sortedCopy(enc, function (a, b) { return guard.bytes.compare(a[0], b[0]); });
|
|
683
|
+
for (var i = 1; i < enc.length; i++) { if (guard.bytes.compare(enc[i - 1][0], enc[i][0]) === 0) throw new CborError("cbor/duplicate-map-key", "build.map: duplicate map key"); }
|
|
674
684
|
var flat = [_head(5, pairs.length, "build.map")];
|
|
675
685
|
enc.forEach(function (p) { flat.push(p[0], p[1]); });
|
|
676
686
|
return _assertDecodes(Buffer.concat(flat), "build.map");
|
|
@@ -680,7 +690,7 @@ var build = Object.assign(Object.create(null), {
|
|
|
680
690
|
nullValue: function () { return Buffer.from([0xf6]); },
|
|
681
691
|
raw: function (buf) { return _assertDecodes(_asBuffer(buf, "build.raw"), "build.raw"); },
|
|
682
692
|
biguint: function (n) {
|
|
683
|
-
var a =
|
|
693
|
+
var a = _authoredInt(n, "build.biguint");
|
|
684
694
|
if (a < 0n) throw new CborError("cbor/bad-argument", "build.biguint: value must be non-negative");
|
|
685
695
|
var hex = a.toString(16); if (hex.length % 2) hex = "0" + hex;
|
|
686
696
|
var mag = a === 0n ? Buffer.alloc(0) : Buffer.from(hex, "hex");
|
|
@@ -695,7 +705,7 @@ var build = Object.assign(Object.create(null), {
|
|
|
695
705
|
if (Number.isNaN(ms)) throw new CborError("cbor/bad-time", "build.time: an Invalid Date has no epoch value");
|
|
696
706
|
if (ms % 1000 !== 0) throw new CborError("cbor/bad-time", "build.time: a Date with sub-second precision cannot be a second-granularity CBOR epoch; round to whole seconds");
|
|
697
707
|
}
|
|
698
|
-
var secs = guard.time.isDate(v) ? BigInt(Math.floor(ms / 1000)) : (
|
|
708
|
+
var secs = guard.time.isDate(v) ? BigInt(Math.floor(ms / 1000)) : _authoredInt(v, "build.time");
|
|
699
709
|
if (secs < -_MAX_EPOCH_SECONDS || secs > _MAX_EPOCH_SECONDS) throw new CborError("cbor/bad-time", "build.time: epoch seconds " + secs + " are outside the representable Date range");
|
|
700
710
|
return build.tag(1, build.int(secs));
|
|
701
711
|
},
|
package/lib/cmc-build.js
CHANGED
|
@@ -590,7 +590,13 @@ async function _recoverProofValue(decoded, recipient, witnessHash) {
|
|
|
590
590
|
for (var i = 0; i < count; i++) {
|
|
591
591
|
var opened;
|
|
592
592
|
try {
|
|
593
|
-
|
|
593
|
+
/** @internal The refusal `pki.cms.decrypt` applies to RSAES-PKCS1-v1_5 key transport protects a
|
|
594
|
+
* content whose padding is the only integrity check. Here the witness digest below is that check:
|
|
595
|
+
* it decides the candidate, and a decrypt that fails and a candidate the witness rejects both
|
|
596
|
+
* continue to the next recipient and end in the one `cmc/pop-failed`, so a submitter who does not
|
|
597
|
+
* know the proof value observes one outcome whatever their ciphertext did. RFC 5272 sec. 6.7
|
|
598
|
+
* defines the position. */
|
|
599
|
+
opened = await cmsDecrypt.decrypt(decoded.cms, recipient, { recipientIndex: i, allowUnauthenticatedRsa15: true });
|
|
594
600
|
} catch (_e) {
|
|
595
601
|
continue; // allow:swallow-unverified this recipient is not addressed to the supplied key; the witness decides
|
|
596
602
|
}
|
|
@@ -677,8 +683,9 @@ async function answerPopChallenge(popChallenge, ids) {
|
|
|
677
683
|
function popLinkWitnessV2(secret, R) {
|
|
678
684
|
_assertSecret(secret, "a POP Link Witness V2 control");
|
|
679
685
|
var key = nodeCrypto.createHash("sha256").update(secret, "utf8").digest();
|
|
680
|
-
var witness = nodeCrypto.createHmac("sha256", key).update(R).digest();
|
|
681
686
|
try {
|
|
687
|
+
/** @internal Inside the try, so a MAC that faults on its message still wipes the key above it. */
|
|
688
|
+
var witness = nodeCrypto.createHmac("sha256", key).update(R).digest();
|
|
682
689
|
return b.sequence([algId(OID_SHA256), algId(OID_HMAC_SHA256), b.octetString(witness)]);
|
|
683
690
|
} finally {
|
|
684
691
|
guard.secret.zeroize(key, CmcError, "cmc/bad-input", "the POP Link MAC key");
|
package/lib/cmc-verify.js
CHANGED
|
@@ -27,6 +27,12 @@ var cmsDecrypt = require("./cms-decrypt");
|
|
|
27
27
|
var guard = require("./guard-all");
|
|
28
28
|
var intrinsic = require("./guard-intrinsic");
|
|
29
29
|
var _hasOwn = intrinsic.hasOwn;
|
|
30
|
+
/** @internal The verify entry gates its body on a resolved promise and returns its refusals as
|
|
31
|
+
* rejected ones, and both build from the RECEIVER, so a replacement read at the call decides whether
|
|
32
|
+
* the body runs and whether a refusal stays a refusal. Taken at load. */
|
|
33
|
+
var _Promise = intrinsic.Promise;
|
|
34
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
35
|
+
var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
30
36
|
var frameworkError = require("./framework-error");
|
|
31
37
|
|
|
32
38
|
var CmcError = frameworkError.CmcError;
|
|
@@ -308,7 +314,7 @@ function verify(response, sent) {
|
|
|
308
314
|
frozenResponse = _snapshotIfBytes(response);
|
|
309
315
|
} catch (e) {
|
|
310
316
|
_wipeOwnedSecret();
|
|
311
|
-
return
|
|
317
|
+
return _promiseReject(_Promise, e);
|
|
312
318
|
}
|
|
313
319
|
return guard.async.deferred(function () { return _verify(frozenResponse, frozenSent); })
|
|
314
320
|
.then(function (v) { _wipeOwnedSecret(); return v; },
|
|
@@ -387,7 +393,7 @@ function _verify(response, sent) {
|
|
|
387
393
|
|
|
388
394
|
|
|
389
395
|
function _assertAuthentic(body, sent, responseBytes) {
|
|
390
|
-
return
|
|
396
|
+
return _promiseResolve(_Promise, undefined).then(function () {
|
|
391
397
|
var haveBytes = typeof responseBytes === "string" ||
|
|
392
398
|
Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array; // allow:byte-source-narrow -- responseBytes is _snapshotIfBytes's return: any BufferSource is already a Buffer here, so this is Buffer-or-string by construction
|
|
393
399
|
if (body.cms && body.cms.contentTypeName === "authData") {
|
|
@@ -413,7 +419,12 @@ function _assertAuthentic(body, sent, responseBytes) {
|
|
|
413
419
|
derivedKey = cmcBuild.macPassword(recipient);
|
|
414
420
|
recipient = { password: derivedKey };
|
|
415
421
|
}
|
|
416
|
-
|
|
422
|
+
/** @internal The refusal `pki.cms.decrypt` applies to RSAES-PKCS1-v1_5 key transport protects a
|
|
423
|
+
* content whose padding is the only integrity check. This is an AuthenticatedData, whose MAC is
|
|
424
|
+
* that check, so the premise does not hold here and the opt-in states it. The opt-in is inert on
|
|
425
|
+
* this path, because an AuthenticatedData is dispatched to the MAC verifier before the refusal is
|
|
426
|
+
* reached; it is passed so the premise stays recorded where the content type is decided. */
|
|
427
|
+
return cmsDecrypt.decrypt(responseBytes, recipient, { allowUnauthenticatedRsa15: true }).then(function (r) { _wipeDerived(); return r; },
|
|
417
428
|
function (e) {
|
|
418
429
|
_wipeDerived();
|
|
419
430
|
throw E("cmc/unverified-response",
|
package/lib/cmp-build.js
CHANGED
|
@@ -29,6 +29,12 @@ var cmp = require("./schema-cmp");
|
|
|
29
29
|
var crmf = require("./crmf-sign");
|
|
30
30
|
var schemaCrmf = require("./schema-crmf");
|
|
31
31
|
var intrinsic = require("./guard-intrinsic");
|
|
32
|
+
/** @internal The promise a signature self-check is awaited through, built from the constructor captured
|
|
33
|
+
* at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
|
|
34
|
+
* the construction to whatever that binding holds, and one settling without awaiting its argument runs
|
|
35
|
+
* the continuation before the check has finished. */
|
|
36
|
+
var _Promise = intrinsic.Promise;
|
|
37
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
32
38
|
var _hasOwn = intrinsic.hasOwn;
|
|
33
39
|
var _isArray = intrinsic.isArray;
|
|
34
40
|
var _isBuffer = intrinsic.isBuffer;
|
|
@@ -603,7 +609,7 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
603
609
|
if (bodySpec.pkiconf !== null && bodySpec.pkiconf !== true) throw _err("cmp/bad-input", "pkiconf takes null or true (PKIConfirmContent is NULL)");
|
|
604
610
|
inner = b.nullValue();
|
|
605
611
|
}
|
|
606
|
-
return
|
|
612
|
+
return _promiseResolve(_Promise, { bodyTLV: b.explicit(tag, inner), usesCmp2021: state.usesCmp2021 });
|
|
607
613
|
}
|
|
608
614
|
|
|
609
615
|
async function _p10crBody(csrDer, parsedCsr, tag, state) {
|
|
@@ -786,8 +792,8 @@ function _resolveProtection(opts, message) {
|
|
|
786
792
|
protectionAlgDer: scheme.sigAlgId,
|
|
787
793
|
certDer: certDer,
|
|
788
794
|
computeBits: function (protectedPartDer) {
|
|
789
|
-
return
|
|
790
|
-
return
|
|
795
|
+
return _promiseResolve(_Promise, signScheme.signOverTbs(scheme, opts.key, protectedPartDer, _signE)).then(function (sig) {
|
|
796
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(protectedPartDer, sig, senderSpki, scheme)).then(function () {
|
|
791
797
|
return b.bitString(sig, 0);
|
|
792
798
|
});
|
|
793
799
|
});
|
|
@@ -999,12 +1005,18 @@ function _tlsForTransfer(opts) {
|
|
|
999
1005
|
function _ctypeToken(headers) { return String((headers || {})["content-type"] || "").split(";")[0].trim().toLowerCase(); }
|
|
1000
1006
|
function _isPkixcmp(headers) { var t = _ctypeToken(headers); return t === PKIXCMP || t === PKIXCMP_POLL; }
|
|
1001
1007
|
|
|
1008
|
+
/** @internal The sentinel is added at the one exit rather than at each `return`, so a branch added
|
|
1009
|
+
* later carries it without being told to. */
|
|
1002
1010
|
function _classifyCmpResponse(status, headers, body, tls) {
|
|
1011
|
+
return guard.verdict.shield(_classifyCmpResponseBody(status, headers, body, tls));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function _classifyCmpResponseBody(status, headers, body, tls) {
|
|
1003
1015
|
var ctype = _ctypeToken(headers);
|
|
1004
1016
|
if (status === 200) {
|
|
1005
1017
|
if (!_isPkixcmp(headers)) throw _err("cmp/bad-content-type", "a 200 CMP response must be application/pkixcmp (RFC 9811 sec. 3.2), got " + JSON.stringify(ctype || null));
|
|
1006
1018
|
if (body.length === 0) throw _err("cmp/empty-response", "a 200 CMP response carried an empty body (RFC 9811 sec. 3.3)");
|
|
1007
|
-
return { response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls };
|
|
1019
|
+
return guard.verdict.of({ response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls });
|
|
1008
1020
|
}
|
|
1009
1021
|
var httpStatus = (typeof status === "number" && Number.isSafeInteger(status) && status >= 100 && status <= 599) ? status : null;
|
|
1010
1022
|
if (httpStatus !== null && httpStatus >= 300 && httpStatus < 400) throw _err("cmp/redirect-not-followed", "a CMP " + httpStatus + " redirect is not followed (RFC 9811 sec. 3.1, sec. 5); reconfigure the endpoint URL");
|
|
@@ -1014,7 +1026,7 @@ function _classifyCmpResponse(status, headers, body, tls) {
|
|
|
1014
1026
|
try { forwarded = cmp.parse(body); }
|
|
1015
1027
|
catch (e) { throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with an undecodable body", e); }
|
|
1016
1028
|
if (forwarded.body.arm !== "error") throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with a non-error '" + forwarded.body.arm + "' body; an HTTP failure forwards only a CMP error message (RFC 9811 sec. 1.2/3.1)");
|
|
1017
|
-
return { response: forwarded, responseBytes: body, status: httpStatus, contentType: ctype, tls: tls };
|
|
1029
|
+
return guard.verdict.of({ response: forwarded, responseBytes: body, status: httpStatus, contentType: ctype, tls: tls });
|
|
1018
1030
|
}
|
|
1019
1031
|
throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with no forwardable CMP error body");
|
|
1020
1032
|
}
|
package/lib/cmp-session.js
CHANGED
|
@@ -125,8 +125,8 @@ var PKI_STATUS_WAITING = 3;
|
|
|
125
125
|
* single process. The authority's own diagnostic strings are not reproduced from stored state, since a
|
|
126
126
|
* verdict must not present text the authority did not sign in this exchange as though it had. */
|
|
127
127
|
function _waitingStatus() {
|
|
128
|
-
return { status: { code: PKI_STATUS_WAITING, name: PKI_STATUS_NAMES[PKI_STATUS_WAITING] },
|
|
129
|
-
statusString: null, failInfo: null };
|
|
128
|
+
return guard.verdict.of({ status: { code: PKI_STATUS_WAITING, name: PKI_STATUS_NAMES[PKI_STATUS_WAITING] },
|
|
129
|
+
statusString: null, failInfo: null });
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function _certReqIdEq(a, b) { return a != null && b != null && BigInt(a) === BigInt(b); }
|
|
@@ -717,10 +717,10 @@ function session(opts) {
|
|
|
717
717
|
if (code === 3) {
|
|
718
718
|
if (txnKind === "enroll") throw _err("cmp/bad-error", "an error message answering an enrollment must not carry status waiting -- the profile places enrollment waiting in an ip/cp/kup (RFC 9483 sec. 4.4)");
|
|
719
719
|
if (si.failInfo != null) throw _err("cmp/bad-error", "an error message with status waiting must not carry failInfo (RFC 9483 sec. 4.4)");
|
|
720
|
-
return { state: "waiting", resp: { status: si } };
|
|
720
|
+
return guard.verdict.of({ state: "waiting", resp: guard.verdict.of({ status: si }) });
|
|
721
721
|
}
|
|
722
722
|
if (code !== 2) throw _err("cmp/bad-error", "an error message carries status rejection(2), or waiting(3) for a revoke/info delayed delivery (RFC 9483 sec. 4.2); got " + (code == null ? "no status" : code));
|
|
723
|
-
return { state: "rejected", status: si };
|
|
723
|
+
return guard.verdict.of({ state: "rejected", status: si });
|
|
724
724
|
}
|
|
725
725
|
|
|
726
726
|
function _classifyRevRep(body) {
|
|
@@ -735,9 +735,9 @@ function session(opts) {
|
|
|
735
735
|
if (si.failInfo != null) {
|
|
736
736
|
return { state: "unexpected", reason: "an accepted rp must not carry failInfo (RFC 9483 sec. 4.2)" };
|
|
737
737
|
}
|
|
738
|
-
return { state: "granted", status: si, revCerts: d.revCerts, crls: d.crls };
|
|
738
|
+
return guard.verdict.of({ state: "granted", status: si, revCerts: d.revCerts, crls: d.crls });
|
|
739
739
|
}
|
|
740
|
-
if (code === 2) return { state: "rejected", status: si };
|
|
740
|
+
if (code === 2) return guard.verdict.of({ state: "rejected", status: si });
|
|
741
741
|
return { state: "unexpected", reason: "an rp status code " + code + " has no transition in a revocation (RFC 9483 sec. 4.2 allows accepted or rejection)" };
|
|
742
742
|
}
|
|
743
743
|
|
|
@@ -851,7 +851,7 @@ function session(opts) {
|
|
|
851
851
|
async function _pollLoop(lastWaitingResp, alreadyWaited) {
|
|
852
852
|
var polls = 0, waited = alreadyWaited || 0, lastStatus = _statusOf(lastWaitingResp);
|
|
853
853
|
for (;;) {
|
|
854
|
-
if (polls >= maxPolls || waited > maxTotalWait) return { timeout: true, polls: polls, status: lastStatus };
|
|
854
|
+
if (polls >= maxPolls || waited > maxTotalWait) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus });
|
|
855
855
|
var verdict = await _send({ pollReq: [{ certReqId: activeCertReqId }] }, "pollReq");
|
|
856
856
|
polls += 1;
|
|
857
857
|
var t = _classify(verdict);
|
|
@@ -865,8 +865,8 @@ function session(opts) {
|
|
|
865
865
|
/** @internal The authority asked to be polled again after `checkAfter`, and the budget ran out
|
|
866
866
|
* before that wait was taken. Reporting it lets a resumed poll honor the interval rather than
|
|
867
867
|
* poll immediately in a new process. */
|
|
868
|
-
if (waited > maxTotalWait) return { timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter };
|
|
869
|
-
if (polls >= maxPolls) return { timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter };
|
|
868
|
+
if (waited > maxTotalWait) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter });
|
|
869
|
+
if (polls >= maxPolls) return guard.verdict.of({ timeout: true, polls: polls, status: lastStatus, dueIn: checkAfter });
|
|
870
870
|
await sleep(checkAfter * constants.TIME.seconds(1));
|
|
871
871
|
continue;
|
|
872
872
|
}
|
|
@@ -920,7 +920,11 @@ function session(opts) {
|
|
|
920
920
|
if (pk == null) return true;
|
|
921
921
|
if (!guard.bytes.isByteSource(pk)) return false;
|
|
922
922
|
var bits;
|
|
923
|
-
|
|
923
|
+
/** @internal `source` rather than `view`, so the set read is the set the line above admits. `view`
|
|
924
|
+
* wants a Buffer or a Uint8Array and its refusal is caught below, so an ArrayBuffer or a DataView
|
|
925
|
+
* carrying a zero-length key was read as an ordinary request for a key the caller holds rather
|
|
926
|
+
* than as the request for a centrally generated one that it is. */
|
|
927
|
+
try { bits = asn1.read.bitString(asn1.decode(guard.bytes.source(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey")).children[1]); }
|
|
924
928
|
catch (_e) { return false; }
|
|
925
929
|
return bits.bytes.length === 0;
|
|
926
930
|
}
|
|
@@ -1031,6 +1035,15 @@ function session(opts) {
|
|
|
1031
1035
|
if (ckp.privateKey.bytes == null) {
|
|
1032
1036
|
throw _err("cmp/bad-key-package", "a delivered key package is carried as an EnvelopedData, and this grant carries the legacy RFC 4211 EncryptedValue form (RFC 9483 sec. 4.1.6, RFC 9810 sec. 5.2.2)");
|
|
1033
1037
|
}
|
|
1038
|
+
/** @internal `pki.cmp.openKeyPackage` leaves the RSAES-PKCS1-v1_5 decision to its caller, because
|
|
1039
|
+
* taking a container on its own it cannot establish that one arrived inside an authenticated
|
|
1040
|
+
* exchange. A session can: it reaches here only for a response whose protection verified AND whose
|
|
1041
|
+
* signer chained to a supplied trust anchor, so a container reaching this point came inside a
|
|
1042
|
+
* message the authority protected and an attacker cannot submit chosen ciphertexts to it. The
|
|
1043
|
+
* premise the refusal protects therefore holds, and the opt-in is the session's to make rather than
|
|
1044
|
+
* a hole the caller has to open. Deployed authorities deliver v1.5 over a CBC content, so withholding
|
|
1045
|
+
* it would refuse the key delivery this verb exists to complete. */
|
|
1046
|
+
open.allowUnauthenticatedRsa15 = true;
|
|
1034
1047
|
return cmp.openKeyPackage(ckp.privateKey.bytes, open);
|
|
1035
1048
|
}
|
|
1036
1049
|
|
|
@@ -1111,7 +1124,7 @@ function session(opts) {
|
|
|
1111
1124
|
if (t.state === "waiting") {
|
|
1112
1125
|
var polled = await _pollLoop(t.resp);
|
|
1113
1126
|
polls = polled.polls;
|
|
1114
|
-
if (polled.timeout) return { timeout: true, polls: polls, status: polled.status };
|
|
1127
|
+
if (polled.timeout) return guard.verdict.of({ timeout: true, polls: polls, status: polled.status });
|
|
1115
1128
|
t = polled.done;
|
|
1116
1129
|
}
|
|
1117
1130
|
return { done: t, polls: polls };
|
package/lib/cmp-verify.js
CHANGED
|
@@ -413,6 +413,11 @@ function _headerChecks(m, opts) {
|
|
|
413
413
|
if (!guard.bytes.isByteSource(m.header.transactionID)) {
|
|
414
414
|
return { code: "cmp/bad-transaction-id", reason: "the header transactionID is absent; a received PKIMessage MUST carry one (RFC 9483 sec. 3.5)" };
|
|
415
415
|
}
|
|
416
|
+
/** @internal `lengthOf` wants a view and the check above admits every BufferSource, which elsewhere
|
|
417
|
+
* let an ArrayBuffer through a door and be refused by the line after it. Not reachable here: this
|
|
418
|
+
* verb takes DER bytes rather than a record, so `m` is always what the parser produced, and the
|
|
419
|
+
* parser reads this field as an OCTET STRING, whose value is a Buffer. The field cannot arrive as
|
|
420
|
+
* anything else, so there is nothing to normalize. */
|
|
416
421
|
if (!guard.bytes.isByteSource(m.header.senderNonce) ||
|
|
417
422
|
guard.bytes.lengthOf(m.header.senderNonce) < constants.LIMITS.CMP_MIN_NONCE_BYTES) {
|
|
418
423
|
return { code: "cmp/bad-sender-nonce", reason: "the header senderNonce is absent or shorter than 128 bits (RFC 9483 sec. 3.5)" };
|
|
@@ -442,18 +447,9 @@ function _headerChecks(m, opts) {
|
|
|
442
447
|
return null;
|
|
443
448
|
}
|
|
444
449
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
for (var i = 0; i < exts.length; i++) {
|
|
449
|
-
if (exts[i].oid !== kuOid) continue;
|
|
450
|
-
var ku;
|
|
451
|
-
try { ku = _certExtDecoders[kuOid](exts[i].value); }
|
|
452
|
-
catch (_e) { return false; }
|
|
453
|
-
return ku.digitalSignature === true;
|
|
454
|
-
}
|
|
455
|
-
return true;
|
|
456
|
-
}
|
|
450
|
+
/** @internal RFC 9483 sec. 3.2 names `digitalSignature` alone, which is stricter than the set a signature
|
|
451
|
+
* over content takes elsewhere; the shared helper carries the rest of the rule. */
|
|
452
|
+
var _keyUsageAllowsSigning = pkix.keyUsagePermits(NS, ["digitalSignature"]);
|
|
457
453
|
|
|
458
454
|
/** @internal The verifying half of RFC 9810 sec. 5.1.3.4. The peer that encapsulated holds the same
|
|
459
455
|
* shared secret, and derives the key from it through the derivation `pki.cmp.build` uses, so the two
|
|
@@ -634,7 +630,7 @@ function _certKey(c) {
|
|
|
634
630
|
|
|
635
631
|
async function _chainSigner(signer, m, opts, extra) {
|
|
636
632
|
if (!_keyUsageAllowsSigning(signer.parsed)) {
|
|
637
|
-
return { trusted: false, reason: "the signer certificate keyUsage does not assert digitalSignature (RFC 9483 sec. 3.2)" };
|
|
633
|
+
return guard.verdict.of({ trusted: false, reason: "the signer certificate keyUsage does not assert digitalSignature (RFC 9483 sec. 3.2)" });
|
|
638
634
|
}
|
|
639
635
|
var time = opts.time != null ? opts.time : new _Date();
|
|
640
636
|
var anchors = _certList(opts.trustAnchors);
|
|
@@ -657,7 +653,7 @@ async function _chainSigner(signer, m, opts, extra) {
|
|
|
657
653
|
if (opts.revocationChecker != null) buildOpts.revocationChecker = opts.revocationChecker;
|
|
658
654
|
try {
|
|
659
655
|
var res = await _engine.build(signer.der, buildOpts);
|
|
660
|
-
if (!res || res.valid !== true) return { trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" };
|
|
656
|
+
if (!res || res.valid !== true) return guard.verdict.of({ trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" });
|
|
661
657
|
var byKey = _create(null);
|
|
662
658
|
var skey = _certKey(signer.parsed); if (skey) byKey[skey] = signer.der;
|
|
663
659
|
var ci, ck;
|
|
@@ -672,12 +668,12 @@ async function _chainSigner(signer, m, opts, extra) {
|
|
|
672
668
|
var d = ck ? byKey[ck] : null;
|
|
673
669
|
if (d) _append(chain, _bufferFrom(d));
|
|
674
670
|
}
|
|
675
|
-
return { trusted: true, reason: null, chain: chain };
|
|
671
|
+
return guard.verdict.of({ trusted: true, reason: null, chain: chain });
|
|
676
672
|
} catch (e) {
|
|
677
673
|
if (e && e.code === "path/bad-input") {
|
|
678
674
|
throw _err("cmp/bad-input", "invalid trust / validation options for signer-certificate path validation: " + (e.message || e), e);
|
|
679
675
|
}
|
|
680
|
-
return { trusted: false, reason: "signer certificate path validation failed: " + (e.message || e) };
|
|
676
|
+
return guard.verdict.of({ trusted: false, reason: "signer certificate path validation failed: " + (e.message || e) });
|
|
681
677
|
}
|
|
682
678
|
}
|
|
683
679
|
|
|
@@ -878,7 +874,7 @@ async function _verify(message, opts) {
|
|
|
878
874
|
|
|
879
875
|
var KNOWN_OPEN_KEY_PACKAGE_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
880
876
|
key: 1, cert: 1, password: 1, trustAnchors: 1, intermediates: 1, time: 1,
|
|
881
|
-
authorizedBySharedSecret: 1,
|
|
877
|
+
authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1,
|
|
882
878
|
});
|
|
883
879
|
var OID_A_KEY_PACKAGE = oid.byName("aKeyPackage");
|
|
884
880
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
@@ -953,6 +949,7 @@ function _fixOpenKeyPackageOptions(opts, made) {
|
|
|
953
949
|
intermediates: _fixPoolCerts(opts.intermediates, "intermediates"),
|
|
954
950
|
time: _fixTime(opts.time),
|
|
955
951
|
exempt: opts.authorizedBySharedSecret === true,
|
|
952
|
+
allowRsa15: opts.allowUnauthenticatedRsa15 === true,
|
|
956
953
|
};
|
|
957
954
|
if (opts.authorizedBySharedSecret != null && typeof opts.authorizedBySharedSecret !== "boolean") {
|
|
958
955
|
throw _err("cmp/bad-input", "opts.authorizedBySharedSecret is a boolean stating that this entity protected its request with the shared secret");
|
|
@@ -995,8 +992,16 @@ async function _openKeyPackage(container, opts) {
|
|
|
995
992
|
* a single uniform result, and splitting that back out here would rebuild the oracle it removes. */
|
|
996
993
|
var opened;
|
|
997
994
|
try {
|
|
995
|
+
/** @internal The opt-in is the caller's, not this verb's. `pki.cms.decrypt` refuses RSAES-PKCS1-v1_5
|
|
996
|
+
* key transport over a content with no integrity tag because acceptance tells an attacker
|
|
997
|
+
* submitting chosen containers that theirs decoded. This verb takes the container itself, so it
|
|
998
|
+
* cannot establish that one arrived inside an authenticated exchange, and only the caller knows
|
|
999
|
+
* whether the containers it opens come from one. The password technique never reaches the refusal:
|
|
1000
|
+
* it is a `pwri` arm, not key transport. */
|
|
1001
|
+
var decOpts = { recipientIndex: 0 };
|
|
1002
|
+
if (opts.allowRsa15) decOpts.allowUnauthenticatedRsa15 = true;
|
|
998
1003
|
opened = await cmsDecrypt.decrypt(bytes, opts.password != null ? { password: opts.password } : { key: opts.key, cert: opts.cert },
|
|
999
|
-
|
|
1004
|
+
decOpts);
|
|
1000
1005
|
} catch (e) {
|
|
1001
1006
|
throw _keyPackageErr("the delivered key package could not be opened under the supplied key material", e);
|
|
1002
1007
|
}
|
|
@@ -1016,7 +1021,7 @@ async function _openKeyPackage(container, opts) {
|
|
|
1016
1021
|
if (!signerCert) throw _keyPackageErr("the SignedData names no signer certificate to authorize as the key generation authority");
|
|
1017
1022
|
_authorizeKga(verdict, signerCert, opts);
|
|
1018
1023
|
if (verdict.eContent == null) throw _keyPackageErr("the SignedData carries no encapsulated content, so no key package was delivered");
|
|
1019
|
-
return { keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true };
|
|
1024
|
+
return guard.verdict.of({ keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true });
|
|
1020
1025
|
}
|
|
1021
1026
|
|
|
1022
1027
|
/** @internal sec. 4.1.6: the entity "MUST use a certificate containing the additional extended key
|
|
@@ -1150,6 +1155,10 @@ function _coerceKeyPackageBytes(container) {
|
|
|
1150
1155
|
* - `authorizedBySharedSecret` (boolean) -- authorize the authority by the secret that protected
|
|
1151
1156
|
* the request rather than by a chain, which sec. 4.1.6 permits a MAC-protected exchange. Requires
|
|
1152
1157
|
* `opts.password` and refuses `opts.trustAnchors`.
|
|
1158
|
+
* - `allowUnauthenticatedRsa15` (boolean) -- forwarded to `pki.cms.decrypt`: open a container that
|
|
1159
|
+
* uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag, such as AES-CBC. Set
|
|
1160
|
+
* it only where the containers this entity opens arrive inside an authenticated exchange, because
|
|
1161
|
+
* acceptance otherwise tells an attacker submitting chosen containers that theirs decoded.
|
|
1153
1162
|
* @example
|
|
1154
1163
|
* var nb = new Date("2026-01-01T00:00:00Z"), na = new Date("2036-01-01T00:00:00Z");
|
|
1155
1164
|
* var kgaPair = await pki.key.generate("Ed25519"); // the key generation authority
|
package/lib/cms-compress.js
CHANGED
|
@@ -52,7 +52,9 @@ async function decompress(input, opts) {
|
|
|
52
52
|
if (parsed.contentTypeName !== "compressedData") throw _err("cms/unsupported-content-type", "input is not a CMS CompressedData (got " + parsed.contentTypeName + ")");
|
|
53
53
|
var alg = parsed.compressionAlgorithm;
|
|
54
54
|
if (alg.oid !== OID_ZLIB) throw _err("cms/unsupported-algorithm", "unsupported compressionAlgorithm " + (alg.name || alg.oid) + " (only id-alg-zlibCompress, RFC 3274 sec. 2)");
|
|
55
|
-
|
|
55
|
+
/** @internal Through the captured comparator, since this refusal is what a replacement answering
|
|
56
|
+
* zero would turn into an acceptance of parameters the specification forbids. */
|
|
57
|
+
if (alg.parameters != null && guard.bytes.compare(alg.parameters, NULL_PARAMS_DER) !== 0) throw _err("cms/bad-algorithm-parameters", "id-alg-zlibCompress parameters must be absent or NULL (RFC 3274 sec. 2)");
|
|
56
58
|
var eci = parsed.encapContentInfo;
|
|
57
59
|
if (eci.eContent == null) throw _err("cms/no-encapsulated-content", "the CompressedData carries no encapsulated content (a detached CompressedData cannot be decompressed)");
|
|
58
60
|
var content = _inflateBounded(eci.eContent, cap);
|