@blamejs/pki 0.6.13 → 0.6.15

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
package/lib/pki-build.js CHANGED
@@ -1,45 +1,21 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- the ENCODE-direction sibling of schema-pkix.js (which is decode-only). `makeBuilder(ctx)`
6
- // binds a domain namespace once and returns the shared PKIX producing primitives every signer module
7
- // composes: the distinguished-name encoder, the GeneralName + RFC 5280 sec. 4.2.1 extension-value
8
- // encoders, the embedded-input validators (a raw Name / a SubjectPublicKeyInfo / a pre-encoded Extension
9
- // run through the same parser the decoder uses), the sign-scheme bridge, and the post-sign signature
10
- // self-check (the key-match / proof-of-possession verify). Each is parameterized on the CALLER's error
11
- // class + code prefix, so pki.x509.sign keeps x509/* codes and pki.csr.sign keeps csr/*.
4
+ // @internal
12
5
 
13
6
  var asn1 = require("./asn1-der");
14
7
  var schema = require("./schema-engine");
15
8
  var compositeSig = require("./composite-sig");
16
9
  var guard = require("./guard-all");
17
10
  var intrinsic = require("./guard-intrinsic");
18
- // Each of these doors refuses a caller argument that is not a list. Read live, a replacement
19
- // answering true lets a non-list through the check written to stop it.
20
11
  var _isArray = intrinsic.isArray;
21
- // Taken at load for the reason every capture here is: the question "do these two encodings name the
22
- // same key" decides whether a proof of possession is accepted, and a caller who replaces the copy or
23
- // the comparison afterwards decides it instead.
24
12
  var _bufferFrom = intrinsic.bufferFrom;
25
13
  var _bufferEquals = intrinsic.bufferEquals;
26
14
  var oid = require("./oid");
27
15
  var ipUtils = require("./ip-utils");
28
- // packIpLiteral decides a GeneralName form (a bare string -> iPAddress or not), so its binding is taken at
29
- // load: calling it through the exports object would be a live property read, and code that reassigned that
30
- // property could steer the form decision without touching a regex.
31
16
  var _packIpLiteral = ipUtils.packIpLiteral;
32
17
  var nodeCrypto = require("crypto");
33
- // The key-material comparison, taken at load like every other operation a decision here rests on.
34
18
  var _keyEquals = intrinsic.uncurry(nodeCrypto.KeyObject.prototype.equals);
35
- // The bare-string GeneralName classifier (_classifyBareGeneralName) decides which name FORM a SAN string
36
- // becomes. It uses no regular expression: a regex is matched through a live, replaceable protocol
37
- // (RegExp.prototype.exec / test, String.prototype.match / split consult the regex's own `exec` and the
38
- // `[Symbol.match]` / `[Symbol.split]` hooks, and RegExp.prototype.compile can mutate a pattern in place),
39
- // so co-resident code could steer a form decision without our seeing it -- and the structural URI/email
40
- // rules a regex cannot state cleanly are what kept drawing edge-case escapes. Instead every form is
41
- // decided by scanning the string one character code at a time through these captured string primitives,
42
- // which read the value directly and have no per-character dispatch to replace.
43
19
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
44
20
  var _stringIndexOf = intrinsic.stringIndexOf;
45
21
  var _stringLastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
@@ -49,67 +25,50 @@ var _objectKeys = intrinsic.keys;
49
25
  var _isBufferChk = intrinsic.isBuffer;
50
26
  var _stringify = intrinsic.stringify;
51
27
 
52
- // --- bare-string GeneralName form detection, by character-code scan (no regex) ---
53
- // Each predicate answers "is this string syntactically THIS name form", reading the string one code unit
54
- // at a time; a caller cannot steer the answer by replacing a prototype method. The classifier below tries
55
- // the forms in a fixed precedence and refuses anything none of them accepts (the object form is the escape).
56
- function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); } // A-Z a-z
57
- function _isDigitCode(c) { return c >= 48 && c <= 57; } // 0-9
28
+ function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); }
29
+ function _isDigitCode(c) { return c >= 48 && c <= 57; }
58
30
  function _isHexCode(c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
59
31
 
60
- // RFC 3986 sec. 2.3 unreserved: ALPHA / DIGIT / "-" / "." / "_" / "~".
61
32
  function _isUnreserved(c) { return _isAlphaCode(c) || _isDigitCode(c) || c === 45 || c === 46 || c === 95 || c === 126; }
62
- // RFC 3986 sec. 2.2 sub-delims: ! $ & ' ( ) * + , ; =
63
33
  function _isSubDelim(c) {
64
34
  switch (c) {
65
35
  case 33: case 36: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 59: case 61: return true;
66
36
  default: return false;
67
37
  }
68
38
  }
69
- // A userinfo byte: unreserved / sub-delim / ":" (percent-encoding handled separately). No "@".
70
39
  function _isUserinfoByte(c) { return _isUnreserved(c) || _isSubDelim(c) || c === 58; }
71
- // A path/query/fragment byte: unreserved / sub-delim / a non-bracket gen-delim (":" "/" "?" "#" "@").
72
- // "%" is handled separately (it must open a "%HEXHEX" triplet); the IP-literal brackets "[" "]" are
73
- // excluded here because they are authority syntax -- a bracketed IPv6 host is validated in
74
- // _validUriAuthority (RFC 3986 sec. 3.2.2), never in the path.
75
40
  function _isUriByte(c) {
76
41
  return _isUnreserved(c) || _isSubDelim(c) || c === 58 || c === 47 || c === 63 || c === 35 || c === 64;
77
42
  }
78
43
 
79
- // An RFC 5322 sec. 3.2.3 "atext" byte: ALPHA / DIGIT and a fixed set of specials, used for an unquoted
80
- // email local part. "." is deliberately excluded so the dot-atom structure is enforced by the scan.
81
44
  function _isAtextByte(c) {
82
45
  if (_isAlphaCode(c) || _isDigitCode(c)) return true;
83
46
  switch (c) {
84
- case 33: case 35: case 36: case 37: case 38: case 39: // ! # $ % & '
85
- case 42: case 43: case 45: case 47: case 61: case 63: // * + - / = ?
86
- case 94: case 95: case 96: case 123: case 124: case 125: case 126: // ^ _ ` { | } ~
47
+ case 33: case 35: case 36: case 37: case 38: case 39:
48
+ case 42: case 43: case 45: case 47: case 61: case 63:
49
+ case 94: case 95: case 96: case 123: case 124: case 125: case 126:
87
50
  return true;
88
51
  default: return false;
89
52
  }
90
53
  }
91
54
 
92
- // IPv4 SHAPE: one or more digit groups joined by single dots (only digits and dots, no leading/trailing/
93
- // doubled dot, at least one dot). This is the "looks like an address" test, not a valid-address test --
94
- // packIpLiteral decides validity; a shape match that is not a valid address is refused as ambiguous.
95
55
  function _looksLikeIPv4Shape(s) {
96
56
  var n = s.length, i, c, sawDot = false, atomEmpty = true;
97
57
  if (n === 0) return false;
98
58
  for (i = 0; i < n; i += 1) {
99
59
  c = _charCodeAt(s, i);
100
- if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; } // "."
60
+ if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; }
101
61
  else if (_isDigitCode(c)) { atomEmpty = false; }
102
62
  else return false;
103
63
  }
104
64
  return sawDot && !atomEmpty;
105
65
  }
106
66
 
107
- // Every byte in [from,to) is an allowed component byte (per `byteOk`) or opens a "%HEXHEX" triplet.
108
67
  function _componentBytesOk(s, from, to, byteOk) {
109
68
  var i = from, c;
110
69
  while (i < to) {
111
70
  c = _charCodeAt(s, i);
112
- if (c === 37) { // "%" -> %HEXHEX
71
+ if (c === 37) {
113
72
  if (i + 2 >= to || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
114
73
  i += 3;
115
74
  } else if (byteOk(c)) { i += 1; }
@@ -118,143 +77,113 @@ function _componentBytesOk(s, from, to, byteOk) {
118
77
  return true;
119
78
  }
120
79
 
121
- // The URI host in [from,to): RFC 5280 sec. 4.2.1.6 requires a URI SAN authority to carry a fully-qualified
122
- // domain name or IP host, stricter than an RFC 3986 reg-name. So the host is either a valid IPv4 literal,
123
- // or an FQDN of strict-LDH labels (the email-domain grammar: no underscore, no hyphen at a label edge) with
124
- // at least one dot -- a single-label host such as "localhost" is not fully qualified and uses the object
125
- // form, matching the email domain. An IPv4-SHAPED host that is not a valid address is refused as ambiguous
126
- // (the same rule the top-level classifier uses). An IPv6-literal host is written in "[" "]" and validated
127
- // by _validUriIp6Host from _validUriAuthority, never reaching this unbracketed-host check.
128
80
  function _validUriHost(s, from, to) {
129
- if (to <= from) return false; // empty host
81
+ if (to <= from) return false;
130
82
  var host = _sliceStr(s, from, to), len = host.length;
131
- if (_looksLikeIPv4Shape(host)) return _packIpLiteral(host) !== null; // IPv4 (first-match-wins)
132
- var end = _charCodeAt(host, len - 1) === 46 ? len - 1 : len; // ignore an absolute-root "."
133
- if (end > 253) return false; // RFC 1035 name length (root dot excluded)
83
+ if (_looksLikeIPv4Shape(host)) return _packIpLiteral(host) !== null;
84
+ var end = _charCodeAt(host, len - 1) === 46 ? len - 1 : len;
85
+ if (end > 253) return false;
134
86
  var firstDot = _stringIndexOf(host, ".", 0);
135
- if (firstDot === -1 || firstDot >= end) return false; // multi-label FQDN ("localhost" is not)
136
- return _labelsValid(host, 0, len, _isHostLabelRange, true); // strict-LDH FQDN, root "." allowed
87
+ if (firstDot === -1 || firstDot >= end) return false;
88
+ return _labelsValid(host, 0, len, _isHostLabelRange, true);
137
89
  }
138
90
 
139
- // A bracketed IPv6-literal URI host (RFC 3986 sec. 3.2.2): the bytes between "[" and "]" must be a valid
140
- // IPv6 address (16 packed octets), never an IPv4 literal (which uses the unbracketed IPv4address form). The
141
- // bracket is the ONLY way to write an IPv6 host in a URI, and an IPv6 host is a valid iPAddress per RFC 5280
142
- // sec. 4.2.1.6, so the shorthand accepts it rather than forcing the object form.
143
91
  function _validUriIp6Host(s, from, to) {
144
- if (to <= from) return false; // empty "[ ]"
92
+ if (to <= from) return false;
145
93
  var packed = _packIpLiteral(_sliceStr(s, from, to));
146
- return packed !== null && intrinsic.sizeOf(packed) === 16; // IPv6 literal only (16 octets)
94
+ return packed !== null && intrinsic.sizeOf(packed) === 16;
147
95
  }
148
96
 
149
- // The authority in [from,to) is "[ userinfo "@" ] host [ ":" port ]" (RFC 3986 sec. 3.2): at most one "@"
150
- // (userinfo and reg-name each exclude it), a host that is an RFC 5280 FQDN or IPv4 (_validUriHost) or a
151
- // bracketed IPv6 literal (_validUriIp6Host), and an all-digit -- possibly empty -- port after the host's
152
- // ":". A bracketed host is taken as ONE unit before the port, so a ":" inside the IPv6 literal is not read
153
- // as the port separator. The userinfo bytes are validated against the RFC 3986 userinfo class.
154
97
  function _validUriAuthority(s, from, to) {
155
98
  var i, c, at = -1;
156
99
  for (i = from; i < to; i += 1) {
157
100
  c = _charCodeAt(s, i);
158
- if (c === 64) { if (at !== -1) return false; at = i; } // "@" -- at most one
101
+ if (c === 64) { if (at !== -1) return false; at = i; }
159
102
  }
160
103
  var hostStart = at === -1 ? from : at + 1;
161
- if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false; // userinfo
162
- if (hostStart < to && _charCodeAt(s, hostStart) === 91) { // "[" -> bracketed IPv6 host
104
+ if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false;
105
+ if (hostStart < to && _charCodeAt(s, hostStart) === 91) {
163
106
  var close = -1;
164
- for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } } // "]"
165
- if (close === -1) return false; // unterminated "["
166
- if (!_validUriIp6Host(s, hostStart + 1, close)) return false; // IPv6 literal in "[ ]"
107
+ for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } }
108
+ if (close === -1) return false;
109
+ if (!_validUriIp6Host(s, hostStart + 1, close)) return false;
167
110
  var after = close + 1;
168
- if (after === to) return true; // "[ ipv6 ]" with no port
169
- if (_charCodeAt(s, after) !== 58) return false; // only a ":" port may follow "]"
170
- for (i = after + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; } // port = *DIGIT
111
+ if (after === to) return true;
112
+ if (_charCodeAt(s, after) !== 58) return false;
113
+ for (i = after + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
171
114
  return true;
172
115
  }
173
116
  var colon = -1;
174
- for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } } // ":" -> port
117
+ for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } }
175
118
  var hostEnd = colon === -1 ? to : colon;
176
- if (!_validUriHost(s, hostStart, hostEnd)) return false; // FQDN or IPv4 host
119
+ if (!_validUriHost(s, hostStart, hostEnd)) return false;
177
120
  if (colon !== -1) {
178
- for (i = colon + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; } // port = *DIGIT
121
+ for (i = colon + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
179
122
  }
180
123
  return true;
181
124
  }
182
125
 
183
- // scheme "://" authority [ path ] [ "?" query ] [ "#" fragment ], parsed by RFC 3986 component. The
184
- // authority is validated by _validUriAuthority; the remainder is path/query/fragment bytes with at most
185
- // one "#" (its single fragment delimiter). A structurally malformed URL is refused; the object form is
186
- // the escape for one the shorthand does not accept.
187
126
  function _looksLikeUri(s) {
188
127
  var n = s.length, i, c;
189
- if (n === 0 || !_isAlphaCode(_charCodeAt(s, 0))) return false; // scheme starts ALPHA
128
+ if (n === 0 || !_isAlphaCode(_charCodeAt(s, 0))) return false;
190
129
  i = 1;
191
130
  while (i < n) {
192
131
  c = _charCodeAt(s, i);
193
- if (_isAlphaCode(c) || _isDigitCode(c) || c === 43 || c === 45 || c === 46) i += 1; // + - . in scheme
132
+ if (_isAlphaCode(c) || _isDigitCode(c) || c === 43 || c === 45 || c === 46) i += 1;
194
133
  else break;
195
134
  }
196
135
  if (i + 3 > n || _charCodeAt(s, i) !== 58 || _charCodeAt(s, i + 1) !== 47 || _charCodeAt(s, i + 2) !== 47) return false;
197
136
  i += 3;
198
137
  var authStart = i, authEnd = n;
199
138
  for (; i < n; i += 1) { c = _charCodeAt(s, i); if (c === 47 || c === 63 || c === 35) { authEnd = i; break; } }
200
- if (authEnd === authStart) return false; // empty authority
139
+ if (authEnd === authStart) return false;
201
140
  if (!_validUriAuthority(s, authStart, authEnd)) return false;
202
- var sawHash = false; // path / query / fragment
141
+ var sawHash = false;
203
142
  i = authEnd;
204
143
  while (i < n) {
205
144
  c = _charCodeAt(s, i);
206
- if (c === 37) { // "%" -> %HEXHEX
145
+ if (c === 37) {
207
146
  if (i + 2 >= n || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
208
147
  i += 3;
209
148
  continue;
210
149
  }
211
- if (c === 35) { if (sawHash) return false; sawHash = true; } // one "#" fragment delimiter
150
+ if (c === 35) { if (sawHash) return false; sawHash = true; }
212
151
  if (!_isUriByte(c)) return false;
213
152
  i += 1;
214
153
  }
215
154
  return true;
216
155
  }
217
156
 
218
- // A single dNSName label over [from,to): 1-63 bytes, LDH plus a LEADING or EMBEDDED underscore, no hyphen
219
- // at either edge, and no underscore at the trailing edge. The underscore tolerance is deliberate and
220
- // leading/embedded only: the real forms are _acme-challenge, _dmarc, _25._tcp (a label-initial "_"); a
221
- // label ending in "_" has no such use. An email DOMAIN is stricter still -- see _isHostLabelRange.
222
157
  function _isDnsLabelRange(s, from, to) {
223
158
  var len = to - from, i, c;
224
159
  if (len < 1 || len > 63) return false;
225
160
  for (i = from; i < to; i += 1) {
226
161
  c = _charCodeAt(s, i);
227
- if (_isAlphaCode(c) || _isDigitCode(c)) continue; // A-Z a-z 0-9 anywhere
228
- if (i === to - 1) return false; // last byte must be alnum
229
- if (c === 95) continue; // "_" leading or embedded
230
- if (c === 45 && i !== from) continue; // "-" embedded (not leading)
162
+ if (_isAlphaCode(c) || _isDigitCode(c)) continue;
163
+ if (i === to - 1) return false;
164
+ if (c === 95) continue;
165
+ if (c === 45 && i !== from) continue;
231
166
  return false;
232
167
  }
233
168
  return true;
234
169
  }
235
170
 
236
- // A single RFC 5321 sec. 2.3.5 hostname label over [from,to): the strict letter-digit-hyphen grammar with
237
- // no hyphen at either edge and NO underscore. This is the email-domain label rule (a mail domain is a real
238
- // hostname), stricter than the underscore-tolerant dNSName label above.
239
171
  function _isHostLabelRange(s, from, to) {
240
172
  var len = to - from, i, c, edge;
241
173
  if (len < 1 || len > 63) return false;
242
174
  for (i = from; i < to; i += 1) {
243
175
  c = _charCodeAt(s, i);
244
176
  edge = (i === from || i === to - 1);
245
- if (_isAlphaCode(c) || _isDigitCode(c)) continue; // A-Z a-z 0-9
246
- if (c === 45 && !edge) continue; // "-" not at an edge
177
+ if (_isAlphaCode(c) || _isDigitCode(c)) continue;
178
+ if (c === 45 && !edge) continue;
247
179
  return false;
248
180
  }
249
181
  return true;
250
182
  }
251
183
 
252
- // Every dot-separated label in [from,to) satisfies `labelFn`. Scans with indexOf; no split allocation. When
253
- // `allowRootDot` is set, a single trailing "." (the absolute-FQDN root) is stripped before validating, so
254
- // "example.com." checks as "example.com"; an interior empty label (a doubled or leading dot) still fails.
255
184
  function _labelsValid(s, from, to, labelFn, allowRootDot) {
256
- if (allowRootDot && to - from >= 1 && _charCodeAt(s, to - 1) === 46) to -= 1; // an absolute-root "."
257
- if (to <= from) return false; // nothing (a lone ".")
185
+ if (allowRootDot && to - from >= 1 && _charCodeAt(s, to - 1) === 46) to -= 1;
186
+ if (to <= from) return false;
258
187
  var start = from, dot;
259
188
  for (;;) {
260
189
  dot = _stringIndexOf(s, ".", start);
@@ -265,56 +194,47 @@ function _labelsValid(s, from, to, labelFn, allowRootDot) {
265
194
  }
266
195
  }
267
196
 
268
- // A dNSName: at most 253 bytes, an optional single leading "*." wildcard, then dot-separated dNSName labels
269
- // (underscore-tolerant), with an optional trailing absolute-root ".".
270
197
  function _looksLikeDnsName(s) {
271
198
  var n = s.length, start = 0;
272
199
  if (n < 1) return false;
273
- var end = _charCodeAt(s, n - 1) === 46 ? n - 1 : n; // exclude an absolute-root "."
274
- if (end < 1 || end > 253) return false; // RFC 1035 name length (root dot excluded)
275
- if (end >= 2 && _charCodeAt(s, 0) === 42 && _charCodeAt(s, 1) === 46) start = 2; // "*."
200
+ var end = _charCodeAt(s, n - 1) === 46 ? n - 1 : n;
201
+ if (end < 1 || end > 253) return false;
202
+ if (end >= 2 && _charCodeAt(s, 0) === 42 && _charCodeAt(s, 1) === 46) start = 2;
276
203
  if (start >= end) return false;
277
204
  return _labelsValid(s, start, n, _isDnsLabelRange, true);
278
205
  }
279
206
 
280
- // An rfc822Name: an unquoted RFC 5321 dot-atom local part (atext runs joined by single dots), one "@",
281
- // and a dotted hostname domain (at least one dot -- an FQDN, no wildcard, strict LDH labels, no underscore).
282
207
  function _looksLikeEmail(s) {
283
- if (s.length > 254) return false; // the whole mailbox <= 254 octets (RFC 5321 sec. 4.5.3.1.3)
208
+ if (s.length > 254) return false;
284
209
  var at = _stringIndexOf(s, "@", 0);
285
- if (at < 1 || at > 64 || _stringLastIndexOf(s, "@") !== at) return false; // one "@", a 1-64 octet local part (RFC 5321 sec. 4.5.3.1.1)
210
+ if (at < 1 || at > 64 || _stringLastIndexOf(s, "@") !== at) return false;
286
211
  var i, c, atomEmpty = true;
287
- for (i = 0; i < at; i += 1) { // local part = dot-atom
212
+ for (i = 0; i < at; i += 1) {
288
213
  c = _charCodeAt(s, i);
289
- if (c === 46) { if (atomEmpty) return false; atomEmpty = true; } // no leading/doubled dot
214
+ if (c === 46) { if (atomEmpty) return false; atomEmpty = true; }
290
215
  else if (_isAtextByte(c)) { atomEmpty = false; }
291
216
  else return false;
292
217
  }
293
- if (atomEmpty) return false; // no trailing dot / empty
218
+ if (atomEmpty) return false;
294
219
  var dStart = at + 1, dLen = s.length - dStart;
295
- if (dLen < 1 || dLen > 253 || _stringIndexOf(s, ".", dStart) === -1) return false; // domain must be dotted
296
- return _labelsValid(s, dStart, s.length, _isHostLabelRange, false); // strict LDH, no underscore or root dot
220
+ if (dLen < 1 || dLen > 253 || _stringIndexOf(s, ".", dStart) === -1) return false;
221
+ return _labelsValid(s, dStart, s.length, _isHostLabelRange, false);
297
222
  }
298
223
 
299
- // A well-formed dotted-decimal OID SHAPE (no regex, by the same character scan the name forms use): a first
300
- // arc 0-2, then one or more arcs each introduced by "." and written without a leading zero ("0" alone, or a
301
- // non-zero digit followed by more digits). Used to accept a raw OID string (an unregistered KeyPurposeId /
302
- // policy OID) where a registered name would go; the encoder (encodeOidContent) remains the authoritative
303
- // validator of the arc-value bounds.
304
224
  function _isDottedOid(s) {
305
225
  var n = s.length, i, c, d0, arcs = 0;
306
- if (n < 3) return false; // shortest is "X.Y"
226
+ if (n < 3) return false;
307
227
  c = _charCodeAt(s, 0);
308
- if (c < 48 || c > 50) return false; // first arc is a single digit 0-2
228
+ if (c < 48 || c > 50) return false;
309
229
  i = 1;
310
230
  while (i < n) {
311
- if (_charCodeAt(s, i) !== 46) return false; // every following arc is introduced by "."
231
+ if (_charCodeAt(s, i) !== 46) return false;
312
232
  i += 1;
313
- if (i >= n) return false; // a trailing "." has no arc
233
+ if (i >= n) return false;
314
234
  d0 = _charCodeAt(s, i);
315
- if (d0 === 48) { i += 1; } // "0" is a complete arc; a leading zero is refused
235
+ if (d0 === 48) { i += 1; }
316
236
  else if (d0 >= 49 && d0 <= 57) { i += 1; while (i < n && _isDigitCode(_charCodeAt(s, i))) i += 1; }
317
- else { return false; } // an arc must start with a digit
237
+ else { return false; }
318
238
  arcs += 1;
319
239
  }
320
240
  return arcs >= 1;
@@ -322,63 +242,37 @@ function _isDottedOid(s) {
322
242
 
323
243
  var b = asn1.build;
324
244
 
325
- // KeyUsage named-bit positions (RFC 5280 sec. 4.2.1.3); contentCommitment is the RFC 5280 rename of the
326
- // X.509 nonRepudiation bit (1).
327
245
  var KU_BIT = {
328
246
  digitalSignature: 0, nonRepudiation: 1, contentCommitment: 1, keyEncipherment: 2,
329
247
  dataEncipherment: 3, keyAgreement: 4, keyCertSign: 5, cRLSign: 6, encipherOnly: 7, decipherOnly: 8,
330
248
  };
331
249
 
332
- // A caller-supplied array a builder is about to map over: reject a sparse hole or a nullish entry as a
333
- // typed error. Array.prototype.map SKIPS a hole, which would otherwise reach Buffer.concat as a native
334
- // ERR_INVALID_ARG_TYPE and, on a huge sparse length, traverse the empty slots. Tests OWN-property presence
335
- // (intrinsic.hasOwn) at each index BEFORE reading it, so a polluted Array.prototype[i] cannot slip a hole
336
- // past the check, and fails fast at the first bad index so the length alone can never force a walk. Takes
337
- // the caller's error factory E (invoked E(fullCode, msg)) so a builder WITHOUT a makeBuilder instance
338
- // routes its caller arrays through the same choke point; makeBuilder's own reqDenseArray delegates here.
339
250
  function reqDenseArrayImpl(list, what, E, code) {
340
251
  if (!_isArray(list)) throw E(code, what + " must be an array");
341
252
  var n = list.length;
342
- // Snapshot each own index into a FRESH dense array and return THAT, never `list`. Reading list[i] can
343
- // fire a caller-defined indexed accessor that mutates `list` (extending it with holes past the captured
344
- // n, or deleting an already-checked index); returning `list` would then hand the caller's map a
345
- // newly-sparse array and the native error this guard exists to prevent. The copy captures the value as
346
- // read, so a later mutation of `list` cannot reintroduce a hole into what the caller iterates.
347
253
  var out = [];
348
254
  for (var i = 0; i < n; i++) {
349
255
  if (!intrinsic.hasOwn(list, i)) throw E(code, what + "[" + i + "] is missing (a sparse array is not allowed)");
350
256
  var v = list[i];
351
257
  if (v === undefined || v === null) throw E(code, what + "[" + i + "] is missing (a nullish array entry is not allowed)");
352
- // defineProperty, not `out[i] = v`: a plain assignment invokes an inherited Array.prototype[i] SETTER
353
- // ([[Set]]) if one is polluted in, which could leave `out` sparse; [[DefineOwnProperty]] always makes
354
- // an own data property, so the snapshot is dense regardless of prototype pollution.
355
258
  intrinsic.defineProperty(out, i, { value: v, writable: true, enumerable: true, configurable: true });
356
259
  }
357
260
  return out;
358
261
  }
359
262
 
360
- // makeBuilder(ctx) -> the bound producing primitives. ctx = { ErrorClass, prefix, O, NS, NAME_SCHEMA,
361
- // SPKI_SCHEMA, EXT_DECODERS }. `E(kind, msg, cause)` builds a `<prefix>/<kind>` typed error.
362
263
  function makeBuilder(ctx) {
363
264
  var ErrorClass = ctx.ErrorClass, O = ctx.O, NS = ctx.NS;
364
265
  var NAME_SCHEMA = ctx.NAME_SCHEMA, SPKI_SCHEMA = ctx.SPKI_SCHEMA;
365
266
  function E(kind, message, cause) { return new ErrorClass(ctx.prefix + "/" + kind, message, cause); }
366
267
  function code(kind) { return ctx.prefix + "/" + kind; }
367
- // A full-code error factory (guard.* helpers invoke E as `E(fullCode, msg)`, not `E(kind, msg)`).
368
268
  function rawErr(fullCode, message, cause) { return new ErrorClass(fullCode, message, cause); }
369
269
 
370
- // RFC 5280 sec. 4.1.2.5 -- UTCTime for 1950..2049, GeneralizedTime otherwise (UTCTime's two-digit year
371
- // only represents that window). The shared cutover so a certificate / CRMF / any validity encoder cannot
372
- // drift on the boundary. `which` labels the instant in a validation error.
373
270
  function timeDer(date, which) {
374
271
  guard.time.assertValid(date, rawErr, code("bad-input"), which);
375
272
  var y = date.getUTCFullYear();
376
273
  return (y >= 1950 && y <= 2049) ? b.utcTime(date) : b.generalizedTime(date);
377
274
  }
378
275
 
379
- // ---- distinguished name encoding (RFC 5280 sec. 4.1.2.4) ----
380
- // countryName is a PrintableString SIZE(2), emailAddress an IA5String; every other new-name attribute
381
- // is a UTF8String (Teletex/BMP/Universal are backward-compat only and never emitted).
382
276
  function atvString(attrName, value) {
383
277
  if (attrName === "countryName") {
384
278
  if (String(value).length !== 2) throw E("bad-name", "countryName must be a two-letter ISO 3166 code (PrintableString SIZE(2))");
@@ -389,7 +283,6 @@ function makeBuilder(ctx) {
389
283
  }
390
284
  function encodeAtv(attrName, value) {
391
285
  if (value == null || value === "") throw E("bad-name", "the " + attrName + " attribute value must be a non-empty string");
392
- // For an unrecognized name oid.byName returns undefined and does not throw, so reject explicitly.
393
286
  var typeOid = O(attrName);
394
287
  if (typeOid == null) throw E("bad-name", "unknown distinguished-name attribute " + JSON.stringify(attrName));
395
288
  var valueTlv;
@@ -403,15 +296,12 @@ function makeBuilder(ctx) {
403
296
  if (!keys.length) throw E("bad-name", "an RDN must carry at least one attribute");
404
297
  return b.set(keys.map(function (k) { return encodeAtv(k, rdnSpec[k]); }));
405
298
  }
406
- // A DN spec -> RDNSequence DER. A string is shorthand for a single commonName RDN; a Buffer is raw
407
- // pre-encoded Name DER (validated through the parser). An empty array yields an empty RDNSequence.
408
299
  function encodeName(spec) {
409
300
  if (guard.bytes.isByteSource(spec)) { var _nd = guard.bytes.snapshotSource(spec, ErrorClass, ctx.prefix + "/bad-name", "raw Name DER"); assertValidNameDer(_nd); return _nd; }
410
301
  if (typeof spec === "string") spec = [{ commonName: spec }];
411
302
  if (!_isArray(spec)) throw E("bad-name", "a name must be a string, an array of RDNs, or raw Name DER");
412
303
  return b.sequence(spec.map(encodeRdn));
413
304
  }
414
- // Full validation of a raw Name DER: walk it through the exact RDNSequence parser the decoder uses.
415
305
  function assertValidNameDer(der) {
416
306
  var node;
417
307
  try { node = asn1.decode(der); }
@@ -424,31 +314,15 @@ function makeBuilder(ctx) {
424
314
  }
425
315
  function isEmptyName(nameDer) { return asn1.decode(nameDer).children.length === 0; }
426
316
 
427
- // ---- GeneralName encoding (RFC 5280 sec. 4.2.1.6) ----
428
317
  function ia5Content(s) {
429
- // Captured String / charCodeAt / bufferFrom: this converts a GeneralName value (a classified bare
430
- // string or an object-form value) to the emitted IA5String bytes, so co-resident code replacing any of
431
- // them after load could otherwise change which host the certificate names from the one that was checked.
432
318
  s = _String(s);
433
319
  for (var i = 0; i < s.length; i++) {
434
320
  if (_charCodeAt(s, i) > 0x7F) throw E("bad-input", "value requires 7-bit ASCII (IA5String): " + _stringify(s));
435
321
  }
436
322
  return _bufferFrom(s, "latin1");
437
323
  }
438
- // A bare-string GeneralName shorthand: classify a string into exactly one form, FAIL-CLOSED. RFC 5280
439
- // sec. 4.2.1.6 GeneralName is a CHOICE, so an input that does not UNAMBIGUOUSLY match one form throws
440
- // rather than being guessed -- the explicit object form ({ dNSName: s } etc.) is always the escape. The
441
- // per-form syntax is decided by the character-scan predicates above (no regex). Precedence resolves the
442
- // overlaps of the later tests: an IP literal is also LDH-with-dots (so it is tested first), and a
443
- // scheme://authority URL can carry userinfo that reads like an email (so URI is tested before rfc822Name).
444
- // Form detection only -- IA5String (7-bit) enforcement stays at ia5Content, so a non-ASCII value still
445
- // throws at encode.
446
324
  function _classifyBareGeneralName(s) {
447
325
  if (_packIpLiteral(s) !== null) return { iPAddress: s };
448
- // A string of only digit groups separated by dots is an IPv4 ATTEMPT; if packIpLiteral rejected it,
449
- // it is a MALFORMED address, not a dNSName. It reads two ways (an invalid IP and a syntactically
450
- // valid all-numeric name), so it is ambiguous and refused rather than silently encoded as a name the
451
- // caller did not intend -- a genuine all-numeric name uses the explicit { dNSName: ... } form.
452
326
  if (_looksLikeIPv4Shape(s)) {
453
327
  throw E("bad-input", "the bare GeneralName string " + _stringify(s) + " looks like an IPv4 address but is not a valid one; pass { iPAddress: ... } for an address or { dNSName: ... } for a name");
454
328
  }
@@ -460,16 +334,11 @@ function makeBuilder(ctx) {
460
334
  " as a dNSName, rfc822Name, iPAddress, or URI; pass an explicit form object, e.g. { dNSName: " + _qs + " }");
461
335
  }
462
336
  function encodeGeneralName(entry) {
463
- // A bare non-empty string is shorthand -- normalized to its single-key object form, fail-closed,
464
- // before the object checks below; an empty string is refused here (never classified).
465
337
  if (typeof entry === "string") {
466
338
  if (entry === "") throw E("bad-input", "an empty GeneralName value is not permitted (RFC 5280 sec. 4.2.1.6)");
467
339
  entry = _classifyBareGeneralName(entry);
468
340
  }
469
341
  if (!entry || typeof entry !== "object" || _isBufferChk(entry)) throw E("bad-input", "a GeneralName must be an object with exactly one name form");
470
- // Captured Object.keys / property read: this reads which form and value the certificate will emit for a
471
- // GeneralName (a freshly classified bare string, or a caller object), so a replaced Object.keys must not
472
- // be able to relabel the classified result into a different, attacker-chosen form after validation.
473
342
  var keys = _objectKeys(entry);
474
343
  if (keys.length !== 1) throw E("bad-input", "a GeneralName entry must have exactly one form, got " + keys.length);
475
344
  var k = keys[0], v = entry[k];
@@ -479,9 +348,6 @@ function makeBuilder(ctx) {
479
348
  case "dNSName": return b.contextPrimitive(2, ia5Content(v));
480
349
  case "uniformResourceIdentifier": case "uri": return b.contextPrimitive(6, ia5Content(v));
481
350
  case "iPAddress":
482
- // Accept a dotted-quad / colon-hex string (packed to its 4/16 network octets) as well as a
483
- // pre-packed Buffer, so an iPAddress SAN reads like dNSName/URI and the caller does not pack
484
- // octets by hand (a GeneralName iPAddress is a bare host address, RFC 5280 sec. 4.2.1.6).
485
351
  var ipBuf = v;
486
352
  if (typeof v === "string") {
487
353
  ipBuf = _packIpLiteral(v);
@@ -489,29 +355,14 @@ function makeBuilder(ctx) {
489
355
  }
490
356
  if (!_isBufferChk(ipBuf) || (ipBuf.length !== 4 && ipBuf.length !== 16)) throw E("bad-input", "iPAddress must be a 4- or 16-octet Buffer or an IPv4/IPv6 string");
491
357
  return b.contextPrimitive(7, ipBuf);
492
- case "directoryName": return b.explicit(4, encodeName(v)); // Name is a CHOICE -> the context tag is EXPLICIT
358
+ case "directoryName": return b.explicit(4, encodeName(v));
493
359
  case "otherName":
494
- // otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }, tagged
495
- // [0] IMPLICIT (RFC 5280 sec. 4.2.1.6). The value is EXPLICIT because ANY carries no tag
496
- // of its own, so the wrapper is what makes the encoding unambiguous -- the same shape the
497
- // decoder requires, and without it an SmtpUTF8Mailbox (RFC 8398 sec. 3) could be read but
498
- // never written.
499
360
  if (typeof v !== "object" || Buffer.isBuffer(v)) throw E("bad-input", "otherName must be an object { typeId, value }");
500
361
  if (typeof v.typeId !== "string" || !v.typeId) throw E("bad-input", "otherName requires a `typeId` OID string");
501
362
  if (!Buffer.isBuffer(v.value) || v.value.length === 0) {
502
363
  throw E("bad-input", "otherName requires a `value` Buffer holding one DER element");
503
364
  }
504
- // The value is spliced in raw and this verb SIGNS the result, so it is strictly
505
- // validated rather than taken on the caller's word. Framing alone is not DER: a
506
- // BOOLEAN whose content octet is 0x01, a NumericString holding "@", or a SET in no
507
- // canonical order all frame cleanly and would ship inside a [0] EXPLICIT wrapper
508
- // under a real signature. guard.der.tlv is the one place that rule lives.
509
365
  guard.der.tlv(v.value, E, "bad-input", "otherName `value`");
510
- // [0] IMPLICIT on a SEQUENCE replaces the SEQUENCE tag, so the content is the two
511
- // members concatenated -- build.contextConstructed takes content bytes, not elements.
512
- // The type-id encode is wrapped so a malformed OID reports in the CALLER's namespace
513
- // (x509/bad-input, csr/bad-input, ...) like every other raw-OID path here, rather than
514
- // surfacing the codec's own oid/* code from a shared builder the caller never named.
515
366
  var typeIdDer;
516
367
  try { typeIdDer = b.oid(v.typeId); }
517
368
  catch (e) { throw E("bad-input", "invalid otherName type-id OID " + JSON.stringify(v.typeId) + " (violates the X.660 arc bounds)", e); }
@@ -520,7 +371,6 @@ function makeBuilder(ctx) {
520
371
  }
521
372
  }
522
373
 
523
- // ---- extension-value encoders (the inverse of certExtensionDecoders) ----
524
374
  function extKeyUsage(names) {
525
375
  if (!_isArray(names) || !names.length) throw E("bad-input", "keyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
526
376
  var positions = names.map(function (n) {
@@ -530,17 +380,10 @@ function makeBuilder(ctx) {
530
380
  });
531
381
  return b.namedBitString(positions);
532
382
  }
533
- // Resolve an OID token to a dotted-decimal OID: a registered name via the registry, OR a raw dotted-OID
534
- // string accepted directly (an unregistered KeyPurposeId / policy OID for BIMI, document-signing or a
535
- // vendor purpose is a common valid input). A token that is neither fails closed (never silently accept a
536
- // typo'd name); a dotted string that passes the shape check is still arc-validated by b.oid's encoder.
537
383
  function _resolveOid(n, label) {
538
384
  var dotted = O(n);
539
385
  if (dotted != null) return dotted;
540
386
  if (typeof n === "string" && _isDottedOid(n)) {
541
- // The shape scan fixes the general form; b.oid's encoder is the authoritative X.660 arc-bounds check
542
- // (a first arc 0/1 caps the second at 39; an arc must DER-encode). A failure there is this producer's
543
- // bad-input, not a leaked oid/* code, so every shared-builder consumer keeps its own error contract.
544
387
  try { b.oid(n); }
545
388
  catch (e) { throw E("bad-input", "invalid " + label + " OID " + JSON.stringify(n) + " (violates the X.660 arc bounds)", e); }
546
389
  return n;
@@ -559,7 +402,7 @@ function makeBuilder(ctx) {
559
402
  }
560
403
  function extBasicConstraints(spec) {
561
404
  var children = [];
562
- if (spec.cA === true) children.push(b.boolean(true)); // cA=FALSE omitted (DER DEFAULT)
405
+ if (spec.cA === true) children.push(b.boolean(true));
563
406
  if (spec.pathLen != null) children.push(b.integer(pathLen(spec.pathLen)));
564
407
  return b.sequence(children);
565
408
  }
@@ -568,12 +411,7 @@ function makeBuilder(ctx) {
568
411
  return BigInt(v);
569
412
  }
570
413
  function extSki(keyid) { return b.octetString(keyid); }
571
- function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); } // keyIdentifier [0] IMPLICIT OCTET STRING
572
- // GeneralNames ::= SEQUENCE SIZE(1..MAX) OF GeneralName. With no implicitTag it is a universal SEQUENCE
573
- // (subjectAltName / issuerAltName / IssuerSerial.issuer). With an implicitTag it is a context-constructed
574
- // [n] whose tag REPLACES the SEQUENCE tag (RFC 5755 IMPLICIT TAGS: Holder.entityName [1],
575
- // RoleSyntax.roleAuthority [0], IetfAttrSyntax.policyAuthority [0]); the [n] node's children ARE the
576
- // GeneralName members, no inner SEQUENCE wrapper.
414
+ function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); }
577
415
  function encodeGeneralNames(entries, implicitTag) {
578
416
  if (!_isArray(entries) || !entries.length) throw E("bad-input", "a GeneralNames must carry at least one GeneralName");
579
417
  var members = entries.map(encodeGeneralName);
@@ -591,7 +429,6 @@ function makeBuilder(ctx) {
591
429
  return b.sequence([b.oid(pOid)]);
592
430
  }));
593
431
  }
594
- // Wrap a value in Extension ::= SEQUENCE { extnID, critical?, extnValue }; a FALSE critical is omitted.
595
432
  function ext(oidStr, critical, valueDer) {
596
433
  var children = [b.oid(oidStr)];
597
434
  if (critical) children.push(b.boolean(true));
@@ -599,15 +436,8 @@ function makeBuilder(ctx) {
599
436
  return b.sequence(children);
600
437
  }
601
438
 
602
- // The SHA-1 subjectKeyIdentifier (RFC 5280 sec. 4.2.1.2 method 1): SHA-1 of the subjectPublicKey BIT
603
- // STRING content, taken past the unused-bits octet. Hashing the whole SPKI or the BIT STRING TLV
604
- // gives a different, wrong identifier.
605
439
  function spkiKeyId(spkiDer) {
606
440
  var keyBytes = asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
607
- // RFC 5280 sec. 4.2.1.2 method 1 DEFINES the subjectKeyIdentifier as the SHA-1 of the
608
- // subjectPublicKey; this is a key identifier, not a signature or a collision-resistance use,
609
- // and the algorithm is fixed by the standard.
610
- // nosemgrep: pki-weak-hash-md5-sha1
611
441
  return nodeCrypto.createHash("sha1").update(keyBytes).digest();
612
442
  }
613
443
  function skiKeyId(val, spkiDer) {
@@ -616,27 +446,16 @@ function makeBuilder(ctx) {
616
446
  throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a BufferSource key id");
617
447
  }
618
448
 
619
- // ---- embedded-input validators ----
620
449
  function reqDer(v, what) {
621
450
  if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, ErrorClass, ctx.prefix + "/bad-input", what);
622
451
  throw E("bad-input", what + " must be a DER Buffer");
623
452
  }
624
- // The bound 2-arg form every makeBuilder consumer calls (`_b.reqDenseArray(list, what)`); it delegates
625
- // to the standalone guard above with this builder's namespace, so the density rule lives in ONE place.
626
453
  function reqDenseArray(list, what) { return reqDenseArrayImpl(list, what, rawErr, ctx.prefix + "/bad-input"); }
627
- // A caller-supplied DER a builder validates with a PEM-accepting parser and then embeds verbatim with
628
- // b.raw. Those parsers unwrap PEM from a byte source, so a PEM-armored input (a Buffer straight from
629
- // fs.readFileSync) passes the parse, but b.raw embeds the raw ASCII armor -- which the enclosing
630
- // structure's own parse rejects only later, after protection is computed, and with the wrong code.
631
- // Require a DER SEQUENCE first (a Certificate / CRL / CSR / PKIMessage / CertReqMessages is one; PEM
632
- // opens with 0x2D), so a PEM or non-DER input is a typed bad-input up front and the bytes the parse
633
- // checks are the bytes embedded.
634
454
  function reqDerSequence(v, what) {
635
455
  var der = reqDer(v, what);
636
456
  if (der.length === 0 || der[0] !== 0x30) throw E("bad-input", what + " must be DER (a SEQUENCE), not PEM or other bytes");
637
457
  return der;
638
458
  }
639
- // Full validation of an embedded SubjectPublicKeyInfo, walked through the parser the decoder uses.
640
459
  function assertValidSpki(spkiDer, what) {
641
460
  var node;
642
461
  try { node = asn1.decode(spkiDer); }
@@ -647,8 +466,6 @@ function makeBuilder(ctx) {
647
466
  throw E("bad-input", what + " is not a well-formed SubjectPublicKeyInfo", e);
648
467
  }
649
468
  }
650
- // A pre-encoded Extension ::= SEQUENCE { extnID OID, critical BOOLEAN OPTIONAL, extnValue OCTET STRING };
651
- // an explicit critical=FALSE is non-canonical (DER DEFAULT) and rejected.
652
469
  function assertValidExtension(der, idx) {
653
470
  var n;
654
471
  try { n = asn1.decode(der); }
@@ -665,11 +482,6 @@ function makeBuilder(ctx) {
665
482
  var last = n.children[n.children.length - 1];
666
483
  if (last.tagNumber !== asn1.TAGS.OCTET_STRING || last.tagClass !== "universal") throw E("bad-input", "pre-encoded extension [" + idx + "] extnValue must be an OCTET STRING");
667
484
  }
668
- // The requested v3 Extensions (a bare SEQUENCE OF Extension) from an object of the recognized keys, or
669
- // an array of pre-encoded Extension DER (shape- + recognized-value-validated). Shared by every enrollment
670
- // request that carries requested extensions (a PKCS#10 extensionRequest attribute, a CRMF CertTemplate
671
- // extensions [9]); a request REQUESTS extensions, so there are no CA cross-field gates. `spki` feeds the
672
- // subjectKeyIdentifier auto-derive.
673
485
  var BC_KEYS = { cA: 1, pathLen: 1, critical: 1 };
674
486
  var REQ_EXT_KEYS = {
675
487
  subjectAltName: 1, keyUsage: 1, keyUsageCritical: 1, extendedKeyUsage: 1, extendedKeyUsageCritical: 1,
@@ -701,8 +513,6 @@ function makeBuilder(ctx) {
701
513
  });
702
514
  var out = [];
703
515
  if (extSpec.subjectKeyIdentifier != null) {
704
- // Auto-deriving the SKI (subjectKeyIdentifier: true) hashes the subject public key, so it needs one;
705
- // reject the request when no public key is available (supply a Buffer key id instead).
706
516
  if (extSpec.subjectKeyIdentifier === true && spki == null) throw E("bad-input", "subjectKeyIdentifier auto-derive (true) requires the public key -- supply a Buffer key id, or include the public key");
707
517
  out.push(ext(O("subjectKeyIdentifier"), false, extSki(skiKeyId(extSpec.subjectKeyIdentifier, spki))));
708
518
  }
@@ -715,16 +525,12 @@ function makeBuilder(ctx) {
715
525
  return b.sequence(out);
716
526
  }
717
527
 
718
- // A positive, non-zero INTEGER of at most 20 content octets (RFC 5280 sec. 4.1.2.2 / RFC 5755 sec.
719
- // 4.2.5 -- the identical serial rule for a certificate and an attribute certificate). A random 20-octet
720
- // positive serial is generated when none is supplied. Accepts a BigInt, a safe integer, a decimal /
721
- // 0x-hex string, or a raw magnitude Buffer.
722
528
  function serialInteger(serial) {
723
529
  var v;
724
530
  if (serial == null) {
725
531
  var rnd = nodeCrypto.randomBytes(20);
726
- rnd[0] &= 0x7f; // keep the top bit clear so the magnitude stays <= 20 octets and positive
727
- if (rnd[0] === 0) rnd[0] = 0x01; // never all-zero leading -> non-zero and no redundant sign octet
532
+ rnd[0] &= 0x7f;
533
+ if (rnd[0] === 0) rnd[0] = 0x01;
728
534
  v = BigInt("0x" + rnd.toString("hex"));
729
535
  } else if (typeof serial === "bigint") { v = serial; }
730
536
  else if (typeof serial === "number") { if (!Number.isSafeInteger(serial)) throw E("bad-serial", "serialNumber number must be a safe integer (pass a BigInt, hex string, or Buffer for a value above 2^53-1)"); v = BigInt(serial); }
@@ -736,7 +542,6 @@ function makeBuilder(ctx) {
736
542
  if (asn1.decode(tlv).content.length > 20) throw E("bad-serial", "serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
737
543
  return tlv;
738
544
  }
739
- // Synthesize the parsed-cert shape resolveSignScheme reads (subjectPublicKeyInfo.algorithm) from a raw SPKI.
740
545
  function certLikeFromSpki(spkiDer) {
741
546
  var spki = asn1.decode(spkiDer);
742
547
  if (!spki.children || !spki.children.length) throw E("bad-input", "the signing key SPKI is not a SubjectPublicKeyInfo");
@@ -747,11 +552,6 @@ function makeBuilder(ctx) {
747
552
  return { subjectPublicKeyInfo: { algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
748
553
  }
749
554
 
750
- // Confirm the produced signature verifies under `spki`. This works for any key type (a PKCS#8
751
- // key, a WebCrypto CryptoKey, or any signer), where deriving-and-comparing the public key cannot
752
- // (a non-extractable CryptoKey has no exportable public half). This is the x509 chain self-check
753
- // AND the CSR proof of possession. Composite verifies both components. Returns a promise for
754
- // composite, sync-throws for classical.
755
555
  function assertSignatureVerifies(preimage, sig, spki, scheme) {
756
556
  if (scheme.composite) {
757
557
  return compositeSig.compositeVerify(spki, sig, preimage, scheme.composite, ErrorClass, code("unsupported-algorithm"), code("bad-input")).then(function (r) {
@@ -767,27 +567,16 @@ function makeBuilder(ctx) {
767
567
  else if (s.name === "RSA-PSS") ok = nodeCrypto.verify(scheme.digest, preimage, { key: pub, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: s.saltLength }, sig);
768
568
  else if (s.name === "RSASSA-PKCS1-v1_5") ok = nodeCrypto.verify(scheme.digest, preimage, pub, sig);
769
569
  // allow:eddsa-verify-without-loworder-gate -- a self-check that OUR just-produced signature verifies
770
- // under the key the caller controls, not a security verify of an untrusted EdDSA signature, so the
771
- // low-order-point gate (a forged-signature defense) does not apply.
772
- else ok = nodeCrypto.verify(null, preimage, pub, sig); // Ed25519 / Ed448 / ML-DSA / SLH-DSA
570
+ else ok = nodeCrypto.verify(null, preimage, pub, sig);
773
571
  } catch (e) { throw E("bad-input", "the signature self-check could not run against the public key", e); }
774
572
  if (!ok) throw E("bad-input", "the signing key does not correspond to the public key -- the signature would not verify");
775
573
  }
776
574
 
777
- // Do two SubjectPublicKeyInfo encodings name the same key? Bytes first, because that is the answer
778
- // almost always and it costs nothing. When they differ the question is still open, since one key has
779
- // more than one legal SPKI: an EC point may be carried compressed or uncompressed, so a P-256 key
780
- // written the compressed way is byte-unequal to the same key written the other. Comparing the DER
781
- // alone would refuse a proof of possession over a key the requester genuinely holds, while the
782
- // sibling arms of the same verb accept that template. The fallback asks the key layer, which
783
- // compares key material and still answers false for a different key, a different curve, or a
784
- // different algorithm. A key neither side can import is equal to nothing, so the refusal stands.
785
575
  function samePublicKey(spkiA, spkiB) {
786
576
  var a = _bufferFrom(spkiA), bb = _bufferFrom(spkiB);
787
577
  if (_bufferEquals(a, bb)) return true;
788
578
  var ka, kb;
789
579
  // allow:swallow-unverified an SPKI the key layer cannot import matches nothing, which is the
790
- // refusal the caller wants; the caller raises its own typed error on the false.
791
580
  try {
792
581
  ka = nodeCrypto.createPublicKey({ key: a, format: "der", type: "spki" });
793
582
  kb = nodeCrypto.createPublicKey({ key: bb, format: "der", type: "spki" });
@@ -810,31 +599,16 @@ function makeBuilder(ctx) {
810
599
  };
811
600
  }
812
601
 
813
- // The raw issuer / subject Name TLV of a parsed X.509 certificate (byte-identical). tbs field layout:
814
- // [version?] serial(0) signature(1) issuer(2) validity(3) subject(4). Used where a producer must chain a
815
- // name to an existing certificate exactly -- an issued certificate's issuer, a CMS SignerInfo issuer, an
816
- // attribute certificate's issuerName / holder baseCertificateID.
817
602
  function tbsNameField(cert, which) {
818
603
  var tbs = asn1.decode(cert.tbsBytes);
819
604
  var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
820
605
  return tbs.children[(hasVersion ? 1 : 0) + (which === "subject" ? 4 : 2)].bytes;
821
606
  }
822
607
 
823
- // The serialNumber of a parsed X.509 certificate, read from the SIGNED bytes. Same tbs layout as
824
- // above, and the same reason: where a producer binds an identity to an existing certificate, that
825
- // identity is issuer AND serial together. Deriving one from the bytes and reading the other from
826
- // the object lets the halves name different certificates, producing a Holder whose issuer is
827
- // genuine and whose serial is whatever the caller wrote.
828
608
  function tbsSerialNumber(cert) {
829
609
  var tbs = asn1.decode(cert.tbsBytes);
830
610
  var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
831
611
  return asn1.read.integer(tbs.children[hasVersion ? 1 : 0]);
832
612
  }
833
613
 
834
- // Frozen so a co-resident cannot reassign an exported guard (e.g. reqDenseArray) to bypass it: the
835
- // standalone consumers read reqDenseArray off this object at call time, so the object holding it must be
836
- // immutable, the way the guard-* modules freeze their surfaces.
837
- // Frozen so a co-resident cannot reassign an exported guard (e.g. reqDenseArray) to bypass it: the
838
- // standalone consumers read reqDenseArray off this object at call time, so the object holding it must be
839
- // immutable, the way the guard-* modules freeze their surfaces.
840
614
  module.exports = intrinsic.freeze({ makeBuilder: makeBuilder, reqDenseArray: reqDenseArrayImpl, KU_BIT: KU_BIT, tbsNameField: tbsNameField, tbsSerialNumber: tbsSerialNumber });