@blamejs/pki 0.6.14 → 0.6.16

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 (112) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/lib/acme.js +59 -815
  3. package/lib/asn1-der.js +7 -245
  4. package/lib/attrcert-sign.js +12 -260
  5. package/lib/attrcert-verify.js +1 -7
  6. package/lib/byte-reader.js +0 -8
  7. package/lib/byte-writer.js +0 -11
  8. package/lib/cbor-det.js +0 -54
  9. package/lib/cmc-build.js +15 -357
  10. package/lib/cmc-verify.js +6 -239
  11. package/lib/cmp-build.js +41 -317
  12. package/lib/cmp-session.js +73 -760
  13. package/lib/cmp-verify.js +32 -488
  14. package/lib/cms-compress.js +3 -32
  15. package/lib/cms-decrypt.js +29 -294
  16. package/lib/cms-encrypt.js +14 -234
  17. package/lib/cms-sign.js +29 -324
  18. package/lib/cms-verify.js +9 -500
  19. package/lib/composite-kem.js +2 -102
  20. package/lib/composite-sig.js +1 -77
  21. package/lib/constants.js +0 -249
  22. package/lib/crl-sign.js +9 -237
  23. package/lib/crl-verify.js +1 -10
  24. package/lib/crmf-sign.js +19 -198
  25. package/lib/crmf-verify.js +1 -12
  26. package/lib/csr-sign.js +3 -34
  27. package/lib/csr-verify.js +1 -12
  28. package/lib/ct.js +28 -253
  29. package/lib/edwards-point.js +10 -44
  30. package/lib/est.js +28 -764
  31. package/lib/framework-error.js +4 -182
  32. package/lib/guard-all.js +1 -58
  33. package/lib/guard-async.js +1 -36
  34. package/lib/guard-bytes.js +6 -650
  35. package/lib/guard-compress.js +5 -102
  36. package/lib/guard-crypto.js +1 -40
  37. package/lib/guard-der.js +2 -93
  38. package/lib/guard-encoding.js +2 -60
  39. package/lib/guard-header.js +1 -36
  40. package/lib/guard-identifier.js +7 -531
  41. package/lib/guard-intrinsic.js +1 -150
  42. package/lib/guard-json.js +10 -73
  43. package/lib/guard-limits.js +1 -77
  44. package/lib/guard-list.js +1 -49
  45. package/lib/guard-name.js +3 -209
  46. package/lib/guard-parsed.js +7 -335
  47. package/lib/guard-range.js +1 -71
  48. package/lib/guard-secret.js +1 -102
  49. package/lib/guard-text.js +1 -64
  50. package/lib/guard-time.js +1 -97
  51. package/lib/hpke.js +3 -105
  52. package/lib/http-digest.js +14 -142
  53. package/lib/http-retry-after.js +1 -47
  54. package/lib/http-transport.js +21 -131
  55. package/lib/inspect.js +30 -284
  56. package/lib/ip-utils.js +6 -47
  57. package/lib/jose.js +7 -142
  58. package/lib/key.js +2 -67
  59. package/lib/lint.js +9 -194
  60. package/lib/merkle.js +1 -27
  61. package/lib/mime.js +9 -82
  62. package/lib/ocsp-request-verify.js +1 -14
  63. package/lib/ocsp-verify.js +1 -82
  64. package/lib/ocsp.js +11 -169
  65. package/lib/oid.js +5 -337
  66. package/lib/path-validate.js +103 -1492
  67. package/lib/pbes2.js +2 -89
  68. package/lib/pkcs12-build.js +31 -379
  69. package/lib/pki-build.js +64 -290
  70. package/lib/rc2.js +2 -18
  71. package/lib/rfc3339.js +4 -27
  72. package/lib/scep.js +140 -286
  73. package/lib/schema-all.js +0 -122
  74. package/lib/schema-attrcert.js +4 -146
  75. package/lib/schema-c509.js +153 -802
  76. package/lib/schema-cmc.js +5 -220
  77. package/lib/schema-cmp.js +2 -223
  78. package/lib/schema-cms.js +13 -427
  79. package/lib/schema-crl.js +5 -68
  80. package/lib/schema-crmf.js +6 -167
  81. package/lib/schema-csr.js +1 -54
  82. package/lib/schema-csrattrs.js +2 -87
  83. package/lib/schema-engine.js +0 -152
  84. package/lib/schema-ocsp.js +0 -117
  85. package/lib/schema-pkcs12.js +2 -93
  86. package/lib/schema-pkcs8.js +3 -35
  87. package/lib/schema-pkix.js +24 -655
  88. package/lib/schema-smime.js +0 -54
  89. package/lib/schema-tsp.js +3 -99
  90. package/lib/schema-x509.js +4 -63
  91. package/lib/shbs.js +11 -95
  92. package/lib/sign-scheme.js +2 -97
  93. package/lib/sigstore.js +9 -169
  94. package/lib/sleep.js +2 -8
  95. package/lib/smime.js +15 -408
  96. package/lib/tls-cert-compress.js +0 -62
  97. package/lib/trust.js +19 -185
  98. package/lib/tsp-sign.js +16 -270
  99. package/lib/validator-all.js +1 -30
  100. package/lib/validator-attcert.js +2 -46
  101. package/lib/validator-cose.js +2 -153
  102. package/lib/validator-keydesc.js +1 -37
  103. package/lib/validator-sig.js +1 -46
  104. package/lib/validator-tls.js +1 -88
  105. package/lib/validator-tpm.js +19 -122
  106. package/lib/verify-seam.js +1 -24
  107. package/lib/webauthn-mds.js +3 -400
  108. package/lib/webauthn.js +12 -833
  109. package/lib/webcrypto.js +5 -485
  110. package/lib/x509-sign.js +4 -108
  111. package/package.json +1 -1
  112. package/sbom.cdx.json +6 -6
package/lib/guard-list.js CHANGED
@@ -1,86 +1,38 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal, so no operator-facing namespace. The documented surface is every verdict whose rule
6
- // composes this guard: pki.path.validate (requiredEku, the policy tree), pki.cms.verify (the
7
- // message trust verdict), the OCSP delegated-responder check, the TPM AIK purpose check, and the
8
- // timestamp unknown-critical-extension gate.
9
- //
10
- // guard-list, membership decided by comparison and never by a prototype method.
11
- //
12
- // Defends the intrinsic-substitution class (CWE-349 / CWE-693: a control the caller can neutralize).
13
- // Written `list.indexOf(v) !== -1` or `list.some(fn)`, a membership test asks the runtime, at the
14
- // moment of the call, which function `indexOf` or `some` is. A caller who receives control after
15
- // this package loads, through an option accessor or a `toJSON` or while a verb is suspended on an
16
- // await, can replace it. The answer becomes whatever they chose, while every check around it still
17
- // runs and still passes.
18
- //
19
- // The distinction that matters is what the answer IS. A scan over a local accumulator produces a
20
- // value. A scan that decides whether a certificate carries a purpose, whether a policy is expected,
21
- // or whether every signer is trusted IS the rule. Measured on the tree before this guard existed:
22
- // with `Array.prototype.indexOf` replaced to report every value present, a certificate restricted
23
- // to emailProtection satisfied a requiredEku of serverAuth, and pki.path.validate went from
24
- // valid:false to valid:true.
25
- //
26
- // Capturing the method at load (guard-intrinsic) is the other half of the same defense, and is what
27
- // a module already inside that discipline uses. This guard is for the rule sites themselves. A loop
28
- // over `===` has nothing to replace, so it holds whatever the prototype says, in a module that has
29
- // been swept and in one that has not.
4
+ // @internal
30
5
 
31
6
  var _freeze = require("./guard-intrinsic").freeze;
32
7
 
33
- // contains(list, value) -> boolean. Strict-equality membership, the `indexOf(v) !== -1` rule.
34
- // A null / undefined list is honestly empty, never a throw: these run on verdict paths.
35
8
  // @enforced-by behavioral -- `.indexOf(x) !== -1` has no rename-proof form that separates a
36
- // membership RULE from its legitimate siblings: the identical token is a substring search
37
- // (`authority.indexOf(":")`, `addr.indexOf("@")`) and a local index lookup, and a shape matching
38
- // all of them fired on 37 sites of which most were strings. What makes a site this guard's is
39
- // what the answer IS -- a decision about a certificate, a policy or a signer -- and that is a
40
- // judgment no regex makes. The RED vectors are the guard: a codeSigning-only certificate still
41
- // fails a serverAuth requiredEku, and an unauthorized responder is still refused, each with
42
- // Array.prototype.indexOf replaced after load.
43
9
  function contains(list, value) {
44
10
  if (!list) return false;
45
11
  for (var i = 0, n = list.length; i < n; i++) if (list[i] === value) return true;
46
12
  return false;
47
13
  }
48
14
 
49
- // containsAll(list, values) -> boolean. Every one of `values` present in `list` -- the shape a
50
- // "these purposes are all required" rule needs. No `values` is vacuously satisfied.
51
15
  // @enforced-by behavioral -- the composition of contains() has no rename-proof shape of its own;
52
- // the RED vectors (a codeSigning-only certificate still fails a serverAuth requiredEku with
53
- // Array.prototype.indexOf replaced) are the guard.
54
16
  function containsAll(list, values) {
55
17
  if (!values) return true;
56
18
  for (var i = 0, n = values.length; i < n; i++) if (!contains(list, values[i])) return false;
57
19
  return true;
58
20
  }
59
21
 
60
- // anyMatches(list, predicate) -> boolean. The `some(fn)` rule: does any member satisfy it? The
61
- // predicate is the caller's own function, so what this guard removes is the DISPATCH, not the test.
62
22
  // @enforced-by behavioral -- `.some(` has legitimate non-rule siblings (building a list, reporting
63
- // a count), so a blanket detector would false-fire; the RED vectors (a critical extension is
64
- // still seen, an untrusted signer still reported) are the guard.
65
23
  function anyMatches(list, predicate) {
66
24
  if (!list) return false;
67
25
  for (var i = 0, n = list.length; i < n; i++) if (predicate(list[i], i)) return true;
68
26
  return false;
69
27
  }
70
28
 
71
- // allMatch(list, predicate) -> boolean. The `every(fn)` rule, same reasoning as anyMatches. An
72
- // empty list is vacuously true, matching Array.prototype.every -- a caller that needs "non-empty
73
- // AND all" tests the length itself, as pki.cms.verify does for the trust verdict.
74
29
  // @enforced-by behavioral -- see anyMatches; the RED vector (an untrusted signer keeps
75
- // trusted:false with Array.prototype.every replaced) is the guard.
76
30
  function allMatch(list, predicate) {
77
31
  if (!list) return true;
78
32
  for (var i = 0, n = list.length; i < n; i++) if (!predicate(list[i], i)) return false;
79
33
  return true;
80
34
  }
81
35
 
82
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
83
- // call, so a writable export would let a caller replace the check rather than pass it.
84
36
  module.exports = _freeze({
85
37
  contains: contains,
86
38
  containsAll: containsAll,
package/lib/guard-name.js CHANGED
@@ -1,41 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // consumers whose name-string integrity composes this guard (pki.path.validate
7
- // DN chaining, pki.schema.x509 GeneralName / SAN decode).
8
- //
9
- // guard-name -- fail-closed name integrity: reject an embedded control byte in a
10
- // distinguished-name / SAN string, and compare two distinguished names by their
11
- // RFC 5280 sec. 7.1 canonical identity, never their raw bytes.
12
- //
13
- // Defends the name-truncation / display-confusion class (CVE-2009-2408): a NUL
14
- // or control byte embedded in a decoded name lets an attacker make two different
15
- // names compare equal (or a UI truncate at the NUL), so a cert issued for
16
- // "good.example.com\0.evil.com" is treated as "good.example.com". CWE-158
17
- // (improper neutralization of null byte) / CWE-20. The reject is at decode, so a
18
- // truncation name never reaches a comparison or a display.
19
- //
20
- // Defends the DN identity-vs-bytes class (CWE-706): a distinguished name has many
21
- // RFC 5280 sec. 7.1-equal DER encodings (case, whitespace, PrintableString vs
22
- // UTF8String). Binding identity to raw bytes (a byte compare, or hashing
23
- // name.bytes as a lookup key) silently treats two equal names as different, so
24
- // certificate chaining breaks, a revocation issuer / OCSP responder fails to
25
- // match, or (the mirror risk) a name constraint is escaped. Every DN identity
26
- // decision routes through the one canonical comparison here.
4
+ // @internal
27
5
 
28
6
  var intrinsic = require("./guard-intrinsic");
29
7
 
30
- // The string operations this module is built out of, taken from the prototypes at module load and
31
- // uncurried so no call site reads a property of them. Every value compared here is a primitive
32
- // string, so each call below would otherwise dispatch to whatever `String.prototype` holds at the
33
- // moment it runs, and those are ordinary writable properties. A `toLowerCase` that returns a
34
- // constant makes every DN compare equal to every other, which is the whole of this guard's job
35
- // answered by the caller: chaining accepts an unrelated issuer, a revocation entry matches a
36
- // certificate it was never written for, and a name constraint stops excluding anything. The same
37
- // reasoning covers the escaping helpers, where a replaced `charAt` decides what a report line says
38
- // a subject was. See guard-intrinsic for why the capture alone is not enough.
39
8
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
40
9
  var _charAt = intrinsic.uncurry(String.prototype.charAt);
41
10
  var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
@@ -45,29 +14,14 @@ var _lastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
45
14
  var _numToString = intrinsic.uncurry(Number.prototype.toString);
46
15
  var _create = intrinsic.create;
47
16
  var _hasOwn = intrinsic.hasOwn;
48
- // Already a plain function rather than a method, so it is invoked directly and has no receiver.
49
17
  var _fromCharCode = String.fromCharCode;
50
18
  var _String = String;
51
- // The shape test that decides whether a comparison can be performed at all. Refusing is the
52
- // fail-closed direction, so replacing this turns comparisons into refusals rather than into false
53
- // matches, and it is captured for the same reason as the rest: what a guard does is settled before
54
- // a caller runs, or it is settled by the caller.
55
19
  var _isArray = Array.isArray;
56
- // Byte identity and the shape test that gates it, both captured for the reason above: a
57
- // distribution-point comparison asks whether two encodings are the same bytes, and an object
58
- // supplying its own `equals` would be answering that question about itself.
59
20
  var _isBuffer = intrinsic.isBuffer;
60
21
  var _bufferEquals = intrinsic.bufferEquals;
61
22
  var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
62
23
 
63
- // assertNoControlBytes(str, E, code, label) -> str | throws E(code, ...)
64
- // DirectoryString policy (a DN attribute value): reject NUL and C0 control bytes
65
- // in a DECODED name string. TAB (0x09) is exempt; printable non-ASCII (a
66
- // UTF8String CN carries accented / CJK characters) is allowed. `str` is assumed a
67
- // string (the caller guards typeof). E is the (code, message) typed-error factory.
68
24
  // @enforced-by behavioral -- the control-byte reject has no rename-proof code
69
- // shape distinct from the ASN.1 charset readers; the CVE-2009-2408 RED vectors
70
- // (a DN string with an embedded NUL / control byte rejects) are the guard.
71
25
  function assertNoControlBytes(str, E, code, label) {
72
26
  for (var i = 0; i < str.length; i++) {
73
27
  var c = _charCodeAt(str, i);
@@ -78,16 +32,8 @@ function assertNoControlBytes(str, E, code, label) {
78
32
  return str;
79
33
  }
80
34
 
81
- // assertPrintableIa5(buf, E, code, label) -> buf | throws E(code, ...)
82
- // IA5String policy (a dNSName / rfc822Name / URI GeneralName): every byte must be
83
- // printable 7-bit ASCII [0x20, 0x7e], since an embedded NUL or control byte enables the
84
- // same name-truncation bypass downstream. `buf` is the raw GeneralName content.
85
35
  // @enforced-by behavioral -- the printable-IA5 byte-range reject has no rename-proof
86
- // code shape distinct from the ASN.1 IA5 reader; the CVE-2009-2408 RED vectors
87
- // (a SAN with a control byte rejects) are the guard.
88
36
  function assertPrintableIa5(buf, E, code, label) {
89
- // The scan end comes from the slot, not from `buf.length`. A shorter answer stops the walk before
90
- // the control byte, which is the whole defense skipped on a name that still carries it.
91
37
  var n = intrinsic.sizeOf(buf);
92
38
  for (var i = 0; i < n; i++) {
93
39
  if (buf[i] < 0x20 || buf[i] > 0x7e) {
@@ -97,24 +43,11 @@ function assertPrintableIa5(buf, E, code, label) {
97
43
  return buf;
98
44
  }
99
45
 
100
- // Canonical form of a single DN attribute value (RFC 5280 sec. 7.1): reject an
101
- // embedded control byte (CVE-2009-2408) then case-fold and collapse internal
102
- // whitespace. Every standard X.520 attribute uses caseIgnoreMatch, and this form
103
- // matches OpenSSL's X509_NAME_cmp, so a chain OpenSSL accepts is not rejected. This
104
- // canonicalization is the shape the guard-shape-reinlined detector keys on
105
- // (declared on dnEqual): a boundary hand-rolling it is re-implementing DN identity.
106
- // The collapse is a single walk, not a pattern replace. This runs on every
107
- // attribute value of every name the toolkit compares, a certificate an attacker
108
- // supplies included, and one pass with a running "was the last character a space"
109
- // flag costs exactly the length. It also spells out which characters count as
110
- // whitespace: RFC 5280 sec. 7.1 defers to X.520's caseIgnoreMatch, whose SPACE is
111
- // the ASCII space, and a pattern's \s silently also folds VT, FF, NBSP and every
112
- // Unicode space separator, which would equate two names X.520 keeps distinct.
113
46
  function _isSpace(c) { return c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d; }
114
47
  function _canonAttrValue(v, E, code, label) {
115
48
  if (typeof v !== "string") return v;
116
49
  assertNoControlBytes(v, E, code, label);
117
- var out = "", lastWasSpace = true; // true so leading whitespace is dropped
50
+ var out = "", lastWasSpace = true;
118
51
  for (var i = 0; i < v.length; i++) {
119
52
  if (_isSpace(_charCodeAt(v, i))) { lastWasSpace = true; continue; }
120
53
  if (lastWasSpace && out.length) out += " ";
@@ -123,28 +56,10 @@ function _canonAttrValue(v, E, code, label) {
123
56
  }
124
57
  return _toLowerCase(out);
125
58
  }
126
- // rdnEqual(a, b, E, code, label) -> boolean. Canonical comparison of a single
127
- // RelativeDistinguishedName (an unordered SET of type/value pairs, compared as a
128
- // multiset). The RDN-level primitive a name-constraint directoryName prefix match
129
- // composes; dnEqual composes it over the RDN sequence. Comparing an RDN by raw DER
130
- // would treat two RFC 5280-equal names as different (or let a truncation name
131
- // compare equal).
132
- // Both comparands must be the RDN SEQUENCE, not the parsed Name object that carries one. This is
133
- // not a style check: a parsed Name has no `length`, so `a.length !== b.length` compares undefined
134
- // with undefined, the loop over `i < undefined` never runs, and the comparison returns TRUE for two
135
- // unrelated names. An identity comparison that answers "equal" because it was handed the wrong
136
- // shape is the worst possible failure of this guard: it is the one place a DN identity is
137
- // decided, and every caller reads a true from it as proof. So a comparison that cannot be
138
- // performed refuses, and the caller learns at the call site instead of trusting a fabricated
139
- // match. (A caller holding a parsed Name passes its `.rdns`.)
140
59
  function _assertSequence(a, b, E, code, label, what) {
141
60
  if (!_isArray(a) || !_isArray(b)) {
142
61
  throw E(code, "cannot compare " + label + ": " + what + " comparison requires the RDN sequence on both sides (pass name.rdns, not the parsed Name)");
143
62
  }
144
- // A hole is refused here rather than read below. Indexing a sparse array walks to
145
- // `Array.prototype`, so a numeric property planted there supplies an attribute at the missing
146
- // index and the comparison decides identity on a value the name does not carry. A real RDN
147
- // sequence has no holes, so refusing costs nothing and there is no value to guess at.
148
63
  if (_hasHole(a) || _hasHole(b)) {
149
64
  throw E(code, "cannot compare " + label + ": " + what + " comparison requires a dense sequence on both sides");
150
65
  }
@@ -158,9 +73,6 @@ function _hasHole(arr) {
158
73
  function rdnEqual(a, b, E, code, label) {
159
74
  _assertSequence(a, b, E, code, label, "an RDN");
160
75
  if (a.length !== b.length) return false;
161
- // A null-prototype accumulator rather than an array: a numeric property planted on
162
- // `Array.prototype` reads back as "already matched" for an index nothing has matched, which skips
163
- // a candidate attribute and reports two equal RDNs as different.
164
76
  var used = _create(null);
165
77
  for (var i = 0; i < a.length; i++) {
166
78
  var found = false;
@@ -174,12 +86,6 @@ function rdnEqual(a, b, E, code, label) {
174
86
  }
175
87
  return true;
176
88
  }
177
- // dnEqual(rdnsA, rdnsB, E, code, label) -> boolean. RFC 5280 sec. 7.1 canonical
178
- // distinguished-name comparison over the RDN sequence, the single place a DN identity
179
- // is decided, so no caller binds identity to raw DER (a byte compare, or hashing
180
- // name.bytes, would treat two RFC 5280-equal names as different, breaking a chain or
181
- // a revocation match, or, the mirror risk, escaping a name constraint). The
182
- // per-RDN canonicalization + control-byte reject is in rdnEqual / _canonAttrValue.
183
89
  // @enforced-by guard-shape-reinlined
184
90
  // @guard-shape replace\(/\\s\+/g,
185
91
  function dnEqual(rdnsA, rdnsB, E, code, label) {
@@ -191,27 +97,7 @@ function dnEqual(rdnsA, rdnsB, E, code, label) {
191
97
  return true;
192
98
  }
193
99
 
194
- // dpnCorresponds(a, b, E, code, label) -> boolean. Do two DistributionPointNames name the
195
- // same CRL distribution point? RFC 5280 sec. 6.3.3(b)(2)(i) asks it of a certificate's
196
- // DistributionPoint against a shard CRL's issuingDistributionPoint, and RFC 9483 sec. 4.3.4
197
- // asks it of the dpn a CMP crlUpdate requested against the IDP of the CRL that came back.
198
- // The answer is "at least one name in common", compared by BYTE-IDENTICAL DER.
199
- //
200
- // The comparison key is pinned by sec. 5.2.5: "The identical encoding MUST be used in the
201
- // distributionPoint fields of the certificate and the CRL". This is therefore the one name
202
- // comparison in the toolkit that must NOT canonicalize. dnEqual's sec. 7.1 rule applied here
203
- // would equate two encodings the spec keeps distinct, and a CRL would be accepted as covering
204
- // a point it was never published for. Whole-set equality is the opposite error: a point
205
- // published under several names (a URI beside a directoryName) shares one of them with a
206
- // reference that lists only that one, and demanding all would reject its own CRL.
207
- //
208
- // Mixed forms never correspond, with fullName on one side and nameRelativeToCRLIssuer on the
209
- // other. Deciding that would mean resolving an RDN fragment against a CRL issuer DN this
210
- // comparison is not given, so the answer is no.
211
- //
212
100
  // @enforced-by behavioral -- the rule's identity is carried by the kind strings, which the
213
- // pattern walk strips before matching, so it has no rename-proof code shape. The RED vectors
214
- // in guard-name.test.js and the crlUpdate vectors in cmp-session.test.js are the guard.
215
101
  function dpnCorresponds(a, b, E, code, label) {
216
102
  var x = _reduceDpn(a, E, code, label), y = _reduceDpn(b, E, code, label);
217
103
  if (x.kind !== y.kind) return false;
@@ -224,25 +110,9 @@ function dpnCorresponds(a, b, E, code, label) {
224
110
  return false;
225
111
  }
226
112
 
227
- // The comparand, checked and REDUCED to the values that were checked. The reason _assertSequence
228
- // gives applies here in full: a comparison handed the wrong shape must refuse at the call site
229
- // instead of answering. Reading `names` off a value that has none would run a loop over
230
- // `i < undefined` zero times and report "no correspondence", which reads as a clean refusal of a
231
- // CRL and hides that nothing was compared; reading it off a sparse array would walk to
232
- // Array.prototype for the missing index and compare a name the reference never carried.
233
- //
234
- // Every field must be the comparand's OWN DATA property, and its value is taken FROM the property
235
- // descriptor. That is one reflective read per field, so the value the shape check validated is the
236
- // value the comparison is answered from: no second read is left for an accessor, or a Proxy trap,
237
- // to answer differently, and a field the decoder never produces (inherited, or reached through a
238
- // getter) is refused. Buffers, not caller byte-likes, for the same reason: the comparison is byte
239
- // identity, and an object answering `equals` or `length` for itself would be deciding the question
240
- // asked of it.
241
113
  function _reduceDpn(d, E, code, label) {
242
114
  function refuse(why) { return E(code, "cannot compare " + label + ": " + why); }
243
115
  function ownValue(o, k) {
244
- // FromPropertyDescriptor hands back a fresh ordinary object, so `value` here is the value the
245
- // object itself reported for that key, including through a Proxy's own-descriptor trap.
246
116
  var desc = _getOwnPropertyDescriptor(o, k);
247
117
  if (!desc || !_hasOwn(desc, "value")) throw refuse("a distribution-point comparison requires " + k + " to be the comparand's own value (RFC 5280 sec. 6.3.3 compares decoded names)");
248
118
  return desc.value;
@@ -259,12 +129,7 @@ function _reduceDpn(d, E, code, label) {
259
129
  }
260
130
  var names = ownValue(d, "names");
261
131
  if (!_isArray(names)) throw refuse("a fullName distribution point must carry a sequence of encoded GeneralNames");
262
- // The length is read once, into the bound the copy runs to. A hole and an inherited element are
263
- // the same refusal as any other missing own value: indexing past a hole walks to Array.prototype,
264
- // where a planted numeric property supplies a name the reference never carried.
265
132
  var len = names.length;
266
- // Indexed assignment, never a push: the accumulator must not be built by a method a caller can
267
- // replace, since what it collects is what the comparison is answered from.
268
133
  var taken = [];
269
134
  for (var i = 0; i < len; i++) {
270
135
  var n = ownValue(names, i);
@@ -274,15 +139,6 @@ function _reduceDpn(d, E, code, label) {
274
139
  return { kind: kind, names: taken };
275
140
  }
276
141
 
277
- // escapeControlBytes(str) -> str. The render-side sibling of assertNoControlBytes:
278
- // where a name string must still be DISPLAYED best-effort (a human-readable report,
279
- // a log line) instead of rejected, neutralize every C0 control byte and DEL by
280
- // rendering it as \xHH. A bare CR / LF / NUL in a decoded dNSName or DN value would
281
- // otherwise forge or overwrite report lines in a terminal or log (CWE-117 output-log
282
- // injection / CWE-116 improper output encoding). Non-control bytes pass through.
283
- // Written as a charCodeAt scan, not a /[\x00-\x1f]/ regex: eslint's no-control-regex
284
- // (correctly) refuses control characters in a regex literal, and the control-byte
285
- // range test is the rename-proof shape the detector keys on regardless.
286
142
  // @enforced-by guard-shape-reinlined
287
143
  // @guard-shape < 0x20 \|\| \w+ === 0x7f
288
144
  function escapeControlBytes(str) {
@@ -296,28 +152,10 @@ function escapeControlBytes(str) {
296
152
  return out;
297
153
  }
298
154
 
299
- // escapeDnValue(v) -> str. RFC 4514 sec. 2.4 string-representation escaping of a DN
300
- // attribute value (the input is the already-unwrapped semantic string): backslash-
301
- // escape the always-specials , + " \ < > ; and a NUL (as \00), then the positional
302
- // rules -- a trailing space as '\ ' and a leading '#' or space as '\#' / '\ '. Without
303
- // this a value like `foo, CN=admin` renders as two RDNs, and a literal `#05` collides
304
- // with the hexstring form, so the report misstates a subject/issuer name (CWE-116).
305
- // The one place a DN attribute value is made display-safe; pki.schema.pkix's DN
306
- // rendering composes it, and pki.inspect reuses that parser output (name.dn).
307
- var DN_SPECIAL = { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 }; // , + " \ < > ;
308
- // RFC 4514 sec. 2.4's special set is the TABLE above and the walk is a single pass.
309
- // The separator escape used to be a pattern replace feeding a second loop, which
310
- // meant two passes over an attacker-supplied value and a character class holding a
311
- // quote and a backslash inside a regex literal, the form most easily misread by a
312
- // human and, as it happened, by the codebase-patterns literal-stripper.
313
- //
155
+ var DN_SPECIAL = { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 };
314
156
  // @enforced-by behavioral -- the escaping has no rename-proof code shape distinct
315
- // from ordinary string building; the guard-name RED vectors + the schema-pkix DN
316
- // round-trip vectors (a comma / plus / leading '#' renders backslash-escaped) are the guard.
317
157
  function escapeDnValue(v) {
318
158
  var s = _String(v), out = "";
319
- // A NUL / control octet becomes '\' + two hex digits, so an embedded CR / LF / NUL
320
- // in a decoded DN value can never forge a report line when displayed.
321
159
  for (var i = 0; i < s.length; i++) {
322
160
  var c = _charCodeAt(s, i);
323
161
  if (c < 0x20 || c === 0x7f) out += "\\" + (c < 16 ? "0" : "") + _toUpperCase(_numToString(c, 16));
@@ -329,33 +167,10 @@ function escapeDnValue(v) {
329
167
  return out;
330
168
  }
331
169
 
332
- // emailEqual(a, b) -> "match" | "no-match" | "not-comparable". The RFC 5280 sec. 7.5
333
- // sibling of dnEqual: two email addresses match iff their local-parts are an exact match
334
- // AND their host-parts match under a case-insensitive ASCII comparison. The local-part is
335
- // NEVER case-folded -- RFC 8398 sec. 5 states it "MUST NOT be transformed in any way, such
336
- // as by doing case folding or normalization of any kind", because Alice@ and alice@ are
337
- // permitted to be different mailboxes.
338
- //
339
- // The result is three-valued on purpose. An internationalized host-part (an `xn--` A-label
340
- // or any non-ASCII octet) needs an IDNA2008 A-label/U-label transform this toolkit does not
341
- // carry, so the rule cannot be applied. Returning "no-match" there would answer a question
342
- // that was never asked, and a caller acting on it would reject a legitimate sender; the
343
- // honest verdict is that the comparison was unavailable. A guard that cannot decide says so
344
- // instead of guessing (the value is fail-CLOSED at the call site: no caller may read
345
- // "not-comparable" as a pass).
346
- //
347
- // RFC 8398 sec. 5 also forbids wildcards outright -- "implementations MUST NOT interpret
348
- // any characters as wildcards" -- so no character here is ever special. A certificate
349
- // carrying `*@example.com` matches that literal string and nothing else. Name-CONSTRAINT
350
- // processing (RFC 5280 sec. 4.2.1.10 as extended by RFC 8398 sec. 6), where a bare domain
351
- // legitimately covers a whole subtree, is a different rule and stays in path-validate.
352
- //
353
170
  // @enforced-by guard-shape-reinlined
354
171
  // @guard-shape lastIndexOf\("@"\)
355
172
  function emailEqual(a, b) {
356
173
  if (typeof a !== "string" || typeof b !== "string") return "not-comparable";
357
- // Split on the LAST "@": a quoted local-part may legally contain one, and taking the
358
- // first would move part of the mailbox into the domain and compare the wrong halves.
359
174
  var ai = _lastIndexOf(a, "@"), bi = _lastIndexOf(b, "@");
360
175
  if (ai <= 0 || bi <= 0 || ai === a.length - 1 || bi === b.length - 1) return "not-comparable";
361
176
  var aLocal = _strSlice(a, 0, ai), bLocal = _strSlice(b, 0, bi);
@@ -365,31 +180,12 @@ function emailEqual(a, b) {
365
180
  return lowerAscii(aHost) === lowerAscii(bHost) ? "match" : "no-match";
366
181
  }
367
182
 
368
- // A host-part this comparison can decide: ASCII, and carrying no A-label. Anything else
369
- // needs the IDNA transform named above, so it is reported as undecidable rather than
370
- // compared as raw bytes -- `xn--e1afmkfd.example` and its Unicode form are one host, and
371
- // treating them as different would be a wrong answer rather than an absent one.
372
183
  function _asciiHost(h) {
373
184
  if (h.length === 0) return false;
374
- // ASCII is the whole test, and an A-label is ASCII. Two hosts already in A-label form
375
- // need no transformation to be compared -- they are the same encoding -- so rejecting
376
- // them for carrying `xn--` would make sender binding unusable for every certificate with
377
- // an internationalized domain, including one whose address matches exactly. The case an
378
- // IDNA transform WOULD be needed for is an A-label against a U-label, and the U-label
379
- // side is non-ASCII, so it is refused here anyway.
380
185
  for (var i = 0; i < h.length; i++) if (_charCodeAt(h, i) > 0x7f) return false;
381
186
  return true;
382
187
  }
383
188
 
384
- // lowerAscii(s) -> string
385
- // ASCII-only lowercase. String.toLowerCase() is Unicode-aware and would fold characters
386
- // outside ASCII (the Kelvin sign lowercases to "k"), which is precisely the mapping a
387
- // name comparison does not authorize: a name folded that way is one the certificate or
388
- // message does not carry, and comparing it equal to a name that was asked for is the
389
- // defect. Every case-insensitive name comparison in the toolkit -- a host part, a CMP
390
- // domain, an ACME identifier set -- folds through this one definition, so none of them can
391
- // disagree about which characters case-fold.
392
- //
393
189
  // @enforced-by guard-shape-reinlined
394
190
  // @guard-shape 0x41\s*&&\s*[A-Za-z_$][\w$]*\s*<=\s*0x5[aA]\s*\)\s*\?[^:]*\+\s*(?:32|0x20)\s*\)
395
191
  function lowerAscii(s) {
@@ -401,8 +197,6 @@ function lowerAscii(s) {
401
197
  return out;
402
198
  }
403
199
 
404
- // Frozen for the reason the family header gives: a boundary reads its guard off this object at the
405
- // call, so a writable export would let a caller replace the check rather than pass it.
406
200
  module.exports = intrinsic.freeze({
407
201
  assertNoControlBytes: assertNoControlBytes, assertPrintableIa5: assertPrintableIa5,
408
202
  dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual, dpnCorresponds: dpnCorresponds,