@blamejs/pki 0.8.7 → 0.8.10
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 +137 -76
- package/README.md +3 -2
- package/index.js +5 -0
- package/lib/acme.js +24 -83
- package/lib/cmc-build.js +1 -1
- package/lib/cmc-verify.js +3 -3
- package/lib/cmp-session.js +1 -1
- package/lib/cms-sign.js +3 -3
- package/lib/cms-verify.js +5 -5
- package/lib/constants.js +11 -0
- package/lib/est.js +1 -1
- package/lib/framework-error.js +6 -0
- package/lib/guard-async.js +53 -2
- package/lib/guard-encoding.js +74 -0
- package/lib/guard-identifier.js +74 -1
- package/lib/guard-intrinsic.js +2 -0
- package/lib/guard-limits.js +40 -1
- package/lib/guard-list.js +25 -0
- package/lib/guard-name.js +133 -16
- package/lib/guard-text.js +33 -1
- package/lib/http-transport.js +39 -7
- package/lib/identity-match.js +561 -0
- package/lib/ip-utils.js +52 -1
- package/lib/key.js +250 -5
- package/lib/ocsp.js +125 -22
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +199 -61
- package/lib/revocation-fetch.js +772 -0
- package/lib/schema-all.js +124 -0
- package/lib/schema-attrcert.js +4 -2
- package/lib/schema-crmf.js +4 -2
- package/lib/schema-pkcs1.js +335 -0
- package/lib/schema-pkcs8.js +15 -0
- package/lib/schema-pkix.js +322 -40
- package/lib/schema-sec1.js +244 -0
- package/lib/schema-tsp.js +4 -2
- package/lib/schema-x509.js +4 -2
- package/lib/sigstore.js +35 -21
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +21 -14
- package/lib/webauthn.js +4 -4
- package/lib/webcrypto.js +17 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-identifier.js
CHANGED
|
@@ -401,7 +401,62 @@ function _settle(opts, E, code, label) {
|
|
|
401
401
|
throw E(code, label + " changes which options it carries while they are read, so no set of " +
|
|
402
402
|
"them can be checked; pass an object whose properties are plain values");
|
|
403
403
|
}
|
|
404
|
-
return opts;
|
|
404
|
+
return _withoutPlantedFields(opts, before);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** @internal A language prototype, as opposed to one the caller built. A defaults object the
|
|
408
|
+
* caller put under their bag is theirs and sits below these in the chain. */
|
|
409
|
+
function _isLanguageProto(o) {
|
|
410
|
+
return o === intrinsic.ObjectProto || o === intrinsic.ArrayProto ||
|
|
411
|
+
o === intrinsic.BufferProto || o === intrinsic.FunctionProto;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function _holderOf(obj, k) {
|
|
415
|
+
var visited = new _Set();
|
|
416
|
+
for (var o = obj; o !== null && o !== undefined && !_setHas(visited, o); o = _getPrototypeOf(o)) {
|
|
417
|
+
_setAdd(visited, o);
|
|
418
|
+
if (_getOwnPropertyDescriptor(o, k)) return o;
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** @internal An option is something the caller supplied, and a name whose descriptor sits on a
|
|
424
|
+
* language prototype was supplied by whoever wrote to that prototype. The verb reads its options
|
|
425
|
+
* with `opts.name`, which walks the chain, so such a name answers as though it had been passed:
|
|
426
|
+
* `historicalMode` treats a revocation as not yet in force and `softFail` waives an undetermined
|
|
427
|
+
* one, each turning a revoked certificate into a valid path. The unknown-key check catches a
|
|
428
|
+
* planted name that is not an option at all; one spelled like a real option passes it.
|
|
429
|
+
*
|
|
430
|
+
* What comes back reads `undefined` for those names and is otherwise the object that was passed:
|
|
431
|
+
* the same prototype chain, the same own fields, and one own `undefined` masking each planted
|
|
432
|
+
* name. So the caller's own field still wins, a defaults object under their bag still supplies its
|
|
433
|
+
* options whatever their type, the reported set of names does not change, and a verb in a polluted
|
|
434
|
+
* process still runs instead of failing on every call. With nothing planted the object is handed
|
|
435
|
+
* back untouched, which is every call outside a process that has been written to.
|
|
436
|
+
*
|
|
437
|
+
* The own fields are carried as OWN fields rather than left to be inherited from the object that
|
|
438
|
+
* was passed. A verb that forwards its options builds the forwarded set by copying own keys, and a
|
|
439
|
+
* bag whose fields had all become inherited arrives at the inner call carrying none of them. */
|
|
440
|
+
function _withoutPlantedFields(obj, names) {
|
|
441
|
+
var planted = null;
|
|
442
|
+
for (var i = 0; i < names.length; i++) {
|
|
443
|
+
if (!_isLanguageProto(_holderOf(obj, names[i]))) continue;
|
|
444
|
+
if (planted === null) planted = [];
|
|
445
|
+
_append(planted, names[i]);
|
|
446
|
+
}
|
|
447
|
+
if (planted === null) return obj;
|
|
448
|
+
var shadow = _create(_getPrototypeOf(obj));
|
|
449
|
+
var own = _ownKeys(obj);
|
|
450
|
+
for (var k = 0; k < own.length; k++) {
|
|
451
|
+
var d = _getOwnPropertyDescriptor(obj, own[k]);
|
|
452
|
+
if (!d) continue;
|
|
453
|
+
_defineProperty(shadow, own[k], d);
|
|
454
|
+
}
|
|
455
|
+
for (var j = 0; j < planted.length; j++) {
|
|
456
|
+
if (_getOwnPropertyDescriptor(obj, planted[j])) continue;
|
|
457
|
+
_defineProperty(shadow, planted[j], { value: undefined, writable: true, enumerable: false, configurable: true });
|
|
458
|
+
}
|
|
459
|
+
return shadow;
|
|
405
460
|
}
|
|
406
461
|
|
|
407
462
|
/** @internal An injectable callback option, refused unless it is callable. A value that is present
|
|
@@ -445,8 +500,26 @@ function assertCallableOption(opts, key, E, code, label, shape) {
|
|
|
445
500
|
return assertCallable(opts[key], E, code, label, shape);
|
|
446
501
|
}
|
|
447
502
|
|
|
503
|
+
/** @internal Whether a caller's object says something about itself, as opposed to inheriting the
|
|
504
|
+
* answer. A declaration is a promise the author of that object made, so it has to sit on the object
|
|
505
|
+
* they wrote: reading the flag through the object finds a value on `Function.prototype` that every
|
|
506
|
+
* function in the process carries, and a boundary reading it that way treats every caller as having
|
|
507
|
+
* promised whatever was planted there. The value is read from the descriptor rather than through
|
|
508
|
+
* the object, so an accessor further up the chain cannot answer either.
|
|
509
|
+
*
|
|
510
|
+
* @enforced-by guard-shape-reinlined
|
|
511
|
+
* @guard-shape /\.\s*blocksPrivateAddresses\s*===\s*true/ */
|
|
512
|
+
function declaresOwn(obj, name) {
|
|
513
|
+
if (obj === null || obj === undefined) return false;
|
|
514
|
+
var d;
|
|
515
|
+
try { d = _getOwnPropertyDescriptor(_Object(obj), name); }
|
|
516
|
+
catch (_e) { return false; }
|
|
517
|
+
return !!d && !d.get && !d.set && d.value === true;
|
|
518
|
+
}
|
|
519
|
+
|
|
448
520
|
module.exports = intrinsic.freeze({
|
|
449
521
|
assertCallable: assertCallable,
|
|
522
|
+
declaresOwn: declaresOwn,
|
|
450
523
|
assertCallableOption: assertCallableOption,
|
|
451
524
|
assertCanonicalOid: assertCanonicalOid,
|
|
452
525
|
assertKnownKeys: assertKnownKeys,
|
package/lib/guard-intrinsic.js
CHANGED
|
@@ -96,6 +96,7 @@ var _ObjectFn = Object;
|
|
|
96
96
|
var _ObjectProto = Object.prototype;
|
|
97
97
|
var _BufferProto = Buffer.prototype;
|
|
98
98
|
var _ArrayProto = Array.prototype;
|
|
99
|
+
var _FunctionProto = Function.prototype;
|
|
99
100
|
var _Set = Set;
|
|
100
101
|
var _Map = Map;
|
|
101
102
|
var _WeakMap = WeakMap;
|
|
@@ -216,6 +217,7 @@ module.exports = Object.freeze({
|
|
|
216
217
|
ObjectProto: _ObjectProto,
|
|
217
218
|
BufferProto: _BufferProto,
|
|
218
219
|
ArrayProto: _ArrayProto,
|
|
220
|
+
FunctionProto: _FunctionProto,
|
|
219
221
|
setAdd: _setAdd,
|
|
220
222
|
setHas: _setHas,
|
|
221
223
|
mapGet: _mapGet,
|
package/lib/guard-limits.js
CHANGED
|
@@ -7,6 +7,10 @@ var constants = require("./constants");
|
|
|
7
7
|
var _isInteger = require("./guard-intrinsic").isInteger;
|
|
8
8
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
9
9
|
var _sizeOf = require("./guard-intrinsic").sizeOf;
|
|
10
|
+
var _isFinite = require("./guard-intrinsic").isFinite;
|
|
11
|
+
var _Number = require("./guard-intrinsic").Number;
|
|
12
|
+
var _hrtimeBigint = process.hrtime.bigint;
|
|
13
|
+
function _monotonicNow() { return _Number(_hrtimeBigint() / 1000000n); }
|
|
10
14
|
|
|
11
15
|
// @enforced-by behavioral -- a config-time cap validator has no rename-proof code
|
|
12
16
|
function cap(value, key, dflt, opts) {
|
|
@@ -109,7 +113,42 @@ function budget(limits) {
|
|
|
109
113
|
return _freeze(self);
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
/** @internal A wall-clock bound shared across several operations, where a per-operation timeout
|
|
117
|
+
* bounds each one and nothing bounds their sum. `now` is the clock, which a caller supplies so a
|
|
118
|
+
* test drives the bound without waiting; it defaults to the monotonic one, because a wall clock
|
|
119
|
+
* that steps backward would extend the deadline it was meant to enforce.
|
|
120
|
+
*
|
|
121
|
+
* `expired()` answers the question and `remaining()` gives the budget a per-operation timeout
|
|
122
|
+
* should be narrowed to, never below zero, so a caller that hands `remaining()` to a timeout
|
|
123
|
+
* cannot ask for one that outlives the deadline.
|
|
124
|
+
*/
|
|
125
|
+
// @enforced-by behavioral -- an elapsed-time comparison has no rename-proof shape that separates a deadline from any other subtraction; the vectors driving a checker to its bound are the guard
|
|
126
|
+
function deadline(totalMs, opts) {
|
|
127
|
+
opts = opts || {};
|
|
128
|
+
var now = typeof opts.now === "function" ? opts.now : _monotonicNow;
|
|
129
|
+
if (!_isInteger(totalMs) || totalMs < 0) {
|
|
130
|
+
throw new TypeError("guard.limits.deadline: the total must be a non-negative integer of milliseconds");
|
|
131
|
+
}
|
|
132
|
+
var started = now();
|
|
133
|
+
if (typeof started !== "number" || !_isFinite(started)) {
|
|
134
|
+
throw new TypeError("guard.limits.deadline: the clock must return a finite number of milliseconds");
|
|
135
|
+
}
|
|
136
|
+
var self = {
|
|
137
|
+
elapsed: function () {
|
|
138
|
+
var at = now();
|
|
139
|
+
return typeof at === "number" && _isFinite(at) && at > started ? at - started : 0;
|
|
140
|
+
},
|
|
141
|
+
remaining: function () {
|
|
142
|
+
var left = totalMs - self.elapsed();
|
|
143
|
+
return left > 0 ? left : 0;
|
|
144
|
+
},
|
|
145
|
+
expired: function () { return self.remaining() === 0; },
|
|
146
|
+
total: function () { return totalMs; },
|
|
147
|
+
};
|
|
148
|
+
return _freeze(self);
|
|
149
|
+
}
|
|
150
|
+
|
|
112
151
|
module.exports = _freeze({
|
|
113
152
|
cap: cap, depthCap: depthCap, counter: counter, byteCap: byteCap,
|
|
114
|
-
budget: budget, isBudgetExceeded: isBudgetExceeded,
|
|
153
|
+
budget: budget, isBudgetExceeded: isBudgetExceeded, deadline: deadline,
|
|
115
154
|
});
|
package/lib/guard-list.js
CHANGED
|
@@ -48,10 +48,35 @@ function allMatch(list, predicate) {
|
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// @enforced-by caller-array-copied-live
|
|
52
|
+
function snapshot(list) {
|
|
53
|
+
if (!_isArray(list)) throw new TypeError("guard.list.snapshot: the receiver must be an array");
|
|
54
|
+
var n = list.length;
|
|
55
|
+
var out = [];
|
|
56
|
+
for (var i = 0; i < n; i++) {
|
|
57
|
+
_defineProperty(out, i, { value: list[i], writable: true, enumerable: true, configurable: true });
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// @enforced-by caller-array-copied-live
|
|
63
|
+
function copyMap(list, fn) {
|
|
64
|
+
if (!_isArray(list)) throw new TypeError("guard.list.copyMap: the receiver must be an array");
|
|
65
|
+
if (typeof fn !== "function") throw new TypeError("guard.list.copyMap: the second argument must be a function");
|
|
66
|
+
var n = list.length;
|
|
67
|
+
var out = [];
|
|
68
|
+
for (var i = 0; i < n; i++) {
|
|
69
|
+
_defineProperty(out, i, { value: fn(list[i], i), writable: true, enumerable: true, configurable: true });
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
module.exports = _freeze({
|
|
52
75
|
append: append,
|
|
53
76
|
contains: contains,
|
|
54
77
|
containsAll: containsAll,
|
|
55
78
|
anyMatches: anyMatches,
|
|
56
79
|
allMatch: allMatch,
|
|
80
|
+
snapshot: snapshot,
|
|
81
|
+
copyMap: copyMap,
|
|
57
82
|
});
|
package/lib/guard-name.js
CHANGED
|
@@ -325,7 +325,6 @@ function _charTable(chars) {
|
|
|
325
325
|
for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true;
|
|
326
326
|
return t;
|
|
327
327
|
}
|
|
328
|
-
var _FQDN_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-");
|
|
329
328
|
var _DIGDOT_TABLE = _charTable("0123456789.");
|
|
330
329
|
function _allCharsIn(s, table) {
|
|
331
330
|
if (s.length === 0) return false;
|
|
@@ -344,12 +343,18 @@ function uriSchemeIs(s, scheme) {
|
|
|
344
343
|
return lowerAscii(_strSlice(s, 0, scheme.length)) === scheme;
|
|
345
344
|
}
|
|
346
345
|
|
|
347
|
-
/** @internal A host name
|
|
348
|
-
*
|
|
346
|
+
/** @internal A host name that is not an address, which is the form a URI name constraint is
|
|
347
|
+
* compared by (RFC 5280 sec. 4.2.1.10). The clause holds a URI constraint to "a fully qualified
|
|
348
|
+
* domain name" and names in the same sentence what that excludes: a URI with no authority
|
|
349
|
+
* component, and one whose authority names an IP address. It states no label count, so a single
|
|
350
|
+
* label is a host like any other and `com` constrains a top-level domain. The host is read by its
|
|
351
|
+
* labels rather than by which characters it holds: a character test admits an empty label and a
|
|
352
|
+
* hyphen at a label edge, and the suffix comparison below would then match a base against a name
|
|
353
|
+
* that is not a name. */
|
|
349
354
|
function isFqdnHost(host) {
|
|
355
|
+
if (typeof host !== "string") return false;
|
|
350
356
|
var h = stripTrailingDot(host);
|
|
351
|
-
if (
|
|
352
|
-
if (!_allCharsIn(h, _FQDN_TABLE)) return false;
|
|
357
|
+
if (!_isHostName(h)) return false;
|
|
353
358
|
if (_allCharsIn(h, _DIGDOT_TABLE)) return false;
|
|
354
359
|
return true;
|
|
355
360
|
}
|
|
@@ -386,6 +391,112 @@ function _isHostName(s) {
|
|
|
386
391
|
return true;
|
|
387
392
|
}
|
|
388
393
|
|
|
394
|
+
/** @internal A host name written as labels separated by dots (RFC 1034 sec. 3.5, RFC 1123 sec. 2.1),
|
|
395
|
+
* with no trailing dot: the caller strips the one dot a comparison strips, so a second one is an
|
|
396
|
+
* empty label and is refused. */
|
|
397
|
+
// @enforced-by behavioral -- an LDH label walk has no rename-proof shape that separates it from any other per-character scan; the vectors refusing an empty label, a hyphen at a label edge and a name past 253 octets are the guard
|
|
398
|
+
function isHostName(s) { return typeof s === "string" && _isHostName(s); }
|
|
399
|
+
|
|
400
|
+
/** @internal The labels of `s`, in order, with no trailing-dot handling of its own: the caller
|
|
401
|
+
* normalizes the dot through `stripTrailingDot` so both sides of a comparison are normalized by
|
|
402
|
+
* one symbol. A name carrying an empty label yields an empty string at that position, which a
|
|
403
|
+
* label test then refuses, rather than being dropped. */
|
|
404
|
+
// @enforced-by behavioral -- splitting on a dot has no rename-proof shape; the vectors counting the labels of a name carrying an empty one are the guard
|
|
405
|
+
function hostLabels(s) {
|
|
406
|
+
var out = [], start = 0;
|
|
407
|
+
for (var i = 0; i <= s.length; i++) {
|
|
408
|
+
if (i === s.length || _charCodeAt(s, i) === 0x2e) {
|
|
409
|
+
_append(out, _strSlice(s, start, i));
|
|
410
|
+
start = i + 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function _isAlnumLower(c) { return (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39); }
|
|
417
|
+
|
|
418
|
+
/** @internal Whether `l` is a well-formed A-label (RFC 5890 sec. 2.3.2.1): the `xn--` prefix
|
|
419
|
+
* followed by groups of lower-case alphanumerics separated by single hyphens, each group non-empty.
|
|
420
|
+
* The label is not decoded, because RFC 5891 sec. 3.1 rule 2 compares a pair of A-labels as
|
|
421
|
+
* case-insensitive ASCII without validating either. */
|
|
422
|
+
// @enforced-by behavioral -- an `xn--` prefix scan has no rename-proof shape; the vectors refusing `xn--`, a trailing hyphen and an empty group are the guard
|
|
423
|
+
function isXnLabel(l) {
|
|
424
|
+
if (typeof l !== "string") return false;
|
|
425
|
+
var n = l.length;
|
|
426
|
+
if (!(n >= 4 && _charCodeAt(l, 0) === 0x78 && _charCodeAt(l, 1) === 0x6e &&
|
|
427
|
+
_charCodeAt(l, 2) === 0x2d && _charCodeAt(l, 3) === 0x2d)) return false;
|
|
428
|
+
var i = 4, g = 0;
|
|
429
|
+
while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g++; }
|
|
430
|
+
if (g < 1) return false;
|
|
431
|
+
while (i < n) {
|
|
432
|
+
if (_charCodeAt(l, i) !== 0x2d) return false;
|
|
433
|
+
i++;
|
|
434
|
+
var g2 = 0;
|
|
435
|
+
while (i < n && _isAlnumLower(_charCodeAt(l, i))) { i++; g2++; }
|
|
436
|
+
if (g2 < 1) return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
var _SCHEME_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-");
|
|
442
|
+
|
|
443
|
+
/** @internal The scheme and the host of a URI, read to RFC 3986 sec. 3: the scheme up to its colon,
|
|
444
|
+
* and, when `//` follows that colon, the authority up to the first `/`, `?` or `#`, with userinfo
|
|
445
|
+
* and port removed and an IPv6 literal's brackets stripped. `hasAuthority` is false for a URI
|
|
446
|
+
* written without `//`, which RFC 3986 sec. 3.3 permits and which therefore has no host; a caller
|
|
447
|
+
* that needs a host decides what to do with that rather than being handed a guess. `host` is null
|
|
448
|
+
* when the authority carries none, and `bracketed` says the host arrived inside `[]`, which is the
|
|
449
|
+
* only place an IPv6 literal may appear. Percent-escapes are left as written, so a caller comparing
|
|
450
|
+
* the host decides whether to accept one. */
|
|
451
|
+
// @enforced-by behavioral -- a URI walk has no rename-proof shape that separates it from any other character scan; the vectors reading a scheme, refusing two userinfo separators and stripping a port are the guard
|
|
452
|
+
function uriParts(uri) {
|
|
453
|
+
if (typeof uri !== "string") return null;
|
|
454
|
+
var n = uri.length;
|
|
455
|
+
if (n === 0) return null;
|
|
456
|
+
var c0 = _charCodeAt(uri, 0);
|
|
457
|
+
if (!((c0 >= 0x41 && c0 <= 0x5a) || (c0 >= 0x61 && c0 <= 0x7a))) return null;
|
|
458
|
+
var p = 1;
|
|
459
|
+
while (p < n && _SCHEME_TABLE[_charCodeAt(uri, p)]) p += 1;
|
|
460
|
+
if (_charCodeAt(uri, p) !== 0x3a) return null;
|
|
461
|
+
var scheme = _strSlice(uri, 0, p);
|
|
462
|
+
if (_charCodeAt(uri, p + 1) !== 0x2f || _charCodeAt(uri, p + 2) !== 0x2f) {
|
|
463
|
+
return { scheme: scheme, hasAuthority: false, host: null, bracketed: false };
|
|
464
|
+
}
|
|
465
|
+
p += 3;
|
|
466
|
+
var authStart = p;
|
|
467
|
+
while (p < n) {
|
|
468
|
+
var d = _charCodeAt(uri, p);
|
|
469
|
+
if (d === 0x2f || d === 0x3f || d === 0x23) break;
|
|
470
|
+
p += 1;
|
|
471
|
+
}
|
|
472
|
+
var authority = _strSlice(uri, authStart, p);
|
|
473
|
+
var firstAt = _indexOf(authority, "@");
|
|
474
|
+
if (firstAt !== _lastIndexOf(authority, "@")) return null;
|
|
475
|
+
if (firstAt !== -1) authority = _strSlice(authority, firstAt + 1);
|
|
476
|
+
var host = _strSlice(authority, 0, _portStripIndex(authority));
|
|
477
|
+
if (host === "") return { scheme: scheme, hasAuthority: true, host: null, bracketed: false };
|
|
478
|
+
var hn = host.length;
|
|
479
|
+
if (hn >= 2 && _charCodeAt(host, 0) === 0x5b && _charCodeAt(host, hn - 1) === 0x5d) {
|
|
480
|
+
return { scheme: scheme, hasAuthority: true, host: _strSlice(host, 1, hn - 1), bracketed: true };
|
|
481
|
+
}
|
|
482
|
+
return { scheme: scheme, hasAuthority: true, host: host, bracketed: false };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** @internal Where a `:port` suffix starts, or the length when there is none. RFC 3986 sec. 3.2.3
|
|
486
|
+
* writes `port = *DIGIT`, so a colon with nothing after it is a port too, and leaving that colon on
|
|
487
|
+
* the host would make the authority compare as a name carrying a character no name carries. A
|
|
488
|
+
* bracketed IPv6 literal has colons of its own, so the scan reads digits back from the end and
|
|
489
|
+
* requires the colon immediately before them; the closing bracket is what stops it. */
|
|
490
|
+
function _portStripIndex(s) {
|
|
491
|
+
var i = s.length;
|
|
492
|
+
while (i > 0) {
|
|
493
|
+
var c = _charCodeAt(s, i - 1);
|
|
494
|
+
if (c >= 0x30 && c <= 0x39) i -= 1;
|
|
495
|
+
else break;
|
|
496
|
+
}
|
|
497
|
+
return (i > 0 && _charCodeAt(s, i - 1) === 0x3a) ? i - 1 : s.length;
|
|
498
|
+
}
|
|
499
|
+
|
|
389
500
|
var _ATEXT_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~");
|
|
390
501
|
|
|
391
502
|
/** @internal A quoted local part (RFC 5321 sec. 4.1.2 Quoted-string): printable ASCII between
|
|
@@ -534,18 +645,23 @@ function constraintBaseRefusal(tag, base) {
|
|
|
534
645
|
: "must be a host name, optionally written as a subtree with a leading dot";
|
|
535
646
|
}
|
|
536
647
|
/** @internal RFC 5280 sec. 4.2.1.10 says of a URI constraint: "The constraint MUST be specified as
|
|
537
|
-
* a fully qualified domain name and MAY specify a host or a domain
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* `
|
|
648
|
+
* a fully qualified domain name and MAY specify a host or a domain", and the same sentence names
|
|
649
|
+
* what that excludes: a URI with no authority component, and one whose authority names an IP
|
|
650
|
+
* address. Its dNSName paragraph states no such requirement, so an address is a valid dNSName base
|
|
651
|
+
* and is not a valid URI base. Neither paragraph states a label count. A caller meets this rule at
|
|
652
|
+
* the door, whether through `pki.trust.anchor` or through the validator's subtree seeds. A base a
|
|
653
|
+
* certificate carries in its own extension never reaches a door, and `uriMatch` holds it to the
|
|
654
|
+
* same `isFqdnHost` this calls, so the door and the comparison cannot answer differently. */
|
|
544
655
|
if (tag === 6) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
656
|
+
/** @internal Only the leading dot comes off here. `isFqdnHost` takes a host as a URI carries
|
|
657
|
+
* it and strips the one trailing dot that makes a name absolute, so stripping it here too
|
|
658
|
+
* would let a base ending in two dots through as one ending in none. */
|
|
659
|
+
var uriBody = _toLowerCase(base);
|
|
660
|
+
if (_charAt(uriBody, 0) === ".") uriBody = _strSlice(uriBody, 1);
|
|
661
|
+
if (isFqdnHost(uriBody)) return null;
|
|
662
|
+
return _allCharsIn(stripTrailingDot(uriBody), _DIGDOT_TABLE)
|
|
663
|
+
? "must be a domain name rather than an address, RFC 5280 sec. 4.2.1.10 naming an address in a URI's authority as what a URI constraint cannot be compared against"
|
|
664
|
+
: "must be a domain name, which RFC 5280 sec. 4.2.1.10 requires of a URI constraint, optionally written as a subtree with a leading dot";
|
|
549
665
|
}
|
|
550
666
|
return null;
|
|
551
667
|
}
|
|
@@ -656,6 +772,7 @@ module.exports = intrinsic.freeze({
|
|
|
656
772
|
isAttributeDescriptor: isAttributeDescriptor,
|
|
657
773
|
lowerAscii: lowerAscii, uriSchemeIs: uriSchemeIs,
|
|
658
774
|
stripTrailingDot: stripTrailingDot, isFqdnHost: isFqdnHost, splitMailbox: splitMailbox,
|
|
775
|
+
isHostName: isHostName, hostLabels: hostLabels, isXnLabel: isXnLabel, uriParts: uriParts,
|
|
659
776
|
constraintBaseRefusal: constraintBaseRefusal,
|
|
660
777
|
constraintBaseUnreadable: constraintBaseUnreadable,
|
|
661
778
|
});
|
package/lib/guard-text.js
CHANGED
|
@@ -50,4 +50,36 @@ function keyOf(v) {
|
|
|
50
50
|
return typeof v === "string" || typeof v === "number" ? v : undefined;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
/** @internal What a caught value says about itself, as a string, for a boundary that reports a
|
|
54
|
+
* fault it did not cause. The value came from caller code, so every step of reading it is a step
|
|
55
|
+
* the caller chose the behavior of: `message` may be an accessor that throws, a Symbol that
|
|
56
|
+
* refuses string conversion, or an object whose `toString` throws, and the value itself may be a
|
|
57
|
+
* Proxy that refuses every read. A boundary whose whole contract is to turn a fault into a verdict
|
|
58
|
+
* cannot afford any of those to leave it, which is what happens when the description is built by
|
|
59
|
+
* reading `e.message` in the open.
|
|
60
|
+
*
|
|
61
|
+
* Every read is attempted and none is required. A value that answers nothing readable is described
|
|
62
|
+
* by its type, which is what `showValue` would have said about it anyway.
|
|
63
|
+
*
|
|
64
|
+
* @enforced-by guard-shape-reinlined
|
|
65
|
+
* @guard-shape /([\w$]+)\s*&&\s*\1\.message\s*\?\s*\1\.message\s*:|([\w$]+)\.message\s*\|\|\s*\2\b/ */
|
|
66
|
+
function describeThrown(e) {
|
|
67
|
+
var m;
|
|
68
|
+
try { m = e === null || e === undefined ? undefined : e.message; }
|
|
69
|
+
catch (_readFailed) { return _thrownFallback(e); }
|
|
70
|
+
if (typeof m === "string" && m.length > 0) return m;
|
|
71
|
+
if (m === undefined || m === null) return _thrownFallback(e);
|
|
72
|
+
try {
|
|
73
|
+
if (typeof m === "symbol") return intrinsic.String(m);
|
|
74
|
+
return "" + m;
|
|
75
|
+
} catch (_convertFailed) { return _thrownFallback(e); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _thrownFallback(e) {
|
|
79
|
+
try { return showValue(e); }
|
|
80
|
+
catch (_showFailed) { return "a value that reports nothing readable"; }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = intrinsic.freeze({
|
|
84
|
+
decode: decode, showValue: showValue, keyOf: keyOf, describeThrown: describeThrown,
|
|
85
|
+
});
|
package/lib/http-transport.js
CHANGED
|
@@ -134,7 +134,7 @@ function _pemifyAnchor(a) {
|
|
|
134
134
|
return a;
|
|
135
135
|
}
|
|
136
136
|
function _pemifyAnchors(anchors) {
|
|
137
|
-
var list = Array.isArray(anchors) ?
|
|
137
|
+
var list = Array.isArray(anchors) ? guard.list.copyMap(anchors, _pemifyAnchor) : [_pemifyAnchor(anchors)];
|
|
138
138
|
return list;
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -366,7 +366,11 @@ function _validateProxy(request, defaults, E, C, url) {
|
|
|
366
366
|
var originHost = _stripIpv6Brackets(url.hostname);
|
|
367
367
|
var originAuthHost = nodeNet.isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
|
|
368
368
|
var phost = _stripIpv6Brackets(purl.hostname);
|
|
369
|
-
|
|
369
|
+
/** @internal The CONNECT target is the origin's own authority, so an origin URL that states no
|
|
370
|
+
* port takes the default for the origin's scheme, not the proxy's. Defaulting it to 443 opens
|
|
371
|
+
* the tunnel to a port an `http:` origin does not listen on. */
|
|
372
|
+
var originPort = url.port || (url.protocol === "http:" ? 80 : 443);
|
|
373
|
+
return { secure: secure, host: phost, port: purl.port ? +purl.port : (secure ? 443 : 80), originHost: originHost, originAuthority: originAuthHost + ":" + originPort, auth: auth, proxyTls: proxyTls };
|
|
370
374
|
}
|
|
371
375
|
|
|
372
376
|
function _basicProxyHeader(auth) {
|
|
@@ -453,13 +457,30 @@ function httpsTransport(defaults) {
|
|
|
453
457
|
var url;
|
|
454
458
|
try { url = new URL(String(request.url)); }
|
|
455
459
|
catch (e) { throw E(C("bad-url"), "the request URL did not parse: " + String(request.url), e); }
|
|
456
|
-
|
|
460
|
+
/** @internal https is the floor, and `request.allowPlaintextHttp` is the one thing that moves
|
|
461
|
+
* it, per request and never as a default. It exists for the two objects whose integrity does
|
|
462
|
+
* not come from the transport: a CRL and an OCSP response are signed, their signatures are
|
|
463
|
+
* verified after the fetch, and an https-only revocation fetch needs the responder's own
|
|
464
|
+
* certificate validated first, which is the loop the plaintext form exists to break. Nothing
|
|
465
|
+
* else in this toolkit sets it. Every other guard stays: the address checks, the size cap, the
|
|
466
|
+
* timeout, and the refusal to follow a redirect, which is what would otherwise turn one
|
|
467
|
+
* plaintext hop into a downgrade of the next. */
|
|
468
|
+
var plaintextAllowed = request.allowPlaintextHttp === true;
|
|
469
|
+
if (url.protocol !== "https:" && !(plaintextAllowed && url.protocol === "http:")) {
|
|
470
|
+
throw E(C("insecure-url"), "transport requires https (RFC 7030 sec. 3.3), got " + url.protocol);
|
|
471
|
+
}
|
|
457
472
|
|
|
458
473
|
var reqTls = request.tls || {};
|
|
459
474
|
var anchors = reqTls.anchors !== undefined ? reqTls.anchors : tlsDefaults.anchors;
|
|
460
475
|
var useSystem = (reqTls.useSystemStore !== undefined ? reqTls.useSystemStore : tlsDefaults.useSystemStore) === true;
|
|
461
476
|
var hasAnchors = anchors !== undefined && anchors !== null && !(Array.isArray(anchors) && anchors.length === 0);
|
|
462
|
-
|
|
477
|
+
/** @internal A trust anchor is what makes an https server identifiable, so a request with
|
|
478
|
+
* neither an anchor nor the system store is refused rather than left unpinned. A plaintext hop
|
|
479
|
+
* negotiates no certificate, so there is nothing for an anchor to pin; what protects the object
|
|
480
|
+
* fetched over one is its own signature, checked after the fetch by the verb that asked. */
|
|
481
|
+
if (url.protocol === "https:" && !hasAnchors && !useSystem) {
|
|
482
|
+
throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
483
|
+
}
|
|
463
484
|
|
|
464
485
|
var timeout = _budget(request.timeout !== undefined ? request.timeout : defaults.timeout, "timeout", DEFAULT_TIMEOUT, MAX_TIMEOUT, E, C("bad-input"));
|
|
465
486
|
var maxBytes = _budget(request.maxResponseBytes !== undefined ? request.maxResponseBytes : defaults.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, E, C("bad-input"));
|
|
@@ -481,10 +502,11 @@ function httpsTransport(defaults) {
|
|
|
481
502
|
}
|
|
482
503
|
|
|
483
504
|
var host = _stripIpv6Brackets(url.hostname);
|
|
505
|
+
var plaintext = url.protocol === "http:";
|
|
484
506
|
var options = {
|
|
485
507
|
method: request.method || "GET",
|
|
486
508
|
hostname: host,
|
|
487
|
-
port: url.port || 443,
|
|
509
|
+
port: url.port || (plaintext ? 80 : 443),
|
|
488
510
|
path: url.pathname + url.search,
|
|
489
511
|
headers: reqHeaders,
|
|
490
512
|
minVersion: minVersion,
|
|
@@ -528,7 +550,7 @@ function httpsTransport(defaults) {
|
|
|
528
550
|
options.lookup = _guardedLookup;
|
|
529
551
|
}
|
|
530
552
|
|
|
531
|
-
return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi };
|
|
553
|
+
return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi, plaintext: plaintext };
|
|
532
554
|
}
|
|
533
555
|
|
|
534
556
|
var _transportFn = function transport(request) {
|
|
@@ -561,7 +583,10 @@ function httpsTransport(defaults) {
|
|
|
561
583
|
|
|
562
584
|
function issue(options) {
|
|
563
585
|
try {
|
|
564
|
-
|
|
586
|
+
/** @internal A plaintext request goes through `node:http`, which has no TLS options to
|
|
587
|
+
* ignore; `prep.plaintext` is set only where the scheme check let `http:` through. */
|
|
588
|
+
var client = prep.plaintext ? nodeHttp : nodeHttps;
|
|
589
|
+
req = client.request(options, function (res) {
|
|
565
590
|
var tlsInfo = _tlsInfo(res.socket);
|
|
566
591
|
var declared = parseInt((res.headers || {})["content-length"], 10);
|
|
567
592
|
if (Number.isFinite(declared) && declared > prep.maxBytes) { fail(C("response-too-large"), "the declared content-length " + declared + " exceeds the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
|
|
@@ -673,6 +698,13 @@ function httpsTransport(defaults) {
|
|
|
673
698
|
creq = null;
|
|
674
699
|
socket.on("error", function (e) { fail(_classifyError(e, C), "the tunnel socket failed: " + ((e && e.message) || String(e)), e); });
|
|
675
700
|
var idHost = prep.options.servername || pinfo.originHost;
|
|
701
|
+
/** @internal A plaintext origin speaks HTTP over the tunnel the CONNECT opened, so the
|
|
702
|
+
* connection the request is issued on is that socket. Handing back a TLS wrapper would
|
|
703
|
+
* start a handshake against a server that does not speak TLS. */
|
|
704
|
+
if (prep.plaintext) {
|
|
705
|
+
issue(Object.assign({}, prep.options, { createConnection: function () { return socket; } }));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
676
708
|
issue(Object.assign({}, prep.options, {
|
|
677
709
|
createConnection: function () {
|
|
678
710
|
return nodeTls.connect({
|