@blamejs/pki 0.8.54 → 0.8.56

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 (50) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/lib/acme.js +2 -2
  3. package/lib/asn1-der.js +17 -5
  4. package/lib/attrcert-verify.js +1 -1
  5. package/lib/cmc-verify.js +1 -1
  6. package/lib/cmp-session.js +2 -2
  7. package/lib/cmp-verify.js +2 -2
  8. package/lib/cms-decrypt.js +4 -1
  9. package/lib/cms-encrypt.js +4 -1
  10. package/lib/cms-sign.js +26 -12
  11. package/lib/cms-verify.js +1 -1
  12. package/lib/composite-kem.js +30 -12
  13. package/lib/composite-sig.js +42 -23
  14. package/lib/constants.js +7 -2
  15. package/lib/crl-verify.js +1 -1
  16. package/lib/crmf-sign.js +72 -43
  17. package/lib/crmf-verify.js +1 -1
  18. package/lib/csr-verify.js +1 -1
  19. package/lib/ct.js +11 -13
  20. package/lib/edwards-point.js +1 -1
  21. package/lib/est.js +3 -3
  22. package/lib/guard-async.js +22 -1
  23. package/lib/guard-bytes.js +6 -3
  24. package/lib/guard-identifier.js +24 -1
  25. package/lib/guard-intrinsic.js +474 -0
  26. package/lib/http-digest.js +146 -62
  27. package/lib/http-transport.js +82 -17
  28. package/lib/identity-match.js +2 -2
  29. package/lib/merkle.js +2 -2
  30. package/lib/ocsp-request-verify.js +1 -1
  31. package/lib/ocsp.js +46 -22
  32. package/lib/path-validate.js +234 -88
  33. package/lib/pbes2.js +14 -7
  34. package/lib/schema-attrcert.js +2 -2
  35. package/lib/schema-cmc.js +2 -2
  36. package/lib/schema-cmp.js +2 -2
  37. package/lib/schema-cms.js +2 -2
  38. package/lib/schema-crmf.js +2 -2
  39. package/lib/schema-csr.js +2 -2
  40. package/lib/schema-pkix.js +3 -3
  41. package/lib/schema-x509.js +2 -2
  42. package/lib/sigstore.js +14 -3
  43. package/lib/tlog.js +7 -4
  44. package/lib/trust.js +187 -104
  45. package/lib/tsp-sign.js +1 -1
  46. package/lib/webauthn-mds.js +257 -121
  47. package/lib/webauthn.js +327 -164
  48. package/lib/webcrypto.js +10 -3
  49. package/package.json +1 -1
  50. package/sbom.cdx.json +6 -6
@@ -6,6 +6,7 @@
6
6
  var crypto = require("crypto");
7
7
  var constants = require("./constants");
8
8
  var intrinsic = require("./guard-intrinsic");
9
+ var guardIdentifier = require("./guard-identifier");
9
10
  var _hasOwn = intrinsic.hasOwn;
10
11
  /** @internal The URL parser, taken at load: the origin it reports is what a challenge is bound to.
11
12
  * Its accessors are taken with it, since the constructor's capture does not cover the prototype the
@@ -16,6 +17,22 @@ var _urlPathname = intrinsic.urlPathname;
16
17
  var _urlSearch = intrinsic.urlSearch;
17
18
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
18
19
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
20
+ /** @internal The operations the challenge is parsed and classified through. This module decides which
21
+ * algorithm and which qop a credential is built for, and whether a weak algorithm is refused at all,
22
+ * so every one of them is a decision: measured, a replaced global `String` rewriting an `MD5`
23
+ * challenge as `SHA-256` turned `transport/proxy-digest-weak-algorithm` into an accepted credential. */
24
+ var _strTrim = intrinsic.uncurry(String.prototype.trim);
25
+ var _strSplit = intrinsic.uncurry(String.prototype.split);
26
+ /** @internal The LEXER's character reads, captured because the lexer copies the challenge one
27
+ * character at a time and a replacement therefore chooses the token the parse assembles. Measured,
28
+ * rewriting `algorithm=MD5` to `algorithm=SHA-256` as the split copies it does NOT reach an accepted
29
+ * credential: the policy still refuses, as `proxy-digest-unsupported-algorithm` rather than
30
+ * `proxy-digest-weak-algorithm`. What is steerable is the REASON, so the capture is what keeps a
31
+ * refusal naming the fault it actually found. A parser is captured at its characters or not at all. */
32
+ var _charAt = intrinsic.uncurry(String.prototype.charAt);
33
+ var _strNormalize = intrinsic.uncurry(String.prototype.normalize);
34
+ var _arrIndexOf = intrinsic.uncurry(Array.prototype.indexOf);
35
+ var _arrJoin = intrinsic.uncurry(Array.prototype.join);
19
36
  var _fromCharCode = intrinsic.fromCharCode;
20
37
  var pkix = require("./schema-pkix");
21
38
 
@@ -29,10 +46,17 @@ var DEFAULT_CODES = intrinsic.assign(intrinsic.create(null), {
29
46
  noQop: "digest/no-qop", badChallenge: "digest/bad-challenge",
30
47
  });
31
48
 
32
- function H(hash, s) { return crypto.createHash(hash).update(s, "latin1").digest("hex"); }
49
+ /** @internal The hash the credential is computed with, taken at load along with the methods of the
50
+ * object it returns. Every Digest response value is this function's output, so a replacement chooses
51
+ * what is sent in place of the password-derived proof. */
52
+ var _createHash = crypto.createHash;
53
+ var _randomBytes = crypto.randomBytes;
54
+ var _hashUpdate = intrinsic.uncurry(crypto.createHash("sha256").update);
55
+ var _hashDigest = intrinsic.uncurry(crypto.createHash("sha256").digest);
56
+ function H(hash, s) { return _hashDigest(_hashUpdate(_createHash(hash), s, "latin1"), "hex"); }
33
57
  function KD(hash, secret, data) { return H(hash, secret + ":" + data); }
34
58
  function _qstr(v) {
35
- var s = String(v), out = "";
59
+ var s = intrinsic.String(v), out = "";
36
60
  for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c === 34 || c === 92) out += "\\"; out += _fromCharCode(c); }
37
61
  return "\"" + out + "\"";
38
62
  }
@@ -40,20 +64,20 @@ function _qstr(v) {
40
64
  function _commaSplitOutsideQuotes(s) {
41
65
  var out = [], buf = "", inQ = false, esc = false;
42
66
  for (var i = 0; i < s.length; i++) {
43
- var c = s.charAt(i);
67
+ var c = _charAt(s, i);
44
68
  if (esc) { buf += c; esc = false; continue; }
45
69
  if (inQ && c === "\\") { buf += c; esc = true; continue; }
46
70
  if (c === "\"") { inQ = !inQ; buf += c; continue; }
47
- if (c === "," && !inQ) { out.push(buf); buf = ""; continue; }
71
+ if (c === "," && !inQ) { intrinsic.append(out, buf); buf = ""; continue; }
48
72
  buf += c;
49
73
  }
50
- out.push(buf);
74
+ intrinsic.append(out, buf);
51
75
  return out;
52
76
  }
53
77
  function _firstEqOutsideQuotes(s) {
54
78
  var inQ = false, esc = false;
55
79
  for (var i = 0; i < s.length; i++) {
56
- var c = s.charAt(i);
80
+ var c = _charAt(s, i);
57
81
  if (esc) { esc = false; continue; }
58
82
  if (inQ && c === "\\") { esc = true; continue; }
59
83
  if (c === "\"") { inQ = !inQ; continue; }
@@ -67,10 +91,10 @@ function _unq(s) {
67
91
  return out + _strSlice(s, last, n - 1);
68
92
  }
69
93
  function _closedQuote(s) {
70
- if (s.length < 2 || s.charAt(0) !== "\"") return false;
94
+ if (s.length < 2 || _charAt(s, 0) !== "\"") return false;
71
95
  var esc = false;
72
96
  for (var i = 1; i < s.length; i++) {
73
- var c = s.charAt(i);
97
+ var c = _charAt(s, i);
74
98
  if (esc) { esc = false; continue; }
75
99
  if (c === "\\") { esc = true; continue; }
76
100
  if (c === "\"") return i === s.length - 1;
@@ -106,12 +130,12 @@ function _splitWhitespace(s) {
106
130
  var out = [], i = 0, n = s.length, ps = 0;
107
131
  while (i < n) {
108
132
  if (pkix.isJsWhitespace(_charCodeAt(s, i))) {
109
- out[out.length] = _strSlice(s, ps, i);
133
+ intrinsic.append(out, _strSlice(s, ps, i));
110
134
  while (i < n && pkix.isJsWhitespace(_charCodeAt(s, i))) i += 1;
111
135
  ps = i;
112
136
  } else i += 1;
113
137
  }
114
- out[out.length] = _strSlice(s, ps);
138
+ intrinsic.append(out, _strSlice(s, ps));
115
139
  return out;
116
140
  }
117
141
 
@@ -119,29 +143,37 @@ function _splitChallenges(s) {
119
143
  var segs = _commaSplitOutsideQuotes(s);
120
144
  var out = [], cur = null;
121
145
  for (var i = 0; i < segs.length; i++) {
122
- var seg = segs[i].trim();
146
+ /** @internal The OUTER trim is captured as well as the inner ones. It controls the complete
147
+ * auth-param before anything slices it, so a replacement mapping the segment `algorithm=MD5` to
148
+ * `algorithm=SHA-256` classifies a weak challenge as a strong one and the refusal never fires:
149
+ * capturing only the key and value slices left the whole parameter steerable. */
150
+ var seg = _strTrim(segs[i]);
123
151
  if (seg === "") continue;
124
152
  var eq = _firstEqOutsideQuotes(seg);
125
- var pre = (eq < 0 ? seg : _strSlice(seg, 0, eq)).trim();
153
+ var pre = _strTrim(eq < 0 ? seg : _strSlice(seg, 0, eq));
126
154
  var scheme = _matchTokenRest(pre);
127
- if (eq < 0) { cur = { scheme: seg, paramText: "" }; out.push(cur); }
128
- else if (scheme !== null) { cur = { scheme: scheme, paramText: _strSlice(seg, _stripTokenIndex(seg)) }; out.push(cur); }
155
+ if (eq < 0) { cur = { scheme: seg, paramText: "" }; intrinsic.append(out, cur); }
156
+ else if (scheme !== null) { cur = { scheme: scheme, paramText: _strSlice(seg, _stripTokenIndex(seg)) }; intrinsic.append(out, cur); }
129
157
  else if (cur) { cur.paramText = cur.paramText ? cur.paramText + "," + seg : seg; }
130
158
  }
131
159
  return out;
132
160
  }
161
+ /** @internal The map this returns IS the challenge the policy is applied to, so the construction is
162
+ * captured along with the lexer that fills it. A replaced construction hands back a map already
163
+ * carrying an `algorithm` entry, and a challenge that names no algorithm then reads as the strong one
164
+ * instead of the MD5 default that `allowMD5: false` refuses. */
133
165
  function _parseParams(paramText, E, code) {
134
- var segs = _commaSplitOutsideQuotes(paramText), map = Object.create(null);
166
+ var segs = _commaSplitOutsideQuotes(paramText), map = intrinsic.create(null);
135
167
  for (var i = 0; i < segs.length; i++) {
136
- var seg = segs[i].trim();
168
+ var seg = _strTrim(segs[i]);
137
169
  if (seg === "") continue;
138
170
  var eq = _firstEqOutsideQuotes(seg);
139
- if (eq < 0) throw E(code, "malformed Digest auth-param (no '='): " + JSON.stringify(seg));
140
- var key = _strSlice(seg, 0, eq).trim().toLowerCase();
141
- var rawVal = _strSlice(seg, eq + 1).trim();
142
- var quoted = rawVal.charAt(0) === "\"";
171
+ if (eq < 0) throw E(code, "malformed Digest auth-param (no '='): " + intrinsic.stringify(seg));
172
+ var key = intrinsic.toLowerCase(_strTrim(_strSlice(seg, 0, eq)));
173
+ var rawVal = _strTrim(_strSlice(seg, eq + 1));
174
+ var quoted = _charCodeAt(rawVal, 0) === 34;
143
175
  if (quoted && !_closedQuote(rawVal)) throw E(code, "an unterminated or trailing-garbage Digest quoted-string (RFC 7230 sec. 3.2.6)");
144
- if (_hasOwn(map, key)) throw E(code, "repeated Digest auth-param " + JSON.stringify(key));
176
+ if (_hasOwn(map, key)) throw E(code, "repeated Digest auth-param " + intrinsic.stringify(key));
145
177
  var value = quoted ? _unq(rawVal) : rawVal;
146
178
  if (_hasCtl(value)) throw E(code, "a Digest auth-param value contains a control character (RFC 7230 sec. 3.2.6)");
147
179
  map[key] = { value: value, quoted: quoted };
@@ -156,42 +188,50 @@ function _validateDigest(paramText, E, code) {
156
188
  var qop = [];
157
189
  if (p.qop) {
158
190
  if (!p.qop.quoted) throw E(code, "the Digest qop must be a quoted list (RFC 7616 sec. 3.3)");
159
- qop = p.qop.value.split(",").map(function (x) { return x.trim().toLowerCase(); }).filter(Boolean);
191
+ /** @internal The predicate that drops an empty member is written out rather than handed the
192
+ * global conversion. It is what leaves `qop=""` with no members, which is the refusal below, so
193
+ * a replaced conversion answering true for the empty string returned a challenge whose quality
194
+ * of protection is a member the scheme does not define. */
195
+ qop = intrinsic.selectList(intrinsic.mapList(intrinsic.splitChar(p.qop.value, 44), function (x) {
196
+ return intrinsic.toLowerCase(_strTrim(x));
197
+ }), function (x) { return x !== ""; });
160
198
  if (qop.length === 0) throw E(code, "a present Digest qop directive must list at least one value (RFC 7616 sec. 3.3)");
161
199
  }
162
200
  if (p.domain && !p.domain.quoted) throw E(code, "the Digest domain must be a quoted-string (RFC 7616 sec. 3.3)");
163
- var domain = (p.domain && p.domain.value.trim() !== "")
164
- ? _splitWhitespace(p.domain.value.trim()) : null;
165
- if (p.charset && (p.charset.quoted || String(p.charset.value).toUpperCase() !== "UTF-8")) throw E(code, "the Digest charset must be the unquoted token UTF-8 (RFC 7616 sec. 3.3)");
201
+ var domain = (p.domain && _strTrim(p.domain.value) !== "")
202
+ ? _splitWhitespace(_strTrim(p.domain.value)) : null;
203
+ if (p.charset && (p.charset.quoted || intrinsic.toUpperCase(intrinsic.String(p.charset.value)) !== "UTF-8")) throw E(code, "the Digest charset must be the unquoted token UTF-8 (RFC 7616 sec. 3.3)");
166
204
  if (p.stale) {
167
- var sv = String(p.stale.value).toLowerCase();
205
+ var sv = intrinsic.toLowerCase(intrinsic.String(p.stale.value));
168
206
  if (p.stale.quoted || (sv !== "true" && sv !== "false")) throw E(code, "the Digest stale directive must be the unquoted token true or false (RFC 7616 sec. 3.3)");
169
207
  }
170
208
  if (p.userhash) {
171
- var uhv = String(p.userhash.value).toLowerCase();
209
+ var uhv = intrinsic.toLowerCase(intrinsic.String(p.userhash.value));
172
210
  if (p.userhash.quoted || (uhv !== "true" && uhv !== "false")) throw E(code, "the Digest userhash directive must be the unquoted token true or false (RFC 7616 sec. 3.3)");
173
211
  }
174
212
  if (p.opaque && !p.opaque.quoted) throw E(code, "the Digest opaque directive must be a quoted-string (RFC 7616 sec. 3.3)");
175
213
  return {
176
214
  scheme: "Digest", realm: p.realm.value, nonce: p.nonce.value, qop: qop, domain: domain,
177
- algorithm: p.algorithm ? p.algorithm.value.toUpperCase() : "MD5",
215
+ algorithm: p.algorithm ? intrinsic.toUpperCase(p.algorithm.value) : "MD5",
178
216
  opaque: p.opaque ? p.opaque.value : null,
179
- stale: !!(p.stale && String(p.stale.value).toLowerCase() === "true"),
180
- userhash: !!(p.userhash && String(p.userhash.value).toLowerCase() === "true"),
217
+ stale: !!(p.stale && intrinsic.toLowerCase(intrinsic.String(p.stale.value)) === "true"),
218
+ userhash: !!(p.userhash && intrinsic.toLowerCase(intrinsic.String(p.userhash.value)) === "true"),
181
219
  charset: p.charset ? p.charset.value : null,
182
220
  };
183
221
  }
184
222
 
185
223
  function parseChallenge(www, E, code, policy) {
186
- var pol = policy || {};
224
+ /** @internal An own-key copy with no prototype, for the reason the answer path states: every policy
225
+ * field is read off this record, so an inherited read is a way to supply one. */
226
+ var pol = guardIdentifier.ownOptions(policy);
187
227
  var codes = pol.codes || DEFAULT_CODES;
188
228
  var preferStale = !!pol.preferStale;
189
- var raw = String(www == null ? "" : www);
229
+ var raw = intrinsic.String(www == null ? "" : www);
190
230
  if (raw.length > constants.LIMITS.HTTP_AUTH_HEADER_MAX_BYTES) throw E(code, "the WWW-Authenticate header exceeds the " + constants.LIMITS.HTTP_AUTH_HEADER_MAX_BYTES + "-byte cap");
191
231
  var challenges = _splitChallenges(raw);
192
232
  var best = null, bestUsable = false, bestApplicable = false, bestStale = false, bestRank = -1, sawDigest = false;
193
233
  for (var i = 0; i < challenges.length; i++) {
194
- if (challenges[i].scheme.toLowerCase() !== "digest") continue;
234
+ if (intrinsic.toLowerCase(challenges[i].scheme) !== "digest") continue;
195
235
  sawDigest = true;
196
236
  var parsed;
197
237
  try { parsed = _validateDigest(challenges[i].paramText, E, code); }
@@ -216,19 +256,22 @@ function parseChallenge(www, E, code, policy) {
216
256
  return null;
217
257
  }
218
258
 
259
+ /** @internal The bytes a credential is computed over, so the conversion and the encoding both come
260
+ * from the captures: what this returns is hashed, and a replacement changes the input to the proof. */
219
261
  function _octets(s, charset) {
220
- var v = s == null ? "" : String(s);
221
- return (charset && String(charset).toUpperCase() === "UTF-8") ? Buffer.from(v, "utf8").toString("latin1") : v;
262
+ var v = s == null ? "" : intrinsic.String(s);
263
+ return (charset && intrinsic.toUpperCase(intrinsic.String(charset)) === "UTF-8")
264
+ ? intrinsic.bufToString(intrinsic.bufferFrom(v, "utf8"), "latin1") : v;
222
265
  }
223
266
 
224
267
  function _hasNonAscii(s) {
225
- s = String(s == null ? "" : s);
268
+ s = intrinsic.String(s == null ? "" : s);
226
269
  for (var i = 0; i < s.length; i++) { if (_charCodeAt(s, i) > 0x7F) return true; }
227
270
  return false;
228
271
  }
229
272
 
230
273
  function _pctEncodeUtf8(s) {
231
- var bytes = Buffer.from(String(s == null ? "" : s), "utf8");
274
+ var bytes = intrinsic.bufferFrom(intrinsic.String(s == null ? "" : s), "utf8");
232
275
  var out = "";
233
276
  for (var i = 0; i < bytes.length; i++) {
234
277
  var b = bytes[i];
@@ -237,7 +280,7 @@ function _pctEncodeUtf8(s) {
237
280
  b === 0x2E || b === 0x5E || b === 0x5F || b === 0x60 || b === 0x7C || b === 0x7E) {
238
281
  out += _fromCharCode(b);
239
282
  } else {
240
- out += "%" + (b < 0x10 ? "0" : "") + b.toString(16).toUpperCase();
283
+ out += "%" + (b < 0x10 ? "0" : "") + intrinsic.toUpperCase(intrinsic.numberToString(b, 16));
241
284
  }
242
285
  }
243
286
  return out;
@@ -249,25 +292,48 @@ function _rejection(challenge, pol, codes) {
249
292
  if (alg.hash === "md5" && !pol.allowMD5) return { code: codes.weakAlgorithm, msg: "MD5 Digest refused by default (RFC 7616 sec. 3.4 discourages it); set opts.auth.allowMD5 for legacy interop" };
250
293
  if (challenge.qop.length === 0) {
251
294
  if (!pol.allowLegacyQop) return { code: codes.noQop, msg: "a no-qop (RFC 2069) Digest challenge is refused by default; set opts.auth.allowLegacyQop for legacy interop" };
252
- } else if (challenge.qop.indexOf("auth") === -1 && challenge.qop.indexOf("auth-int") === -1) {
295
+ /** @internal The qop membership tests come from the capture. This arm REFUSES a challenge whose
296
+ * qop offers nothing this client supports, and the one below SELECTS which qop the credential is
297
+ * computed for, so a replaced `indexOf` either skips the refusal or changes the computation. */
298
+ } else if (_arrIndexOf(challenge.qop, "auth") === -1 && _arrIndexOf(challenge.qop, "auth-int") === -1) {
253
299
  return { code: codes.badChallenge, msg: "the Digest qop offered no member this client supports (auth / auth-int)" };
254
300
  }
255
301
  return null;
256
302
  }
257
303
 
258
304
  function answer(challenge, params, E) {
259
- var pol = params.policy || {};
305
+ /** @internal The whole bag is taken as an OWN-key copy with no prototype, once, and every field
306
+ * below is read off that copy. Each field is a separate way to supply a value the caller did not:
307
+ * `Object.prototype.policy` answered for a caller who passed no policy and `Object.prototype
308
+ * .allowMD5` for one who passed an empty one, either turning the default MD5 refusal into an
309
+ * issued credential; and `Object.prototype.rng` answered for the client nonce, which makes every
310
+ * credential this function issues carry the same cnonce and so replayable. The policy is copied
311
+ * the same way for the same reason, since it is a record of its own. */
312
+ params = guardIdentifier.ownOptions(params);
313
+ var pol = guardIdentifier.ownOptions(_hasOwn(params, "policy") ? params.policy : null);
260
314
  var codes = pol.codes || DEFAULT_CODES;
261
315
  var rej = _rejection(challenge, pol, codes);
262
316
  if (rej) throw E(rej.code, rej.msg);
263
317
  var alg = ALGS[challenge.algorithm];
264
- var useQop = challenge.qop.indexOf("auth") !== -1 ? "auth" : (challenge.qop.indexOf("auth-int") !== -1 ? "auth-int" : null);
265
- var cnonce = String((params.rng || function () { return crypto.randomBytes(18).toString("base64"); })());
318
+ var useQop = _arrIndexOf(challenge.qop, "auth") !== -1 ? "auth"
319
+ : (_arrIndexOf(challenge.qop, "auth-int") !== -1 ? "auth-int" : null);
320
+ /** @internal The client nonce comes from the captured random source. Read off the module export it
321
+ * is replaceable, and a predictable cnonce is a replayable response: the whole point of the value
322
+ * is that the server cannot have seen it before. A caller's own `rng` still wins, as before. */
323
+ /** @internal The ENCODING of the random bytes is captured too. Capturing the random source alone
324
+ * left the value it produced readable through a replaced `Buffer.prototype.toString`, which can
325
+ * answer with a fixed string: the unpredictability is a property of what reaches the header, not of
326
+ * where the bytes came from. A caller's own `rng` still wins. */
327
+ var cnonce = intrinsic.String((params.rng || function () {
328
+ return intrinsic.bufToString(_randomBytes(18), "base64");
329
+ })());
266
330
  var realm = challenge.realm, nonce = challenge.nonce;
267
- var isUtf8 = String(challenge.charset || "").toUpperCase() === "UTF-8";
268
- var pUser = params.username == null ? "" : String(params.username);
269
- var pPass = params.password == null ? "" : String(params.password);
270
- if (isUtf8) { pUser = pUser.normalize("NFC"); pPass = pPass.normalize("NFC"); }
331
+ var isUtf8 = intrinsic.toUpperCase(intrinsic.String(challenge.charset || "")) === "UTF-8";
332
+ var pUser = params.username == null ? "" : intrinsic.String(params.username);
333
+ var pPass = params.password == null ? "" : intrinsic.String(params.password);
334
+ /** @internal The credentials are normalized through the capture: what this produces is hashed, so a
335
+ * replaced `normalize` changes the input to the proof the server checks. */
336
+ if (isUtf8) { pUser = _strNormalize(pUser, "NFC"); pPass = _strNormalize(pPass, "NFC"); }
271
337
  var user = _octets(pUser, challenge.charset);
272
338
  var pass = _octets(pPass, challenge.charset);
273
339
  var HA1 = H(alg.hash, user + ":" + realm + ":" + pass);
@@ -276,8 +342,8 @@ function answer(challenge, params, E) {
276
342
  ? params.method + ":" + params.uri + ":" + H(alg.hash, params.body == null ? "" : params.body)
277
343
  : params.method + ":" + params.uri;
278
344
  var HA2 = H(alg.hash, A2);
279
- var ncNum = (typeof params.nc === "number" && params.nc >= 1) ? Math.floor(params.nc) : 1;
280
- var nc = _strSlice("0000000" + ncNum.toString(16), -8);
345
+ var ncNum = (typeof params.nc === "number" && params.nc >= 1) ? intrinsic.floor(params.nc) : 1;
346
+ var nc = _strSlice("0000000" + intrinsic.numberToString(ncNum, 16), -8);
281
347
  var response = useQop
282
348
  ? KD(alg.hash, HA1, nonce + ":" + nc + ":" + cnonce + ":" + useQop + ":" + HA2)
283
349
  : KD(alg.hash, HA1, nonce + ":" + HA2);
@@ -288,34 +354,52 @@ function answer(challenge, params, E) {
288
354
  var sentUser = challenge.userhash ? H(alg.hash, _octets(pUser, challenge.charset) + ":" + realm) : _octets(pUser, challenge.charset);
289
355
  parts = ["username=" + _qstr(sentUser)];
290
356
  }
291
- parts.push("realm=" + _qstr(realm), "nonce=" + _qstr(nonce), "uri=" + _qstr(params.uri), "algorithm=" + challenge.algorithm);
292
- if (useQop) { parts.push("qop=" + useQop); parts.push("nc=" + nc); }
293
- if (useQop || alg.sess) { parts.push("cnonce=" + _qstr(cnonce)); }
294
- parts.push("response=" + _qstr(response));
295
- if (challenge.opaque != null) parts.push("opaque=" + _qstr(challenge.opaque));
296
- if (challenge.userhash) parts.push("userhash=true");
297
- return "Digest " + parts.join(", ");
357
+ /** @internal The header is assembled through the captures. These decide which parameters reach the
358
+ * server: a dropped `cnonce` or `nc` is a credential the server reads under different rules than
359
+ * the one it was computed under. */
360
+ /** @internal Appended with the operation that writes an OWN property. `push` writes through Set,
361
+ * which walks the prototype for a numeric setter, so an accessor installed at `Array.prototype[0]`
362
+ * takes the value and the index reads back as whatever its getter answers: a dropped `cnonce` or
363
+ * nonce count, or a substituted `qop`, is a credential the server reads under different rules than
364
+ * the one it was computed under. */
365
+ intrinsic.append(parts, "realm=" + _qstr(realm));
366
+ intrinsic.append(parts, "nonce=" + _qstr(nonce));
367
+ intrinsic.append(parts, "uri=" + _qstr(params.uri));
368
+ intrinsic.append(parts, "algorithm=" + challenge.algorithm);
369
+ if (useQop) { intrinsic.append(parts, "qop=" + useQop); intrinsic.append(parts, "nc=" + nc); }
370
+ if (useQop || alg.sess) { intrinsic.append(parts, "cnonce=" + _qstr(cnonce)); }
371
+ intrinsic.append(parts, "response=" + _qstr(response));
372
+ if (challenge.opaque != null) intrinsic.append(parts, "opaque=" + _qstr(challenge.opaque));
373
+ if (challenge.userhash) intrinsic.append(parts, "userhash=true");
374
+ return "Digest " + _arrJoin(parts, ", ");
298
375
  }
299
376
 
377
+ /** @internal The protection-space entries are parsed through the captures. They decide whether a
378
+ * request is inside the space a credential was issued for, so an entry read differently is a
379
+ * credential sent to an origin the challenge did not cover. */
300
380
  function _domainEntry(d) {
301
- d = String(d == null ? "" : d);
302
- if (d.charAt(0) === "/") return { origin: null, full: d };
303
- try { var u = new _URL(d); return { origin: _urlOrigin(u).toLowerCase(), full: (_urlPathname(u) || "/") + (_urlSearch(u) || "") }; }
381
+ d = intrinsic.String(d == null ? "" : d);
382
+ if (_charAt(d, 0) === "/") return { origin: null, full: d };
383
+ try { var u = new _URL(d); return { origin: intrinsic.toLowerCase(_urlOrigin(u)), full: (_urlPathname(u) || "/") + (_urlSearch(u) || "") }; }
304
384
  catch (_e) { return null; }
305
385
  }
306
386
 
307
387
  function inProtectionSpace(challenge, requestOrigin, requestPathAndSearch) {
308
388
  var domain = challenge && challenge.domain;
309
389
  if (!domain || !domain.length) return true;
310
- var origin = String(requestOrigin == null ? "" : requestOrigin).toLowerCase();
311
- var full = String(requestPathAndSearch == null ? "" : requestPathAndSearch);
390
+ /** @internal This answers whether a credential may be sent to this request's origin, so every
391
+ * operation it compares through is captured: `true` here is what lets the credential go out. */
392
+ var origin = intrinsic.toLowerCase(intrinsic.String(requestOrigin == null ? "" : requestOrigin));
393
+ var full = intrinsic.String(requestPathAndSearch == null ? "" : requestPathAndSearch);
312
394
  for (var i = 0; i < domain.length; i++) {
313
395
  var e = _domainEntry(domain[i]);
314
396
  if (e === null) continue;
315
397
  if (e.origin !== null && e.origin !== origin) continue;
316
- if (full.indexOf(e.full) === 0) return true;
398
+ if (intrinsic.stringIndexOf(full, e.full) === 0) return true;
317
399
  }
318
400
  return false;
319
401
  }
320
402
 
321
- module.exports = { parseChallenge: parseChallenge, answer: answer, inProtectionSpace: inProtectionSpace };
403
+ /** @internal Frozen, so a consumer that reads one of these off this object at the call cannot be
404
+ * handed a replacement in place of it. */
405
+ module.exports = intrinsic.freeze({ parseChallenge: parseChallenge, answer: answer, inProtectionSpace: inProtectionSpace });
@@ -67,7 +67,21 @@
67
67
  var nodeHttps = require("node:https");
68
68
  var nodeHttp = require("node:http");
69
69
  var nodeNet = require("node:net");
70
+ /** @internal The address-family classification, captured at load. It is consulted BEFORE any of the
71
+ * string operations in `_isBlockedIp`, and the export is writable: answering `0` for `127.0.0.1`
72
+ * makes the function fall through to "not blocked", so a request under `blockPrivateAddresses`
73
+ * reaches the private address no matter how carefully the operations after it are captured. */
74
+ var _isIP = nodeNet.isIP;
75
+ /** @internal The TLS identity check, captured at load. It RETURNS A VERDICT about whether a
76
+ * certificate belongs to the host that was asked for, and the export is writable: a replacement
77
+ * answering with no error is the server-authentication step reporting success for a certificate
78
+ * issued to someone else. Every route that asks the question reads this one reference. */
70
79
  var nodeTls = require("node:tls");
80
+ /** @internal The TLS identity check, captured where `nodeTls` is in scope. It RETURNS A VERDICT about
81
+ * whether a certificate belongs to the host that was asked for, and the export is writable: a
82
+ * replacement answering with no error is the server-authentication step reporting success for a
83
+ * certificate issued to someone else. Every route that asks the question reads this one reference. */
84
+ var _checkServerIdentity = nodeTls.checkServerIdentity;
71
85
  var nodeDns = require("node:dns");
72
86
  var nodeUtil = require("node:util");
73
87
  var constants = require("./constants");
@@ -84,6 +98,11 @@ var _Promise = intrinsic.Promise;
84
98
  * with it, since a replacement answers with a host of its own. */
85
99
  var _URL = intrinsic.URL;
86
100
  var _stringOf = intrinsic.String;
101
+ /** @internal The split an address literal is classified by. Read off the live prototype, it decides
102
+ * which octets the blocklist sees, and a replacement answering with a public address's octets is
103
+ * admitted: measured, `127.0.0.1` split into `93.184.216.34` cleared the private-address refusal and
104
+ * the request went out. */
105
+ var _strSplit = intrinsic.uncurry(String.prototype.split);
87
106
  /** @internal The parts of that destination are read through the accessors captured at load, not off the
88
107
  * prototype the parsed value reads through: a replaced `URL.prototype.hostname` names the host this
89
108
  * module then opens a socket to. */
@@ -201,9 +220,22 @@ function _systemCa() {
201
220
  }
202
221
 
203
222
  function _isBlockedIp(ip) {
204
- var fam = nodeNet.isIP(ip);
223
+ /** @internal The argument has to BE a string, and what cannot be classified is blocked. `isIP`
224
+ * converts its argument, so a value whose conversion answers a loopback literal to the family
225
+ * check and a public one to the octet scan was classified twice and the second answer decided:
226
+ * measured, such a value came back NOT blocked while the family check had seen `127.0.0.1`. A
227
+ * caller-supplied `lookup` reaches this with whatever it yields, and `pki.transport.isBlockedIp`
228
+ * takes the value directly. A non-IP STRING is still not an address literal and answers false. */
229
+ if (typeof ip !== "string") return true;
230
+ var fam = _isIP(ip);
205
231
  if (fam === 4) {
206
- var o = ip.split("."), a = +o[0], b = +o[1], c = +o[2];
232
+ /** @internal The literal is scanned on the delimiter CHARACTER rather than split on a separator
233
+ * string. `String.prototype.split` is specified to look the separator's `Symbol.split` method up
234
+ * and call it, so the separator's prototype chain is part of the operation and capturing `split`
235
+ * does not close it; hard rule 11 bans `split` in lib/ for that reason. MEASURED on Node 24.21,
236
+ * the supported runtime: a hook answering with a public address's octets made this refusal clear
237
+ * for `127.0.0.1` and a request to loopback proceeded. */
238
+ var o = intrinsic.splitChar(ip, 46), a = +o[0], b = +o[1], c = +o[2];
207
239
  return a === 0 || a === 10 || a === 127 || a >= 224 ||
208
240
  (a === 100 && b >= 64 && b <= 127) ||
209
241
  (a === 169 && b === 254) ||
@@ -216,12 +248,22 @@ function _isBlockedIp(ip) {
216
248
  (a === 203 && b === 0 && c === 113);
217
249
  }
218
250
  if (fam === 6) {
219
- var l = ip.toLowerCase();
220
- if (l.indexOf("::ffff:") === 0) return true;
221
- var parts = l.split(":");
222
- var h = parseInt(parts[0], 16);
251
+ /** @internal The fold and the index read come from the load-time captures. This classification
252
+ * ADMITS anything inside global unicast, so a replaced `toLowerCase` answering with a
253
+ * global-unicast spelling for a private address turns a refusal into a connection attempt:
254
+ * measured, `fc00::1` folded to `2606:4700::1` was admitted past this blocklist and the request
255
+ * went out. The substitute has to be an address this function admits, or the refusal comes from
256
+ * another rule and the probe measures nothing. */
257
+ var l = intrinsic.toLowerCase(ip);
258
+ if (intrinsic.stringIndexOf(l, "::ffff:") === 0) return true;
259
+ var parts = intrinsic.splitChar(l, 58);
260
+ /** @internal The numeric conversion is captured too. The fold and the split were converted first
261
+ * and this was still live, so `parseInt("fc00", 16)` answering `0x2606` put the address inside
262
+ * global unicode and the refusal was skipped: converting the operations that produce the hextets
263
+ * without the one that reads them leaves the gate exactly as open. */
264
+ var h = intrinsic.parseInt(parts[0], 16);
223
265
  if (!(h >= 0x2000 && h <= 0x3fff)) return true;
224
- var h2 = parts[1] ? parseInt(parts[1], 16) : 0;
266
+ var h2 = parts[1] ? intrinsic.parseInt(parts[1], 16) : 0;
225
267
  if (h === 0x2002) return true;
226
268
  if (h === 0x2001 && h2 < 0x0200) return true;
227
269
  if (h === 0x2001 && h2 === 0x0db8) return true;
@@ -380,7 +422,7 @@ function _validateProxy(request, defaults, E, C, originHost, originStatedPort, o
380
422
  if (scheme !== "digest") throw E(C("bad-proxy"), "opts.proxy.auth." + kn + " applies only to Digest authentication");
381
423
  if (typeof kv !== "boolean") throw E(C("bad-proxy"), "opts.proxy.auth." + kn + " must be a boolean, got " + guard.text.showValue(kv));
382
424
  }
383
- if (scheme === "basic" && proxy.auth.username.indexOf(":") !== -1) throw E(C("bad-proxy"), "a Basic proxy user-id must not contain a colon (RFC 7617 sec. 2)");
425
+ if (scheme === "basic" && intrinsic.stringIndexOf(proxy.auth.username, ":") !== -1) throw E(C("bad-proxy"), "a Basic proxy user-id must not contain a colon (RFC 7617 sec. 2)");
384
426
  auth = { scheme: scheme, username: proxy.auth.username, password: proxy.auth.password,
385
427
  allowMD5: proxy.auth.allowMD5 === true, allowLegacyQop: proxy.auth.allowLegacyQop === true };
386
428
  }
@@ -394,6 +436,10 @@ function _validateProxy(request, defaults, E, C, originHost, originStatedPort, o
394
436
  guard.identifier.assertKnownKeys(ptls, PROXY_TLS_KEYS, E, C("bad-proxy"), "opts.proxy.tls has an unknown option ");
395
437
  if (ptls.servername !== undefined && ptls.servername !== null && typeof ptls.servername !== "string") throw E(C("bad-proxy"), "opts.proxy.tls.servername must be a string");
396
438
  }
439
+ /** @internal Taken here, so the value the tunnel is opened under is the one typed above rather
440
+ * than a later read of the caller's record. The door refuses an accessor and refuses a Proxy, so
441
+ * the two reads cannot differ today; holding the value keeps that true without depending on it. */
442
+ var pServername = (ptls && ptls.servername) || null;
397
443
  var pAnchors = ptls ? ptls.anchors : undefined;
398
444
  if (pAnchors !== undefined && pAnchors !== null && typeof pAnchors === "object" && nodeUtil.types.isProxy(pAnchors)) throw E(C("bad-proxy"), "opts.proxy.tls.anchors must not be a Proxy");
399
445
  var pUseSystem = ptls ? ptls.useSystemStore === true : false;
@@ -412,10 +458,10 @@ function _validateProxy(request, defaults, E, C, originHost, originStatedPort, o
412
458
  var pCa = [];
413
459
  if (pHasAnchors) pCa = pCa.concat(_pemifyAnchors(pAnchors));
414
460
  if (pUseSystem) pCa = pCa.concat(_systemCa());
415
- proxyTls = { ca: pCa.length ? pCa : undefined, minVersion: pMinVersion, servername: (ptls && ptls.servername) || null };
461
+ proxyTls = { ca: pCa.length ? pCa : undefined, minVersion: pMinVersion, servername: pServername };
416
462
  }
417
463
 
418
- var originAuthHost = nodeNet.isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
464
+ var originAuthHost = _isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
419
465
  var phost = _stripIpv6Brackets(_urlHostname(purl));
420
466
  /** @internal The CONNECT target is the origin's own authority, so an origin URL that states no
421
467
  * port takes the default for the origin's scheme, not the proxy's. Defaulting it to 443 opens
@@ -435,7 +481,9 @@ function _basicProxyHeader(auth) {
435
481
  * the request line carries (RFC 9112 sec. 3.2.3), which is the pair the proxy checks it against. */
436
482
  function _digestProxyCredential(headers, pinfo, C, E) {
437
483
  var httpDigest = require("./http-digest"); // allow:inline-require -- circular load: http-digest -> schema-pkix -> ct -> this module
438
- var offered = String((headers && headers["proxy-authenticate"]) || "");
484
+ /** @internal Captured: this is the challenge the digest credential is computed against, so the
485
+ * conversion decides which parameters, and which algorithm, the response is built for. */
486
+ var offered = intrinsic.String((headers && headers["proxy-authenticate"]) || "");
439
487
  var policy = {
440
488
  allowMD5: pinfo.auth.allowMD5, allowLegacyQop: pinfo.auth.allowLegacyQop,
441
489
  codes: {
@@ -579,7 +627,13 @@ function httpsTransport(defaults) {
579
627
  var callerCsi = typeof reqTls.checkServerIdentity === "function" ? reqTls.checkServerIdentity
580
628
  : (typeof tlsDefaults.checkServerIdentity === "function" ? tlsDefaults.checkServerIdentity : null);
581
629
  var sni = (reqTls.servername !== undefined ? reqTls.servername : tlsDefaults.servername) || host;
582
- if (sni && !nodeNet.isIP(sni)) options.servername = sni;
630
+ /** @internal Typed here, the way `opts.proxy.tls.servername` is typed on the proxy arm. The value
631
+ * is read three times after this point and each read is a separate conversion: once to decide it
632
+ * is not an address literal, once by the TLS layer for the name sent in the handshake, and once
633
+ * as `idHost` for the name the server certificate is matched against. A value answering a
634
+ * different string per read sends one name and verifies another. */
635
+ if (typeof sni !== "string") throw E(C("bad-input"), "tls.servername must be a string, got " + guard.text.showValue(sni));
636
+ if (sni && !_isIP(sni)) options.servername = sni;
583
637
  var caList = [];
584
638
  if (hasAnchors) caList = caList.concat(_pemifyAnchors(anchors));
585
639
  if (useSystem) caList = caList.concat(_systemCa());
@@ -588,10 +642,18 @@ function httpsTransport(defaults) {
588
642
  if (key) options.key = key;
589
643
  if (callerCsi) {
590
644
  options.checkServerIdentity = function (host, cert2) {
591
- var baseErr = nodeTls.checkServerIdentity(host, cert2);
645
+ var baseErr = _checkServerIdentity(host, cert2);
592
646
  if (baseErr) return baseErr;
593
647
  return callerCsi(host, cert2);
594
648
  };
649
+ } else {
650
+ /** @internal With no caller hook, the option is set to the CAPTURE rather than left out. Left
651
+ * out, node's own TLS stack looks the function up on its module export when the handshake
652
+ * completes, so a replacement there answers the identity question and a certificate issued to
653
+ * another host is accepted. Measured: with this absent, a request asking for a name the
654
+ * server's certificate does not carry was admitted. The function is node's own default, taken
655
+ * at load, so the verdict is the one it would have reached anyway. */
656
+ options.checkServerIdentity = _checkServerIdentity;
595
657
  }
596
658
  var proxyInfo;
597
659
  try { proxyInfo = _validateProxy(request, defaults, E, C, host, statedPort, scheme); }
@@ -649,7 +711,10 @@ function httpsTransport(defaults) {
649
711
  var client = prep.plaintext ? nodeHttp : nodeHttps;
650
712
  req = client.request(options, function (res) {
651
713
  var tlsInfo = _tlsInfo(res.socket);
652
- var declared = parseInt((res.headers || {})["content-length"], 10);
714
+ /** @internal Captured for consistency with the rest of the file, not because this gates
715
+ * anything: an understated `content-length` only skips this EARLY refusal, and the cap is
716
+ * enforced below on the bytes actually counted as they arrive. */
717
+ var declared = intrinsic.parseInt((res.headers || {})["content-length"], 10);
653
718
  if (Number.isFinite(declared) && declared > prep.maxBytes) { fail(C("response-too-large"), "the declared content-length " + declared + " exceeds the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
654
719
  var buf = Buffer.allocUnsafe(0);
655
720
  var len = 0;
@@ -727,8 +792,8 @@ function httpsTransport(defaults) {
727
792
  connectOpts.rejectUnauthorized = true;
728
793
  connectOpts.minVersion = pinfo.proxyTls.minVersion;
729
794
  var pIdentity = pinfo.proxyTls.servername || pinfo.host;
730
- connectOpts.servername = (pIdentity && !nodeNet.isIP(pIdentity)) ? pIdentity : "";
731
- connectOpts.checkServerIdentity = function (host, cert) { return nodeTls.checkServerIdentity(pIdentity, cert); };
795
+ connectOpts.servername = (pIdentity && !_isIP(pIdentity)) ? pIdentity : "";
796
+ connectOpts.checkServerIdentity = function (host, cert) { return _checkServerIdentity(pIdentity, cert); };
732
797
  cr = nodeHttps.request(connectOpts);
733
798
  } else {
734
799
  cr = nodeHttp.request(connectOpts);
@@ -775,7 +840,7 @@ function httpsTransport(defaults) {
775
840
  minVersion: prep.options.minVersion,
776
841
  rejectUnauthorized: true,
777
842
  checkServerIdentity: function (host, cert) {
778
- var baseErr = nodeTls.checkServerIdentity(idHost, cert);
843
+ var baseErr = _checkServerIdentity(idHost, cert);
779
844
  if (baseErr) return baseErr;
780
845
  return prep.callerCsi ? prep.callerCsi(idHost, cert) : undefined;
781
846
  },
@@ -212,7 +212,7 @@ var _SNAPSHOTS = new WeakSet();
212
212
  /** @internal Whether this list is one `snapshotReferences` produced. The record is kept off the
213
213
  * value, so a caller cannot make an arbitrary array pass by writing a field on it. */
214
214
  function _isSnapshot(v) {
215
- return _isArray(v) && _SNAPSHOTS.has(v);
215
+ return _isArray(v) && intrinsic.weakSetHas(_SNAPSHOTS, v);
216
216
  }
217
217
 
218
218
  /** @internal The reference identities, read and copied in one pass, so a caller that mutates its
@@ -233,7 +233,7 @@ function snapshotReferences(references) {
233
233
  intrinsic.freeze(parsed[i]);
234
234
  }
235
235
  intrinsic.freeze(parsed);
236
- _SNAPSHOTS.add(parsed);
236
+ intrinsic.weakSetAdd(_SNAPSHOTS, parsed);
237
237
  return parsed;
238
238
  }
239
239
 
package/lib/merkle.js CHANGED
@@ -516,7 +516,7 @@ function consistencyProof(opts) {
516
516
  return _subproof(leaves, 0, n, m, true);
517
517
  }
518
518
 
519
- module.exports = {
519
+ module.exports = intrinsic.freeze({
520
520
  leafHash: leafHash,
521
521
  nodeHash: nodeHash,
522
522
  emptyRootHash: emptyRootHash,
@@ -525,4 +525,4 @@ module.exports = {
525
525
  verifyConsistency: verifyConsistency,
526
526
  inclusionProof: inclusionProof,
527
527
  consistencyProof: consistencyProof,
528
- };
528
+ });
@@ -12,4 +12,4 @@ function verifyRequestSignature(signatureAlgorithm, signature, signerSpkiBytes,
12
12
  return _seam.verify(signatureAlgorithm, signature, signerSpkiBytes, tbsRequestBytes);
13
13
  }
14
14
 
15
- module.exports = { setEngine: _seam.setEngine, verifyRequestSignature: verifyRequestSignature };
15
+ module.exports = Object.freeze({ setEngine: _seam.setEngine, verifyRequestSignature: verifyRequestSignature });