@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.
- package/CHANGELOG.md +28 -0
- package/README.md +2 -2
- package/lib/acme.js +347 -90
- package/lib/asn1-der.js +97 -33
- package/lib/attrcert-sign.js +1 -1
- package/lib/cmc-build.js +19 -8
- package/lib/cmc-verify.js +4 -4
- package/lib/cmp-build.js +36 -6
- package/lib/cmp-session.js +5 -5
- package/lib/cmp-verify.js +86 -16
- package/lib/cms-compress.js +2 -2
- package/lib/cms-decrypt.js +3 -3
- package/lib/cms-encrypt.js +1 -1
- package/lib/cms-sign.js +7 -7
- package/lib/cms-verify.js +9 -4
- package/lib/crl-sign.js +5 -5
- package/lib/crmf-sign.js +2 -1
- package/lib/csr-sign.js +1 -4
- package/lib/ct.js +8 -4
- package/lib/est.js +234 -34
- package/lib/framework-error.js +20 -6
- package/lib/guard-bytes.js +24 -3
- package/lib/http-digest.js +69 -16
- package/lib/http-retry-after.js +68 -24
- package/lib/http-transport.js +60 -6
- package/lib/inspect.js +13 -2
- package/lib/ip-utils.js +102 -25
- package/lib/key.js +2 -2
- package/lib/lint.js +1 -1
- package/lib/mime.js +27 -4
- package/lib/ocsp.js +7 -7
- package/lib/oid.js +16 -0
- package/lib/path-validate.js +76 -18
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +2 -2
- package/lib/pki-build.js +320 -21
- package/lib/rfc3339.js +75 -17
- package/lib/schema-c509.js +36 -9
- package/lib/schema-cmc.js +3 -3
- package/lib/schema-pkix.js +174 -25
- package/lib/schema-tsp.js +10 -1
- package/lib/sign-scheme.js +27 -7
- package/lib/sigstore.js +52 -9
- package/lib/smime.js +86 -6
- package/lib/trust.js +114 -14
- package/lib/tsp-sign.js +39 -27
- package/lib/validator-tpm.js +2 -2
- package/lib/webauthn-mds.js +24 -6
- package/lib/webauthn.js +3 -3
- package/lib/webcrypto.js +1 -1
- package/lib/x509-sign.js +9 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/http-digest.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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].
|
|
153
|
+
var seg = segs[i].trim();
|
|
101
154
|
if (seg === "") continue;
|
|
102
155
|
var eq = _firstEqOutsideQuotes(seg);
|
|
103
|
-
var pre = (eq < 0 ? seg : seg
|
|
104
|
-
var
|
|
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 (
|
|
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].
|
|
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
|
|
120
|
-
var rawVal = seg
|
|
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.
|
|
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.
|
|
154
|
-
? p.domain.value.
|
|
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
|
|
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 +=
|
|
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)
|
|
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);
|
package/lib/http-retry-after.js
CHANGED
|
@@ -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
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
var
|
|
51
|
-
if (
|
|
52
|
-
|
|
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 (
|
|
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.
|
package/lib/http-transport.js
CHANGED
|
@@ -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 (
|
|
79
|
-
var b64 = a.toString("base64")
|
|
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
|
-
|
|
186
|
-
|
|
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 (
|
|
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
|
|
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")
|
|
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
|
|
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;
|
|
7
|
-
// constraint
|
|
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
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
26
|
-
// RFC 4291 sec. 2.5.5.2 IPv4-mapped / dual-stack: fold a trailing dotted-quad
|
|
27
|
-
// 16-bit hex groups before the pure-hex parse.
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
107
|
+
var dbl = _splitOn(ip, "::");
|
|
35
108
|
if (dbl.length > 2) return null;
|
|
36
|
-
var left = dbl[0] === "" ? [] : dbl[0]
|
|
37
|
-
var right = dbl.length === 2 ? (dbl[1] === "" ? [] : dbl[1]
|
|
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
|
|
45
|
-
|
|
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 (
|
|
51
|
-
hex += g
|
|
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))
|
|
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 :
|
|
143
|
+
return hex === null ? null : _bufferFrom(hex, "hex");
|
|
67
144
|
}
|
|
68
145
|
|
|
69
|
-
module.exports = { isIPv4: isIPv4, expandIpv6Hex: expandIpv6Hex, isIpLiteral: isIpLiteral, packIpLiteral: packIpLiteral
|
|
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 (
|
|
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 (
|
|
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" ||
|
|
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;
|