@blamejs/pki 0.8.6 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +143 -76
  2. package/README.md +4 -2
  3. package/index.js +11 -0
  4. package/lib/acme.js +15 -78
  5. package/lib/asn1-der.js +58 -6
  6. package/lib/cbor-det.js +11 -1
  7. package/lib/cmc-build.js +1 -1
  8. package/lib/cmc-verify.js +3 -3
  9. package/lib/cmp-session.js +1 -1
  10. package/lib/cms-sign.js +3 -3
  11. package/lib/cms-verify.js +5 -5
  12. package/lib/constants.js +11 -0
  13. package/lib/est.js +1 -1
  14. package/lib/framework-error.js +8 -0
  15. package/lib/guard-async.js +53 -2
  16. package/lib/guard-encoding.js +105 -1
  17. package/lib/guard-identifier.js +74 -1
  18. package/lib/guard-intrinsic.js +2 -0
  19. package/lib/guard-json.js +1 -6
  20. package/lib/guard-limits.js +40 -1
  21. package/lib/guard-list.js +25 -0
  22. package/lib/guard-name.js +134 -22
  23. package/lib/guard-text.js +33 -1
  24. package/lib/http-transport.js +39 -7
  25. package/lib/identity-match.js +561 -0
  26. package/lib/ip-utils.js +52 -1
  27. package/lib/key.js +250 -5
  28. package/lib/ocsp.js +125 -22
  29. package/lib/oid.js +1 -1
  30. package/lib/path-validate.js +199 -61
  31. package/lib/pkcs11-uri.js +646 -0
  32. package/lib/pkcs12-build.js +6 -1
  33. package/lib/revocation-fetch.js +772 -0
  34. package/lib/schema-all.js +124 -0
  35. package/lib/schema-attrcert.js +4 -2
  36. package/lib/schema-crmf.js +4 -2
  37. package/lib/schema-pkcs1.js +335 -0
  38. package/lib/schema-pkcs8.js +15 -0
  39. package/lib/schema-pkix.js +322 -40
  40. package/lib/schema-sec1.js +244 -0
  41. package/lib/schema-tsp.js +4 -2
  42. package/lib/schema-x509.js +4 -2
  43. package/lib/trust.js +2 -2
  44. package/lib/tsp-sign.js +1 -1
  45. package/lib/webauthn.js +4 -4
  46. package/lib/webcrypto.js +17 -5
  47. package/package.json +1 -1
  48. package/sbom.cdx.json +6 -6
@@ -8,6 +8,13 @@ var _reject = _intrinsic.uncurry(_intrinsic.promiseReject);
8
8
  var _apply = _intrinsic.apply;
9
9
  var _String = _intrinsic.String;
10
10
  var _Promise = Promise;
11
+ /** @internal Bound at load, like every other operation this module decides with: a replacement
12
+ * installed later would otherwise choose when a bound expires, or whether it ever does. */
13
+ var _setTimeout = setTimeout;
14
+ var _clearTimeout = clearTimeout;
15
+ /** @internal The losing side of a race is observed, not acted on: attaching this is what keeps an
16
+ * outcome nobody wants from becoming an unhandled rejection. */
17
+ function _ignore() { return undefined; }
11
18
  // @internal
12
19
  // @enforced-by behavioral -- the rule has no rename-proof code shape (it is the ABSENCE of a wrapper
13
20
  function deferred(body) {
@@ -40,9 +47,53 @@ function invoked(fn, args, E, code, label) {
40
47
  var out;
41
48
  try { out = _apply(fn, undefined, args); }
42
49
  catch (e) {
43
- throw E(code, label + " threw when it was called: " + (e && e.message ? e.message : _String(e)), e);
50
+ /** @internal The thrown value is the caller's, so describing it is a read of caller data: a
51
+ * `message` accessor that throws would leave this boundary by the one path it exists to close.
52
+ * @guard-via guard.text.describeThrown */
53
+ // allow:inline-require guard-text reaches guard-bytes, which reaches this module, so binding it at load leaves guard-bytes half-built for whichever of the two is required first; this line runs only after a producer has already thrown
54
+ throw E(code, label + " threw when it was called: " +
55
+ require("./guard-text").describeThrown(e), e);
44
56
  }
45
57
  return awaited(out, E, code, label);
46
58
  }
47
59
 
48
- module.exports = _intrinsic.freeze({ deferred: deferred, awaited: awaited, invoked: invoked });
60
+ // @internal
61
+ // @enforced-by behavioral -- a RED vector drives a never-settling caller operation through a client
62
+ /** @internal Await a caller-supplied operation under a bound this side controls. A request field
63
+ * naming a timeout is a request: an operation that does not implement it never settles, and the
64
+ * verb awaiting it never returns. The timer holds the event loop until it fires or is cleared,
65
+ * which is what keeps a process from exiting while a bound is still in flight and abandoning the
66
+ * await in silence. It is cleared on either outcome, so it holds the loop for at most `ms`. The
67
+ * losing side of the race is not cancelled, because a promise cannot be; the caller stops waiting.
68
+ *
69
+ * `ms` at or below zero is already past and refuses without awaiting anything. */
70
+ function bounded(value, ms, E, code, label) {
71
+ var settled = false;
72
+ return new _Promise(function (resolve, reject) {
73
+ if (!(ms > 0)) {
74
+ /** @internal The operation may already be running: a deadline can expire between the check
75
+ * that started it and the bound computed for it. Refusing without observing its promise
76
+ * leaves a rejection nobody handled, which ends the process on the default
77
+ * `unhandledRejection` behavior, and the caller catching this refusal does not help. */
78
+ _resolve(_Promise, value).then(_ignore, _ignore);
79
+ reject(E(code, label + " had no time left to run in"));
80
+ return;
81
+ }
82
+ var timer = _setTimeout(function () {
83
+ if (settled) return;
84
+ settled = true;
85
+ reject(E(code, label + " did not settle within " + ms + "ms"));
86
+ }, ms);
87
+ function done(fn) {
88
+ return function (x) {
89
+ if (settled) return;
90
+ settled = true;
91
+ _clearTimeout(timer);
92
+ fn(x);
93
+ };
94
+ }
95
+ _resolve(_Promise, value).then(done(resolve), done(reject));
96
+ });
97
+ }
98
+
99
+ module.exports = _intrinsic.freeze({ deferred: deferred, awaited: awaited, invoked: invoked, bounded: bounded });
@@ -16,6 +16,9 @@ var _charCodeAt = _intrinsic.uncurry(String.prototype.charCodeAt);
16
16
  var _strSlice = _intrinsic.uncurry(String.prototype.slice);
17
17
  var _toLowerCase = _intrinsic.uncurry(String.prototype.toLowerCase);
18
18
  var _isInteger = _intrinsic.isInteger;
19
+ var _charAt = _intrinsic.uncurry(String.prototype.charAt);
20
+ var _fromCharCode = String.fromCharCode;
21
+ var _guardBytes = require("./guard-bytes");
19
22
  var _floor = _intrinsic.floor;
20
23
 
21
24
  var UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", LOWER = "abcdefghijklmnopqrstuvwxyz", DIGITS = "0123456789";
@@ -76,4 +79,105 @@ function hex(text, maxBytes, E, code, label) {
76
79
  return buf;
77
80
  }
78
81
 
79
- module.exports = _intrinsic.freeze({ base64url: base64url, base64: base64, hex: hex });
82
+ /**
83
+ * @internal The value of one hexadecimal digit, or -1 when the byte is not one. Every
84
+ * character-scanning reader of an escape (a DN string, a JSON string, a URI) asks the same
85
+ * question, and asking it in one place keeps the three ranges from drifting apart.
86
+ *
87
+ * @enforced-by guard-shape-reinlined
88
+ * @guard-shape c >= 0x30 && c <= 0x39\) return c - 0x30
89
+ */
90
+ function hexNibble(c) {
91
+ if (c >= 0x30 && c <= 0x39) return c - 0x30;
92
+ if (c >= 0x41 && c <= 0x46) return c - 0x41 + 10;
93
+ if (c >= 0x61 && c <= 0x66) return c - 0x61 + 10;
94
+ return -1;
95
+ }
96
+
97
+ /**
98
+ * @internal Whether a byte is an ASCII letter or digit. It is the base of every
99
+ * character-class a textual syntax defines on top of it, so the three ranges live here
100
+ * rather than being spelled again per reader.
101
+ *
102
+ * @enforced-by behavioral -- the ranges have no rename-proof shape distinct from a legitimate
103
+ * range test, and each consumer's own acceptance vector is the guard.
104
+ */
105
+ function isAlphanumericByte(c) {
106
+ return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39);
107
+ }
108
+
109
+ var _HEX_UPPER = "0123456789ABCDEF";
110
+
111
+ /** @internal Whether a byte is in RFC 3986 sec. 2.3's unreserved set: `ALPHA / DIGIT / "-" / "."
112
+ * / "_" / "~"`. Every other byte is written as a percent-triplet, which is what sec. 2.1 requires
113
+ * of a character that is not allowed where it sits. The set is deliberately the unreserved one
114
+ * and not the larger set a path segment also admits, so the output is safe wherever it is
115
+ * placed and a caller cannot reach a delimiter by choosing a component. */
116
+ function _isUnreservedByte(c) {
117
+ return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39) ||
118
+ c === 0x2d || c === 0x2e || c === 0x5f || c === 0x7e;
119
+ }
120
+
121
+ /** @internal Percent-encode bytes to RFC 3986 sec. 2.1, with the unreserved set of sec. 2.3 passed
122
+ * through and every other byte written as `%` and two upper-case hex digits. sec. 2.1 states that
123
+ * the hexadecimal digits of a triplet may be either case and that uppercase is the canonical form
124
+ * a producer emits.
125
+ *
126
+ * This is the only escaping in `lib/`. `encodeURIComponent` reads a JavaScript string, so bytes
127
+ * reach it only after a decode this cannot assume, and it leaves `!`, `*`, `'`, `(` and `)`
128
+ * unescaped, which are sub-delims in sec. 2.2 and therefore delimiters in some components. */
129
+ // @enforced-by guard-shape-reinlined
130
+ // @guard-shape %[0-9A-F]{2}|toString\(16\)[\s\S]{0,80}?padStart\(\s*2
131
+ // @guard-via guard\.encoding\.(?:percentEncode|uriPathSegment)\(
132
+ function percentEncode(bytes, E, code, label) {
133
+ var buf = _guardBytes.source(bytes, _rawError(E), code, label || "value to percent-encode");
134
+ var out = "";
135
+ for (var i = 0; i < buf.length; i++) {
136
+ var c = buf[i];
137
+ if (_isUnreservedByte(c)) { out += _fromCharCode(c); continue; }
138
+ out += "%" + _charAt(_HEX_UPPER, (c >> 4) & 0x0f) + _charAt(_HEX_UPPER, c & 0x0f);
139
+ }
140
+ return out;
141
+ }
142
+
143
+ /** @internal One path segment of a URI, built from `text` read as its latin1 bytes. Every byte
144
+ * outside the unreserved set becomes a triplet, so a value carrying `/`, `?`, `#` or a percent of
145
+ * its own cannot reach past the segment it was placed in. */
146
+ function uriPathSegment(text, E, code, label) {
147
+ if (typeof text !== "string") {
148
+ throw _rawError(E)(code, (label || "URI path segment") + " must be a string");
149
+ }
150
+ return percentEncode(_bufferFrom(text, "latin1"), E, code, label);
151
+ }
152
+
153
+ /** @internal The guards take a `(code, message)` factory. A caller that passes an error CLASS is a
154
+ * configuration fault and not an input one, so it is refused here instead of producing
155
+ * `undefined is not a constructor` at the first refusal. */
156
+ function _rawError(E) {
157
+ if (typeof E !== "function") throw new TypeError("guard.encoding: an error factory (code, message) is required");
158
+ return E;
159
+ }
160
+
161
+ /** @internal Whether a string is already the form that goes on the wire. RFC 3986 sec. 2 builds a
162
+ * URI out of US-ASCII alone, so a string carrying anything above it is an IRI (RFC 3987) and the
163
+ * bytes a transport would send are not the characters counted here. Every length a specification
164
+ * states about a URL is a byte count, which makes this the check a boundary needs before measuring
165
+ * one: a code point in U+0080..U+07FF is one character and two UTF-8 bytes, so a rule measured in
166
+ * characters passes a URL the rule was meant to refuse.
167
+ *
168
+ * @enforced-by guard-shape-reinlined
169
+ * @guard-shape /charCodeAt\s*\(\s*[\w$]+\s*,\s*[\w$]+\s*\)\s*>\s*(?:0x7f|127)\b/ */
170
+ function isAsciiText(s) {
171
+ if (typeof s !== "string") return false;
172
+ for (var i = 0; i < s.length; i++) {
173
+ if (_charCodeAt(s, i) > 0x7f) return false;
174
+ }
175
+ return true;
176
+ }
177
+
178
+ module.exports = _intrinsic.freeze({
179
+ base64url: base64url, base64: base64, hex: hex,
180
+ hexNibble: hexNibble, isAlphanumericByte: isAlphanumericByte,
181
+ isAsciiText: isAsciiText,
182
+ percentEncode: percentEncode, uriPathSegment: uriPathSegment,
183
+ });
@@ -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,
@@ -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-json.js CHANGED
@@ -15,12 +15,7 @@ var _stringify = require("./guard-intrinsic").stringify;
15
15
  var _append = require("./guard-list").append;
16
16
  var limits = require("./guard-limits");
17
17
 
18
- function _hexVal(c) {
19
- if (c >= 0x30 && c <= 0x39) return c - 0x30;
20
- if (c >= 0x61 && c <= 0x66) return c - 0x61 + 10;
21
- if (c >= 0x41 && c <= 0x46) return c - 0x41 + 10;
22
- return -1;
23
- }
18
+ var _hexVal = require("./guard-encoding").hexNibble;
24
19
 
25
20
  // @enforced-by json-parse-not-via-guard
26
21
  function parse(input, ErrorClass, spec) {
@@ -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
@@ -164,12 +164,7 @@ function escapeControlBytes(str) {
164
164
 
165
165
  var DN_SPECIAL = intrinsic.assign(intrinsic.create(null), { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 });
166
166
 
167
- function _hexNibble(c) {
168
- if (c >= 0x30 && c <= 0x39) return c - 0x30;
169
- if (c >= 0x41 && c <= 0x46) return c - 0x41 + 10;
170
- if (c >= 0x61 && c <= 0x66) return c - 0x61 + 10;
171
- return -1;
172
- }
167
+ var _hexNibble = require("./guard-encoding").hexNibble;
173
168
  function _isAttrTypeStart(c) { return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); }
174
169
  function _isAttrTypeChar(c) { return _isAttrTypeStart(c) || (c >= 0x30 && c <= 0x39) || c === 0x2d; }
175
170
  function _isDigit(c) { return c >= 0x30 && c <= 0x39; }
@@ -330,7 +325,6 @@ function _charTable(chars) {
330
325
  for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true;
331
326
  return t;
332
327
  }
333
- var _FQDN_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-");
334
328
  var _DIGDOT_TABLE = _charTable("0123456789.");
335
329
  function _allCharsIn(s, table) {
336
330
  if (s.length === 0) return false;
@@ -349,12 +343,18 @@ function uriSchemeIs(s, scheme) {
349
343
  return lowerAscii(_strSlice(s, 0, scheme.length)) === scheme;
350
344
  }
351
345
 
352
- /** @internal A host name of two or more labels that is not an address, which is the form a URI
353
- * name constraint is compared by (RFC 5280 sec. 4.2.1.10). */
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. */
354
354
  function isFqdnHost(host) {
355
+ if (typeof host !== "string") return false;
355
356
  var h = stripTrailingDot(host);
356
- if (h === "" || _indexOf(h, ".") === -1) return false;
357
- if (!_allCharsIn(h, _FQDN_TABLE)) return false;
357
+ if (!_isHostName(h)) return false;
358
358
  if (_allCharsIn(h, _DIGDOT_TABLE)) return false;
359
359
  return true;
360
360
  }
@@ -391,6 +391,112 @@ function _isHostName(s) {
391
391
  return true;
392
392
  }
393
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
+
394
500
  var _ATEXT_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~");
395
501
 
396
502
  /** @internal A quoted local part (RFC 5321 sec. 4.1.2 Quoted-string): printable ASCII between
@@ -539,18 +645,23 @@ function constraintBaseRefusal(tag, base) {
539
645
  : "must be a host name, optionally written as a subtree with a leading dot";
540
646
  }
541
647
  /** @internal RFC 5280 sec. 4.2.1.10 says of a URI constraint: "The constraint MUST be specified as
542
- * a fully qualified domain name and MAY specify a host or a domain. Examples would be
543
- * host.example.com and .example.com." Its dNSName paragraph states no such requirement, so a single
544
- * label is a valid dNSName base and is not a valid URI base. A caller meets this rule at the door,
545
- * whether through `pki.trust.anchor` or through the validator's subtree seeds. A base a certificate
546
- * carries in its own extension never reaches a door, and `uriMatch` is what holds it to the rule
547
- * there, answering `path/name-constraint-unsupported` for a host it cannot read as fully qualified.
548
- * `constraintBaseUnreadable` does not judge label count and does not stand in for that check. */
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. */
549
655
  if (tag === 6) {
550
- var h = _hostConstraintBody(_toLowerCase(base));
551
- var dotted = _isHostName(h) && _indexOf(h, ".") !== -1 && !_allCharsIn(h, _DIGDOT_TABLE);
552
- return dotted ? null
553
- : "must be a fully qualified domain name, which RFC 5280 sec. 4.2.1.10 requires of a URI constraint and not of a dNSName one";
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";
554
665
  }
555
666
  return null;
556
667
  }
@@ -661,6 +772,7 @@ module.exports = intrinsic.freeze({
661
772
  isAttributeDescriptor: isAttributeDescriptor,
662
773
  lowerAscii: lowerAscii, uriSchemeIs: uriSchemeIs,
663
774
  stripTrailingDot: stripTrailingDot, isFqdnHost: isFqdnHost, splitMailbox: splitMailbox,
775
+ isHostName: isHostName, hostLabels: hostLabels, isXnLabel: isXnLabel, uriParts: uriParts,
664
776
  constraintBaseRefusal: constraintBaseRefusal,
665
777
  constraintBaseUnreadable: constraintBaseUnreadable,
666
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
- module.exports = intrinsic.freeze({ decode: decode, showValue: showValue, keyOf: keyOf });
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
+ });