@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/acme.js CHANGED
@@ -321,10 +321,41 @@ function _checkType(kind, field, value, obj) {
321
321
  return null;
322
322
  }
323
323
 
324
- function _codeSlug(kind) {
325
- var out = "";
326
- for (var i = 0; i < kind.length; i++) { var c = _charCodeAt(kind, i); if (c >= 65 && c <= 90) out += "-"; out += _charAt(kind, i); }
327
- return _lower(out);
324
+ /** @internal The code each resource kind is refused with, written out. A code assembled at
325
+ * runtime from the kind is not a frozen contract: it cannot be read off the source, so the
326
+ * api-snapshot recorded the stem and none of these six, and renaming or losing one would
327
+ * have passed every gate. An unknown kind cannot reach here (the caller's kind is checked
328
+ * first), and falls back to the module's general input code rather than inventing one. */
329
+ var BAD_BY_KIND = intrinsic.assign(intrinsic.create(null), {
330
+ directory: "acme/bad-directory",
331
+ account: "acme/bad-account",
332
+ order: "acme/bad-order",
333
+ authorization: "acme/bad-authorization",
334
+ challenge: "acme/bad-challenge",
335
+ renewalInfo: "acme/bad-renewal-info",
336
+ ordersList: "acme/bad-orders-list",
337
+ });
338
+ /** @internal The table above is written out so the codes can be read off the source, and this
339
+ * holds it to SPECS so it cannot drift from the kinds that use it: a kind added to SPECS
340
+ * without a code here would have been refused with the general input code, losing the one
341
+ * that names the resource. Measured the hard way, by omitting `ordersList`. */
342
+ (function () {
343
+ var kinds = _objectKeys(SPECS);
344
+ for (var i = 0; i < kinds.length; i++) {
345
+ if (BAD_BY_KIND[kinds[i]] === undefined) {
346
+ throw new Error("acme: SPECS kind " + kinds[i] + " has no entry in BAD_BY_KIND");
347
+ }
348
+ }
349
+ var coded = _objectKeys(BAD_BY_KIND);
350
+ for (var j = 0; j < coded.length; j++) {
351
+ if (SPECS[coded[j]] === undefined) {
352
+ throw new Error("acme: BAD_BY_KIND names " + coded[j] + ", which is not a SPECS kind");
353
+ }
354
+ }
355
+ })();
356
+ function _badKindCode(kind) {
357
+ var code = BAD_BY_KIND[guard.text.keyOf(kind)];
358
+ return code === undefined ? "acme/bad-input" : code;
328
359
  }
329
360
 
330
361
  /** @internal The NAMED members of a caller's resource object, read ONCE each into a plain record. A
@@ -363,7 +394,7 @@ var SPEC_NAMES = intrinsic.create(null);
363
394
  })();
364
395
 
365
396
  function _validate(kind, obj, pre) {
366
- if (!_isObject(obj)) throw E("acme/bad-" + _codeSlug(kind), "an ACME " + kind + " must be a JSON object");
397
+ if (!_isObject(obj)) throw E(_badKindCode(kind), "an ACME " + kind + " must be a JSON object");
367
398
  var spec = SPECS[kind];
368
399
  /** @internal Every own member is read ONCE, here, before any check runs, and every check below decides
369
400
  * from this reading. The walk used to read `obj[field.name]` three times per field, for the closed-enum
@@ -396,7 +427,7 @@ function _validate(kind, obj, pre) {
396
427
  throw E("acme/bad-status", "the " + kind + " " + field.name + " " + JSON.stringify(value) + " is not a recognized value");
397
428
  }
398
429
  var err = _checkType(kind, field, value, read);
399
- if (err) throw E("acme/bad-" + _codeSlug(kind), "the " + kind + " field " + JSON.stringify(field.name) + " " + err);
430
+ if (err) throw E(_badKindCode(kind), "the " + kind + " field " + JSON.stringify(field.name) + " " + err);
400
431
  }
401
432
  return obj;
402
433
  }
@@ -866,16 +897,16 @@ function newAccount(o) {
866
897
  o = _gateRequestOpts(o, "newAccount");
867
898
  if (!_isObject(o.jwk)) throw E("acme/bad-input", "newAccount must embed the account public jwk (RFC 8555 sec. 7.3)");
868
899
  var payload = {};
869
- if (o.contact !== undefined) { _assertContacts(o.contact); payload.contact = o.contact; }
870
- if (o.termsOfServiceAgreed !== undefined) {
900
+ if (o.contact != null) { _assertContacts(o.contact); payload.contact = o.contact; }
901
+ if (o.termsOfServiceAgreed != null) {
871
902
  if (typeof o.termsOfServiceAgreed !== "boolean") throw E("acme/bad-input", "termsOfServiceAgreed must be a boolean");
872
903
  payload.termsOfServiceAgreed = o.termsOfServiceAgreed;
873
904
  }
874
- if (o.onlyReturnExisting !== undefined) {
905
+ if (o.onlyReturnExisting != null) {
875
906
  if (typeof o.onlyReturnExisting !== "boolean") throw E("acme/bad-input", "onlyReturnExisting must be a boolean");
876
907
  payload.onlyReturnExisting = o.onlyReturnExisting;
877
908
  }
878
- if (o.externalAccountBinding !== undefined) {
909
+ if (o.externalAccountBinding != null) {
879
910
  if (!_isObject(o.externalAccountBinding)) throw E("acme/bad-input", "externalAccountBinding must be an EAB inner JWS object");
880
911
  payload.externalAccountBinding = o.externalAccountBinding;
881
912
  }
@@ -969,9 +1000,9 @@ function newOrder(o) {
969
1000
  o = _gateRequestOpts(o, "newOrder");
970
1001
  if (!Array.isArray(o.identifiers) || o.identifiers.length === 0) throw E("acme/bad-order", "newOrder requires a non-empty identifiers array (RFC 8555 sec. 7.4)");
971
1002
  var payload = { identifiers: o.identifiers.map(_validateOrderIdentifier) };
972
- if (o.notBefore !== undefined) { if (!_isRfc3339(o.notBefore)) throw E("acme/bad-order", "notBefore must be an RFC 3339 date-time"); payload.notBefore = o.notBefore; }
973
- if (o.notAfter !== undefined) { if (!_isRfc3339(o.notAfter)) throw E("acme/bad-order", "notAfter must be an RFC 3339 date-time"); payload.notAfter = o.notAfter; }
974
- if (o.replaces !== undefined) { if (!_isString(o.replaces)) throw E("acme/bad-order", "replaces must be an ARI certID string (RFC 9773 sec. 5)"); payload.replaces = o.replaces; }
1003
+ if (o.notBefore != null) { if (!_isRfc3339(o.notBefore)) throw E("acme/bad-order", "notBefore must be an RFC 3339 date-time"); payload.notBefore = o.notBefore; }
1004
+ if (o.notAfter != null) { if (!_isRfc3339(o.notAfter)) throw E("acme/bad-order", "notAfter must be an RFC 3339 date-time"); payload.notAfter = o.notAfter; }
1005
+ if (o.replaces != null) { if (!_isString(o.replaces)) throw E("acme/bad-order", "replaces must be an ARI certID string (RFC 9773 sec. 5)"); payload.replaces = o.replaces; }
975
1006
  return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
976
1007
  });
977
1008
  }
@@ -1154,7 +1185,7 @@ async function finalize(o) {
1154
1185
  if (!guard.bytes.isByteSource(o.csr)) throw E("acme/bad-input", "finalize requires a DER CSR BufferSource (opts.csr)");
1155
1186
  var csrBuf = guard.bytes.snapshotSource(o.csr, E, "acme/bad-input", "the finalize CSR");
1156
1187
  var ids = null;
1157
- if (identifiers !== undefined) {
1188
+ if (identifiers != null) {
1158
1189
  if (!Array.isArray(identifiers) || identifiers.length === 0) throw E("acme/bad-input", "finalize identifiers must be the non-empty order identifier array");
1159
1190
  ids = [];
1160
1191
  for (var ii = 0; ii < identifiers.length; ii++) {
@@ -1165,7 +1196,7 @@ async function finalize(o) {
1165
1196
  }
1166
1197
  }
1167
1198
  var parsed = csr.parse(csrBuf);
1168
- if (accountJwk !== undefined) {
1199
+ if (accountJwk != null) {
1169
1200
  var accountSpki = await _jwkToSpki(accountJwk);
1170
1201
  if (parsed.subjectPublicKeyInfo && Buffer.isBuffer(parsed.subjectPublicKeyInfo.bytes) &&
1171
1202
  parsed.subjectPublicKeyInfo.bytes.equals(accountSpki)) {
@@ -1202,7 +1233,7 @@ function challengeResponse(o) {
1202
1233
  return _promised(function () {
1203
1234
  if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
1204
1235
  o = _gateRequestOpts(o, "challengeResponse");
1205
- var payload = o.payload !== undefined ? o.payload : {};
1236
+ var payload = o.payload != null ? o.payload : {};
1206
1237
  if (!_isObject(payload)) throw E("acme/bad-input", "a challenge response payload must be a JSON object (RFC 8555 sec. 7.5.1)");
1207
1238
  return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
1208
1239
  });
@@ -1301,7 +1332,7 @@ function revokeCert(o) {
1301
1332
  var hasJwk = _hasOwn(o, "jwk");
1302
1333
  if (hasKid === hasJwk) throw E("acme/bad-input", "revokeCert must be signed with EXACTLY ONE of the account kid or the certificate jwk (RFC 8555 sec. 7.6)");
1303
1334
  var payload = { certificate: jose.base64url.encode(certBuf) };
1304
- if (o.reason !== undefined) {
1335
+ if (o.reason != null) {
1305
1336
  if (typeof o.reason !== "number" || !isFinite(o.reason) || Math.floor(o.reason) !== o.reason || CRL_REASONS.indexOf(o.reason) === -1) {
1306
1337
  throw E("acme/bad-revocation-reason", "reason must be an assigned RFC 5280 CRLReason (0-6, 8-10; value 7 is unassigned)");
1307
1338
  }
@@ -1514,10 +1545,17 @@ function _queryRepaired(rawRef, resolvedUrl) {
1514
1545
  return qi !== -1 && rawRef.slice(qi).split("#")[0] !== _urlSearch(resolvedUrl);
1515
1546
  }
1516
1547
  var _defaultSleep = require("./sleep").sleep;
1517
- /** @internal The settings this client forwards to its transport. */
1518
- var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
1548
+ /** @internal The settings this client forwards to its transport. The distrust list is typed here, when
1549
+ * the client is built, and the frozen copy is the one every request carries. A setting not on the list
1550
+ * is refused. */
1551
+ var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
1552
+ var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
1519
1553
  function _clientTls(o) {
1520
- return guard.identifier.pickSettings(o.tls, TLS_SETTINGS);
1554
+ var tls = o.tls;
1555
+ guard.identifier.assertKnownKeys(tls, TLS_KNOWN, E, "acme/bad-input", "unknown opts.tls setting ");
1556
+ var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
1557
+ t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, E, "acme/bad-input", "opts.tls.distrustedKeys");
1558
+ return t;
1521
1559
  }
1522
1560
  function _splitPemChain(text) {
1523
1561
  var ders;
@@ -1877,7 +1915,7 @@ function _parseLinkNext(headers, base) { var m = _parseLinkRelation(headers, bas
1877
1915
  * @opts
1878
1916
  * - `accountKey` / `accountJwk` / `alg` -- REQUIRED: the account private key, its public JWK, and the JWS alg.
1879
1917
  * - `transport` -- injectable `transport(request) -> Promise<{status, headers, body}>`; default pki.transport.https. It must return a promise of the response; anything else is `acme/bad-input`.
1880
- * - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity } for the default transport.
1918
+ * - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys } for the default transport. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `acme/server-key-distrusted`, on every request including a cross-origin one, and a malformed list is `acme/bad-input` when the client is built. A setting not named in that list is refused `acme/bad-input` when the client is built.
1881
1919
  * - `timeout` / `maxResponseBytes` / `maxRedirects` -- transport budgets; `maxNonceRetries` -- badNonce retry cap (default 1).
1882
1920
  * - `proxy` -- reach the ACME server through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
1883
1921
  * - `maxPolls` / `maxTotalWait` / `sleep` -- poll-loop budgets + an injectable sleeper; `clock` -- an injectable receipt clock (default Date.now) for a Retry-After HTTP-date.
@@ -1976,14 +2014,16 @@ function client(directoryUrl, opts) {
1976
2014
  var resignKeys = _resignKeysOf(opts.resignKeys);
1977
2015
 
1978
2016
  var transport = checkedTransport;
2017
+ /** @internal One record answers both the trust-anchor check and every request. */
2018
+ var clientTls = _clientTls(opts);
1979
2019
  if (!transport) {
1980
- var t0 = opts.tls != null ? opts.tls : intrinsic.create(null);
2020
+ var t0 = clientTls;
1981
2021
  var hasAnchors = t0.anchors !== undefined && t0.anchors !== null && !(Array.isArray(t0.anchors) && t0.anchors.length === 0);
1982
2022
  if (!hasAnchors && t0.useSystemStore !== true) throw E("acme/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true (RFC 8555 sec. 6.1)");
1983
2023
  transport = httpTransport.https({ E: E, errPrefix: "acme" });
1984
2024
  }
1985
2025
  var budgets = {
1986
- tls: _clientTls(opts),
2026
+ tls: clientTls,
1987
2027
  proxy: httpTransport.snapshotProxy(opts.proxy),
1988
2028
  timeout: guard.limits.cap(opts.timeout, "timeout", CLIENT_DEFAULT_TIMEOUT, { E: E, code: "acme/bad-input", min: 1, max: CLIENT_MAX_TIMEOUT }),
1989
2029
  maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: E, code: "acme/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES }),
@@ -2234,17 +2274,17 @@ function client(directoryUrl, opts) {
2234
2274
 
2235
2275
  function _bindingSpec(opts) {
2236
2276
  var spki = opts.expectedSpki, ids = opts.identifiers, req = opts.requireBinding;
2237
- if (spki !== undefined) {
2277
+ if (spki != null) {
2238
2278
  if (!guard.bytes.isByteSource(spki)) throw E("acme/bad-input", "downloadCertificate opts.expectedSpki must be a DER SubjectPublicKeyInfo BufferSource");
2239
2279
  spki = guard.bytes.snapshotSource(spki, AcmeError, "acme/bad-input", "downloadCertificate expectedSpki");
2240
2280
  }
2241
- if (ids !== undefined) {
2281
+ if (ids != null) {
2242
2282
  if (!_isArrayI(ids) || ids.length === 0) throw E("acme/bad-input", "downloadCertificate opts.identifiers must be the non-empty order identifier array");
2243
2283
  ids = _mapI(ids, _validateOrderIdentifier);
2244
2284
  }
2245
- if (req !== undefined && typeof req !== "boolean") throw E("acme/bad-input", "downloadCertificate opts.requireBinding must be a boolean");
2246
- var require_ = req === undefined ? true : req;
2247
- if (require_ && spki === undefined && ids === undefined) {
2285
+ if (req != null && typeof req !== "boolean") throw E("acme/bad-input", "downloadCertificate opts.requireBinding must be a boolean");
2286
+ var require_ = req == null ? true : req;
2287
+ if (require_ && spki == null && ids == null) {
2248
2288
  throw E("acme/binding-required",
2249
2289
  "downloadCertificate needs the material that binds the issued certificate to this order -- " +
2250
2290
  "opts.expectedSpki (the DER SubjectPublicKeyInfo the CSR asked to have certified) and/or " +
@@ -2258,7 +2298,7 @@ function client(directoryUrl, opts) {
2258
2298
  var leaf;
2259
2299
  try { leaf = x509.parse(leafDer); }
2260
2300
  catch (e) { throw E("acme/bad-certificate-chain", "the downloaded end-entity certificate did not parse (RFC 8555 sec. 7.4.2)", e); }
2261
- if (spec.spki !== undefined) {
2301
+ if (spec.spki != null) {
2262
2302
  var got = leaf.subjectPublicKeyInfo && leaf.subjectPublicKeyInfo.bytes;
2263
2303
  if (!_isBuffer(got) || !guard.crypto.constantTimeEqual(got, spec.spki)) {
2264
2304
  throw E("acme/certificate-key-mismatch",
@@ -2266,7 +2306,7 @@ function client(directoryUrl, opts) {
2266
2306
  "to have certified, so it cannot be used with the private key held here (RFC 8555 sec. 7.4)");
2267
2307
  }
2268
2308
  }
2269
- if (spec.ids !== undefined) {
2309
+ if (spec.ids != null) {
2270
2310
  var have = _certIdentifierSet(leaf), want = _orderIdentifierSet(spec.ids);
2271
2311
  var haveKeys = _objectKeys(have), wantKeys = _objectKeys(want);
2272
2312
  var mismatch = haveKeys.length !== wantKeys.length ||
@@ -2284,7 +2324,7 @@ function client(directoryUrl, opts) {
2284
2324
  return _resolve(_Promise, undefined).then(function () {
2285
2325
  opts = _gateOpts(opts, _METHOD_OPTS.download, "downloadCertificate");
2286
2326
  var select = opts.selectChain;
2287
- if (select !== undefined && typeof select !== "function") throw E("acme/bad-input", "downloadCertificate opts.selectChain must be a function");
2327
+ if (select != null && typeof select !== "function") throw E("acme/bad-input", "downloadCertificate opts.selectChain must be a function");
2288
2328
  var maxAlt = guard.limits.cap(opts.maxAlternates, "maxAlternates", DEFAULT_MAX_ALTERNATES, { E: E, code: "acme/bad-input", min: 0, max: 64 });
2289
2329
  var binding = _bindingSpec(opts);
2290
2330
  var dlUrl = _clientUrl(url);
@@ -2297,7 +2337,10 @@ function client(directoryUrl, opts) {
2297
2337
  function result(cand) {
2298
2338
  _assertCertBinding(cand.certificate, binding);
2299
2339
  return { certificate: cand.certificate, chain: cand.chain, certificates: cand.certificates, alternates: alternates,
2300
- boundToKey: binding.spki !== undefined, boundToIdentifiers: binding.ids !== undefined };
2340
+ /** @internal Read as "not absent", not as "not undefined": with the binding
2341
+ * material left out as `null` the record carries a null, and a test against
2342
+ * `undefined` alone reported the certificate as bound to a key nobody named. */
2343
+ boundToKey: binding.spki != null, boundToIdentifiers: binding.ids != null };
2301
2344
  }
2302
2345
  if (!select) return result(primary);
2303
2346
  return _resolve(_Promise, select(primary)).then(function (primaryMatch) {
@@ -2327,7 +2370,7 @@ function client(directoryUrl, opts) {
2327
2370
  var certKeyMode = o.certKey != null || _isObject(o.certJwk) || o.certAlg != null;
2328
2371
  if (certKeyMode && !(o.certKey != null && _isObject(o.certJwk) && _isString(o.certAlg))) throw E("acme/bad-input", "revokeCert certificate-key mode requires certKey, certJwk, AND certAlg together, or none (RFC 8555 sec. 7.6)");
2329
2372
  var extra = { certificate: o.certificate };
2330
- if (o.reason !== undefined) extra.reason = o.reason;
2373
+ if (o.reason != null) extra.reason = o.reason;
2331
2374
  return _resource("revokeCert").then(function (url) {
2332
2375
  if (certKeyMode) {
2333
2376
  return _postJws(url, function (nonce) {
@@ -2347,10 +2390,10 @@ function client(directoryUrl, opts) {
2347
2390
  function _updateAccount(o) {
2348
2391
  return _promised(function () {
2349
2392
  o = guard.identifier.optionsObject(o, E, "acme/bad-input", "pki.acme updateAccount options");
2350
- if (o.status !== undefined) throw E("acme/bad-input", "updateAccount cannot change the account status; use deactivateAccount (RFC 8555 sec. 7.3.6 is the only client-settable status)");
2351
- if (o.termsOfServiceAgreed !== undefined) throw E("acme/bad-input", "the server MUST ignore a termsOfServiceAgreed update to the account (RFC 8555 sec. 7.3.2); it is set at account creation, and a changed-terms CA answers with a userActionRequired problem (sec. 7.3.3) directing a human to the terms URL");
2352
- if (o.orders !== undefined) throw E("acme/bad-input", "the account orders URL is server-assigned and cannot be updated (RFC 8555 sec. 7.3.2)");
2353
- if (o.externalAccountBinding !== undefined) throw E("acme/bad-input", "externalAccountBinding is not updateable by the client (RFC 8555 sec. 7.1.2 / 7.3.4)");
2393
+ if (o.status != null) throw E("acme/bad-input", "updateAccount cannot change the account status; use deactivateAccount (RFC 8555 sec. 7.3.6 is the only client-settable status)");
2394
+ if (o.termsOfServiceAgreed != null) throw E("acme/bad-input", "the server MUST ignore a termsOfServiceAgreed update to the account (RFC 8555 sec. 7.3.2); it is set at account creation, and a changed-terms CA answers with a userActionRequired problem (sec. 7.3.3) directing a human to the terms URL");
2395
+ if (o.orders != null) throw E("acme/bad-input", "the account orders URL is server-assigned and cannot be updated (RFC 8555 sec. 7.3.2)");
2396
+ if (o.externalAccountBinding != null) throw E("acme/bad-input", "externalAccountBinding is not updateable by the client (RFC 8555 sec. 7.1.2 / 7.3.4)");
2354
2397
  guard.identifier.assertKnownKeys(o, _METHOD_OPTS.updateAccount, E, "acme/bad-input", "unknown updateAccount option ");
2355
2398
  var account = _requireKid();
2356
2399
  return _post(account, updateAccount, { contact: o.contact }, "kid").then(function (res) { return validate("account", _json(res)); });
@@ -2435,10 +2478,10 @@ function client(directoryUrl, opts) {
2435
2478
  return _resolve(_Promise, undefined).then(function () {
2436
2479
  o = _gateOpts(o, _METHOD_OPTS.renewalWindow, "renewalWindow");
2437
2480
  if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "renewalWindow requires a DER certificate BufferSource");
2438
- if (o.random !== undefined && typeof o.random !== "function") throw E("acme/bad-input", "renewalWindow opts.random must be a function returning a number in [0, 1]");
2439
- if (o.clock !== undefined && typeof o.clock !== "function") throw E("acme/bad-input", "renewalWindow opts.clock must be a function returning epoch milliseconds");
2440
- if (o.replaced !== undefined && typeof o.replaced !== "boolean") throw E("acme/bad-input", "renewalWindow opts.replaced must be a boolean");
2441
- if (o.previous !== undefined && !_isObject(o.previous)) throw E("acme/bad-input", "renewalWindow opts.previous must be a prior renewalWindow result object");
2481
+ if (o.random != null && typeof o.random !== "function") throw E("acme/bad-input", "renewalWindow opts.random must be a function returning a number in [0, 1]");
2482
+ if (o.clock != null && typeof o.clock !== "function") throw E("acme/bad-input", "renewalWindow opts.clock must be a function returning epoch milliseconds");
2483
+ if (o.replaced != null && typeof o.replaced !== "boolean") throw E("acme/bad-input", "renewalWindow opts.replaced must be a boolean");
2484
+ if (o.previous != null && !_isObject(o.previous)) throw E("acme/bad-input", "renewalWindow opts.previous must be a prior renewalWindow result object");
2442
2485
  /** @internal `previous` is READ ONCE, here, before the await below, and the stickiness decision
2443
2486
  * reads this reading. It was validated as an object here and then read three more times after the
2444
2487
  * renewalInfo fetch: `o.previous.suggestedWindow` twice in one expression, and
@@ -2446,7 +2489,7 @@ function client(directoryUrl, opts) {
2446
2489
  * each read separately and the fetch is the window in which to change its answer, so the prior
2447
2490
  * window this compares against need not be the one the caller validated, and a non-object arriving
2448
2491
  * after the await reached `.start` and raised a bare TypeError. */
2449
- var prev = o.previous === undefined ? undefined : _readOnce(o.previous, PREVIOUS_WINDOW_NAMES);
2492
+ var prev = o.previous == null ? undefined : _readOnce(o.previous, PREVIOUS_WINDOW_NAMES);
2450
2493
  var prevWindow = prev && _isObject(prev.suggestedWindow) ? _readOnce(prev.suggestedWindow, WINDOW_BOUND_NAMES) : null;
2451
2494
  var _clk = typeof o.clock === "function" ? o.clock : clock;
2452
2495
  function clk() { var t = _clk(); if (typeof t !== "number" || !isFinite(t)) throw E("acme/bad-input", "the renewalWindow clock returned a non-finite value"); return t; }
@@ -2490,11 +2533,13 @@ function client(directoryUrl, opts) {
2490
2533
  return _resolve(_Promise).then(function () {
2491
2534
  o = _gateOpts(o, _METHOD_OPTS.scheduleRenewal, "scheduleRenewal");
2492
2535
  if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "scheduleRenewal requires a DER certificate BufferSource");
2493
- if (o.random !== undefined && typeof o.random !== "function") throw E("acme/bad-input", "scheduleRenewal opts.random must be a function returning a number in [0, 1]");
2494
- if (o.shouldStop !== undefined && typeof o.shouldStop !== "function") throw E("acme/bad-input", "scheduleRenewal opts.shouldStop must be a function returning a boolean");
2495
- if (o.renew !== undefined && typeof o.renew !== "function") throw E("acme/bad-input", "scheduleRenewal opts.renew must be an async function (decision) -> newCertDer | void");
2536
+ if (o.random != null && typeof o.random !== "function") throw E("acme/bad-input", "scheduleRenewal opts.random must be a function returning a number in [0, 1]");
2537
+ if (o.shouldStop != null && typeof o.shouldStop !== "function") throw E("acme/bad-input", "scheduleRenewal opts.shouldStop must be a function returning a boolean");
2538
+ if (o.renew != null && typeof o.renew !== "function") throw E("acme/bad-input", "scheduleRenewal opts.renew must be an async function (decision) -> newCertDer | void");
2539
+ /** @internal The value handed in is the caller's own option, so absence keeps both
2540
+ * of its forms here: a `null` states no count and reads as an omitted one. */
2496
2541
  function optCount(v, name) {
2497
- if (v === undefined) return null;
2542
+ if (v == null) return null;
2498
2543
  if (typeof v !== "number" || !intrinsic.isFinite(v) || v < 1 || intrinsic.floor(v) !== v) throw E("acme/bad-input", "scheduleRenewal opts." + name + " must be a positive integer");
2499
2544
  return v;
2500
2545
  }
@@ -709,7 +709,7 @@ function _checkTargeting(ac, want) {
709
709
  if (ac.extensions[i].name === "targetInformation") ext = ac.extensions[i];
710
710
  }
711
711
  if (!ext) return guard.verdict.of({ checked: true, ok: true });
712
- if (want === undefined) {
712
+ if (want == null) {
713
713
  return guard.verdict.of({ checked: false, ok: false,
714
714
  reason: "this attribute certificate is targeted, and no opts.target names this verifier (RFC 5755 sec. 4.3.2)" });
715
715
  }
@@ -860,11 +860,12 @@ function _fixVerifyOptions(opts) {
860
860
  guard.identifier.assertKnownKeys(o, KNOWN_VERIFY_OPTS, _err, "attrcert/bad-input",
861
861
  "pki.attrcert.verify has an unknown option ");
862
862
  var at = null;
863
- if (o.time !== undefined) {
863
+ if (o.time != null) {
864
864
  guard.time.assertValid(o.time, _err, "attrcert/bad-input", "pki.attrcert.verify opts.time");
865
865
  at = guard.time.instantOf(o.time);
866
866
  }
867
867
  var revocationStatus = null;
868
+ // allow:caller-option-presence-tested-against-undefined -- absence means no revocation status is applied, so a null must be refused
868
869
  if (o.revocationStatus !== undefined) {
869
870
  if (typeof o.revocationStatus !== "string" ||
870
871
  !_hasOwn(REVOCATION_STATUS, o.revocationStatus)) {
@@ -874,7 +875,7 @@ function _fixVerifyOptions(opts) {
874
875
  }
875
876
  return {
876
877
  at: at, revocationStatus: revocationStatus,
877
- target: o.target === undefined ? undefined : _callerTarget(o.target),
878
+ target: o.target == null ? undefined : _callerTarget(o.target),
878
879
  };
879
880
  }
880
881
 
package/lib/cmp-build.js CHANGED
@@ -1009,10 +1009,16 @@ function _cmpMessageDer(input) {
1009
1009
  return der;
1010
1010
  }
1011
1011
 
1012
- /** @internal The settings this transfer forwards to its transport. */
1013
- var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
1012
+ /** @internal The settings this transfer forwards to its transport, with the distrust list typed into a
1013
+ * frozen copy. A setting not on the list is refused. */
1014
+ var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
1015
+ var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
1014
1016
  function _tlsForTransfer(opts) {
1015
- return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
1017
+ var tls = opts.tls;
1018
+ guard.identifier.assertKnownKeys(tls, TLS_KNOWN, _err, "cmp/bad-input", "unknown opts.tls setting ");
1019
+ var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
1020
+ t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, _err, "cmp/bad-input", "opts.tls.distrustedKeys");
1021
+ return t;
1016
1022
  }
1017
1023
 
1018
1024
  /** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
@@ -1076,8 +1082,10 @@ function _transfer(url, message, opts) {
1076
1082
  var parsedUrl;
1077
1083
  try { parsedUrl = new _URL(_stringOf(url)); }
1078
1084
  catch (e) { throw _err("cmp/bad-url", "the CMP URL did not parse: " + guard.text.showValue(url), e); }
1085
+ /** @internal One record answers both the trust-anchor check and the request. */
1086
+ var tls = _tlsForTransfer(opts);
1079
1087
  if (!transport) {
1080
- var t = opts.tls != null ? opts.tls : intrinsic.create(null);
1088
+ var t = tls;
1081
1089
  var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
1082
1090
  if (!hasAnchors && t.useSystemStore !== true) throw _err("cmp/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned server (RFC 9811 sec. 5)");
1083
1091
  transport = httpTransport.https({ E: _err, errPrefix: "cmp" });
@@ -1093,7 +1101,6 @@ function _transfer(url, message, opts) {
1093
1101
  if (lk !== "content-length" && lk !== "transfer-encoding" && lk !== "content-type") headers[k] = opts.headers[k];
1094
1102
  });
1095
1103
  headers["content-type"] = PKIXCMP;
1096
- var tls = _tlsForTransfer(opts);
1097
1104
  return guard.async.invoked(transport, [{ method: "POST", url: parsedUrl.href, headers: headers, body: der, tls: tls, proxy: proxy, timeout: timeout, maxResponseBytes: maxResponseBytes }], _err, "cmp/bad-input", "opts.transport").then(function (res) {
1098
1105
  res = res || intrinsic.create(null);
1099
1106
  var h = guard.header.lowerCased(res.headers);
@@ -1185,8 +1192,11 @@ function wellKnownUrl(base, opts) {
1185
1192
  * @opts
1186
1193
  * - `transport` -- an injectable `transport(request) -> Promise<{status,headers,body}>` (default
1187
1194
  * `pki.transport.https`, which fail-closes on a non-https URL and an unpinned server).
1188
- * - `tls` -- `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }` for the
1195
+ * - `tls` -- `{ anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }` for the
1189
1196
  * default transport (mutual-TLS `cert`/`key` is common for CMP in addition to message protection).
1197
+ * `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain
1198
+ * carries a listed key is refused with `cmp/server-key-distrusted`, and a malformed list is `cmp/bad-input`. A
1199
+ * setting not named in that list is refused `cmp/bad-input`.
1190
1200
  * - `headers` -- extra request headers. The `content-type: application/pkixcmp` is always set and not
1191
1201
  * overridable (any casing), and the request-framing headers (`content-length`, `transfer-encoding`) are
1192
1202
  * stripped -- the transport computes Content-Length from the exact body, so a caller cannot desync framing.
@@ -70,7 +70,7 @@ var KNOWN_SESSION_OPTS = Object.assign(Object.create(null), {
70
70
  extraCerts: 1, implicitConfirm: 1, maxPolls: 1, maxTotalWait: 1, sleep: 1, time: 1,
71
71
  transport: 1, tls: 1, headers: 1, timeout: 1, maxResponseBytes: 1, proxy: 1, pss: 1, digestAlgorithm: 1,
72
72
  acceptCert: 1, senderKID: 1, recipKID: 1, expectedSender: 1, acceptCentralKeyGeneration: 1,
73
- authorizedBySharedSecret: 1,
73
+ authorizedBySharedSecret: 1, distrustedKeys: 1,
74
74
  });
75
75
 
76
76
  var TRANSCRIPT_RETAIN_RESPONSES = 2;
@@ -407,6 +407,7 @@ function _certConfHash(certDer) {
407
407
  * - `url` -- REQUIRED: the CMP endpoint URL.
408
408
  * - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }` for PBMAC1 protection.
409
409
  * - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the issued certificate's own signature + chain before confirmation (not the response protection). `intermediates` supplies an extra chain pool.
410
+ * - `distrustedKeys`: SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or 32 bytes each), read once at construction. A listed key on the response signer's chain is `cmp/untrusted-signer`, on the issued certificate's chain `cmp/bad-cert-response`, and on the key generation authority's chain `cmp/unauthorized-kga`, which also refuses a listed authority key under `authorizedBySharedSecret`. A `rootCaKeyUpdate` answer whose new root key is listed is `cmp/bad-info-value`. A malformed list, or a list naming a key on a session without `trustAnchors`, is `cmp/bad-input`; an empty list refuses nothing and is accepted.
410
411
  * - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an empty subject (identified only by its subjectAltName) REQUIRES an explicit `sender`, because the empty subject cannot name the requester for a peer that binds the sender to the SAN.
411
412
  * - `senderKID` / `recipKID` -- optional key identifiers emitted on every request header, so a CA selecting among several shared secrets (senderKID) or recipient keys resolves the right credential.
412
413
  * - `expectedSender` -- optional CA signer certificate (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it also resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it, rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
@@ -454,6 +455,10 @@ function session(opts) {
454
455
  if (!hasAnchors) throw _err("cmp/bad-input", "signature protection requires opts.trustAnchors to authenticate the CA's response signer (RFC 9483 sec. 3.2)");
455
456
  }
456
457
  if (isMac && Array.isArray(opts.trustAnchors) && opts.trustAnchors.length === 0) opts.trustAnchors = null;
458
+ var _distrusted = guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys");
459
+ if (_distrusted !== null && _distrusted.length > 0 && opts.trustAnchors == null) {
460
+ throw _err("cmp/bad-input", "opts.distrustedKeys applies to the certificate paths this session validates against opts.trustAnchors, and this shared-secret session supplies none");
461
+ }
457
462
  if (opts.acceptCentralKeyGeneration != null && typeof opts.acceptCentralKeyGeneration !== "boolean") {
458
463
  throw _err("cmp/bad-input", "opts.acceptCentralKeyGeneration is a boolean stating that this caller accepts a key pair the authority generated (RFC 9483 sec. 4.1.6)");
459
464
  }
@@ -596,7 +601,10 @@ function session(opts) {
596
601
  function _verifyOpts(fresh, signerCertOverride, extraIntermediates, responseExtra) {
597
602
  var o = Object.assign(Object.create(null), { transactionID: transactionID, expectRecipNonce: fresh });
598
603
  if (isMac) o.sharedSecret = opts.mac.secret;
599
- else if (opts.trustAnchors != null) o.trustAnchors = opts.trustAnchors;
604
+ else if (opts.trustAnchors != null) {
605
+ o.trustAnchors = opts.trustAnchors;
606
+ if (_distrusted !== null) o.distrustedKeys = _distrusted;
607
+ }
600
608
  var issuers = Array.isArray(responseExtra) ? responseExtra : [];
601
609
  var extra = [];
602
610
  if (Array.isArray(extraIntermediates)) {
@@ -982,7 +990,9 @@ function session(opts) {
982
990
  cachedChain.forEach(function (c) { if (Buffer.isBuffer(c)) _push(added,c); });
983
991
  var pool = _boundedPool(caPubsList, added);
984
992
  var res;
985
- try { res = await _engine.build(leaf, { trustAnchors: _asCertList(opts.trustAnchors), intermediates: pool, validate: true, time: opts.time != null ? opts.time : new Date() }); }
993
+ var leafOpts = { trustAnchors: _asCertList(opts.trustAnchors), intermediates: pool, validate: true, time: opts.time != null ? opts.time : new Date() };
994
+ if (_distrusted !== null) leafOpts.distrustedKeys = _distrusted;
995
+ try { res = await _engine.build(leaf, leafOpts); }
986
996
  catch (e) {
987
997
  if (e.code === "path/bad-input") throw _err("cmp/bad-input", "invalid trust / validation options for issued-certificate validation: " + guard.text.describeThrown(e), e);
988
998
  throw _err("cmp/bad-cert-response", "the issued certificate could not be validated to a supplied trust anchor: " + guard.text.describeThrown(e), e);
@@ -1041,6 +1051,9 @@ function session(opts) {
1041
1051
  open.trustAnchors = _asOpenableCerts(opts.trustAnchors, "opts.trustAnchors", true);
1042
1052
  if (opts.intermediates != null) open.intermediates = _asOpenableCerts(opts.intermediates, "opts.intermediates", false);
1043
1053
  }
1054
+ /** @internal The list goes with either rule: on the chain the verb validates, or, under the
1055
+ * exemption, on the authority's own key. */
1056
+ if (_distrusted !== null) open.distrustedKeys = _distrusted;
1044
1057
  /** @internal The instant the session validates every other certificate at governs the authority's
1045
1058
  * chain too, so a historical enrollment does not judge one certificate at a stated time and
1046
1059
  * another at the current one. */
@@ -1689,6 +1702,9 @@ function session(opts) {
1689
1702
  var newWithOld = _asCertificate(value.newWithOld, "the rootCaKeyUpdate newWithOld");
1690
1703
  var oldWithNew = value.oldWithNew == null ? null : _asCertificate(value.oldWithNew, "the rootCaKeyUpdate oldWithNew");
1691
1704
  var pNewNew = x509.parse(newWithNew), pNewOld = x509.parse(newWithOld), pOldRoot = x509.parse(oldRootDer);
1705
+ if (guard.distrust.has(_distrusted, pNewNew.subjectPublicKeyInfo.bytes)) {
1706
+ throw _err("cmp/bad-info-value", "the rootCaKeyUpdate new root key is listed in opts.distrustedKeys");
1707
+ }
1692
1708
  _assertCaCapable(pNewNew, "rootCaKeyUpdate newWithNew");
1693
1709
  _assertCaCapable(pNewOld, "rootCaKeyUpdate newWithOld");
1694
1710
  if (!cmpBuild.samePublicKey(pNewOld.subjectPublicKeyInfo.bytes, pNewNew.subjectPublicKeyInfo.bytes)) {
package/lib/cmp-verify.js CHANGED
@@ -57,7 +57,7 @@ var _certExtDecoders = pkix.certExtensionDecoders(NS).byOid;
57
57
  var KNOWN_VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), {
58
58
  sharedSecret: 1, signerCert: 1, trustAnchors: 1, intermediates: 1, time: 1,
59
59
  transactionID: 1, expectRecipNonce: 1, revocationChecker: 1, maxIterations: 1, kem: 1,
60
- messageTimeTolerance: 1,
60
+ messageTimeTolerance: 1, distrustedKeys: 1,
61
61
  });
62
62
  var KNOWN_KEM_VERIFY_KEYS = intrinsic.assign(intrinsic.create(null),
63
63
  { sharedSecret: 1, transactionID: 1 });
@@ -651,6 +651,7 @@ async function _chainSigner(signer, m, opts, extra) {
651
651
  }
652
652
  var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: time };
653
653
  if (opts.revocationChecker != null) buildOpts.revocationChecker = opts.revocationChecker;
654
+ if (opts.distrustedKeys !== null) buildOpts.distrustedKeys = opts.distrustedKeys;
654
655
  try {
655
656
  var res = await _engine.build(signer.der, buildOpts);
656
657
  if (!res || res.valid !== true) return guard.verdict.of({ trusted: false, reason: "the signer certificate did not chain to a supplied trust anchor" });
@@ -826,10 +827,16 @@ function _fixVerifyOptions(opts, made) {
826
827
  maxIterations: opts.maxIterations,
827
828
  kem: _fixKem(opts.kem, made),
828
829
  messageTimeTolerance: _fixTolerance(opts.messageTimeTolerance),
830
+ distrustedKeys: guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys"),
829
831
  };
830
832
 
831
833
  _assertEchoBytes(f.transactionID, "transactionID");
832
834
  _assertEchoBytes(f.expectRecipNonce, "expectRecipNonce");
835
+ /** @internal Only a list that names a key is refused here: an empty one refuses nothing, so it
836
+ * asks for no check that the missing path would leave undone. */
837
+ if (f.distrustedKeys !== null && f.distrustedKeys.length > 0 && f.trustAnchors == null) {
838
+ throw _err("cmp/bad-input", "opts.distrustedKeys applies to the signer certificate's path, which is validated only against opts.trustAnchors; MAC and KEM protection validate no path");
839
+ }
833
840
  return f;
834
841
  }
835
842
 
@@ -874,7 +881,7 @@ async function _verify(message, opts) {
874
881
 
875
882
  var KNOWN_OPEN_KEY_PACKAGE_OPTS = intrinsic.assign(intrinsic.create(null), {
876
883
  key: 1, cert: 1, password: 1, trustAnchors: 1, intermediates: 1, time: 1,
877
- authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1,
884
+ authorizedBySharedSecret: 1, allowUnauthenticatedRsa15: 1, distrustedKeys: 1,
878
885
  });
879
886
  var OID_A_KEY_PACKAGE = oid.byName("aKeyPackage");
880
887
  var OID_SIGNED_DATA = oid.byName("signedData");
@@ -950,6 +957,7 @@ function _fixOpenKeyPackageOptions(opts, made) {
950
957
  time: _fixTime(opts.time),
951
958
  exempt: opts.authorizedBySharedSecret === true,
952
959
  allowRsa15: opts.allowUnauthenticatedRsa15 === true,
960
+ distrustedKeys: guard.distrust.keys(opts.distrustedKeys, _err, "cmp/bad-input", "opts.distrustedKeys"),
953
961
  };
954
962
  if (opts.authorizedBySharedSecret != null && typeof opts.authorizedBySharedSecret !== "boolean") {
955
963
  throw _err("cmp/bad-input", "opts.authorizedBySharedSecret is a boolean stating that this entity protected its request with the shared secret");
@@ -1020,6 +1028,11 @@ async function _openKeyPackage(container, opts) {
1020
1028
  var signerCert = verdict.signers[0] && verdict.signers[0].cert;
1021
1029
  if (!signerCert) throw _keyPackageErr("the SignedData names no signer certificate to authorize as the key generation authority");
1022
1030
  _authorizeKga(verdict, signerCert, opts);
1031
+ /** @internal The shared-secret exemption replaces the authority's chain check, which is where the
1032
+ * list is otherwise applied, so the authority's own key is checked here. */
1033
+ if (opts.exempt && guard.distrust.has(opts.distrustedKeys, x509.parse(signerCert).subjectPublicKeyInfo.bytes)) {
1034
+ throw _err("cmp/unauthorized-kga", "the key generation authority's key is listed in opts.distrustedKeys");
1035
+ }
1023
1036
  if (verdict.eContent == null) throw _keyPackageErr("the SignedData carries no encapsulated content, so no key package was delivered");
1024
1037
  return guard.verdict.of({ keys: _readKeyPackage(verdict.eContent), kga: _bufferFrom(signerCert), trusted: opts.exempt ? false : true });
1025
1038
  }
@@ -1047,6 +1060,7 @@ function _signedDataVerifyOpts(opts) {
1047
1060
  var v = { trustAnchors: opts.trustAnchors, requiredEku: [OID_CM_KGA] };
1048
1061
  if (opts.intermediates != null) v.certs = opts.intermediates;
1049
1062
  if (opts.time != null) v.time = opts.time;
1063
+ if (opts.distrustedKeys !== null) v.distrustedKeys = opts.distrustedKeys;
1050
1064
  return v;
1051
1065
  }
1052
1066
 
@@ -1152,6 +1166,10 @@ function _coerceKeyPackageBytes(container) {
1152
1166
  * authority. REQUIRED unless `authorizedBySharedSecret` is set.
1153
1167
  * - `intermediates` (Buffer|Buffer[]|PEM) -- extra untrusted pool certificates for path building.
1154
1168
  * - `time` (Date) -- the validity instant for path validation. Defaults to the current time.
1169
+ * - `distrustedKeys` (Array): SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters
1170
+ * or 32 bytes each). A listed key on the authority, an intermediate or the anchor is
1171
+ * `cmp/unauthorized-kga`. Under `authorizedBySharedSecret`, which validates no chain, the list
1172
+ * is applied to the authority's own key.
1155
1173
  * - `authorizedBySharedSecret` (boolean) -- authorize the authority by the secret that protected
1156
1174
  * the request rather than by a chain, which sec. 4.1.6 permits a MAC-protected exchange. Requires
1157
1175
  * `opts.password` and refuses `opts.trustAnchors`.
@@ -1264,6 +1282,11 @@ function openKeyPackage(container, opts) {
1264
1282
  * - `transactionID` (Buffer) -- opt-in: require `header.transactionID` to equal it (response-echo defense).
1265
1283
  * - `expectRecipNonce` (Buffer) -- opt-in: require `header.recipNonce` to echo the sent sender nonce.
1266
1284
  * - `revocationChecker` -- forwarded to `pki.path.validate` when chaining the signer certificate.
1285
+ * - `distrustedKeys` (Array): SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or
1286
+ * 32 bytes each). A listed key on the signer, an intermediate or the anchor makes the verdict
1287
+ * `trusted: false` with `cmp/untrusted-signer`. A list that names a key requires `trustAnchors`, so
1288
+ * a MAC- or KEM-protected message refuses one as `cmp/bad-input`, as it does a malformed list; an
1289
+ * empty list refuses nothing and is accepted.
1267
1290
  * - `maxIterations` (number) -- downward-only override of the PBKDF2 iteration cap.
1268
1291
  *
1269
1292
  * @example
@@ -27,12 +27,12 @@ async function compress(content, opts) {
27
27
  opts = opts || _create(null);
28
28
  var raw = guard.bytes.source(content, CmsError, "cms/bad-input", "content");
29
29
  var level = opts.level;
30
- if (level !== undefined && (typeof level !== "number" || !isFinite(level) || Math.floor(level) !== level)) throw _err("cms/bad-input", "opts.level must be an integer");
30
+ if (level != null && (typeof level !== "number" || !isFinite(level) || Math.floor(level) !== level)) throw _err("cms/bad-input", "opts.level must be an integer");
31
31
  var ctName = opts.contentType || "data";
32
32
  var ctOid = O(ctName);
33
33
  if (!ctOid) throw _err("cms/bad-input", "opts.contentType is not a known OID name: " + ctName);
34
34
  var stream;
35
- try { stream = zlib.deflateSync(raw, level !== undefined ? { level: level } : undefined); }
35
+ try { stream = zlib.deflateSync(raw, level != null ? { level: level } : undefined); }
36
36
  catch (e) { throw _err("cms/bad-input", "the content could not be compressed (check opts.level)", e); }
37
37
  var cd = b.sequence([
38
38
  b.integer(0),
@@ -46,7 +46,7 @@ async function compress(content, opts) {
46
46
  async function decompress(input, opts) {
47
47
  opts = opts || _create(null);
48
48
  var cap = C.LIMITS.COMPRESS_MAX_BYTES;
49
- if (opts.maxOutputBytes !== undefined) {
49
+ if (opts.maxOutputBytes != null) {
50
50
  var mo = opts.maxOutputBytes;
51
51
  if (typeof mo !== "number" || !isFinite(mo) || mo <= 0 || Math.floor(mo) !== mo) throw _err("cms/bad-input", "opts.maxOutputBytes must be a positive integer");
52
52
  if (mo < cap) cap = mo;