@blamejs/pki 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +7 -6
  3. package/bin/pki.js +110 -34
  4. package/lib/acme.js +91 -46
  5. package/lib/attrcert-sign.js +4 -3
  6. package/lib/cmp-build.js +16 -6
  7. package/lib/cmp-session.js +19 -3
  8. package/lib/cmp-verify.js +25 -2
  9. package/lib/cms-compress.js +3 -3
  10. package/lib/cms-digest.js +10 -5
  11. package/lib/cms-encrypt.js +6 -1
  12. package/lib/cms-sign.js +16 -0
  13. package/lib/cms-verify.js +32 -14
  14. package/lib/constants.js +1 -0
  15. package/lib/crl-sign.js +3 -3
  16. package/lib/csr-sign.js +6 -2
  17. package/lib/ct.js +16 -3
  18. package/lib/est.js +67 -31
  19. package/lib/guard-all.js +2 -0
  20. package/lib/guard-distrust.js +224 -0
  21. package/lib/guard-identifier.js +27 -1
  22. package/lib/guard-json.js +1 -1
  23. package/lib/http-transport.js +299 -36
  24. package/lib/jose.js +1 -1
  25. package/lib/lint.js +67 -7
  26. package/lib/ocsp-verify.js +9 -3
  27. package/lib/ocsp.js +16 -4
  28. package/lib/oid.js +4 -0
  29. package/lib/path-validate.js +119 -18
  30. package/lib/pkcs12-build.js +6 -1
  31. package/lib/pki-build.js +38 -6
  32. package/lib/possession.js +6 -2
  33. package/lib/related-cert.js +2 -2
  34. package/lib/revocation-fetch.js +46 -11
  35. package/lib/scep.js +39 -14
  36. package/lib/schema-pkix.js +3 -3
  37. package/lib/sigstore.js +11 -6
  38. package/lib/smime.js +32 -16
  39. package/lib/tls-cert-compress.js +6 -5
  40. package/lib/trust.js +121 -20
  41. package/lib/trustanchor-build.js +16 -1
  42. package/lib/tsp-sign.js +15 -2
  43. package/lib/webauthn-mds.js +29 -8
  44. package/lib/webauthn.js +46 -14
  45. package/lib/x509-sign.js +26 -10
  46. package/package.json +2 -2
  47. package/sbom.cdx.json +6 -6
package/lib/cms-digest.js CHANGED
@@ -51,7 +51,7 @@ var _DIGEST_OPTS = intrinsic.assign(intrinsic.create(null), {
51
51
  var _VERIFY_DIGEST_OPTS = intrinsic.assign(intrinsic.create(null), { content: 1 });
52
52
 
53
53
  function _digestName(opts) {
54
- var name = opts.digestAlgorithm === undefined ? "sha256" : opts.digestAlgorithm;
54
+ var name = opts.digestAlgorithm == null ? "sha256" : opts.digestAlgorithm;
55
55
  var key = guard.text.keyOf(name);
56
56
  if (!NODE_DIGEST[key]) {
57
57
  throw _err("cms/unsupported-algorithm", "unsupported digest algorithm " + guard.text.showValue(name) +
@@ -78,7 +78,8 @@ function _digestName(opts) {
78
78
  * @opts
79
79
  * - `digestAlgorithm` (string) -- `sha256` (default), `sha384` or `sha512`.
80
80
  * - `contentType` (string) -- the encapsulated content type by OID name, default `data`.
81
- * - `detached` (boolean) -- omit the content, leaving the digest to travel alone.
81
+ * - `detached` (boolean) -- omit the content, leaving the digest to travel alone. A value other
82
+ * than `true`, `false`, `null` or an absent option is refused `cms/bad-input`.
82
83
  * - `pem` (boolean) -- return a PEM `CMS` block instead of DER.
83
84
  * @example
84
85
  * var der = await pki.cms.digest(Buffer.from("hello"));
@@ -94,14 +95,18 @@ async function _digest(content, opts) {
94
95
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.digest options");
95
96
  guard.identifier.assertKnownKeys(opts, _DIGEST_OPTS, _err, "cms/bad-input",
96
97
  "pki.cms.digest has an unknown option ");
98
+ /** @internal Whether the content rides in the message is decided below by comparing this option
99
+ * to `true`, so a value of any other type attached a content the caller asked to leave out. */
100
+ var detached = guard.identifier.booleanOption(opts.detached, _err, "cms/bad-input",
101
+ "pki.cms.digest opts.detached");
97
102
  var raw = guard.bytes.source(content, CmsError, "cms/bad-input", "content");
98
103
  var alg = _digestName(opts);
99
- var ctName = opts.contentType === undefined ? "data" : opts.contentType;
104
+ var ctName = opts.contentType == null ? "data" : opts.contentType;
100
105
  var ctOid = O(ctName);
101
106
  if (!ctOid) throw _err("cms/bad-input", "opts.contentType is not a known OID name: " + guard.text.showValue(ctName));
102
107
 
103
108
  var value = guard.crypto.digest(NODE_DIGEST[alg], raw);
104
- var eciChildren = opts.detached === true
109
+ var eciChildren = detached
105
110
  ? [b.oid(ctOid)]
106
111
  : [b.oid(ctOid), b.explicit(0, b.octetString(raw))];
107
112
  var dd = b.sequence([
@@ -164,7 +169,7 @@ async function _verifyDigest(input, opts) {
164
169
  guard.der.assertParams(parsed.digestAlgorithm.parameters, "absentOrNull", _err,
165
170
  "cms/unsupported-algorithm", parsed.digestAlgorithm.name + " digest algorithm (RFC 5754 sec. 2)");
166
171
  var embedded = parsed.encapContentInfo.eContent;
167
- var supplied = opts.content === undefined ? null
172
+ var supplied = opts.content == null ? null
168
173
  : guard.bytes.snapshotSource(opts.content, CmsError, "cms/bad-input", "opts.content");
169
174
  if (embedded != null && supplied != null) {
170
175
  throw _err("cms/bad-input", "the DigestedData carries its own content, so opts.content is redundant");
@@ -649,7 +649,12 @@ function authenticate(content, recipients, opts) {
649
649
 
650
650
  async function _authenticateImpl(content, recipients, opts) {
651
651
  opts = opts || intrinsic.create(null);
652
- var withAttrs = opts.authenticatedAttributes !== false;
652
+ /** @internal Typed before it is used, because this switch picks the option set the door below
653
+ * checks against as well as the message that gets built: with `authenticatedAttributes: false`
654
+ * the MAC covers the content octets directly, so a value of any other type selected a
655
+ * structurally different message than the caller asked for and said nothing. */
656
+ var withAttrs = guard.identifier.booleanOptionDefaultOn(opts.authenticatedAttributes, _err,
657
+ "cms/bad-input", "pki.cms.authenticate opts.authenticatedAttributes");
653
658
  guard.identifier.assertKnownKeys(opts, withAttrs ? _AUTHENTICATE_OPTS_ATTRS : _AUTHENTICATE_OPTS_BARE,
654
659
  _err, "cms/bad-input", function (k) {
655
660
  return "pki.cms.authenticate has an unknown option " + JSON.stringify(k) +
package/lib/cms-sign.js CHANGED
@@ -186,6 +186,19 @@ function _algorithmProtectionPair(attrOid, digestAlgId, armAlgId, armTag) {
186
186
  * The emitter below reads the same option again, which is sound because the options door refuses
187
187
  * one supplied through an accessor, own or inherited, before any value is read: two reads of a
188
188
  * plain field are one answer. */
189
+ /** @internal The three switches these verbs document, typed where the options arrive. Each is read
190
+ * further down by comparing it to `false`, which answers for a value of any other type as the
191
+ * state the caller did not ask for: measured, `certificates: "false"` embedded the signer
192
+ * certificate the caller asked to withhold, and `detached: "false"` is read as "not detached" by
193
+ * the shape test and as "detached" by the content test, emitting an attached SignedData with no
194
+ * content in it. Typed here, those comparisons read a value already known to be a boolean.
195
+ * `detached` is not an option every one of these verbs accepts; an absent one types as absent. */
196
+ function _assertSignSwitches(opts, verb) {
197
+ guard.identifier.booleanOptionDefaultOn(opts.certificates, _err, "cms/bad-input", verb + " opts.certificates");
198
+ guard.identifier.booleanOptionDefaultOn(opts.signedAttributes, _err, "cms/bad-input", verb + " opts.signedAttributes");
199
+ guard.identifier.booleanOption(opts.detached, _err, "cms/bad-input", verb + " opts.detached");
200
+ }
201
+
189
202
  function _assertProtectionPlaceable(opts) {
190
203
  var wanted = cms.algorithmProtectionOid(opts.algorithmProtection, _err);
191
204
  if (opts.signedAttributes === false && wanted) {
@@ -373,6 +386,7 @@ function _signDispatch(content, signers, opts) {
373
386
  async function _signStream(stream, signers, opts) {
374
387
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
375
388
  guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
389
+ _assertSignSwitches(opts, "pki.cms.sign");
376
390
  _assertProtectionPlaceable(opts);
377
391
  if (opts.detached !== true) {
378
392
  throw _err("cms/bad-input", "a streaming (async-iterable) content requires opts.detached: true; an attached " +
@@ -413,6 +427,7 @@ async function _signStream(stream, signers, opts) {
413
427
  function _sign(content, signers, opts) {
414
428
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
415
429
  guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
430
+ _assertSignSwitches(opts, "pki.cms.sign");
416
431
  _assertProtectionPlaceable(opts);
417
432
  var contentBuf = _toBuf(content, "content");
418
433
  var list = Array.isArray(signers) ? _b.reqDenseArray(signers, "the signer list") : [signers];
@@ -680,6 +695,7 @@ function countersign(cmsInput, signers, opts) {
680
695
  function _countersign(cmsInput, signers, opts) {
681
696
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.countersign options");
682
697
  guard.identifier.assertKnownKeys(opts, KNOWN_COUNTERSIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
698
+ _assertSignSwitches(opts, "pki.cms.countersign");
683
699
  _assertProtectionPlaceable(opts);
684
700
  var list = Array.isArray(signers) ? signers : [signers];
685
701
  if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
package/lib/cms-verify.js CHANGED
@@ -761,6 +761,7 @@ async function _verifyOneTimestamp(attr, targetSig, claimedSigningTime, opts) {
761
761
  trustAnchors: opts.timestampTrustAnchors,
762
762
  revocationChecker: opts.timestampRevocationChecker,
763
763
  allowWeakDigests: opts.allowWeakDigests === true,
764
+ distrustedKeys: opts.distrustedKeys,
764
765
  });
765
766
  } catch (e) {
766
767
  return guard.verdict.of({ valid: false, trusted: false, genTime: null,
@@ -975,6 +976,10 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
975
976
  * trust decision from the signer: an operator may anchor one and not the other.
976
977
  * @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
977
978
  * takes one. Each row reports what it established in `revocationChecked`.
979
+ * @opts distrustedKeys SHA-256 digests of SubjectPublicKeyInfo DER (64 hexadecimal characters or
980
+ * 32 bytes each, see `pki.trust.spkiDigest`), applied to the signer chain and to the
981
+ * TSA chain of every timestamp row. A listed key on a chain, the anchor included,
982
+ * reports that signer or that row as not trusted.
978
983
  * @opts requireAlgorithmProtection Fail a signer that carries no RFC 6211
979
984
  * `CMSAlgorithmProtection` attribute, with `cms/missing-algorithm-protection`. Off
980
985
  * by default: RFC 6211 says nothing about absence and RFC 8933 sec. 4.1 makes
@@ -1026,7 +1031,7 @@ var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content:
1026
1031
  * leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
1027
1032
  * two answers one. */
1028
1033
  timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1,
1029
- requireAlgorithmProtection: 1 });
1034
+ requireAlgorithmProtection: 1, distrustedKeys: 1 });
1030
1035
 
1031
1036
  function verify(input, opts) {
1032
1037
  return guard.async.deferred(function () { return _verify(input, opts); });
@@ -1043,12 +1048,15 @@ async function _verify(input, opts) {
1043
1048
  var allowWeakDigests = opts.allowWeakDigests === true;
1044
1049
  var requireProtection = guard.identifier.booleanOption(opts.requireAlgorithmProtection, _err,
1045
1050
  "cms/bad-input", "pki.cms.verify opts.requireAlgorithmProtection");
1051
+ /** @internal Typed here, before any signer is judged: the signer chain is built only for a signer
1052
+ * that verified, and every throw from a timestamp verify becomes a row. One list serves both. */
1053
+ var distrustedKeys = guard.distrust.keys(opts.distrustedKeys, _err, "cms/bad-input", "pki.cms.verify opts.distrustedKeys");
1046
1054
  var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
1047
1055
  return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
1048
1056
  }, _err, "cms/bad-input", "the SignedData");
1049
1057
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
1050
- var trustCfg = _snapshotTrust(opts);
1051
- var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
1058
+ var trustCfg = _snapshotTrust(opts, distrustedKeys);
1059
+ var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests, distrustedKeys);
1052
1060
  var parsedCerts = [];
1053
1061
  (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
1054
1062
  (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
@@ -1130,7 +1138,7 @@ function _cloneAnchorValue(v, depth) {
1130
1138
  }
1131
1139
  return out;
1132
1140
  }
1133
- function _snapshotTrust(opts) {
1141
+ function _snapshotTrust(opts, distrustedKeys) {
1134
1142
  var raw = opts.trustAnchors;
1135
1143
  var anchors = null;
1136
1144
  if (raw != null) {
@@ -1140,9 +1148,10 @@ function _snapshotTrust(opts) {
1140
1148
  }
1141
1149
  return {
1142
1150
  trustAnchors: anchors,
1143
- time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time === undefined ? new intrinsic.Date() : opts.time),
1151
+ time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time == null ? new intrinsic.Date() : opts.time),
1144
1152
  requiredEku: Array.isArray(opts.requiredEku) ? guard.list.snapshot(opts.requiredEku) : opts.requiredEku,
1145
1153
  checkPurpose: opts.checkPurpose,
1154
+ distrustedKeys: distrustedKeys,
1146
1155
  };
1147
1156
  }
1148
1157
 
@@ -1153,7 +1162,7 @@ function _snapshotTrust(opts) {
1153
1162
  * authorities. The certificates are copied by value, since a caller holding the array could swap an
1154
1163
  * entry after the row that read it. The checker is a function and is captured as the one reference
1155
1164
  * every row calls. */
1156
- function _snapshotTimestampTrust(opts, allowWeakDigests) {
1165
+ function _snapshotTimestampTrust(opts, allowWeakDigests, distrustedKeys) {
1157
1166
  var certs = opts.timestampCerts;
1158
1167
  var anchors = opts.timestampTrustAnchors;
1159
1168
  return {
@@ -1167,6 +1176,7 @@ function _snapshotTimestampTrust(opts, allowWeakDigests) {
1167
1176
  }),
1168
1177
  timestampRevocationChecker: opts.timestampRevocationChecker,
1169
1178
  allowWeakDigests: allowWeakDigests,
1179
+ distrustedKeys: distrustedKeys,
1170
1180
  };
1171
1181
  }
1172
1182
 
@@ -1200,7 +1210,7 @@ async function _applyTrust(res, parsedCerts, cfg) {
1200
1210
  if (!anchors.length) {
1201
1211
  throw _err("cms/bad-input", "opts.trustAnchors is empty -- name at least one root, or omit it to state that the signer is not being anchored");
1202
1212
  }
1203
- if (cfg.time !== undefined) guard.time.assertValid(cfg.time, _err, "cms/bad-input", "opts.time");
1213
+ if (cfg.time != null) guard.time.assertValid(cfg.time, _err, "cms/bad-input", "opts.time");
1204
1214
  var purposes = _engine.resolvePurposeOpts({ requiredEku: cfg.requiredEku, checkPurpose: cfg.checkPurpose });
1205
1215
  intrinsic.forEach(anchors, function (a) {
1206
1216
  _engine.toAnchor(a);
@@ -1210,7 +1220,7 @@ async function _applyTrust(res, parsedCerts, cfg) {
1210
1220
  var s = res.signers[_si];
1211
1221
  {
1212
1222
  if (s.ok !== true || !s.cert) { s.trusted = false; continue; }
1213
- var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at };
1223
+ var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at, distrustedKeys: cfg.distrustedKeys };
1214
1224
  if (cfg.requiredEku != null) buildOpts.requiredEku = cfg.requiredEku;
1215
1225
  if (cfg.checkPurpose != null) buildOpts.checkPurpose = cfg.checkPurpose;
1216
1226
  if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; continue; }
@@ -1275,16 +1285,20 @@ function _addCert(out, der) {
1275
1285
  * the same content as the buffered form.
1276
1286
  *
1277
1287
  * @opts detached Omit the encapsulated content (a detached signature; the verifier
1278
- * supplies the content). Default false.
1288
+ * supplies the content). Default false. A value other than `true`,
1289
+ * `false`, `null` or an absent option is refused `cms/bad-input`.
1279
1290
  * @opts eContentType The encapsulated content type (an OID name). Default `data`.
1280
1291
  * @opts signedAttributes Include signed attributes (content-type, message-digest, signing-time);
1281
- * false signs the content directly. Default true.
1292
+ * false signs the content directly. Default true. A value other than
1293
+ * `true`, `false`, `null` or an absent option is refused `cms/bad-input`.
1282
1294
  * @opts additionalSignedAttributes Extra signed attributes to add to every SignerInfo, each
1283
1295
  * `{ type, values }` (`type` an OID name or dotted string, `values` an
1284
1296
  * array of DER value `Buffer`s). Covered by the signature.
1285
1297
  * @opts signingTime A `Date` for the signing-time attribute, or false to omit it.
1286
1298
  * @opts sid `"issuerAndSerial"` (default) or `"ski"` (subjectKeyIdentifier).
1287
- * @opts certificates Embed the signer certificates in the output. Default true.
1299
+ * @opts certificates Embed the signer certificates in the output. Default true. A value other
1300
+ * than `true`, `false`, `null` or an absent option is refused
1301
+ * `cms/bad-input`.
1288
1302
  * @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
1289
1303
  * @opts unsignedAttributes Unsigned attributes for every SignerInfo, each `{ type, values }`
1290
1304
  * (`type` an OID name or dotted string, `values` an array of DER value
@@ -1341,12 +1355,15 @@ var sign = cmsSign.sign;
1341
1355
  * instead of the primary signature (a nested countersignature).
1342
1356
  * @opts signedAttributes Include signed attributes (message-digest + signing-time; never a
1343
1357
  * content-type). Default true; false signs the target signature octets
1344
- * directly.
1358
+ * directly. A value other than `true`, `false`, `null` or an absent
1359
+ * option is refused `cms/bad-input`.
1345
1360
  * @opts signingTime A `Date` for the countersignature's signing-time, or false to omit it.
1346
1361
  * @opts additionalSignedAttributes Extra signed attributes on the countersignature (a content-type
1347
1362
  * is rejected, RFC 5652 sec. 11.4).
1348
1363
  * @opts sid `"issuerAndSerial"` (default) or `"ski"`.
1349
- * @opts certificates Embed the countersigner certificate(s) in the SignedData. Default true.
1364
+ * @opts certificates Embed the countersigner certificate(s) in the SignedData. Default
1365
+ * true. A value other than `true`, `false`, `null` or an absent option
1366
+ * is refused `cms/bad-input`.
1350
1367
  * @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` attribute on the
1351
1368
  * countersignature, copying ITS own digest and signature algorithm
1352
1369
  * identifiers rather than the countersigned signature's. Off by
@@ -1440,7 +1457,8 @@ var encrypt = cmsEncrypt.encrypt;
1440
1457
  * @opts digestAlgorithm The authenticated-attributes message-digest hash (defaults to the MAC hash).
1441
1458
  * @opts authenticatedAttributes Include the content-type + message-digest authenticated attributes
1442
1459
  * (the default, and required for a non-`data` content type); false MACs the
1443
- * content directly.
1460
+ * content directly. A value other than `true`, `false`, `null` or an absent
1461
+ * option is refused `cms/bad-input`.
1444
1462
  * @opts authAttrs Extra authenticated attributes appended to the content-type + message-digest pair.
1445
1463
  * @opts contentType The encapsulated content type (an OID name). Default `data`.
1446
1464
  * @opts keyIdentifier `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
package/lib/constants.js CHANGED
@@ -165,6 +165,7 @@ var LIMITS = Object.assign(Object.create(null), {
165
165
  MERKLE_MAX_PROOF_NODES: 65,
166
166
  MERKLE_MAX_LEAVES: 1048576,
167
167
  PROXY_MAX_TLS_ANCHORS: 256,
168
+ DISTRUSTED_KEYS_MAX: 4096,
168
169
  PATH_MAX_CERTS: 100,
169
170
  PATH_MAX_POLICY_NODES: 4096,
170
171
  PATH_BUILD_MAX_DEPTH: 20,
package/lib/crl-sign.js CHANGED
@@ -770,16 +770,16 @@ function isRevoked(crl, serialNumber, opts) {
770
770
  return "pki.crl.isRevoked has an unknown option " + JSON.stringify(k) +
771
771
  "; it takes `time` and `historicalMode`";
772
772
  });
773
- if (o.historicalMode !== undefined && o.time === undefined) {
773
+ if (o.historicalMode != null && o.time == null) {
774
774
  throw _err("crl/bad-input", "pki.crl.isRevoked opts.historicalMode says how to read an entry " +
775
775
  "against the instant asked about, so it needs opts.time as well");
776
776
  }
777
- if (o.historicalMode !== undefined && typeof o.historicalMode !== "boolean") {
777
+ if (o.historicalMode != null && typeof o.historicalMode !== "boolean") {
778
778
  throw _err("crl/bad-input", "pki.crl.isRevoked opts.historicalMode must be a boolean");
779
779
  }
780
780
  var parsed = _coerceCrl(crl);
781
781
  var hex = _serialHexOf(serialNumber);
782
- if (o.time !== undefined) {
782
+ if (o.time != null) {
783
783
  guard.time.assertValid(o.time, _err, "crl/bad-input", "pki.crl.isRevoked opts.time");
784
784
  var at = guard.time.instantOf(o.time);
785
785
  if (at < guard.time.instantOf(parsed.thisUpdate)) {
package/lib/csr-sign.js CHANGED
@@ -114,7 +114,7 @@ function _possessionStatement(spec) {
114
114
  "privateKeyPossessionStatement needs the certificate: this verb resolves the signature algorithm from its key and checks the signature against it. Pass includeCertificate: false to leave it off the wire, which RFC 9883 sec. 3 allows");
115
115
  }
116
116
  var certDer = _b.reqDer(spec.certificate, "privateKeyPossessionStatement certificate");
117
- var include = spec.includeCertificate === undefined ? true : spec.includeCertificate;
117
+ var include = spec.includeCertificate == null ? true : spec.includeCertificate;
118
118
  if (include !== true && include !== false) {
119
119
  throw _err("csr/bad-input", "privateKeyPossessionStatement includeCertificate is a flag, so it is true, false or omitted");
120
120
  }
@@ -172,7 +172,11 @@ function _relatedCertRequest(spec) {
172
172
  * subject-owned set `pki.x509.sign` takes, or an array of pre-encoded Extension DER) and
173
173
  * `challengePassword`. An extension the issuing CA assigns (authorityKeyIdentifier, the
174
174
  * certificate-transparency pair, the CA version and previous-certificate hash, ocspNoCheck) is not a
175
- * request and is refused by name. `key` (or
175
+ * request and is refused by name. The per-extension criticality flags `pki.x509.sign` takes
176
+ * (`keyUsageCritical`, default true, and `extendedKeyUsageCritical`,
177
+ * `certificatePoliciesCritical`, `policyMappingsCritical`, `qcStatementsCritical`,
178
+ * `tlsFeatureCritical`, each default false) apply here too; each is a boolean, and a value other
179
+ * than `true`, `false`, `null` or an absent flag is refused `csr/bad-input`. `key` (or
176
180
  * `{ key }`) is the subject's own PKCS#8 private key, WebCrypto CryptoKey, or signer
177
181
  * `{ algorithm, publicKey, sign }`, so the request is self-signed
178
182
  * to prove possession of the private half of `subjectPublicKey`, and that proof is verified before the
package/lib/ct.js CHANGED
@@ -1216,6 +1216,9 @@ function _trustValue(v, field) {
1216
1216
  return v;
1217
1217
  }
1218
1218
 
1219
+ /** @internal The settings `_tlsForFetch` reads. A setting not on the list is refused at the door. */
1220
+ var _TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, ["anchors", "useSystemStore", "cert", "key",
1221
+ "minVersion", "servername", "checkServerIdentity", "distrustedKeys"]));
1219
1222
  function _tlsForFetch(opts) {
1220
1223
  /** @internal A record with NO prototype where the caller supplied no TLS at all, because the empty
1221
1224
  * object a literal gives inherits from `Object.prototype` and every field below is then answerable
@@ -1232,9 +1235,12 @@ function _tlsForFetch(opts) {
1232
1235
  anchors = intrinsic.isArray(anchors)
1233
1236
  ? guard.list.copyMap(anchors, function (a, i) { return _trustValue(a, "opts.tls.anchors[" + i + "]"); })
1234
1237
  : _trustValue(anchors, "opts.tls.anchors");
1238
+ /** @internal The distrust list is typed into a frozen copy. `guard.distrust.keys` reads each entry
1239
+ * through its descriptor and refuses an accessor, a hole and a Proxy, so no caller code runs. */
1240
+ var distrustedKeys = guard.distrust.keys(t.distrustedKeys, _ctErr, "ct/bad-input", "opts.tls.distrustedKeys");
1235
1241
  return { anchors: anchors, useSystemStore: t.useSystemStore, cert: _trustValue(t.cert, "opts.tls.cert"),
1236
1242
  key: _trustValue(t.key, "opts.tls.key"), minVersion: t.minVersion, servername: t.servername,
1237
- checkServerIdentity: t.checkServerIdentity };
1243
+ checkServerIdentity: t.checkServerIdentity, distrustedKeys: distrustedKeys };
1238
1244
  }
1239
1245
 
1240
1246
  function _sameOrigin(a, b) { return _urlOrigin(new _URL(a)) === _urlOrigin(new _URL(b)); }
@@ -1418,6 +1424,7 @@ function _settleDecisive(o, keyName) {
1418
1424
  guard.identifier.refuseAccessorFields(o.tls,
1419
1425
  guard.identifier.readableNames(o.tls, _ctErr, "ct/bad-input", "opts.tls"),
1420
1426
  _ctErr, "ct/bad-input", "opts.tls");
1427
+ guard.identifier.assertKnownKeys(o.tls, _TLS_KNOWN, _ctErr, "ct/bad-input", "unknown opts.tls setting ");
1421
1428
  /** @internal And the ELEMENTS of the anchor list, which refusing accessors on `tls` itself does not
1422
1429
  * reach. An accessor on `anchors[0]` runs when the list is copied, and one that overwrote
1423
1430
  * `sth.rootHash` with the leaf being proven had an empty audit path verify. The refusal has to go
@@ -1652,11 +1659,17 @@ async function verifySth(sth, logKey) {
1652
1659
  *
1653
1660
  * `treeSize` and `timestamp` come back as `BigInt`, since both are uint64 on the wire.
1654
1661
  *
1662
+ * `opts.tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest). A
1663
+ * log server whose chain carries a listed key is refused with `ct/server-key-distrusted`, and a
1664
+ * malformed list is `ct/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`,
1665
+ * `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused
1666
+ * `ct/bad-input`. The other sec. 4 messages read it the same way.
1667
+ *
1655
1668
  * @opts
1656
1669
  * url: string, // the log's base URL, with or without the ct/v1/ prefix
1657
1670
  * logKey: BufferSource, // the log's SubjectPublicKeyInfo, pinned by the caller
1658
1671
  * transport: function, // optional (request) => Promise<{ status, headers, body }>
1659
- * tls: object, // optional { anchors, useSystemStore, ... } when no transport is given
1672
+ * tls: object, // optional { anchors, useSystemStore, distrustedKeys, ... } when no transport is given
1660
1673
  * headers: object, // optional extra request headers
1661
1674
  * timeout: number, // optional per-request timeout in milliseconds
1662
1675
  * maxResponseBytes: number, // optional response cap
@@ -2194,7 +2207,7 @@ function _b64Array(obj, name, code, label, exactLen, maxCount) {
2194
2207
  * @opts signerKey REQUIRED and caller-pinned -- the distributor SubjectPublicKeyInfo as a DER Buffer; no baked-in key.
2195
2208
  * @opts sigUrl OPTIONAL -- the detached `log_list.sig` URL (https, must share the log-list URL's origin); default `url` with `.json` -> `.sig` (a non-.json url requires an explicit sigUrl).
2196
2209
  * @opts transport OPTIONAL injectable `transport(request) -> Promise<{status,headers,body}>` (default `pki.transport.https`); the test seam.
2197
- * @opts tls OPTIONAL `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }` threaded to the default transport (ignored when a transport is injected); `rejectUnauthorized` is always on.
2210
+ * @opts tls OPTIONAL `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }` threaded to the default transport (ignored when a transport is injected); `rejectUnauthorized` is always on. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest): a distributor whose chain carries a listed key is refused with `ct/server-key-distrusted`, and a malformed list is `ct/bad-input`. A setting not named in that list is refused `ct/bad-input`.
2198
2211
  * @opts headers OPTIONAL extra request headers (the request-framing headers are stripped; the verb owns the GET method).
2199
2212
  * @opts timeout OPTIONAL ms budget, default 30s (cap-validated).
2200
2213
  * @opts maxResponseBytes OPTIONAL per-GET size cap, default 4 MiB, tightenable DOWNWARD only.