@blamejs/pki 0.5.30 → 0.5.32

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 (53) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -2
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +97 -33
  5. package/lib/attrcert-sign.js +1 -1
  6. package/lib/cmc-build.js +19 -8
  7. package/lib/cmc-verify.js +4 -4
  8. package/lib/cmp-build.js +36 -6
  9. package/lib/cmp-session.js +5 -5
  10. package/lib/cmp-verify.js +86 -16
  11. package/lib/cms-compress.js +2 -2
  12. package/lib/cms-decrypt.js +3 -3
  13. package/lib/cms-encrypt.js +1 -1
  14. package/lib/cms-sign.js +7 -7
  15. package/lib/cms-verify.js +9 -4
  16. package/lib/crl-sign.js +5 -5
  17. package/lib/crmf-sign.js +2 -1
  18. package/lib/csr-sign.js +1 -4
  19. package/lib/ct.js +8 -4
  20. package/lib/est.js +234 -34
  21. package/lib/framework-error.js +20 -6
  22. package/lib/guard-bytes.js +24 -3
  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/ip-utils.js +102 -25
  28. package/lib/key.js +2 -2
  29. package/lib/lint.js +1 -1
  30. package/lib/mime.js +27 -4
  31. package/lib/ocsp.js +7 -7
  32. package/lib/oid.js +16 -0
  33. package/lib/path-validate.js +76 -18
  34. package/lib/pbes2.js +2 -2
  35. package/lib/pkcs12-build.js +2 -2
  36. package/lib/pki-build.js +320 -21
  37. package/lib/rfc3339.js +75 -17
  38. package/lib/schema-c509.js +36 -9
  39. package/lib/schema-cmc.js +3 -3
  40. package/lib/schema-pkix.js +174 -25
  41. package/lib/schema-tsp.js +10 -1
  42. package/lib/sign-scheme.js +27 -7
  43. package/lib/sigstore.js +52 -9
  44. package/lib/smime.js +86 -6
  45. package/lib/trust.js +114 -14
  46. package/lib/tsp-sign.js +39 -27
  47. package/lib/validator-tpm.js +2 -2
  48. package/lib/webauthn-mds.js +24 -6
  49. package/lib/webauthn.js +3 -3
  50. package/lib/webcrypto.js +1 -1
  51. package/lib/x509-sign.js +9 -5
  52. package/package.json +1 -1
  53. package/sbom.cdx.json +6 -6
@@ -19,6 +19,10 @@ var intrinsic = require("./guard-intrinsic");
19
19
  // replaceable properties to answer one thing, and here it decides whether an auth-param repeats --
20
20
  // answering false lets the second copy of a nonce overwrite the first.
21
21
  var _hasOwn = intrinsic.hasOwn;
22
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
23
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
24
+ var _fromCharCode = intrinsic.fromCharCode;
25
+ var pkix = require("./schema-pkix"); // the shared JS-\s code-point predicate (pkix.isJsWhitespace)
22
26
 
23
27
  // Algorithm registry, a data row and not a switch: rank orders the RFC 7616 sec. 3.7
24
28
  // "most secure the client can use" selection (SHA-512-256 > SHA-256 > MD5); an unknown algorithm ranks 0.
@@ -41,7 +45,12 @@ var DEFAULT_CODES = {
41
45
  // is hashed over its exact bytes. KD(secret, data) = H(secret ":" data).
42
46
  function H(hash, s) { return crypto.createHash(hash).update(s, "latin1").digest("hex"); }
43
47
  function KD(hash, secret, data) { return H(hash, secret + ":" + data); }
44
- function _qstr(v) { return "\"" + String(v).replace(/(["\\])/g, "\\$1") + "\""; }
48
+ // Quote a value: wrap in double quotes, backslash-escaping any " or \ (matching /(["\\])/g -> "\\$1").
49
+ function _qstr(v) {
50
+ var s = String(v), out = "";
51
+ for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c === 34 || c === 92) out += "\\"; out += _fromCharCode(c); }
52
+ return "\"" + out + "\"";
53
+ }
45
54
 
46
55
  // Split a header at commas outside a quoted-string (a quoted comma is a literal, not a delimiter).
47
56
  function _commaSplitOutsideQuotes(s) {
@@ -69,7 +78,13 @@ function _firstEqOutsideQuotes(s) {
69
78
  }
70
79
  return -1;
71
80
  }
72
- function _unq(s) { return s.slice(1, -1).replace(/\\(.)/g, "$1"); } // only ever called on a well-formed quoted-string (see _closedQuote)
81
+ // Unquote a well-formed quoted-string (only ever called after _closedQuote): drop the surrounding
82
+ // quotes and each escaping backslash (matching s.slice(1,-1).replace(/\\(.)/g, "$1")).
83
+ function _unq(s) {
84
+ var out = "", last = 1, n = s.length; // skip the opening quote at 0
85
+ for (var i = 1; i < n - 1; i++) { if (_charCodeAt(s, i) === 92) { out += _strSlice(s, last, i); last = i + 1; i += 1; } }
86
+ return out + _strSlice(s, last, n - 1); // trailing content, excluding the closing quote
87
+ }
73
88
  // A well-formed quoted-string: opens with ", closes with an unescaped " as the last character (nothing trails
74
89
  // it). An unterminated / trailing-garbage quoted-string is malformed (RFC 7230 sec. 3.2.6) and rejected.
75
90
  function _closedQuote(s) {
@@ -86,24 +101,62 @@ function _closedQuote(s) {
86
101
  // A control octet (RFC 7230 sec. 3.2.6 quoted-string / qdtext forbids CTL except HTAB). Scanned with charCodeAt
87
102
  // instead of a control-char regex literal (which eslint no-control-regex correctly refuses).
88
103
  function _hasCtl(s) {
89
- for (var i = 0; i < s.length; i++) { var c = s.charCodeAt(i); if ((c < 0x20 && c !== 0x09) || c === 0x7f) return true; }
104
+ for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if ((c < 0x20 && c !== 0x09) || c === 0x7f) return true; }
90
105
  return false;
91
106
  }
92
107
 
93
108
  // Split a WWW-Authenticate header into [{ scheme, paramText }]. A comma-separated segment whose pre-'='
94
109
  // text is "token WS token" (a scheme name before the first auth-param key), or a bare token with no '=',
95
110
  // STARTS a new challenge; a "key=value" segment is a param continuation of the current challenge.
111
+ // The leading token when `pre` is "token WS more" (/^(\S+)\s+(\S[\s\S]*)$/) -- a scheme name before the
112
+ // first auth-param -- else null. \s is the shared JS-\s predicate (RFC 7230 OWS is a subset).
113
+ function _matchTokenRest(pre) {
114
+ var n = pre.length, p = 0;
115
+ while (p < n && !pkix.isJsWhitespace(_charCodeAt(pre, p))) p += 1; // ^(\S+)
116
+ if (p === 0) return null;
117
+ var tokEnd = p;
118
+ if (p >= n || !pkix.isJsWhitespace(_charCodeAt(pre, p))) return null; // \s+
119
+ while (p < n && pkix.isJsWhitespace(_charCodeAt(pre, p))) p += 1;
120
+ if (p >= n || pkix.isJsWhitespace(_charCodeAt(pre, p))) return null; // (\S[\s\S]*)$ -- at least one non-\s
121
+ return _strSlice(pre, 0, tokEnd);
122
+ }
123
+ // The index past a leading "OWS token OWS" (/^\s*\S+\s+/), or 0 when the string does not begin that way.
124
+ // The caller slices from this index (the slice that replaces the removed .replace).
125
+ function _stripTokenIndex(seg) {
126
+ var n = seg.length, p = 0;
127
+ while (p < n && pkix.isJsWhitespace(_charCodeAt(seg, p))) p += 1; // ^\s*
128
+ var ts = p;
129
+ while (p < n && !pkix.isJsWhitespace(_charCodeAt(seg, p))) p += 1; // \S+
130
+ if (p === ts) return 0;
131
+ var ws = p;
132
+ while (p < n && pkix.isJsWhitespace(_charCodeAt(seg, p))) p += 1; // \s+ (required)
133
+ return p === ws ? 0 : p;
134
+ }
135
+ // String.prototype.split(/\s+/): the pieces between \s runs (a leading/trailing run yields a "" element).
136
+ function _splitWhitespace(s) {
137
+ var out = [], i = 0, n = s.length, ps = 0;
138
+ while (i < n) {
139
+ if (pkix.isJsWhitespace(_charCodeAt(s, i))) {
140
+ out[out.length] = _strSlice(s, ps, i);
141
+ while (i < n && pkix.isJsWhitespace(_charCodeAt(s, i))) i += 1;
142
+ ps = i;
143
+ } else i += 1;
144
+ }
145
+ out[out.length] = _strSlice(s, ps);
146
+ return out;
147
+ }
148
+
96
149
  function _splitChallenges(s) {
97
150
  var segs = _commaSplitOutsideQuotes(s);
98
151
  var out = [], cur = null;
99
152
  for (var i = 0; i < segs.length; i++) {
100
- var seg = segs[i].replace(/^\s+|\s+$/g, "");
153
+ var seg = segs[i].trim();
101
154
  if (seg === "") continue;
102
155
  var eq = _firstEqOutsideQuotes(seg);
103
- var pre = (eq < 0 ? seg : seg.slice(0, eq)).replace(/^\s+|\s+$/g, "");
104
- var ws = /^(\S+)\s+(\S[\s\S]*)$/.exec(pre);
156
+ var pre = (eq < 0 ? seg : _strSlice(seg, 0, eq)).trim();
157
+ var scheme = _matchTokenRest(pre);
105
158
  if (eq < 0) { cur = { scheme: seg, paramText: "" }; out.push(cur); } // a bare scheme token (e.g. "Basic")
106
- else if (ws) { cur = { scheme: ws[1], paramText: seg.replace(/^\s*\S+\s+/, "") }; out.push(cur); } // "scheme firstKey=..."
159
+ else if (scheme !== null) { cur = { scheme: scheme, paramText: _strSlice(seg, _stripTokenIndex(seg)) }; out.push(cur); } // "scheme firstKey=..."
107
160
  else if (cur) { cur.paramText = cur.paramText ? cur.paramText + "," + seg : seg; } // "key=value" continuation
108
161
  }
109
162
  return out;
@@ -112,12 +165,12 @@ function _splitChallenges(s) {
112
165
  function _parseParams(paramText, E, code) {
113
166
  var segs = _commaSplitOutsideQuotes(paramText), map = Object.create(null);
114
167
  for (var i = 0; i < segs.length; i++) {
115
- var seg = segs[i].replace(/^\s+|\s+$/g, "");
168
+ var seg = segs[i].trim();
116
169
  if (seg === "") continue;
117
170
  var eq = _firstEqOutsideQuotes(seg);
118
171
  if (eq < 0) throw E(code, "malformed Digest auth-param (no '='): " + JSON.stringify(seg));
119
- var key = seg.slice(0, eq).replace(/^\s+|\s+$/g, "").toLowerCase();
120
- var rawVal = seg.slice(eq + 1).replace(/^\s+|\s+$/g, "");
172
+ var key = _strSlice(seg, 0, eq).trim().toLowerCase();
173
+ var rawVal = _strSlice(seg, eq + 1).trim();
121
174
  var quoted = rawVal.charAt(0) === "\"";
122
175
  if (quoted && !_closedQuote(rawVal)) throw E(code, "an unterminated or trailing-garbage Digest quoted-string (RFC 7230 sec. 3.2.6)");
123
176
  if (_hasOwn(map, key)) throw E(code, "repeated Digest auth-param " + JSON.stringify(key));
@@ -138,7 +191,7 @@ function _validateDigest(paramText, E, code) {
138
191
  var qop = [];
139
192
  if (p.qop) {
140
193
  if (!p.qop.quoted) throw E(code, "the Digest qop must be a quoted list (RFC 7616 sec. 3.3)");
141
- qop = p.qop.value.split(",").map(function (x) { return x.replace(/^\s+|\s+$/g, "").toLowerCase(); }).filter(Boolean);
194
+ qop = p.qop.value.split(",").map(function (x) { return x.trim().toLowerCase(); }).filter(Boolean);
142
195
  // A qop directive that is present but lists no value (qop="" / qop=", ,") is malformed, and is not the
143
196
  // absent-qop (RFC 2069) case. Reject it instead of silently collapsing to a no-qop response the server
144
197
  // cannot accept (RFC 7616 sec. 3.3). Only an omitted qop directive selects the legacy no-qop path.
@@ -150,8 +203,8 @@ function _validateDigest(paramText, E, code) {
150
203
  // server intended. Surfaced as an array of the listed URIs, or null when omitted or quoted-empty, which per
151
204
  // the RFC means "all URIs on the responding server" (the answer may be reused for any same-origin URI, sec. 3.5).
152
205
  if (p.domain && !p.domain.quoted) throw E(code, "the Digest domain must be a quoted-string (RFC 7616 sec. 3.3)");
153
- var domain = (p.domain && p.domain.value.replace(/^\s+|\s+$/g, "") !== "")
154
- ? p.domain.value.replace(/^\s+|\s+$/g, "").split(/\s+/) : null;
206
+ var domain = (p.domain && p.domain.value.trim() !== "")
207
+ ? _splitWhitespace(p.domain.value.trim()) : null;
155
208
  // charset (RFC 7616 sec. 3.3): the one permitted value is the unquoted token "UTF-8". A quoted charset, or any
156
209
  // other value, is malformed, since answering it would hash the credentials in the wrong encoding, so it is
157
210
  // rejected (and thus skipped during multi-offer selection in favor of a conforming offer).
@@ -250,7 +303,7 @@ function _octets(s, charset) {
250
303
  // Does the string carry any non-ASCII code unit (> U+007F)? Such a UTF-8 username is sent via `username*`.
251
304
  function _hasNonAscii(s) {
252
305
  s = String(s == null ? "" : s);
253
- for (var i = 0; i < s.length; i++) { if (s.charCodeAt(i) > 0x7F) return true; }
306
+ for (var i = 0; i < s.length; i++) { if (_charCodeAt(s, i) > 0x7F) return true; }
254
307
  return false;
255
308
  }
256
309
 
@@ -264,7 +317,7 @@ function _pctEncodeUtf8(s) {
264
317
  if ((b >= 0x30 && b <= 0x39) || (b >= 0x41 && b <= 0x5A) || (b >= 0x61 && b <= 0x7A) ||
265
318
  b === 0x21 || b === 0x23 || b === 0x24 || b === 0x26 || b === 0x2B || b === 0x2D ||
266
319
  b === 0x2E || b === 0x5E || b === 0x5F || b === 0x60 || b === 0x7C || b === 0x7E) {
267
- out += String.fromCharCode(b);
320
+ out += _fromCharCode(b);
268
321
  } else {
269
322
  out += "%" + (b < 0x10 ? "0" : "") + b.toString(16).toUpperCase();
270
323
  }
@@ -323,7 +376,7 @@ function answer(challenge, params, E) {
323
376
  // (a same-origin redirect, an auth-int retry) increments nc; a fresh nonce restarts at 1. The caller supplies
324
377
  // the count for this nonce (default 1); formatted as 8 lowercase hex.
325
378
  var ncNum = (typeof params.nc === "number" && params.nc >= 1) ? Math.floor(params.nc) : 1;
326
- var nc = ("0000000" + ncNum.toString(16)).slice(-8);
379
+ var nc = _strSlice("0000000" + ncNum.toString(16), -8);
327
380
  var response = useQop
328
381
  ? KD(alg.hash, HA1, nonce + ":" + nc + ":" + cnonce + ":" + useQop + ":" + HA2)
329
382
  : KD(alg.hash, HA1, nonce + ":" + HA2);
@@ -19,12 +19,32 @@ var MAX_RETRY_AFTER_SECONDS = constants.TIME.days(365) / constants.TIME.seconds(
19
19
  // rfc850-date / asctime-date a recipient must still accept. Gating Date.parse on this grammar keeps its
20
20
  // permissiveness (an ISO string like "2026-07-10") from passing as a valid Retry-After header.
21
21
  var MONTHS = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" ");
22
- var MONTH = "(?:" + MONTHS.join("|") + ")";
23
- var HTTP_DATE_FORMS = [
24
- new RegExp("^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \\d{2} " + MONTH + " \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$"),
25
- new RegExp("^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \\d{2}-" + MONTH + "-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT$"),
26
- new RegExp("^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) " + MONTH + " [ \\d]\\d \\d{2}:\\d{2}:\\d{2} \\d{4}$"),
27
- ];
22
+ var DOW_SHORT = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
23
+ var DOW_LONG = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
24
+
25
+ // The grammar is scanned character by character (no regex): each form is a fixed layout of a
26
+ // day-of-week name, delimiters, an all-digit field, a three-letter month name, and an HH:MM:SS time.
27
+ function _isDigit(ch) { return ch >= "0" && ch <= "9"; }
28
+ // The two-digit / four-digit integer at `i`, or -1 when those positions are not all digits.
29
+ function _digits2(s, i) { return _isDigit(s.charAt(i)) && _isDigit(s.charAt(i + 1)) ? (s.charCodeAt(i) - 48) * 10 + (s.charCodeAt(i + 1) - 48) : -1; }
30
+ function _digits4(s, i) { for (var k = 0; k < 4; k++) if (!_isDigit(s.charAt(i + k))) return -1; return parseInt(s.slice(i, i + 4), 10); }
31
+ // The length of a name from `list` matching `s` at `i` (the day-of-week alternation), or -1.
32
+ function _matchName(s, i, list) { for (var j = 0; j < list.length; j++) { if (s.slice(i, i + list[j].length) === list[j]) return list[j].length; } return -1; }
33
+ // The three-letter month at `i` when it is a real month name, else null.
34
+ function _monthAt(s, i) { return _matchName(s, i, MONTHS) === -1 ? null : s.slice(i, i + 3); }
35
+ // An "HH:MM:SS" time at `i` -> { hh, mi, ss, next } (next = i + 8), or null.
36
+ function _timeAt(s, i) {
37
+ var hh = _digits2(s, i); if (hh < 0 || s.charAt(i + 2) !== ":") return null;
38
+ var mi = _digits2(s, i + 3); if (mi < 0 || s.charAt(i + 5) !== ":") return null;
39
+ var ss = _digits2(s, i + 6); if (ss < 0) return null;
40
+ return { hh: hh, mi: mi, ss: ss, next: i + 8 };
41
+ }
42
+ // A non-empty run of ASCII digits (matching /^\d+$/, the delay-seconds form).
43
+ function _isAllDigits(s) {
44
+ if (s.length === 0) return false;
45
+ for (var i = 0; i < s.length; i++) if (!_isDigit(s.charAt(i))) return false;
46
+ return true;
47
+ }
28
48
 
29
49
  // Expand an obsolete RFC 850 two-digit year: HTTP interprets it relative to the receipt year, as the full
30
50
  // year with these two digits within [refYear-50, refYear+49] (moved to the past only when it would
@@ -40,29 +60,53 @@ function _rfc850Year(yy, refMs) {
40
60
  return full;
41
61
  }
42
62
 
63
+ // IMF-fixdate (the required form): "Sun, 06 Nov 1994 08:49:37 GMT".
64
+ function _scanImf(s) {
65
+ var p = _matchName(s, 0, DOW_SHORT);
66
+ if (p === -1 || s.slice(p, p + 2) !== ", ") return null; p += 2;
67
+ var dd = _digits2(s, p); if (dd < 0 || s.charAt(p + 2) !== " ") return null; p += 3;
68
+ var mo = _monthAt(s, p); if (!mo || s.charAt(p + 3) !== " ") return null; p += 4;
69
+ var yy = _digits4(s, p); if (yy < 0 || s.charAt(p + 4) !== " ") return null; p += 5;
70
+ var t = _timeAt(s, p); if (!t || s.slice(t.next, t.next + 4) !== " GMT" || t.next + 4 !== s.length) return null;
71
+ return { day: dd, monName: mo, year: yy, hh: t.hh, mi: t.mi, ss: t.ss };
72
+ }
73
+ // rfc850-date (obsolete, still accepted): "Sunday, 06-Nov-94 08:49:37 GMT" -- a two-digit year expanded
74
+ // relative to the receipt year.
75
+ function _scanRfc850(s, refMs) {
76
+ var p = _matchName(s, 0, DOW_LONG);
77
+ if (p === -1 || s.slice(p, p + 2) !== ", ") return null; p += 2;
78
+ var dd = _digits2(s, p); if (dd < 0 || s.charAt(p + 2) !== "-") return null; p += 3;
79
+ var mo = _monthAt(s, p); if (!mo || s.charAt(p + 3) !== "-") return null; p += 4;
80
+ var yy = _digits2(s, p); if (yy < 0 || s.charAt(p + 2) !== " ") return null; p += 3;
81
+ var t = _timeAt(s, p); if (!t || s.slice(t.next, t.next + 4) !== " GMT" || t.next + 4 !== s.length) return null;
82
+ return { day: dd, monName: mo, year: _rfc850Year(yy, refMs), hh: t.hh, mi: t.mi, ss: t.ss };
83
+ }
84
+ // asctime-date (obsolete, still accepted): "Sun Nov 6 08:49:37 1994" -- a space-padded one/two-digit day.
85
+ function _scanAsctime(s) {
86
+ var p = _matchName(s, 0, DOW_SHORT);
87
+ if (p === -1 || s.charAt(p) !== " ") return null; p += 1;
88
+ var mo = _monthAt(s, p); if (!mo || s.charAt(p + 3) !== " ") return null; p += 4;
89
+ var c0 = s.charAt(p);
90
+ if (!(c0 === " " || _isDigit(c0)) || !_isDigit(s.charAt(p + 1))) return null;
91
+ var dd = parseInt(s.slice(p, p + 2), 10); if (s.charAt(p + 2) !== " ") return null; p += 3;
92
+ var t = _timeAt(s, p); if (!t || s.charAt(t.next) !== " ") return null;
93
+ var yy = _digits4(s, t.next + 1); if (yy < 0 || t.next + 1 + 4 !== s.length) return null;
94
+ return { day: dd, monName: mo, year: yy, hh: t.hh, mi: t.mi, ss: t.ss };
95
+ }
96
+
43
97
  // Parse an HTTP-date to epoch ms, or NaN when its grammar, calendar, or time is invalid. Built from the
44
98
  // extracted fields via Date.UTC (never Date.parse): every HTTP-date is UTC, the asctime form carries
45
99
  // no GMT token so delegating would parse it in local time, and the round-trip check rejects an
46
100
  // impossible date / time (Date.UTC normalizes Feb 31 -> Mar 2 just as Date.parse would).
47
101
  function httpDateMs(s, refMs) {
48
- if (!HTTP_DATE_FORMS.some(function (re) { return re.test(s); })) return NaN;
49
- var day, monName, year;
50
- var asc = /^\w{3} (\w{3}) ([ \d]\d) /.exec(s); // asctime: month then day
51
- if (asc) { monName = asc[1]; day = parseInt(asc[2], 10); year = parseInt(/(\d{4})$/.exec(s)[1], 10); }
52
- else {
53
- var dmy = /(\d{2})[ -](\w{3})[ -](\d{2,4})/.exec(s); // IMF / rfc850: day month year
54
- if (!dmy) return NaN;
55
- day = parseInt(dmy[1], 10); monName = dmy[2]; year = parseInt(dmy[3], 10);
56
- if (year < 100) year = _rfc850Year(year, refMs); // rfc850 two-digit year
57
- }
58
- var t = /(\d{2}):(\d{2}):(\d{2})/.exec(s);
59
- var mon = MONTHS.indexOf(monName);
60
- if (!t || mon < 0) return NaN;
61
- var hh = parseInt(t[1], 10), mi = parseInt(t[2], 10), ss = parseInt(t[3], 10);
62
- var when = Date.UTC(year, mon, day, hh, mi, ss);
102
+ var f = _scanImf(s) || _scanRfc850(s, refMs) || _scanAsctime(s);
103
+ if (!f) return NaN;
104
+ var mon = MONTHS.indexOf(f.monName);
105
+ if (mon < 0) return NaN;
106
+ var when = Date.UTC(f.year, mon, f.day, f.hh, f.mi, f.ss);
63
107
  var d = new Date(when);
64
- if (d.getUTCDate() !== day || d.getUTCMonth() !== mon || d.getUTCFullYear() !== year ||
65
- d.getUTCHours() !== hh || d.getUTCMinutes() !== mi || d.getUTCSeconds() !== ss) return NaN;
108
+ if (d.getUTCDate() !== f.day || d.getUTCMonth() !== mon || d.getUTCFullYear() !== f.year ||
109
+ d.getUTCHours() !== f.hh || d.getUTCMinutes() !== f.mi || d.getUTCSeconds() !== f.ss) return NaN;
66
110
  return when;
67
111
  }
68
112
 
@@ -78,7 +122,7 @@ function parse(value, opts) {
78
122
  opts = opts || {};
79
123
  var raStr = String(value).trim();
80
124
  var out = { retryAfterSeconds: null, retryAfterDate: null };
81
- if (/^\d+$/.test(raStr)) {
125
+ if (_isAllDigits(raStr)) {
82
126
  var n = parseInt(raStr, 10);
83
127
  // opts.cap (seconds): a caller that will clamp anyway (e.g. an ARI poll cadence) wants a delay above its
84
128
  // ceiling reduced to the ceiling, not rejected, so a valid-but-huge value never discards the response.
@@ -69,14 +69,38 @@ function _budget(value, key, dflt, max, E, code) {
69
69
  // form (e.g. what pki.est.cacerts / parseCertsOnly return), is silently ignored there, so the
70
70
  // connection would fail server authentication despite a valid anchor. Wrap a DER anchor as PEM
71
71
  // (a Buffer already carrying PEM text, or a PEM string, passes through unchanged).
72
+ // The JS regex \s code points representable in a latin1 string (0..255): TAB LF VT FF CR SP NBSP.
73
+ // Built at load so the source stays pure ASCII. The armor scan runs over a latin1 prefix, where these
74
+ // are the only \s characters that can appear, so this is exactly /^\s*/ for that input.
75
+ var _LATIN1_WS = String.fromCharCode(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0xa0);
76
+
77
+ // PEM armor at the START after optional whitespace, decided by character scan (no regex): skip the
78
+ // leading \s run, then require "-----BEGIN ".
79
+ function _looksLikePemArmor(s) {
80
+ var i = 0;
81
+ while (i < s.length && _LATIN1_WS.indexOf(s.charAt(i)) !== -1) i += 1;
82
+ return s.slice(i, i + 11) === "-----BEGIN ";
83
+ }
84
+ // Base64 wrapped to <=64-column lines, each terminated by LF (matching /.{1,64}/g -> "$&\n", which
85
+ // keeps a trailing LF on the final line).
86
+ function _wrap64Lines(s) {
87
+ var out = "";
88
+ for (var i = 0; i < s.length; i += 64) out += s.slice(i, i + 64) + "\n";
89
+ return out;
90
+ }
91
+ // The surrounding "[" "]" of an IPv6 literal removed, else the string unchanged (matching /^\[(.*)\]$/).
92
+ function _stripIpv6Brackets(s) {
93
+ return (s.length >= 2 && s.charAt(0) === "[" && s.charAt(s.length - 1) === "]") ? s.slice(1, s.length - 1) : s;
94
+ }
95
+
72
96
  function _pemifyAnchor(a) {
73
97
  if (typeof a === "string") return a;
74
98
  if (Buffer.isBuffer(a)) {
75
99
  // Detect PEM by its armor at the START (after optional whitespace) only: a DER certificate begins
76
100
  // with the SEQUENCE tag 0x30 and may itself contain the bytes "-----BEGIN" in a subject / extension
77
101
  // value, so an anywhere-search would leave such a DER anchor unwrapped and unusable.
78
- if (/^\s*-----BEGIN /.test(a.toString("latin1", 0, 64))) return a;
79
- var b64 = a.toString("base64").replace(/.{1,64}/g, "$&\n");
102
+ if (_looksLikePemArmor(a.toString("latin1", 0, 64))) return a;
103
+ var b64 = _wrap64Lines(a.toString("base64"));
80
104
  return "-----BEGIN CERTIFICATE-----\n" + b64 + "-----END CERTIFICATE-----\n";
81
105
  }
82
106
  return a;
@@ -179,11 +203,41 @@ var _guardedLookup = _makeGuardedLookup(nodeDns.lookup);
179
203
  // certificate / identity / handshake failure is a server-authentication failure;
180
204
  // anything else is a generic transport error. Every arm threads the raw fault as
181
205
  // `.cause`, so the diagnostic survives.
206
+ // SELF.?SIGNED : "self", an optional single non-line-terminator character, then "signed" (in a lowercased string).
207
+ function _hasSelfSigned(l) {
208
+ for (var i = 0; i + 4 <= l.length; i++) {
209
+ if (l.charCodeAt(i) === 0x73 && l.charCodeAt(i + 1) === 0x65 && l.charCodeAt(i + 2) === 0x6c && l.charCodeAt(i + 3) === 0x66) {
210
+ if (l.indexOf("signed", i + 4) === i + 4) return true;
211
+ var mid = l.charCodeAt(i + 4);
212
+ if (i + 4 < l.length && mid !== 0x0a && mid !== 0x0d && mid !== 0x2028 && mid !== 0x2029 && l.indexOf("signed", i + 5) === i + 5) return true;
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+ // \bWORD\b : `word` (lowercase) bounded on both sides by a non-word character ([a-z0-9_]) or a string edge.
218
+ function _hasWordBounded(l, word) {
219
+ var at = l.indexOf(word);
220
+ while (at !== -1) {
221
+ var before = at === 0 || !_isLowerWordChar(l.charCodeAt(at - 1));
222
+ var afterI = at + word.length;
223
+ var after = afterI === l.length || !_isLowerWordChar(l.charCodeAt(afterI));
224
+ if (before && after) return true;
225
+ at = l.indexOf(word, at + 1);
226
+ }
227
+ return false;
228
+ }
229
+ function _isLowerWordChar(c) { return (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c === 95; }
182
230
  function _classifyError(e, C) {
183
231
  if (e && e.pkiBlockedAddress) return C("blocked-address");
184
232
  var s = String((e && e.code) || "") + " " + String((e && e.message) || "");
185
- if (/PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL|VERSION_TOO_LOW|WRONG_VERSION|NO_PROTOCOLS_AVAILABLE|INAPPROPRIATE_FALLBACK/i.test(s)) return C("tls-floor");
186
- if (/CERT|SELF.?SIGNED|VERIFY|ALTNAME|HOSTNAME|DEPTH_ZERO|LOCAL_ISSUER|HANDSHAKE|\bSSL\b|\bTLS\b/i.test(s)) return C("server-auth-failed");
233
+ var l = s.toLowerCase();
234
+ // /PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL|VERSION_TOO_LOW|WRONG_VERSION|NO_PROTOCOLS_AVAILABLE|INAPPROPRIATE_FALLBACK/i
235
+ if (l.indexOf("protocol_version") !== -1 || l.indexOf("unsupported_protocol") !== -1 || l.indexOf("version_too_low") !== -1 ||
236
+ l.indexOf("wrong_version") !== -1 || l.indexOf("no_protocols_available") !== -1 || l.indexOf("inappropriate_fallback") !== -1) return C("tls-floor");
237
+ // /CERT|SELF.?SIGNED|VERIFY|ALTNAME|HOSTNAME|DEPTH_ZERO|LOCAL_ISSUER|HANDSHAKE|\bSSL\b|\bTLS\b/i
238
+ if (l.indexOf("cert") !== -1 || l.indexOf("verify") !== -1 || l.indexOf("altname") !== -1 || l.indexOf("hostname") !== -1 ||
239
+ l.indexOf("depth_zero") !== -1 || l.indexOf("local_issuer") !== -1 || l.indexOf("handshake") !== -1 ||
240
+ _hasSelfSigned(l) || _hasWordBounded(l, "ssl") || _hasWordBounded(l, "tls")) return C("server-auth-failed");
187
241
  return C("transport-error");
188
242
  }
189
243
 
@@ -256,7 +310,7 @@ function httpsTransport(defaults) {
256
310
  if (minVersion !== "TLSv1.2" && minVersion !== "TLSv1.3") throw E(C("bad-input"), "tls.minVersion must be 'TLSv1.2' or 'TLSv1.3' (never below the floor), got " + minVersion);
257
311
 
258
312
  var body = request.body;
259
- if (Buffer.isBuffer(body)) body = guard.bytes.view(body, E, C("bad-input"), "the request body");
313
+ if (guard.bytes.isByteSource(body)) body = guard.bytes.source(body, E, C("bad-input"), "the request body");
260
314
 
261
315
  // Frame a body-bearing request with a fixed Content-Length computed from the EXACT body, so it is sent
262
316
  // length-delimited rather than Transfer-Encoding: chunked (node's default when a body is written with no
@@ -273,7 +327,7 @@ function httpsTransport(defaults) {
273
327
 
274
328
  // URL.hostname keeps the square brackets on an IPv6 literal ([2001:db8::1]); node's `hostname`
275
329
  // option and its IP-detection expect the bare address, so strip the brackets once here.
276
- var host = url.hostname.replace(/^\[(.*)\]$/, "$1");
330
+ var host = _stripIpv6Brackets(url.hostname);
277
331
  var options = {
278
332
  method: request.method || "GET",
279
333
  hostname: host,
package/lib/inspect.js CHANGED
@@ -121,7 +121,7 @@ function _serial(cert, indent) {
121
121
  // anything larger renders as a colon-hex block.
122
122
  if (buf.length <= 6) {
123
123
  var n = parseInt(buf.toString("hex") || "0", 16);
124
- return "Serial Number: " + n + " (0x" + (buf.toString("hex").replace(/^0+/, "") || "0") + ")";
124
+ return "Serial Number: " + n + " (0x" + (_stripLeadingZeros(buf.toString("hex")) || "0") + ")";
125
125
  }
126
126
  return "Serial Number:\n" + " ".repeat(indent) + _hexColon(buf, {});
127
127
  }
@@ -905,11 +905,22 @@ _CMS_SHAPE[oid.byName("compressedData")] = function (o) { return _isObj(o.compre
905
905
  // SEQUENCE is a real Certificate/CertificateList -> delegate to the full sub-report
906
906
  // (guarded; a one-line summary on any failure); a CONTEXT-tagged CHOICE alternative
907
907
  // (attribute certificate / other) renders a one-line tag+size summary, never parsed.
908
+ // Leading "0" characters removed, and a single trailing "\n" removed -- both by character scan
909
+ // (no regex): display helpers, matching the /^0+/ and /\n$/ replacements they succeed.
910
+ function _stripLeadingZeros(s) {
911
+ var i = 0;
912
+ while (i < s.length && s.charAt(i) === "0") i += 1;
913
+ return s.slice(i);
914
+ }
915
+ function _stripTrailingNewline(s) {
916
+ return (s.length > 0 && s.charAt(s.length - 1) === "\n") ? s.slice(0, s.length - 1) : s;
917
+ }
918
+
908
919
  function _cmsEmbedded(kind, el, pad) {
909
920
  if (el.tagClass === "universal") {
910
921
  try {
911
922
  var sub = (kind === "CRL") ? crlReport(el.bytes) : certificate(el.bytes);
912
- return pad + kind + ":\n" + sub.replace(/\n$/, "").split("\n").map(function (l) { return pad + " " + l; }).join("\n");
923
+ return pad + kind + ":\n" + _stripTrailingNewline(sub).split("\n").map(function (l) { return pad + " " + l; }).join("\n");
913
924
  } catch (_e) { /* fall through to the summary */ }
914
925
  }
915
926
  return pad + kind + " [" + el.tagClass + " " + el.tagNumber + "] (" + el.bytes.length + " bytes)";
package/lib/ip-utils.js CHANGED
@@ -3,8 +3,8 @@
3
3
  "use strict";
4
4
  //
5
5
  // @internal -- no operator-facing namespace. Textual IP-literal validation, shared so a
6
- // consumer (pki.lint's commonName-in-SAN check today; a future SAN iPAddress / name-
7
- // constraint surface) does not hand-roll a partial regex that misses a valid IPv6 form.
6
+ // consumer (pki.lint's commonName-in-SAN check today; the SAN iPAddress GeneralName shorthand
7
+ // and name-constraint surfaces) does not hand-roll a partial regex that misses a valid IPv6 form.
8
8
  //
9
9
  // The IPv4 grammar is strict per-octet 0-255 (RFC 791); the IPv6 parser follows RFC 4291
10
10
  // sec. 2.2 (8 groups of 1-4 hex, one "::" run compressing a contiguous zero run) plus the
@@ -12,43 +12,116 @@
12
12
  // This mirrors the vetted validation in the sibling blamejs framework (lib/ip-utils.js);
13
13
  // this toolkit keeps its own copy, since Hard rule #1 rules out a runtime dependency.
14
14
 
15
- // Strict RFC 791 dotted-quad: four 0-255 octets. Anchored + per-octet repeat-capped (no
16
- // ReDoS on unbounded input).
17
- var IPV4_RE = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
18
- function isIPv4(s) { return typeof s === "string" && IPV4_RE.test(s); }
15
+ // This parser uses no regular expression. packIpLiteral gates a GeneralName form decision (iPAddress
16
+ // vs dNSName), and a regex is matched through a live, replaceable protocol -- RegExp.prototype.exec /
17
+ // test and String.prototype.match / split consult the regex's own `exec` and the `[Symbol.match]` /
18
+ // `[Symbol.split]` hooks, and RegExp.prototype.compile can mutate a pattern in place -- so co-resident
19
+ // code could steer the decision after load. Instead every byte is read directly through captured string
20
+ // primitives, and separation on a literal is done by scanning with captured indexOf/slice, so nothing the
21
+ // decision rests on is re-dispatchable.
22
+ var intrinsic = require("./guard-intrinsic");
23
+ var _slice = intrinsic.uncurry(String.prototype.slice);
24
+ var _padStart = intrinsic.uncurry(String.prototype.padStart);
25
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
26
+ var _lastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
27
+ var _toLowerCase = intrinsic.toLowerCase;
28
+ var _indexOf = intrinsic.stringIndexOf;
29
+ var _numToString = intrinsic.numberToString;
30
+ var _bufferFrom = intrinsic.bufferFrom;
31
+ var _bufferAlloc = intrinsic.bufferAlloc;
32
+ var _defineProperty = intrinsic.defineProperty;
33
+ var _Number = intrinsic.Number;
34
+ // Every array here is built and read as own elements only. Appending through a captured Object.defineProperty
35
+ // (not `arr[i] = v`) creates an own element, so a co-resident Array.prototype numeric setter is never invoked
36
+ // and the own element shadows any inherited getter on read; and no Array.prototype.map / concat runs, so
37
+ // Array[Symbol.species] is never consulted. Both are routes a hostile prototype could otherwise use to
38
+ // rewrite a packed octet and make a certificate carry an attacker-chosen address. The emitted octets are
39
+ // written straight into a Buffer, whose integer-indexed store bypasses the prototype entirely.
40
+ function _push(arr, v) { _defineProperty(arr, arr.length, { value: v, writable: true, enumerable: true, configurable: true }); }
41
+ // Split on a literal string separator without String.prototype.split, which consults the separator's
42
+ // `[Symbol.split]`; this scans with captured indexOf/slice only.
43
+ function _splitOn(str, sep) {
44
+ var out = [], from = 0, at;
45
+ while ((at = _indexOf(str, sep, from)) !== -1) { _push(out, _slice(str, from, at)); from = at + sep.length; }
46
+ _push(out, _slice(str, from));
47
+ return out;
48
+ }
49
+
50
+ function _isDigitCode(c) { return c >= 48 && c <= 57; } // 0-9
51
+ function _isHexCode(c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
52
+
53
+ // A strict RFC 791 dotted-quad octet: 1-3 digits, value 0-255, no leading zero beyond "0" itself.
54
+ function _isOctet(p) {
55
+ var n = p.length, i, c;
56
+ if (n < 1 || n > 3) return false;
57
+ for (i = 0; i < n; i += 1) { c = _charCodeAt(p, i); if (!_isDigitCode(c)) return false; }
58
+ if (n > 1 && _charCodeAt(p, 0) === 48) return false; // a leading zero is not the canonical form
59
+ return _Number(p) <= 255;
60
+ }
61
+
62
+ // Strict RFC 791 dotted quad: four 0-255 octets, nothing else.
63
+ function isIPv4(s) {
64
+ if (typeof s !== "string") return false;
65
+ var parts = _splitOn(s, ".");
66
+ if (parts.length !== 4) return false;
67
+ for (var i = 0; i < 4; i += 1) { if (!_isOctet(parts[i])) return false; }
68
+ return true;
69
+ }
70
+
71
+ // A single 1-4 hex-digit IPv6 group.
72
+ function _isHexGroup(g) {
73
+ var n = g.length, i;
74
+ if (n < 1 || n > 4) return false;
75
+ for (i = 0; i < n; i += 1) { if (!_isHexCode(_charCodeAt(g, i))) return false; }
76
+ return true;
77
+ }
78
+
79
+ // The RFC 4291 sec. 2.5.5.2 dual-stack tail SHAPE: exactly four 1-3 digit groups joined by dots. Validity
80
+ // (0-255 per octet) is a separate isIPv4 check, matching the historical two-step (shape then value).
81
+ function _isDottedQuadShape(t) {
82
+ var q = _splitOn(t, "."), i, p, j;
83
+ if (q.length !== 4) return false;
84
+ for (i = 0; i < 4; i += 1) {
85
+ p = q[i];
86
+ if (p.length < 1 || p.length > 3) return false;
87
+ for (j = 0; j < p.length; j += 1) { if (!_isDigitCode(_charCodeAt(p, j))) return false; }
88
+ }
89
+ return true;
90
+ }
19
91
 
20
92
  // Expand an IPv6 textual address to its 32-hex-character form, or null on any parse failure
21
93
  // (bad hex group, group count != 8, more than one "::", an over-length group). Accepts the
22
94
  // IPv4-mapped / dual-stack ".d.d.d.d" tail.
23
95
  var IPV6_TEXT_MAX_LEN = 45; // 8 groups x4 hex + 7 colons + a dotted-quad tail headroom
24
96
  function expandIpv6Hex(ip) {
25
- if (typeof ip !== "string" || ip.length > IPV6_TEXT_MAX_LEN || ip.indexOf(":") === -1) return null;
26
- // RFC 4291 sec. 2.5.5.2 IPv4-mapped / dual-stack: fold a trailing dotted-quad into two
27
- // 16-bit hex groups before the pure-hex parse.
28
- var dual = ip.match(/^(.*):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
29
- if (dual) {
30
- if (!isIPv4(dual[2])) return null;
31
- var v4 = dual[2].split(".").map(Number);
32
- ip = dual[1] + ":" + (((v4[0] << 8) | v4[1]).toString(16)) + ":" + (((v4[2] << 8) | v4[3]).toString(16));
97
+ if (typeof ip !== "string" || ip.length > IPV6_TEXT_MAX_LEN || _indexOf(ip, ":") === -1) return null;
98
+ // RFC 4291 sec. 2.5.5.2 IPv4-mapped / dual-stack: fold a trailing dotted-quad (the suffix after the
99
+ // last colon, when it carries a dot) into two 16-bit hex groups before the pure-hex parse.
100
+ var lastColon = _lastIndexOf(ip, ":");
101
+ var tail = _slice(ip, lastColon + 1);
102
+ if (_indexOf(tail, ".") !== -1) {
103
+ if (!_isDottedQuadShape(tail) || !isIPv4(tail)) return null;
104
+ var q = _splitOn(tail, "."), o0 = _Number(q[0]), o1 = _Number(q[1]), o2 = _Number(q[2]), o3 = _Number(q[3]);
105
+ ip = _slice(ip, 0, lastColon) + ":" + _numToString((o0 << 8) | o1, 16) + ":" + _numToString((o2 << 8) | o3, 16);
33
106
  }
34
- var dbl = ip.split("::");
107
+ var dbl = _splitOn(ip, "::");
35
108
  if (dbl.length > 2) return null;
36
- var left = dbl[0] === "" ? [] : dbl[0].split(":");
37
- var right = dbl.length === 2 ? (dbl[1] === "" ? [] : dbl[1].split(":")) : [];
109
+ var left = dbl[0] === "" ? [] : _splitOn(dbl[0], ":");
110
+ var right = dbl.length === 2 ? (dbl[1] === "" ? [] : _splitOn(dbl[1], ":")) : [];
38
111
  if (dbl.length === 1 && left.length !== 8) return null;
39
112
  var fill = 8 - left.length - right.length;
40
113
  // A "::" run MUST compress at least one zero group (RFC 4291 sec. 2.2 / RFC 5952 sec.
41
114
  // 4.2.2): a "::" with fill 0 is a full 8-group address that must not use compression.
42
115
  if (dbl.length === 2 ? fill < 1 : fill !== 0) return null;
43
- var groups = left, i;
44
- for (i = 0; i < fill; i += 1) groups = groups.concat(["0"]);
45
- groups = groups.concat(right);
116
+ var groups = left, i; // `left` is a fresh own-element array; append through _push (no inherited setter)
117
+ for (i = 0; i < fill; i += 1) _push(groups, "0");
118
+ for (i = 0; i < right.length; i += 1) _push(groups, right[i]);
46
119
  if (groups.length !== 8) return null;
47
120
  var hex = "";
48
121
  for (i = 0; i < 8; i += 1) {
49
122
  var g = groups[i];
50
- if (g.length < 1 || g.length > 4 || !/^[0-9a-f]+$/i.test(g)) return null;
51
- hex += g.toLowerCase().padStart(4, "0");
123
+ if (!_isHexGroup(g)) return null;
124
+ hex += _padStart(_toLowerCase(g), 4, "0");
52
125
  }
53
126
  return hex;
54
127
  }
@@ -61,9 +134,13 @@ function isIpLiteral(s) { return isIPv4(s) || expandIpv6Hex(s) !== null; }
61
134
  // expandIpv6Hex validate, so a SAN/GeneralName iPAddress (RFC 5280 sec. 4.2.1.6, always a bare
62
135
  // host address of 4 or 16 octets) can be given as a string instead of a pre-packed Buffer.
63
136
  function packIpLiteral(s) {
64
- if (isIPv4(s)) return Buffer.from(s.split(".").map(Number)); // each octet already range-checked by IPV4_RE
137
+ if (isIPv4(s)) {
138
+ var parts = _splitOn(s, "."), buf = _bufferAlloc(4), i;
139
+ for (i = 0; i < 4; i += 1) buf[i] = _Number(parts[i]); // Buffer integer-index store; each octet range-checked by _isOctet
140
+ return buf;
141
+ }
65
142
  var hex = expandIpv6Hex(s);
66
- return hex === null ? null : Buffer.from(hex, "hex");
143
+ return hex === null ? null : _bufferFrom(hex, "hex");
67
144
  }
68
145
 
69
- module.exports = { isIPv4: isIPv4, expandIpv6Hex: expandIpv6Hex, isIpLiteral: isIpLiteral, packIpLiteral: packIpLiteral, IPV4_RE: IPV4_RE };
146
+ module.exports = { isIPv4: isIPv4, expandIpv6Hex: expandIpv6Hex, isIpLiteral: isIpLiteral, packIpLiteral: packIpLiteral };
package/lib/key.js CHANGED
@@ -441,7 +441,7 @@ function _inferAlgorithm(detected) {
441
441
  function _importUsages(name, isPublic) {
442
442
  var n = String(name == null ? "" : name).toUpperCase();
443
443
  if (n === "X25519" || n === "X448" || n === "ECDH") return isPublic ? [] : ["deriveBits", "deriveKey"];
444
- if (/^ML-KEM/.test(n)) return isPublic ? ["encapsulateBits"] : ["decapsulateBits"];
444
+ if (n.indexOf("ML-KEM") === 0) return isPublic ? ["encapsulateBits"] : ["decapsulateBits"];
445
445
  if (n === "RSA-OAEP") return isPublic ? ["encrypt"] : ["decrypt"];
446
446
  return isPublic ? ["verify"] : ["sign"]; // signing default (Ed / ECDSA / RSA-* sign / ML-DSA / SLH-DSA)
447
447
  }
@@ -449,7 +449,7 @@ function _importUsages(name, isPublic) {
449
449
  function _generateUsages(name) {
450
450
  var n = String(name == null ? "" : name).toUpperCase();
451
451
  if (n === "X25519" || n === "X448" || n === "ECDH") return ["deriveBits", "deriveKey"];
452
- if (/^ML-KEM/.test(n)) return ["encapsulateBits", "decapsulateBits"];
452
+ if (n.indexOf("ML-KEM") === 0) return ["encapsulateBits", "decapsulateBits"];
453
453
  if (n === "RSA-OAEP") return ["encrypt", "decrypt"];
454
454
  return ["sign", "verify"];
455
455
  }
package/lib/lint.js CHANGED
@@ -97,7 +97,7 @@ function _finding(rule, detail) {
97
97
  // would be the check computed and thrown away. Never throws: _ingest's contract is to return a
98
98
  // finding for bad DATA and reserve throws for a wrong-TYPE argument.
99
99
  function _derivedCert(o) {
100
- if (!o || typeof o !== "object" || Buffer.isBuffer(o) || ArrayBuffer.isView(o) || o instanceof ArrayBuffer) return null;
100
+ if (!o || typeof o !== "object" || guard.bytes.isByteSource(o)) return null;
101
101
  try {
102
102
  var p = guard.parsed.acceptDerived(o, "certificate", x509.parse, _cfg, "lint/bad-input", "the certificate");
103
103
  return guard.parsed.isCert(p) ? p : null;