@blamejs/pki 0.9.5 → 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.
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; }
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
  }
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.
package/lib/est.js CHANGED
@@ -918,10 +918,23 @@ function _parseUrl(urlStr) {
918
918
  return url;
919
919
  }
920
920
 
921
- /** @internal The settings this client forwards to its transport. */
922
- var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
923
- function _tlsForRequest(opts) {
924
- return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
921
+ /** @internal The settings this client forwards to its transport. The distrust list is typed here, once
922
+ * per verb, and the frozen copy is the one every hop carries. A setting not on the list is refused. */
923
+ var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
924
+ var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
925
+ function _tlsForRequest(tls) {
926
+ guard.identifier.assertKnownKeys(tls, TLS_KNOWN, E, "est/bad-input", "unknown opts.tls setting ");
927
+ var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
928
+ t.distrustedKeys = guard.distrust.keys(t.distrustedKeys, E, "est/bad-input", "opts.tls.distrustedKeys");
929
+ return t;
930
+ }
931
+ /** @internal The proxy and the tls settings, taken at the synchronous verb door. Every request and
932
+ * redirect hop of the verb uses this record. */
933
+ function _doorSnapshot(opts) {
934
+ var proxy, tls;
935
+ try { proxy = _snapProxy(opts); tls = opts.tls; }
936
+ catch (_pe) { throw E("est/bad-input", "the options object could not be read", _pe); }
937
+ return { proxy: proxy, tls: _tlsForRequest(tls) };
925
938
  }
926
939
 
927
940
  function _redirectTarget(current, location, method, opts) {
@@ -980,7 +993,7 @@ function _authScheme(opts) {
980
993
  if (sc !== "basic" && sc !== "digest") throw E("est/bad-input", "opts.auth.scheme must be \"basic\" or \"digest\"");
981
994
  return sc;
982
995
  }
983
- if (opts.username !== undefined || opts.password !== undefined) return "basic";
996
+ if (opts.username != null || opts.password != null) return "basic";
984
997
  return null;
985
998
  }
986
999
  function _authUser(opts) { return opts.auth && opts.auth.username !== undefined ? opts.auth.username : opts.username; }
@@ -1095,7 +1108,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
1095
1108
  /** @internal `checked` is the transport the verb already took at its door. Taking it again here
1096
1109
  * would ask the caller's options a second time, and anything read in between could change the
1097
1110
  * answer, so a verb that holds one passes it and this reads the option only when none was held. */
1098
- function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt, checked) {
1111
+ function _client(op, method, baseUrl, body, headers, opts, snap, onAttempt, checked) {
1099
1112
  var base;
1100
1113
  try { base = new _URL(_stringOf(baseUrl)); }
1101
1114
  catch (e) { throw E("est/bad-url", "the EST base URL did not parse: " + guard.text.showValue(baseUrl), e); }
@@ -1103,14 +1116,14 @@ function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt,
1103
1116
  var url = _parseUrl(paths(baseUrl, { label: opts.label })[op]);
1104
1117
  var transport = checked !== undefined ? checked : _assertTransport(opts);
1105
1118
  if (!transport) {
1106
- var t = opts.tls != null ? opts.tls : intrinsic.create(null);
1119
+ var t = snap.tls;
1107
1120
  var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
1108
1121
  if (!hasAnchors && t.useSystemStore !== true) throw E("est/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned server (RFC 7030 sec. 3.6)");
1109
1122
  transport = httpTransport.https({ E: E, errPrefix: "est" });
1110
1123
  }
1111
1124
  var budgets = {
1112
- tls: _tlsForRequest(opts),
1113
- proxy: proxySnap,
1125
+ tls: snap.tls,
1126
+ proxy: snap.proxy,
1114
1127
  timeout: guard.limits.cap(opts.timeout, "timeout", DEFAULT_TIMEOUT, { E: E, code: "est/bad-input", min: 1, max: MAX_TIMEOUT }),
1115
1128
  maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: E, code: "est/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES }),
1116
1129
  maxRedirects: guard.limits.cap(opts.maxRedirects, "maxRedirects", 5, { E: E, code: "est/bad-input", min: 0, max: 32 }),
@@ -1241,18 +1254,19 @@ function fullcmc(baseUrl, request, opts) {
1241
1254
  * caller's nested objects, and one of those reads must not be able to change which transport
1242
1255
  * the request goes out on, so the value that was checked is the value carried below. */
1243
1256
  var carriedTransport = opts.transport;
1244
- var proxySnap;
1245
- try { proxySnap = _snapProxy(opts); } catch (_pe) { throw E("est/bad-input", "the options object could not be read", _pe); }
1257
+ /** @internal Taken before the own-property copy below, so a tls a defaults bag carries on its
1258
+ * prototype is the one the request uses. */
1259
+ var snap = _doorSnapshot(opts);
1246
1260
  der = _cmcRequestDer(request);
1247
1261
  wanted = _requestedPublicKeys(der);
1248
1262
  sent = _cmcSent(opts, der);
1249
1263
  /** @internal The copy below keeps own properties only, and a defaults bag holds its options on
1250
1264
  * a prototype, so the transport is put back from the value held at the door. */
1251
1265
  opts = _shallowCopy(opts);
1252
- if (carriedTransport !== undefined) opts.transport = carriedTransport;
1266
+ if (carriedTransport != null) opts.transport = carriedTransport;
1253
1267
  var body = transferEncode(der);
1254
1268
  return _client("fullcmc", "POST", baseUrl, body,
1255
- { accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts, proxySnap, undefined, carriedTransport)
1269
+ { accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts, snap, undefined, carriedTransport)
1256
1270
  .then(function (res) { return _fullcmcResult(res, opts, wanted, sent); });
1257
1271
  } catch (e) {
1258
1272
  return _rej(e);
@@ -1571,7 +1585,7 @@ function _causedBy(e, target) {
1571
1585
 
1572
1586
  var ENROLL_HEADERS = intrinsic.assign(intrinsic.create(null), { accept: "application/pkcs7-mime", "content-type": "application/pkcs10" });
1573
1587
 
1574
- function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1588
+ function _enrollBound(op, baseUrl, builder, opts, snap) {
1575
1589
  /** @internal Held before anything else the caller supplied is read, and carried to the client:
1576
1590
  * the builder runs during the handshake, so a later read could answer with a different value. */
1577
1591
  var checkedTransport = opts.transport;
@@ -1601,7 +1615,7 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1601
1615
  .then(null, function (e) { attempt.error = e; attempt.done = true; throw e; });
1602
1616
  };
1603
1617
  }
1604
- return _client(op, "POST", baseUrl, hopBody, ENROLL_HEADERS, opts, proxySnap, hopBody, checkedTransport)
1618
+ return _client(op, "POST", baseUrl, hopBody, ENROLL_HEADERS, opts, snap, hopBody, checkedTransport)
1605
1619
  .then(function (res) {
1606
1620
  if (current && current.error) throw current.error;
1607
1621
  if (!current || !current.done) {
@@ -1617,13 +1631,13 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1617
1631
  });
1618
1632
  }
1619
1633
 
1620
- function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1621
- if (typeof csrInput === "function") return _enrollBound(op, baseUrl, csrInput, opts, proxySnap);
1634
+ function _enroll(op, baseUrl, csrInput, opts, snap) {
1635
+ if (typeof csrInput === "function") return _enrollBound(op, baseUrl, csrInput, opts, snap);
1622
1636
  var checkedTransport = opts.transport;
1623
1637
  var csrDer = _csrDer(csrInput);
1624
1638
  var spki = csr.parse(csrDer).subjectPublicKeyInfo;
1625
1639
  var body = transferEncode(csrDer);
1626
- return _client(op, "POST", baseUrl, body, ENROLL_HEADERS, opts, proxySnap, undefined, checkedTransport)
1640
+ return _client(op, "POST", baseUrl, body, ENROLL_HEADERS, opts, snap, undefined, checkedTransport)
1627
1641
  .then(function (res) { return _certsResult(op, res, opts, spki); });
1628
1642
  }
1629
1643
 
@@ -1646,7 +1660,7 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1646
1660
  *
1647
1661
  * @opts
1648
1662
  * - `transport`: an injected `transport(request) -> Promise<{status, headers, body, tls}>`; default pki.transport.https. It must return a promise of the response; anything else is `est/bad-input`.
1649
- * - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }.
1663
+ * - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity, distrustedKeys }. `distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `est/server-key-distrusted`, on every hop including a cross-origin redirect, and a malformed list is `est/bad-input`. A setting not named in that list is refused `est/bad-input`. The verb reads `tls` when it is called, so a change the caller makes to it afterward does not reach the request.
1650
1664
  * - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
1651
1665
  * - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
1652
1666
  * - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
@@ -1664,10 +1678,10 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1664
1678
  */
1665
1679
  function cacerts(baseUrl, opts) {
1666
1680
  opts = opts || intrinsic.create(null);
1667
- var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1681
+ var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
1668
1682
  return _promiseResolve(_Promise, undefined).then(function () {
1669
1683
  opts = _knownOpts(opts, CACERTS_OPTS, "cacerts");
1670
- return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts, proxySnap, undefined, opts.transport);
1684
+ return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts, snap, undefined, opts.transport);
1671
1685
  }).then(function (res) { return _certsResult("cacerts", res, opts, null); });
1672
1686
  }
1673
1687
 
@@ -1732,10 +1746,10 @@ function cacerts(baseUrl, opts) {
1732
1746
  */
1733
1747
  function simpleenroll(baseUrl, csrInput, opts) {
1734
1748
  opts = opts || intrinsic.create(null);
1735
- var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1749
+ var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
1736
1750
  return _promiseResolve(_Promise, undefined).then(function () {
1737
1751
  opts = _knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
1738
- return _enroll("simpleenroll", baseUrl, csrInput, opts, proxySnap);
1752
+ return _enroll("simpleenroll", baseUrl, csrInput, opts, snap);
1739
1753
  });
1740
1754
  }
1741
1755
 
@@ -1772,12 +1786,12 @@ function simpleenroll(baseUrl, csrInput, opts) {
1772
1786
  */
1773
1787
  function simplereenroll(baseUrl, csrInput, opts) {
1774
1788
  opts = opts || intrinsic.create(null);
1775
- var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1789
+ var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
1776
1790
  return _promiseResolve(_Promise, undefined).then(function () {
1777
1791
  opts = _knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
1778
1792
  if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
1779
1793
  if (typeof csrInput !== "function") reenrollGuard(opts.oldCert, _csrDer(csrInput));
1780
- return _enroll("simplereenroll", baseUrl, csrInput, opts, proxySnap);
1794
+ return _enroll("simplereenroll", baseUrl, csrInput, opts, snap);
1781
1795
  });
1782
1796
  }
1783
1797
 
@@ -1944,7 +1958,7 @@ async function _serverkeygenResult(res, opts, derived) {
1944
1958
  */
1945
1959
  function serverkeygen(baseUrl, csrInput, opts) {
1946
1960
  opts = opts || intrinsic.create(null);
1947
- var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1961
+ var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
1948
1962
  return _promiseResolve(_Promise, undefined).then(function () {
1949
1963
  opts = _knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
1950
1964
  var checkedTransport = opts.transport;
@@ -1955,6 +1969,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
1955
1969
  * coerced to `false` and contradicted a CSR the caller never disagreed with. The value is a
1956
1970
  * boolean by the time it reaches here, so it is compared rather than coerced. */
1957
1971
  if (opts.requestedEncryption != null && opts.requestedEncryption !== derived.requestedEncryption) throw E("est/bad-input", "opts.requestedEncryption (" + opts.requestedEncryption + ") contradicts the CSR's advertised key-encryption attribute (" + derived.requestedEncryption + ") (RFC 7030 sec. 4.4.1)");
1972
+ // allow:caller-option-presence-tested-against-undefined -- absence means the recipient is not checked against the CSR, so a null must be refused
1958
1973
  if (opts.expectedRecipientKeyId !== undefined) {
1959
1974
  if (!Buffer.isBuffer(opts.expectedRecipientKeyId)) throw E("est/bad-input", "opts.expectedRecipientKeyId must be a Buffer");
1960
1975
  if (derived.expectedRecipientKeyId && !opts.expectedRecipientKeyId.equals(derived.expectedRecipientKeyId)) throw E("est/bad-input", "opts.expectedRecipientKeyId contradicts the key identifier the CSR advertised (RFC 7030 sec. 4.4.1)");
@@ -1968,7 +1983,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
1968
1983
  if ((opts.expectedRecipientKeyId !== undefined || opts.expectedRecipientIssuerSerial != null) && !derived.requestedEncryption) {
1969
1984
  throw E("est/bad-input", "a recipient expectation (expectedRecipientKeyId / expectedRecipientIssuerSerial) implies an encrypted key, but the CSR advertised no DecryptKeyIdentifier / AsymmetricDecryptKeyIdentifier attribute (RFC 7030 sec. 4.4.1)");
1970
1985
  }
1971
- return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts, proxySnap, undefined, checkedTransport)
1986
+ return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts, snap, undefined, checkedTransport)
1972
1987
  .then(function (res) { return _serverkeygenResult(res, opts, derived); });
1973
1988
  });
1974
1989
  }
@@ -2010,10 +2025,10 @@ function _csrattrsResult(res, opts) {
2010
2025
  */
2011
2026
  function csrattrs(baseUrl, opts) {
2012
2027
  opts = opts || intrinsic.create(null);
2013
- var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
2028
+ var snap; try { snap = _doorSnapshot(opts); } catch (e) { return _rej(e); }
2014
2029
  return _promiseResolve(_Promise, undefined).then(function () {
2015
2030
  opts = _knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
2016
- return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts, proxySnap, undefined, opts.transport);
2031
+ return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts, snap, undefined, opts.transport);
2017
2032
  }).then(function (res) { return _csrattrsResult(res, opts); });
2018
2033
  }
2019
2034
 
package/lib/guard-all.js CHANGED
@@ -22,6 +22,7 @@ var secret = require("./guard-secret");
22
22
  var parsed = require("./guard-parsed");
23
23
  var verdict = require("./guard-verdict");
24
24
  var async_ = require("./guard-async");
25
+ var distrust = require("./guard-distrust");
25
26
 
26
27
  module.exports = _freeze({
27
28
  bytes: bytes,
@@ -42,4 +43,5 @@ module.exports = _freeze({
42
43
  parsed: parsed,
43
44
  verdict: verdict,
44
45
  async: async_,
46
+ distrust: distrust,
45
47
  });