@blamejs/pki 0.5.31 → 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 +16 -1
- package/README.md +2 -2
- package/lib/acme.js +347 -90
- package/lib/asn1-der.js +93 -33
- 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 +4 -4
- 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/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 +6 -6
- 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 +1 -1
- 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 +1 -1
- package/package.json +1 -1
- 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) ||
|
|
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) &&
|
|
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) {
|
|
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 (
|
|
292
|
-
if (l.indexOf("xn--") === 0 &&
|
|
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 (
|
|
438
|
+
if (_looksLikeIpv4(value)) {
|
|
304
439
|
var parts = value.split(".");
|
|
305
440
|
for (var i = 0; i < 4; i++) {
|
|
306
|
-
if (
|
|
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 (!
|
|
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) || !
|
|
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
|
|
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 =
|
|
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) || !
|
|
1173
|
-
var
|
|
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(
|
|
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) || !
|
|
1274
|
-
|
|
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(
|
|
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 (!
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
1609
|
+
var _auth = _uriAuthority(s, true);
|
|
1473
1610
|
if (_auth) {
|
|
1474
|
-
var _hp = _auth
|
|
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
|
|
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
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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,
|
|
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(
|
|
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 =
|
|
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) {
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
1604
|
-
|
|
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
|
|
1613
|
-
if (!
|
|
1614
|
-
var scheme =
|
|
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 =
|
|
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
|
|
1633
|
-
if (
|
|
1634
|
-
var rest = uri
|
|
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
|
|
1641
|
-
return 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 (
|
|
1649
|
-
return uri
|
|
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
|
|
1657
|
-
|
|
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
|
|
1666
|
-
if (
|
|
1667
|
-
if (
|
|
1668
|
-
return !URL.canParse("http://[" +
|
|
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
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
|
1962
|
+
var segs = _split(_strSlice(uri, 0, _firstDelim(uri, 0, 0x3f, 0x23, -1)), "/");
|
|
1706
1963
|
for (var _si = 0; _si < segs.length; _si++) {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
if (
|
|
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
|
|
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 (!
|
|
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 (!
|
|
1773
|
-
v = inner
|
|
1774
|
-
} else if (!
|
|
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
|
|
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
|
|
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" &&
|
|
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 (!
|
|
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.
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|