@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/est.js CHANGED
@@ -173,6 +173,17 @@ function _knownOpts(opts, known, verb) {
173
173
  Object.keys(known).sort().join(", ");
174
174
  });
175
175
  _assertTransport(settled);
176
+ /** @internal The two switches these verbs document, typed here because this is the one door every
177
+ * one of them passes through and it runs before any request goes out. Both are read further down
178
+ * by truthiness: `allowCrossOriginRedirect: "false"` is truthy, so it ALLOWED the cross-origin
179
+ * redirect on an unsafe method that the check exists to refuse (sec. 3.2.1), and
180
+ * `requestedEncryption: "false"` is read as "encryption was requested", which skips the
181
+ * sec. 4.4.2 check that an encrypted key part was advertised. A verb that does not accept one of
182
+ * them never sees a value for it. */
183
+ guard.identifier.booleanOption(settled.allowCrossOriginRedirect, E, "est/bad-input",
184
+ "pki.est." + verb + " opts.allowCrossOriginRedirect");
185
+ guard.identifier.booleanOption(settled.requestedEncryption, E, "est/bad-input",
186
+ "pki.est." + verb + " opts.requestedEncryption");
176
187
  return settled;
177
188
  }
178
189
 
@@ -907,10 +918,23 @@ function _parseUrl(urlStr) {
907
918
  return url;
908
919
  }
909
920
 
910
- /** @internal The settings this client forwards to its transport. */
911
- var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
912
- function _tlsForRequest(opts) {
913
- 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) };
914
938
  }
915
939
 
916
940
  function _redirectTarget(current, location, method, opts) {
@@ -969,7 +993,7 @@ function _authScheme(opts) {
969
993
  if (sc !== "basic" && sc !== "digest") throw E("est/bad-input", "opts.auth.scheme must be \"basic\" or \"digest\"");
970
994
  return sc;
971
995
  }
972
- if (opts.username !== undefined || opts.password !== undefined) return "basic";
996
+ if (opts.username != null || opts.password != null) return "basic";
973
997
  return null;
974
998
  }
975
999
  function _authUser(opts) { return opts.auth && opts.auth.username !== undefined ? opts.auth.username : opts.username; }
@@ -1084,7 +1108,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
1084
1108
  /** @internal `checked` is the transport the verb already took at its door. Taking it again here
1085
1109
  * would ask the caller's options a second time, and anything read in between could change the
1086
1110
  * answer, so a verb that holds one passes it and this reads the option only when none was held. */
1087
- function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt, checked) {
1111
+ function _client(op, method, baseUrl, body, headers, opts, snap, onAttempt, checked) {
1088
1112
  var base;
1089
1113
  try { base = new _URL(_stringOf(baseUrl)); }
1090
1114
  catch (e) { throw E("est/bad-url", "the EST base URL did not parse: " + guard.text.showValue(baseUrl), e); }
@@ -1092,14 +1116,14 @@ function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt,
1092
1116
  var url = _parseUrl(paths(baseUrl, { label: opts.label })[op]);
1093
1117
  var transport = checked !== undefined ? checked : _assertTransport(opts);
1094
1118
  if (!transport) {
1095
- var t = opts.tls != null ? opts.tls : intrinsic.create(null);
1119
+ var t = snap.tls;
1096
1120
  var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
1097
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)");
1098
1122
  transport = httpTransport.https({ E: E, errPrefix: "est" });
1099
1123
  }
1100
1124
  var budgets = {
1101
- tls: _tlsForRequest(opts),
1102
- proxy: proxySnap,
1125
+ tls: snap.tls,
1126
+ proxy: snap.proxy,
1103
1127
  timeout: guard.limits.cap(opts.timeout, "timeout", DEFAULT_TIMEOUT, { E: E, code: "est/bad-input", min: 1, max: MAX_TIMEOUT }),
1104
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 }),
1105
1129
  maxRedirects: guard.limits.cap(opts.maxRedirects, "maxRedirects", 5, { E: E, code: "est/bad-input", min: 0, max: 32 }),
@@ -1230,18 +1254,19 @@ function fullcmc(baseUrl, request, opts) {
1230
1254
  * caller's nested objects, and one of those reads must not be able to change which transport
1231
1255
  * the request goes out on, so the value that was checked is the value carried below. */
1232
1256
  var carriedTransport = opts.transport;
1233
- var proxySnap;
1234
- 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);
1235
1260
  der = _cmcRequestDer(request);
1236
1261
  wanted = _requestedPublicKeys(der);
1237
1262
  sent = _cmcSent(opts, der);
1238
1263
  /** @internal The copy below keeps own properties only, and a defaults bag holds its options on
1239
1264
  * a prototype, so the transport is put back from the value held at the door. */
1240
1265
  opts = _shallowCopy(opts);
1241
- if (carriedTransport !== undefined) opts.transport = carriedTransport;
1266
+ if (carriedTransport != null) opts.transport = carriedTransport;
1242
1267
  var body = transferEncode(der);
1243
1268
  return _client("fullcmc", "POST", baseUrl, body,
1244
- { 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)
1245
1270
  .then(function (res) { return _fullcmcResult(res, opts, wanted, sent); });
1246
1271
  } catch (e) {
1247
1272
  return _rej(e);
@@ -1560,7 +1585,7 @@ function _causedBy(e, target) {
1560
1585
 
1561
1586
  var ENROLL_HEADERS = intrinsic.assign(intrinsic.create(null), { accept: "application/pkcs7-mime", "content-type": "application/pkcs10" });
1562
1587
 
1563
- function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1588
+ function _enrollBound(op, baseUrl, builder, opts, snap) {
1564
1589
  /** @internal Held before anything else the caller supplied is read, and carried to the client:
1565
1590
  * the builder runs during the handshake, so a later read could answer with a different value. */
1566
1591
  var checkedTransport = opts.transport;
@@ -1590,7 +1615,7 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1590
1615
  .then(null, function (e) { attempt.error = e; attempt.done = true; throw e; });
1591
1616
  };
1592
1617
  }
1593
- 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)
1594
1619
  .then(function (res) {
1595
1620
  if (current && current.error) throw current.error;
1596
1621
  if (!current || !current.done) {
@@ -1606,13 +1631,13 @@ function _enrollBound(op, baseUrl, builder, opts, proxySnap) {
1606
1631
  });
1607
1632
  }
1608
1633
 
1609
- function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1610
- 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);
1611
1636
  var checkedTransport = opts.transport;
1612
1637
  var csrDer = _csrDer(csrInput);
1613
1638
  var spki = csr.parse(csrDer).subjectPublicKeyInfo;
1614
1639
  var body = transferEncode(csrDer);
1615
- 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)
1616
1641
  .then(function (res) { return _certsResult(op, res, opts, spki); });
1617
1642
  }
1618
1643
 
@@ -1635,7 +1660,7 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1635
1660
  *
1636
1661
  * @opts
1637
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`.
1638
- * - `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.
1639
1664
  * - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
1640
1665
  * - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
1641
1666
  * - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
@@ -1643,6 +1668,8 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1643
1668
  * There is no `"auto"`: the scheme is chosen here, not by whatever a server offers. `username` / `password`
1644
1669
  * at the top level are the older form and mean Basic. Answered only after the transport authenticated the server.
1645
1670
  * - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on an unsafe method.
1671
+ * A value other than `true`, `false`, `null` or an absent option is refused `est/bad-input`
1672
+ * before the request goes out.
1646
1673
  * @example
1647
1674
  * // a live CA uses the default pki.transport.https; here an injected transport returns a canned bag
1648
1675
  * var r = await pki.est.cacerts("https://ca.example",
@@ -1651,10 +1678,10 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1651
1678
  */
1652
1679
  function cacerts(baseUrl, opts) {
1653
1680
  opts = opts || intrinsic.create(null);
1654
- 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); }
1655
1682
  return _promiseResolve(_Promise, undefined).then(function () {
1656
1683
  opts = _knownOpts(opts, CACERTS_OPTS, "cacerts");
1657
- 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);
1658
1685
  }).then(function (res) { return _certsResult("cacerts", res, opts, null); });
1659
1686
  }
1660
1687
 
@@ -1695,7 +1722,9 @@ function cacerts(baseUrl, opts) {
1695
1722
  * - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `proxy` / `now`, as in pki.est.cacerts.
1696
1723
  * - `strict`: reject an enroll response that carries more than the single issued certificate.
1697
1724
  * - `username` / `password` / `auth` -- HTTP credentials: top-level `username` / `password` mean Basic; `auth` (`{ scheme: "basic" | "digest", username, password, ... }`, as in pki.est.cacerts) selects Basic or Digest. Answered only after server authorization (empty username allowed).
1698
- * - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on this POST.
1725
+ * - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on this POST. A
1726
+ * value other than `true`, `false`, `null` or an absent option is refused `est/bad-input`
1727
+ * before the request goes out.
1699
1728
  * @example
1700
1729
  * var req = await pki.csr.sign({ subject: "device.example", subjectPublicKey: signerSpki }, { key: signerKeyPkcs8 });
1701
1730
  * // a 202 means the CA queued the request -- the verb surfaces the delay, never sleeps
@@ -1717,10 +1746,10 @@ function cacerts(baseUrl, opts) {
1717
1746
  */
1718
1747
  function simpleenroll(baseUrl, csrInput, opts) {
1719
1748
  opts = opts || intrinsic.create(null);
1720
- 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); }
1721
1750
  return _promiseResolve(_Promise, undefined).then(function () {
1722
1751
  opts = _knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
1723
- return _enroll("simpleenroll", baseUrl, csrInput, opts, proxySnap);
1752
+ return _enroll("simpleenroll", baseUrl, csrInput, opts, snap);
1724
1753
  });
1725
1754
  }
1726
1755
 
@@ -1757,12 +1786,12 @@ function simpleenroll(baseUrl, csrInput, opts) {
1757
1786
  */
1758
1787
  function simplereenroll(baseUrl, csrInput, opts) {
1759
1788
  opts = opts || intrinsic.create(null);
1760
- 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); }
1761
1790
  return _promiseResolve(_Promise, undefined).then(function () {
1762
1791
  opts = _knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
1763
1792
  if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
1764
1793
  if (typeof csrInput !== "function") reenrollGuard(opts.oldCert, _csrDer(csrInput));
1765
- return _enroll("simplereenroll", baseUrl, csrInput, opts, proxySnap);
1794
+ return _enroll("simplereenroll", baseUrl, csrInput, opts, snap);
1766
1795
  });
1767
1796
  }
1768
1797
 
@@ -1919,6 +1948,8 @@ async function _serverkeygenResult(res, opts, derived) {
1919
1948
  * @opts
1920
1949
  * - `requestedEncryption` / `expectedRecipientKeyId` / `expectedRecipientIssuerSerial` -- OPTIONAL
1921
1950
  * overrides of the CSR-derived recipient coherence; a value that contradicts the CSR is `est/bad-input`.
1951
+ * `requestedEncryption` is a boolean: a value other than `true`, `false`, `null` or an absent
1952
+ * option is refused `est/bad-input`.
1922
1953
  * - every option of pki.est.simpleenroll (transport, tls, label, budgets, credentials incl. `auth`).
1923
1954
  * @example
1924
1955
  * var r = await pki.est.serverkeygen("https://ca.example", csrDer,
@@ -1927,13 +1958,18 @@ async function _serverkeygenResult(res, opts, derived) {
1927
1958
  */
1928
1959
  function serverkeygen(baseUrl, csrInput, opts) {
1929
1960
  opts = opts || intrinsic.create(null);
1930
- 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); }
1931
1962
  return _promiseResolve(_Promise, undefined).then(function () {
1932
1963
  opts = _knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
1933
1964
  var checkedTransport = opts.transport;
1934
1965
  var csrDer = _csrDer(csrInput);
1935
1966
  var derived = _serverkeygenEncryptionFromCsr(csrDer);
1936
- if (opts.requestedEncryption !== undefined && !!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)");
1967
+ /** @internal An override is a value the caller stated, and `null` states nothing: it is the
1968
+ * absent form the switch door accepts, so the CSR's own answer stands. Read as present it
1969
+ * coerced to `false` and contradicted a CSR the caller never disagreed with. The value is a
1970
+ * boolean by the time it reaches here, so it is compared rather than coerced. */
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
1937
1973
  if (opts.expectedRecipientKeyId !== undefined) {
1938
1974
  if (!Buffer.isBuffer(opts.expectedRecipientKeyId)) throw E("est/bad-input", "opts.expectedRecipientKeyId must be a Buffer");
1939
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)");
@@ -1947,7 +1983,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
1947
1983
  if ((opts.expectedRecipientKeyId !== undefined || opts.expectedRecipientIssuerSerial != null) && !derived.requestedEncryption) {
1948
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)");
1949
1985
  }
1950
- 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)
1951
1987
  .then(function (res) { return _serverkeygenResult(res, opts, derived); });
1952
1988
  });
1953
1989
  }
@@ -1989,10 +2025,10 @@ function _csrattrsResult(res, opts) {
1989
2025
  */
1990
2026
  function csrattrs(baseUrl, opts) {
1991
2027
  opts = opts || intrinsic.create(null);
1992
- 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); }
1993
2029
  return _promiseResolve(_Promise, undefined).then(function () {
1994
2030
  opts = _knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
1995
- 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);
1996
2032
  }).then(function (res) { return _csrattrsResult(res, opts); });
1997
2033
  }
1998
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
  });
@@ -0,0 +1,224 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ // @internal
5
+
6
+ var util = require("util");
7
+ var intrinsic = require("./guard-intrinsic");
8
+ var constants = require("./constants");
9
+ var gBytes = require("./guard-bytes");
10
+ var gList = require("./guard-list");
11
+ var gCrypto = require("./guard-crypto");
12
+
13
+ var _freeze = intrinsic.freeze;
14
+ var _isArray = intrinsic.isArray;
15
+ var _create = intrinsic.create;
16
+ var _hasOwn = intrinsic.hasOwn;
17
+ var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
18
+ var _bufToString = intrinsic.bufToString;
19
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
20
+ var _fromCharCode = intrinsic.fromCharCode;
21
+ var _isProxy = util.types.isProxy;
22
+
23
+ var DIGEST_BYTES = 32;
24
+ var DIGEST_HEX = 64;
25
+
26
+ /** @internal One hexadecimal character, folded to lower case, or -1. The fold is arithmetic on the
27
+ * character code, so the normalized digest is not the answer of a `toLowerCase` read off the live
28
+ * prototype. */
29
+ function _lowerHexCode(c) {
30
+ if (c >= 48 && c <= 57) return c;
31
+ if (c >= 97 && c <= 102) return c;
32
+ if (c >= 65 && c <= 70) return c + 32;
33
+ return -1;
34
+ }
35
+
36
+ function _hexEntry(s, E, code, where) {
37
+ if (s.length !== DIGEST_HEX) {
38
+ throw E(code, where + " must be a SHA-256 digest of a SubjectPublicKeyInfo: 64 hexadecimal characters or 32 bytes, got " + s.length + " characters");
39
+ }
40
+ var out = "";
41
+ for (var i = 0; i < DIGEST_HEX; i++) {
42
+ var c = _lowerHexCode(_charCodeAt(s, i));
43
+ if (c === -1) throw E(code, where + " must be 64 hexadecimal characters with no separators; character " + i + " is not a hexadecimal digit");
44
+ out += _fromCharCode(c);
45
+ }
46
+ return out;
47
+ }
48
+
49
+ function _bytesEntry(v, E, code, where) {
50
+ var copy = gBytes.snapshotSource(v, E, code, where);
51
+ if (copy.length !== DIGEST_BYTES) {
52
+ throw E(code, where + " must be a SHA-256 digest of a SubjectPublicKeyInfo: 32 bytes or 64 hexadecimal characters, got " + copy.length + " bytes");
53
+ }
54
+ return _bufToString(copy, "hex");
55
+ }
56
+
57
+ /** The caller's list of distrusted keys, read once and returned as a frozen list of lower-case hex
58
+ * digests with duplicates removed, or `null` when the option is absent or `null`. Each entry is the
59
+ * SHA-256 digest of a certificate's DER SubjectPublicKeyInfo, given as 64 hexadecimal characters or
60
+ * as 32 bytes.
61
+ *
62
+ * Every slot is read through its descriptor, so a hole, an accessor and a Proxy are refused rather
63
+ * than read: a hole is answered by the array prototype and an accessor answers each read separately,
64
+ * and either one makes the list the check uses differ from the list the caller wrote. The list is a
65
+ * refusal list, so reading it as shorter than it is lets through a key the caller distrusted.
66
+ *
67
+ * @enforced-by behavioral -- the defect is a door that does not call this, which has no rename-proof
68
+ * code shape; the guard is the RED vector each verb carries, driving a listed key through the
69
+ * shipped verb and asserting the refusal.
70
+ */
71
+ function keys(value, E, code, label) {
72
+ if (value === undefined || value === null) return null;
73
+ if (typeof value !== "object" || _isProxy(value) || !_isArray(value)) {
74
+ throw E(code, label + " must be an array of SHA-256 SubjectPublicKeyInfo digests");
75
+ }
76
+ var n = gList.count(value, "keys");
77
+ if (n > constants.LIMITS.DISTRUSTED_KEYS_MAX) {
78
+ throw E(code, label + " has " + n + " entries, more than the " + constants.LIMITS.DISTRUSTED_KEYS_MAX + " this toolkit reads");
79
+ }
80
+ var seen = _create(null);
81
+ var out = [];
82
+ for (var i = 0; i < n; i++) {
83
+ var where = label + "[" + i + "]";
84
+ var d = _getOwnPropertyDescriptor(value, i);
85
+ if (!d) throw E(code, where + " is a hole; every entry must be present");
86
+ if (d.get || d.set) throw E(code, where + " is an accessor; every entry must be a data property");
87
+ var v = d.value;
88
+ var hex;
89
+ if (typeof v === "string") hex = _hexEntry(v, E, code, where);
90
+ else if (gBytes.isByteSource(v)) hex = _bytesEntry(v, E, code, where);
91
+ else throw E(code, where + " must be 64 hexadecimal characters or 32 bytes");
92
+ if (_hasOwn(seen, hex)) continue;
93
+ seen[hex] = true;
94
+ gList.append(out, hex);
95
+ }
96
+ return _freeze(out);
97
+ }
98
+
99
+ /** The lower-case hex SHA-256 of `spkiBytes`, the DER SubjectPublicKeyInfo exactly as a certificate
100
+ * or an anchor carries it.
101
+ *
102
+ * @enforced-by behavioral -- a digest has no rename-proof shape that separates this use from the
103
+ * others `guard-crypto` serves; the guard is the membership vector, which fails if the bytes hashed
104
+ * are not the ones listed.
105
+ */
106
+ function digest(spkiBytes) {
107
+ return gCrypto.digest("sha256", spkiBytes, { output: "hex" });
108
+ }
109
+
110
+ /** @internal The codec and the registry, reached through inline requires: both load this module
111
+ * through guard-all, so both are loaded by the time a digest is asked for. */
112
+ function _codec() {
113
+ return {
114
+ asn1: require("./asn1-der"), // allow:inline-require -- circular load: asn1-der -> guard-all -> this module
115
+ oid: require("./oid"), // allow:inline-require -- circular load: oid -> guard-all -> this module
116
+ };
117
+ }
118
+
119
+ /** @internal The SubjectPublicKeyInfo with an identifier that restricts the key's use replaced by the
120
+ * key's own: id-ecDH and id-ecMQV (RFC 5480 sec. 2.1.2) become ecPublicKey with the curve kept, and
121
+ * RSASSA-PSS and RSAES-OAEP (RFC 4055) become rsaEncryption with NULL. The subjectPublicKey bits are
122
+ * the same key in each pair. Any other identifier is returned unchanged. */
123
+ function _unrestricted(spkiBytes, c) {
124
+ var b = c.asn1.build;
125
+ var root = c.asn1.decode(spkiBytes);
126
+ var alg = root.children[0];
127
+ var name = c.oid.name(c.asn1.read.oid(alg.children[0]));
128
+ if (name === "ecDH" || name === "ecMQV") {
129
+ var params = alg.children.length > 1 ? [b.raw(alg.children[1].bytes)] : [];
130
+ return b.sequence([b.sequence(intrinsic.concatList([b.oid(c.oid.byName("ecPublicKey"))], params)), b.raw(root.children[1].bytes)]);
131
+ }
132
+ if (name === "rsassaPss" || name === "rsaesOaep") {
133
+ return b.sequence([b.sequence([b.oid(c.oid.byName("rsaEncryption")), b.nullValue()]), b.raw(root.children[1].bytes)]);
134
+ }
135
+ return spkiBytes;
136
+ }
137
+
138
+ /** @internal The key in canonical form, or null when node does not read the bytes as a public key.
139
+ * An elliptic curve key is written with ecPublicKey, its named curve and an uncompressed point,
140
+ * whatever point form or parameter form it was read in. Any other key node writes as a JWK is
141
+ * written the way node writes a key it builds from one, which puts an RSA key under rsaEncryption
142
+ * with NULL. A key with neither form is re-exported as node read it. */
143
+ function _canonicalSpki(spkiBytes) {
144
+ try {
145
+ var c = _codec();
146
+ var spki = _unrestricted(spkiBytes, c);
147
+ var key = gCrypto.publicKey({ key: spki, format: "der", type: "spki" });
148
+ if (gCrypto.keyTypeOf(key) === "ec") {
149
+ var details = gCrypto.keyDetailsOf(key);
150
+ var curveOid = details && typeof details.namedCurve === "string" ? c.oid.byName(details.namedCurve) : undefined;
151
+ if (curveOid !== undefined) {
152
+ var b = c.asn1.build;
153
+ var point = c.asn1.read.bitString(c.asn1.decode(spki).children[1]).bytes;
154
+ var uncompressed = gCrypto.ecConvertPoint(point, details.namedCurve, "uncompressed");
155
+ return b.sequence([b.sequence([b.oid(c.oid.byName("ecPublicKey")), b.oid(curveOid)]), b.bitString(uncompressed, 0)]);
156
+ }
157
+ }
158
+ var jwk = null;
159
+ try { jwk = gCrypto.exportKey(key, { format: "jwk" }); }
160
+ catch (_je) {
161
+ // allow:swallow-unverified a key type with no JWK form is re-exported as node read it
162
+ jwk = null;
163
+ }
164
+ if (jwk !== null) key = gCrypto.publicKey({ key: jwk, format: "jwk" });
165
+ return gCrypto.exportKey(key, { type: "spki", format: "der" });
166
+ } catch (_e) {
167
+ // allow:swallow-unverified a key node does not read has no second encoding, and the raw digest still decides
168
+ return null;
169
+ }
170
+ }
171
+
172
+ /** The digest a distrust entry names a key by: the lower-case hex SHA-256 of the key's
173
+ * SubjectPublicKeyInfo in canonical form, an uncompressed EC point with its named curve and an RSA
174
+ * key with NULL parameters. That is how nearly every certificate encodes a key, so for those it is
175
+ * the digest of the bytes the certificate carries. A key node does not read is digested as given.
176
+ *
177
+ * @enforced-by behavioral -- a digest has no rename-proof shape; the guard is the two-direction
178
+ * re-encoding vectors in guard-distrust.test.js.
179
+ */
180
+ function keyDigest(spkiBytes) {
181
+ var canonical = _canonicalSpki(spkiBytes);
182
+ return digest(canonical !== null ? canonical : spkiBytes);
183
+ }
184
+
185
+ /** Whether the key `spkiBytes` names is on `list`, a value `keys` returned. A `null` or empty list
186
+ * answers `false`. The digest of the bytes as given is compared, and so is `keyDigest`, so an entry
187
+ * `keyDigest` computed matches the key however a certificate encodes it (a compressed EC point, RSA
188
+ * parameters without NULL), and an entry computed over raw bytes matches a certificate with the same
189
+ * bytes.
190
+ *
191
+ * @enforced-by behavioral -- membership has no rename-proof code shape; the RED vectors are the
192
+ * listed-key refusals at each verb, and the re-encodings in guard-distrust.test.js in both directions.
193
+ */
194
+ function has(list, spkiBytes) {
195
+ if (list === null || list === undefined || list.length === 0 || spkiBytes === null || spkiBytes === undefined) return false;
196
+ if (gList.contains(list, digest(spkiBytes))) return true;
197
+ return gList.contains(list, keyDigest(spkiBytes));
198
+ }
199
+
200
+ /** Every entry of two lists that `keys` returned, once, as a frozen list; `null` when both are
201
+ * `null`. A transport default and a per-request list are combined this way, so a request cannot
202
+ * remove a key the default distrusts by naming a shorter list.
203
+ *
204
+ * @enforced-by behavioral -- a union has no rename-proof code shape; the RED vector is a request
205
+ * list that omits a default entry and the default entry still being refused.
206
+ */
207
+ function union(a, b) {
208
+ if ((a === null || a === undefined) && (b === null || b === undefined)) return null;
209
+ var seen = _create(null);
210
+ var out = [];
211
+ var parts = [a || [], b || []];
212
+ for (var p = 0; p < 2; p++) {
213
+ var part = parts[p];
214
+ for (var i = 0, n = gList.count(part, "union"); i < n; i++) {
215
+ var hex = part[i];
216
+ if (_hasOwn(seen, hex)) continue;
217
+ seen[hex] = true;
218
+ gList.append(out, hex);
219
+ }
220
+ }
221
+ return _freeze(out);
222
+ }
223
+
224
+ module.exports = _freeze({ keys: keys, digest: digest, keyDigest: keyDigest, has: has, union: union });
@@ -743,7 +743,32 @@ function assertCallableOption(opts, key, E, code, label, shape) {
743
743
  // @enforced-by behavioral -- guard: each drives the shipped verb with a non-boolean and asserts the
744
744
  // @enforced-by behavioral -- typed refusal, and with false and absence to pin the two off forms.
745
745
  function booleanOption(value, E, code, label) {
746
- if (value === undefined || value === null) return false;
746
+ return _switchValue(value, false, E, code, label);
747
+ }
748
+
749
+ /** @internal The same read for a switch whose absent form is ON: an option the caller turns OFF,
750
+ * where the documented default is the state an omitted option selects. The two differ only in what
751
+ * absence means, so the type rule is written once and each boundary names the default it documents.
752
+ * A boundary comparing such an option to `false` answers for every other value as the default
753
+ * state, which is how `certificates: "false"` embedded the certificate the caller asked to
754
+ * withhold; once the value has been through here, that comparison reads a boolean.
755
+ *
756
+ * @enforced-by behavioral -- the per-verb vectors in coercion-safe-options.test.js drive each
757
+ * @enforced-by behavioral -- shipped verb with a non-boolean and assert the typed refusal names the
758
+ * @enforced-by behavioral -- option, and with true, false and absence to pin the three accepted
759
+ * @enforced-by behavioral -- forms. The comparison this replaces has its own codebase-wide
760
+ * @enforced-by behavioral -- detector, optout-switch-read-by-coercion.
761
+ */
762
+ function booleanOptionDefaultOn(value, E, code, label) {
763
+ return _switchValue(value, true, E, code, label);
764
+ }
765
+
766
+ /** @internal A switch is the boolean it documents, an absent one is the stated default, and every
767
+ * other value is refused. The value is rendered from its own type rather than coerced, so a caller
768
+ * value with a throwing `toString` reports the typed refusal. `null` is read as absence, the same
769
+ * reading the rest of this module gives it. */
770
+ function _switchValue(value, whenAbsent, E, code, label) {
771
+ if (value === undefined || value === null) return whenAbsent;
747
772
  if (value === true || value === false) return value;
748
773
  throw E(code, label + " must be true or false, got " + _showSwitch(value));
749
774
  }
@@ -776,6 +801,7 @@ function declaresOwn(obj, name) {
776
801
  module.exports = intrinsic.freeze({
777
802
  assertCallable: assertCallable,
778
803
  booleanOption: booleanOption,
804
+ booleanOptionDefaultOn: booleanOptionDefaultOn,
779
805
  declaresOwn: declaresOwn,
780
806
  assertCallableOption: assertCallableOption,
781
807
  assertCanonicalOid: assertCanonicalOid,
package/lib/guard-json.js CHANGED
@@ -24,7 +24,7 @@ var _hexVal = require("./guard-encoding").hexNibble;
24
24
  // @enforced-by json-parse-not-via-guard
25
25
  function parse(input, ErrorClass, spec) {
26
26
  function E(code, message, cause) { return new ErrorClass(code, message, cause); }
27
- if (spec.maxBytes === undefined || spec.maxDepth === undefined) {
27
+ if (spec.maxBytes == null || spec.maxDepth == null) {
28
28
  throw new TypeError("guard.json.parse: spec.maxBytes and spec.maxDepth are required");
29
29
  }
30
30
  var maxBytes = limits.cap(spec.maxBytes, "guard.json.parse spec.maxBytes", undefined);