@blamejs/pki 0.5.31 → 0.5.33

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 (52) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -3
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +93 -33
  5. package/lib/cmc-build.js +19 -8
  6. package/lib/cmc-verify.js +4 -4
  7. package/lib/cmp-build.js +36 -6
  8. package/lib/cmp-session.js +5 -5
  9. package/lib/cmp-verify.js +86 -16
  10. package/lib/cms-compress.js +2 -2
  11. package/lib/cms-decrypt.js +3 -3
  12. package/lib/cms-encrypt.js +1 -1
  13. package/lib/cms-sign.js +214 -93
  14. package/lib/cms-verify.js +363 -237
  15. package/lib/crl-sign.js +4 -4
  16. package/lib/crmf-sign.js +2 -1
  17. package/lib/csr-sign.js +1 -4
  18. package/lib/ct.js +8 -4
  19. package/lib/est.js +234 -34
  20. package/lib/framework-error.js +20 -6
  21. package/lib/guard-bytes.js +56 -4
  22. package/lib/guard-intrinsic.js +13 -0
  23. package/lib/http-digest.js +69 -16
  24. package/lib/http-retry-after.js +68 -24
  25. package/lib/http-transport.js +60 -6
  26. package/lib/inspect.js +13 -2
  27. package/lib/key.js +2 -2
  28. package/lib/lint.js +1 -1
  29. package/lib/mime.js +27 -4
  30. package/lib/ocsp.js +7 -7
  31. package/lib/oid.js +16 -0
  32. package/lib/path-validate.js +76 -18
  33. package/lib/pbes2.js +2 -2
  34. package/lib/pkcs12-build.js +2 -2
  35. package/lib/pki-build.js +6 -6
  36. package/lib/rfc3339.js +75 -17
  37. package/lib/schema-c509.js +36 -9
  38. package/lib/schema-cmc.js +3 -3
  39. package/lib/schema-pkix.js +174 -25
  40. package/lib/schema-tsp.js +10 -1
  41. package/lib/sign-scheme.js +1 -1
  42. package/lib/sigstore.js +52 -9
  43. package/lib/smime.js +86 -6
  44. package/lib/trust.js +114 -14
  45. package/lib/tsp-sign.js +39 -27
  46. package/lib/validator-tpm.js +2 -2
  47. package/lib/webauthn-mds.js +24 -6
  48. package/lib/webauthn.js +3 -3
  49. package/lib/webcrypto.js +80 -1
  50. package/lib/x509-sign.js +1 -1
  51. package/package.json +2 -2
  52. package/sbom.cdx.json +6 -6
package/lib/acme.js CHANGED
@@ -74,12 +74,143 @@ var _concat = intrinsic.concat;
74
74
  var _split = intrinsic.uncurry(String.prototype.split);
75
75
  var _parseIntI = intrinsic.parseInt;
76
76
  var _numToString = intrinsic.numberToString;
77
- var _reTest = intrinsic.uncurry(RegExp.prototype.test);
78
77
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
79
78
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
80
79
  var _charAt = intrinsic.uncurry(String.prototype.charAt);
81
80
  var pkgVersion = require("../package.json").version;
82
81
 
82
+ // Charset tables + character-scan validators/trimmers replacing acme's rule-#11 regexes. The token / link-token
83
+ // tables feed pkix.allCharsIn (a NON-EMPTY string of only these characters); the rest scan by character code.
84
+ var _TOKEN_TABLE = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"); // ^[A-Za-z0-9_-]
85
+ var _LINK_TOKEN_TABLE = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+.^_`|~-"); // RFC 7230 tchar
86
+ function _isHex1to4(s) { // /^[0-9a-f]{1,4}$/
87
+ var n = s.length;
88
+ if (n < 1 || n > 4) return false;
89
+ for (var i = 0; i < n; i++) { var c = _charCodeAt(s, i); if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false; }
90
+ return true;
91
+ }
92
+ function _looksLikeIpv4(s) { // /^\d{1,3}(\.\d{1,3}){3}$/ -- a loose dotted quad (octets are not range-checked)
93
+ var n = s.length, i = 0, octet, d;
94
+ for (octet = 0; octet < 4; octet++) {
95
+ if (octet > 0) { if (i >= n || _charCodeAt(s, i) !== 0x2e) return false; i++; }
96
+ for (d = 0; i < n && _charCodeAt(s, i) >= 48 && _charCodeAt(s, i) <= 57 && d < 3; d++) i++;
97
+ if (d < 1) return false;
98
+ }
99
+ return i === n;
100
+ }
101
+ function _countAtSigns(s) { var c = 0; for (var i = 0; i < s.length; i++) { if (_charCodeAt(s, i) === 0x40) c++; } return c; } // (s.match(/@/g)||[]).length
102
+ function _stripLeadingOWS(s, from) { // s.slice(from).replace(/^[ \t]+/, "")
103
+ var i = from;
104
+ while (i < s.length && (_charCodeAt(s, i) === 0x20 || _charCodeAt(s, i) === 0x09)) i++;
105
+ return _strSlice(s, i);
106
+ }
107
+ function _stripTrailingSlash(s) { // s.replace(/\/+$/, "")
108
+ var end = s.length;
109
+ while (end > 0 && _charCodeAt(s, end - 1) === 0x2f) end--;
110
+ return _strSlice(s, 0, end);
111
+ }
112
+ // /^(?:[^"\\]|\\.)*$/ : a valid quoted-string interior -- no unescaped '"', every '\' escapes a following
113
+ // NON-line-terminator character (the regex '.' matches no line terminator), and no dangling final '\'.
114
+ function _isValidQuotedInterior(s) {
115
+ var i = 0, n = s.length;
116
+ while (i < n) {
117
+ var c = _charCodeAt(s, i);
118
+ if (c === 0x22) return false;
119
+ if (c === 0x5c) {
120
+ if (i + 1 >= n) return false;
121
+ var nx = _charCodeAt(s, i + 1);
122
+ if (nx === 0x0a || nx === 0x0d || nx === 0x2028 || nx === 0x2029) return false;
123
+ i += 2;
124
+ } else i += 1;
125
+ }
126
+ return true;
127
+ }
128
+ // Shared character predicates for the scheme / label / URL scans below.
129
+ function _isAlpha(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); }
130
+ function _isAlnumLower(c) { return (c >= 97 && c <= 122) || (c >= 48 && c <= 57); }
131
+ function _isSchemeChar(c) { return _isAlpha(c) || (c >= 48 && c <= 57) || c === 0x2b || c === 0x2e || c === 0x2d; } // [A-Za-z0-9+.-]
132
+ // /^https?:\/\/[^\s]+$/ : lowercase http(s), "://", then >=1 non-whitespace character to the end.
133
+ function _isHttpUrlShape(v) {
134
+ var n = v.length;
135
+ if (!(_charCodeAt(v, 0) === 0x68 && _charCodeAt(v, 1) === 0x74 && _charCodeAt(v, 2) === 0x74 && _charCodeAt(v, 3) === 0x70)) return false;
136
+ var i = 4;
137
+ if (_charCodeAt(v, i) === 0x73) i++;
138
+ if (_charCodeAt(v, i) !== 0x3a || _charCodeAt(v, i + 1) !== 0x2f || _charCodeAt(v, i + 2) !== 0x2f) return false;
139
+ i += 3;
140
+ if (i >= n) return false;
141
+ for (; i < n; i++) { if (pkix.isJsWhitespace(_charCodeAt(v, i))) return false; }
142
+ return true;
143
+ }
144
+ // /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]+$/ : an RFC 3986 scheme, ":", then >=1 non-whitespace character to the end.
145
+ function _isUriShape(v) {
146
+ var n = v.length;
147
+ if (n === 0 || !_isAlpha(_charCodeAt(v, 0))) return false;
148
+ var i = 1;
149
+ while (i < n && _isSchemeChar(_charCodeAt(v, i))) i++;
150
+ if (i >= n || _charCodeAt(v, i) !== 0x3a) return false;
151
+ i++;
152
+ if (i >= n) return false;
153
+ for (; i < n; i++) { if (pkix.isJsWhitespace(_charCodeAt(v, i))) return false; }
154
+ return true;
155
+ }
156
+ // /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/ : a DNS label -- first and last characters alphanumeric-lowercase, the
157
+ // interior characters additionally allowing '-'.
158
+ function _isLdhLabel(l) {
159
+ var n = l.length;
160
+ if (n === 0 || !_isAlnumLower(_charCodeAt(l, 0)) || !_isAlnumLower(_charCodeAt(l, n - 1))) return false;
161
+ for (var i = 1; i < n - 1; i++) { var c = _charCodeAt(l, i); if (!(_isAlnumLower(c) || c === 0x2d)) return false; }
162
+ return true;
163
+ }
164
+ // /^xn--[a-z0-9]+(-[a-z0-9]+)*$/ : an A-label -- "xn--" then hyphen-separated non-empty alphanumeric-lowercase runs.
165
+ function _isXnLabel(l) {
166
+ var n = l.length;
167
+ if (!(n >= 4 && _charCodeAt(l, 0) === 0x78 && _charCodeAt(l, 1) === 0x6e && _charCodeAt(l, 2) === 0x2d && _charCodeAt(l, 3) === 0x2d)) return false;
168
+ var i = 4, g = 0;
169
+ while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g++; }
170
+ if (g < 1) return false;
171
+ while (i < n) {
172
+ if (_charCodeAt(l, i) !== 0x2d) return false;
173
+ i++; var g2 = 0;
174
+ while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g2++; }
175
+ if (g2 < 1) return false;
176
+ }
177
+ return true;
178
+ }
179
+ // /^(0|[1-9]\d{0,2})$/ : a canonical decimal octet -- a single digit, or 2-3 digits with no leading zero.
180
+ function _isCanonicalOctet(s) {
181
+ var n = s.length;
182
+ if (n === 0 || n > 3) return false;
183
+ if (n === 1) { var c0 = _charCodeAt(s, 0); return c0 >= 48 && c0 <= 57; }
184
+ if (!(_charCodeAt(s, 0) >= 49 && _charCodeAt(s, 0) <= 57)) return false;
185
+ for (var i = 1; i < n; i++) { var c = _charCodeAt(s, i); if (!(c >= 48 && c <= 57)) return false; }
186
+ return true;
187
+ }
188
+ // /[\s\\]/ : contains a whitespace character or a backslash.
189
+ function _hasWsOrBackslash(s) {
190
+ for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c === 0x5c || pkix.isJsWhitespace(c)) return true; }
191
+ return false;
192
+ }
193
+ // /^[A-Za-z][A-Za-z0-9+.-]*:/ : begins with an RFC 3986 scheme followed by ':' (a prefix test, not anchored at end).
194
+ function _hasSchemePrefix(uri) {
195
+ var n = uri.length;
196
+ if (n === 0 || !_isAlpha(_charCodeAt(uri, 0))) return false;
197
+ var i = 1;
198
+ while (i < n && _isSchemeChar(_charCodeAt(uri, i))) i++;
199
+ return i < n && _charCodeAt(uri, i) === 0x3a;
200
+ }
201
+ // uri.split(/[/?#]/)[0] : the substring up to the first '/', '?', or '#' (the whole string if none).
202
+ function _uriFirstSegment(uri) {
203
+ for (var i = 0; i < uri.length; i++) { var c = _charCodeAt(uri, i); if (c === 0x2f || c === 0x3f || c === 0x23) return _strSlice(uri, 0, i); }
204
+ return uri;
205
+ }
206
+ // Does `s` end with the literal `suffix`? (/suffix$/ for a fixed suffix.)
207
+ function _endsWith(s, suffix) {
208
+ var n = s.length, m = suffix.length;
209
+ if (m > n) return false;
210
+ for (var i = 0; i < m; i++) { if (_charCodeAt(s, n - m + i) !== _charCodeAt(suffix, i)) return false; }
211
+ return true;
212
+ }
213
+
83
214
  var AcmeError = frameworkError.AcmeError;
84
215
  function E(code, message, cause) { return new AcmeError(code, message, cause); }
85
216
 
@@ -112,7 +243,7 @@ function _isUrl(v) {
112
243
  // silently repairs " https://.." (trim) and "https:host/.." (insert //), and the
113
244
  // original string is what gets copied into a protected `url` field, so a repaired
114
245
  // value must be rejected here, never accepted and mismatched at transport time.
115
- if (!_isString(v) || !/^https?:\/\/[^\s]+$/.test(v)) return false;
246
+ if (!_isString(v) || !_isHttpUrlShape(v)) return false;
116
247
  var u;
117
248
  // A parse failure is the "not a URL" verdict for this boolean predicate. There
118
249
  // is no PkiError here to thread a cause into, so the error is intentionally ignored.
@@ -123,7 +254,7 @@ function _isUrl(v) {
123
254
  // A URI string with any RFC 3986 scheme (mailto:, tel:, http(s):, ...). An account
124
255
  // `contact` is a URI, most commonly `mailto:` (RFC 8555 sec. 7.1.2 / RFC 6068), so
125
256
  // it must not be narrowed to http(s); the strict mailto hygiene lives in the builder.
126
- function _isUriString(v) { return _isString(v) && /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]+$/.test(v); }
257
+ function _isUriString(v) { return _isString(v) && _isUriShape(v); }
127
258
 
128
259
  // ---- resource object specs (RFC 8555 sec. 7.1.x) -------------------------
129
260
 
@@ -232,7 +363,11 @@ function _checkType(kind, field, value, obj) {
232
363
  // lowercase-kebab): a camelCase kind like "renewalInfo" must become
233
364
  // "renewal-info", never leak "acme/bad-renewalInfo" (which the PkiError code
234
365
  // validator rejects, turning a fault into a raw TypeError).
235
- function _codeSlug(kind) { return kind.replace(/([A-Z])/g, "-$1").toLowerCase(); }
366
+ function _codeSlug(kind) { // camelCase -> kebab-case: kind.replace(/([A-Z])/g, "-$1").toLowerCase()
367
+ var out = "";
368
+ for (var i = 0; i < kind.length; i++) { var c = _charCodeAt(kind, i); if (c >= 65 && c <= 90) out += "-"; out += _charAt(kind, i); }
369
+ return _lower(out);
370
+ }
236
371
 
237
372
  // Validate an object against a spec table. Returns the object; throws acme/*.
238
373
  function _validate(kind, obj) {
@@ -288,8 +423,8 @@ function _assertDnsName(name) {
288
423
  for (var i = 0; i < labels.length; i++) {
289
424
  var l = labels[i];
290
425
  if (l.length > 63) throw E("acme/bad-identifier", "a dns label must be 1..63 characters (RFC 1035 sec. 2.3.4): " + JSON.stringify(l));
291
- if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(l)) throw E("acme/bad-identifier", "a dns label must be lowercase LDH ASCII (A-label): " + JSON.stringify(l));
292
- if (l.indexOf("xn--") === 0 && !/^xn--[a-z0-9]+(-[a-z0-9]+)*$/.test(l)) throw E("acme/bad-identifier", "a malformed xn-- A-label: " + JSON.stringify(l));
426
+ if (!_isLdhLabel(l)) throw E("acme/bad-identifier", "a dns label must be lowercase LDH ASCII (A-label): " + JSON.stringify(l));
427
+ if (l.indexOf("xn--") === 0 && !_isXnLabel(l)) throw E("acme/bad-identifier", "a malformed xn-- A-label: " + JSON.stringify(l));
293
428
  }
294
429
  }
295
430
 
@@ -300,10 +435,10 @@ function _assertDnsName(name) {
300
435
  // normalized-and-guessed.
301
436
  function _assertIpAddress(value) {
302
437
  if (!_isString(value)) throw E("acme/bad-identifier", "an ip identifier value must be a string");
303
- if (/^\d{1,3}(\.\d{1,3}){3}$/.test(value)) {
438
+ if (_looksLikeIpv4(value)) {
304
439
  var parts = value.split(".");
305
440
  for (var i = 0; i < 4; i++) {
306
- if (!/^(0|[1-9]\d{0,2})$/.test(parts[i]) || parseInt(parts[i], 10) > 255) throw E("acme/bad-identifier", "an ip identifier IPv4 octet is out of range or non-canonical: " + JSON.stringify(value));
441
+ if (!_isCanonicalOctet(parts[i]) || parseInt(parts[i], 10) > 255) throw E("acme/bad-identifier", "an ip identifier IPv4 octet is out of range or non-canonical: " + JSON.stringify(value));
307
442
  }
308
443
  return;
309
444
  }
@@ -337,7 +472,7 @@ function _canonicalizeIpv6(value) {
337
472
  if (groups.length !== 8) return null;
338
473
  var nums = [];
339
474
  for (var i = 0; i < 8; i++) {
340
- if (!_reTest(/^[0-9a-f]{1,4}$/, groups[i])) return null;
475
+ if (!_isHex1to4(groups[i])) return null;
341
476
  _push(nums, _parseIntI(groups[i], 16));
342
477
  }
343
478
  var hex = _mapI(nums, function (n) { return _numToString(n, 16); });
@@ -503,9 +638,8 @@ function identify(obj) {
503
638
  // A challenge token: >= 128 bits of entropy => >= 22 base64url chars, alphabet
504
639
  // only, no `=` (RFC 8555 sec. 8, errata 6950). Validated BEFORE any use (also the
505
640
  // http-01 reflection-XSS guard).
506
- var TOKEN_RE = new RegExp("^[A-Za-z0-9_-]{" + constants.LIMITS.ACME_TOKEN_MIN_CHARS + ",}$");
507
641
  function _assertToken(token) {
508
- if (!_isString(token) || !TOKEN_RE.test(token)) throw E("acme/bad-token", "a challenge token must be >= " + constants.LIMITS.ACME_TOKEN_MIN_CHARS + " base64url characters with no padding (RFC 8555 sec. 8)");
642
+ if (!_isString(token) || token.length < constants.LIMITS.ACME_TOKEN_MIN_CHARS || !pkix.allCharsIn(token, _TOKEN_TABLE)) throw E("acme/bad-token", "a challenge token must be >= " + constants.LIMITS.ACME_TOKEN_MIN_CHARS + " base64url characters with no padding (RFC 8555 sec. 8)");
509
643
  }
510
644
 
511
645
  async function _sha256(bytes) { return Buffer.from(await subtle.digest("SHA-256", bytes)); }
@@ -769,7 +903,7 @@ function _assertContacts(contacts) {
769
903
  var addr = c.slice("mailto:".length);
770
904
  if (addr.indexOf("?") !== -1) throw E("acme/bad-contact", "a mailto contact must not carry header fields (RFC 8555 sec. 7.3 / RFC 6068)");
771
905
  if (addr.indexOf(",") !== -1) throw E("acme/bad-contact", "a mailto contact must be a single addr-spec, not a comma list (RFC 8555 sec. 7.3)");
772
- if ((addr.match(/@/g) || []).length !== 1) throw E("acme/bad-contact", "a mailto contact must be exactly one addr-spec");
906
+ if (_countAtSigns(addr) !== 1) throw E("acme/bad-contact", "a mailto contact must be exactly one addr-spec");
773
907
  }
774
908
  });
775
909
  }
@@ -1024,7 +1158,7 @@ function _csrIdentifierSet(parsedCsr) {
1024
1158
  // RFC 5952 / RFC 8738 form is neither a usable dns name nor a canonical ip identifier, and it is
1025
1159
  // refused rather than normalized -- the same rule the ip identifier validator applies.
1026
1160
  function _addCommonName(set, cn, what) {
1027
- var looksIp = _reTest(/^\d{1,3}(\.\d{1,3}){3}$/, cn) || intrinsic.stringIndexOf(cn, ":") !== -1;
1161
+ var looksIp = _looksLikeIpv4(cn) || intrinsic.stringIndexOf(cn, ":") !== -1;
1028
1162
  // An address in a common name adds no identity of its own. Address matching reads the iPAddress
1029
1163
  // subjectAltName and never falls back to the common name, so a subject naming the ordered address
1030
1164
  // only there cannot authenticate it. What the ordinary IP certificate does -- CABF TLS BR
@@ -1169,8 +1303,9 @@ function _assertCsrIdentifiers(parsedCsr, identifiers) {
1169
1303
  * await pki.acme.finalize({ key, alg: "ES256", nonce, url, kid, csr: csrDer, identifiers, accountJwk });
1170
1304
  */
1171
1305
  async function finalize(o) {
1172
- if (!_isObject(o) || !Buffer.isBuffer(o.csr)) throw E("acme/bad-input", "finalize requires a DER CSR Buffer (opts.csr)");
1173
- var parsed = csr.parse(o.csr); // strict DER; rejects PEM/garbage
1306
+ if (!_isObject(o) || !guard.bytes.isByteSource(o.csr)) throw E("acme/bad-input", "finalize requires a DER CSR BufferSource (opts.csr)");
1307
+ var csrBuf = guard.bytes.snapshotSource(o.csr, E, "acme/bad-input", "the finalize CSR");
1308
+ var parsed = csr.parse(csrBuf); // strict DER; rejects PEM/garbage
1174
1309
  if (o.accountJwk !== undefined) {
1175
1310
  var accountSpki = await _jwkToSpki(o.accountJwk);
1176
1311
  if (parsed.subjectPublicKeyInfo && Buffer.isBuffer(parsed.subjectPublicKeyInfo.bytes) &&
@@ -1183,7 +1318,7 @@ async function finalize(o) {
1183
1318
  o.identifiers.forEach(_validateOrderIdentifier); // caller-supplied -> validate before comparing (no raw TypeError)
1184
1319
  _assertCsrIdentifiers(parsed, o.identifiers);
1185
1320
  }
1186
- var payload = { csr: jose.base64url.encode(o.csr) };
1321
+ var payload = { csr: jose.base64url.encode(csrBuf) };
1187
1322
  return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
1188
1323
  }
1189
1324
 
@@ -1270,12 +1405,13 @@ function deactivate(o) {
1270
1405
  */
1271
1406
  function revokeCert(o) {
1272
1407
  return _promised(function () {
1273
- if (!_isObject(o) || !Buffer.isBuffer(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate Buffer (opts.certificate)");
1274
- x509.parse(o.certificate); // structural validation of the target
1408
+ if (!_isObject(o) || !guard.bytes.isByteSource(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate BufferSource (opts.certificate)");
1409
+ var certBuf = guard.bytes.snapshotSource(o.certificate, E, "acme/bad-input", "the revokeCert certificate");
1410
+ x509.parse(certBuf); // structural validation of the target
1275
1411
  var hasKid = _hasOwn(o, "kid");
1276
1412
  var hasJwk = _hasOwn(o, "jwk");
1277
1413
  if (hasKid === hasJwk) throw E("acme/bad-input", "revokeCert must be signed with EXACTLY ONE of the account kid or the certificate jwk (RFC 8555 sec. 7.6)");
1278
- var payload = { certificate: jose.base64url.encode(o.certificate) };
1414
+ var payload = { certificate: jose.base64url.encode(certBuf) };
1279
1415
  if (o.reason !== undefined) {
1280
1416
  if (typeof o.reason !== "number" || !isFinite(o.reason) || Math.floor(o.reason) !== o.reason || CRL_REASONS.indexOf(o.reason) === -1) {
1281
1417
  throw E("acme/bad-revocation-reason", "reason must be an assigned RFC 5280 CRLReason (0-6, 8-10; value 7 is unassigned)");
@@ -1360,7 +1496,7 @@ async function keyChange(o) {
1360
1496
  * pki.acme.ariCertId(certDer); // -> "<b64u-aki>.<b64u-serial>"
1361
1497
  */
1362
1498
  function ariCertId(certDer) {
1363
- if (!Buffer.isBuffer(certDer)) throw E("acme/bad-input", "ariCertId requires a DER certificate Buffer");
1499
+ if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "ariCertId requires a DER certificate BufferSource");
1364
1500
  var cert = x509.parse(certDer);
1365
1501
  var akiExt = (cert.extensions || []).filter(function (e) { return e.oid === OID_AKI; })[0];
1366
1502
  if (!akiExt) throw E("acme/bad-certid", "the certificate has no authorityKeyIdentifier extension (RFC 9773 sec. 4.1)");
@@ -1451,11 +1587,12 @@ function _clientUrl(urlStr) {
1451
1587
  // (RFC 8555 sec. 6.4); refuse it rather than sign a credential-bearing URL that steers nothing. Test the RAW
1452
1588
  // authority for a literal "@", not url.username/password: an EMPTY userinfo ("https://@host", "https://:@host")
1453
1589
  // leaves both empty while the verbatim URL still carries the "@" the transport drops.
1454
- if (((/^[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/?#]*)/.exec(s) || [])[1] || "").indexOf("@") !== -1) throw E("acme/bad-url", "an ACME URL must not contain userinfo: " + JSON.stringify(s));
1590
+ var _uAuth = _uriAuthority(s, true);
1591
+ if (intrinsic.stringIndexOf(_uAuth ? _uAuth.authority : "", "@") !== -1) throw E("acme/bad-url", "an ACME URL must not contain userinfo: " + JSON.stringify(s));
1455
1592
  // Reject a spelling the transport would REPAIR into a different path -- whitespace (WHATWG percent-
1456
1593
  // encodes or trims it) or a backslash (rewritten to `/`). The transport re-parses the URL to the
1457
1594
  // normalized path while the JWS `url` keeps the original, so the signed and requested URLs would differ.
1458
- if (/[\s\\]/.test(s)) throw E("acme/bad-url", "an ACME URL must not contain whitespace or a backslash: " + JSON.stringify(s));
1595
+ if (_hasWsOrBackslash(s)) throw E("acme/bad-url", "an ACME URL must not contain whitespace or a backslash: " + JSON.stringify(s));
1459
1596
  // The path/query round-trip checks below cover only pathname+search; apply the shared structural authority
1460
1597
  // guard too, so a repaired AUTHORITY (a bracket in userinfo -- "a[b@host" -> "a%5Bb@host", a double "@") is
1461
1598
  // rejected on THIS direct path (a directory / Location / caller URL) exactly as the Link-alternate path
@@ -1469,9 +1606,9 @@ function _clientUrl(urlStr) {
1469
1606
  // a different (often loopback / internal, an SSRF-adjacent) address. Compare the raw host (ASCII-lowercased, the
1470
1607
  // only tolerated normalization) to the parsed hostname: uppercase-host and a default :443 still match, an IPv4
1471
1608
  // coercion or a non-canonical IPv6 literal does not. (path/query/fragment are checked separately below.)
1472
- var _auth = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/?#]*)/.exec(s);
1609
+ var _auth = _uriAuthority(s, true);
1473
1610
  if (_auth) {
1474
- var _hp = _auth[1].slice(_auth[1].lastIndexOf("@") + 1); // strip any userinfo (does not route the request)
1611
+ var _hp = _auth.authority.slice(_auth.authority.lastIndexOf("@") + 1); // strip any userinfo (does not route the request)
1475
1612
  var _rawHost = _hp.charAt(0) === "[" ? _hp.slice(0, _hp.indexOf("]") + 1) : _hp.split(":")[0]; // strip the port
1476
1613
  if (_rawHost.toLowerCase() !== url.hostname) throw E("acme/bad-url", "an ACME URL host is not canonical (the transport would rewrite it): " + JSON.stringify(s));
1477
1614
  }
@@ -1483,7 +1620,8 @@ function _clientUrl(urlStr) {
1483
1620
  // whose verbatim `s` (no slash) differs from the `/` the transport requests. (The authority may still
1484
1621
  // normalize -- a default :443 port, an uppercase host -- as that does not change the path; an encoded char
1485
1622
  // like `%41` or a dot inside a segment name round-trips unchanged.)
1486
- var rawPath = s.replace(/^[a-z]+:\/\/[^/?#]*/i, "").split(/[?#]/)[0];
1623
+ var _pathStart = _rawPathStart(s);
1624
+ var rawPath = _strSlice(s, _pathStart, _firstDelim(s, _pathStart, 0x3f, 0x23, -1));
1487
1625
  if (rawPath !== url.pathname) throw E("acme/bad-url", "an ACME URL path is not canonical (the transport would normalize it): " + JSON.stringify(s));
1488
1626
  // The query round-trips too (the transport sends pathname+search): an EMPTY query ("...?") -- which WHATWG
1489
1627
  // drops (url.search === "") while the verbatim URL keeps the "?" -- or any query the parser would rewrite
@@ -1541,22 +1679,26 @@ function _clientTls(o) {
1541
1679
  return { anchors: t.anchors, useSystemStore: t.useSystemStore, cert: t.cert, key: t.key, minVersion: t.minVersion, servername: t.servername, checkServerIdentity: t.checkServerIdentity };
1542
1680
  }
1543
1681
  // Split a PEM certificate chain (RFC 8555 sec. 7.4.2 application/pem-certificate-chain) into its DER certs.
1682
+ var PEM_CERT_BEGIN = "-----BEGIN CERTIFICATE-----";
1683
+ var PEM_CERT_END = "-----END CERTIFICATE-----";
1544
1684
  function _splitPemChain(text) {
1545
1685
  var out = [];
1546
- var re = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
1547
- var m;
1548
- var lastEnd = 0;
1549
- while ((m = re.exec(text)) !== null) {
1686
+ var lastEnd = 0, from = 0, begin, end;
1687
+ // Each block is the shortest BEGIN..END span (the lazy /BEGIN[\s\S]*?END/g scan by literal index).
1688
+ while ((begin = intrinsic.stringIndexOf(text, PEM_CERT_BEGIN, from)) !== -1 &&
1689
+ (end = intrinsic.stringIndexOf(text, PEM_CERT_END, begin + PEM_CERT_BEGIN.length)) !== -1) {
1690
+ var blockEnd = end + PEM_CERT_END.length;
1550
1691
  // application/pem-certificate-chain is ONLY PEM blocks separated by whitespace (RFC 8555 sec. 7.4.2) --
1551
1692
  // no explanatory text before, between, or after; reject any non-whitespace outside a block.
1552
- if (text.slice(lastEnd, m.index).trim() !== "") throw E("acme/bad-certificate-chain", "the certificate chain contained non-whitespace text outside a PEM block (RFC 8555 sec. 7.4.2)");
1693
+ if (text.slice(lastEnd, begin).trim() !== "") throw E("acme/bad-certificate-chain", "the certificate chain contained non-whitespace text outside a PEM block (RFC 8555 sec. 7.4.2)");
1553
1694
  var der;
1554
1695
  // Decode the armor AND structurally parse it: valid PEM armor can wrap arbitrary base64, so without
1555
1696
  // the strict X.509 parse the download could return non-certificate bytes as the issued certificate.
1556
- try { der = x509.pemDecode(m[0], "CERTIFICATE"); x509.parse(der); }
1697
+ try { der = x509.pemDecode(_strSlice(text, begin, blockEnd), "CERTIFICATE"); x509.parse(der); }
1557
1698
  catch (e) { throw E("acme/bad-certificate-chain", "a downloaded certificate did not decode as a valid X.509 certificate (RFC 8555 sec. 7.4.2)", e); }
1558
1699
  out.push(der);
1559
- lastEnd = re.lastIndex;
1700
+ lastEnd = blockEnd;
1701
+ from = blockEnd;
1560
1702
  }
1561
1703
  if (text.slice(lastEnd).trim() !== "") throw E("acme/bad-certificate-chain", "the certificate chain contained trailing non-whitespace text outside a PEM block (RFC 8555 sec. 7.4.2)");
1562
1704
  if (!out.length) throw E("acme/bad-certificate-chain", "the certificate download carried no PEM certificate (RFC 8555 sec. 7.4.2)");
@@ -1568,19 +1710,125 @@ function _splitPemChain(text) {
1568
1710
  // (CWE-770 fetch amplification), the same posture as maxRedirects.
1569
1711
  var LINK_HEADER_MAX_BYTES = constants.BYTES.kib(8);
1570
1712
  var DEFAULT_MAX_ALTERNATES = 8;
1571
- // An RFC 7230 token (a Link parameter name, or an unquoted parameter value): 1*tchar, no whitespace/specials.
1572
- var LINK_TOKEN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
1573
1713
  // Trim only HTTP OWS -- SP (0x20) and HTAB (0x09) -- NOT arbitrary Unicode whitespace. String.prototype.trim
1574
1714
  // would strip an NBSP (obs-text) and other Unicode spaces, letting them masquerade as field whitespace.
1575
- function _trimOWS(s) { return s.replace(/^[ \t]+/, "").replace(/[ \t]+$/, ""); }
1715
+ function _trimOWS(s) { // s.replace(/^[ \t]+/, "").replace(/[ \t]+$/, "")
1716
+ var a = 0, b = s.length;
1717
+ while (a < b && (_charCodeAt(s, a) === 0x20 || _charCodeAt(s, a) === 0x09)) a++;
1718
+ while (b > a && (_charCodeAt(s, b - 1) === 0x20 || _charCodeAt(s, b - 1) === 0x09)) b--;
1719
+ return _strSlice(s, a, b);
1720
+ }
1721
+ // ---- URI / percent / Link character-scan helpers (rule #11: no regex) ----
1722
+ var _URIREF_TBL = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._~:/?#[]@!$&'()*+,;=%-"); // RFC 3986 URI-Reference allowed set
1723
+ var _UNRES_TBL = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._~-"); // RFC 3986 unreserved
1724
+ var _IPVF_TBL = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._~!$&'()*+,;=:-"); // IPvFuture tail chars
1725
+ var _HEX_TBL = pkix.charTable("0123456789ABCDEFabcdef"); // hex digit, by table lookup (not an OR-chain)
1726
+ function _hexVal(c) { if (c >= 48 && c <= 57) return c - 48; if (c >= 65 && c <= 70) return c - 55; return c - 87; }
1727
+ function _upperHexChar(c) { return (c >= 97 && c <= 102) ? c - 32 : c; }
1728
+ // The RFC 3986 authority front-half shared by every authority check: an optional (or, when schemeRequired,
1729
+ // mandatory) scheme, "//", then the authority up to the first "/?#". Returns null when there is no "//".
1730
+ function _uriAuthority(uri, schemeRequired) {
1731
+ var n = uri.length, i = 0, schemeEnd = -1, j;
1732
+ if (n > 0 && _isAlpha(_charCodeAt(uri, 0))) {
1733
+ j = 1;
1734
+ while (j < n && _isSchemeChar(_charCodeAt(uri, j))) j++;
1735
+ if (j < n && _charCodeAt(uri, j) === 0x3a) schemeEnd = j;
1736
+ }
1737
+ if (schemeEnd !== -1) {
1738
+ if (_charCodeAt(uri, schemeEnd + 1) === 0x2f && _charCodeAt(uri, schemeEnd + 2) === 0x2f) i = schemeEnd + 1;
1739
+ else if (schemeRequired) return null;
1740
+ else { schemeEnd = -1; i = 0; }
1741
+ }
1742
+ if (schemeRequired && schemeEnd === -1) return null;
1743
+ if (_charCodeAt(uri, i) !== 0x2f || _charCodeAt(uri, i + 1) !== 0x2f) return null;
1744
+ var authStart = i + 2, authEnd = authStart;
1745
+ while (authEnd < n) { var c = _charCodeAt(uri, authEnd); if (c === 0x2f || c === 0x3f || c === 0x23) break; authEnd++; }
1746
+ return { scheme: schemeEnd === -1 ? "" : _strSlice(uri, 0, schemeEnd + 1), authorityStart: authStart, authorityEnd: authEnd, authority: _strSlice(uri, authStart, authEnd) };
1747
+ }
1748
+ // The first index at/after `from` whose code is one of the (up to three) delimiters, else the string length.
1749
+ function _firstDelim(s, from, d0, d1, d2) { for (var i = from; i < s.length; i++) { var c = _charCodeAt(s, i); if (c === d0 || c === d1 || c === d2) return i; } return s.length; }
1750
+ // /^:[0-9]*$/ : ":" then zero or more digits (an empty port ":" passes).
1751
+ function _isNumericPort(s) { if (s.length === 0 || _charCodeAt(s, 0) !== 0x3a) return false; for (var i = 1; i < s.length; i++) { var c = _charCodeAt(s, i); if (c < 48 || c > 57) return false; } return true; }
1752
+ // /^[vV][0-9A-Fa-f]+\.[A-Za-z0-9._~!$&'()*+,;=:-]+$/ : a valid RFC 3986 IPvFuture literal interior.
1753
+ function _isIpvFuture(s) {
1754
+ var n = s.length, i = 1, h = 0, g = 0;
1755
+ if (n === 0 || (_charCodeAt(s, 0) !== 0x76 && _charCodeAt(s, 0) !== 0x56)) return false;
1756
+ while (i < n) { if (_HEX_TBL[_charCodeAt(s, i)]) { i++; h++; } else break; }
1757
+ if (h < 1 || i >= n || _charCodeAt(s, i) !== 0x2e) return false;
1758
+ i++;
1759
+ while (i < n) { if (_IPVF_TBL[_charCodeAt(s, i)]) { i++; g++; } else break; }
1760
+ return g >= 1 && i === n;
1761
+ }
1762
+ // The prefix ^(?:scheme:)?//(?:[^/?#@[\]]*@)?\[[^[\]]*\] : optional scheme, "//", optional BRACKET-EXCLUDING
1763
+ // userinfo, then an IP-literal host. Returns the index past "]" (the match length from 0) or -1.
1764
+ function _matchBracketAuthPrefix(uri) {
1765
+ var a = _uriAuthority(uri, false);
1766
+ if (!a) return -1;
1767
+ var n = uri.length, i = a.authorityStart, save = i, k = i, uiOk = true, c;
1768
+ while (k < n) { c = _charCodeAt(uri, k); if (c === 0x40) break; if (c === 0x2f || c === 0x3f || c === 0x23 || c === 0x5b || c === 0x5d) { uiOk = false; break; } k++; }
1769
+ if (uiOk && k < n && _charCodeAt(uri, k) === 0x40) i = k + 1; else i = save;
1770
+ if (_charCodeAt(uri, i) !== 0x5b) return -1;
1771
+ i++;
1772
+ while (i < n) { c = _charCodeAt(uri, i); if (c === 0x5b || c === 0x5d) break; i++; }
1773
+ if (i >= n || _charCodeAt(uri, i) !== 0x5d) return -1;
1774
+ return i + 1;
1775
+ }
1776
+ // The prefix ^(?:scheme:)?//(?:[^/?#@]*@)?\[([^\]]*)\] : optional scheme, "//", optional BRACKET-PERMITTING
1777
+ // userinfo, then an IP-literal host. Returns the interior (between "[" and "]") or null.
1778
+ function _ipLiteralInterior(uri) {
1779
+ var a = _uriAuthority(uri, false);
1780
+ if (!a) return null;
1781
+ var n = uri.length, save = a.authorityStart, k = save, c, bracketAt;
1782
+ while (k < n) { c = _charCodeAt(uri, k); if (c === 0x2f || c === 0x3f || c === 0x23 || c === 0x40) break; k++; }
1783
+ if (k < n && _charCodeAt(uri, k) === 0x40 && _charCodeAt(uri, k + 1) === 0x5b) bracketAt = k + 1;
1784
+ else if (_charCodeAt(uri, save) === 0x5b) bracketAt = save;
1785
+ else return null;
1786
+ var start = bracketAt + 1, p = start;
1787
+ while (p < n && _charCodeAt(uri, p) !== 0x5d) p++;
1788
+ if (p >= n) return null;
1789
+ return _strSlice(uri, start, p);
1790
+ }
1791
+ // s.replace(/^[a-z]+:\/\/[^/?#]*/i, "") -> the index where the path begins (0 if there is no scheme+"//").
1792
+ function _rawPathStart(s) {
1793
+ var n = s.length, i = 0;
1794
+ while (i < n && _isAlpha(_charCodeAt(s, i))) i++;
1795
+ if (i === 0) return 0;
1796
+ if (_charCodeAt(s, i) !== 0x3a || _charCodeAt(s, i + 1) !== 0x2f || _charCodeAt(s, i + 2) !== 0x2f) return 0;
1797
+ i += 3;
1798
+ while (i < n) { var c = _charCodeAt(s, i); if (c === 0x2f || c === 0x3f || c === 0x23) break; i++; }
1799
+ return i;
1800
+ }
1801
+ // Does `s` contain a character NOT in `table`? (A bespoke false-on-empty scan: !pkix.allCharsIn would wrongly
1802
+ // report an empty string as carrying a bad character.)
1803
+ function _hasCharOutside(s, table) { for (var i = 0; i < s.length; i++) { if (!table[_charCodeAt(s, i)]) return true; } return false; }
1804
+ // /%(?![0-9A-Fa-f]{2})/ : a "%" NOT introducing a valid pct-escape (two hex digits).
1805
+ function _hasBadPct(s) { for (var i = 0; i < s.length; i++) { if (_charCodeAt(s, i) === 0x25 && !(_HEX_TBL[_charCodeAt(s, i + 1)] && _HEX_TBL[_charCodeAt(s, i + 2)])) return true; } return false; }
1806
+ // /^[A-Za-z][A-Za-z0-9.-]*$/ : an RFC 8288 reg-rel-type (a letter then letters / digits / "." / "-").
1807
+ function _isRegRelType(t) { var n = t.length; if (n === 0 || !_isAlpha(_charCodeAt(t, 0))) return false; for (var i = 1; i < n; i++) { var c = _charCodeAt(t, i); if (!(_isAlpha(c) || (c >= 48 && c <= 57) || c === 0x2e || c === 0x2d)) return false; } return true; }
1808
+ // Fold every "%2e"/"%2E" in a path segment to "." (that specific dot escape, NOT a general pct-decode); records
1809
+ // whether any fold occurred (the /%2e/i prefilter). Returns { decoded, folded }.
1810
+ function _foldPct2e(seg) { var out = "", i = 0, n = seg.length, folded = false; while (i < n) { if (_charCodeAt(seg, i) === 0x25 && _charCodeAt(seg, i + 1) === 0x32 && (_charCodeAt(seg, i + 2) === 0x65 || _charCodeAt(seg, i + 2) === 0x45)) { out += "."; folded = true; i += 3; } else { out += _charAt(seg, i); i += 1; } } return { decoded: out, folded: folded }; }
1811
+ // inner.replace(/\\(.)/g, "$1") : unescape RFC 7230 quoted-pairs ("\x" -> "x"). "\" + a line terminator is left
1812
+ // intact (the regex "." matches no line terminator), as is a dangling final "\".
1813
+ function _unescapeQuotedPair(inner) {
1814
+ var out = "", i = 0, n = inner.length;
1815
+ while (i < n) {
1816
+ if (_charCodeAt(inner, i) === 0x5c && i + 1 < n) {
1817
+ var nx = _charCodeAt(inner, i + 1);
1818
+ if (nx === 0x0a || nx === 0x0d || nx === 0x2028 || nx === 0x2029) { out += _charAt(inner, i); i += 1; }
1819
+ else { out += _charAt(inner, i + 1); i += 2; }
1820
+ } else { out += _charAt(inner, i); i += 1; }
1821
+ }
1822
+ return out;
1823
+ }
1576
1824
  // Is `t` a valid RFC 8288 sec. 3.3 relation-type -- a reg-rel-type (a letter then LDH, compared case-
1577
1825
  // insensitively) OR an ext-rel-type (an absolute URI: a scheme then ':', AND a canonical RFC 3986 reference so
1578
1826
  // a scheme prefix alone is not enough, e.g. "http:%ZZ" is rejected for its malformed pct-escape)? "@" is neither.
1579
1827
  function _validRelType(t) {
1580
- if (/^[A-Za-z][A-Za-z0-9.-]*$/.test(t)) return true; // reg-rel-type
1828
+ if (_isRegRelType(t)) return true; // reg-rel-type
1581
1829
  // ext-rel-type: a full absolute URI. A relation type is an identifier, never resolved, so a percent-encoded dot
1582
1830
  // (%2e) is a legal char (not a dot-segment); other than that its structure must be a valid absolute URI.
1583
- if (LINK_URI_BADCHAR.test(t)) return false;
1831
+ if (_hasCharOutside(t, _URIREF_TBL)) return false;
1584
1832
  return _validAbsoluteUri(t);
1585
1833
  }
1586
1834
  // Is `t` a valid RFC 3986 ABSOLUTE URI (a relation-type identifier, never connected to)? A scheme, valid pct-
@@ -1591,7 +1839,7 @@ function _validRelType(t) {
1591
1839
  // numeric reg-name "1.2.3.4.5", a 16-bit port cap, IPvFuture, an empty reg-name "foo://user@/"); WHATWG is used
1592
1840
  // only inside _badIpLiteral to validate an IPv6 literal, the one place it is correct.
1593
1841
  function _validAbsoluteUri(t) {
1594
- return /^[A-Za-z][A-Za-z0-9+.-]*:/.test(t) && !LINK_URI_BADPCT.test(t) && !_uriStructurallyInvalid(t);
1842
+ return _hasSchemePrefix(t) && !_hasBadPct(t) && !_uriStructurallyInvalid(t);
1595
1843
  }
1596
1844
  // A URI carries at most ONE "#" (the fragment delimiter); a fragment cannot itself contain "#" (RFC 3986 sec.
1597
1845
  // 3.5), so a second literal "#" is invalid, which WHATWG ACCEPTS by folding the rest into the fragment. A pct-
@@ -1600,8 +1848,11 @@ function _multiFragment(uri) { return uri.split("#").length > 2; }
1600
1848
  // The authority (after "//", optionally after a scheme, up to the next "/?#") carries at most one "@" (userinfo
1601
1849
  // "@" host); a second literal "@" is invalid RFC 3986, which WHATWG ACCEPTS while rewriting the first "@".
1602
1850
  function _authorityMultiAt(uri) {
1603
- var m = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/([^/?#]*)/.exec(uri);
1604
- return !!m && m[1].split("@").length > 2;
1851
+ var a = _uriAuthority(uri, false);
1852
+ if (!a) return false;
1853
+ var cnt = 0;
1854
+ for (var i = 0; i < a.authority.length; i++) { if (_charCodeAt(a.authority, i) === 0x40) cnt++; }
1855
+ return cnt >= 2;
1605
1856
  }
1606
1857
  // A URI whose scheme REQUIRES an authority (or a scheme-relative reference, which inherits one -- always https
1607
1858
  // here) must have a NON-EMPTY authority. A "//" immediately followed by "/", "?", "#", or end is an empty
@@ -1609,14 +1860,14 @@ function _authorityMultiAt(uri) {
1609
1860
  // "////acme.example/p" -> host "acme.example"; "http:///relations" -> host "relations"). A scheme that legitimately
1610
1861
  // allows an empty authority (file:///path, or any non-authority scheme) is NOT flagged.
1611
1862
  function _hasEmptyAuthority(uri) {
1612
- var m = /^([A-Za-z][A-Za-z0-9+.-]*:)?\/\/([^/?#]*)/.exec(uri);
1613
- if (!m) return false;
1614
- var scheme = m[1] ? m[1].slice(0, -1).toLowerCase() : ""; // "" == scheme-relative (inherits the https base)
1863
+ var a = _uriAuthority(uri, false);
1864
+ if (!a) return false;
1865
+ var scheme = a.scheme === "" ? "" : a.scheme.slice(0, -1).toLowerCase(); // "" == scheme-relative (inherits the https base)
1615
1866
  if (scheme !== "" && scheme !== "http" && scheme !== "https" && scheme !== "ws" && scheme !== "wss" && scheme !== "ftp") return false;
1616
1867
  // The HOST itself (the authority with any userinfo and port stripped) must be non-empty -- not only the char
1617
1868
  // right after "//": "https://user@/x" and "https://:443/x" have an empty host after the userinfo / before the
1618
1869
  // port, which WHATWG rejects (or repairs) for a special scheme.
1619
- var host = m[2].slice(m[2].lastIndexOf("@") + 1);
1870
+ var host = a.authority.slice(a.authority.lastIndexOf("@") + 1);
1620
1871
  if (host.charAt(0) !== "[") { var ci = host.indexOf(":"); if (ci !== -1) host = host.slice(0, ci); }
1621
1872
  return host === "";
1622
1873
  }
@@ -1629,32 +1880,43 @@ function _bracketsOnlyInAuthority(uri) {
1629
1880
  // A raw "[" / "]" is NOT permitted in userinfo (RFC 3986 sec. 3.2.1/3.2.2 -- the IP-literal host is the ONLY place
1630
1881
  // square brackets are allowed), so the userinfo class excludes them; otherwise "a[b@[::1]" (which WHATWG repairs
1631
1882
  // to "a%5Bb@[::1]") would anchor on the real host and slip.
1632
- var m = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/(?:[^/?#@[\]]*@)?\[[^[\]]*\]/.exec(uri);
1633
- if (!m) return false;
1634
- var rest = uri.slice(m[0].length);
1883
+ var mlen = _matchBracketAuthPrefix(uri);
1884
+ if (mlen === -1) return false;
1885
+ var rest = _strSlice(uri, mlen);
1635
1886
  if (rest.indexOf("[") !== -1 || rest.indexOf("]") !== -1) return false; // no bracket past the authority
1636
1887
  // After the IP-literal HOST "]", the rest of the authority (up to the next "/?#") may be ONLY an optional numeric
1637
1888
  // ":port". Anything else -- an "@" ("[::1]:80@host" / "[::1]@host", where "[::1]" is a bracketed userinfo, invalid
1638
1889
  // RFC 3986), a non-numeric port, or trailing host text -- means the literal sits in an invalid position, which
1639
1890
  // WHATWG would re-parse to a DIFFERENT host.
1640
- var afterHost = rest.split(/[/?#]/)[0];
1641
- return afterHost === "" || /^:[0-9]*$/.test(afterHost);
1891
+ var afterHost = _strSlice(rest, 0, _firstDelim(rest, 0, 0x2f, 0x3f, 0x23));
1892
+ return afterHost === "" || _isNumericPort(afterHost);
1642
1893
  }
1643
1894
  // A relative-PATH reference (no scheme, no "//" authority) whose FIRST path segment contains a ":" is a
1644
1895
  // path-noscheme violation (RFC 3986 sec. 3.3 / 4.2: the first segment "cannot contain a colon (':') character",
1645
1896
  // as it would be mistaken for a scheme). ":foo" or "1x:y" is such a case, which WHATWG resolves against the base
1646
1897
  // (same-origin) rather than rejecting. A real scheme ("foo:bar") is handled by the scheme/https gates, not here.
1647
1898
  function _relFirstSegHasColon(uri) {
1648
- if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(uri) || uri.indexOf("//") === 0) return false;
1649
- return uri.split(/[/?#]/)[0].indexOf(":") !== -1;
1899
+ if (_hasSchemePrefix(uri) || uri.indexOf("//") === 0) return false;
1900
+ return _uriFirstSegment(uri).indexOf(":") !== -1;
1650
1901
  }
1651
1902
  // The authority port (":" after the host, before the next "/?#") must be *DIGIT (RFC 3986 sec. 3.2.3). A
1652
1903
  // non-numeric port ("host:bad") is malformed -- the character + structural checks otherwise miss it, and WHATWG
1653
1904
  // rejects it only on connect (so a fetched target would be skipped rather than failing closed). An empty port
1654
1905
  // (":") and a missing port are fine.
1655
1906
  function _badPort(uri) {
1656
- var m = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/(?:[^/?#@]*@)?(?:\[[^\]]*\]|[^/?#:]*)(:[^/?#]*)?/.exec(uri);
1657
- return !!(m && m[1] && !/^:[0-9]*$/.test(m[1]));
1907
+ var a = _uriAuthority(uri, false);
1908
+ if (!a) return false;
1909
+ var auth = a.authority, n = auth.length, i = 0, at = -1, hostEnd, e;
1910
+ for (var u = 0; u < n; u++) { if (_charCodeAt(auth, u) === 0x40) { at = u; break; } } // userinfo up to first "@"
1911
+ if (at !== -1) i = at + 1;
1912
+ if (i < n && _charCodeAt(auth, i) === 0x5b) { // IP-literal host "[...]"
1913
+ e = i + 1;
1914
+ while (e < n && _charCodeAt(auth, e) !== 0x5d) e++;
1915
+ if (e < n) hostEnd = e + 1;
1916
+ else { hostEnd = i; while (hostEnd < n && _charCodeAt(auth, hostEnd) !== 0x3a) hostEnd++; }
1917
+ } else { hostEnd = i; while (hostEnd < n && _charCodeAt(auth, hostEnd) !== 0x3a) hostEnd++; } // reg-name up to ":"
1918
+ if (hostEnd >= n || _charCodeAt(auth, hostEnd) !== 0x3a) return false; // no port
1919
+ return !_isNumericPort(_strSlice(auth, hostEnd));
1658
1920
  }
1659
1921
  // An IP-literal host "[...]" must CONTAIN a valid IPv6 address or IPvFuture (RFC 3986 sec. 3.2.2) -- "[not-ip]",
1660
1922
  // "[]", "[garbage]" are malformed, which the bracket-placement check alone accepts (it only confirms the brackets
@@ -1662,10 +1924,10 @@ function _badPort(uri) {
1662
1924
  // failing closed). IPvFuture is checked by grammar; an IPv6 by WHATWG (which parses it correctly). `URL.canParse`
1663
1925
  // never throws, so no swallow.
1664
1926
  function _badIpLiteral(uri) {
1665
- var m = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/(?:[^/?#@]*@)?\[([^\]]*)\]/.exec(uri);
1666
- if (!m) return false;
1667
- if (/^[vV][0-9A-Fa-f]+\.[A-Za-z0-9._~!$&'()*+,;=:-]+$/.test(m[1])) return false; // valid IPvFuture
1668
- return !URL.canParse("http://[" + m[1] + "]/");
1927
+ var interior = _ipLiteralInterior(uri);
1928
+ if (interior === null) return false;
1929
+ if (_isIpvFuture(interior)) return false; // valid IPvFuture
1930
+ return !URL.canParse("http://[" + interior + "]/");
1669
1931
  }
1670
1932
  // The RFC 3986 STRUCTURAL rules a URI-reference must satisfy that WHATWG would otherwise silently REPAIR (accept
1671
1933
  // while rewriting) or reject only on connect: brackets only in an authority IP-literal, a non-empty authority, at
@@ -1681,32 +1943,27 @@ function _uriStructurallyInvalid(uri) {
1681
1943
  // "%61" == "a") and UPPERCASE the hex of any other escape (sec. 6.2.2.1), so equivalent spellings collapse to one
1682
1944
  // signed fetch (CWE-770). fromCharCode keeps the source pure-ASCII while matching a runtime byte.
1683
1945
  function _dedupKey(href) {
1684
- return href.replace(/%[0-9A-Fa-f]{2}/g, function (m) {
1685
- var c = String.fromCharCode(parseInt(m.slice(1), 16));
1686
- return /[A-Za-z0-9._~-]/.test(c) ? c : "%" + m.slice(1).toUpperCase();
1687
- });
1946
+ var out = "", i = 0, n = href.length;
1947
+ while (i < n) {
1948
+ if (_charCodeAt(href, i) === 0x25 && _HEX_TBL[_charCodeAt(href, i + 1)] && _HEX_TBL[_charCodeAt(href, i + 2)]) {
1949
+ var h1 = _charCodeAt(href, i + 1), h2 = _charCodeAt(href, i + 2), v = (_hexVal(h1) << 4) | _hexVal(h2);
1950
+ if (_UNRES_TBL[v]) out += String.fromCharCode(v); // decode an unreserved escape
1951
+ else out += "%" + String.fromCharCode(_upperHexChar(h1)) + String.fromCharCode(_upperHexChar(h2)); // else uppercase the hex
1952
+ i += 3;
1953
+ } else { out += _charAt(href, i); i += 1; }
1954
+ }
1955
+ return out;
1688
1956
  }
1689
- // The INTERIOR of a well-formed RFC 7230 quoted-string (between the DQUOTEs): a sequence of qdtext (any char
1690
- // but `"`/`\`) and quoted-pair (`\` + a char). Rejects an unescaped interior quote (a""b) or a dangling `\`,
1691
- // which first/last-char-is-a-quote alone would miss. Disjoint alternation -> linear, no ReDoS.
1692
- var LINK_QUOTED_INTERIOR = /^(?:[^"\\]|\\.)*$/;
1693
- // A character NOT permitted raw in an RFC 3986 URI-Reference (unreserved / reserved / pct-encode marker). A
1694
- // Link target containing one -- a space, brace, backslash, control char -- is one WHATWG URL parsing would
1695
- // silently repair (trim / percent-encode) on resolution, masking a malformed or off-path value; reject it.
1696
- var LINK_URI_BADCHAR = /[^A-Za-z0-9._~:/?#[\]@!$&'()*+,;=%-]/;
1697
- // A `%` that does NOT introduce a valid RFC 3986 pct-encoded triplet (%HEXDIG HEXDIG): the char allowlist
1698
- // permits `%` for percent-encoding, but `%ZZ` / a trailing `%` is malformed and URL parsing would repair it.
1699
- var LINK_URI_BADPCT = /%(?![0-9A-Fa-f]{2})/;
1700
1957
  // A path SEGMENT that decodes to "." or ".." via a percent-encoded dot (`%2e`): URL resolution decodes `%2e`
1701
1958
  // to `.` for dot-segment removal, so an encoded `..` silently becomes a path traversal that changes the
1702
1959
  // resolved target. Reject only such an encoded dot-SEGMENT -- an encoded dot elsewhere (a filename `0%2ex` ->
1703
1960
  // `0.x`) is a valid target, and a LITERAL `.`/`..` segment resolves transparently. Path only (before ?/#).
1704
1961
  function _hasEncodedDotSegment(uri) {
1705
- var segs = uri.split(/[?#]/)[0].split("/");
1962
+ var segs = _split(_strSlice(uri, 0, _firstDelim(uri, 0, 0x3f, 0x23, -1)), "/");
1706
1963
  for (var _si = 0; _si < segs.length; _si++) {
1707
- if (!/%2e/i.test(segs[_si])) continue;
1708
- var dec = segs[_si].replace(/%2e/ig, ".");
1709
- if (dec === "." || dec === "..") return true;
1964
+ var r = _foldPct2e(segs[_si]);
1965
+ if (!r.folded) continue;
1966
+ if (r.decoded === "." || r.decoded === "..") return true;
1710
1967
  }
1711
1968
  return false;
1712
1969
  }
@@ -1715,7 +1972,7 @@ function _hasEncodedDotSegment(uri) {
1715
1972
  function _linkUriInvalid(uri) {
1716
1973
  // A bracket in a resolved target/anchor is valid only in an authority IP-literal (a same-origin IPv6/IPvFuture
1717
1974
  // cert URL is legal); in a path it is invalid and WHATWG would repair it by percent-encoding.
1718
- return LINK_URI_BADCHAR.test(uri) || LINK_URI_BADPCT.test(uri) || _hasEncodedDotSegment(uri) || _uriStructurallyInvalid(uri);
1975
+ return _hasCharOutside(uri, _URIREF_TBL) || _hasBadPct(uri) || _hasEncodedDotSegment(uri) || _uriStructurallyInvalid(uri);
1719
1976
  }
1720
1977
  // A control octet forbidden in an HTTP field-value (RFC 9110): any C0 control except HTAB, plus DEL. Checked
1721
1978
  // by code point (not a control-char regex) to keep the source pure ASCII and clear of eslint no-control-regex.
@@ -1761,7 +2018,7 @@ function _splitLinkParams(rest) {
1761
2018
  var name = (eq === -1 ? p : _trimOWS(p.slice(0, eq))).toLowerCase();
1762
2019
  // A parameter NAME is a token (RFC 8288 / RFC 7230): reject whitespace / specials rather than accept a
1763
2020
  // malformed "bad name=x" as a silently-ignored parameter that masks the value grammar below.
1764
- if (!LINK_TOKEN.test(name)) throw E("acme/bad-link", "a Link parameter name must be a token (RFC 8288 / RFC 7230): " + JSON.stringify(p));
2021
+ 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));
1765
2022
  // A valueless token parameter is VALID: RFC 8288 link-param = token BWS [ "=" BWS ( token / quoted-string ) ]
1766
2023
  // -- the value is OPTIONAL. Keep it as an empty-valued extension param (it is never `rel`, so it is ignored).
1767
2024
  // (An EMPTY part -- a `;` with no token at all, e.g. `;;` -- was already rejected above.)
@@ -1769,9 +2026,9 @@ function _splitLinkParams(rest) {
1769
2026
  var v = _trimOWS(p.slice(eq + 1));
1770
2027
  if (v.length >= 2 && v.charAt(0) === "\"" && v.charAt(v.length - 1) === "\"") {
1771
2028
  var inner = v.slice(1, -1); // a quoted-string may hold a space-separated list (RFC 8288)
1772
- if (!LINK_QUOTED_INTERIOR.test(inner)) throw E("acme/bad-link", "a quoted Link parameter value is malformed (an unescaped quote or dangling backslash, RFC 7230): " + JSON.stringify(p));
1773
- v = inner.replace(/\\(.)/g, "$1");
1774
- } else if (!LINK_TOKEN.test(v)) {
2029
+ 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));
2030
+ v = _unescapeQuotedPair(inner);
2031
+ } else if (!pkix.allCharsIn(v, _LINK_TOKEN_TABLE)) {
1775
2032
  // An UNQUOTED value is a single NON-EMPTY RFC 7230 token -- no whitespace/specials, and an empty value
1776
2033
  // (`foo=`) is not a token (an empty value must be quoted, `foo=""`). Reject a malformed one rather than
1777
2034
  // split-and-match it as if it were a quoted relation list.
@@ -1800,7 +2057,7 @@ function _parseLinkValue(raw) {
1800
2057
  // After the "<URI>" the remainder is *( OWS ";" OWS link-param ) -- so, past any leading whitespace, it is
1801
2058
  // empty or begins with ';'. Any other trailing text (a param not introduced by ';', e.g. "<uri>rel=...") is
1802
2059
  // malformed and MUST NOT be leniently read as a parameter.
1803
- var rest = s.slice(gt + 1).replace(/^[ \t]+/, "");
2060
+ var rest = _stripLeadingOWS(s, gt + 1);
1804
2061
  if (rest !== "" && rest.charAt(0) !== ";") throw E("acme/bad-link", "a Link value's parameters must be introduced by ';' (RFC 8288): " + JSON.stringify(raw));
1805
2062
  var uri = s.slice(1, gt);
1806
2063
  // A URI-Reference carries only RFC 3986 characters; WHATWG URL parsing would silently REPAIR anything else
@@ -2044,7 +2301,7 @@ function client(directoryUrl, opts) {
2044
2301
  while (nonces.length >= CLIENT_MAX_NONCE_POOL) nonces.shift(); // evict the oldest, bound the pool (CWE-770)
2045
2302
  nonces.push(v);
2046
2303
  }
2047
- function _isProblem(res) { return /application\/problem\+json/i.test(String(res.headers["content-type"] || "")); }
2304
+ function _isProblem(res) { return intrinsic.stringIndexOf(_lower(String(res.headers["content-type"] || "")), "application/problem+json") !== -1; } // ci substring (ASCII content-type)
2048
2305
  function _serverProblem(res) {
2049
2306
  var prob = null;
2050
2307
  try { prob = jose.parseJson(_jsonInput(res)); if (_isObject(prob)) validateProblem(prob); }
@@ -2143,7 +2400,7 @@ function client(directoryUrl, opts) {
2143
2400
  var prob = null;
2144
2401
  try { prob = jose.parseJson(_jsonInput(res)); }
2145
2402
  catch (_e) { /* allow:swallow-unverified a non-JSON problem body is not a badNonce; prob stays null, falling through to the typed acme/server-problem throw */ }
2146
- if (prob && typeof prob.type === "string" && /:badNonce$/.test(prob.type) && attempt < maxNonceRetries) { attempt++; return once(); }
2403
+ if (prob && typeof prob.type === "string" && _endsWith(prob.type, ":badNonce") && attempt < maxNonceRetries) { attempt++; return once(); }
2147
2404
  }
2148
2405
  throw _serverProblem(res);
2149
2406
  }
@@ -2308,11 +2565,11 @@ function client(directoryUrl, opts) {
2308
2565
  function _bindingSpec(opts) {
2309
2566
  var spki = opts.expectedSpki, ids = opts.identifiers, req = opts.requireBinding;
2310
2567
  if (spki !== undefined) {
2311
- if (!_isBuffer(spki)) throw E("acme/bad-input", "downloadCertificate opts.expectedSpki must be a DER SubjectPublicKeyInfo Buffer");
2568
+ if (!guard.bytes.isByteSource(spki)) throw E("acme/bad-input", "downloadCertificate opts.expectedSpki must be a DER SubjectPublicKeyInfo BufferSource");
2312
2569
  // SNAPSHOT: the comparison happens after a network round trip, so a retained reference would let
2313
2570
  // the caller rewrite the key the certificate is checked against once the request had gone out --
2314
2571
  // rejecting the right certificate, or accepting one minted for the replacement bytes.
2315
- spki = guard.bytes.snapshot(spki, AcmeError, "acme/bad-input", "downloadCertificate expectedSpki");
2572
+ spki = guard.bytes.snapshotSource(spki, AcmeError, "acme/bad-input", "downloadCertificate expectedSpki");
2316
2573
  }
2317
2574
  if (ids !== undefined) {
2318
2575
  if (!_isArrayI(ids) || ids.length === 0) throw E("acme/bad-input", "downloadCertificate opts.identifiers must be the non-empty order identifier array");
@@ -2431,7 +2688,7 @@ function client(directoryUrl, opts) {
2431
2688
  // uniform for a .catch caller rather than throwing synchronously.
2432
2689
  return _promised(function () {
2433
2690
  o = _gateOpts(o, _METHOD_OPTS.revoke, "revokeCert");
2434
- if (!Buffer.isBuffer(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate Buffer (opts.certificate)");
2691
+ if (!guard.bytes.isByteSource(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate BufferSource (opts.certificate)");
2435
2692
  // Certificate-key mode (RFC 8555 sec. 7.6) needs the COMPLETE triple certKey + certJwk + certAlg: an
2436
2693
  // incomplete set must NOT fall through to the account-key path (wrong credential), and certAlg must be
2437
2694
  // explicit -- inheriting the account alg would sign a different-family certificate key under the wrong
@@ -2483,7 +2740,7 @@ function client(directoryUrl, opts) {
2483
2740
  return _promised(function () { return _renewalInfoBody(certDer, clockFn, retryAfterCapSeconds); });
2484
2741
  }
2485
2742
  function _renewalInfoBody(certDer, clockFn, retryAfterCapSeconds) {
2486
- if (!Buffer.isBuffer(certDer)) throw E("acme/bad-input", "renewalInfo requires a DER certificate Buffer");
2743
+ if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "renewalInfo requires a DER certificate BufferSource");
2487
2744
  var _rawClk = typeof clockFn === "function" ? clockFn : clock; // renewalWindow may pass a per-call clock
2488
2745
  // The clock MUST return finite epoch ms: a NaN / Infinity would make the expiry comparison below silently
2489
2746
  // false and bypass the RFC 9773 sec. 4.3 gate, so the validating wrapper fails closed on a non-finite value.
@@ -2502,7 +2759,7 @@ function client(directoryUrl, opts) {
2502
2759
  // Append the certID to the PATH, before any query string (RFC 9773): a directory renewalInfo URL may
2503
2760
  // carry a query, so string concatenation would push the certID into the query rather than the path.
2504
2761
  var u = new URL(base);
2505
- u.pathname = u.pathname.replace(/\/+$/, "") + "/" + certId;
2762
+ u.pathname = _stripTrailingSlash(u.pathname) + "/" + certId;
2506
2763
  var url = _clientUrl(u.href);
2507
2764
  return _send("GET", url, { accept: "application/json" }, null).then(function (res) {
2508
2765
  if (res.status !== 200) throw _serverProblem(res);
@@ -2529,7 +2786,7 @@ function client(directoryUrl, opts) {
2529
2786
  function _renewalWindow(certDer, o) {
2530
2787
  return Promise.resolve().then(function () {
2531
2788
  o = _gateOpts(o, _METHOD_OPTS.renewalWindow, "renewalWindow");
2532
- if (!Buffer.isBuffer(certDer)) throw E("acme/bad-input", "renewalWindow requires a DER certificate Buffer");
2789
+ if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "renewalWindow requires a DER certificate BufferSource");
2533
2790
  if (o.random !== undefined && typeof o.random !== "function") throw E("acme/bad-input", "renewalWindow opts.random must be a function returning a number in [0, 1]");
2534
2791
  if (o.clock !== undefined && typeof o.clock !== "function") throw E("acme/bad-input", "renewalWindow opts.clock must be a function returning epoch milliseconds");
2535
2792
  // replaced is the sec. 4.3 "MUST NOT act on a replaced certificate" signal: require a real boolean so a