@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
@@ -32,36 +32,23 @@ var b = asn1.build;
32
32
  var C = constants;
33
33
  var C509Error = frameworkError.C509Error;
34
34
  function _err(code, message, cause) { return new C509Error(code, message, cause); }
35
- // The ECMAScript Date window is +/- 8.64e15 ms = +/- 8.64e12 seconds; a C509 ~time is an unsigned
36
- // epoch-seconds value, so only the upper bound can be exceeded (mirrors cbor-det read.time).
37
35
  var MAX_EPOCH_SECONDS = 8640000000000n;
38
36
 
39
- // The C509 integer registries (draft-20 sec. 8.6/sec. 8.8/sec. 8.14/sec. 8.15): a C509 int is a compact ALIAS of an
40
- // OID, resolved to the same name oid.byName returns for the DER form. Declared as int -> registered
41
- // OID NAME (never a dotted-decimal literal, which the oid-dotted-decimal-literal gate enforces);
42
- // oid.byName then yields the dotted string. A row whose target name is not registered fails closed at module load.
43
37
  function _name(n) { var d = oid.byName(n); if (!d) { throw new Error("schema-c509: unregistered OID name " + JSON.stringify(n)); } return n; }
44
38
 
45
- // sec. 8.14 issuerSignatureAlgorithm / signature (the subset v1 covers; negative = legacy SHA-1 values).
46
39
  var SIG_ALG_BY_INT = {
47
40
  0: _name("ecdsaWithSHA256"),
48
41
  1: _name("ecdsaWithSHA384"),
49
42
  2: _name("ecdsaWithSHA512"),
50
43
  };
51
- // sec. 8.15 subjectPublicKeyAlgorithm (int -> {alg, curve?} so the reconstruction can rebuild the SPKI).
52
44
  var PK_ALG_BY_INT = {
53
45
  0: { alg: _name("rsaEncryption") },
54
46
  1: { alg: _name("ecPublicKey"), curve: _name("prime256v1") },
55
47
  2: { alg: _name("ecPublicKey"), curve: _name("secp384r1") },
56
48
  3: { alg: _name("ecPublicKey"), curve: _name("secp521r1") },
57
49
  };
58
- // EC curve -> field size in bytes (the SEC1 coordinate width), for point-length validation.
59
50
  var EC_FIELD_BYTES = { "prime256v1": 32, "secp384r1": 48, "secp521r1": 66 };
60
51
 
61
- // sec. 8.6 RDN attribute types (abs(int) -> name; the sign selects the X.509 string type: positive utf8String,
62
- // negative printableString). An attribute whose value is always an IA5String is instead "unambiguously
63
- // represented using a non-negative int" (draft sec. 3.1.4). emailAddress is int 0, so its value reconstructs
64
- // as an IA5String and its sign carries no string-type meaning.
65
52
  var ATTR_BY_INT = {
66
53
  0: _name("emailAddress"),
67
54
  1: _name("commonName"),
@@ -75,11 +62,6 @@ var ATTR_BY_INT = {
75
62
  9: _name("organizationalUnitName"),
76
63
  10: _name("title"),
77
64
  };
78
- // sec. 8.8 extension types (abs(int) -> name; the sign selects criticality). The general-name-bearing
79
- // extensions (subjectAltName / issuerAltName / nameConstraints / cRLDistributionPoints / freshestCRL /
80
- // authorityInfoAccess / subjectInfoAccess) ride the shared sec. 8.13 GeneralNames value codec below; each
81
- // carries a specific CBOR value form and falls back to ~oid + byte-string only when a member is outside
82
- // the sec. 8.13 registry (an x400Address / ediPartyName general name, a non-URI access location, ...).
83
65
  var EXT_BY_INT = {
84
66
  1: _name("subjectKeyIdentifier"),
85
67
  2: _name("keyUsage"),
@@ -98,8 +80,6 @@ var EXT_BY_INT = {
98
80
  29: _name("freshestCRL"),
99
81
  30: _name("inhibitAnyPolicy"),
100
82
  31: _name("subjectInfoAccess"),
101
- // RFC 3779 resource-delegation extensions, and their RFC 8360 "v2" twins, which the draft
102
- // encodes "exactly like" the originals: same two codecs, four registry rows (sec. 8.8).
103
83
  32: _name("ipAddrBlocks"),
104
84
  33: _name("autonomousSysIds"),
105
85
  34: _name("ipAddrBlocksV2"),
@@ -107,8 +87,6 @@ var EXT_BY_INT = {
107
87
  36: _name("ocspNoCheck"),
108
88
  38: _name("tlsFeature"),
109
89
  };
110
- // The extensions whose compact CBOR value form (sec. 3.3) is invertible to/from the DER extnValue.
111
- // A registered extension outside this set carries its value as the ~oid + byte-string form (sec. 3.7).
112
90
  var EXT_COMPACT = {
113
91
  subjectKeyIdentifier: 1, keyUsage: 1, basicConstraints: 1, authorityKeyIdentifier: 1,
114
92
  extKeyUsage: 1, inhibitAnyPolicy: 1, ocspNoCheck: 1, tlsFeature: 1,
@@ -117,8 +95,6 @@ var EXT_COMPACT = {
117
95
  policyMappings: 1, policyConstraints: 1, subjectDirectoryAttributes: 1,
118
96
  ipAddrBlocks: 1, autonomousSysIds: 1, ipAddrBlocksV2: 1, autonomousSysIdsV2: 1,
119
97
  };
120
- // sec. 8.12 Extended Key Usages registry (C509 int -> registered id-kp purpose name). A KeyPurposeId
121
- // outside this set encodes as an unwrapped ~oid; a C509 int outside it fails closed on decode.
122
98
  var EKU_BY_INT = {
123
99
  0: _name("anyExtendedKeyUsage"), 1: _name("serverAuth"), 2: _name("clientAuth"),
124
100
  3: _name("codeSigning"), 4: _name("emailProtection"), 8: _name("timeStamping"), 9: _name("ocspSigning"),
@@ -126,29 +102,16 @@ var EKU_BY_INT = {
126
102
  14: _name("bundleSecurity"), 15: _name("cmcCA"), 16: _name("cmcRA"), 17: _name("cmcArchive"), 18: _name("cmKGA"),
127
103
  20: _name("fanDevice"),
128
104
  };
129
- var EKU_TO_INT = {}; // dotted OID -> C509 int (the encode inverse; keyed on the resolved OID)
105
+ var EKU_TO_INT = {};
130
106
  Object.keys(EKU_BY_INT).forEach(function (k) { EKU_TO_INT[oid.byName(EKU_BY_INT[k])] = Number(k); });
131
107
 
132
- // The shared pkix namespace + GeneralName leaves the general-name value codec composes for the DER ->
133
- // CBOR direction (the CVE-2009-2408 IA5 control-byte guard, the otherName/directoryName shape, the
134
- // iPAddress length and RFC 9549 subtree-base length are all enforced there, never re-validated by hand).
135
108
  var NS = pkix.makeNS("c509", C509Error, oid);
136
- var GN_LEAF = pkix.generalName(NS, { decodeValue: true }); // SAN-form GeneralName leaf
137
- var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: true }); // name-constraints subtree base
138
-
139
- // sec. 8.13 C509 General Names registry: for the standard forms the C509 int == the RFC 5280 sec. 4.2.1.6
140
- // GeneralName CHOICE context tag (1 rfc822Name, 2 dNSName, 4 directoryName, 6 URI, 7 iPAddress, 8
141
- // registeredID); the _generalNameToDer / _generalNameFromDer switches carry that mapping directly.
142
- // x400Address [3] and ediPartyName [5] have no registry row, so a GeneralNames carrying either is not
143
- // compact-encodable (the enclosing extension falls back to ~oid + byte-string).
144
- // The id-on otherName specials get their own negative C509 ints (sec. 8.13 / sec. 3.3): int -> the id-on
145
- // type-id OID name; the derived inverse keys on the resolved OID so a decode selects the negative int.
109
+ var GN_LEAF = pkix.generalName(NS, { decodeValue: true });
110
+ var GN_LEAF_SUBTREE = pkix.generalName(NS, { decodeValue: true, subtreeBase: true });
111
+
146
112
  var ON_NAME_BY_INT = { "-1": _name("hardwareModuleName"), "-2": _name("smtpUtf8Mailbox"), "-3": _name("macAddress") };
147
113
  var ON_INT_BY_OID = {};
148
114
  Object.keys(ON_NAME_BY_INT).forEach(function (k) { ON_INT_BY_OID[oid.byName(ON_NAME_BY_INT[k])] = Number(k); });
149
- // sec. 8.11 C509 Information Access registry (int -> registered access-method OID name; the id-ad methods
150
- // keep the id-ad- prefix, see oid.js). A method OID outside the table encodes as ~oid; a C509 method int
151
- // outside it fails closed. IA_TO_INT is the derived encode inverse, keyed on the resolved OID.
152
115
  var IA_BY_INT = {
153
116
  1: _name("ocsp"), 2: _name("caIssuers"), 3: _name("id-ad-timeStamping"), 5: _name("id-ad-caRepository"),
154
117
  10: _name("id-ad-rpkiManifest"), 11: _name("id-ad-signedObject"), 13: _name("id-ad-rpkiNotify"),
@@ -156,9 +119,6 @@ var IA_BY_INT = {
156
119
  var IA_TO_INT = {};
157
120
  Object.keys(IA_BY_INT).forEach(function (k) { IA_TO_INT[oid.byName(IA_BY_INT[k])] = Number(k); });
158
121
 
159
- // sec. 8.9 C509 Certificate Policies registry (int -> registered policy OID name; the CA/Browser Forum
160
- // levels, the RFC 3779 id-cp-ipAddr policies, and the GSMA SGP.22 id-rspRole roles). A policy OID outside
161
- // this set encodes as ~oid; a C509 policy int outside it fails closed. CP_TO_INT is the derived inverse.
162
122
  var CP_BY_INT = {
163
123
  0: _name("anyPolicy"), 1: _name("domain-validated"), 2: _name("organization-validated"),
164
124
  3: _name("individual-validated"), 4: _name("ev-guidelines"),
@@ -173,16 +133,11 @@ var CP_BY_INT = {
173
133
  };
174
134
  var CP_TO_INT = {};
175
135
  Object.keys(CP_BY_INT).forEach(function (k) { CP_TO_INT[oid.byName(CP_BY_INT[k])] = Number(k); });
176
- // sec. 8.10 C509 Policies Qualifiers registry (int -> id-qt qualifier OID name; the derived inverse).
177
136
  var PQ_BY_INT = { 1: _name("cps"), 2: _name("unotice") };
178
137
  var PQ_TO_INT = {};
179
138
  Object.keys(PQ_BY_INT).forEach(function (k) { PQ_TO_INT[oid.byName(PQ_BY_INT[k])] = Number(k); });
180
139
 
181
- // ---- field readers (the unwrapped ~biguint / ~time / ~oid contracts; draft-20 sec. 3.1) ----
182
140
 
183
- // ~biguint (sec. 3.1.2): a BARE byte string (major type 2), big-endian magnitude, the non-negative
184
- // leading 0x00 OMITTED. This is not the shipped read.biguint, which requires the tag-2 wrapper and
185
- // rejects <= 8-byte content. A leading 0x00 is non-minimal; content over the cap is rejected.
186
141
  function _biguint(node, code, label) {
187
142
  if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~biguint)");
188
143
  var b = node.content;
@@ -191,18 +146,14 @@ function _biguint(node, code, label) {
191
146
  return b.length ? BigInt("0x" + b.toString("hex")) : 0n;
192
147
  }
193
148
 
194
- // ~time (sec. 3.1.5): a BARE unsigned integer (major type 0), epoch seconds. A major-type-1 / tag / float
195
- // MUST reject. Bound to the Date window; the CBOR simple null (permitted only for notAfter) -> null.
196
149
  function _time(node, allowNull, label) {
197
- if (allowNull && node.majorType === 7 && node.ai === 22) return null; // CBOR simple null (0xF6)
150
+ if (allowNull && node.majorType === 7 && node.ai === 22) return null;
198
151
  if (node.majorType !== 0) throw _err("c509/bad-validity", label + " must be an unwrapped CBOR epoch integer (~time)");
199
152
  var secs = node.argument;
200
153
  if (secs > MAX_EPOCH_SECONDS) throw _err("c509/bad-validity", label + " is outside the representable Date range");
201
154
  return new Date(C.TIME.seconds(Number(secs)));
202
155
  }
203
156
 
204
- // ~oid (sec. 3.1.3 etc.): a BARE byte string carrying the BER OID content octets (RFC 9090), no tag-111
205
- // head. Compose asn1.decodeOidContent -> dotted -> oid.name (the same the tag reader does internally).
206
157
  function _oidName(node, code, label) {
207
158
  if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~oid)");
208
159
  var dotted;
@@ -211,11 +162,10 @@ function _oidName(node, code, label) {
211
162
  return { oid: dotted, name: oid.name(dotted) || dotted };
212
163
  }
213
164
 
214
- // AlgorithmIdentifier (sec. 3.1.3/sec. 3.1.7): int (registry) | ~oid (bare bytes) | [ ~oid, params ].
215
165
  function _algorithm(node, byInt, code, label) {
216
166
  if (node.majorType === 0 || node.majorType === 1) {
217
167
  var i = Number(cbor.read.int(node));
218
- var mapped = byInt[i]; // registry keyed by the signed int (negatives are legacy SHA-1 rows)
168
+ var mapped = byInt[i];
219
169
  if (mapped === undefined) throw _err("c509/unknown-algorithm", label + " integer " + i + " has no C509 registry row");
220
170
  if (typeof mapped === "string") return { name: mapped, oid: oid.byName(mapped) };
221
171
  return { name: mapped.alg, oid: oid.byName(mapped.alg), curve: mapped.curve || null };
@@ -223,27 +173,17 @@ function _algorithm(node, byInt, code, label) {
223
173
  if (node.majorType === 2) { var r = _oidName(node, code, label); return { name: r.name, oid: r.oid }; }
224
174
  if (node.majorType === 4 && node.children && node.children.length === 2) {
225
175
  var a = _oidName(node.children[0], code, label);
226
- // The [~oid, params] form carries the DER parameters as a CBOR byte string; a non-byte-string here
227
- // is malformed and cannot be reconstructed (b.raw would append garbage), so it fails closed.
228
176
  if (node.children[1].majorType !== 2) throw _err(code, label + " algorithm parameters must be a CBOR byte string");
229
- // The parameters carry one complete DER element spliced into the reconstruction. An empty byte
230
- // string is no element at all: it rebuilds the same AlgorithmIdentifier as the bare ~oid form,
231
- // which is how the CDDL already spells "no parameters", so accepting it would give one
232
- // algorithm two encodings, and one X.509 signature would cover both.
233
177
  if (node.children[1].content.length === 0) throw _err(code, label + " algorithm parameters must not be an empty byte string; omit them with the bare ~oid form (draft sec. 3.1.3)");
234
178
  return { name: a.name, oid: a.oid, parameters: node.children[1].content };
235
179
  }
236
180
  throw _err(code, label + " is not a C509 AlgorithmIdentifier (int / ~oid / [~oid, params])");
237
181
  }
238
182
 
239
- // SpecialText attribute value (sec. 3.1.4/sec. 3.1.6): text | bytes (even-length-hex optimization) | tag-48
240
- // (a MAC address, RFC 9542). v1 surfaces the value; the DN string uses the text form.
241
183
  function _specialText(node) {
242
184
  if (node.majorType === 3) return { text: cbor.read.textString(node) };
243
185
  if (node.majorType === 2) return { hex: node.content.toString("hex") };
244
186
  if (node.majorType === 6 && Number(node.argument) === 48) {
245
- // A tag-48 MAC address (RFC 9542) MUST wrap a CBOR byte string of 6 (EUI-48/MAC-48) or 8 (EUI-64)
246
- // bytes; anything else is malformed and cannot reconstruct a well-formed EUI-64 commonName.
247
187
  if (!node.children || !node.children[0] || node.children[0].majorType !== 2) {
248
188
  throw _err("c509/bad-name", "a tag-48 MAC-address value must wrap a CBOR byte string");
249
189
  }
@@ -254,34 +194,22 @@ function _specialText(node) {
254
194
  throw _err("c509/bad-name", "an attribute value is not a C509 SpecialText (text / bytes / tag-48)");
255
195
  }
256
196
 
257
- // draft sec. 3.1.4 fixes which of the three SpecialText spellings a value takes; the choice belongs to
258
- // the specification, not the sender. A text string of an even length >= 2 drawn only from '0'-'9'/'a'-'f'
259
- // is encoded as a byte string; a text string of the form "HH-HH-HH-HH-HH-HH-HH-HH" is encoded as a tag-48
260
- // MAC address, 48-bit when it matches "HH-HH-HH-FF-FE-HH-HH-HH" and 64-bit otherwise; anything else is a
261
- // text string. Accepting a second spelling of one value would give the reconstructed certificate more than
262
- // one C509 encoding, so a single X.509 signature would cover them all and two distinct byte strings would
263
- // name one certificate. A natively signed certificate is exempt: sec. 3.1.4 says bytes and tag 48
264
- // there "do not correspond to any predefined text string encoding and may also be used for other attribute
265
- // types", so no canonical text spelling exists to hold one to.
266
- // Even-length lowercase hex ("HHHH..."), decided by character so no regex match protocol -- a live,
267
- // replaceable exec -- stands between the canonicalization gate and the value it inspects.
268
197
  function _isEvenHexLower(s) {
269
198
  var n = s.length;
270
199
  if (n < 2 || (n & 1) !== 0) return false;
271
200
  for (var i = 0; i < n; i++) {
272
201
  var c = s.charCodeAt(i);
273
- if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false; // 0-9 a-f
202
+ if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false;
274
203
  }
275
204
  return true;
276
205
  }
277
- // EUI-64 text form: eight uppercase hex pairs joined by single hyphens ("HH-HH-...-HH", 23 chars).
278
206
  function _isEui64Text(s) {
279
207
  if (s.length !== 23) return false;
280
208
  for (var i = 0; i < 23; i++) {
281
- if ((i % 3) === 2) { if (s.charCodeAt(i) !== 45) return false; } // "-" at 2,5,...,20
209
+ if ((i % 3) === 2) { if (s.charCodeAt(i) !== 45) return false; }
282
210
  else {
283
211
  var c = s.charCodeAt(i);
284
- if (!((c >= 48 && c <= 57) || (c >= 65 && c <= 70))) return false; // 0-9 A-F
212
+ if (!((c >= 48 && c <= 57) || (c >= 65 && c <= 70))) return false;
285
213
  }
286
214
  }
287
215
  return true;
@@ -299,7 +227,6 @@ function _assertCanonicalSpecialText(node, isNative, E) {
299
227
  return;
300
228
  }
301
229
  if (node.majorType === 2) {
302
- // An empty byte string renders as the empty text, whose canonical spelling is a text string.
303
230
  if (node.content.length === 0) throw _err(E, "an empty attribute value must be encoded as a CBOR text string (draft sec. 3.1.4)");
304
231
  return;
305
232
  }
@@ -309,14 +236,6 @@ function _assertCanonicalSpecialText(node, isNative, E) {
309
236
  }
310
237
  }
311
238
 
312
- // A single Name (sec. 3.1.4/sec. 3.1.6): the CBOR simple null (issuer only) | a bare SpecialText single
313
- // commonName | an array of RDNAttributes. Surfaces { dn, rdns, eui64? } shape-compatible with x509.
314
- // An rdn's (attribute type, sign) pair DECLARES an X.509 string type, so its text must be valid for that type --
315
- // a printableString sign over a non-PrintableString character, a non-ASCII emailAddress (IA5String-only), or a
316
- // value outside the type's SIZE. Assert it by running the same builder the reconstruction uses, so a Name this
317
- // parse accepts is always one it can rebuild, and a natively-signed certificate (which never reconstructs) is
318
- // held to the identical rule. Both Name forms get it: the array form and the bare single-commonName form.
319
- // The builder's asn1/* fault is re-thrown in this module's domain so it cannot leak onto the parse surface.
320
239
  function _assertAttrValue(rdn) {
321
240
  try { _reconAttrValue(rdn); }
322
241
  catch (e) {
@@ -326,16 +245,10 @@ function _assertAttrValue(rdn) {
326
245
  }
327
246
 
328
247
  function _name509(node, isSubject, isNative) {
329
- if (!isSubject && node.majorType === 7 && node.ai === 22) return null; // issuer == subject (self-signed)
330
- // A bare SpecialText (not an array) is a single commonName attribute (attributeType == +1).
248
+ if (!isSubject && node.majorType === 7 && node.ai === 22) return null;
331
249
  if (node.majorType === 3 || node.majorType === 2 || node.majorType === 6) {
332
250
  var sv = _specialText(node);
333
251
  _assertCanonicalSpecialText(node, isNative, "c509/bad-name");
334
- // The bare form is a single commonName. Its value is held to the same rules the array form applies, so a
335
- // natively-signed certificate (which never reconstructs) cannot carry a value the reconstruction would refuse.
336
- // A tag-48 MAC renders to a fixed 17-character EUI-64 string that satisfies every value rule by
337
- // construction, and _reconAttrValue short-circuits on eui64 before any of them, which makes an assertion
338
- // here a no-op. The rules bind on the TEXT forms below.
339
252
  if (sv.eui64) return { rdns: [{ type: "commonName", eui64: sv.eui64 }], eui64: sv.eui64, dn: "CN=" + _macToEui64String(sv.eui64) };
340
253
  var val = sv.text !== undefined ? sv.text : sv.hex;
341
254
  var bareRdn = { type: "commonName", value: val };
@@ -346,88 +259,41 @@ function _name509(node, isSubject, isNative) {
346
259
  var rdns = [];
347
260
  var parts = [];
348
261
  var kids = node.children || [];
349
- // Each RDN attribute is an (attributeType, attributeValue) pair; an odd-length array is a dangling
350
- // attribute type with no value, so the whole Name is rejected.
351
262
  if (kids.length % 2 !== 0) throw _err("c509/bad-name", "a C509 Name array must be attribute-type/value pairs (dangling attribute type)");
352
- // RFC 5280 sec. 4.1.2.4 -- the issuer MUST be a non-empty distinguished name (only the SUBJECT may be empty,
353
- // for the subjectAltName case). An empty issuer array would reconstruct a certificate this toolkit's own
354
- // x509.parse refuses to load (x509/bad-issuer), so the codec must not produce one.
355
263
  if (kids.length === 0 && !isSubject) throw _err("c509/bad-name", "the issuer Name must not be empty (RFC 5280 sec. 4.1.2.4)");
356
264
  for (var i = 0; i + 1 < kids.length; i += 2) {
357
- // The attributeType slot must be a CBOR integer (major type 0/1). Guard the major type before the read
358
- // so a non-integer type slot fails in this module's own domain (c509/bad-name), matching the value slot,
359
- // and cbor.read.int's cbor/unexpected-major fault never leaks onto the parse surface. Reached with
360
- // attacker-controlled bytes via the sec. 8.13 directoryName general name (subjectAltName, authorityKey-
361
- // Identifier issuer, cRLDistributionPoints cRLIssuer, nameConstraints subtree base) and the top-level Name.
362
265
  if (kids[i].majorType !== 0 && kids[i].majorType !== 1) throw _err("c509/bad-name", "a C509 Name attribute type must be a CBOR integer");
363
266
  var ti = Number(cbor.read.int(kids[i]));
364
- // draft sec. 3.1.4: "in natively signed C509 certificates all CBOR ints SHALL be non-negative." The sign
365
- // exists only to reproduce the string type of an original X.509 DER, which a native certificate does not
366
- // have. A negative attributeType there is an encoding no conformant producer emits, and accepting it
367
- // would let this parser read a natively-signed name a conformant peer must reject.
368
267
  if (isNative && ti < 0) throw _err("c509/bad-name", "a natively signed C509 Name attribute type integer must be non-negative (draft sec. 3.1.4), got " + ti);
369
268
  var tname = ATTR_BY_INT[Math.abs(ti)];
370
269
  if (tname === undefined) throw _err("c509/bad-name", "attribute type integer " + ti + " has no C509 registry row");
371
270
  var v = _specialText(kids[i + 1]);
372
271
  _assertCanonicalSpecialText(kids[i + 1], isNative, "c509/bad-name");
373
272
  var vv = v.text !== undefined ? v.text : (v.hex !== undefined ? v.hex : _macToEui64String(v.eui64));
374
- // The rdn carries the RENDERED text, because the EUI shortcut
375
- // in _reconAttrValue is unconditional and would take precedence over the attribute's declared string type,
376
- // rebuilding a tag-48 emailAddress or countryName as a UTF8String. Only the bare-SpecialText form above is
377
- // always a commonName and may carry it; here the value is held to whatever type the attribute integer declares.
378
273
  var rdn = { type: tname, value: vv, printable: ti < 0 };
379
274
  _assertAttrValue(rdn);
380
275
  rdns.push(rdn);
381
276
  parts.push(_shortName(tname) + "=" + guard.name.escapeDnValue(vv));
382
277
  }
383
- // sec. 3.1.4: "If Name contains a single 'common name' attribute with attributeType = +1, it is for
384
- // compactness encoded as just the SpecialText containing the single attribute value." The array
385
- // spelling of that one case is therefore a second encoding of a name that already has a canonical
386
- // one. Only +1 has a bare form; a negative (printableString) commonName keeps the array.
387
278
  if (!isNative && rdns.length === 1 && rdns[0].type === "commonName" && !rdns[0].printable) {
388
279
  throw _err("c509/bad-name", "a Name holding a single +1 commonName must be encoded as a bare SpecialText, not an array (draft sec. 3.1.4)");
389
280
  }
390
281
  return { rdns: rdns, dn: parts.join(", ") };
391
282
  }
392
- // The rendered dn uses the same spelling as every other name renderer here (schema-pkix): the RDNs in
393
- // certificate order joined by ", ", which is what `openssl x509 -subject` prints. One certificate therefore
394
- // has one dn string whichever parser read it, and the interop gate can pin both against openssl. It is
395
- // deliberately not the RFC 4514 / LDAP distinguishedName (which reverses the RDNs and drops the space).
396
- // What IS taken from RFC 4514 is sec. 2.4 value escaping, through the shared guard: raw concatenation
397
- // would let a single attribute whose value contains a comma render identically to a genuine multi-RDN
398
- // name (a spoofable identity string, and the only name surface a natively signed certificate has, since
399
- // it never reconstructs DER), and would carry control bytes into logs.
400
- // The label table is the one schema-pkix renders with (pki.C.NAMES.DN_SHORT), not a local list: a hand-rolled
401
- // one covers whichever attributes it was written for and silently renders the rest by their long name,
402
- // so the same certificate would still read differently depending on which parser produced the string.
403
283
  function _shortName(n) { return (n && constants.NAMES.DN_SHORT[n]) || n; }
404
284
 
405
- // ---- compact per-extension value codec (draft-20 sec. 3.3) -------------------
406
- // Each registered extension in EXT_COMPACT has a specific CBOR encoding of its value that is invertible
407
- // to/from the DER extnValue inner content (the bytes _reconExtensions wraps in an OCTET STRING). Native
408
- // C509 MUST use the specific form (sec. 3.7); the decode side reconstructs the DER, the encode side emits
409
- // the compact form only when it round-trips byte-for-byte (else the ~oid + byte-string fallback).
410
285
 
411
- // A CBOR unsigned integer node -> its BigInt value (major type 0 guarantees non-negative). Arbitrary
412
- // precision: a DER INTEGER holds any width, so there is no cap; the round-trip guard verifies the reconstruction.
413
286
  function _cborUint(node, label) {
414
287
  if (node.majorType !== 0) throw _err("c509/bad-extensions", "a " + label + " value must be a CBOR unsigned integer");
415
288
  return cbor.read.int(node);
416
289
  }
417
- // A CBOR integer node (either sign) -> its BigInt value (basicConstraints uses -2/-1/pathLen).
418
290
  function _cborIntVal(node, label) {
419
291
  if (node.majorType !== 0 && node.majorType !== 1) throw _err("c509/bad-extensions", "a " + label + " value must be a CBOR integer");
420
292
  return cbor.read.int(node);
421
293
  }
422
- // A non-negative reconstruct-side count (a policyConstraints / inhibitAnyPolicy SkipCerts, a basicConstraints
423
- // pathLenConstraint) narrowed to [0, 2^31-1] through guard.range.uint31, the same bound the toolkit's own DER
424
- // decoders (schema-pkix) put on these INTEGER (0..MAX) counts. A native CBOR value past 2^31-1 fails closed
425
- // here; it never reconstructs a DER the toolkit's own decoder would then reject, because the reconstruct path
426
- // must produce nothing that decoder rejects. Returns a Number; b.integer narrows it to the minimal DER INTEGER.
427
294
  function _boundCount(value, label) {
428
295
  return guard.range.uint31(value, _err, "c509/bad-extensions", label + " (0..2^31-1)");
429
296
  }
430
- // A C509 KeyPurposeId (int registry alias OR unwrapped ~oid) -> the dotted extended-key-usage OID.
431
297
  function _ekuPurposeOid(node) {
432
298
  if (node.majorType === 0 || node.majorType === 1) {
433
299
  var i = Number(cbor.read.int(node));
@@ -435,34 +301,16 @@ function _ekuPurposeOid(node) {
435
301
  if (nm === undefined) throw _err("c509/bad-extensions", "an extKeyUsage int " + i + " has no C509 registry row");
436
302
  return oid.byName(nm);
437
303
  }
438
- // A ~oid KeyPurposeId routes through _oidName, which validates the BER OID content and remaps an oid/*
439
- // fault to c509/bad-extensions (fault preserved as .cause). A non-byte-string node fails closed there too.
440
304
  return _oidName(node, "c509/bad-extensions", "an extKeyUsage KeyPurposeId").oid;
441
305
  }
442
306
 
443
- // ---- the shared C509 GeneralNames value codec (draft-20 sec. 3.3 / sec. 8.13) ------------------
444
- // One int <-> DER-context-tag pair (_generalNameToDer / _generalNameFromDer) drives every general-name-
445
- // bearing extension (subjectAltName, nameConstraints, authorityKeyIdentifier, cRLDistributionPoints,
446
- // authorityInfoAccess, ...), so a tag/int mapping bug cannot diverge across them. The DER -> CBOR side
447
- // composes the shared pkix.generalName leaf; the CBOR -> DER side builds directly with asn1.build.*, the
448
- // exact inverse. Every encode stays behind the extension-level byte-exact round-trip guard
449
- // (_tryCompactExtValue), so a member the codec cannot invert byte-for-byte falls the whole extension back
450
- // to the conformant ~oid + byte-string form (sec. 3.7). A partial GeneralNames is never emitted.
451
307
 
452
- // The unwrapped ~oid CBOR form (a bare byte string of the BER OID content octets; RFC 9090).
453
308
  function _oidCbor(dotted) { return cbor.build.byteString(asn1.encodeOidContent(dotted)); }
454
- // The CBOR simple value null (0xF6): the omitted permitted / excluded / reasons / cRLIssuer field.
455
309
  function _isCborNull(node) { return node.majorType === 7 && node.ai === 22; }
456
310
 
457
- // A CBOR text node -> the IA5String content octets of an IMPLICIT [n] GeneralName (rfc822Name/dNSName/URI).
458
- // IA5String is 7-bit ASCII, so a code point > 0x7f cannot be one; a control byte (C0/DEL) is an injection
459
- // vector (CVE-2009-2408) the shared name guard rejects. That matches the pkix.generalName decode side, so a
460
- // value it rejects also fails the reconstruction direction (no round-trip guard runs on decode).
461
311
  function _ia5Bytes(node, tag) {
462
312
  if (node.majorType !== 3) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] value must be a CBOR text string (IA5String)");
463
313
  var s = cbor.read.textString(node);
464
- // An empty IA5 GeneralName is rejected by the shared pkix.generalName leaf (a non-empty rfc822Name /
465
- // dNSName / URI), so the reconstruction side rejects it too.
466
314
  if (s.length === 0) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] IA5String must be non-empty");
467
315
  for (var i = 0; i < s.length; i++) { if (s.charCodeAt(i) > 0x7f) throw _err("c509/bad-extensions", "a GeneralName [" + tag + "] IA5String has a non-ASCII code point"); }
468
316
  var buf = Buffer.from(s, "latin1");
@@ -470,35 +318,27 @@ function _ia5Bytes(node, tag) {
470
318
  return buf;
471
319
  }
472
320
 
473
- // A named-bit-list value (bit i -> named bit i, MSB-first; the 9-bit keyUsage / ReasonFlags range) -> the
474
- // DER BIT STRING content { unusedBits, bytes }, or null when the value is outside the 9 defined bits.
475
321
  function _namedBitsToContent(value) {
476
322
  if (!Number.isInteger(value) || value <= 0 || value > 0x1ff) return null;
477
- var hi = 0; for (var t = value; t; t >>= 1) hi++; hi -= 1; // highest set bit index
323
+ var hi = 0; for (var t = value; t; t >>= 1) hi++; hi -= 1;
478
324
  var buf = Buffer.alloc((hi >> 3) + 1);
479
325
  for (var bit = 0; bit <= hi; bit++) { if (value & (1 << bit)) buf[bit >> 3] |= 0x80 >> (bit & 7); }
480
326
  return { unusedBits: 7 - (hi & 7), bytes: buf };
481
327
  }
482
- // The inverse: a DER BIT STRING content -> the named-bit value (bit i -> 2^i), or null when out of range.
483
328
  function _namedBitsFromContent(bytes, unusedBits) {
484
329
  var total = bytes.length * 8 - unusedBits, value = 0;
485
330
  for (var bit = 0; bit < total && bit < 31; bit++) { if (bytes[bit >> 3] & (0x80 >> (bit & 7))) value |= (1 << bit); }
486
331
  return value > 0 && value <= 0x1ff ? value : null;
487
332
  }
488
333
 
489
- // One C509 (int, value) general name -> one DER GeneralName TLV. `ipMode` (name-constraints subtree base)
490
- // only changes the iPAddress arm: SAN is a bare 4/16-octet address, a subtree base is the RFC 9549 form.
491
334
  function _generalNameToDer(intVal, valueNode, ipMode, isNative) {
492
- if (intVal === 1 || intVal === 2 || intVal === 6) return b.contextPrimitive(intVal, _ia5Bytes(valueNode, intVal)); // IMPLICIT IA5String
493
- if (intVal === 4) return b.explicit(4, _reconName(_name509(valueNode, true, isNative))); // directoryName [4] EXPLICIT Name
494
- if (intVal === 7) return b.contextPrimitive(7, ipMode ? _ncIpToDer(valueNode) : _sanIpBytes(valueNode)); // iPAddress [7] IMPLICIT OCTET STRING
495
- if (intVal === 8) return b.contextPrimitive(8, asn1.encodeOidContent(_oidName(valueNode, "c509/bad-extensions", "a registeredID [8]").oid)); // registeredID [8] IMPLICIT OID
496
- if (intVal === 0 || intVal === -1 || intVal === -2 || intVal === -3) return _otherNameToDer(valueNode, intVal); // otherName [0]
335
+ if (intVal === 1 || intVal === 2 || intVal === 6) return b.contextPrimitive(intVal, _ia5Bytes(valueNode, intVal));
336
+ if (intVal === 4) return b.explicit(4, _reconName(_name509(valueNode, true, isNative)));
337
+ if (intVal === 7) return b.contextPrimitive(7, ipMode ? _ncIpToDer(valueNode) : _sanIpBytes(valueNode));
338
+ if (intVal === 8) return b.contextPrimitive(8, asn1.encodeOidContent(_oidName(valueNode, "c509/bad-extensions", "a registeredID [8]").oid));
339
+ if (intVal === 0 || intVal === -1 || intVal === -2 || intVal === -3) return _otherNameToDer(valueNode, intVal);
497
340
  throw _err("c509/bad-extensions", "GeneralName int " + intVal + " has no C509 sec. 8.13 registry row");
498
341
  }
499
- // The DER GeneralName leaf result { tagNumber, value, bytes } -> a [intCbor, valueCbor] pair, or null when
500
- // the name is not compact-representable (x400Address [3] / ediPartyName [5], a directoryName the compact
501
- // Name codec cannot hold, a non-canonical name-constraints IP mask).
502
342
  function _generalNameFromDer(gn, ipMode) {
503
343
  var t = gn.tagNumber;
504
344
  if (t === 1 || t === 2 || t === 6) return [cbor.build.int(BigInt(t)), cbor.build.textString(gn.value)];
@@ -509,13 +349,9 @@ function _generalNameFromDer(gn, ipMode) {
509
349
  }
510
350
  if (t === 8) return [cbor.build.int(8n), _oidCbor(gn.value)];
511
351
  if (t === 0) return _otherNameFromDer(gn.value);
512
- return null; // tags 3 / 5 (x400Address / ediPartyName) -- no sec. 8.13 registry row
352
+ return null;
513
353
  }
514
354
 
515
- // A flat CBOR array [int, value, int, value, ...] -> the list of DER GeneralName TLVs (the caller wraps
516
- // them: a universal SEQUENCE for SAN/IAN, an implicit [n] for AKI issuer / DP cRLIssuer). These are always
517
- // SAN-form (the RFC 9549 subtree-base iPAddress form is name-constraints-only and routes through
518
- // _subtreesToDer / _subtreesFromDer, which call the singular codec with ipMode directly).
519
355
  function _generalNamesToDer(node, isNative) {
520
356
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a GeneralNames value must be a CBOR array");
521
357
  var kids = node.children;
@@ -524,8 +360,6 @@ function _generalNamesToDer(node, isNative) {
524
360
  for (var i = 0; i + 1 < kids.length; i += 2) out.push(_generalNameToDer(Number(_cborIntVal(kids[i], "a GeneralName type")), kids[i + 1], false, isNative));
525
361
  return out;
526
362
  }
527
- // The inverse: a list of DER GeneralName nodes -> the flat CBOR items, or null if ANY member is not
528
- // compact-representable (the whole enclosing extension then falls back, and no partial GeneralNames is produced).
529
363
  function _generalNamesFromDer(gnNodes) {
530
364
  var out = [];
531
365
  for (var i = 0; i < gnNodes.length; i++) {
@@ -536,85 +370,67 @@ function _generalNamesFromDer(gnNodes) {
536
370
  return out;
537
371
  }
538
372
 
539
- // otherName [0] IMPLICIT SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }. The context tag
540
- // REPLACES the SEQUENCE tag (RFC 5280 IMPLICIT tagging), so the [0] node holds type-id then value directly
541
- // (no inner universal SEQUENCE). The generic form (C509 int 0) carries [ ~oid(type-id), bytes(the [0]
542
- // EXPLICIT inner value's raw TLV) ]; the id-on specials (-1/-2/-3) carry the RFC 4108 / RFC 9598 /
543
- // lamps-macaddress value shapes and reconstruct the full OtherName.
544
373
  function _otherNameToDer(node, intVal) {
545
374
  var typeId, inner;
546
375
  if (intVal === 0) {
547
376
  if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a generic otherName value must be a CBOR [ ~oid, bytes ] pair");
548
377
  typeId = _oidName(node.children[0], "c509/bad-extensions", "an otherName type-id").oid;
549
378
  if (node.children[1].majorType !== 2) throw _err("c509/bad-extensions", "an otherName value must be a CBOR byte string (the [0] EXPLICIT inner TLV)");
550
- // The [0] EXPLICIT inner TLV is an ANY spliced verbatim: strict-validate it, not just its framing.
551
379
  inner = _requireStrictDerTlv(node.children[1].content, "c509/bad-extensions", "an otherName value");
552
- } else if (intVal === -1) { // id-on-hardwareModuleName: [ ~oid(hwType), bytes(hwSerialNum) ] -> HardwareModuleName
380
+ } else if (intVal === -1) {
553
381
  if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "an id-on-hardwareModuleName value must be a CBOR [ ~oid, bytes ] pair");
554
382
  if (node.children[1].majorType !== 2) throw _err("c509/bad-extensions", "a hardwareModuleName hwSerialNum must be a CBOR byte string");
555
383
  typeId = oid.byName("hardwareModuleName");
556
384
  inner = b.sequence([b.oid(_oidName(node.children[0], "c509/bad-extensions", "a hardwareModuleName hwType").oid), b.octetString(node.children[1].content)]);
557
- } else if (intVal === -2) { // id-on-SmtpUTF8Mailbox: text -> UTF8String (RFC 9598 SIZE (1..MAX))
385
+ } else if (intVal === -2) {
558
386
  if (node.majorType !== 3) throw _err("c509/bad-extensions", "an id-on-SmtpUTF8Mailbox value must be a CBOR text string");
559
387
  var smtp = cbor.read.textString(node);
560
388
  if (smtp.length === 0) throw _err("c509/bad-extensions", "an id-on-SmtpUTF8Mailbox value must be non-empty (RFC 9598 SIZE (1..MAX))");
561
389
  typeId = oid.byName("smtpUtf8Mailbox");
562
390
  inner = b.utf8(smtp);
563
- } else { // intVal === -3, id-on-MACAddress: bytes (6 = EUI-48, 8 = EUI-64) -> OCTET STRING
391
+ } else {
564
392
  if (node.majorType !== 2) throw _err("c509/bad-extensions", "an id-on-MACAddress value must be a CBOR byte string");
565
393
  if (node.content.length !== 6 && node.content.length !== 8) throw _err("c509/bad-extensions", "an id-on-MACAddress value must be 6 (EUI-48) or 8 (EUI-64) octets");
566
394
  typeId = oid.byName("macAddress");
567
395
  inner = b.octetString(node.content);
568
396
  }
569
- return b.contextConstructed(0, Buffer.concat([b.oid(typeId), b.explicit(0, inner)])); // [0] IMPLICIT SEQUENCE
397
+ return b.contextConstructed(0, Buffer.concat([b.oid(typeId), b.explicit(0, inner)]));
570
398
  }
571
- // The inverse: the pkix.generalName otherName value { typeId, valueBytes } -> a [intCbor, valueCbor] pair,
572
- // or null when an id-on special's inner value is not its RFC-defined shape (the extension then falls back).
573
399
  function _otherNameFromDer(v) {
574
400
  var onInt = ON_INT_BY_OID[v.typeId];
575
401
  if (onInt === undefined) return [cbor.build.int(0n), cbor.build.array([_oidCbor(v.typeId), cbor.build.byteString(v.valueBytes)])];
576
402
  var inner;
577
- try { inner = asn1.decode(v.valueBytes); } catch (_e) { return null; }
578
- if (onInt === -1) { // HardwareModuleName ::= SEQUENCE { hwType OID, hwSerialNum OCTET STRING } (RFC 4108)
403
+ try { inner = asn1.decode(v.valueBytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed otherName value yields null, so the extension does not compact and is carried in C509 general form (raw bytes)
404
+ if (onInt === -1) {
579
405
  if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.SEQUENCE || !inner.children || inner.children.length !== 2) return null;
580
406
  var hwType, hwSerial;
581
- try { hwType = asn1.read.oid(inner.children[0]); hwSerial = asn1.read.octetString(inner.children[1]); } catch (_e2) { return null; }
407
+ try { hwType = asn1.read.oid(inner.children[0]); hwSerial = asn1.read.octetString(inner.children[1]); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed hardwareModuleName yields null, so the extension is carried in C509 general form instead
582
408
  return [cbor.build.int(-1n), cbor.build.array([_oidCbor(hwType), cbor.build.byteString(hwSerial)])];
583
409
  }
584
- if (onInt === -2) { // SmtpUTF8Mailbox ::= UTF8String (RFC 9598)
410
+ if (onInt === -2) {
585
411
  if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
586
- var txt; try { txt = asn1.read.string(inner); } catch (_e3) { return null; }
412
+ var txt; try { txt = asn1.read.string(inner); } catch (_e3) { return null; } // allow:swallow-unverified -- a non-UTF8String otherName yields null, so the extension is carried in C509 general form instead
587
413
  return [cbor.build.int(-2n), cbor.build.textString(txt)];
588
414
  }
589
- if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.OCTET_STRING) return null; // onInt === -3: MACAddress ::= OCTET STRING
590
- var mac; try { mac = asn1.read.octetString(inner); } catch (_e4) { return null; }
591
- // mac is a PUBLIC hardware MAC address (an EUI-48/64 identifier, RFC 9542 / lamps-macaddress), not a
592
- // MAC / HMAC / auth tag / secret; this is a length check.
593
- // nosemgrep: pki-non-constant-time-secret-compare
415
+ if (inner.tagClass !== "universal" || inner.tagNumber !== asn1.TAGS.OCTET_STRING) return null;
416
+ var mac; try { mac = asn1.read.octetString(inner); } catch (_e4) { return null; } // allow:swallow-unverified -- a malformed octet-string otherName value yields null, so the extension is carried in C509 general form instead
594
417
  if (mac.length !== 6 && mac.length !== 8) return null;
595
418
  return [cbor.build.int(-3n), cbor.build.byteString(mac)];
596
419
  }
597
420
 
598
- // directoryName [4] EXPLICIT Name (a CHOICE, hence EXPLICIT) <-> the C509 compact Name. Reuses the file's
599
- // Name codecs; a Name the compact form cannot hold (a multi-value RDN, an attribute outside sec. 8.6)
600
- // returns null so the whole extension falls back to ~oid + byte-string.
601
421
  function _dirNameToCbor(gnBytes) {
602
- var node = asn1.decode(gnBytes); // the [4]-tagged EXPLICIT wrapper
422
+ var node = asn1.decode(gnBytes);
603
423
  if (!node.children || node.children.length !== 1) return null;
604
424
  var name;
605
- try { name = _c509NameFromDer(node.children[0].bytes); } catch (_e) { return null; }
606
- try { return _encName(name, true); } catch (_e2) { return null; }
425
+ try { name = _c509NameFromDer(node.children[0].bytes); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed directoryName yields null, so the extension is carried in C509 general form instead
426
+ try { return _encName(name, true); } catch (_e2) { return null; } // allow:swallow-unverified -- a directoryName that does not re-encode yields null, so the extension is carried in C509 general form instead
607
427
  }
608
428
 
609
- // iPAddress [7] (SAN form): a bare 4-octet (IPv4) or 16-octet (IPv6) address, CBOR bytes <-> DER content.
610
429
  function _sanIpBytes(node) {
611
430
  if (node.majorType !== 2) throw _err("c509/bad-extensions", "an iPAddress value must be a CBOR byte string");
612
431
  if (node.content.length !== 4 && node.content.length !== 16) throw _err("c509/bad-extensions", "an iPAddress must be 4 (IPv4) or 16 (IPv6) octets");
613
432
  return node.content;
614
433
  }
615
- // iPAddress [7] (name-constraints subtree base): RFC 9549 sec. 2.2. CBOR is address || prefix-length (5 or
616
- // 17 octets, the last octet the prefix bit count); DER is address || mask (8 or 32 octets). CBOR -> DER
617
- // expands the prefix length to a contiguous mask.
618
434
  function _ncIpToDer(node) {
619
435
  if (node.majorType !== 2) throw _err("c509/bad-extensions", "a name-constraints iPAddress value must be a CBOR byte string");
620
436
  var buf = node.content, addrLen;
@@ -626,9 +442,6 @@ function _ncIpToDer(node) {
626
442
  for (var bit = 0; bit < prefixLen; bit++) mask[bit >> 3] |= 0x80 >> (bit & 7);
627
443
  return Buffer.concat([buf.subarray(0, addrLen), mask]);
628
444
  }
629
- // DER -> CBOR: address || mask (8/32) -> address || prefix-length (5/17). The mask MUST be a canonical
630
- // contiguous prefix mask (N leading 1-bits then all 0-bits); a non-prefix mask (host bits set,
631
- // non-contiguous) is not representable in the prefix form -> null (the extension falls back, losing no bits).
632
445
  function _ncIpFromDer(buf) {
633
446
  var addrLen = buf.length >> 1, mask = buf.subarray(addrLen), prefixLen = 0, seenZero = false;
634
447
  for (var i = 0; i < mask.length; i++) {
@@ -640,41 +453,14 @@ function _ncIpFromDer(buf) {
640
453
  return Buffer.concat([buf.subarray(0, addrLen), Buffer.from([prefixLen])]);
641
454
  }
642
455
 
643
- // ---- RFC 3779 IPAddrBlocks / ASIdentifiers (draft sec. 3.3, ext ints 32-35) -----------------
644
- //
645
- // An RFC 3779 IPAddress is a BIT STRING whose unused-bit count carries the prefix length, so the
646
- // draft maps it to the byte sequence `unusedBits || value`, which "preserves the exact information
647
- // contained in the ASN.1 BIT STRING", which stays lossless even for a prefix ending in zero bits. Per
648
- // IPAddressFamily the draft then picks one of two forms and makes the choice a SHALL: if any of the
649
- // family's byte sequences exceeds 8 octets the whole family uses the bytes form, otherwise the int
650
- // form. Accepting the wrong one would give one DER two CBOR encodings, so decode enforces it.
651
- //
652
- // The int form is the big-endian integer of `(unusedBits + 1) || value`. The +1 guarantees a
653
- // non-zero leading octet, which is what makes the minimal big-endian representation unambiguous,
654
- // and every IPAddress after the first is stored as the DIFFERENCE from its predecessor. The chain
655
- // runs flat over each address in order (a range contributes min then max) and RESETS at each
656
- // family, so a family always opens with an absolute value.
657
- //
658
- // These integers reach 2^64-1: an IPv6 /48 already exceeds 2^53, and the draft's own A.5 vector
659
- // lands there. Everything below is BigInt end to end and bounds through guard.range.uint64 (the
660
- // BigInt-preserving guard). The uint31 counter bound is never used here; it would reject a valid prefix.
661
-
662
- // An asn1 node exposes tagClass + tagNumber (never a combined `.tag`), so a universal-type test
663
- // has to check both: a context-tagged [16] must not read as a SEQUENCE.
456
+
664
457
  function _isUniversal(n, tagNumber) { return !!n && n.tagClass === "universal" && n.tagNumber === tagNumber; }
665
458
 
666
- // `unusedBits || value` -> the (unusedBits+1)||value integer. Returns null if the sequence cannot
667
- // be one (empty, or an unused-bit count DER would not accept).
668
459
  function _ipSeqToInt(seq) {
669
460
  if (!seq.length || seq[0] > 7) return null;
670
- // One-shot base-256 parse of (unusedBits+1)||value. A per-byte shift-accumulate is quadratic in
671
- // the operand width, so the toolkit builds a BigInt from its hex in a single call instead.
672
461
  var head = Buffer.from([seq[0] + 1]);
673
462
  return BigInt("0x" + Buffer.concat([head, seq.subarray(1)]).toString("hex"));
674
463
  }
675
- // The inverse: the minimal big-endian octets of `n`, with the leading octet decremented back to the
676
- // unused-bit count. Returns null when `n` cannot be an encoded IPAddress: a leading octet outside
677
- // 1..8 is not a DER unused-bit count, and past 8 octets the family was required to use the bytes form.
678
464
  function _ipIntToSeq(n) {
679
465
  if (n < 1n) return null;
680
466
  var out = [];
@@ -683,28 +469,19 @@ function _ipIntToSeq(n) {
683
469
  out[0] -= 1;
684
470
  return Buffer.from(out);
685
471
  }
686
- // The address width in octets of an address family, from its AFI (RFC 3779 sec. 2.2.3.3). Only a
687
- // family whose width is known can have its canonical form checked, so an unrecognized AFI yields
688
- // null and the caller declines to compact; it will not compact something it cannot verify.
689
472
  var _IP_WIDTH = { 1: 4, 2: 16 };
690
473
 
691
- // The lowest address an `unusedBits || value` sequence denotes, as `width` big-endian octets. DER
692
- // forces the unused bits to zero, so the value IS its own lowest address once zero-extended.
693
474
  function _ipLow(seq, width) {
694
475
  var v = Buffer.alloc(width);
695
476
  seq.subarray(1).copy(v);
696
477
  return v;
697
478
  }
698
- // The highest address it denotes: the same prefix with every host bit set (RFC 3779 sec. 2.2.3.8 --
699
- // a range's max is likewise the prefix with its trailing bits taken as ones).
700
479
  function _ipHigh(seq, width) {
701
480
  var v = _ipLow(seq, width);
702
481
  var bits = (seq.length - 1) * 8 - seq[0];
703
482
  for (var i = bits; i < width * 8; i++) v[i >> 3] |= 0x80 >> (i & 7);
704
483
  return v;
705
484
  }
706
- // Big-endian octet-string compare, and "is `b` the immediate successor of `a`": the test that
707
- // distinguishes a legal gap from a contiguous pair the RFC requires be merged.
708
485
  function _ipOctCmp(a, b) {
709
486
  for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
710
487
  return 0;
@@ -712,15 +489,10 @@ function _ipOctCmp(a, b) {
712
489
  function _ipIsSuccessor(a, b) {
713
490
  var carry = 1, inc = Buffer.from(a);
714
491
  for (var i = inc.length - 1; i >= 0 && carry; i--) { var s = inc[i] + carry; inc[i] = s & 0xff; carry = s >> 8; }
715
- if (carry) return false; // `a` was already the maximum address
492
+ if (carry) return false;
716
493
  return _ipOctCmp(inc, b) === 0;
717
494
  }
718
495
 
719
- // RFC 3779 sec. 2.2.3.7: "any range of addresses that can be encoded as a prefix MUST be encoded
720
- // using an IPAddress element", with the choice fixed by the spec's own pseudocode. Let N be the
721
- // count of matching leading bits of the low and high addresses; if every remaining bit of the low
722
- // is zero AND every remaining bit of the high is one, the span IS the N-bit prefix and the range
723
- // form is forbidden. Two encodings of one address span would otherwise both be legal.
724
496
  function _ipRangeIsPrefix(lo, hi) {
725
497
  var bits = lo.length * 8, n = 0;
726
498
  while (n < bits) {
@@ -730,29 +502,18 @@ function _ipRangeIsPrefix(lo, hi) {
730
502
  }
731
503
  for (var i = n; i < bits; i++) {
732
504
  var bt = i >> 3, mk = 0x80 >> (i & 7);
733
- if ((lo[bt] & mk) !== 0) return false; // a low-address host bit is set
734
- if ((hi[bt] & mk) === 0) return false; // a high-address host bit is clear
505
+ if ((lo[bt] & mk) !== 0) return false;
506
+ if ((hi[bt] & mk) === 0) return false;
735
507
  }
736
508
  return true;
737
509
  }
738
510
 
739
- // RFC 3779 sec. 2.2.3.3 orders the families themselves: "There MUST be only one IPAddressFamily
740
- // SEQUENCE per unique combination of AFI and SAFI. Each SEQUENCE MUST be ordered by ascending
741
- // addressFamily values (treating the octets as unsigned quantities). An addressFamily without a
742
- // SAFI MUST precede one that contains an SAFI." A plain unsigned octet-string compare gives all
743
- // three at once, because a two-octet family is a PREFIX of the three-octet one sharing its AFI and
744
- // a prefix sorts first. Returns < 0, 0 or > 0; 0 means the same AFI/SAFI appeared twice.
745
511
  function _famOctCmp(a, b) {
746
512
  var n = Math.min(a.length, b.length);
747
513
  for (var i = 0; i < n; i++) { if (a[i] !== b[i]) return a[i] - b[i]; }
748
514
  return a.length - b.length;
749
515
  }
750
516
 
751
- // Is `unusedBits || value` a sequence DER would accept as a BIT STRING? The declared unused low
752
- // bits MUST be zero (RFC 3779 sec. 2.2.3.8 restates the DER rule), and a value with no octets can
753
- // only declare zero unused bits. The check lives here, not in the BIT STRING builder: the builder's
754
- // fault is an asn1/* error surfacing out of a CBOR-layer decode, and a caller handed a malformed
755
- // compact value should see this module's own verdict, not the ASN.1 layer's.
756
517
  function _ipSeqBitsClear(seq) {
757
518
  var unused = seq[0], value = seq.subarray(1);
758
519
  if (!value.length) return unused === 0;
@@ -760,50 +521,23 @@ function _ipSeqBitsClear(seq) {
760
521
  return (value[value.length - 1] & ((1 << unused) - 1)) === 0;
761
522
  }
762
523
 
763
- // RFC 3779 sec. 2.2.3.6 fixes the canonical form of an address list: entries sorted on
764
- // `<lowest address> | <prefix length>` (which is neither the DER byte order nor the compact integer
765
- // order; the RFC warns about the first and the second sorts the same wrong way), no pair
766
- // overlapping, and any contiguous pair combined into one entry. All three bind together: a list
767
- // violating any of them is not the one canonical encoding of its address set, so this codec
768
- // declines to compact it and the extension keeps its original bytes.
769
- // Two comparisons carry all three rules. `cur.lo > prev.hi` is the no-overlap rule AND the sort
770
- // rule at once: entries are already known to have lo <= hi, so an entry that started at or before
771
- // its predecessor's low address would also start at or before its high one. A separate ascending
772
- // test would therefore be a branch nothing can reach.
773
524
  function _ipRangesCanonical(bounds) {
774
525
  for (var i = 1; i < bounds.length; i++) {
775
526
  var prev = bounds[i - 1], cur = bounds[i];
776
- if (_ipOctCmp(cur.lo, prev.hi) <= 0) return false; // out of order, or overlapping
777
- if (_ipIsSuccessor(prev.hi, cur.lo)) return false; // contiguous: MUST have been merged
527
+ if (_ipOctCmp(cur.lo, prev.hi) <= 0) return false;
528
+ if (_ipIsSuccessor(prev.hi, cur.lo)) return false;
778
529
  }
779
530
  return true;
780
531
  }
781
532
 
782
- // One family's IntIPAddressChoice / IPAddressChoice -> the DER IPAddressOrRange SEQUENCE list.
783
- // The int form stores each address after the first as a DIFFERENCE from its predecessor, flat over
784
- // every address in order (a range contributes min then max); the chain resets at each family, which
785
- // is why `prev` starts null here and is never threaded across families. The reconstructed
786
- // ABSOLUTE is what gets bounded: the delta itself is an arbitrary CBOR int, and bounding it would
787
- // miss a chain that walks out of range in steps.
788
533
  function _ipChoiceToDer(items, afi) {
789
534
  var out = [], prev = null, sawBytes = false, sawInt = false, seqs = [];
790
- // An address may not be wider than its family: RFC 3779 sec. 2.2.3.8 sizes an IPAddress by the
791
- // family, so a 5-octet address under AFI 1 is not an IPv4 address at all. Without this, a native
792
- // C509 this codec had accepted would reconstruct into a DER carrying an over-wide address that the
793
- // RFC forbids and an independent validator refuses. The mirror check lives on the encode side;
794
- // both directions have to hold or the pair is not a bijection.
795
- // Without the family's address width none of the RFC 3779 rules below can be evaluated: the
796
- // width bound has nothing to compare against, and the low/high bounds that drive the order,
797
- // overlap, adjacency and endpoint checks cannot be computed at all. Accepting such a family
798
- // would therefore wave every one of those checks through. A family this codec cannot
799
- // measure is refused outright, matching the encode side, which declines to compact it. An
800
- // `inherit` family is unaffected: it carries no addresses and never reaches here.
801
535
  var width = _IP_WIDTH[afi];
802
536
  if (!width) throw _err("c509/bad-extensions", "address family " + afi + " has no known address width, so its addresses cannot be checked (RFC 3779 sec. 2.2.3.3)");
803
537
  function widthOk(seq) { return seq.length - 1 <= width; }
804
538
  function absolute(node) {
805
539
  var seq;
806
- if (node.majorType === 2) { // bytes form: the sequence verbatim
540
+ if (node.majorType === 2) {
807
541
  sawBytes = true;
808
542
  if (node.content.length === 0) throw _err("c509/bad-extensions", "an IPAddress byte sequence must be non-empty");
809
543
  if (node.content[0] > 7) throw _err("c509/bad-extensions", "an IPAddress unused-bit count must be 0..7 (DER)");
@@ -813,8 +547,6 @@ function _ipChoiceToDer(items, afi) {
813
547
  var d = _cborIntVal(node, "an IPAddress");
814
548
  var abs = prev === null ? d : prev + d;
815
549
  prev = abs;
816
- // Bound the reconstructed absolute through the BigInt-preserving guard: this domain reaches
817
- // 2^64-1, so narrowing to Number would corrupt the value being guarded.
818
550
  guard.range.uint64(abs, _err, "c509/bad-extensions", "an IPAddress");
819
551
  seq = _ipIntToSeq(abs);
820
552
  if (!seq) throw _err("c509/bad-extensions", "an IPAddress integer does not encode a DER BIT STRING (sec. 3.3)");
@@ -830,37 +562,27 @@ function _ipChoiceToDer(items, afi) {
830
562
  var bounds = [];
831
563
  for (var i = 0; i < items.length; i++) {
832
564
  var it = items[i];
833
- if (it.majorType === 4) { // [min, max] -> an addressRange
565
+ if (it.majorType === 4) {
834
566
  if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an IPAddress range must be exactly [min, max]");
835
567
  var lo = absolute(it.children[0]), hi = absolute(it.children[1]);
836
568
  seqs.push(lo); seqs.push(hi);
837
569
  var rlo = _ipLow(lo, width), rhi = _ipHigh(hi, width);
838
570
  {
839
- // A span expressible as a prefix MUST use the prefix form (RFC 3779 sec. 2.2.3.7), or one
840
- // address span would have two legal encodings.
841
571
  if (_ipRangeIsPrefix(rlo, rhi)) throw _err("c509/bad-extensions", "an address range that is exactly a prefix must use the prefix form (RFC 3779 sec. 2.2.3.7)");
842
572
  bounds.push({ lo: rlo, hi: rhi });
843
573
  }
844
574
  out.push(b.sequence([b.bitString(lo.subarray(1), lo[0]), b.bitString(hi.subarray(1), hi[0])]));
845
- } else { // a single addressPrefix
575
+ } else {
846
576
  var pfx = absolute(it);
847
577
  seqs.push(pfx);
848
578
  bounds.push({ lo: _ipLow(pfx, width), hi: _ipHigh(pfx, width) });
849
579
  out.push(b.bitString(pfx.subarray(1), pfx[0]));
850
580
  }
851
581
  }
852
- // The form choice is a SHALL, so the wrong one would give one DER two CBOR encodings. A family
853
- // mixing the two arms is not a choice either admits; a bytes-form family whose every sequence
854
- // fits 8 octets was required to use the int form.
855
582
  if (sawBytes && sawInt) throw _err("c509/bad-extensions", "an IPAddressFamily must use one address form throughout (sec. 3.3)");
856
583
  if (sawBytes && !seqs.some(function (s) { return s.length > 8; })) {
857
584
  throw _err("c509/bad-extensions", "an IPAddressFamily whose addresses all fit 8 octets must use the integer form (sec. 3.3)");
858
585
  }
859
- // The same RFC 3779 sec. 2.2.3.6 canonical form the encode side requires, enforced here too.
860
- // draft sec. 3.3 says "The limitations specified in [RFC3779] apply here as well", so a compact
861
- // list that is unsorted, overlapping, or unmerged is malformed C509, and reconstructing it
862
- // would emit a certificate an independent validator refuses, from CBOR this codec had accepted.
863
- // Both directions must hold or the pair is not a bijection.
864
586
  for (var r = 0; r < bounds.length; r++) {
865
587
  if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) throw _err("c509/bad-extensions", "an IPAddress range must not end below its start (RFC 3779 sec. 2.2.3.9)");
866
588
  }
@@ -870,13 +592,8 @@ function _ipChoiceToDer(items, afi) {
870
592
  return out;
871
593
  }
872
594
 
873
- // DER IPAddressChoice (a SEQUENCE of BIT STRING / SEQUENCE-of-two-BIT-STRING) -> the compact CBOR
874
- // array. The form is chosen per family and is a SHALL: the bytes form applies to the whole family
875
- // as soon as any one sequence exceeds 8 octets, otherwise every member takes the delta-coded int
876
- // form. Returns null on any shape the compact form cannot carry exactly.
877
595
  function _ipChoiceFromDer(ch, afi) {
878
596
  if (!_isUniversal(ch, asn1.TAGS.SEQUENCE) || !ch.children || ch.children.length === 0) return null;
879
- // Collect each address as its `unusedBits || value` sequence, keeping the range grouping.
880
597
  var groups = [], flat = [];
881
598
  for (var i = 0; i < ch.children.length; i++) {
882
599
  var el = ch.children[i], pair;
@@ -892,32 +609,16 @@ function _ipChoiceFromDer(ch, afi) {
892
609
  groups.push(pair);
893
610
  for (var k = 0; k < pair.length; k++) flat.push(pair[k]);
894
611
  }
895
- // The list must be in RFC 3779 sec. 2.2.3.6 canonical form: sorted, non-overlapping, and with
896
- // every contiguous pair already merged. A list that is not is not the one canonical encoding of
897
- // its address set, so it is never re-encoded into a conforming one. This returns null and the
898
- // extension rides the byte-string form, keeping its exact bytes and leaving the defect visible to
899
- // a validator. Checking overlap and adjacency needs the family's address width, so a family whose
900
- // AFI this codec does not know declines too, since there is no way to verify what it would compact.
901
- // Defense-in-depth, and deliberately explicit: without the width the bound arithmetic below would
902
- // fault and the caller's catch would produce the same fallback, so this line changes no observable
903
- // verdict and no vector can isolate it. It stays because a fallback that depends on an exception
904
- // being raised somewhere downstream is one refactor away from becoming an accepted value.
905
612
  var width = _IP_WIDTH[afi];
906
613
  if (!width) return null;
907
614
  var bounds = [];
908
615
  for (var g = 0; g < groups.length; g++) {
909
616
  var bg = groups[g];
910
- if (bg[0].length - 1 > width || bg[bg.length - 1].length - 1 > width) return null; // longer than the family's addresses
617
+ if (bg[0].length - 1 > width || bg[bg.length - 1].length - 1 > width) return null;
911
618
  var blo = _ipLow(bg[0], width), bhi = _ipHigh(bg[bg.length - 1], width);
912
- // The mirror of the decode-side rule: a range that is exactly a prefix had to be written as one.
913
- // No vector can isolate this line today, because the encode path's round-trip self-verify
914
- // re-parses what it produced and the decode rule rejects it there, yielding the same fallback.
915
- // It stays explicit because that makes encode's correctness depend on decode continuing to
916
- // throw: soften the decode rule and this path would silently start compacting again.
917
619
  if (bg.length === 2 && _ipRangeIsPrefix(blo, bhi)) return null;
918
620
  bounds.push({ lo: blo, hi: bhi });
919
621
  }
920
- // A range's own endpoints must ascend too, which a single-entry list has no chance to violate.
921
622
  for (var r = 0; r < bounds.length; r++) {
922
623
  if (_ipOctCmp(bounds[r].lo, bounds[r].hi) > 0) return null;
923
624
  }
@@ -955,20 +656,13 @@ function _ipChoiceFromDer(ch, afi) {
955
656
  return cbor.build.array(out);
956
657
  }
957
658
 
958
- // One ASId in the delta chain -> its absolute value, bounded to the RFC 3779 32-bit ASId domain.
959
- // Safe to narrow here (2^32-1 < 2^53), unlike the IP domain.
960
659
  function _asDelta(node, prev) {
961
660
  var d = _cborIntVal(node, "an ASIdentifier");
962
661
  var abs = prev === null ? d : prev + d;
963
- // Bound only -- the narrowed Number the guard returns is deliberately discarded, because the
964
- // chain's next step adds a BigInt delta to this value and mixing the two throws.
965
662
  guard.range.int(abs, 0n, 4294967295n, _err, "c509/bad-extensions", "an ASIdentifier");
966
663
  return abs;
967
664
  }
968
665
 
969
- // GeneralSubtrees = [ + GeneralName ] (the flat int/value array) <-> the concatenated GeneralSubtree
970
- // SEQUENCEs (RFC 5280 sec. 4.2.1.10: SEQUENCE { base, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }); the
971
- // C509 profile omits minimum/maximum, so each GeneralSubtree is base-only.
972
666
  function _subtreesToDer(node, isNative) {
973
667
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a GeneralSubtrees value must be a CBOR array");
974
668
  var kids = node.children;
@@ -982,7 +676,7 @@ function _subtreesFromDer(subtreeNodes) {
982
676
  var out = [];
983
677
  for (var i = 0; i < subtreeNodes.length; i++) {
984
678
  var st = subtreeNodes[i];
985
- if (st.tagClass !== "universal" || st.tagNumber !== asn1.TAGS.SEQUENCE || !st.children || st.children.length !== 1) return null; // base only (minimum/maximum omitted)
679
+ if (st.tagClass !== "universal" || st.tagNumber !== asn1.TAGS.SEQUENCE || !st.children || st.children.length !== 1) return null;
986
680
  var pair = _generalNameFromDer(schema.walk(GN_LEAF_SUBTREE, st.children[0], NS), true);
987
681
  if (pair == null) return null;
988
682
  out.push(pair[0]); out.push(pair[1]);
@@ -990,7 +684,6 @@ function _subtreesFromDer(subtreeNodes) {
990
684
  return out;
991
685
  }
992
686
 
993
- // A CBOR accessMethod (int, sec. 8.11 registry, or ~oid) -> the dotted access-method OID.
994
687
  function _accessMethodOid(node) {
995
688
  if (node.majorType === 0 || node.majorType === 1) {
996
689
  var i = Number(cbor.read.int(node)), nm = IA_BY_INT[i];
@@ -999,37 +692,24 @@ function _accessMethodOid(node) {
999
692
  }
1000
693
  return _oidName(node, "c509/bad-extensions", "an accessMethod").oid;
1001
694
  }
1002
- // A reasons uint (bit i -> named ReasonFlags bit i) -> the [1] IMPLICIT ReasonFlags BIT STRING TLV.
1003
695
  function _reasonsBitsToDer(value) {
1004
- // ReasonFlags bit 0 is reserved "unused" (RFC 5280 sec. 4.2.1.13; the reason bits are 1..8), so a value
1005
- // that sets it is not a valid ReasonFlags even though the shared 9-bit range check would accept it. Bit 0
1006
- // is a legitimate keyUsage bit (digitalSignature), so this reason-specific reject lives here, not in the
1007
- // shared named-bit helper. A malformed native C509 reasons value fails closed; it never reconstructs
1008
- // into a semantically invalid X.509 ReasonFlags.
1009
696
  if ((value & 1) !== 0) throw _err("c509/bad-extensions", "a cRLDistributionPoints reasons value must not set the reserved unused ReasonFlags bit 0 (RFC 5280 sec. 4.2.1.13)");
1010
697
  var c = _namedBitsToContent(value);
1011
698
  if (c == null) throw _err("c509/bad-extensions", "a cRLDistributionPoints reasons value must be within the 9 defined ReasonFlags bits");
1012
699
  return b.contextPrimitive(1, Buffer.concat([Buffer.from([c.unusedBits]), c.bytes]));
1013
700
  }
1014
- // A CBOR ~biguint serial node -> the DER INTEGER content octets (minimal two's-complement of the
1015
- // non-negative serial): the bare magnitude, prefixed with a 0x00 sign octet when its top bit is set.
1016
701
  function _serialIntContent(node) {
1017
702
  var mag = _minBytes(_biguint(node, "c509/bad-extensions", "an authorityKeyIdentifier serial"));
1018
- if (mag.length === 0) return Buffer.from([0x00]); // serial 0
703
+ if (mag.length === 0) return Buffer.from([0x00]);
1019
704
  return (mag[0] & 0x80) ? Buffer.concat([Buffer.from([0x00]), mag]) : mag;
1020
705
  }
1021
706
 
1022
- // ---- certificatePolicies (draft-20 sec. 3.3 / sec. 8.9 / sec. 8.10; RFC 5280 sec. 4.2.1.4) ----
1023
- // A CBOR text -> a UNIVERSAL IA5String TLV, running the same 7-bit + CVE-2009-2408 control-byte guard the
1024
- // general-name IA5 arms use (never the bare b.ia5, which permits control bytes). Non-empty (the general-name
1025
- // IA5 arms reject empty too; this is a CPSuri parity tightening, since RFC 5280 sets no SIZE floor on CPSuri).
1026
707
  function _ia5Universal(text, label) {
1027
708
  if (text.length === 0) throw _err("c509/bad-extensions", label + " must be non-empty");
1028
709
  for (var i = 0; i < text.length; i++) { if (text.charCodeAt(i) > 0x7f) throw _err("c509/bad-extensions", label + " must be a 7-bit IA5String"); }
1029
710
  guard.name.assertPrintableIa5(Buffer.from(text, "latin1"), _err, "c509/bad-extensions", label);
1030
711
  return b.ia5(text);
1031
712
  }
1032
- // A CBOR policyIdentifier (a sec. 8.9 registry int, or an unwrapped ~oid) -> the dotted policy OID.
1033
713
  function _policyIdToDerOid(node) {
1034
714
  if (node.majorType === 0 || node.majorType === 1) {
1035
715
  var i = Number(cbor.read.int(node)), nm = CP_BY_INT[i];
@@ -1038,54 +718,36 @@ function _policyIdToDerOid(node) {
1038
718
  }
1039
719
  return _oidName(node, "c509/bad-extensions", "a certificatePolicies policyIdentifier").oid;
1040
720
  }
1041
- // The inverse: a dotted policy OID -> CBOR (a sec. 8.9 int if registered, else an unwrapped ~oid).
1042
721
  function _policyIdFromDer(dotted) {
1043
722
  var i = CP_TO_INT[dotted];
1044
723
  return i !== undefined ? cbor.build.int(BigInt(i)) : _oidCbor(dotted);
1045
724
  }
1046
- // (qualifierId CBOR, qualifier-text CBOR) -> one DER PolicyQualifierInfo SEQUENCE { policyQualifierId OID,
1047
- // qualifier ANY DEFINED BY }. The qualifierId resolves to a sec. 8.10 int and nothing else: a ~oid / unregistered
1048
- // qualifierId has no defined qualifier:text semantics, so it is not compact-representable (fail closed, never
1049
- // guess the ANY type). id-qt-cps -> CPSuri IA5String; id-qt-unotice -> UserNotice { explicitText utf8String }.
1050
725
  function _qualifierToDer(qidNode, qtextNode) {
1051
726
  if (qidNode.majorType !== 0 && qidNode.majorType !== 1) throw _err("c509/bad-extensions", "a policyQualifierId must be a C509 sec. 8.10 int (a ~oid qualifier is not compact-representable)");
1052
727
  var qi = Number(cbor.read.int(qidNode)), nm = PQ_BY_INT[qi];
1053
728
  if (nm === undefined) throw _err("c509/bad-extensions", "a policyQualifierId int " + qi + " has no C509 sec. 8.10 registry row");
1054
729
  if (qtextNode.majorType !== 3) throw _err("c509/bad-extensions", "a policyQualifier value must be a CBOR text string");
1055
730
  var text = cbor.read.textString(qtextNode);
1056
- if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]); // CPSuri ::= IA5String
1057
- // id-qt-unotice: UserNotice ::= SEQUENCE { explicitText utf8String }, noticeRef omitted. Only the SIZE
1058
- // (1..200) FLOOR is enforced: an empty explicitText is a degenerate value no encoder can have produced,
1059
- // while RFC 5280 sec. 4.2.1.4 directs certificate users to gracefully handle a notice ABOVE 200
1060
- // characters, and draft-20 sec. 3.3's compact predicate is SIZE-silent. An over-long notice
1061
- // therefore transcodes and is not refused.
731
+ if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]);
1062
732
  if (text.length === 0) throw _err("c509/bad-extensions", "a UserNotice explicitText must be non-empty (DisplayText SIZE 1..200)");
1063
733
  return b.sequence([b.oid(oid.byName("unotice")), b.sequence([b.utf8(text)])]);
1064
734
  }
1065
- // One DER PolicyQualifierInfo node -> [ qidCbor, qtextCbor ], or null when not compact-representable: an
1066
- // unregistered qualifierId, a cps qualifier whose value is not a plain IA5String, or a UserNotice that is
1067
- // anything but SEQUENCE { explicitText utf8String } (a noticeRef, a non-UTF8String DisplayText arm, or more
1068
- // than the one explicitText child) -> whole-extension ~oid fallback.
1069
735
  function _qualifierFromDer(pq) {
1070
736
  var qid = asn1.read.oid(pq.children[0]), qi = PQ_TO_INT[qid];
1071
737
  if (qi === undefined) return null;
1072
738
  var q = pq.children[1];
1073
- if (qi === 1) { // CPSuri ::= IA5String
739
+ if (qi === 1) {
1074
740
  if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.IA5_STRING) return null;
1075
- var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; }
741
+ var uri; try { uri = asn1.read.string(q); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed URI policy qualifier yields null, so the extension is carried in C509 general form instead
1076
742
  return [cbor.build.int(1n), cbor.build.textString(uri)];
1077
743
  }
1078
- // id-qt-unotice: SEQUENCE { explicitText utf8String } and nothing more (noticeRef absent).
1079
744
  if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.SEQUENCE || !q.children || q.children.length !== 1) return null;
1080
745
  var dt = q.children[0];
1081
746
  if (dt.tagClass !== "universal" || dt.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
1082
- var txt; try { txt = asn1.read.string(dt); } catch (_e2) { return null; }
747
+ var txt; try { txt = asn1.read.string(dt); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed displayText qualifier yields null, so the extension is carried in C509 general form instead
1083
748
  return [cbor.build.int(2n), cbor.build.textString(txt)];
1084
749
  }
1085
750
 
1086
- // One CBOR DistributionPointName [ fullName, reasons, cRLIssuer ] -> one DER DistributionPoint SEQUENCE.
1087
- // distributionPoint [0] is EXPLICIT (it wraps the DistributionPointName CHOICE); fullName [0], reasons [1],
1088
- // cRLIssuer [2] are IMPLICIT (RFC 5280 sec. 4.2.1.13); mixing these is the classic byte-exactness trap.
1089
751
  function _dpToDer(dpNode, isNative) {
1090
752
  if (dpNode.majorType !== 4 || !dpNode.children || dpNode.children.length !== 3) throw _err("c509/bad-extensions", "a DistributionPoint must be a CBOR [ fullName, reasons, cRLIssuer ] array");
1091
753
  var fullName = dpNode.children[0], reasons = dpNode.children[1], crlIssuer = dpNode.children[2];
@@ -1095,45 +757,41 @@ function _dpToDer(dpNode, isNative) {
1095
757
  if (fullName.children.length < 2) throw _err("c509/bad-extensions", "a DistributionPoint fullName array must hold 2 or more URIs (a single URI is a bare text; sec. 3.3)");
1096
758
  uris = fullName.children.map(function (u) { return b.contextPrimitive(6, _ia5Bytes(u, 6)); });
1097
759
  } else throw _err("c509/bad-extensions", "a DistributionPoint fullName must be a URI text or an array of URIs");
1098
- var fields = [b.explicit(0, b.contextConstructed(0, Buffer.concat(uris)))]; // distributionPoint [0] EXPLICIT { fullName [0] IMPLICIT GeneralNames }
760
+ var fields = [b.explicit(0, b.contextConstructed(0, Buffer.concat(uris)))];
1099
761
  if (!_isCborNull(reasons)) fields.push(_reasonsBitsToDer(Number(_cborUint(reasons, "cRLDistributionPoints reasons"))));
1100
- if (!_isCborNull(crlIssuer)) fields.push(b.contextConstructed(2, b.explicit(4, _reconName(_name509(crlIssuer, true, isNative))))); // cRLIssuer [2] { [4] directoryName }
762
+ if (!_isCborNull(crlIssuer)) fields.push(b.contextConstructed(2, b.explicit(4, _reconName(_name509(crlIssuer, true, isNative)))));
1101
763
  return b.sequence(fields);
1102
764
  }
1103
- // One DER DistributionPoint SEQUENCE -> the pieces for the CBOR DistributionPointName, or null when the DP
1104
- // is not compact-representable (no fullName, a non-URI fullName member, a nameRelativeToCRLIssuer, or a
1105
- // cRLIssuer that is not a single directoryName). oneUri is set when fullName is exactly one URI, so the
1106
- // whole-extension bare-text shortcut can fire.
1107
765
  function _dpFromDer(dp) {
1108
766
  if (dp.tagClass !== "universal" || dp.tagNumber !== asn1.TAGS.SEQUENCE || !dp.children) return null;
1109
767
  var fullNameUris = null, reasonsVal = null, crlIssuerCbor = null, lastTag = -1;
1110
768
  for (var i = 0; i < dp.children.length; i++) {
1111
769
  var f = dp.children[i];
1112
- if (f.tagClass !== "context" || f.tagNumber <= lastTag) return null; // DER: unique + ascending
770
+ if (f.tagClass !== "context" || f.tagNumber <= lastTag) return null;
1113
771
  lastTag = f.tagNumber;
1114
772
  if (f.tagNumber === 0) {
1115
773
  if (!f.children || f.children.length !== 1) return null;
1116
774
  var dpn = f.children[0];
1117
- if (dpn.tagClass !== "context" || dpn.tagNumber !== 0 || !dpn.children || !dpn.children.length) return null; // fullName [0] only
775
+ if (dpn.tagClass !== "context" || dpn.tagNumber !== 0 || !dpn.children || !dpn.children.length) return null;
1118
776
  fullNameUris = [];
1119
777
  for (var j = 0; j < dpn.children.length; j++) {
1120
778
  var mm = dpn.children[j];
1121
- if (mm.tagClass !== "context" || mm.tagNumber !== 6) return null; // non-URI fullName member
779
+ if (mm.tagClass !== "context" || mm.tagNumber !== 6) return null;
1122
780
  fullNameUris.push(schema.walk(GN_LEAF, mm, NS).value);
1123
781
  }
1124
782
  } else if (f.tagNumber === 1) {
1125
783
  var bs;
1126
- try { bs = asn1.read.bitStringImplicit(f, 1); } catch (_e) { return null; }
784
+ try { bs = asn1.read.bitStringImplicit(f, 1); } catch (_e) { return null; } // allow:swallow-unverified -- a malformed reason-flags bit string yields null, so the distributionPoint does not compact and is carried in C509 general form
1127
785
  reasonsVal = _namedBitsFromContent(bs.bytes, bs.unusedBits);
1128
786
  if (reasonsVal == null) return null;
1129
787
  } else if (f.tagNumber === 2) {
1130
788
  if (!f.children || f.children.length !== 1) return null;
1131
789
  var ci = f.children[0];
1132
- if (ci.tagClass !== "context" || ci.tagNumber !== 4 || !ci.children || ci.children.length !== 1) return null; // one directoryName
1133
- try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes), true); } catch (_e2) { return null; }
790
+ if (ci.tagClass !== "context" || ci.tagNumber !== 4 || !ci.children || ci.children.length !== 1) return null;
791
+ try { crlIssuerCbor = _encName(_c509NameFromDer(ci.children[0].bytes), true); } catch (_e2) { return null; } // allow:swallow-unverified -- a malformed CRL-issuer name yields null, so the distributionPoint is carried in C509 general form instead
1134
792
  } else return null;
1135
793
  }
1136
- if (fullNameUris == null) return null; // C509 requires the fullName distributionPoint
794
+ if (fullNameUris == null) return null;
1137
795
  var oneUri = fullNameUris.length === 1 ? fullNameUris[0] : null;
1138
796
  var fnCbor = oneUri != null ? cbor.build.textString(oneUri) : cbor.build.array(fullNameUris.map(function (u) { return cbor.build.textString(u); }));
1139
797
  return {
@@ -1142,34 +800,32 @@ function _dpFromDer(dp) {
1142
800
  };
1143
801
  }
1144
802
 
1145
- // Decode a compact extension value (a decoded CBOR node) to the DER extnValue inner content. Fails closed
1146
- // (c509/bad-extensions) on a CBOR shape the named extension does not define.
1147
803
  function _extValueToDer(name, node, isNative) {
1148
804
  switch (name) {
1149
- case "subjectKeyIdentifier": // KeyIdentifier = bytes -> OCTET STRING(keyid)
805
+ case "subjectKeyIdentifier":
1150
806
  if (node.majorType !== 2) throw _err("c509/bad-extensions", "a subjectKeyIdentifier value must be a CBOR byte string");
1151
807
  return b.octetString(node.content);
1152
- case "keyUsage": // uint -> KeyUsage BIT STRING (a value past the 9 named bits fails closed)
808
+ case "keyUsage":
1153
809
  return _reconKeyUsageBits(Number(_cborUint(node, "keyUsage")));
1154
- case "basicConstraints": { // int -> SEQUENCE { cA?, pathLen? }
810
+ case "basicConstraints": {
1155
811
  var iv = _cborIntVal(node, "basicConstraints");
1156
- if (iv === -2n) return b.sequence([]); // cA false (omitted)
1157
- if (iv === -1n) return b.sequence([b.boolean(true)]); // cA true, no pathLen
1158
- if (iv >= 0n) return b.sequence([b.boolean(true), b.integer(_boundCount(iv, "a basicConstraints pathLenConstraint"))]); // cA true, pathLen (0..2^31-1, as the DER decoder bounds it)
812
+ if (iv === -2n) return b.sequence([]);
813
+ if (iv === -1n) return b.sequence([b.boolean(true)]);
814
+ if (iv >= 0n) return b.sequence([b.boolean(true), b.integer(_boundCount(iv, "a basicConstraints pathLenConstraint"))]);
1159
815
  throw _err("c509/bad-extensions", "a basicConstraints int " + iv + " is outside the -2/-1/pathLen range");
1160
816
  }
1161
- case "authorityKeyIdentifier": // keyId-only bytes, or [ keyId, authorityCertIssuer, serial ] (sec. 3.3)
817
+ case "authorityKeyIdentifier":
1162
818
  if (node.majorType === 2) return b.sequence([b.contextPrimitive(0, node.content)]);
1163
819
  if (node.majorType === 4 && node.children && node.children.length === 3) {
1164
820
  if (node.children[0].majorType !== 2) throw _err("c509/bad-extensions", "an authorityKeyIdentifier keyIdentifier must be a CBOR byte string");
1165
821
  return b.sequence([
1166
- b.contextPrimitive(0, node.children[0].content), // keyIdentifier [0] IMPLICIT OCTET STRING
1167
- b.contextConstructed(1, Buffer.concat(_generalNamesToDer(node.children[1], isNative))), // authorityCertIssuer [1] IMPLICIT GeneralNames
1168
- b.contextPrimitive(2, _serialIntContent(node.children[2])), // authorityCertSerialNumber [2] IMPLICIT INTEGER
822
+ b.contextPrimitive(0, node.children[0].content),
823
+ b.contextConstructed(1, Buffer.concat(_generalNamesToDer(node.children[1], isNative))),
824
+ b.contextPrimitive(2, _serialIntContent(node.children[2])),
1169
825
  ]);
1170
826
  }
1171
827
  throw _err("c509/bad-extensions", "an authorityKeyIdentifier value must be a keyId byte string or the [ keyId, authorityCertIssuer, serial ] array (sec. 3.3)");
1172
- case "extKeyUsage": { // [2* KeyPurposeId] / KeyPurposeId -> SEQUENCE OF OID (draft-20 sec. 3.3)
828
+ case "extKeyUsage": {
1173
829
  var items;
1174
830
  if (node.majorType === 4) {
1175
831
  items = node.children || [];
@@ -1179,37 +835,34 @@ function _extValueToDer(name, node, isNative) {
1179
835
  }
1180
836
  return b.sequence(items.map(function (it) { return b.oid(_ekuPurposeOid(it)); }));
1181
837
  }
1182
- case "inhibitAnyPolicy": // uint -> INTEGER SkipCerts (0..2^31-1, as the DER decoder bounds it)
838
+ case "inhibitAnyPolicy":
1183
839
  return b.integer(_boundCount(_cborUint(node, "inhibitAnyPolicy"), "an inhibitAnyPolicy SkipCerts"));
1184
- case "ocspNoCheck": // null -> NULL
840
+ case "ocspNoCheck":
1185
841
  if (node.majorType !== 7 || !(Buffer.isBuffer(node.bytes) && node.bytes.length === 1 && node.bytes[0] === 0xf6)) throw _err("c509/bad-extensions", "an ocspNoCheck value must be the CBOR simple value null");
1186
842
  return b.nullValue();
1187
- case "tlsFeature": { // [uint ...] -> SEQUENCE OF INTEGER
843
+ case "tlsFeature": {
1188
844
  if (node.majorType !== 4) throw _err("c509/bad-extensions", "a tlsFeature value must be a CBOR array");
1189
845
  return b.sequence((node.children || []).map(function (f) { return b.integer(_cborUint(f, "tlsFeature feature")); }));
1190
846
  }
1191
847
  case "subjectAltName":
1192
- case "issuerAltName": // SubjectAltName = GeneralNames / text (exactly one dNSName -> bare text)
848
+ case "issuerAltName":
1193
849
  if (node.majorType === 3) return b.sequence([b.contextPrimitive(2, _ia5Bytes(node, 2))]);
1194
- // sec. 3.3: "If subjectAltName contains exactly one dNSName, the array and the int are omitted and
1195
- // extensionValue is the dNSName encoded as a CBOR text string." The array spelling of that one case
1196
- // is a second encoding of the same extension value.
1197
850
  if (!isNative && node.majorType === 4 && node.children && node.children.length === 2 &&
1198
851
  node.children[0].majorType === 0 && Number(cbor.read.int(node.children[0])) === 2 &&
1199
852
  node.children[1].majorType === 3) {
1200
853
  throw _err("c509/bad-extensions", "a " + name + " holding exactly one dNSName must be encoded as a bare CBOR text string, not an array (draft sec. 3.3)");
1201
854
  }
1202
855
  return b.sequence(_generalNamesToDer(node, isNative));
1203
- case "nameConstraints": { // [ permittedSubtrees / null, excludedSubtrees / null ]
856
+ case "nameConstraints": {
1204
857
  if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a nameConstraints value must be a 2-element CBOR array [ permitted, excluded ] (sec. 3.3)");
1205
858
  var ncFields = [];
1206
- if (!_isCborNull(node.children[0])) ncFields.push(b.contextConstructed(0, _subtreesToDer(node.children[0], isNative))); // permittedSubtrees [0]
1207
- if (!_isCborNull(node.children[1])) ncFields.push(b.contextConstructed(1, _subtreesToDer(node.children[1], isNative))); // excludedSubtrees [1]
859
+ if (!_isCborNull(node.children[0])) ncFields.push(b.contextConstructed(0, _subtreesToDer(node.children[0], isNative)));
860
+ if (!_isCborNull(node.children[1])) ncFields.push(b.contextConstructed(1, _subtreesToDer(node.children[1], isNative)));
1208
861
  if (ncFields.length === 0) throw _err("c509/bad-extensions", "nameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 sec. 4.2.1.10)");
1209
862
  return b.sequence(ncFields);
1210
863
  }
1211
864
  case "authorityInfoAccess":
1212
- case "subjectInfoAccess": { // [ accessMethod, uri, ... ] -> SEQUENCE OF AccessDescription { OID, [6] URI }
865
+ case "subjectInfoAccess": {
1213
866
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an " + name + " value must be a CBOR array");
1214
867
  var aiaKids = node.children;
1215
868
  if (aiaKids.length === 0 || aiaKids.length % 2 !== 0) throw _err("c509/bad-extensions", "an " + name + " array must be non-empty (accessMethod, uri) pairs (sec. 3.3)");
@@ -1218,12 +871,10 @@ function _extValueToDer(name, node, isNative) {
1218
871
  return b.sequence(descs);
1219
872
  }
1220
873
  case "cRLDistributionPoints":
1221
- case "freshestCRL": // [ + DistributionPointName ] / text (one DP, one-URI fullName -> bare text)
874
+ case "freshestCRL":
1222
875
  if (node.majorType === 3) return b.sequence([b.sequence([b.explicit(0, b.contextConstructed(0, b.contextPrimitive(6, _ia5Bytes(node, 6))))])]);
1223
876
  if (node.majorType !== 4 || !node.children || node.children.length < 1) throw _err("c509/bad-extensions", "a " + name + " value must be a CBOR array of DistributionPoints or a bare URI text (sec. 3.3)");
1224
877
  return b.sequence(node.children.map(function (dp) { return _dpToDer(dp, isNative); }));
1225
- // IPAddrBlocks (and its RFC 8360 v2 twin): a FLAT array of (AFI, SAFI, choice) triples --
1226
- // IPAddressFamily is a parenthesized CDDL group, so it splices and does not nest.
1227
878
  case "ipAddrBlocks":
1228
879
  case "ipAddrBlocksV2": {
1229
880
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "an IPAddrBlocks value must be a CBOR array");
@@ -1239,16 +890,13 @@ function _extValueToDer(name, node, isNative) {
1239
890
  if (safi > 0xffn) throw _err("c509/bad-extensions", "an IPAddrBlocks SAFI must fit one octet (RFC 3779 sec. 2.2.3.3)");
1240
891
  famBytes.push(Number(safi));
1241
892
  }
1242
- // The families themselves are ordered and unique (RFC 3779 sec. 2.2.3.3), the same way the
1243
- // addresses inside one are. Without this a value could name AFI 2 before AFI 1, or repeat a
1244
- // family, and reconstruct a certificate an independent validator refuses.
1245
893
  var famOct = Buffer.from(famBytes);
1246
894
  if (prevFam !== null && _famOctCmp(prevFam, famOct) >= 0) {
1247
895
  throw _err("c509/bad-extensions", "IPAddrBlocks address families must be unique and in ascending addressFamily order (RFC 3779 sec. 2.2.3.3)");
1248
896
  }
1249
897
  prevFam = famOct;
1250
898
  var choice = ipKids[fi + 2], famFields = [b.octetString(famOct)];
1251
- if (_isCborNull(choice)) { // null -> inherit
899
+ if (_isCborNull(choice)) {
1252
900
  famFields.push(b.nullValue());
1253
901
  } else {
1254
902
  if (choice.majorType !== 4 || !choice.children || choice.children.length === 0) {
@@ -1260,27 +908,16 @@ function _extValueToDer(name, node, isNative) {
1260
908
  }
1261
909
  return b.sequence(families);
1262
910
  }
1263
- // ASIdentifiers (and its v2 twin): null = inherit, else a flat array of uint / [min,max].
1264
- // Only the asnum field is representable -- a present rdi has no compact form (sec. 3.3), so a
1265
- // certificate carrying one rides the ~oid byte-string form and never reaches this arm.
1266
911
  case "autonomousSysIds":
1267
912
  case "autonomousSysIdsV2": {
1268
- if (_isCborNull(node)) return b.sequence([b.explicit(0, b.nullValue())]); // asnum inherit
913
+ if (_isCborNull(node)) return b.sequence([b.explicit(0, b.nullValue())]);
1269
914
  if (node.majorType !== 4 || !node.children || node.children.length === 0) {
1270
915
  throw _err("c509/bad-extensions", "an ASIdentifiers value must be null (inherit) or a non-empty CBOR array");
1271
916
  }
1272
- // The deltas are `uint` in the draft's CDDL precisely because RFC 3779 sec. 3.2.3.4 sorts AS
1273
- // ids by increasing value: a negative delta would walk the chain backwards. That section also
1274
- // forbids a pair overlapping and requires a contiguous series to be one range: the same
1275
- // three rules the encode side applies, tracked here across members through `asPrevHigh`.
1276
- // A negative delta needs no separate test: on the first entry it drives the absolute below
1277
- // zero and the range guard refuses it, and on any later entry it lands at or below the
1278
- // previous high, which the canonical test already refuses. A separate uint check would be a
1279
- // branch nothing can reach.
1280
917
  var asKids = node.children, asDers = [], asPrev = null, asPrevHigh = null;
1281
918
  for (var asi2 = 0; asi2 < asKids.length; asi2++) {
1282
919
  var it = asKids[asi2];
1283
- if (it.majorType === 4) { // [min, max] -> ASRange
920
+ if (it.majorType === 4) {
1284
921
  if (!it.children || it.children.length !== 2) throw _err("c509/bad-extensions", "an ASIdentifiers range must be exactly [min, max]");
1285
922
  var amin = _asDelta(it.children[0], asPrev), amax = _asDelta(it.children[1], amin);
1286
923
  if (amax <= amin) throw _err("c509/bad-extensions", "an ASIdentifiers range must be ascending (RFC 3779 sec. 3.2.3.6)");
@@ -1288,7 +925,7 @@ function _extValueToDer(name, node, isNative) {
1288
925
  asDers.push(b.sequence([b.integer(amin), b.integer(amax)]));
1289
926
  asPrev = amax;
1290
927
  asPrevHigh = amax;
1291
- } else { // uint -> ASId
928
+ } else {
1292
929
  var aid = _asDelta(it, asPrev);
1293
930
  if (asPrevHigh !== null && aid <= asPrevHigh + 1n) throw _err("c509/bad-extensions", "ASIdentifiers must be sorted, non-overlapping and maximally merged (RFC 3779 sec. 3.2.3.4)");
1294
931
  asDers.push(b.integer(aid));
@@ -1298,7 +935,7 @@ function _extValueToDer(name, node, isNative) {
1298
935
  }
1299
936
  return b.sequence([b.explicit(0, b.sequence(asDers))]);
1300
937
  }
1301
- case "certificatePolicies": { // [ pid, [ *(qid, qtext) ], ... ] -> SEQUENCE OF PolicyInformation
938
+ case "certificatePolicies": {
1302
939
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a certificatePolicies value must be a CBOR array");
1303
940
  var cpKids = node.children;
1304
941
  if (cpKids.length === 0 || cpKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a certificatePolicies array must be non-empty (policyIdentifier, qualifiers) pairs (sec. 3.3)");
@@ -1306,9 +943,6 @@ function _extValueToDer(name, node, isNative) {
1306
943
  for (var cpi = 0; cpi + 1 < cpKids.length; cpi += 2) {
1307
944
  var quals = cpKids[cpi + 1];
1308
945
  if (quals.majorType !== 4 || !quals.children) throw _err("c509/bad-extensions", "a certificatePolicies qualifiers slot must be a CBOR array");
1309
- // A policy OID MUST NOT appear more than once (RFC 5280 sec. 4.2.1.4); the toolkit's own DER decoder
1310
- // rejects a duplicate (schema-pkix certExtensionDecoders), so reconstruct fails closed here and
1311
- // never produces a certificatePolicies extension that decoder would reject.
1312
946
  var policyOid = _policyIdToDerOid(cpKids[cpi]);
1313
947
  if (seenPolicy[policyOid]) throw _err("c509/bad-extensions", "a certificatePolicies policy OID must not appear more than once (RFC 5280 sec. 4.2.1.4)");
1314
948
  seenPolicy[policyOid] = true;
@@ -1317,83 +951,71 @@ function _extValueToDer(name, node, isNative) {
1317
951
  if (quals.children.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyQualifiers array must be (policyQualifierId, qualifier) pairs");
1318
952
  var pqDers = [];
1319
953
  for (var qk = 0; qk + 1 < quals.children.length; qk += 2) pqDers.push(_qualifierToDer(quals.children[qk], quals.children[qk + 1]));
1320
- polFields.push(b.sequence(pqDers)); // policyQualifiers SEQUENCE OF PolicyQualifierInfo
954
+ polFields.push(b.sequence(pqDers));
1321
955
  }
1322
956
  polInfos.push(b.sequence(polFields));
1323
957
  }
1324
958
  return b.sequence(polInfos);
1325
959
  }
1326
- case "policyMappings": { // [ idp, sdp, idp, sdp, ... ] -> SEQUENCE OF SEQUENCE { OID, OID } (sec. 3.3)
960
+ case "policyMappings": {
1327
961
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a policyMappings value must be a CBOR array");
1328
962
  var pmKids = node.children;
1329
963
  if (pmKids.length === 0 || pmKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyMappings array must be non-empty (issuerDomainPolicy, subjectDomainPolicy) pairs (sec. 3.3)");
1330
964
  var maps = [];
1331
- // each member is a CertPolicyId -- the same sec. 8.9 int/~oid policy space as certificatePolicies (an int
1332
- // outside sec. 8.9 fails closed in _policyIdToDerOid). anyPolicy is accepted: RFC 5280 sec. 4.2.1.5's
1333
- // "MUST NOT map to/from anyPolicy" is a generation rule the toolkit's own DER decoder does not reject.
1334
965
  for (var mi = 0; mi + 1 < pmKids.length; mi += 2) maps.push(b.sequence([b.oid(_policyIdToDerOid(pmKids[mi])), b.oid(_policyIdToDerOid(pmKids[mi + 1]))]));
1335
966
  return b.sequence(maps);
1336
967
  }
1337
- case "policyConstraints": { // [ requireExplicitPolicy: uint/null, inhibitPolicyMapping: uint/null ] -> SEQUENCE { [0]?, [1]? }
968
+ case "policyConstraints": {
1338
969
  if (node.majorType !== 4 || !node.children || node.children.length !== 2) throw _err("c509/bad-extensions", "a policyConstraints value must be a 2-element CBOR array [ requireExplicitPolicy, inhibitPolicyMapping ] (sec. 3.3)");
1339
970
  var pcFields = [], repN = node.children[0], ipmN = node.children[1];
1340
- if (!_isCborNull(repN)) pcFields.push(b.implicit(0, b.integer(_boundCount(_cborUint(repN, "a policyConstraints requireExplicitPolicy"), "a policyConstraints requireExplicitPolicy")))); // requireExplicitPolicy [0] IMPLICIT SkipCerts
1341
- if (!_isCborNull(ipmN)) pcFields.push(b.implicit(1, b.integer(_boundCount(_cborUint(ipmN, "a policyConstraints inhibitPolicyMapping"), "a policyConstraints inhibitPolicyMapping")))); // inhibitPolicyMapping [1] IMPLICIT SkipCerts
1342
- // both-null is the empty-SEQUENCE case RFC 5280 sec. 4.2.1.11 forbids ("either ... MUST be present"); the
1343
- // toolkit's own DER decoder rejects the empty PolicyConstraints, so the reconstruct fails closed too.
971
+ if (!_isCborNull(repN)) pcFields.push(b.implicit(0, b.integer(_boundCount(_cborUint(repN, "a policyConstraints requireExplicitPolicy"), "a policyConstraints requireExplicitPolicy"))));
972
+ if (!_isCborNull(ipmN)) pcFields.push(b.implicit(1, b.integer(_boundCount(_cborUint(ipmN, "a policyConstraints inhibitPolicyMapping"), "a policyConstraints inhibitPolicyMapping"))));
1344
973
  if (pcFields.length === 0) throw _err("c509/bad-extensions", "policyConstraints must contain requireExplicitPolicy or inhibitPolicyMapping (RFC 5280 sec. 4.2.1.11)");
1345
- return b.sequence(pcFields); // positional slot -> tag makes [0] < [1] unique + ascending by construction
974
+ return b.sequence(pcFields);
1346
975
  }
1347
- case "subjectDirectoryAttributes": // [ type, values, type, values, ... ] -> SEQUENCE OF Attribute (sec. 3.3)
976
+ case "subjectDirectoryAttributes":
1348
977
  return _sdaToDer(node, isNative);
1349
978
  default:
1350
979
  throw _err("c509/bad-extensions", "extension " + name + " has no compact value decoder");
1351
980
  }
1352
981
  }
1353
982
 
1354
- // Encode a DER extnValue inner content to its compact CBOR value bytes, or null when the DER is not the
1355
- // canonical form the compact encoding covers (the caller then emits the ~oid + byte-string fallback).
1356
983
  function _extValueFromDer(name, der) {
1357
984
  var node;
1358
- // A malformed DER extnValue (an ill-formed OID/INTEGER child, a truncated SEQUENCE) is simply not
1359
- // compact-encodable; any decode/read fault surfaces as null so the caller emits the ~oid byte-string form.
1360
- // Wrong-type children throw from asn1.read.* / cbor.build.uint (a negative value) and surface as the null
1361
- // fallback; the encode-side round-trip guard is the final net, so only the structural dispatch is inline.
1362
985
  try {
1363
986
  node = asn1.decode(der);
1364
987
  switch (name) {
1365
- case "subjectKeyIdentifier": // OCTET STRING(keyid) -> the bare key id
988
+ case "subjectKeyIdentifier":
1366
989
  return cbor.build.byteString(asn1.read.octetString(node));
1367
- case "keyUsage": { // BIT STRING -> uint
990
+ case "keyUsage": {
1368
991
  var bits = _keyUsageBitsFromDer(der);
1369
992
  return bits == null ? null : cbor.build.uint(BigInt(bits));
1370
993
  }
1371
- case "basicConstraints": { // SEQUENCE { cA?, pathLen? } -> int
994
+ case "basicConstraints": {
1372
995
  var kids = node.children || [];
1373
- if (kids.length === 0) return cbor.build.int(-2n); // cA absent (a non-SEQUENCE falls back via the guard)
1374
- if (asn1.read.boolean(kids[0]) !== true) return null; // explicit cA=false is non-canonical -> fall back
996
+ if (kids.length === 0) return cbor.build.int(-2n);
997
+ if (asn1.read.boolean(kids[0]) !== true) return null;
1375
998
  if (kids.length === 1) return cbor.build.int(-1n);
1376
999
  if (kids.length !== 2) return null;
1377
- return cbor.build.uint(asn1.read.integer(kids[1])); // pathLen (>= 0; a negative INTEGER throws -> fall back)
1000
+ return cbor.build.uint(asn1.read.integer(kids[1]));
1378
1001
  }
1379
- case "authorityKeyIdentifier": { // SEQUENCE { [0] keyId } or the 3-tuple form
1002
+ case "authorityKeyIdentifier": {
1380
1003
  var akids = node.children || [];
1381
1004
  if (akids.length === 1 && akids[0].tagClass === "context" && akids[0].tagNumber === 0) {
1382
- return cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0)); // keyId-only
1005
+ return cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0));
1383
1006
  }
1384
- // the [ keyIdentifier [0], authorityCertIssuer [1], authorityCertSerialNumber [2] ] all-three form.
1385
1007
  if (akids.length === 3 && akids[0].tagClass === "context" && akids[0].tagNumber === 0 &&
1386
1008
  akids[1].tagClass === "context" && akids[1].tagNumber === 1 && akids[1].children &&
1387
1009
  akids[2].tagClass === "context" && akids[2].tagNumber === 2) {
1388
1010
  var akiIssuer = _generalNamesFromDer(akids[1].children);
1389
1011
  if (akiIssuer == null) return null;
1390
1012
  var akiSerial = asn1.read.integerImplicit(akids[2], 2);
1391
- if (akiSerial < 0n) return null; // C509 authorityCertSerialNumber is a non-negative ~biguint
1013
+ if (akiSerial < 0n) return null;
1392
1014
  return cbor.build.array([cbor.build.byteString(asn1.read.octetStringImplicit(akids[0], 0)), cbor.build.array(akiIssuer), cbor.build.byteString(_minBytes(akiSerial))]);
1393
1015
  }
1394
- return null; // keyId+serial-without-issuer / issuer-only / other combinations fall back to ~oid
1016
+ return null;
1395
1017
  }
1396
- case "extKeyUsage": { // SEQUENCE OF OID -> [int/~oid ...] / single
1018
+ case "extKeyUsage": {
1397
1019
  var purposes = node.children || [];
1398
1020
  if (!purposes.length) return null;
1399
1021
  var out = purposes.map(function (p) {
@@ -1402,29 +1024,29 @@ function _extValueFromDer(name, der) {
1402
1024
  });
1403
1025
  return out.length === 1 ? out[0] : cbor.build.array(out);
1404
1026
  }
1405
- case "inhibitAnyPolicy": // INTEGER -> uint
1027
+ case "inhibitAnyPolicy":
1406
1028
  return cbor.build.uint(asn1.read.integer(node));
1407
- case "ocspNoCheck": // NULL -> null
1029
+ case "ocspNoCheck":
1408
1030
  asn1.read.nullValue(node);
1409
1031
  return cbor.build.nullValue();
1410
- case "tlsFeature": // SEQUENCE OF INTEGER -> [uint ...]
1032
+ case "tlsFeature":
1411
1033
  return cbor.build.array((node.children || []).map(function (f) { return cbor.build.uint(asn1.read.integer(f)); }));
1412
1034
  case "subjectAltName":
1413
- case "issuerAltName": { // SEQUENCE OF GeneralName -> flat array / dNSName text
1035
+ case "issuerAltName": {
1414
1036
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
1415
1037
  var sanMembers = node.children;
1416
1038
  if (sanMembers.length === 1 && sanMembers[0].tagClass === "context" && sanMembers[0].tagNumber === 2) {
1417
- return cbor.build.textString(schema.walk(GN_LEAF, sanMembers[0], NS).value); // exactly one dNSName -> bare text
1039
+ return cbor.build.textString(schema.walk(GN_LEAF, sanMembers[0], NS).value);
1418
1040
  }
1419
1041
  var sanItems = _generalNamesFromDer(sanMembers);
1420
1042
  return sanItems == null ? null : cbor.build.array(sanItems);
1421
1043
  }
1422
- case "nameConstraints": { // SEQUENCE { [0] permitted?, [1] excluded? } -> [perm, excl]
1044
+ case "nameConstraints": {
1423
1045
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
1424
1046
  var permCbor = cbor.build.nullValue(), exclCbor = cbor.build.nullValue(), sawNc = false, ncLast = -1;
1425
1047
  for (var nci = 0; nci < node.children.length; nci++) {
1426
1048
  var ncf = node.children[nci];
1427
- if (ncf.tagClass !== "context" || ncf.tagNumber <= ncLast) return null; // DER: unique + ascending
1049
+ if (ncf.tagClass !== "context" || ncf.tagNumber <= ncLast) return null;
1428
1050
  ncLast = ncf.tagNumber;
1429
1051
  var subItems = _subtreesFromDer(ncf.children || []);
1430
1052
  if (subItems == null) return null;
@@ -1435,7 +1057,7 @@ function _extValueFromDer(name, der) {
1435
1057
  return sawNc ? cbor.build.array([permCbor, exclCbor]) : null;
1436
1058
  }
1437
1059
  case "authorityInfoAccess":
1438
- case "subjectInfoAccess": { // SEQUENCE OF AccessDescription -> [method, uri, ...]
1060
+ case "subjectInfoAccess": {
1439
1061
  var aiaDescs = node.children || [];
1440
1062
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || aiaDescs.length < 1) return null;
1441
1063
  var aiaOut = [];
@@ -1443,7 +1065,7 @@ function _extValueFromDer(name, der) {
1443
1065
  var d = aiaDescs[adi];
1444
1066
  if (d.tagClass !== "universal" || d.tagNumber !== asn1.TAGS.SEQUENCE || !d.children || d.children.length !== 2) return null;
1445
1067
  var aiaLoc = d.children[1];
1446
- if (aiaLoc.tagClass !== "context" || aiaLoc.tagNumber !== 6) return null; // the compact form is URI-only
1068
+ if (aiaLoc.tagClass !== "context" || aiaLoc.tagNumber !== 6) return null;
1447
1069
  var methodDotted = asn1.read.oid(d.children[0]), mint = IA_TO_INT[methodDotted];
1448
1070
  aiaOut.push(mint !== undefined ? cbor.build.int(BigInt(mint)) : _oidCbor(methodDotted));
1449
1071
  aiaOut.push(cbor.build.textString(schema.walk(GN_LEAF, aiaLoc, NS).value));
@@ -1451,7 +1073,7 @@ function _extValueFromDer(name, der) {
1451
1073
  return cbor.build.array(aiaOut);
1452
1074
  }
1453
1075
  case "cRLDistributionPoints":
1454
- case "freshestCRL": { // SEQUENCE OF DistributionPoint -> array / bare URI text
1076
+ case "freshestCRL": {
1455
1077
  var dps = node.children || [];
1456
1078
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || dps.length < 1) return null;
1457
1079
  var dpResults = [];
@@ -1459,8 +1081,6 @@ function _extValueFromDer(name, der) {
1459
1081
  if (dpResults.length === 1 && dpResults[0].oneUri != null && dpResults[0].noReasons && dpResults[0].noIssuer) return cbor.build.textString(dpResults[0].oneUri);
1460
1082
  return cbor.build.array(dpResults.map(function (r) { return r.triple; }));
1461
1083
  }
1462
- // IPAddrBlocks -> the flat (AFI, SAFI, choice) array. Any shape the compact form cannot
1463
- // represent exactly returns null, so the extension rides the ~oid byte-string form intact.
1464
1084
  case "ipAddrBlocks":
1465
1085
  case "ipAddrBlocksV2": {
1466
1086
  if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length === 0) return null;
@@ -1469,36 +1089,28 @@ function _extValueFromDer(name, der) {
1469
1089
  var fam = node.children[ifi];
1470
1090
  if (!_isUniversal(fam, asn1.TAGS.SEQUENCE) || !fam.children || fam.children.length !== 2) return null;
1471
1091
  var famOct = asn1.read.octetString(fam.children[0]);
1472
- if (famOct.length !== 2 && famOct.length !== 3) return null; // OCTET STRING (SIZE (2..3))
1473
- // Families ordered and unique (RFC 3779 sec. 2.2.3.3) -- the mirror of the decode side.
1092
+ if (famOct.length !== 2 && famOct.length !== 3) return null;
1474
1093
  if (prevFamOct !== null && _famOctCmp(prevFamOct, famOct) >= 0) return null;
1475
1094
  prevFamOct = famOct;
1476
1095
  var afiVal = (famOct[0] << 8) | famOct[1];
1477
1096
  ipOut.push(cbor.build.uint(BigInt(afiVal)));
1478
1097
  ipOut.push(famOct.length === 3 ? cbor.build.uint(BigInt(famOct[2])) : cbor.build.nullValue());
1479
1098
  var ch = fam.children[1];
1480
- if (_isUniversal(ch, asn1.TAGS.NULL)) { ipOut.push(cbor.build.nullValue()); continue; } // inherit
1099
+ if (_isUniversal(ch, asn1.TAGS.NULL)) { ipOut.push(cbor.build.nullValue()); continue; }
1481
1100
  var chOut = _ipChoiceFromDer(ch, afiVal);
1482
1101
  if (!chOut) return null;
1483
1102
  ipOut.push(chOut);
1484
1103
  }
1485
1104
  return cbor.build.array(ipOut);
1486
1105
  }
1487
- // ASIdentifiers -> null (inherit) or the flat delta array. Only asnum is representable: a
1488
- // present rdi has no compact form (sec. 3.3), so such a certificate returns null here.
1489
1106
  case "autonomousSysIds":
1490
1107
  case "autonomousSysIdsV2": {
1491
1108
  if (!_isUniversal(node, asn1.TAGS.SEQUENCE) || !node.children || node.children.length !== 1) return null;
1492
1109
  var asnum = node.children[0];
1493
1110
  if (asnum.tagClass !== "context" || asnum.tagNumber !== 0 || !asnum.children || asnum.children.length !== 1) return null;
1494
1111
  var inner = asnum.children[0];
1495
- if (_isUniversal(inner, asn1.TAGS.NULL)) return cbor.build.nullValue(); // asnum inherit
1112
+ if (_isUniversal(inner, asn1.TAGS.NULL)) return cbor.build.nullValue();
1496
1113
  if (!_isUniversal(inner, asn1.TAGS.SEQUENCE) || !inner.children || inner.children.length === 0) return null;
1497
- // RFC 3779 sec. 3.2.3.4 fixes the canonical form the same way sec. 2.2.3.6 does for addresses:
1498
- // sorted by increasing value, no pair overlapping, and any contiguous series already merged
1499
- // into one range. `asPrevHigh` carries the previous entry's upper bound so all three hold
1500
- // ACROSS members. Checking only within a range would let a descending or adjacent pair
1501
- // through. A list that is not canonical is left uncompacted with its bytes intact.
1502
1114
  var asOut = [], asPrevOut = null, asPrevHigh = null;
1503
1115
  for (var aoi = 0; aoi < inner.children.length; aoi++) {
1504
1116
  var el = inner.children[aoi], elLo, elHi;
@@ -1511,7 +1123,7 @@ function _extValueFromDer(name, der) {
1511
1123
  elHi = asn1.read.integer(el.children[1]);
1512
1124
  if (elLo < 0n || elHi > 4294967295n || elHi <= elLo) return null;
1513
1125
  } else return null;
1514
- if (asPrevHigh !== null && elLo <= asPrevHigh + 1n) return null; // descending, overlapping, or contiguous
1126
+ if (asPrevHigh !== null && elLo <= asPrevHigh + 1n) return null;
1515
1127
  if (el.tagNumber === asn1.TAGS.INTEGER) {
1516
1128
  asOut.push(cbor.build.int(asPrevOut === null ? elLo : elLo - asPrevOut));
1517
1129
  asPrevOut = elLo;
@@ -1526,7 +1138,7 @@ function _extValueFromDer(name, der) {
1526
1138
  }
1527
1139
  return cbor.build.array(asOut);
1528
1140
  }
1529
- case "certificatePolicies": { // SEQUENCE OF PolicyInformation -> [ pid, [ *(qid, qtext) ], ... ]
1141
+ case "certificatePolicies": {
1530
1142
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
1531
1143
  var cpOut = [];
1532
1144
  for (var pli = 0; pli < node.children.length; pli++) {
@@ -1536,20 +1148,20 @@ function _extValueFromDer(name, der) {
1536
1148
  var qArr = [];
1537
1149
  if (pol.children.length === 2) {
1538
1150
  var qseq = pol.children[1];
1539
- if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null; // policyQualifiers SIZE 1..MAX
1151
+ if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null;
1540
1152
  for (var qj = 0; qj < qseq.children.length; qj++) {
1541
1153
  var pq = qseq.children[qj];
1542
1154
  if (pq.tagClass !== "universal" || pq.tagNumber !== asn1.TAGS.SEQUENCE || !pq.children || pq.children.length !== 2) return null;
1543
1155
  var qpair = _qualifierFromDer(pq);
1544
- if (qpair == null) return null; // any non-cps/unotice qualifier -> whole-ext fallback
1156
+ if (qpair == null) return null;
1545
1157
  qArr.push(qpair[0]); qArr.push(qpair[1]);
1546
1158
  }
1547
1159
  }
1548
- cpOut.push(cbor.build.array(qArr)); // empty [] when policyQualifiers absent
1160
+ cpOut.push(cbor.build.array(qArr));
1549
1161
  }
1550
1162
  return cbor.build.array(cpOut);
1551
1163
  }
1552
- case "policyMappings": { // SEQUENCE OF SEQUENCE { OID, OID } -> [ idp, sdp, ... ]
1164
+ case "policyMappings": {
1553
1165
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
1554
1166
  var pmOut = [];
1555
1167
  for (var pm = 0; pm < node.children.length; pm++) {
@@ -1560,22 +1172,22 @@ function _extValueFromDer(name, der) {
1560
1172
  }
1561
1173
  return cbor.build.array(pmOut);
1562
1174
  }
1563
- case "policyConstraints": { // SEQUENCE { [0] rep?, [1] ipm? } -> [ rep/null, ipm/null ]
1175
+ case "policyConstraints": {
1564
1176
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1 || node.children.length > 2) return null;
1565
1177
  var repC = cbor.build.nullValue(), ipmC = cbor.build.nullValue(), pcLast = -1;
1566
1178
  for (var pci = 0; pci < node.children.length; pci++) {
1567
1179
  var f = node.children[pci];
1568
- if (f.tagClass !== "context" || f.tagNumber <= pcLast) return null; // DER: unique + ascending [0] < [1]
1180
+ if (f.tagClass !== "context" || f.tagNumber <= pcLast) return null;
1569
1181
  pcLast = f.tagNumber;
1570
1182
  var sv = asn1.read.integerImplicit(f, f.tagNumber);
1571
- if (sv < 0n) return null; // SkipCerts (0..MAX) -- a negative is non-compact
1183
+ if (sv < 0n) return null;
1572
1184
  if (f.tagNumber === 0) repC = cbor.build.uint(sv);
1573
1185
  else if (f.tagNumber === 1) ipmC = cbor.build.uint(sv);
1574
1186
  else return null;
1575
1187
  }
1576
1188
  return cbor.build.array([repC, ipmC]);
1577
1189
  }
1578
- case "subjectDirectoryAttributes": // SEQUENCE OF Attribute -> [ type, values, ... ]
1190
+ case "subjectDirectoryAttributes":
1579
1191
  return _sdaFromDer(node);
1580
1192
  default:
1581
1193
  return null;
@@ -1585,67 +1197,15 @@ function _extValueFromDer(name, der) {
1585
1197
  }
1586
1198
  }
1587
1199
 
1588
- // ---- subjectDirectoryAttributes compact value codec (draft-20 sec. 3.3 / 8.6 / 8.8, RFC 5280 sec. 4.2.1.8) ----
1589
- // SubjectDirectoryAttributes ::= SEQUENCE SIZE(1..MAX) OF Attribute ::= SEQUENCE { type, values SET OF value }.
1590
- // The compact form is a flat CBOR array [ type1, values1, ... ] where each values slot is ITSELF a non-empty
1591
- // CBOR array, since an Attribute's SET may hold more than one value where a Name's RDN holds one. type is a
1592
- // sec. 8.6 registry int (the sign selects the string type) with text values, or an unwrapped ~oid with raw DER
1593
- // value bytes (an unregistered type stays compact via its own ~oid form, never dropping the whole extension).
1594
- // The extension-level _tryCompactExtValue round-trip guard is the byte-exactness net.
1595
-
1596
- // A ~oid-form attributeValue must be exactly one non-empty, well-formed DER AttributeValue TLV. An empty byte
1597
- // string would build a SET OF SIZE 0 (RFC 5280 sec. 4.2.1.8 requires at least one value); a byte string carrying
1598
- // more than one TLV would fan one declared value into several SET members (draft-20 sec. 3.3: one `bytes` is one
1599
- // AttributeValue). There is no downstream DER decoder to catch either (SDA ships c509-only), so the reconstruct
1600
- // arm self-enforces the single-TLV shape, so a degenerate or invalid Attribute is never spliced.
1601
- // ---- the shared strict-DER gate for a raw-spliced ANY value --------------------------------------
1602
- // Several reconstruct sites splice CALLER/ATTACKER-supplied bytes into the rebuilt certificate verbatim (an
1603
- // AlgorithmIdentifier's `parameters`, a generic otherName's `value [0] EXPLICIT ANY`, a subjectDirectoryAttributes
1604
- // `~oid`-form AttributeValue). asn1.decode validates TLV framing alone. It rejects truncation, trailing bytes and
1605
- // non-minimal lengths, and enforces the DER primitive/constructed rules, but it does not check per-type content:
1606
- // it frames an empty INTEGER, the reserved end-of-contents tag 0, and an out-of-alphabet string quite happily.
1607
- // Splicing those produces a certificate an independent decoder refuses to load, and one this toolkit's own readers
1608
- // reject. Every such site therefore routes through the one gate below instead of a bare asn1.decode.
1609
-
1610
- // Every universal PRIMITIVE tag whose content this toolkit can strictly validate, mapped to its reader. A
1611
- // universal primitive outside this map has no strict content validator here, so the gate REJECTS it and
1612
- // nothing is spliced unchecked. The documented residual: a value whose type this toolkit cannot yet strictly
1613
- // validate is not compact-representable. The ASN.1 types in that residual are `VideotexString` /
1614
- // `GraphicString` / `GeneralString` / `ObjectDescriptor` / `REAL` (no content reader in this toolkit), and a
1615
- // fractional-seconds GeneralizedTime (the X.690 sec. 11.7 relaxation is deliberately scoped to the codec
1616
- // and RFC 3161 timestamping, and must not creep into a third consumer). On the ENCODE path such a value simply
1617
- // degrades to the byte-exact ~oid + byte-string form, losing nothing; on the DECODE path it is a fail-closed
1618
- // verdict.
1619
- // The per-type strict content readers, the SET canonical-order rule and the recursive element
1620
- // walk all moved to guard.der, so this module and pki-build cannot drift on what "one strictly
1621
- // valid DER element" means.
1622
-
1623
- // A universal SET's required member order depends on a type the ANY does not carry: X.690 sec. 11.6 orders a
1624
- // SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6), and the two
1625
- // differ whenever the constructed bit does (a SEQUENCE member, tag 16, sorts before a PrintableString, tag 19, by
1626
- // tag but after it by octets). A structured SET cannot repeat a tag, so a repeated tag proves SET OF and the
1627
- // octet rule binds; with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
1628
- // (rejecting only what is non-canonical under both readings: sound in both directions, never a guess).
1629
-
1630
- // Strict-validate a decoded DER element at ANY depth, in the caller's error domain. Rejects the reserved EOC tag
1631
- // 0; runs a universal primitive through its strict content reader (or rejects a type with none); recurses into a
1632
- // constructed element's children; and holds a universal SET to a canonical order. Only SEQUENCE and SET are
1633
- // accepted as universal CONSTRUCTED types. An EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory
1634
- // components this gate cannot verify (the degenerate empty form is not a valid encoding of any of them), so it is
1635
- // refused for the same reason an unvalidatable primitive is. A NON-universal element (a legitimately context- or
1636
- // application-tagged ANY) passes on its framing, but its constructed children are still walked.
1637
- // Raw ANY bytes about to be spliced verbatim must be exactly one non-empty, well-formed AND strictly-valid DER
1638
- // element. The rule itself lives in guard.der -- framing + no-trailing-data, then content / structure / SET
1639
- // order -- so this module and the certificate builder cannot drift apart on what "one DER element" means. This
1640
- // stays as a named local because every reconstruct site here reads better naming the intent than the guard.
1200
+
1201
+
1202
+
1203
+
1641
1204
 
1642
1205
  function _requireStrictDerTlv(content, code, label) {
1643
1206
  return guard.der.tlv(content, _err, code, label);
1644
1207
  }
1645
1208
 
1646
- // asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would silently rewrite
1647
- // the declared order, and many distinct C509 encodings would reconstruct one and the same certificate. Require the
1648
- // declared order to already BE canonical, so the compact form maps one-to-one onto the DER it rebuilds.
1649
1209
  function _derSetInDeclaredOrder(vals) {
1650
1210
  for (var i = 1; i < vals.length; i++) {
1651
1211
  if (Buffer.compare(vals[i - 1], vals[i]) > 0) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue list must be in DER ascending order (X.690 sec. 11.6)");
@@ -1653,8 +1213,6 @@ function _derSetInDeclaredOrder(vals) {
1653
1213
  return b.set(vals);
1654
1214
  }
1655
1215
 
1656
- // [ type1, values1, ... ] -> the DER SubjectDirectoryAttributes. A malformed native value fails closed
1657
- // (c509/bad-extensions); on the encode path the same throw is a round-trip mismatch -> whole-ext ~oid fallback.
1658
1216
  function _sdaToDer(node, isNative) {
1659
1217
  if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes value must be a CBOR array");
1660
1218
  var kids = node.children;
@@ -1664,28 +1222,19 @@ function _sdaToDer(node, isNative) {
1664
1222
  var typeNode = kids[i], valuesNode = kids[i + 1];
1665
1223
  if (valuesNode.majorType !== 4 || !valuesNode.children || valuesNode.children.length < 1) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue must be a non-empty CBOR array (SET OF, SIZE 1..MAX, RFC 5280 sec. 4.2.1.8)");
1666
1224
  var vals = [];
1667
- if (typeNode.majorType === 0 || typeNode.majorType === 1) { // int form: a sec. 8.6 registry alias, text values
1225
+ if (typeNode.majorType === 0 || typeNode.majorType === 1) {
1668
1226
  var ti = Number(cbor.read.int(typeNode));
1669
- // draft sec. 3.1.4 applies to every int in a natively signed certificate, not just the top-level Name's.
1670
1227
  if (isNative && ti < 0) throw _err("c509/bad-extensions", "a natively signed C509 subjectDirectoryAttributes attribute type integer must be non-negative (draft sec. 3.1.4), got " + ti);
1671
1228
  var tname = ATTR_BY_INT[Math.abs(ti)];
1672
1229
  if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
1673
- // countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
1674
- // characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
1675
- // _reconAttrValue asserts the charset and honors the declared string type. Requiring the negative sign
1676
- // here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
1677
- // non-negative (same sec.), so the rule is the charset, not the sign.
1678
1230
  for (var vi = 0; vi < valuesNode.children.length; vi++) {
1679
1231
  var vn = valuesNode.children[vi];
1680
1232
  if (vn.majorType !== 3) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes int-form attribute value must be a CBOR text string (a non-string value requires the ~oid form)");
1681
- // _reconAttrValue -> b.printable / b.utf8 throws only an Asn1Error for a value outside its string type's
1682
- // alphabet (a printableString sign over non-PrintableString characters); remap it to this module's
1683
- // domain so attacker-controlled native input fails closed as c509/bad-extensions, not a leaked asn1/*.
1684
1233
  try { vals.push(_reconAttrValue({ type: tname, value: cbor.read.textString(vn), printable: ti < 0 })); }
1685
1234
  catch (e) { throw _err("c509/bad-extensions", "a subjectDirectoryAttributes " + tname + " value is not valid for its string type", e); }
1686
1235
  }
1687
1236
  attrs.push(b.sequence([b.oid(oid.byName(tname)), _derSetInDeclaredOrder(vals)]));
1688
- } else if (typeNode.majorType === 2) { // ~oid form: raw AttributeValue TLVs
1237
+ } else if (typeNode.majorType === 2) {
1689
1238
  var dotted = _oidName(typeNode, "c509/bad-extensions", "a subjectDirectoryAttributes attribute type").oid;
1690
1239
  for (var vj = 0; vj < valuesNode.children.length; vj++) {
1691
1240
  var vb = valuesNode.children[vj];
@@ -1700,15 +1249,9 @@ function _sdaToDer(node, isNative) {
1700
1249
  return b.sequence(attrs);
1701
1250
  }
1702
1251
 
1703
- // One DER Attribute's SET members -> the int-form { sign, values:[CBOR text] }, or null when the int form is
1704
- // not confidently byte-exact (an unregistered type, a non-utf8/printable value, or a SET mixing utf8String and
1705
- // printableString, which one sign cannot express). A null routes that one attribute to the always-byte-exact
1706
- // ~oid + raw-bytes form (a still-compact sec. 3.3 SDA form); the extension round-trip guard is the ultimate net.
1707
1252
  function _sdaTryIntForm(tint, name, valueNodes) {
1708
1253
  if (tint === undefined) return null;
1709
1254
  var i;
1710
- // An IA5String-only attribute (emailAddress) rides its non-negative int with IA5String values. Its type,
1711
- // not a sign, fixes the string type, so it is matched separately from the utf8/printable sign convention.
1712
1255
  if (name === "emailAddress") {
1713
1256
  var mails = [];
1714
1257
  for (i = 0; i < valueNodes.length; i++) {
@@ -1717,22 +1260,19 @@ function _sdaTryIntForm(tint, name, valueNodes) {
1717
1260
  }
1718
1261
  return { sign: 1, values: mails };
1719
1262
  }
1720
- var sign = 0, out = []; // sign: -1 printableString, +1 utf8String
1263
+ var sign = 0, out = [];
1721
1264
  for (i = 0; i < valueNodes.length; i++) {
1722
1265
  var vn = valueNodes[i], s;
1723
1266
  if (vn.tagClass === "universal" && vn.tagNumber === asn1.TAGS.UTF8_STRING) s = 1;
1724
1267
  else if (vn.tagClass === "universal" && vn.tagNumber === asn1.TAGS.PRINTABLE_STRING) s = -1;
1725
- else return null; // any other value tag -> the ~oid form preserves it exactly
1268
+ else return null;
1726
1269
  if (sign === 0) sign = s;
1727
- else if (sign !== s) return null; // a mixed utf8/printable SET -- the headline trap
1270
+ else if (sign !== s) return null;
1728
1271
  out.push(cbor.build.textString(asn1.read.string(vn)));
1729
1272
  }
1730
1273
  return { sign: sign, values: out };
1731
1274
  }
1732
1275
 
1733
- // The DER SubjectDirectoryAttributes -> the flat compact CBOR array, or null (whole-ext ~oid fallback). Per
1734
- // attribute: the int form when the type is sec. 8.6-registered AND every value is a single-signed utf8/
1735
- // printable string, else the ~oid + raw-bytes form (always byte-exact).
1736
1276
  function _sdaFromDer(node) {
1737
1277
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
1738
1278
  var out = [];
@@ -1740,7 +1280,7 @@ function _sdaFromDer(node) {
1740
1280
  var attr = node.children[ai];
1741
1281
  if (attr.tagClass !== "universal" || attr.tagNumber !== asn1.TAGS.SEQUENCE || !attr.children || attr.children.length !== 2) return null;
1742
1282
  var setNode = attr.children[1];
1743
- if (setNode.tagClass !== "universal" || setNode.tagNumber !== asn1.TAGS.SET || !setNode.children || setNode.children.length < 1) return null; // SET OF, non-empty
1283
+ if (setNode.tagClass !== "universal" || setNode.tagNumber !== asn1.TAGS.SET || !setNode.children || setNode.children.length < 1) return null;
1744
1284
  var dotted = asn1.read.oid(attr.children[0]);
1745
1285
  var nm = oid.name(dotted), tint = nm != null ? ATTR_TO_INT[nm] : undefined;
1746
1286
  var intForm = _sdaTryIntForm(tint, nm, setNode.children);
@@ -1755,13 +1295,6 @@ function _sdaFromDer(node) {
1755
1295
  return cbor.build.array(out);
1756
1296
  }
1757
1297
 
1758
- // Encode-side guard: emit the compact value only when it decodes back to the EXACT DER extnValue, so a
1759
- // non-canonical or unrepresentable value can never produce a lossy compact form (it falls back to ~oid).
1760
- // Some values _extValueFromDer produced cannot be reconstructed by _extValueToDer: an empty GeneralNames the
1761
- // DER builder rejects (an AKI authorityCertIssuer [1] with no members), or any future codec asymmetry. None of
1762
- // them is compact-representable, and the round-trip reconstruction throwing is treated identically to a byte
1763
- // mismatch. encode() falls back to the ~oid form and does not raise on a certificate x509.parse
1764
- // accepts. The final encode() self-verify still gates byte-exactness end to end.
1765
1298
  function _tryCompactExtValue(name, der) {
1766
1299
  var compact = _extValueFromDer(name, der);
1767
1300
  if (compact == null) return null;
@@ -1774,10 +1307,7 @@ function _tryCompactExtValue(name, der) {
1774
1307
  return recon.equals(der) ? compact : null;
1775
1308
  }
1776
1309
 
1777
- // extensions (sec. 3.1.10/sec. 3.3/sec. 8.8): [ * Extension ] | a single keyUsage int-shortcut.
1778
1310
  function _extensions(node, isNative) {
1779
- // The keyUsage int-shortcut (sec. 3.1.10): a bare int -> one keyUsage extension, criticality from the
1780
- // sign, value = abs(int) (Appendix A.1.1: the single int 1 -> non-critical keyUsage digitalSignature).
1781
1311
  if (node.majorType === 0 || node.majorType === 1) {
1782
1312
  var iv = Number(cbor.read.int(node));
1783
1313
  return [{ name: "keyUsage", oid: oid.byName("keyUsage"), critical: iv < 0, keyUsageBits: Math.abs(iv) }];
@@ -1785,12 +1315,7 @@ function _extensions(node, isNative) {
1785
1315
  if (node.majorType !== 4) throw _err("c509/bad-extensions", "C509 extensions must be an array or a keyUsage int shortcut");
1786
1316
  var out = [];
1787
1317
  var kids = node.children || [];
1788
- // Each extension is an (extensionID, extensionValue) pair; an odd-length array is a dangling
1789
- // extension identifier with no value, so the whole extensions array is rejected.
1790
1318
  if (kids.length % 2 !== 0) throw _err("c509/bad-extensions", "a C509 extensions array must be id/value pairs (dangling extension identifier)");
1791
- // sec. 3.1.10: "If the CBOR array contains exactly two ints and the absolute value of the first int is
1792
- // 2 (corresponding to keyUsage), the CBOR array is omitted and the 'extensions' field is encoded as a
1793
- // single CBOR int." The array spelling of that one case is a second encoding of the same extensions.
1794
1319
  if (!isNative && kids.length === 2 &&
1795
1320
  (kids[0].majorType === 0 || kids[0].majorType === 1) && (kids[1].majorType === 0 || kids[1].majorType === 1) &&
1796
1321
  Math.abs(Number(cbor.read.int(kids[0]))) === 2) {
@@ -1804,11 +1329,6 @@ function _extensions(node, isNative) {
1804
1329
  name = EXT_BY_INT[Math.abs(ei)];
1805
1330
  if (name === undefined) throw _err("c509/bad-extensions", "extension type integer " + ei + " has no C509 registry row");
1806
1331
  extOid = oid.byName(name); critical = ei < 0;
1807
- // A registered extension with a compact value form (sec. 3.3) decodes its specific CBOR value to the
1808
- // DER extnValue inner content. A registered extension without one (subjectAltName, until the general-
1809
- // name value codec lands) can only reconstruct a byte-string value (the raw DER extnValue); a
1810
- // non-byte-string value there is an unsupported compact form and MUST fail closed. A text/array value
1811
- // is not raw DER, and copying its bytes would reconstruct a structurally invalid extension.
1812
1332
  if (EXT_COMPACT[name]) {
1813
1333
  valContent = _extValueToDer(name, valNode, isNative);
1814
1334
  } else if (valNode.majorType === 2) {
@@ -1819,8 +1339,6 @@ function _extensions(node, isNative) {
1819
1339
  } else {
1820
1340
  var r = _oidName(idNode, "c509/bad-extensions", "an extension id");
1821
1341
  name = r.name; extOid = r.oid;
1822
- // ~oid extension (sec. 3.1.10): the extnValue is a byte string, either BARE (non-critical) or wrapped in a
1823
- // single-element array (critical). Validate the shape so a malformed value fails closed at decode.
1824
1342
  critical = valNode.majorType === 4;
1825
1343
  if (critical) {
1826
1344
  if (!valNode.children || valNode.children.length !== 1 || valNode.children[0].majorType !== 2) {
@@ -1837,13 +1355,7 @@ function _extensions(node, isNative) {
1837
1355
  return out;
1838
1356
  }
1839
1357
 
1840
- // ---- type-3 DER reconstruction (byte-exact inversion; draft-20 sec. 3 / sec. 5) ----
1841
- // Type 3 is an INVERTIBLE transform: the reconstruction reproduces the ORIGINAL DER Certificate
1842
- // byte-for-byte (so the original signature verifies). A field outside the covered set fails closed
1843
- // (c509/non-invertible); it never emits a partial or best-effort DER.
1844
1358
 
1845
- // A C509 tag-48 MAC (RFC 9542) reconstructs to the commonName EUI-64 string: a 6-byte value is a 48-bit
1846
- // MAC expanded to the EUI-64 HH-HH-HH-FF-FE-HH-HH-HH by inserting FF-FE; an 8-byte value is the EUI-64.
1847
1359
  function _macToEui64String(buf) {
1848
1360
  var bytes = buf.length === 6 ? Buffer.concat([buf.subarray(0, 3), Buffer.from([0xff, 0xfe]), buf.subarray(3)]) : buf;
1849
1361
  var s = [];
@@ -1851,64 +1363,40 @@ function _macToEui64String(buf) {
1851
1363
  return s.join("-");
1852
1364
  }
1853
1365
 
1854
- // One RDN attribute value -> its DER string. The C509 sign convention: a positive attribute int ->
1855
- // utf8String, a negative -> printableString; countryName / serialNumber are PrintableString-restricted.
1856
1366
  function _reconAttrValue(rdn) {
1857
- if (rdn.eui64) return b.utf8(_macToEui64String(rdn.eui64)); // the bare-form commonName MAC (a fixed 17 chars)
1367
+ if (rdn.eui64) return b.utf8(_macToEui64String(rdn.eui64));
1858
1368
  var s = String(rdn.value);
1859
- // The attribute's registered ASN.1 type carries a SIZE constraint as well as an alphabet, and a value outside
1860
- // it is not a valid encoding of that type: every DirectoryString-valued attribute is SIZE (1..MAX) and
1861
- // emailAddress is IA5String SIZE (1..MAX) (RFC 5280 App. A.1), so an empty value is never valid; countryName
1862
- // "SHALL have length 2" (draft sec. 3.1.4, X.520 SIZE (2)). Enforced HERE, the single place the value is built
1863
- // for its declared type, so the decode, reconstruct and subject-directory-attributes paths cannot disagree.
1864
1369
  if (s.length === 0) throw _err("c509/bad-name", "a " + rdn.type + " value must be non-empty (SIZE (1..MAX))");
1865
1370
  if (rdn.type === "countryName" && s.length !== 2) throw _err("c509/bad-name", "a countryName value must have length 2 (draft sec. 3.1.4)");
1866
- // emailAddress is an IA5String-only attribute (draft sec. 3.1.4): its type, not the int's sign, fixes the
1867
- // string type, so it reconstructs as an IA5String whichever way the non-negative int was written.
1868
1371
  if (rdn.type === "emailAddress") return b.ia5(s);
1869
- // serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
1870
- // "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
1871
- // that on the CHARACTERS and still honor the sign for the string type. Coercing them to PrintableString
1872
- // regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
1873
- // X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
1874
- // b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
1875
- // utf8String, so the restriction binds on the characters without overriding the declared string type.
1876
1372
  if (!rdn.printable && (rdn.type === "countryName" || rdn.type === "serialNumber")) b.printable(s);
1877
1373
  return rdn.printable ? b.printable(s) : b.utf8(s);
1878
1374
  }
1879
1375
 
1880
- // A Name -> the DER RDNSequence (SEQUENCE OF SET OF SEQUENCE{ type, value }); one attribute per RDN.
1881
1376
  function _reconName(name) {
1882
1377
  return b.sequence(name.rdns.map(function (rdn) {
1883
1378
  return b.set([b.sequence([b.oid(oid.byName(rdn.type)), _reconAttrValue(rdn)])]);
1884
1379
  }));
1885
1380
  }
1886
1381
 
1887
- // A validity instant -> UTCTime (RFC 5280 sec. 4.1.2.5: year < 2050) or GeneralizedTime. A null notAfter
1888
- // -> the no-well-defined-expiry sentinel 99991231235959Z.
1889
1382
  function _reconTime(date) {
1890
1383
  if (date === null) return b.generalizedTime(new Date(Date.UTC(9999, 11, 31, 23, 59, 59)));
1891
1384
  return date.getUTCFullYear() < 2050 ? b.utcTime(date) : b.generalizedTime(date);
1892
1385
  }
1893
1386
 
1894
- // subjectPublicKeyInfo -> DER. EC: rebuild AlgorithmIdentifier{ ecPublicKey, namedCurve } + the BIT
1895
- // STRING point, de-compressing a C509 0xFE/0xFD marker back to the original uncompressed 0x04||X||Y.
1896
1387
  function _reconSpki(spkAlg, keyBytes, rsaKey) {
1897
1388
  if (spkAlg.name === "ecPublicKey") {
1898
1389
  var fieldSize = EC_FIELD_BYTES[spkAlg.curve];
1899
1390
  if (!fieldSize) throw _err("c509/non-invertible", "unsupported EC curve " + spkAlg.curve);
1900
1391
  if (!keyBytes || keyBytes.length === 0) throw _err("c509/non-invertible", "the EC subjectPublicKey byte string is empty");
1901
1392
  var head = keyBytes[0], point;
1902
- // The point length must match the curve field size for its encoding: an uncompressed 0x04 point is
1903
- // 1 + 2*fieldSize, and a compressed 0x02/0x03/0xFE/0xFD point is 1 + fieldSize. A truncated / padded
1904
- // point cannot be re-emitted as a valid (or byte-exact) SubjectPublicKeyInfo.
1905
1393
  if (head === 0x04) {
1906
1394
  if (keyBytes.length !== 1 + 2 * fieldSize) throw _err("c509/non-invertible", "uncompressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
1907
1395
  point = keyBytes;
1908
1396
  } else if (head === 0x02 || head === 0x03) {
1909
1397
  if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "compressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
1910
1398
  point = keyBytes;
1911
- } else if (head === 0xfe || head === 0xfd) { // C509 marker -> de-compress
1399
+ } else if (head === 0xfe || head === 0xfd) {
1912
1400
  if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "C509-marked EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
1913
1401
  var sec1 = Buffer.concat([Buffer.from([head === 0xfe ? 0x02 : 0x03]), keyBytes.subarray(1)]);
1914
1402
  point = webcrypto.decompressEcPoint(sec1, spkAlg.curve, _err, "c509/non-invertible");
@@ -1916,32 +1404,23 @@ function _reconSpki(spkAlg, keyBytes, rsaKey) {
1916
1404
  return b.sequence([b.sequence([b.oid(oid.byName("ecPublicKey")), b.oid(oid.byName(spkAlg.curve))]), b.bitString(point, 0)]);
1917
1405
  }
1918
1406
  if (spkAlg.name === "rsaEncryption") {
1919
- // draft-20 sec. 3.2.1: the RSA key is [modulus, exponent] ~biguints, OR just the modulus ~biguint
1920
- // when the exponent is 65537 (parse has already resolved rsaKey to { modulus, exponent }). Reconstruct
1921
- // AlgorithmIdentifier{ rsaEncryption, NULL } + the BIT STRING wrapping RSAPublicKey ::= SEQUENCE {
1922
- // modulus INTEGER, publicExponent INTEGER }.
1923
1407
  var rsaPk = b.sequence([b.integer(rsaKey.modulus), b.integer(rsaKey.exponent)]);
1924
1408
  return b.sequence([b.sequence([b.oid(oid.byName("rsaEncryption")), b.nullValue()]), b.bitString(rsaPk, 0)]);
1925
1409
  }
1926
1410
  throw _err("c509/non-invertible", "subjectPublicKey algorithm " + spkAlg.name + " is not in the type-3 reconstruction covered set");
1927
1411
  }
1928
1412
 
1929
- // The keyUsage int value -> the DER KeyUsage BIT STRING (RFC 5280 sec. 4.2.1.3): bit i of the value ->
1930
- // BIT STRING named bit i (bit 0 = digitalSignature = the MSB of the first content octet). Composes the
1931
- // shared named-bit content builder (also used by the cRLDistributionPoints reasons transform); a
1932
- // non-positive, non-integer, or > 0x1FF value is not a valid KeyUsage and fails closed.
1933
1413
  function _reconKeyUsageBits(value) {
1934
1414
  var c = _namedBitsToContent(value);
1935
1415
  if (c == null) throw _err("c509/non-invertible", "a keyUsage value must be a positive integer within the 9 defined bits");
1936
1416
  return b.bitString(c.bytes, c.unusedBits);
1937
1417
  }
1938
1418
 
1939
- // extensions -> the [3] EXPLICIT SEQUENCE OF Extension DER.
1940
1419
  function _reconExtensions(exts) {
1941
1420
  var items = exts.map(function (ext) {
1942
1421
  var extnValue;
1943
1422
  if (ext.name === "keyUsage" && typeof ext.keyUsageBits === "number") extnValue = _reconKeyUsageBits(ext.keyUsageBits);
1944
- else if (Buffer.isBuffer(ext.value)) extnValue = ext.value; // raw DER extnValue bytes
1423
+ else if (Buffer.isBuffer(ext.value)) extnValue = ext.value;
1945
1424
  else throw _err("c509/non-invertible", "extension " + ext.name + " has no reconstructable value in the covered set");
1946
1425
  var fields = [b.oid(ext.oid || oid.byName(ext.name))];
1947
1426
  if (ext.critical) fields.push(b.boolean(true));
@@ -1951,55 +1430,35 @@ function _reconExtensions(exts) {
1951
1430
  return b.explicit(3, b.sequence(items));
1952
1431
  }
1953
1432
 
1954
- // An AlgorithmIdentifier -> DER SEQUENCE { algorithm OID, parameters? }. A C509 [~oid, params]
1955
- // algorithm carries its DER parameters bytes, which MUST be reproduced so the reconstruction inverts
1956
- // byte-exact (silently dropping them would change the signed bytes); the int / ~oid forms carry no
1957
- // parameters (ecdsaWith* and the like omit them).
1958
1433
  function _reconAlgId(alg) {
1959
1434
  var fields = [b.oid(alg.oid)];
1960
1435
  if (alg.parameters && alg.parameters.length) {
1961
- // AlgorithmIdentifier.parameters is ANY, spliced verbatim, so it gets the same strict gate as every other
1962
- // raw-ANY splice, not just a framing check. Framing alone admits an empty INTEGER / reserved tag 0 / a
1963
- // non-minimal INTEGER, each of which reconstructs an AlgorithmIdentifier that an independent X.509 decoder
1964
- // refuses to load and that this toolkit's own readers reject.
1965
1436
  fields.push(b.raw(_requireStrictDerTlv(alg.parameters, "c509/non-invertible", "algorithm parameters")));
1966
1437
  }
1967
1438
  return b.sequence(fields);
1968
1439
  }
1969
1440
 
1970
- // The full type-3 -> DER Certificate reconstruction, byte-for-byte.
1971
1441
  function _reconstructDer(r, sigNode) {
1972
1442
  var sigAlgSeq = _reconAlgId(r.signatureAlgorithm);
1973
1443
  var subjectName = _reconName(r.subject);
1974
1444
  var spelledIssuer = r.issuer && r.issuer.rdns ? _reconName(r.issuer) : null;
1975
- // sec. 3.1.4: an issuer identical to the subject MUST be the CBOR simple value null. Spelling it
1976
- // out instead rebuilds the same DER, so one certificate would have two C509 encodings and the one
1977
- // X.509 signature over that DER would cover both.
1978
1445
  if (spelledIssuer !== null && spelledIssuer.equals(subjectName)) {
1979
1446
  throw _err("c509/bad-name", "an issuer identical to the subject must be encoded as the CBOR simple value null (draft sec. 3.1.4)");
1980
1447
  }
1981
1448
  var tbsFields = [
1982
- b.explicit(0, b.integer(2n)), // version v3 (type-3 is X.509 v3)
1449
+ b.explicit(0, b.integer(2n)),
1983
1450
  b.integer(r.serialNumber),
1984
1451
  sigAlgSeq,
1985
- spelledIssuer === null ? subjectName : spelledIssuer, // null issuer -> issuer == subject
1452
+ spelledIssuer === null ? subjectName : spelledIssuer,
1986
1453
  b.sequence([_reconTime(r.validity.notBefore), _reconTime(r.validity.notAfter)]),
1987
1454
  subjectName,
1988
1455
  _reconSpki(r.subjectPublicKeyAlgorithm, r.subjectPublicKey, r.rsaPublicKey),
1989
1456
  ];
1990
- // RFC 5280 sec. 4.1: the [3] extensions field is OPTIONAL and, when present, SHALL contain at least one
1991
- // extension, so an empty C509 extensions array reconstructs to an OMITTED field, not an empty SEQUENCE.
1992
1457
  if (r.extensions.length) tbsFields.push(_reconExtensions(r.extensions));
1993
1458
  var tbs = b.sequence(tbsFields);
1994
- // The signature is re-wrapped as a DER ECDSA-Sig-Value from the fixed-width r||s, so only an ECDSA
1995
- // signature algorithm is in the type-3 reconstruction covered set (an RSA/EdDSA signature is raw bytes,
1996
- // not r||s, so it is rejected). A wrong-length r||s surfaces the caller's typed code.
1997
1459
  if (String(r.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) {
1998
1460
  throw _err("c509/non-invertible", "type-3 signature reconstruction covers only ECDSA; got " + r.signatureAlgorithm.name);
1999
1461
  }
2000
- // The fixed-width r||s must split at a real curve field width: P-256/384/521 = 64/96/132 bytes
2001
- // (RFC 9053 sec. 2.1). A width that is not 2x a supported field size is not a valid ECDSA signature and
2002
- // cannot be re-wrapped byte-exact, so it surfaces the caller's typed code.
2003
1462
  var coordLen = r.signatureValue.length / 2;
2004
1463
  if (coordLen !== 32 && coordLen !== 48 && coordLen !== 66) {
2005
1464
  throw _err("c509/bad-signature", "the type-3 ECDSA signature width " + r.signatureValue.length + " is not a valid fixed-width r||s (expected 64/96/132 for P-256/384/521)");
@@ -2008,7 +1467,6 @@ function _reconstructDer(r, sigNode) {
2008
1467
  return b.sequence([tbs, sigAlgSeq, b.bitString(sigValue, 0)]);
2009
1468
  }
2010
1469
 
2011
- // ---- the parse ----
2012
1470
 
2013
1471
  /**
2014
1472
  * @primitive pki.schema.c509.parse
@@ -2041,8 +1499,6 @@ function parse(input) {
2041
1499
  var f = root.children;
2042
1500
  if (f.length !== 11) throw _err("c509/bad-tbs", "a C509 certificate must be an array of exactly 11 elements, got " + f.length);
2043
1501
 
2044
- // The type slot must be a CBOR integer -- guard the major type here so a text/array/byte-string field 0 fails
2045
- // in this module's domain rather than leaking cbor.read.int's cbor/unexpected-major fault to the caller.
2046
1502
  if (f[0].majorType !== 0 && f[0].majorType !== 1) throw _err("c509/bad-certificate-type", "c509CertificateType must be a CBOR integer");
2047
1503
  var type = Number(cbor.read.int(f[0]));
2048
1504
  if (type !== 2 && type !== 3) throw _err("c509/bad-certificate-type", "c509CertificateType must be 2 (native) or 3 (re-encoded), got " + type);
@@ -2056,16 +1512,12 @@ function parse(input) {
2056
1512
  var notBefore = _time(f[4], false, "validityNotBefore");
2057
1513
  var notAfter = _time(f[5], true, "validityNotAfter");
2058
1514
  var subject = _name509(f[6], true, type === 2);
2059
- // A CBOR-null issuer means issuer == subject (self-signed), so the EFFECTIVE issuer is the subject -- and it
2060
- // must still satisfy RFC 5280 sec. 4.1.2.4. Checking only the array form would let the null form rebuild the
2061
- // very empty issuer the array form is refused for.
2062
1515
  if (issuer === null && (!subject || !subject.rdns || subject.rdns.length === 0)) {
2063
1516
  throw _err("c509/bad-name", "a self-signed C509 (issuer == subject) requires a non-empty subject, since it is also the issuer (RFC 5280 sec. 4.1.2.4)");
2064
1517
  }
2065
1518
  var spkAlg = _algorithm(f[7], PK_ALG_BY_INT, "c509/unknown-algorithm", "subjectPublicKeyAlgorithm");
2066
1519
  var subjectPublicKey = null, rsaKey = null;
2067
1520
  if (spkAlg.name === "rsaEncryption") {
2068
- // draft-20 sec. 3.2.1: [modulus, exponent] ~biguints, OR a bare modulus ~biguint (exponent = 65537).
2069
1521
  if (f[8].majorType === 2) rsaKey = { modulus: _biguint(f[8], "c509/bad-spki", "RSA modulus"), exponent: 65537n };
2070
1522
  else if (f[8].majorType === 4 && f[8].children && f[8].children.length === 2) {
2071
1523
  rsaKey = { modulus: _biguint(f[8].children[0], "c509/bad-spki", "RSA modulus"), exponent: _biguint(f[8].children[1], "c509/bad-spki", "RSA exponent") };
@@ -2094,42 +1546,21 @@ function parse(input) {
2094
1546
  signatureValue: signatureValue,
2095
1547
  };
2096
1548
 
2097
- // The RAW bytes of CBOR-array elements 0..9 (NOT the outer array head, NOT the signature) -- a zero-copy
2098
- // subarray. For a native (type-2) certificate this is the signed region a verifier hashes (sec. 3.1.12);
2099
- // surfaced as `_fieldBytes` for BOTH types so encode() re-emits a parsed certificate byte-for-byte (a
2100
- // re-derivation from the decoded values could differ on a canonical-equivalent form, breaking a type-2
2101
- // signature or a type-3 DER reconstruction). `signedData` keeps its type-2-only signed-region meaning.
2102
- // From the DECODED root's own bytes (root.bytes == array head + all 11 elements), not offset arithmetic
2103
- // on `input` (which breaks when input is a Uint8Array the codec normalized to a different buffer): the
2104
- // fields region is root.bytes minus the 1-byte array(11) head and minus the trailing signatureValue.
2105
1549
  result._fieldBytes = root.bytes.subarray(1, root.bytes.length - f[10].bytes.length);
2106
1550
  if (type === 2) result.signedData = result._fieldBytes;
2107
1551
 
2108
- // Type-3 is an invertible re-encoding of a DER X.509 certificate: reconstruct the original DER
2109
- // byte-for-byte so the original signature verifies and x509.parse recovers the certificate.
2110
1552
  if (type === 3) result.reconstructedDer = _reconstructDer(result, f[10]);
2111
1553
 
2112
1554
  return result;
2113
1555
  }
2114
1556
 
2115
- // matches(node) -- a STRUCTURAL probe over a DECODED CBOR node: an array of 11 whose first element is
2116
- // a major-type-0/1 int equal to 2 or 3. Not wired into the DER orchestrator (C509 is CBOR, not DER).
2117
1557
  function matches(node) {
2118
1558
  return !!node && node.majorType === 4 && !!node.children && node.children.length === 11 &&
2119
1559
  (node.children[0].majorType === 0 || node.children[0].majorType === 1) &&
2120
1560
  (Number(node.children[0].argument) === 2 || Number(node.children[0].argument) === 3);
2121
1561
  }
2122
1562
 
2123
- // ---- the encode (the producing side; draft-20 sec. 3) -----------------------
2124
- // The byte-exact inverse of parse/reconstruct: emit the 11-element deterministic-CBOR C509 array via the
2125
- // cbor.build.* emitter. Two inputs, dispatched structurally: a DER X.509 certificate (Buffer/PEM) -> the
2126
- // FLAGSHIP type-3 forward transform (parse(encode(der)).reconstructedDer == der, so the original signature
2127
- // verifies); a c509.parse result -> re-emit its native array. Signing-free (mirrors ct.encodeSctList).
2128
1563
 
2129
- // The registry INVERSE tables (name -> canonical int). The RDN-attribute and extension inverses are
2130
- // DERIVED from the *_BY_INT decode tables so the two directions cannot drift apart (a hand-kept inverse
2131
- // once carried a stale pre-draft-20 numbering); the extension inverse keeps only the compact-encodable
2132
- // rows, so a registered extension without a value codec (subjectAltName) routes to the ~oid form.
2133
1564
  var SIG_ALG_TO_INT = { ecdsaWithSHA256: 0, ecdsaWithSHA384: 1, ecdsaWithSHA512: 2 };
2134
1565
  var PK_ALG_TO_INT = { rsaEncryption: 0, "ecPublicKey|prime256v1": 1, "ecPublicKey|secp384r1": 2, "ecPublicKey|secp521r1": 3 };
2135
1566
  var ATTR_TO_INT = {};
@@ -2137,40 +1568,28 @@ Object.keys(ATTR_BY_INT).forEach(function (k) { ATTR_TO_INT[ATTR_BY_INT[k]] = Nu
2137
1568
  var EXT_TO_INT = {};
2138
1569
  Object.keys(EXT_BY_INT).forEach(function (k) { if (EXT_COMPACT[EXT_BY_INT[k]]) EXT_TO_INT[EXT_BY_INT[k]] = Number(k); });
2139
1570
 
2140
- // A non-negative BigInt -> its minimal big-endian ~biguint bytes (the leading 0x00 sign octet omitted).
2141
1571
  function _minBytes(n) {
2142
1572
  if (n < 0n) throw _err("c509/bad-serial", "a ~biguint value must be non-negative");
2143
1573
  if (n === 0n) return Buffer.alloc(0);
2144
1574
  var hex = n.toString(16); if (hex.length % 2) hex = "0" + hex;
2145
1575
  return Buffer.from(hex, "hex");
2146
1576
  }
2147
- // A C509 AlgorithmIdentifier -> int (registry) | ~oid (bare bytes) | [~oid, params]. `key` selects the row.
2148
- // The bare-~oid arm is unreachable from either call site as the type-3 encoder stands, and is kept as
2149
- // the honest fallback rather than an assumption: the signature slot refuses every non-ECDSA algorithm
2150
- // before this runs, and every curve the subjectPublicKey slot accepts (prime256v1 / secp384r1 /
2151
- // secp521r1) carries an ecPublicKey registry row. Adding a curve without its row would reach it.
2152
1577
  function _encAlgorithm(alg, toInt, key) {
2153
1578
  var i = toInt[key];
2154
1579
  if (i !== undefined && !(alg.parameters && alg.parameters.length)) return cbor.build.int(BigInt(i));
2155
- var oidBytes = cbor.build.byteString(asn1.encodeOidContent(alg.oid)); // ~oid: bare BER OID content
1580
+ var oidBytes = cbor.build.byteString(asn1.encodeOidContent(alg.oid));
2156
1581
  if (alg.parameters && alg.parameters.length) return cbor.build.array([oidBytes, cbor.build.byteString(alg.parameters)]);
2157
1582
  return oidBytes;
2158
1583
  }
2159
- // A SpecialText attribute value -> CBOR, walking the sec. 3.1.4 cascade: an EUI-64 is a tag-48 MAC
2160
- // address, a text string of even length >= 2 drawn only from '0'-'9'/'a'-'f' is a byte string, and
2161
- // anything else is a text string. The decoder holds an incoming certificate to exactly this cascade,
2162
- // so the encoder walks it too -- a spelling chosen here that the decoder refuses would make this
2163
- // encoder emit certificates its own parser rejects.
2164
1584
  function _encSpecialText(rdn) {
2165
1585
  if (rdn.eui64) return cbor.build.tag(48, cbor.build.byteString(rdn.eui64));
2166
1586
  var v = String(rdn.value);
2167
1587
  if (v.length >= 2 && _isEvenHexLower(v)) return cbor.build.byteString(Buffer.from(v, "hex"));
2168
1588
  return cbor.build.textString(v);
2169
1589
  }
2170
- // A Name -> CBOR: null (issuer only) | a bare SpecialText single utf8 commonName | an array of RDN pairs.
2171
1590
  function _encName(name, isSubject) {
2172
1591
  if (name === null || name === undefined) {
2173
- if (!isSubject) return cbor.build.nullValue(); // issuer == subject (self-signed)
1592
+ if (!isSubject) return cbor.build.nullValue();
2174
1593
  throw _err("c509/bad-name", "the subject Name is required");
2175
1594
  }
2176
1595
  var rdns = name.rdns || [];
@@ -2179,12 +1598,11 @@ function _encName(name, isSubject) {
2179
1598
  rdns.forEach(function (rdn) {
2180
1599
  var ai = ATTR_TO_INT[rdn.type];
2181
1600
  if (ai === undefined) throw _err("c509/bad-name", "attribute type " + rdn.type + " has no C509 registry int");
2182
- items.push(cbor.build.int(BigInt(rdn.printable ? -ai : ai))); // sign selects printableString
1601
+ items.push(cbor.build.int(BigInt(rdn.printable ? -ai : ai)));
2183
1602
  items.push(_encSpecialText(rdn));
2184
1603
  });
2185
1604
  return cbor.build.array(items);
2186
1605
  }
2187
- // subjectPublicKey -> CBOR: EC point byte string, or an RSA ~biguint modulus ([modulus, exponent] when e != 65537).
2188
1606
  function _encSpk(r) {
2189
1607
  if (r.rsaPublicKey) {
2190
1608
  var mod = cbor.build.byteString(_minBytes(r.rsaPublicKey.modulus));
@@ -2194,7 +1612,6 @@ function _encSpk(r) {
2194
1612
  if (!Buffer.isBuffer(r.subjectPublicKey)) throw _err("c509/bad-spki", "the subjectPublicKey bytes are missing");
2195
1613
  return cbor.build.byteString(r.subjectPublicKey);
2196
1614
  }
2197
- // extensions -> CBOR: the keyUsage int-shortcut (a lone keyUsage), else an array of [extID, extValue] pairs.
2198
1615
  function _encExtensions(exts) {
2199
1616
  if (exts.length === 1 && exts[0].name === "keyUsage" && typeof exts[0].keyUsageBits === "number") {
2200
1617
  return cbor.build.int(BigInt(exts[0].critical ? -exts[0].keyUsageBits : exts[0].keyUsageBits));
@@ -2202,26 +1619,20 @@ function _encExtensions(exts) {
2202
1619
  var items = [];
2203
1620
  exts.forEach(function (ext) {
2204
1621
  var ei = EXT_TO_INT[ext.name];
2205
- // A registered extension with a compact value form emits int extID + the specific CBOR value, but
2206
- // only when that value inverts to the EXACT DER extnValue (the round-trip guard); otherwise it falls
2207
- // through to the conformant ~oid + byte-string form (sec. 3.7) -- never a lossy compact encoding.
2208
1622
  var compact = (ei !== undefined && Buffer.isBuffer(ext.value)) ? _tryCompactExtValue(ext.name, ext.value) : null;
2209
1623
  if (compact != null) {
2210
1624
  items.push(cbor.build.int(BigInt(ext.critical ? -ei : ei)));
2211
1625
  items.push(compact);
2212
1626
  } else {
2213
- items.push(cbor.build.byteString(asn1.encodeOidContent(ext.oid || oid.byName(ext.name)))); // ~oid extension id
1627
+ items.push(cbor.build.byteString(asn1.encodeOidContent(ext.oid || oid.byName(ext.name))));
2214
1628
  if (!Buffer.isBuffer(ext.value)) throw _err("c509/non-invertible", "extension " + (ext.oid || ext.name) + " has no byte-string value to encode");
2215
1629
  var bs = cbor.build.byteString(ext.value);
2216
- items.push(ext.critical ? cbor.build.array([bs]) : bs); // critical ~oid value wraps in a 1-element array
1630
+ items.push(ext.critical ? cbor.build.array([bs]) : bs);
2217
1631
  }
2218
1632
  });
2219
1633
  return cbor.build.array(items);
2220
1634
  }
2221
- // forward-declared below; the DER X.509 -> type-3 C509 structured result.
2222
1635
  var _derToType3;
2223
- // A hand-built (no _fieldBytes) result must carry the structured fields the encode below reads; a missing or
2224
- // wrong-typed field fails closed with a typed verdict rather than a raw property-access crash.
2225
1636
  function _requireResultShape(r) {
2226
1637
  if (r.certificateType == null) throw _err("c509/bad-input", "a C509 result must carry certificateType");
2227
1638
  if (r.serialNumber == null && r.serialNumberHex == null) throw _err("c509/bad-input", "a C509 result must carry serialNumber or serialNumberHex");
@@ -2231,24 +1642,16 @@ function _requireResultShape(r) {
2231
1642
  if (!Array.isArray(r.extensions)) throw _err("c509/bad-input", "a C509 result must carry an extensions array");
2232
1643
  if (!Buffer.isBuffer(r.signatureValue)) throw _err("c509/bad-input", "a C509 result must carry a Buffer signatureValue");
2233
1644
  }
2234
- // A validity Date -> its C509 ~time (a non-negative CBOR epoch uint). A pre-epoch date cannot be
2235
- // represented (the parser accepts only an unwrapped major-type-0 integer) and fails closed here.
2236
1645
  function _validityUint(date, label) {
2237
1646
  var secs = Math.floor(guard.time.instantOf(date) / 1000);
2238
1647
  if (!isFinite(secs) || secs < 0) throw _err("c509/bad-validity", label + " is before the Unix epoch or not a valid date; C509 ~time is a non-negative CBOR epoch");
2239
1648
  return cbor.build.uint(BigInt(secs));
2240
1649
  }
2241
- // A structured C509 result -> the 11-element deterministic-CBOR array.
2242
1650
  function _encodeC509Array(r) {
2243
- // Re-emit a PARSED certificate's raw fields (elements 0..9) VERBATIM -- re-deriving from the decoded
2244
- // values could differ on a canonical-equivalent form (a byte-string attribute value, a registry alias)
2245
- // and break a type-2 native signature (which covers these bytes) or a type-3 DER reconstruction (which
2246
- // depends on the field values). Both types preserve the exact bytes; only a hand-built result (no
2247
- // _fieldBytes) re-derives from the structured values below.
2248
1651
  if (Buffer.isBuffer(r._fieldBytes)) {
2249
1652
  if (!Buffer.isBuffer(r.signatureValue)) throw _err("c509/bad-input", "a re-emitted certificate must carry a Buffer signatureValue");
2250
- var out = Buffer.concat([Buffer.from([0x8b]), r._fieldBytes, cbor.build.byteString(r.signatureValue)]); // array(11) head + fields 0..9 + signatureValue
2251
- parse(out); // fail closed: a caller-mutated _fieldBytes must still re-parse as a valid C509, else parse throws a typed c509/* verdict
1653
+ var out = Buffer.concat([Buffer.from([0x8b]), r._fieldBytes, cbor.build.byteString(r.signatureValue)]);
1654
+ parse(out);
2252
1655
  return out;
2253
1656
  }
2254
1657
  _requireResultShape(r);
@@ -2266,7 +1669,7 @@ function _encodeC509Array(r) {
2266
1669
  _encExtensions(r.extensions),
2267
1670
  cbor.build.byteString(r.signatureValue),
2268
1671
  ]);
2269
- parse(arr); // fail closed: a hand-built result must re-parse as a valid C509 (as the verbatim path checks), else parse throws a typed c509/* verdict
1672
+ parse(arr);
2270
1673
  return arr;
2271
1674
  }
2272
1675
 
@@ -2310,11 +1713,6 @@ function _encodeC509Array(r) {
2310
1713
  * var cbor = pki.schema.c509.encode(signerCertDer); // a DER cert -> a compact type-3 C509
2311
1714
  * pki.schema.c509.parse(cbor).certificateType; // 3
2312
1715
  */
2313
- // A commonName string that is a MAC / EUI address ("HH-HH-HH-FF-FE-HH-HH-HH") -> the C509 tag-48 byte
2314
- // value (draft-20 sec. 3.2.3): an FF-FE-in-the-middle EUI-64 collapses to its 6-byte EUI-48, else the
2315
- // 8-byte EUI-64 verbatim. A non-MAC commonName returns null (encoded as text). The exact inverse of
2316
- // _macToEui64String, so the reconstruction rebuilds the identical DER commonName string.
2317
- // The "-" characters removed from a string, by character scan (no regex replace protocol).
2318
1716
  function _stripHyphens(s) {
2319
1717
  var out = "";
2320
1718
  for (var i = 0; i < s.length; i++) { var ch = s.charAt(i); if (ch !== "-") out += ch; }
@@ -2326,8 +1724,6 @@ function _euiFromCn(value) {
2326
1724
  if (bytes[3] === 0xff && bytes[4] === 0xfe) return Buffer.concat([bytes.subarray(0, 3), bytes.subarray(5)]);
2327
1725
  return bytes;
2328
1726
  }
2329
- // A DER Name -> the C509 structured rdns, decoding each attribute's string type (PrintableString ->
2330
- // printable, UTF8String -> utf8; the C509 int sign carries this). Single-attribute RDNs only (v1).
2331
1727
  function _c509NameFromDer(nameBytes) {
2332
1728
  var node = asn1.decode(nameBytes);
2333
1729
  var rdns = [];
@@ -2337,47 +1733,35 @@ function _c509NameFromDer(nameBytes) {
2337
1733
  var attrName = oid.name(asn1.read.oid(attr.children[0]));
2338
1734
  if (attrName == null || ATTR_TO_INT[attrName] === undefined) throw _err("c509/non-invertible", "attribute type " + attrName + " has no C509 registry integer");
2339
1735
  var valNode = attr.children[1];
2340
- // A value whose string type this codec cannot represent (NumericString and the other non-DirectoryString
2341
- // types read.string declines) is NOT compact-representable -- report that in THIS module's domain rather
2342
- // than leaking the codec's own asn1/* fault out of encode(), which is what every sibling shape does.
2343
1736
  var value;
2344
1737
  try { value = asn1.read.string(valNode); }
2345
1738
  catch (e) { throw _err("c509/non-invertible", "attribute " + attrName + " carries a value whose string type the C509 sec. 8.6 int form cannot represent", e); }
2346
- // The IA5String type belongs ONLY to an IA5-only attribute (emailAddress, draft sec. 3.1.4), whose value
2347
- // reconstructs from its type rather than the int's sign. Refuse either mismatch here with a precise verdict
2348
- // instead of emitting an int form whose reconstruction would differ from the source bytes.
2349
1739
  var isIa5 = valNode.tagClass === "universal" && valNode.tagNumber === asn1.TAGS.IA5_STRING;
2350
1740
  if ((attrName === "emailAddress") !== isIa5) throw _err("c509/non-invertible", "attribute " + attrName + " carries a " + (isIa5 ? "IA5String" : "non-IA5String") + " value the C509 sec. 8.6 int form cannot represent");
2351
1741
  var eui = attrName === "commonName" ? _euiFromCn(value) : null;
2352
- if (eui) rdns.push({ type: attrName, value: value, eui64: eui }); // tag-48 MAC commonName
1742
+ if (eui) rdns.push({ type: attrName, value: value, eui64: eui });
2353
1743
  else rdns.push({ type: attrName, value: value, printable: valNode.tagNumber === asn1.TAGS.PRINTABLE_STRING });
2354
1744
  });
2355
1745
  return { rdns: rdns };
2356
1746
  }
2357
- // A keyUsage extnValue (a DER KeyUsage BIT STRING) -> the C509 integer whose bit i is the named bit i.
2358
- // Composes the shared named-bit reader (also used by the cRLDistributionPoints reasons transform).
2359
1747
  function _keyUsageBitsFromDer(extnValue) {
2360
1748
  var bs;
2361
1749
  try {
2362
1750
  bs = asn1.read.bitString(asn1.decode(extnValue));
2363
1751
  } catch (_e) {
2364
- return null; // a malformed keyUsage BIT STRING cannot take the int shortcut -> encode it as a raw extension
1752
+ return null;
2365
1753
  }
2366
1754
  return _namedBitsFromContent(bs.bytes, bs.unusedBits);
2367
1755
  }
2368
1756
  var _NO_EXPIRY = Date.UTC(9999, 11, 31, 23, 59, 59);
2369
- // The C509 (OID/node) curve names <-> the WebCrypto namedCurve the P1363 converter expects, and the
2370
- // RFC 5480 standard digest<->curve pairing an ECDSA signature algorithm implies.
2371
1757
  var NODE_TO_WEBCRYPTO = { "prime256v1": "P-256", "secp384r1": "P-384", "secp521r1": "P-521" };
2372
1758
  var WEBCRYPTO_FIELD_BYTES = { "P-256": 32, "P-384": 48, "P-521": 66 };
2373
1759
  var SIG_ALG_TO_CURVE = { "ecdsaWithSHA256": "P-256", "ecdsaWithSHA384": "P-384", "ecdsaWithSHA512": "P-521" };
2374
- // A DER INTEGER magnitude byte length: the content past any leading sign/pad octet.
2375
1760
  function _magBytes(intNode) {
2376
1761
  var c = intNode.content, i = 0;
2377
1762
  while (i < c.length - 1 && c[i] === 0x00) i++;
2378
1763
  return c.length - i;
2379
1764
  }
2380
- // The max r/s magnitude width of a DER ECDSA signature (validating its two-INTEGER shape).
2381
1765
  function _sigMagWidth(derSig) {
2382
1766
  var n;
2383
1767
  try { n = asn1.decode(derSig); } catch (e) { throw _err("c509/bad-signature", "the ECDSA issuer signature is not valid DER", e); }
@@ -2388,21 +1772,11 @@ function _sigMagWidth(derSig) {
2388
1772
  }
2389
1773
  return Math.max(_magBytes(n.children[0]), _magBytes(n.children[1]));
2390
1774
  }
2391
- var CURVE_ORDER = ["P-256", "P-384", "P-521"]; // ascending field size
2392
- // The smallest supported curve whose field holds a magnitude, or null if none does.
1775
+ var CURVE_ORDER = ["P-256", "P-384", "P-521"];
2393
1776
  function _minCurveForMag(mag) {
2394
1777
  for (var i = 0; i < CURVE_ORDER.length; i++) { if (mag <= WEBCRYPTO_FIELD_BYTES[CURVE_ORDER[i]]) return CURVE_ORDER[i]; }
2395
1778
  return null;
2396
1779
  }
2397
- // The ISSUER signature's fixed-width r||s is sized by the ISSUER's signing curve. The DER ECDSA signature
2398
- // does NOT carry the curve and the r/s magnitudes are only a lower bound (a P-384 signature with small r/s
2399
- // is byte-indistinguishable from a P-256 one), so the curve is resolved from an AUTHORITATIVE source, never
2400
- // a guess: (1) an explicit opts.issuerCurve, or (2) the RFC 5480 standard digest<->curve pairing the
2401
- // signature algorithm implies -- and ONLY when that pairing is the smallest curve the magnitude admits.
2402
- // Matching issuer/subject Names are NOT treated as proof of self-signing (a self-issued certificate may be
2403
- // cross-signed by a different key on a different curve). A signature whose r/s exceed the digest's standard
2404
- // curve (a larger key) OR whose magnitude also fits a SMALLER curve (a smaller key signing with a larger
2405
- // digest) leaves the curve undetermined: it fails closed and directs the caller to opts.issuerCurve.
2406
1780
  function _resolveIssuerSigCurve(c, opts) {
2407
1781
  var mag = _sigMagWidth(c.signatureValue.bytes), curve;
2408
1782
  if (opts && opts.issuerCurve != null) {
@@ -2420,27 +1794,15 @@ function _resolveIssuerSigCurve(c, opts) {
2420
1794
  if (mag > WEBCRYPTO_FIELD_BYTES[curve]) throw _err("c509/non-invertible", "the ECDSA signature r/s width " + mag + " does not fit the resolved " + curve + " field");
2421
1795
  return curve;
2422
1796
  }
2423
- // Compress an uncompressed SEC1 EC point (0x04||X||Y) to the C509 marker form (draft-20 sec. 3.2.2): the
2424
- // sign-of-Y marker 0xFE (Y even) / 0xFD (Y odd) followed by X. The inverse of webcrypto.decompressEcPoint,
2425
- // so the type-3 reconstruction recovers the exact original point. A non-0x04 point is kept verbatim.
2426
1797
  function _compressEcPoint(point, coordLen) {
2427
1798
  if (!point.length || point[0] !== 0x04) return point;
2428
1799
  if (point.length !== 1 + 2 * coordLen) throw _err("c509/non-invertible", "uncompressed EC point length " + point.length + " does not match the curve field size");
2429
1800
  var x = point.subarray(1, 1 + coordLen), y = point.subarray(1 + coordLen);
2430
1801
  return Buffer.concat([Buffer.from([(y[y.length - 1] & 1) ? 0xfd : 0xfe]), x]);
2431
1802
  }
2432
- // A DER X.509 v3 certificate -> the type-3 C509 structured result (the inverse of _reconstructDer). Only
2433
- // the reconstruction's covered set is invertible; encode() self-verifies the byte-exact round trip.
2434
1803
  _derToType3 = function (input, opts) {
2435
1804
  var c;
2436
1805
  try { c = x509.parse(input); } catch (e) { throw _err("c509/bad-input", "the input is not a valid X.509 certificate", e); }
2437
- // Both C509 certificate types are defined over X.509 v3 (draft-ietf-cose-cbor-encoded-cert
2438
- // sec. 1), and the encoding carries no version field -- reconstruction always emits v3. So a v1
2439
- // or v2 certificate is outside the format, not a codec limitation, and saying so here keeps it
2440
- // from falling through to the byte-compare below, whose "does not reconstruct byte-for-byte"
2441
- // reads as a defect in this encoder rather than a certificate the format does not cover.
2442
- // (A v3 certificate with the extensions field OMITTED is fully supported: sec. 3.1.10 encodes
2443
- // an omitted 'extensions' field as an empty CBOR array.)
2444
1806
  if (c.version !== 3) throw _err("c509/non-invertible", "C509 covers X.509 v3 certificates; got v" + c.version);
2445
1807
  if (String(c.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (c.signatureAlgorithm.name || "an unregistered algorithm"));
2446
1808
  if (c.subjectPublicKeyInfo.algorithm.name !== "ecPublicKey") throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
@@ -2448,27 +1810,21 @@ _derToType3 = function (input, opts) {
2448
1810
  var curve = oid.name(curveOid);
2449
1811
  var coordLen = EC_FIELD_BYTES[curve];
2450
1812
  if (!coordLen) throw _err("c509/non-invertible", "unsupported EC subject curve " + (curve || curveOid));
2451
- var sigCurve = _resolveIssuerSigCurve(c, opts); // the ISSUER signing curve, resolved authoritatively (opts / digest pairing)
1813
+ var sigCurve = _resolveIssuerSigCurve(c, opts);
2452
1814
  var spkiNode = asn1.decode(c.subjectPublicKeyInfo.bytes);
2453
1815
  return {
2454
1816
  certificateType: 3,
2455
- serialNumber: c.serialNumber, // no serialNumberHex -> the encoder uses the minimal ~biguint magnitude
1817
+ serialNumber: c.serialNumber,
2456
1818
  signatureAlgorithm: { name: c.signatureAlgorithm.name, oid: c.signatureAlgorithm.oid },
2457
- // sec. 3.1.4: "If the 'issuer' field is identical to the 'subject' field, e.g., in case of
2458
- // self-signed certificates, then the 'issuer' field MUST be encoded as the CBOR simple value
2459
- // null." Compared on the RAW DER bytes, not on a canonicalized name: the reconstruction rebuilds
2460
- // a null issuer FROM the subject, so two names that merely compare equal (a PrintableString
2461
- // against a UTF8String of the same characters) would rebuild different bytes and break the
2462
- // signature that covers them. Byte-identical is exactly the condition under which that is safe.
2463
1819
  issuer: c.issuer.bytes.equals(c.subject.bytes) ? null : _c509NameFromDer(c.issuer.bytes),
2464
1820
  validity: { notBefore: c.validity.notBefore, notAfter: guard.time.instantOf(c.validity.notAfter) === _NO_EXPIRY ? null : c.validity.notAfter },
2465
1821
  subject: _c509NameFromDer(c.subject.bytes),
2466
1822
  subjectPublicKeyAlgorithm: { name: "ecPublicKey", oid: c.subjectPublicKeyInfo.algorithm.oid, curve: curve },
2467
- subjectPublicKey: _compressEcPoint(asn1.read.bitString(spkiNode.children[1]).bytes, coordLen), // 0x04||X||Y -> C509 compressed marker
1823
+ subjectPublicKey: _compressEcPoint(asn1.read.bitString(spkiNode.children[1]).bytes, coordLen),
2468
1824
  rsaPublicKey: null,
2469
1825
  extensions: (c.extensions || []).map(function (e) {
2470
1826
  var ext = { name: e.name, oid: e.oid, critical: !!e.critical, value: e.value };
2471
- if (e.name === "keyUsage") { var bits = _keyUsageBitsFromDer(e.value); if (bits != null) ext.keyUsageBits = bits; } // enable the int-shortcut
1827
+ if (e.name === "keyUsage") { var bits = _keyUsageBitsFromDer(e.value); if (bits != null) ext.keyUsageBits = bits; }
2472
1828
  return ext;
2473
1829
  }),
2474
1830
  signatureValue: validator.sig.ecdsaDerToP1363(c.signatureValue.bytes, sigCurve, C509Error, "c509/bad-signature"),
@@ -2477,16 +1833,11 @@ _derToType3 = function (input, opts) {
2477
1833
 
2478
1834
  function encode(input, opts) {
2479
1835
  if (input && typeof input === "object" && !Buffer.isBuffer(input) && input.certificateType != null) {
2480
- return _encodeC509Array(input); // a parse result -> re-emit its native array
1836
+ return _encodeC509Array(input);
2481
1837
  }
2482
1838
  if (!Buffer.isBuffer(input) && typeof input !== "string") throw _err("c509/bad-input", "encode input must be a DER/PEM X.509 certificate or a c509.parse result");
2483
- // Normalize input to DER through the SAME shared coercion x509.parse uses (a PEM string, a PEM-armored
2484
- // Buffer, or raw DER all collapse to the certificate DER), so the self-verify compares against exactly the
2485
- // bytes _derToType3 processed -- never a Buffer of PEM text.
2486
1839
  var origDer = pkix.coerceToDer(input, { pemLabel: "CERTIFICATE", PemError: frameworkError.PemError, ErrorClass: C509Error, prefix: "c509" });
2487
1840
  var encoded = _encodeC509Array(_derToType3(origDer, opts));
2488
- // The type-3 transform MUST invert back to the original DER byte-for-byte (so the original signature
2489
- // verifies) -- self-verify it, failing closed on any edge the reconstruction cannot reproduce.
2490
1841
  var recon = parse(encoded).reconstructedDer;
2491
1842
  if (!recon || Buffer.compare(recon, origDer) !== 0) throw _err("c509/non-invertible", "the type-3 C509 does not reconstruct the source certificate byte-for-byte");
2492
1843
  return encoded;