@blamejs/pki 0.8.31 → 0.8.49

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 (87) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README.md +66 -15
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +26 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +1008 -58
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +33 -6
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +228 -2
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/guard-list.js CHANGED
@@ -6,15 +6,43 @@
6
6
  var _freeze = require("./guard-intrinsic").freeze;
7
7
  var _defineProperty = require("./guard-intrinsic").defineProperty;
8
8
  var _isArray = require("./guard-intrinsic").isArray;
9
+ var _arrSort = require("./guard-intrinsic").sort;
10
+ var _isSafeInteger = require("./guard-intrinsic").isSafeInteger;
9
11
 
10
12
  var MAX_ARRAY_LENGTH = 4294967295;
11
13
 
14
+ /** @internal The receiver's length, NARROWED to a number rather than merely captured. `Array.isArray`
15
+ * is true for a Proxy whose target is an array, and a `get` trap can answer `length` with an OBJECT:
16
+ * capturing an object captures no value, so every later use coerces it again and each coercion may
17
+ * answer differently. MEASURED through `pki.merkle.root`, whose captured count was handed to the leaf
18
+ * cap as 0 and then walked as 4, returning a four-leaf root the cap had approved as empty and clearing
19
+ * `MERKLE_MAX_LEAVES` the same way. A length that is not already a non-negative safe integer describes
20
+ * no array any of these walks can take, so it is refused rather than coerced. Every walk below takes
21
+ * its count from here, so none of them can hold a value that changes between two reads.
22
+ *
23
+ * Exported, because the walks in `guard-bytes` and `guard-identifier` take a caller's list too, and one
24
+ * of them is a DOOR: a Proxy over `["a", "secret"]` whose length coerced to 2 and then to 0 walked
25
+ * `refuseAccessorFields` past the accessor named `secret` and returned as though it had checked it. A
26
+ * door that can be walked past is not a door, so the three families share this one narrow.
27
+ *
28
+ * @enforced-by behavioral -- `x.length` has no rename-proof shape that separates a caller's list from
29
+ * a local array, and the RED vectors are the shrinking and object-valued lengths each walk is driven
30
+ * with.
31
+ */
32
+ function _count(list, verb) {
33
+ var n = list.length;
34
+ if (typeof n !== "number" || !_isSafeInteger(n) || n < 0) {
35
+ throw new TypeError("guard.list." + verb + ": the receiver's length is not a non-negative integer");
36
+ }
37
+ return n;
38
+ }
39
+
12
40
  // @enforced-by guard-shape-reinlined
13
41
  // @guard-shape defineProperty\(\s*\w+\s*,\s*\w+\.length\s*,\s*\{\s*value:
14
42
  // @guard-via guard\.list\.append\(
15
43
  function append(list, value) {
16
44
  if (!_isArray(list)) throw new TypeError("guard.list.append: the receiver must be an array");
17
- var n = list.length;
45
+ var n = _count(list, "append");
18
46
  if (n >= MAX_ARRAY_LENGTH) throw new RangeError("guard.list.append: the array is at its maximum length");
19
47
  _defineProperty(list, n, { value: value, writable: true, enumerable: true, configurable: true });
20
48
  return list;
@@ -23,35 +51,35 @@ function append(list, value) {
23
51
  // @enforced-by behavioral -- `.indexOf(x) !== -1` has no rename-proof form that separates a
24
52
  function contains(list, value) {
25
53
  if (!list) return false;
26
- for (var i = 0, n = list.length; i < n; i++) if (list[i] === value) return true;
54
+ for (var i = 0, n = _count(list, "contains"); i < n; i++) if (list[i] === value) return true;
27
55
  return false;
28
56
  }
29
57
 
30
58
  // @enforced-by behavioral -- the composition of contains() has no rename-proof shape of its own;
31
59
  function containsAll(list, values) {
32
60
  if (!values) return true;
33
- for (var i = 0, n = values.length; i < n; i++) if (!contains(list, values[i])) return false;
61
+ for (var i = 0, n = _count(values, "containsAll"); i < n; i++) if (!contains(list, values[i])) return false;
34
62
  return true;
35
63
  }
36
64
 
37
65
  // @enforced-by behavioral -- `.some(` has legitimate non-rule siblings (building a list, reporting
38
66
  function anyMatches(list, predicate) {
39
67
  if (!list) return false;
40
- for (var i = 0, n = list.length; i < n; i++) if (predicate(list[i], i)) return true;
68
+ for (var i = 0, n = _count(list, "anyMatches"); i < n; i++) if (predicate(list[i], i)) return true;
41
69
  return false;
42
70
  }
43
71
 
44
72
  // @enforced-by behavioral -- see anyMatches; the RED vector (an untrusted signer keeps
45
73
  function allMatch(list, predicate) {
46
74
  if (!list) return true;
47
- for (var i = 0, n = list.length; i < n; i++) if (!predicate(list[i], i)) return false;
75
+ for (var i = 0, n = _count(list, "allMatch"); i < n; i++) if (!predicate(list[i], i)) return false;
48
76
  return true;
49
77
  }
50
78
 
51
79
  // @enforced-by caller-array-copied-live
52
80
  function snapshot(list) {
53
81
  if (!_isArray(list)) throw new TypeError("guard.list.snapshot: the receiver must be an array");
54
- var n = list.length;
82
+ var n = _count(list, "snapshot");
55
83
  var out = [];
56
84
  for (var i = 0; i < n; i++) {
57
85
  _defineProperty(out, i, { value: list[i], writable: true, enumerable: true, configurable: true });
@@ -59,11 +87,36 @@ function snapshot(list) {
59
87
  return out;
60
88
  }
61
89
 
90
+ /** @internal A sorted COPY, taken and ordered through the captured operations. A DER SET OF is ordered
91
+ * by its encoded bytes, so the sort decides the bytes emitted and the comparison decides the order:
92
+ * MEASURED, replacing either `Array.prototype.slice` or `Array.prototype.sort` made a certification
93
+ * request's attribute set encode a member other than the one that had just been validated, on a path
94
+ * whose output is then signed. The snapshot comes first, so the caller's array is never reordered.
95
+ *
96
+ * @enforced-by behavioral -- `x.slice().sort(cmp)` has no rename-proof shape of its own beyond the two
97
+ * method names, and the conformance vector is the SET-OF ordering one each format already carries.
98
+ */
99
+ function sortedCopy(list, compare) {
100
+ var out = snapshot(list);
101
+ return _arrSort(out, compare);
102
+ }
103
+
104
+ /** @internal `onCount`, where a caller passes one, is handed the count this walk will use, before any
105
+ * slot is read. It is how a caller caps the length WITHOUT reading it a second time. A caller that read
106
+ * `list.length` itself to cap it and then called this had two reads of one quantity, and they can
107
+ * disagree: `Array.isArray` is true for a Proxy whose target is an array, and a `get` trap answers
108
+ * "length" afresh each time, so MEASURED, a cap that saw 0 passed while the walk below saw 1 and the
109
+ * caller committed to a one-element result the cap had approved as empty. The caller keeps its own typed
110
+ * error and its own message; only the number is the guard's to hand over. */
62
111
  // @enforced-by caller-array-copied-live
63
- function copyMap(list, fn) {
112
+ function copyMap(list, fn, onCount) {
64
113
  if (!_isArray(list)) throw new TypeError("guard.list.copyMap: the receiver must be an array");
65
114
  if (typeof fn !== "function") throw new TypeError("guard.list.copyMap: the second argument must be a function");
66
- var n = list.length;
115
+ if (onCount !== undefined && typeof onCount !== "function") {
116
+ throw new TypeError("guard.list.copyMap: onCount must be a function when supplied");
117
+ }
118
+ var n = _count(list, "copyMap");
119
+ if (onCount !== undefined) onCount(n);
67
120
  var out = [];
68
121
  for (var i = 0; i < n; i++) {
69
122
  _defineProperty(out, i, { value: fn(list[i], i), writable: true, enumerable: true, configurable: true });
@@ -72,7 +125,9 @@ function copyMap(list, fn) {
72
125
  }
73
126
 
74
127
  module.exports = _freeze({
128
+ count: _count,
75
129
  append: append,
130
+ sortedCopy: sortedCopy,
76
131
  contains: contains,
77
132
  containsAll: containsAll,
78
133
  anyMatches: anyMatches,
package/lib/guard-name.js CHANGED
@@ -4,7 +4,8 @@
4
4
  // @internal
5
5
 
6
6
  var intrinsic = require("./guard-intrinsic");
7
- var _append = require("./guard-list").append;
7
+ var guardList = require("./guard-list");
8
+ var _append = guardList.append;
8
9
  var _assertCanonicalOid = require("./guard-identifier").assertCanonicalOid;
9
10
  var _ipUtils = require("./ip-utils");
10
11
  var _isIPv4 = _ipUtils.isIPv4;
@@ -33,6 +34,12 @@ var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
33
34
 
34
35
  // @enforced-by behavioral -- the control-byte reject has no rename-proof code
35
36
  function assertNoControlBytes(str, E, code, label) {
37
+ /** @internal A PRIMITIVE string, required rather than assumed. Every lib caller passes one, but this
38
+ * is exported, and on anything else the scan reads a `length` and a character through hooks that can
39
+ * answer differently on each of the two reads per position: a scan that is handed an allowed
40
+ * character at each position it tests never examines one stable value. A primitive's length and
41
+ * characters cannot change, which is what makes every read below the same read. */
42
+ if (typeof str !== "string") throw E(code, label + " must be a string to be scanned for control bytes");
36
43
  for (var i = 0; i < str.length; i++) {
37
44
  var c = _charCodeAt(str, i);
38
45
  if (c === 0 || (c < 0x20 && c !== 0x09)) {
@@ -46,7 +53,11 @@ function assertNoControlBytes(str, E, code, label) {
46
53
  function assertPrintableIa5(buf, E, code, label) {
47
54
  var n = intrinsic.sizeOf(buf);
48
55
  for (var i = 0; i < n; i++) {
49
- if (buf[i] < 0x20 || buf[i] > 0x7e) {
56
+ /** @internal ONE read per position. Written `buf[i] < 0x20 || buf[i] > 0x7e` the byte was read
57
+ * twice whenever the first test was false, so two answers could pass both tests although neither
58
+ * is printable: a high answer clears the low bound and a low answer clears the high one. */
59
+ var byte = buf[i];
60
+ if (byte < 0x20 || byte > 0x7e) {
50
61
  throw E(code, label + " must be a printable IA5String (no control bytes)");
51
62
  }
52
63
  }
@@ -66,29 +77,51 @@ function _canonAttrValue(v, E, code, label) {
66
77
  }
67
78
  return _toLowerCase(out);
68
79
  }
80
+ /** @internal Both lengths are NARROWED where they are taken, and every test and walk below asks the
81
+ * local. `Array.isArray` is true for a Proxy whose target is an array and a `get` trap answers `length`
82
+ * afresh on each read, so the equality test and the walk that follows it could see two different counts:
83
+ * a length answering equal to its partner and then shorter had two names that differ in a later element
84
+ * compare EQUAL, and this comparison is what chains a certificate to its issuer, matches a CRL to its
85
+ * scope and applies a name constraint. Returned as a pair so the caller holds one count per side. */
69
86
  function _assertSequence(a, b, E, code, label, what) {
70
87
  if (!_isArray(a) || !_isArray(b)) {
71
88
  throw E(code, "cannot compare " + label + ": " + what + " comparison requires the RDN sequence on both sides (pass name.rdns, not the parsed Name)");
72
89
  }
73
- if (_hasHole(a) || _hasHole(b)) {
90
+ var na = guardList.count(a, "name comparison");
91
+ var nb = guardList.count(b, "name comparison");
92
+ if (_hasHole(a, na) || _hasHole(b, nb)) {
74
93
  throw E(code, "cannot compare " + label + ": " + what + " comparison requires a dense sequence on both sides");
75
94
  }
95
+ return { a: na, b: nb };
76
96
  }
77
97
 
78
- function _hasHole(arr) {
79
- for (var i = 0; i < arr.length; i++) { if (!_hasOwn(arr, i)) return true; }
98
+ function _hasHole(arr, n) {
99
+ for (var i = 0; i < n; i++) { if (!_hasOwn(arr, i)) return true; }
80
100
  return false;
81
101
  }
82
102
  // @enforced-by guard-shape-reinlined (shares the canonicalization shape declared on dnEqual)
83
103
  function rdnEqual(a, b, E, code, label) {
84
- _assertSequence(a, b, E, code, label, "an RDN");
85
- if (a.length !== b.length) return false;
104
+ var n = _assertSequence(a, b, E, code, label, "an RDN");
105
+ if (n.a !== n.b) return false;
86
106
  var used = _create(null);
87
- for (var i = 0; i < a.length; i++) {
107
+ for (var i = 0; i < n.a; i++) {
108
+ /** @internal ONE read of the element, and one of each field, into locals the comparison then asks.
109
+ * Written `a[i].type === b[j].type && canon(a[i].value) === canon(b[j].value)` the element was read
110
+ * TWICE per side, so a Proxy answering a different attribute on each index read had the type come
111
+ * from one attribute and the value from another: measured, an RDN whose first read answered
112
+ * `{2.5.4.3, "evil"}` and whose second answered `{2.5.4.10, "good"}` compared EQUAL to
113
+ * `{2.5.4.3, "good"}`, matching neither. A name that equals a name it does not hold is this
114
+ * comparison's one unacceptable answer, since it is what chains a certificate to its issuer. */
115
+ var ea = a[i];
116
+ var typeA = ea == null ? undefined : ea.type;
117
+ var valueA = _canonAttrValue(ea == null ? undefined : ea.value, E, code, label);
88
118
  var found = false;
89
- for (var j = 0; j < b.length; j++) {
119
+ for (var j = 0; j < n.b; j++) {
90
120
  if (used[j]) continue;
91
- if (a[i].type === b[j].type && _canonAttrValue(a[i].value, E, code, label) === _canonAttrValue(b[j].value, E, code, label)) {
121
+ var eb = b[j];
122
+ var typeB = eb == null ? undefined : eb.type;
123
+ if (typeA !== typeB) continue;
124
+ if (valueA === _canonAttrValue(eb == null ? undefined : eb.value, E, code, label)) {
92
125
  used[j] = true; found = true; break;
93
126
  }
94
127
  }
@@ -99,9 +132,9 @@ function rdnEqual(a, b, E, code, label) {
99
132
  // @enforced-by guard-shape-reinlined
100
133
  // @guard-shape replace\(/\\s\+/g,
101
134
  function dnEqual(rdnsA, rdnsB, E, code, label) {
102
- _assertSequence(rdnsA, rdnsB, E, code, label, "a distinguished-name");
103
- if (rdnsA.length !== rdnsB.length) return false;
104
- for (var i = 0; i < rdnsA.length; i++) {
135
+ var n = _assertSequence(rdnsA, rdnsB, E, code, label, "a distinguished-name");
136
+ if (n.a !== n.b) return false;
137
+ for (var i = 0; i < n.a; i++) {
105
138
  if (!rdnEqual(rdnsA[i], rdnsB[i], E, code, label)) return false;
106
139
  }
107
140
  return true;
@@ -331,6 +364,11 @@ function _allCharsIn(s, table) {
331
364
  return true;
332
365
  }
333
366
 
367
+ /** @internal The one trailing dot a host name may be written with, removed before it is compared.
368
+ *
369
+ * @enforced-by behavioral -- reading the last character and slicing it off is the shape of any string
370
+ * trim; the guard is the vector that a base written with the dot and a name written without it reach the
371
+ * same verdict, and that a name of one dot alone does not become the empty string */
334
372
  function stripTrailingDot(s) { return _charAt(s, s.length - 1) === "." ? _strSlice(s, 0, -1) : s; }
335
373
 
336
374
  /** @internal Whether `s` opens with `scheme` followed by a colon, the scheme compared without
@@ -349,7 +387,11 @@ function uriSchemeIs(s, scheme) {
349
387
  * label is a host like any other and `com` constrains a top-level domain. The host is read by its
350
388
  * labels rather than by which characters it holds: a character test admits an empty label and a
351
389
  * hyphen at a label edge, and the suffix comparison below would then match a base against a name
352
- * that is not a name. */
390
+ * that is not a name.
391
+ *
392
+ * @enforced-by behavioral -- the rule is which strings are host names and which are addresses, which no
393
+ * code shape states; the guard is the vector set driving a dotted-quad, a single label, an empty label and
394
+ * a hyphen at a label edge through a URI name constraint */
353
395
  function isFqdnHost(host) {
354
396
  if (typeof host !== "string") return false;
355
397
  var h = stripTrailingDot(host);
@@ -636,6 +678,88 @@ function uriEqual(a, b) {
636
678
  return pa === pb ? "match" : "no-match";
637
679
  }
638
680
 
681
+ /** @internal Whether two dNSName values name the same host. RFC 4343 makes DNS comparison
682
+ * case-insensitive, and RFC 5280 sec. 4.2.1.6 puts a dNSName in the preferred name syntax, so the value
683
+ * is ASCII and folding its case is the whole comparison. A value carrying a byte above 0x7f is not that
684
+ * syntax and is "not-comparable" rather than compared as written.
685
+ *
686
+ * Case is the only thing folded. A trailing dot is a different encoded value and stays one here, unlike in
687
+ * the name-constraint readers, where `stripTrailingDot` removes the dot an operator writes a base with:
688
+ * this compares what two artifacts encode, and folding the dot would report a match their bytes do not
689
+ * support.
690
+ *
691
+ * @enforced-by behavioral -- the case fold is `lowerAscii`, whose own shape detector catches a re-inlined
692
+ * 0x41..0x5a arithmetic anywhere in lib/; what this adds is applying it to a whole dNSName, which no shape
693
+ * separates from any other string comparison, and the guard is the pair differing only in case that
694
+ * matches beside the pair differing in a label that does not */
695
+ function dnsNameEqual(a, b) {
696
+ if (typeof a !== "string" || typeof b !== "string") return "not-comparable";
697
+ if (!_asciiHost(a) || !_asciiHost(b)) return "not-comparable";
698
+ return lowerAscii(a) === lowerAscii(b) ? "match" : "no-match";
699
+ }
700
+
701
+ /** @internal Whether two decoded GeneralName entries name the same thing, each form under its own rule.
702
+ * Takes the entries `pki.schema.x509.decodeExtensions` puts in a subjectAltName's `names`, keyed by the
703
+ * context tag number RFC 5280 sec. 4.2.1.6 assigns the form.
704
+ *
705
+ * Rendering every form to a string and comparing the renderings reads two names as different that name
706
+ * one entity, and two as the same that do not. A dNSName is case-insensitive, an rfc822Name is
707
+ * case-insensitive in its host and case-sensitive in its local part, a URI needs the sec. 7.4 preparation
708
+ * before an exact match, a directoryName needs the sec. 7.1 attribute-by-attribute comparison, and an
709
+ * iPAddress is bytes. Comparing renderings answers all five the same way, so `EXAMPLE.com` and
710
+ * `example.com` read as two hosts while a re-encoded but equivalent DN reads as two names.
711
+ *
712
+ * Returns "match", "no-match", or "not-comparable" for a form whose values this cannot hold to its rule,
713
+ * which the caller reports rather than resolving either way. A form with no comparison rule of its own,
714
+ * including otherName, compares by its encoded bytes: two such entries are equal when they are the same
715
+ * bytes and otherwise unequal, which is the only verdict available without knowing the form's semantics.
716
+ *
717
+ * `E`, `code` and `label` are the caller's, for the directoryName arm, which holds a malformed rdns
718
+ * sequence to the caller's own domain.
719
+ *
720
+ * @enforced-by behavioral -- a switch over GeneralName tag numbers is the shape of every reader of the
721
+ * type, so a pattern matching it cannot tell a comparison from a decode; the guard is the matrix of
722
+ * thirteen vectors, one pair per form in each direction, where the pair differing only where its rule folds
723
+ * MATCHES and the pair differing where its rule is exact does NOT. A single arm would pass for a
724
+ * comparison that folded case everywhere, which is the opposite defect.
725
+ */
726
+ function generalNameEqual(a, b, E, code, label) {
727
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return "not-comparable";
728
+ /** @internal Each member is read ONCE, and the tag, the dispatch and every comparison below ask the
729
+ * local. Read again per arm, the tag that was MATCHED need not be the tag that was DISPATCHED on: a
730
+ * getter reporting 8 to the equality test and 2 to the switch had two otherName values compared under
731
+ * the case-insensitive dNSName rule, which is looser than the rule their own tag names. The same
732
+ * shape sat on `value` and `bytes` in four arms, where the form check and the comparison were two
733
+ * reads: the check saw a Buffer and the comparison got whatever answered next. This verb decides
734
+ * whether a name matches a name constraint, so a match it reports is a subtree a certificate is
735
+ * allowed to speak for. */
736
+ var tagA = a.tagNumber, tagB = b.tagNumber;
737
+ if (tagA !== tagB) return "no-match";
738
+ var valueA = a.value, valueB = b.value;
739
+ switch (tagA) {
740
+ case 1: return emailEqual(valueA, valueB);
741
+ case 2: return dnsNameEqual(valueA, valueB);
742
+ case 4: {
743
+ var ra = valueA == null ? undefined : valueA.rdns;
744
+ var rb = valueB == null ? undefined : valueB.rdns;
745
+ if (!_isArray(ra) || !_isArray(rb)) return "not-comparable";
746
+ return dnEqual(ra, rb, E, code, label) ? "match" : "no-match";
747
+ }
748
+ case 6: return uriEqual(valueA, valueB);
749
+ case 7:
750
+ if (!_isBuffer(valueA) || !_isBuffer(valueB)) return "not-comparable";
751
+ return _bufferEquals(valueA, valueB) ? "match" : "no-match";
752
+ case 8:
753
+ if (typeof valueA !== "string" || typeof valueB !== "string") return "not-comparable";
754
+ return valueA === valueB ? "match" : "no-match";
755
+ default: {
756
+ var bytesA = a.bytes, bytesB = b.bytes;
757
+ if (!_isBuffer(bytesA) || !_isBuffer(bytesB)) return "not-comparable";
758
+ return _bufferEquals(bytesA, bytesB) ? "match" : "no-match";
759
+ }
760
+ }
761
+ }
762
+
639
763
  var _ATEXT_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~");
640
764
 
641
765
  /** @internal A quoted local part (RFC 5321 sec. 4.1.2 Quoted-string): printable ASCII between
@@ -693,7 +817,11 @@ function _isAddressLiteral(s) {
693
817
 
694
818
  /** @internal The two halves of a mailbox, `null` when it carries no separator and the string
695
819
  * "ambiguous" when it carries more than one. This is the split the rfc822Name comparison runs, so a
696
- * constraint the comparison cannot separate is one this answers the same way. */
820
+ * constraint the comparison cannot separate is one this answers the same way.
821
+ *
822
+ * @enforced-by behavioral -- splitting on a separator has no shape distinct from any other split; the
823
+ * guard is the vector that an address carrying two at-signs answers "ambiguous" rather than splitting on
824
+ * one of them, which is what a boundary using `indexOf` alone would do */
697
825
  function splitMailbox(addr) {
698
826
  var first = _indexOf(addr, "@");
699
827
  if (first === -1) return null;
@@ -761,7 +889,11 @@ function constraintBaseUnreadable(tag, base) {
761
889
  * form its tag names is not a narrower namespace: an empty dNSName base matches every name, and a
762
890
  * base holding characters no host name can hold matches none, so the same value that reads as a
763
891
  * restriction either lifts one or applies to nothing. The comparisons this mirrors are in
764
- * lib/path-validate.js, and a base refused here is one they would answer with no verdict. */
892
+ * lib/path-validate.js, and a base refused here is one they would answer with no verdict.
893
+ *
894
+ * @enforced-by behavioral -- a per-tag switch over name forms is the shape of every GeneralName reader in
895
+ * the toolkit, so no pattern tells a refusal from a decode; the guard is the vector set proving each tag's
896
+ * unenforceable base is refused where an anchor is built, an empty dNSName base among them */
765
897
  function constraintBaseRefusal(tag, base) {
766
898
  if (typeof base !== "string") return "must be a string for tag " + tag;
767
899
  if (tag === 1) {
@@ -811,7 +943,12 @@ var _ATV_KEYS = ["type", "value", "name"];
811
943
  * descriptors only. A Proxy answers its own reads and an accessor runs caller code, so either one
812
944
  * at any level is refused before anything is copied: a name that decides what it says while it is
813
945
  * being read can show a narrow value to a check and a wide one to the use, and where the name is a
814
- * namespace restriction that is the difference between a certificate being refused and admitted. */
946
+ * namespace restriction that is the difference between a certificate being refused and admitted.
947
+ *
948
+ * @enforced-by behavioral -- `getOwnPropertyDescriptor` is read legitimately across lib/, so a shape built
949
+ * on it would fire on every honest reader; the guard is the vector set handing an anchor builder a name
950
+ * whose attribute is an accessor, one inheriting from a Proxy, and one carrying an index hole, each refused
951
+ * before a value is copied */
815
952
  function copyDnRdns(rdns, E, code, who) {
816
953
  assertPlainDnRdns(rdns, E, code, who);
817
954
  /** @internal A name of no relative names is a prefix of every name, so as a constraint it reaches
@@ -857,7 +994,13 @@ function copyDnRdns(rdns, E, code, who) {
857
994
  /** @internal The shape check the copy above relies on. */
858
995
  function assertPlainDnRdns(rdns, E, code, who) {
859
996
  if (intrinsic.types.isProxy(rdns)) throw E(code, who + " must be a plain array, not a Proxy");
860
- for (var i = 0; i < rdns.length; i++) {
997
+ /** @internal An ARRAY, and its count narrowed where it is taken. The Proxy refusal above covers a
998
+ * Proxy; a plain object carrying a `length` GETTER is neither a Proxy nor an array, and it answered
999
+ * one count to this validation pass and another to the copy that follows, so the two passes covered
1000
+ * different elements and the copy carried what was never checked. */
1001
+ if (!_isArray(rdns)) throw E(code, who + " must be an array of relative distinguished names");
1002
+ var n = guardList.count(rdns, "assertPlainDnRdns");
1003
+ for (var i = 0; i < n; i++) {
861
1004
  var rd = intrinsic.getOwnPropertyDescriptor(rdns, i);
862
1005
  if (rd === undefined) continue;
863
1006
  if (!_hasOwn(rd, "value")) throw E(code, who + " entry " + i + " must be a data property, not an accessor");
@@ -885,7 +1028,11 @@ function assertPlainDnRdns(rdns, E, code, who) {
885
1028
  /** @internal The `rdns` of a directoryName base, captured the same way. An inherited Proxy answers
886
1029
  * `in` through its has trap, which is caller code running inside a read meant to be getter-free, so
887
1030
  * the prototype chain is walked before the property is asked for at all. Returns undefined when the
888
- * base carries no rdns array, which the caller reads as "not a directoryName base". */
1031
+ * base carries no rdns array, which the caller reads as "not a directoryName base".
1032
+ *
1033
+ * @enforced-by behavioral -- walking a prototype chain before a read has no shape distinct from any other
1034
+ * chain walk; the guard is the vector that a base inheriting from a Proxy is refused rather than having its
1035
+ * has trap answer the `in` test, which is the read this ordering exists to keep getter-free */
889
1036
  function capturedRdns(base, E, code, who) {
890
1037
  if (intrinsic.types.isProxy(base)) throw E(code, who + " must not be a Proxy");
891
1038
  var baseProto = intrinsic.getPrototypeOf(base);
@@ -912,7 +1059,7 @@ module.exports = intrinsic.freeze({
912
1059
  lowerAscii: lowerAscii, uriSchemeIs: uriSchemeIs,
913
1060
  stripTrailingDot: stripTrailingDot, isFqdnHost: isFqdnHost, splitMailbox: splitMailbox,
914
1061
  isHostName: isHostName, hostLabels: hostLabels, isXnLabel: isXnLabel, uriParts: uriParts,
915
- uriEqual: uriEqual,
1062
+ uriEqual: uriEqual, dnsNameEqual: dnsNameEqual, generalNameEqual: generalNameEqual,
916
1063
  constraintBaseRefusal: constraintBaseRefusal,
917
1064
  constraintBaseUnreadable: constraintBaseUnreadable,
918
1065
  });
@@ -3,10 +3,12 @@
3
3
  "use strict";
4
4
  // @internal
5
5
 
6
- var _isSafeInteger = require("./guard-intrinsic").isSafeInteger;
7
- var _freeze = require("./guard-intrinsic").freeze;
8
- var _Number = require("./guard-intrinsic").Number;
9
- var _BigInt = require("./guard-intrinsic").BigInt;
6
+ var intrinsic = require("./guard-intrinsic");
7
+ var _isSafeInteger = intrinsic.isSafeInteger;
8
+ var _freeze = intrinsic.freeze;
9
+ var _Number = intrinsic.Number;
10
+ var _BigInt = intrinsic.BigInt;
11
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
10
12
 
11
13
  var UINT31_MAX = 2147483647n;
12
14
  var SAFE_MAX = 9007199254740991n;
@@ -56,8 +58,43 @@ function uint64(value, E, code, label) {
56
58
  return v;
57
59
  }
58
60
 
61
+ var ASCII_ZERO = 48;
62
+ var ASCII_NINE = 57;
63
+ var UINT64_MAX_DIGITS = 20;
64
+
65
+ /** @internal An ASCII decimal integer arriving as TEXT: a C2SP checkpoint's tree-size line, or
66
+ * protojson, which encodes int64 and uint64 as JSON strings. `BigInt(s)` is the wrong tool here
67
+ * and silently converts "", " 12 ", "007", "+7", "-0", "0x10", "0b11" and "0o17"; the empty string
68
+ * becoming 0n turns a missing size into a claim about an empty tree. The scan accepts ASCII digits
69
+ * and nothing else, so a radix or sign notation added later is refused without being enumerated.
70
+ * UINT64_MAX_DIGITS is the width of 18446744073709551615, so a longer run is out of range before
71
+ * a value is built and a hostile run of digits never becomes a BigInt. */
72
+ // @enforced-by guard-shape-reinlined
73
+ // @guard-shape \* 10n \+
74
+ function decimalUint64(value, E, code, label) {
75
+ if (typeof value !== "string" || value.length === 0) {
76
+ throw E(code, label + " must be a non-empty string holding an ASCII decimal integer");
77
+ }
78
+ if (value.length > 1 && _charCodeAt(value, 0) === ASCII_ZERO) {
79
+ throw E(code, label + " must not carry a leading zero");
80
+ }
81
+ if (value.length > UINT64_MAX_DIGITS) {
82
+ throw E(code, label + " must be a uint64 in [0, 2^64)");
83
+ }
84
+ var v = 0n;
85
+ for (var i = 0; i < value.length; i++) {
86
+ var c = _charCodeAt(value, i);
87
+ if (c < ASCII_ZERO || c > ASCII_NINE) {
88
+ throw E(code, label + " must be ASCII decimal digits with no sign, radix prefix or whitespace");
89
+ }
90
+ v = v * 10n + _BigInt(c - ASCII_ZERO);
91
+ }
92
+ if (v > UINT64_MAX) throw E(code, label + " must be a uint64 in [0, 2^64)");
93
+ return v;
94
+ }
95
+
59
96
  // @enforced-by guard-shape-reinlined
60
- // @guard-shape Number\.isSafeInteger\(\s*[A-Za-z_$][\w$]*\s*\)\s*\)\s*return BigInt
97
+ // @guard-shape Number\.isSafeInteger\(\s*[A-Za-z_$][\w$]*\s*\)\s*\)\s*return BigInt|typeof\s+[A-Za-z_$][\w$.\[\]]*\s*===\s*""\s*\?\s*[A-Za-z_$][\w$.\[\]]*\s*:\s*_?BigInt\(
61
98
  function authoredInteger(value, E, code, label) {
62
99
  if (typeof value === "bigint") return value;
63
100
  if (typeof value === "number" && _isSafeInteger(value)) return _BigInt(value);
@@ -66,5 +103,6 @@ function authoredInteger(value, E, code, label) {
66
103
 
67
104
  module.exports = _freeze({
68
105
  int: int, uint31: uint31, positiveInt31: positiveInt31, uint64: uint64,
106
+ decimalUint64: decimalUint64,
69
107
  authoredInteger: authoredInteger,
70
108
  });
package/lib/guard-text.js CHANGED
@@ -7,6 +7,7 @@ var bytes = require("./guard-bytes");
7
7
  var intrinsic = require("./guard-intrinsic");
8
8
 
9
9
  var _toString = intrinsic.uncurry(Buffer.prototype.toString);
10
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
10
11
  var _decode = intrinsic.uncurry(TextDecoder.prototype.decode);
11
12
  var _byteLength = Buffer.byteLength;
12
13
  var _isInteger = Number.isInteger;
@@ -37,6 +38,45 @@ function decode(input, maxBytes, ErrorClass, spec) {
37
38
  throw new ErrorClass(spec.badInput, spec.label + " expects a string or a byte source (Buffer / TypedArray / DataView / ArrayBuffer)");
38
39
  }
39
40
 
41
+ /** A string is well-formed UTF-16 before it is converted to UTF-8 bytes. An unpaired surrogate has no
42
+ * UTF-8 encoding, and every conversion in the platform substitutes U+FFFD for it rather than failing:
43
+ * `Buffer.from(s, "utf8")`, `TextEncoder.encode` and `Buffer.byteLength` all do. So two strings that
44
+ * differ only in that code unit produce the SAME bytes, and wherever those bytes are signed, hashed or
45
+ * compared the substitution is a collision: a signature made over one verifies over the other, and the
46
+ * value a caller reads back is the other one. The string is scanned by code unit, per the project rule
47
+ * against regular expressions in lib/, and a well-formed pair is left alone, being an ordinary code
48
+ * point above the BMP.
49
+ *
50
+ * `label` names the string in the fault. The caller supplies its own ErrorClass and code so each
51
+ * boundary keeps its own domain/reason.
52
+ *
53
+ * The shape is a surrogate-boundary constant followed, inside the same function, by a throw: a range
54
+ * test that REFUSES. A function that tests the same range to MEASURE a string rather than to reject it
55
+ * is not this rule and is not matched, which is why the throw is part of the shape rather than the
56
+ * constants alone. Bounded by the function-closing brace rather than a character count, so it does not
57
+ * stop matching as a body grows.
58
+ *
59
+ * @enforced-by guard-shape-reinlined
60
+ * @guard-shape 0x[dD](?:[bB][fF][fF]|[cC]00)(?:(?!\n\})[\s\S])*?throw
61
+ */
62
+ function assertWellFormedUtf16(s, ErrorClass, code, label) {
63
+ for (var i = 0; i < s.length; i++) {
64
+ var u = _charCodeAt(s, i);
65
+ if (u < 0xd800 || u > 0xdfff) continue;
66
+ if (u > 0xdbff) {
67
+ throw new ErrorClass(code, label + " has an unpaired low surrogate at index " + i +
68
+ "; it has no UTF-8 encoding and would be converted to U+FFFD, colliding with other strings");
69
+ }
70
+ var next = i + 1 < s.length ? _charCodeAt(s, i + 1) : -1;
71
+ if (next < 0xdc00 || next > 0xdfff) {
72
+ throw new ErrorClass(code, label + " has an unpaired high surrogate at index " + i +
73
+ "; it has no UTF-8 encoding and would be converted to U+FFFD, colliding with other strings");
74
+ }
75
+ i += 1;
76
+ }
77
+ return s;
78
+ }
79
+
40
80
  // @enforced-by behavioral -- a safe error-message formatter has no rename-proof code shape; the RED vector (a BigInt or cyclic caller value renders to a typed error, never a native TypeError out of a raw JSON.stringify) is the guard.
41
81
  function showValue(v) {
42
82
  if (typeof v === "string") return intrinsic.stringify(v);
@@ -82,4 +122,5 @@ function _thrownFallback(e) {
82
122
 
83
123
  module.exports = intrinsic.freeze({
84
124
  decode: decode, showValue: showValue, keyOf: keyOf, describeThrown: describeThrown,
125
+ assertWellFormedUtf16: assertWellFormedUtf16,
85
126
  });
@@ -20,17 +20,45 @@ function _copyOwn(out, src) {
20
20
  }
21
21
  }
22
22
 
23
+ /** @internal Resolving a promise reads `then` off the value, so an inherited accessor would run with
24
+ * the verdict as its receiver and could rewrite a field or hand the caller a different object. An own
25
+ * `then` ends that lookup, and is non-enumerable so keys, JSON, a spread and a deep-equality
26
+ * comparison do not see it.
27
+ *
28
+ * `shield` adds that sentinel to the object it is given. It is the form for a result whose SHAPE must
29
+ * survive: a parse result carrying nested structure, a row inside an array a caller indexes, an
30
+ * object whose prototype is null. `of` copies, which is right for a verdict assembled from a literal
31
+ * and wrong for anything whose non-enumerable members, accessors or identity a caller reads.
32
+ *
33
+ * @enforced-by behavioral -- the defect is the ABSENCE of this call at a construction site, which has
34
+ * no code shape of its own. The guard is the conformance vector per verb: a verdict-shaped object
35
+ * returned to a caller while an inherited `Object.prototype.then` is installed must still report the
36
+ * toolkit's answer.
37
+ */
38
+ function shield(target) {
39
+ if (target === null || typeof target !== "object") return target;
40
+ /** @internal An own `then` the object already carries is left alone when it is not callable: the
41
+ * lookup ends there already, and overwriting it would DELETE a member. A parsed JSON document may
42
+ * carry a `then` of its own, and `{"then":42,"other":1}` round-tripped as `{"other":1}` when this
43
+ * replaced it unconditionally. A CALLABLE own `then` is the thenable itself rather than data, and
44
+ * JSON cannot produce one, so it is replaced. */
45
+ if (_hasOwn(target, "then")) {
46
+ var d = _getOwnPropertyDescriptor(target, "then");
47
+ /** @internal A DATA property only. An own accessor is replaced without being read: it answers each
48
+ * read separately, so one that returns a non-function now can return a function when the promise
49
+ * machinery looks, and reading it to decide would be the one read that does not count. */
50
+ if (d && _hasOwn(d, "value") && typeof d.value !== "function") return target;
51
+ }
52
+ _defineProperty(target, "then", { value: undefined, enumerable: false, configurable: true, writable: true });
53
+ return target;
54
+ }
55
+
23
56
  // @enforced-by behavioral -- the defect is the ABSENCE of this call at a construction site, not a
24
57
  function of(src, extras) {
25
58
  var out = {};
26
59
  _copyOwn(out, src);
27
60
  if (extras != null) _copyOwn(out, extras);
28
- /** @internal Resolving a promise reads `then` off the value, so an inherited accessor would run
29
- * with the verdict as its receiver and could rewrite a field or hand the caller a different
30
- * object. An own `then` ends that lookup, and is non-enumerable so keys, JSON, a spread and a
31
- * deep-equality comparison do not see it. */
32
- _defineProperty(out, "then", { value: undefined, enumerable: false, configurable: true, writable: true });
33
- return out;
61
+ return shield(out);
34
62
  }
35
63
 
36
64
  // @enforced-by behavioral -- Object.hasOwn and hasOwnProperty are replaceable at any time, and a
@@ -41,7 +69,10 @@ function carries(obj, key) {
41
69
  // @enforced-by behavioral -- for a target that cannot be rebuilt, an Error carrying a field a
42
70
  function set(target, key, value) {
43
71
  _defineProperty(target, key, { value: value, enumerable: true, configurable: true, writable: true });
44
- return target;
72
+ /** @internal The target leaves here carrying the sentinel as well. A field set on an object is the
73
+ * same kind of value a verdict is, and a target that reached a promise resolution without one would
74
+ * hand the lookup to Object.prototype however it was assembled. */
75
+ return shield(target);
45
76
  }
46
77
 
47
- module.exports = _freeze({ of: of, carries: carries, set: set });
78
+ module.exports = _freeze({ of: of, shield: shield, carries: carries, set: set });