@blamejs/pki 0.8.57 → 0.8.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to `@blamejs/pki` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.8.59 — 2026-10-07
8
+
9
+ Render an object identifier, match a header name and read a media type through operations a replacement cannot answer.
10
+
11
+ ### Changed
12
+
13
+ - The check that holds these modules to captured operations now counts a method reached through an index, not only one reached through a call. It is the same receiver one bracket over, and the earlier form could not see it: two live copies in the RFC 5280 policy-mapping walk, where the copy becomes a node's expected policy set, were found by other means.
14
+
15
+ ### Fixed
16
+
17
+ - An object identifier is rendered to its dotted form through operations bound when the module loaded, and its arcs are collected by defining each index rather than by appending to the array. Three different replacements each decided that string, which is the value `pki.oid` resolves, which selects a signature algorithm and which names an extension: a replaced integer conversion made the content octets of `1.2.3` render as `1.3.4`, and an accessor inherited at the first index of every array took each arc as it was collected and made the same octets render as `0.1.3`. Appending stores at an index, and a store consults the prototype chain for a setter, so a list this build assembles is now built by defining its indexes.
18
+ - A media type is read on the delimiter character and folded through a captured conversion, in `pki.acme`, `pki.cmp` and `pki.ct`. The token decides which parser a response body is handed to, and it was produced by splitting, trimming and folding through live operations. Splitting is now a character scan, which this toolkit requires in its libraries because a separator's own split hook is part of that operation.
19
+ - `pki.est` reads a content-type parameter through captured operations, and collects the segments it searches by defining each index. That is where the RFC 7030 multipart boundary comes from, so the value decides where a multi-part response is divided: measured, a header declaring `boundary=wire` yielded `forged`, both through a replaced append and through an accessor inherited at an array index. The name comparison that selects a parameter, the slicing that extracts it and the quote stripping around it were each live, as were the name and value of every header on a part, which is where a part's own content type comes from: measured, a part declaring `application/pkcs8` was read as `application/pkcs7-mime` carrying `smime-type=certs-only`. Its two case-insensitive header lookups are folded through the capture on both sides of the comparison.
20
+ - `pki.acme` parses a Link header through captured operations: the header is divided into links, each link into parameters, and the target cut out of its angle brackets, all through operations bound when the module loaded. The relation decides whether a header names a resource the client will follow and the target is the URL it would fetch, and both were produced by methods read off the live prototype: measured, a header stating one alternate chain resolved to a different URL.
21
+ - `pki.cmc.build` selects which request arm a message carries through a selection that reads each candidate once and builds its own result, rather than through a method that is both read off the prototype and specified to construct its result through a replaceable protocol.
22
+ - The algorithm, extension and criticality tables that `pki.path.validate`, `pki.cms`, `pki.crl`, `pki.key`, `pki.webcrypto`, `pki.x509` and the signature-scheme registry build when they load are built through captured walks, so a replacement installed before a module loads cannot decide which rows exist.
23
+
24
+ ## v0.8.58 — 2026-10-07
25
+
26
+ Connect under the trust anchors, the address and the identity check the request named, not the ones a second read answers with.
27
+
28
+ ### Fixed
29
+
30
+ - `pki.transport.https` applies the private-address refusal the request asked for. `blockPrivateAddresses` was read once to test for presence and again for the value, so a value that answered `true` and then `false` left the refusal unarmed: measured, a request to `127.0.0.1` completed with the block in force. The same split applied to `tls.servername`, which is the name the server's certificate is verified against, to `tls.checkServerIdentity`, where the caller's hook was found to be a function and then not installed, to `tls` and `tls.anchors` themselves, to the transport's own `defaults.tls`, to `proxy`, and to `timeout` and `maxResponseBytes`. Each is now read once.
31
+ - `pki.transport.https` connects under the trust anchors the request named. The list is assembled by appending to an empty array and the appending operation was read off the live prototype, so one answering with an empty array left the connection with no trust list of its own and the platform's certificate store decided instead: a caller that passed `tls.anchors` to pin one issuer was pinned to none of them. Measured against a loopback endpoint whose certificate only the named anchor trusts, the request then failed on the platform store's verdict rather than succeeding on the caller's. The proxy CONNECT path assembled its own trust list the same way.
32
+ - The address a private-address check cleared is the address the connection is made to. A resolver that returns a list had its own entries forwarded to the socket layer, so an entry whose address answered a public value to the check and a loopback one afterwards reached loopback with `blockPrivateAddresses` set. Each address is now read once, that read is what is classified, and the snapshot is what is handed on.
33
+ - A sparse proxy trust-anchor list is refused rather than read. The eligibility check walks the array's own property descriptors, which cannot see a gap, so a list whose gap was answered from the array prototype was validated as holding one anchor and converted as holding another: reading an array by index is specified to consult the prototype at a hole. A list with a gap is now refused as `transport/bad-proxy`.
34
+ - The platform trust store is read through the provider bound when the module loads, so a replacement installed afterwards cannot answer with its own certificate list for a `useSystemStore` connection.
35
+ - Every connection is opened through the function bound when the module loaded, not a property read off `node:https`, `node:http` or `node:tls` at the moment of the call: the request itself, the proxy CONNECT, the TLS connection made over a tunnel, and the one `pki.transport.peerChain` opens. A replacement installed afterwards sent a request prepared for one destination to another and handed the caller that peer's answer.
36
+ - The socket itself is opened by this module rather than by the agent node builds, on the request and on the proxy CONNECT alike. `agent: false` does not mean no agent, which is what it had been relied on for: node builds one from the default agent's constructor, and that agent's own connection builder opened the socket, so replacing it returned a connection to another destination and the request went out over it. Measured, an `https:` request with `blockPrivateAddresses` set and certificate verification on reached a local plaintext listener that presented no certificate, and its body reached the caller. On the CONNECT path the same replacement could answer with a plain socket in place of the proxy's TLS connection, which is the request that carries `Proxy-Authorization`. The request options node computed for the hop are forwarded unchanged, so every TLS setting still applies, and there is still no connection pooling.
37
+ - A per-request `tls.checkServerIdentity` keeps the transport's default from being read at all. The default was read whether or not the request overrode it, so a default supplied as an accessor ran even for a request that replaced it, and one that throws refused the request before it connected.
38
+ - A response is as long as the bytes that arrived, and holds those bytes. It is allocated at the counted length and written through operations bound at load, because three earlier spellings were each steerable: a copy read off the prototype, a slice whose result is built through the species constructor, and a conversion that reads the received view's own `valueOf` and `length`. The last of those decided the length as well as the content: measured, an eight-byte response came back as 64 bytes with a `maxResponseBytes` of 8, so the size cap was reported as enforced on a body that exceeded it.
39
+ - Every remaining operation `pki.transport.https` reaches a socket through is bound when the module loads: the PEM armor and IPv6 bracket scanners, the peer-chain walk, the anchor conversion, the TLS error classifier that decides whether a failure is reported as a trust failure or a transport one, the header names a proxy credential is stripped from, and the CONNECT tunnel's own options. The trust lists are assembled by copying and appending index by index, because appending with the built-in consults a spreading hook each argument can inherit, which replaced a validated anchor with another one.
40
+ - `guard.limits.cap` compares a value against the bound it validated. `min` and `max` were each read more than once, with the integer check reading one value and the comparison another, so a bound answering a small integer to the check and a large one to the comparison configured a cap that admitted a value above its own limit. Each bound is now read once. No shipped verb could reach this, because every caller in the toolkit passes a fixed bound; the guard is what has to hold for the next one.
41
+
7
42
  ## v0.8.57 — 2026-10-07
8
43
 
9
44
  Read the OCSP response a CMS message carries in its crls field, instead of accepting it unread.
package/lib/acme.js CHANGED
@@ -88,6 +88,7 @@ var _sortI = intrinsic.sort;
88
88
  var _objectKeys = intrinsic.keys;
89
89
  var _lower = intrinsic.toLowerCase;
90
90
  var _StringI = intrinsic.String;
91
+ var _acmeTrim = intrinsic.uncurry(String.prototype.trim);
91
92
  var _stringify = intrinsic.stringify;
92
93
  var _push = intrinsic.push;
93
94
  var _pop = intrinsic.pop;
@@ -98,6 +99,7 @@ var _split = intrinsic.uncurry(String.prototype.split);
98
99
  var _parseIntI = intrinsic.parseInt;
99
100
  var _numToString = intrinsic.numberToString;
100
101
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
102
+ var _charAtA = intrinsic.uncurry(String.prototype.charAt);
101
103
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
102
104
  var _charAt = intrinsic.uncurry(String.prototype.charAt);
103
105
  var pkgVersion = require("../package.json").version;
@@ -1707,61 +1709,71 @@ function _hasCtlOctet(s) {
1707
1709
  }
1708
1710
 
1709
1711
  function _splitLinkValues(s) {
1712
+ /** @internal The header is cut through the captured operations, the same as the parameters below:
1713
+ * each piece becomes a link whose relation decides whether this client follows it. */
1710
1714
  var out = [], start = 0, inAngle = false, inQuote = false;
1711
1715
  for (var i = 0; i < s.length; i++) {
1712
- var ch = s.charAt(i);
1716
+ var ch = _charAtA(s, i);
1713
1717
  if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
1714
1718
  if (ch === "\"") { inQuote = true; continue; }
1715
1719
  if (ch === "<") inAngle = true;
1716
1720
  else if (ch === ">") inAngle = false;
1717
- else if (ch === "," && !inAngle) { out.push(s.slice(start, i)); start = i + 1; }
1721
+ else if (ch === "," && !inAngle) { intrinsic.append(out, _strSlice(s, start, i)); start = i + 1; }
1718
1722
  }
1719
1723
  if (inQuote || inAngle) throw E("acme/bad-link", "the Link header has an unterminated quote or angle bracket (RFC 8288)");
1720
- out.push(s.slice(start));
1724
+ intrinsic.append(out, _strSlice(s, start));
1721
1725
  return out;
1722
1726
  }
1723
1727
  function _splitLinkParams(rest) {
1724
1728
  var parts = [], start = 0, inQuote = false;
1725
1729
  for (var i = 0; i < rest.length; i++) {
1726
- var ch = rest.charAt(i);
1730
+ var ch = _charAtA(rest, i);
1727
1731
  if (inQuote) { if (ch === "\\") { i++; continue; } if (ch === "\"") inQuote = false; continue; }
1728
1732
  if (ch === "\"") inQuote = true;
1729
- else if (ch === ";") { parts.push(rest.slice(start, i)); start = i + 1; }
1733
+ else if (ch === ";") { intrinsic.append(parts, _strSlice(rest, start, i)); start = i + 1; }
1730
1734
  }
1731
- parts.push(rest.slice(start));
1735
+ intrinsic.append(parts, _strSlice(rest, start));
1732
1736
  var out = [];
1733
1737
  for (var j = 0; j < parts.length; j++) {
1734
1738
  var p = _trimOWS(parts[j]);
1735
1739
  if (p === "") { if (j === 0) continue; throw E("acme/bad-link", "an empty Link parameter (a ';' with no parameter) is malformed (RFC 8288): " + JSON.stringify(rest)); }
1736
- var eq = p.indexOf("=");
1737
- var name = (eq === -1 ? p : _trimOWS(p.slice(0, eq))).toLowerCase();
1740
+ /** @internal The parameter name and value are cut and folded through the captured operations.
1741
+ * A Link parameter is what names the relation this client follows, and read off the prototype a
1742
+ * replacement chose it: measured, a header stating `rel=next` parsed as `alternate`. */
1743
+ var eq = intrinsic.stringIndexOf(p, "=");
1744
+ var name = _lower(eq === -1 ? p : _trimOWS(_strSlice(p, 0, eq)));
1738
1745
  if (!pkix.allCharsIn(name, _LINK_TOKEN_TABLE)) throw E("acme/bad-link", "a Link parameter name must be a token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
1739
- if (eq === -1) { out.push({ name: name, value: "", hasValue: false }); continue; }
1740
- var v = _trimOWS(p.slice(eq + 1));
1741
- if (v.length >= 2 && v.charAt(0) === "\"" && v.charAt(v.length - 1) === "\"") {
1742
- var inner = v.slice(1, -1);
1746
+ if (eq === -1) { intrinsic.append(out, { name: name, value: "", hasValue: false }); continue; }
1747
+ var v = _trimOWS(_strSlice(p, eq + 1));
1748
+ if (v.length >= 2 && _charAtA(v, 0) === "\"" && _charAtA(v, v.length - 1) === "\"") {
1749
+ var inner = _strSlice(v, 1, -1);
1743
1750
  if (!_isValidQuotedInterior(inner)) throw E("acme/bad-link", "a quoted Link parameter value is malformed (an unescaped quote or dangling backslash, RFC 7230): " + JSON.stringify(p));
1744
1751
  v = _unescapeQuotedPair(inner);
1745
1752
  } else if (!pkix.allCharsIn(v, _LINK_TOKEN_TABLE)) {
1746
1753
  throw E("acme/bad-link", "an unquoted Link parameter value must be a non-empty token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
1747
1754
  }
1748
- out.push({ name: name, value: v, hasValue: true });
1755
+ intrinsic.append(out, { name: name, value: v, hasValue: true });
1749
1756
  }
1750
1757
  return out;
1751
1758
  }
1752
1759
  function _relHas(rel, relName) {
1753
- var toks = String(rel).split(" ");
1754
- for (var i = 0; i < toks.length; i++) { if (toks[i].toLowerCase() === relName) return true; }
1760
+ /** @internal The relation names are scanned on the delimiter CHARACTER, which hard rule 11 requires
1761
+ * in lib/, and each token is folded through the captured conversion: the match decides whether a
1762
+ * Link header names the resource this client then follows. */
1763
+ var toks = intrinsic.splitChar(_StringI(rel), 32);
1764
+ for (var i = 0; i < toks.length; i++) { if (_lower(toks[i]) === relName) return true; }
1755
1765
  return false;
1756
1766
  }
1757
1767
  function _parseLinkValue(raw) {
1758
1768
  var s = _trimOWS(raw);
1759
1769
  if (s === "") return null;
1760
- var gt = s.indexOf(">");
1761
- if (s.charAt(0) !== "<" || gt === -1) throw E("acme/bad-link", "a Link value must be <URI-Reference> with parameters (RFC 8288): " + JSON.stringify(raw));
1770
+ var gt = intrinsic.stringIndexOf(s, ">");
1771
+ if (_charAtA(s, 0) !== "<" || gt === -1) throw E("acme/bad-link", "a Link value must be <URI-Reference> with parameters (RFC 8288): " + JSON.stringify(raw));
1762
1772
  var rest = _stripLeadingOWS(s, gt + 1);
1763
- if (rest !== "" && rest.charAt(0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
1764
- var uri = s.slice(1, gt);
1773
+ if (rest !== "" && _charAtA(rest, 0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
1774
+ /** @internal The target this client may follow is cut through the captured operation: read off the
1775
+ * prototype, a replacement decided the URL a Link header resolves to. */
1776
+ var uri = _strSlice(s, 1, gt);
1765
1777
  if (_linkUriInvalid(uri)) throw E("acme/bad-link", "a Link URI-Reference contains a character, percent-escape, or encoded dot-segment not permitted by RFC 3986: " + JSON.stringify(raw));
1766
1778
  var params = _splitLinkParams(rest), rel = "", relSeen = false, anchor = null, anchorSeen = false;
1767
1779
  for (var i = 0; i < params.length; i++) {
@@ -1780,14 +1792,16 @@ function _parseLinkRelation(headers, base, relName, opts) {
1780
1792
  var raw = null;
1781
1793
  var hNames = guard.identifier.optionNames(headers);
1782
1794
  for (var hi = 0; hi < hNames.length; hi++) {
1783
- if (hNames[hi].toLowerCase() === "link") { raw = headers[hNames[hi]]; break; }
1795
+ if (_lower(hNames[hi]) === "link") { raw = headers[hNames[hi]]; break; }
1784
1796
  }
1785
1797
  if (raw == null) return [];
1786
1798
  var baseUrl = new _URL(base), baseOrigin = _urlOrigin(baseUrl), baseHref = _urlHref(baseUrl);
1787
1799
  var baseKey = _dedupKey(baseHref);
1788
- var fields = Array.isArray(raw) ? raw : [raw], out = [], seen = Object.create(null), distinctCount = 0, totalBytes = 0;
1800
+ var fields = intrinsic.isArray(raw) ? raw : [raw], out = [], seen = intrinsic.create(null), distinctCount = 0, totalBytes = 0;
1789
1801
  for (var fi = 0; fi < fields.length; fi++) {
1790
- var field = String(fields[fi]);
1802
+ /** @internal The header field is converted through the capture: read through the live global, a
1803
+ * replacement supplied the whole Link header and with it the target this client resolves. */
1804
+ var field = _StringI(fields[fi]);
1791
1805
  totalBytes += field.length + 1;
1792
1806
  if (totalBytes > LINK_HEADER_MAX_BYTES) throw E("acme/bad-link", "the Link header(s) exceed the " + LINK_HEADER_MAX_BYTES + "-byte aggregate cap (RFC 8288, CWE-770)");
1793
1807
  if (_hasCtlOctet(field)) throw E("acme/bad-link", "a Link header must not contain control octets (RFC 9110 field-value)");
@@ -2202,7 +2216,8 @@ function client(directoryUrl, opts) {
2202
2216
  function _pollAuthorization(url, budget) { return _poll("authorization", url, budget); }
2203
2217
 
2204
2218
  function _readCertChain(res) {
2205
- var ctToken = String(res.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
2219
+ var ctParts = intrinsic.splitChar(_StringI(res.headers["content-type"] || ""), 59);
2220
+ var ctToken = _lower(_acmeTrim(ctParts.length ? ctParts[0] : ""));
2206
2221
  if (ctToken !== "application/pem-certificate-chain") throw E("acme/bad-certificate-chain", "the certificate download returned an unexpected media type " + JSON.stringify(ctToken) + " (expected application/pem-certificate-chain, RFC 8555 sec. 7.4.2)");
2207
2222
  var chain = _splitPemChain(_bodyText(res));
2208
2223
  return { certificate: chain[0], chain: chain.slice(1), certificates: chain };
package/lib/asn1-der.js CHANGED
@@ -566,8 +566,16 @@ function decodeOidContent(buf) {
566
566
  "OBJECT IDENTIFIER carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
567
567
  }
568
568
  var value = 0n;
569
- for (var k = arcStart; k <= i; k++) value = value * 128n + BigInt(buf[k] & 0x7f);
570
- arcs.push(value);
569
+ /** @internal The arc is accumulated through the captured conversion. Read off the global, a
570
+ * replacement decides the sub-identifier, and therefore the dotted string every algorithm and
571
+ * extension decision in this toolkit is made on: measured, the same two bytes rendered as
572
+ * `1.2.3` and as `1.2.4`. */
573
+ for (var k = arcStart; k <= i; k++) value = value * 128n + intrinsic.BigInt(buf[k] & 0x7f);
574
+ /** @internal `append`, not a captured `push`: appending with `push` STORES at the index, and a
575
+ * store walks the prototype chain for a setter, so an accessor installed at
576
+ * `Array.prototype[0]` took the arc and answered with another. `append` defines the index on
577
+ * the array itself. */
578
+ intrinsic.append(arcs, value);
571
579
  arcStart = i + 1;
572
580
  }
573
581
  }
@@ -577,9 +585,12 @@ function decodeOidContent(buf) {
577
585
  if (first < 40n) { a1 = 0n; a2 = first; }
578
586
  else if (first < 80n) { a1 = 1n; a2 = first - 40n; }
579
587
  else { a1 = 2n; a2 = first - 80n; }
580
- var out = [a1.toString(), a2.toString()];
581
- for (var j = 1; j < arcs.length; j++) out.push(arcs[j].toString());
582
- return out.join(".");
588
+ /** @internal Each arc is rendered through the captured conversion. Read off the prototype, the
589
+ * value that becomes the dotted identifier is whatever a replacement returns, and the identifier
590
+ * is what every algorithm and extension decision in this toolkit is made on. */
591
+ var out = [intrinsic.bigIntToString(a1, 10), intrinsic.bigIntToString(a2, 10)];
592
+ for (var j = 1; j < arcs.length; j++) intrinsic.append(out, intrinsic.bigIntToString(arcs[j], 10));
593
+ return intrinsic.join(out, ".");
583
594
  }
584
595
 
585
596
  function _decodeText(buf, encoding) {
package/lib/cmc-build.js CHANGED
@@ -173,7 +173,10 @@ function fixedRequestId(req, index) {
173
173
  return "unknown field " + JSON.stringify(k) + " on request " + index +
174
174
  " -- a request names one of tcr / crm / orm, and optionally bodyPartID";
175
175
  });
176
- var arms = ["tcr", "crm", "orm"].filter(function (k) { return req[k] != null; });
176
+ /** @internal `selectList`, not the live `filter`: the selection decides which request arm this
177
+ * message carries, and `Array.prototype.filter` is both read off the prototype and specified to
178
+ * build its result through the species constructor. */
179
+ var arms = intrinsic.selectList(["tcr", "crm", "orm"], function (k) { return req[k] != null; });
177
180
  if (arms.length !== 1) {
178
181
  throw E("cmc/bad-input",
179
182
  "each request names exactly one of tcr / crm / orm, got " + (arms.length ? arms.join(" + ") : "none") +
package/lib/cmp-build.js CHANGED
@@ -29,6 +29,7 @@ var cmp = require("./schema-cmp");
29
29
  var crmf = require("./crmf-sign");
30
30
  var schemaCrmf = require("./schema-crmf");
31
31
  var intrinsic = require("./guard-intrinsic");
32
+ var _strTrim = intrinsic.uncurry(String.prototype.trim);
32
33
  /** @internal The promise a signature self-check is awaited through, built from the constructor captured
33
34
  * at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
34
35
  * the construction to whatever that binding holds, and one settling without awaiting its argument runs
@@ -1012,7 +1013,15 @@ function _tlsForTransfer(opts) {
1012
1013
  return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
1013
1014
  }
1014
1015
 
1015
- function _ctypeToken(headers) { return String((headers || {})["content-type"] || "").split(";")[0].trim().toLowerCase(); }
1016
+ /** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
1017
+ * rather than through `split`, which hard rule 11 bans in lib/ because the separator's own
1018
+ * `Symbol.split` is part of that operation. The token decides which parser a response body is
1019
+ * handed to. */
1020
+ function _ctypeToken(headers) {
1021
+ var raw = intrinsic.String((headers || {})["content-type"] || "");
1022
+ var parts = intrinsic.splitChar(raw, 59);
1023
+ return intrinsic.toLowerCase(_strTrim(parts.length ? parts[0] : ""));
1024
+ }
1016
1025
  function _isPkixcmp(headers) { var t = _ctypeToken(headers); return t === PKIXCMP || t === PKIXCMP_POLL; }
1017
1026
 
1018
1027
  /** @internal The sentinel is added at the one exit rather than at each `return`, so a branch added
@@ -631,9 +631,13 @@ var HASH_W3C = intrinsic.create(null); HASH_W3C[O("sha1")] = "SHA-1"; HASH_W3C[O
631
631
  function _hashW3c(o) { if (!HASH_W3C[o]) throw _err("cms/unsupported-algorithm", "unsupported OAEP hash " + o); return HASH_W3C[o]; }
632
632
 
633
633
  var X963_HASH = intrinsic.create(null);
634
- [["dhSinglePass-stdDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-stdDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-stdDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-stdDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-stdDH-sha512kdf-scheme", "SHA-512"],
634
+ var _X963_ROWS = [["dhSinglePass-stdDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-stdDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-stdDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-stdDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-stdDH-sha512kdf-scheme", "SHA-512"],
635
635
  ["dhSinglePass-cofactorDH-sha1kdf-scheme", "SHA-1"], ["dhSinglePass-cofactorDH-sha224kdf-scheme", "SHA-224"], ["dhSinglePass-cofactorDH-sha256kdf-scheme", "SHA-256"], ["dhSinglePass-cofactorDH-sha384kdf-scheme", "SHA-384"], ["dhSinglePass-cofactorDH-sha512kdf-scheme", "SHA-512"]
636
- ].forEach(function (r) { X963_HASH[O(r[0])] = r[1]; });
636
+ ];
637
+ /** @internal The registry is built through the captured walk. The method is read off the live
638
+ * prototype at the moment it runs even where the receiver is a literal, and these rows decide which
639
+ * hash a key-agreement recipient's KDF uses. */
640
+ intrinsic.forEach(_X963_ROWS, function (r) { X963_HASH[O(r[0])] = r[1]; });
637
641
  function _x963Hash(scheme) { if (!X963_HASH[scheme]) throw _err("cms/unsupported-algorithm", "unsupported kari key-agreement scheme " + scheme); return X963_HASH[scheme]; }
638
642
  function _kariWrap(kea) {
639
643
  var params = asn1.decode(kea.parameters);
package/lib/cms-verify.js CHANGED
@@ -126,9 +126,9 @@ var SIG_SCHEME = intrinsic.assign(intrinsic.create(null), {
126
126
  ecdsaWithSHA384: { kind: "ec", hash: "SHA-384", params: "absent" },
127
127
  ecdsaWithSHA512: { kind: "ec", hash: "SHA-512", params: "absent" },
128
128
  });
129
- ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
130
- "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
131
- ].forEach(function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + s.toUpperCase(), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
129
+ intrinsic.forEach(["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
130
+ "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
131
+ ], function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + intrinsic.toUpperCase(s), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
132
132
  function _isDerNull(p) { return guard.der.isNull(p); }
133
133
  function _algParamsOk(shape, p) { return shape === "null" ? _isDerNull(p) : (p === null || p === undefined); }
134
134
  var EC_CURVE = intrinsic.create(null);
package/lib/crl-sign.js CHANGED
@@ -108,8 +108,8 @@ var KNOWN_REVOKED_KEYS = intrinsic.assign(intrinsic.create(null), {
108
108
  });
109
109
 
110
110
  var REQUIRED_CRITICALITY = intrinsic.create(null);
111
- [["cRLNumber", false], ["authorityKeyIdentifier", false], ["freshestCRL", false], ["authorityInfoAccess", false],
112
- ["issuingDistributionPoint", true], ["deltaCRLIndicator", true]].forEach(function (r) { REQUIRED_CRITICALITY[O(r[0])] = r[1]; });
111
+ intrinsic.forEach([["cRLNumber", false], ["authorityKeyIdentifier", false], ["freshestCRL", false], ["authorityInfoAccess", false],
112
+ ["issuingDistributionPoint", true], ["deltaCRLIndicator", true]], function (r) { REQUIRED_CRITICALITY[O(r[0])] = r[1]; });
113
113
 
114
114
  var REQUIRED_ENTRY_CRITICALITY = intrinsic.create(null);
115
115
  (function () {
@@ -199,7 +199,7 @@ var _PRE_ENCODED_IDP_SCHEMA = pkix.issuingDistributionPoint("crl/bad-idp");
199
199
  function _validatePreEncodedIdp(inner) {
200
200
  if (!inner.children || !inner.children.length) throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint MUST NOT be empty (RFC 5280 sec. 5.2.5)");
201
201
  var f = schema.walk(_PRE_ENCODED_IDP_SCHEMA, inner, NS).fields;
202
- ["onlyContainsUserCerts", "onlyContainsCACerts", "onlyContainsAttributeCerts"].forEach(function (name) {
202
+ intrinsic.forEach(["onlyContainsUserCerts", "onlyContainsCACerts", "onlyContainsAttributeCerts"], function (name) {
203
203
  if (f[name].present && f[name].value !== true) throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint " + name + " encodes its DEFAULT FALSE, which DER omits (X.690 sec. 11.5)");
204
204
  });
205
205
  if (f.indirectCRL.present) throw _err("crl/bad-idp", _INDIRECT_IDP_DEFERRED);
package/lib/ct.js CHANGED
@@ -63,6 +63,7 @@ var _bufToString = intrinsic.bufToString;
63
63
  var _bufferCompare = intrinsic.compare;
64
64
  var _subarray = intrinsic.subarray;
65
65
  var _toLowerCase = intrinsic.toLowerCase;
66
+ var _ctTrim = intrinsic.uncurry(String.prototype.trim);
66
67
  var _bufCopy = intrinsic.uncurry(Buffer.prototype.copy);
67
68
  var _writeU64BE = intrinsic.uncurry(Buffer.prototype.writeBigUInt64BE);
68
69
  var _searchSet = intrinsic.uncurry(URLSearchParams.prototype.set);
@@ -1230,7 +1231,15 @@ function _assertTransport(opts) {
1230
1231
  "(request) => Promise<{ status, headers, body }>");
1231
1232
  }
1232
1233
 
1233
- function _fetchCtypeToken(headers) { return String((headers || {})["content-type"] || "").split(";")[0].trim().toLowerCase(); }
1234
+ /** @internal The media type is read through the captured operations, and on the delimiter CHARACTER
1235
+ * rather than through `split`, which hard rule 11 bans in lib/ because the separator's own
1236
+ * `Symbol.split` is part of that operation. The token decides which parser a log's response body is
1237
+ * handed to. */
1238
+ function _fetchCtypeToken(headers) {
1239
+ var raw = intrinsic.String((headers || {})["content-type"] || "");
1240
+ var parts = intrinsic.splitChar(raw, 59);
1241
+ return _toLowerCase(_ctTrim(parts.length ? parts[0] : ""));
1242
+ }
1234
1243
 
1235
1244
  function _parseHttpsUrl(u, label) {
1236
1245
  var parsed;
package/lib/est.js CHANGED
@@ -76,6 +76,9 @@ var intrinsic = require("./guard-intrinsic");
76
76
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
77
77
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
78
78
  var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
79
+ var _estTrim = intrinsic.uncurry(String.prototype.trim);
80
+ var _estCharAt = intrinsic.uncurry(String.prototype.charAt);
81
+ var _estIndexOf = intrinsic.stringIndexOf;
79
82
  /** @internal Each verb gates its body on a resolved promise and returns its refusals as rejected ones,
80
83
  * and both build from the RECEIVER, so reading the global binding at the call decides whether the body
81
84
  * runs and whether a refusal stays a refusal. Taken at load. */
@@ -250,7 +253,7 @@ function _isMultipartMixedPrefix(ct) {
250
253
  }
251
254
 
252
255
  function _multipartBoundary(contentType) {
253
- var ct = String(contentType || "");
256
+ var ct = _stringOf(contentType || "");
254
257
  if (!_isMultipartMixedPrefix(ct)) return null;
255
258
  var bp = _ctParam(ct, "boundary");
256
259
  if (bp.duplicated) {
@@ -356,12 +359,16 @@ function splitMultipartMixed(body, contentType) {
356
359
  var lines = _splitCrlf(_unfoldHeaders(rawHeaders));
357
360
  for (var li = 0; li < lines.length; li++) {
358
361
  var line = lines[li];
359
- var col = line.indexOf(":");
360
- if (col > 0) headers[line.slice(0, col).trim().toLowerCase()] = line.slice(col + 1).trim();
362
+ /** @internal A part's header name and value are cut, trimmed and folded through the captured
363
+ * operations. One of those headers is the part's own content type, which selects the reader
364
+ * its body goes to: measured, a part stating `application/pkcs8` was read as
365
+ * `application/pkcs7-mime` with a `smime-type` of `certs-only`. */
366
+ var col = _estIndexOf(line, ":");
367
+ if (col > 0) headers[_toLowerCase(_estTrim(_strSlice(line, 0, col)))] = _estTrim(_strSlice(line, col + 1));
361
368
  }
362
369
  var partCt = headers["content-type"] || "";
363
370
  if (_ciStartsWith(partCt, "MULTIPART/")) throw E("est/bad-multipart", "a nested multipart part is not permitted");
364
- parts.push({ headers: headers, contentType: partCt, body: partBody });
371
+ intrinsic.append(parts, { headers: headers, contentType: partCt, body: partBody });
365
372
  }
366
373
  return parts;
367
374
  }
@@ -457,29 +464,37 @@ function _recipientMatches(recipientInfos, opts) {
457
464
  return false;
458
465
  }
459
466
 
467
+ /** @internal The segments are collected through the captured append. Read off the prototype, a
468
+ * replacement decides which segments a parameter is then looked for in, and one of those parameters
469
+ * is the multipart boundary a response body is divided on: measured, a `boundary=wire` header
470
+ * yielded `forged`. */
460
471
  function _splitContentTypeParams(s) {
461
472
  var segs = [], cur = "", inQuote = false;
462
473
  for (var i = 0; i < s.length; i++) {
463
- var ch = s[i];
474
+ var ch = _estCharAt(s, i);
464
475
  if (ch === '"') { inQuote = !inQuote; cur += ch; }
465
- else if (ch === ";" && !inQuote) { segs.push(cur); cur = ""; }
476
+ else if (ch === ";" && !inQuote) { intrinsic.append(segs, cur); cur = ""; }
466
477
  else cur += ch;
467
478
  }
468
- segs.push(cur);
479
+ intrinsic.append(segs, cur);
469
480
  return segs;
470
481
  }
471
482
 
472
483
  function _ctParam(contentType, name) {
473
- var segs = _splitContentTypeParams(String(contentType || ""));
484
+ var segs = _splitContentTypeParams(_stringOf(contentType || ""));
474
485
  var value = null, count = 0;
486
+ /** @internal Each parameter is read through the captured operations. The name decides which
487
+ * parameter this is, and the value it returns is the one a caller acts on: for the RFC 7030
488
+ * multipart boundary, it is where a response body is split. */
475
489
  for (var i = 1; i < segs.length; i++) {
476
- var eq = segs[i].indexOf("=");
490
+ var seg = segs[i];
491
+ var eq = _estIndexOf(seg, "=");
477
492
  if (eq === -1) continue;
478
- if (segs[i].slice(0, eq).trim().toLowerCase() !== name) continue;
493
+ if (_toLowerCase(_estTrim(_strSlice(seg, 0, eq))) !== name) continue;
479
494
  count++;
480
495
  if (count > 1) continue;
481
- var val = segs[i].slice(eq + 1).trim();
482
- if (val.length >= 2 && val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') val = val.slice(1, -1);
496
+ var val = _estTrim(_strSlice(seg, eq + 1));
497
+ if (val.length >= 2 && _estCharAt(val, 0) === '"' && _estCharAt(val, val.length - 1) === '"') val = _strSlice(val, 1, -1);
483
498
  value = val;
484
499
  }
485
500
  return { value: value, duplicated: count > 1 };
@@ -501,12 +516,15 @@ function _mediaType(seg) {
501
516
  return _toLowerCase(_strSlice(seg, a, b));
502
517
  }
503
518
 
519
+ /** @internal The media type and the S/MIME type are produced through the captured conversions. The
520
+ * media type selects which reader a part's body goes to, and read through the live global `String`
521
+ * a replacement chose it: measured, `text/plain` arrived as `application/pkcs8`. */
504
522
  function _partMediaType(contentType) {
505
- var segs = _splitContentTypeParams(String(contentType || ""));
523
+ var segs = _splitContentTypeParams(_stringOf(contentType || ""));
506
524
  var st = _ctParam(contentType, "smime-type");
507
525
  return {
508
526
  media: _mediaType(segs[0]),
509
- smimeType: st.value === null ? null : st.value.toLowerCase(),
527
+ smimeType: st.value === null ? null : _toLowerCase(st.value),
510
528
  ambiguous: st.duplicated,
511
529
  };
512
530
  }
@@ -1279,7 +1297,7 @@ function _requestBinding(der) {
1279
1297
  var body = cmcFmt.parse(der);
1280
1298
  var ids = [];
1281
1299
  var paths = [];
1282
- [body.requests, body.controls, body.cmsSequence, body.otherMsgs].forEach(function (list) {
1300
+ intrinsic.forEach([body.requests, body.controls, body.cmsSequence, body.otherMsgs], function (list) {
1283
1301
  (list || []).forEach(function (el) {
1284
1302
  if (el && el.bodyPartID != null) { ids.push(el.bodyPartID); paths.push([el.bodyPartID]); }
1285
1303
  });
@@ -1325,7 +1343,7 @@ function _collectNestedPaths(list, prefix, out, depth) {
1325
1343
  catch (_e) { /* allow:swallow-unverified the outer request must itself parse for this to run, which already rejects a malformed nested ContentInfo; a payload that survives that and still fails to read back lands on the same no-path refusal the non-pkiData route below takes, which the vectors drive */ return; }
1326
1344
  if (!inner || inner.kind !== "pkiData") return;
1327
1345
  var path = prefix.concat([el.bodyPartID]);
1328
- [inner.requests, inner.controls, inner.cmsSequence, inner.otherMsgs].forEach(function (l) {
1346
+ intrinsic.forEach([inner.requests, inner.controls, inner.cmsSequence, inner.otherMsgs], function (l) {
1329
1347
  (l || []).forEach(function (e2) { if (e2 && e2.bodyPartID != null) out.push(path.concat([e2.bodyPartID])); });
1330
1348
  });
1331
1349
  _collectNestedPaths(inner.cmsSequence, path, out, depth + 1);
@@ -1405,7 +1423,7 @@ function _fullcmcResult(res, opts, wanted, sent) {
1405
1423
  }
1406
1424
  var smimeType = pt200.smimeType;
1407
1425
  if (smimeType === "certs-only") {
1408
- var unecho = ["transactionId", "senderNonce", "dataReturn"].filter(function (k) {
1426
+ var unecho = intrinsic.selectList(["transactionId", "senderNonce", "dataReturn"], function (k) {
1409
1427
  return sent[k] != null;
1410
1428
  });
1411
1429
  if (unecho.length) {
@@ -1731,21 +1749,23 @@ function _assertConfidentialCipher(res) {
1731
1749
 
1732
1750
  function _ciHeaderList(headers, name) {
1733
1751
  headers = headers || {};
1734
- var lname = name.toLowerCase(), keys = guard.identifier.optionNames(headers), parts = [];
1752
+ /** @internal The header NAME a value is selected under is folded through the captured conversion on
1753
+ * both sides of the comparison: the match decides which header this response is read from. */
1754
+ var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), parts = [];
1735
1755
  for (var i = 0; i < keys.length; i++) {
1736
- if (keys[i].toLowerCase() !== lname) continue;
1756
+ if (_toLowerCase(keys[i]) !== lname) continue;
1737
1757
  var v = headers[keys[i]];
1738
1758
  if (v == null) continue;
1739
- parts.push(Array.isArray(v) ? v.join(", ") : String(v));
1759
+ intrinsic.append(parts, intrinsic.isArray(v) ? intrinsic.join(v, ", ") : _stringOf(v));
1740
1760
  }
1741
- return parts.length ? parts.join(", ") : null;
1761
+ return parts.length ? intrinsic.join(parts, ", ") : null;
1742
1762
  }
1743
1763
 
1744
1764
  function _ciHeader(headers, name) {
1745
1765
  headers = headers || {};
1746
- var lname = name.toLowerCase(), keys = guard.identifier.optionNames(headers), found = null, n = 0;
1766
+ var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), found = null, n = 0;
1747
1767
  for (var i = 0; i < keys.length; i++) {
1748
- if (keys[i].toLowerCase() !== lname) continue;
1768
+ if (_toLowerCase(keys[i]) !== lname) continue;
1749
1769
  n += 1;
1750
1770
  if (n === 1) found = headers[keys[i]];
1751
1771
  }
@@ -257,7 +257,7 @@ var _CONCRETE_TYPED_MEMBERS = ["BYTES_PER_ELEMENT"];
257
257
  var _BUFFER_PROTO_MEMBERS = ["parent", "offset"];
258
258
 
259
259
  var _ARRAY_MEMBERS = _ARRAY_PROTO_MEMBERS;
260
- var _REGEXP_MEMBERS = ["lastIndex"].concat(_REGEXP_PROTO_MEMBERS);
260
+ var _REGEXP_MEMBERS = intrinsic.concatList(["lastIndex"], _REGEXP_PROTO_MEMBERS);
261
261
  var _VIEW_MEMBERS = _TYPED_PROTO_MEMBERS.concat(_CONCRETE_TYPED_MEMBERS);
262
262
  var _BUFFER_KIND_MEMBERS = _VIEW_MEMBERS.concat(_BUFFER_PROTO_MEMBERS);
263
263
  var _DATAVIEW_MEMBERS = _DATAVIEW_PROTO_MEMBERS;
@@ -16,13 +16,19 @@ function _monotonicNow() { return _Number(_hrtimeBigint() / 1000000n); }
16
16
  function cap(value, key, dflt, opts) {
17
17
  if (value === undefined) return dflt;
18
18
  opts = opts || {};
19
- var min = opts.min === undefined ? 0 : opts.min;
20
- if (!_isInteger(min) || (opts.max !== undefined && !_isInteger(opts.max))) {
19
+ /** @internal Each bound is read ONCE, into the value the integer check and the comparison share.
20
+ * Read twice, the check saw one bound and the comparison another, so an `opts` answering a valid
21
+ * small integer to the check and a large one to the comparison configured a cap that admitted a
22
+ * value above the bound it had just been validated against. */
23
+ var rawMin = opts.min, rawMax = opts.max;
24
+ var min = rawMin === undefined ? 0 : rawMin;
25
+ var hasMax = rawMax !== undefined;
26
+ if (!_isInteger(min) || (hasMax && !_isInteger(rawMax))) {
21
27
  throw new TypeError("guard.limits.cap: the min/max bounds for " + key + " must be integers");
22
28
  }
23
- if (!_isInteger(value) || value < min || (opts.max !== undefined && value > opts.max)) {
24
- var want = "an integer >= " + min + (opts.max !== undefined ? " and <= " + opts.max : "");
25
- if (min === 0 && opts.max === undefined) want = "a non-negative integer";
29
+ if (!_isInteger(value) || value < min || (hasMax && value > rawMax)) {
30
+ var want = "an integer >= " + min + (hasMax ? " and <= " + rawMax : "");
31
+ if (min === 0 && !hasMax) want = "a non-negative integer";
26
32
  if (opts.E) throw opts.E(opts.code, (opts.label || key) + " must be " + want);
27
33
  throw new TypeError("decode: " + key + " must be " + want);
28
34
  }
@@ -136,7 +142,8 @@ function budget(limits) {
136
142
  // @enforced-by behavioral -- an elapsed-time comparison has no rename-proof shape that separates a deadline from any other subtraction; the vectors driving a checker to its bound are the guard
137
143
  function deadline(totalMs, opts) {
138
144
  opts = opts || {};
139
- var now = typeof opts.now === "function" ? opts.now : _monotonicNow;
145
+ var rawNow = opts.now;
146
+ var now = typeof rawNow === "function" ? rawNow : _monotonicNow;
140
147
  if (!_isInteger(totalMs) || totalMs < 0) {
141
148
  throw new TypeError("guard.limits.deadline: the total must be a non-negative integer of milliseconds");
142
149
  }