tunnelfetch 1.0.0

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 (96) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +617 -0
  3. package/README.zh-CN.md +470 -0
  4. package/package.json +74 -0
  5. package/src/client/cookies.js +429 -0
  6. package/src/client/decode.js +346 -0
  7. package/src/client/redirect.js +249 -0
  8. package/src/client.js +704 -0
  9. package/src/errors.js +181 -0
  10. package/src/http1/chunked.js +289 -0
  11. package/src/http1/index.js +10 -0
  12. package/src/http1/request.js +143 -0
  13. package/src/http1/response.js +493 -0
  14. package/src/http2/connection.js +1170 -0
  15. package/src/http2/constants.js +129 -0
  16. package/src/http2/frames.js +291 -0
  17. package/src/http2/hpack.js +420 -0
  18. package/src/http2/huffman.js +203 -0
  19. package/src/http2/index.js +21 -0
  20. package/src/index.js +46 -0
  21. package/src/pool.js +256 -0
  22. package/src/proxy/direct.js +62 -0
  23. package/src/proxy/http-connect.js +206 -0
  24. package/src/proxy/index.js +197 -0
  25. package/src/proxy/socks5.js +344 -0
  26. package/src/tls/aead.js +263 -0
  27. package/src/tls/connect.js +407 -0
  28. package/src/tls/constants.js +334 -0
  29. package/src/tls/extensions.js +376 -0
  30. package/src/tls/handshake-messages.js +901 -0
  31. package/src/tls/handshake.js +568 -0
  32. package/src/tls/handshake12.js +507 -0
  33. package/src/tls/index.js +44 -0
  34. package/src/tls/keyschedule.js +473 -0
  35. package/src/tls/record.js +872 -0
  36. package/src/tls/tickets.js +145 -0
  37. package/src/tls/transcript.js +101 -0
  38. package/src/tls/wire.js +224 -0
  39. package/src/transport.js +296 -0
  40. package/src/trust/der.js +551 -0
  41. package/src/trust/index.js +375 -0
  42. package/src/trust/name.js +235 -0
  43. package/src/trust/ocsp.js +759 -0
  44. package/src/trust/path.js +595 -0
  45. package/src/trust/roots.js +454 -0
  46. package/src/trust/x509.js +902 -0
  47. package/src/util/bytes.js +470 -0
  48. package/src/util/deadline.js +266 -0
  49. package/src/warmup-fixture.js +85 -0
  50. package/src/warmup.js +243 -0
  51. package/types/client/cookies.d.ts +159 -0
  52. package/types/client/decode.d.ts +54 -0
  53. package/types/client/redirect.d.ts +96 -0
  54. package/types/client.d.ts +323 -0
  55. package/types/errors.d.ts +141 -0
  56. package/types/http1/chunked.d.ts +48 -0
  57. package/types/http1/index.d.ts +3 -0
  58. package/types/http1/request.d.ts +44 -0
  59. package/types/http1/response.d.ts +183 -0
  60. package/types/http2/connection.d.ts +282 -0
  61. package/types/http2/constants.d.ts +95 -0
  62. package/types/http2/frames.d.ts +116 -0
  63. package/types/http2/hpack.d.ts +99 -0
  64. package/types/http2/huffman.d.ts +21 -0
  65. package/types/http2/index.d.ts +5 -0
  66. package/types/index.d.ts +17 -0
  67. package/types/pool.d.ts +135 -0
  68. package/types/proxy/direct.d.ts +26 -0
  69. package/types/proxy/http-connect.d.ts +37 -0
  70. package/types/proxy/index.d.ts +62 -0
  71. package/types/proxy/socks5.d.ts +47 -0
  72. package/types/tls/aead.d.ts +67 -0
  73. package/types/tls/connect.d.ts +280 -0
  74. package/types/tls/constants.d.ts +275 -0
  75. package/types/tls/extensions.d.ts +195 -0
  76. package/types/tls/handshake-messages.d.ts +430 -0
  77. package/types/tls/handshake.d.ts +90 -0
  78. package/types/tls/handshake12.d.ts +35 -0
  79. package/types/tls/index.d.ts +9 -0
  80. package/types/tls/keyschedule.d.ts +272 -0
  81. package/types/tls/record.d.ts +361 -0
  82. package/types/tls/tickets.d.ts +66 -0
  83. package/types/tls/transcript.d.ts +52 -0
  84. package/types/tls/wire.d.ts +106 -0
  85. package/types/transport.d.ts +222 -0
  86. package/types/trust/der.d.ts +239 -0
  87. package/types/trust/index.d.ts +194 -0
  88. package/types/trust/name.d.ts +33 -0
  89. package/types/trust/ocsp.d.ts +138 -0
  90. package/types/trust/path.d.ts +139 -0
  91. package/types/trust/roots.d.ts +36 -0
  92. package/types/trust/x509.d.ts +401 -0
  93. package/types/util/bytes.d.ts +183 -0
  94. package/types/util/deadline.d.ts +133 -0
  95. package/types/warmup-fixture.d.ts +11 -0
  96. package/types/warmup.d.ts +45 -0
@@ -0,0 +1,551 @@
1
+ // ASN.1 DER reader.
2
+ //
3
+ // This feeds signature verification, so exactness is the whole point: every element carries the
4
+ // byte range of its full encoding AND of its content, and callers verify signatures over slices
5
+ // of the ORIGINAL buffer. Nothing here re-encodes, ever — a re-encoder that "fixes" anything is a
6
+ // forgery laundromat.
7
+ //
8
+ // DER, not BER. BER's flexibilities (indefinite lengths, non-minimal lengths, constructed
9
+ // strings) are exactly the degrees of freedom historical certificate forgeries lived in, so each
10
+ // one is rejected by name rather than tolerated. Every rejection says where and why, because a
11
+ // parse failure in a certificate is either a broken CA or an attack, and both deserve a precise
12
+ // log line.
13
+
14
+ import { CertificateError, codes } from '../errors.js';
15
+
16
+ /** Universal tag numbers we name in errors and match on. */
17
+ export const TAG = {
18
+ BOOLEAN: 1,
19
+ INTEGER: 2,
20
+ BIT_STRING: 3,
21
+ OCTET_STRING: 4,
22
+ NULL: 5,
23
+ OID: 6,
24
+ ENUMERATED: 10,
25
+ UTF8_STRING: 12,
26
+ SEQUENCE: 16,
27
+ SET: 17,
28
+ NUMERIC_STRING: 18,
29
+ PRINTABLE_STRING: 19,
30
+ TELETEX_STRING: 20,
31
+ IA5_STRING: 22,
32
+ UTC_TIME: 23,
33
+ GENERALIZED_TIME: 24,
34
+ VISIBLE_STRING: 26,
35
+ BMP_STRING: 30,
36
+ };
37
+
38
+ export const CLS = { UNIVERSAL: 0, APPLICATION: 1, CONTEXT: 2, PRIVATE: 3 };
39
+
40
+ const TAG_NAME = Object.fromEntries(Object.entries(TAG).map(([k, v]) => [v, k]));
41
+
42
+ /**
43
+ * One decoded tag-length-value element, as byte ranges into the ORIGINAL buffer. Every reader
44
+ * in the trust layer passes these around instead of slices precisely so that signature checks
45
+ * always run over the peer's own bytes.
46
+ * @typedef {object} Tlv
47
+ * @property {number} cls tag class, per {@link CLS}
48
+ * @property {boolean} constructed
49
+ * @property {number} tag tag number, high-tag-number form already decoded
50
+ * @property {number} start offset of the first header byte
51
+ * @property {number} headerLen tag + length octets
52
+ * @property {number} contentStart
53
+ * @property {number} contentEnd
54
+ * @property {number} end one past the element; equals contentEnd for every legal DER element
55
+ */
56
+
57
+ /**
58
+ * Describe a tag for error messages: "SEQUENCE", "[0]", "APPLICATION 3".
59
+ * @param {number} cls
60
+ * @param {number} tag
61
+ * @returns {string}
62
+ */
63
+ export function tagName(cls, tag) {
64
+ if (cls === CLS.UNIVERSAL) return TAG_NAME[tag] ?? `UNIVERSAL ${tag}`;
65
+ if (cls === CLS.CONTEXT) return `[${tag}]`;
66
+ return `${cls === CLS.APPLICATION ? 'APPLICATION' : 'PRIVATE'} ${tag}`;
67
+ }
68
+
69
+ /**
70
+ * @param {number} offset
71
+ * @param {string} message
72
+ * @returns {CertificateError}
73
+ */
74
+ export function parseError(offset, message) {
75
+ return new CertificateError(codes.CERT_PARSE, `DER at offset ${offset}: ${message}`, { offset });
76
+ }
77
+
78
+ /**
79
+ * Read one TLV starting at `offset`. Returns byte ranges only — content is always a subarray of
80
+ * the caller's original buffer, never a copy, so signatures can be verified over the same bytes
81
+ * the peer sent.
82
+ *
83
+ * @param {Uint8Array} bytes
84
+ * @param {number} offset
85
+ * @returns {Tlv}
86
+ */
87
+ export function readTlv(bytes, offset) {
88
+ const len = bytes.byteLength;
89
+ if (offset >= len) throw parseError(offset, `element expected but buffer ends at ${len}`);
90
+ const first = bytes[offset];
91
+ const cls = first >> 6;
92
+ const constructed = (first & 0x20) !== 0;
93
+ let tag = first & 0x1f;
94
+ let p = offset + 1;
95
+ if (tag === 0x1f) {
96
+ // High tag number form: base-128, minimal, terminated by a byte without the high bit.
97
+ tag = 0;
98
+ if (p < len && bytes[p] === 0x80) {
99
+ throw parseError(p, 'non-minimal multi-byte tag number (leading 0x80)');
100
+ }
101
+ for (let i = 0; ; i++) {
102
+ if (p >= len) throw parseError(p, 'truncated multi-byte tag number');
103
+ if (i >= 4) throw parseError(p, 'tag number wider than 28 bits, refusing');
104
+ const b = bytes[p++];
105
+ tag = tag * 128 + (b & 0x7f);
106
+ if ((b & 0x80) === 0) break;
107
+ }
108
+ if (tag < 0x1f) throw parseError(offset, `tag ${tag} used high-tag-number form unnecessarily`);
109
+ }
110
+ if (p >= len) throw parseError(p, `length byte expected but buffer ends at ${len}`);
111
+ const l0 = bytes[p++];
112
+ let contentLen;
113
+ if (l0 < 0x80) {
114
+ contentLen = l0;
115
+ } else if (l0 === 0x80) {
116
+ // Indefinite length is BER-only. In DER it is illegal, and accepting it would let an attacker
117
+ // move element boundaries around — the classic constructed-string forgery.
118
+ throw parseError(p - 1, 'indefinite length is not allowed in DER');
119
+ } else if (l0 === 0xff) {
120
+ throw parseError(p - 1, 'reserved length byte 0xff');
121
+ } else {
122
+ const n = l0 & 0x7f;
123
+ if (n > 4) throw parseError(p - 1, `length of length ${n} bytes, refusing (> 4)`);
124
+ if (p + n > len) throw parseError(p, 'truncated long-form length');
125
+ if (bytes[p] === 0x00) {
126
+ throw parseError(p, 'non-minimal length encoding (leading zero length byte)');
127
+ }
128
+ contentLen = 0;
129
+ for (let i = 0; i < n; i++) contentLen = contentLen * 256 + bytes[p + i];
130
+ p += n;
131
+ if (contentLen < 0x80) {
132
+ throw parseError(offset, `non-minimal length encoding (long form for length ${contentLen})`);
133
+ }
134
+ }
135
+ const contentStart = p;
136
+ const contentEnd = contentStart + contentLen;
137
+ if (contentEnd > len) {
138
+ throw parseError(
139
+ offset,
140
+ `${tagName(cls, tag)} content of ${contentLen} bytes runs past end of buffer ` +
141
+ `(${contentEnd} > ${len})`,
142
+ );
143
+ }
144
+ return {
145
+ cls,
146
+ constructed,
147
+ tag,
148
+ start: offset,
149
+ headerLen: contentStart - offset,
150
+ contentStart,
151
+ contentEnd,
152
+ end: contentEnd,
153
+ };
154
+ }
155
+
156
+ /** The content bytes of a TLV, as a subarray of the original buffer. */
157
+ /** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
158
+ export const content = (bytes, tlv) => bytes.subarray(tlv.contentStart, tlv.contentEnd);
159
+ /** The full element (tag + length + content), as a subarray of the original buffer. */
160
+ /** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
161
+ export const element = (bytes, tlv) => bytes.subarray(tlv.start, tlv.end);
162
+
163
+ /**
164
+ * Assert a TLV has the given shape, with an error naming expected vs got at the offset.
165
+ * @param {Tlv} tlv
166
+ * @param {{ cls?: number, tag: number, constructed?: boolean }} shape omitted `constructed`
167
+ * accepts either form
168
+ * @param {string} what
169
+ * @returns {Tlv} the same tlv, for chaining into a reader
170
+ */
171
+ export function expectTlv(tlv, { cls = CLS.UNIVERSAL, tag, constructed }, what) {
172
+ const wrongShape = constructed !== undefined && tlv.constructed !== constructed;
173
+ if (tlv.cls !== cls || tlv.tag !== tag || wrongShape) {
174
+ const wanted = constructed === undefined ? '' : constructed ? ' (constructed)' : ' (primitive)';
175
+ throw parseError(
176
+ tlv.start,
177
+ `expected ${tagName(cls, tag)}${wanted} for ${what}, ` +
178
+ `got ${tagName(tlv.cls, tlv.tag)} (${tlv.constructed ? 'constructed' : 'primitive'})`,
179
+ );
180
+ }
181
+ return tlv;
182
+ }
183
+
184
+ /**
185
+ * Read a TLV and require it to be a constructed SEQUENCE.
186
+ * @param {Uint8Array} bytes
187
+ * @param {number} offset
188
+ * @param {string} [what]
189
+ * @returns {Tlv}
190
+ */
191
+ export function readSequence(bytes, offset, what = 'SEQUENCE') {
192
+ return expectTlv(readTlv(bytes, offset), { tag: TAG.SEQUENCE, constructed: true }, what);
193
+ }
194
+
195
+ /**
196
+ * Parse the content of a constructed element into its immediate children, requiring them to fill
197
+ * the content exactly. Trailing bytes inside a container are as suspicious as trailing bytes
198
+ * after the certificate.
199
+ * @param {Uint8Array} bytes
200
+ * @param {Tlv} tlv
201
+ * @param {string} [what]
202
+ * @returns {Tlv[]}
203
+ */
204
+ export function children(bytes, tlv, what = tagName(tlv.cls, tlv.tag)) {
205
+ if (!tlv.constructed) {
206
+ throw parseError(tlv.start, `${what} must be constructed to have children`);
207
+ }
208
+ const out = [];
209
+ let p = tlv.contentStart;
210
+ while (p < tlv.contentEnd) {
211
+ const child = readTlv(bytes, p);
212
+ if (child.end > tlv.contentEnd) {
213
+ throw parseError(child.start,
214
+ `child overruns enclosing ${what} (ends ${child.end} > ${tlv.contentEnd})`);
215
+ }
216
+ out.push(child);
217
+ p = child.end;
218
+ }
219
+ return out;
220
+ }
221
+
222
+ /**
223
+ * Read the single top-level element and reject trailing bytes after it.
224
+ * @param {Uint8Array} bytes
225
+ * @param {string} [what]
226
+ * @returns {Tlv}
227
+ */
228
+ export function readAll(bytes, what = 'top-level element') {
229
+ const tlv = readTlv(bytes, 0);
230
+ if (tlv.end !== bytes.byteLength) {
231
+ throw parseError(tlv.end, `${bytes.byteLength - tlv.end} trailing bytes after ${what}`);
232
+ }
233
+ return tlv;
234
+ }
235
+
236
+ /**
237
+ * INTEGER: returns the raw big-endian content and, when it fits a safe JS number, the value.
238
+ * DER minimality: the first nine bits must not be all-zero or all-one, else a shorter encoding
239
+ * exists. Laxness here would let two different byte strings claim the same serial number.
240
+ * @param {Uint8Array} bytes
241
+ * @param {Tlv} tlv
242
+ * @param {string} [what]
243
+ * @returns {{ bytes: Uint8Array, value: number | null, negative: boolean }} `value` is null
244
+ * when the magnitude does not fit a safe number (serials routinely do not)
245
+ */
246
+ export function readInteger(bytes, tlv, what = 'INTEGER') {
247
+ expectTlv(tlv, { tag: TAG.INTEGER, constructed: false }, what);
248
+ const c = content(bytes, tlv);
249
+ if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what}`);
250
+ if (c.byteLength > 1) {
251
+ if (c[0] === 0x00 && c[1] < 0x80) {
252
+ throw parseError(tlv.start, `non-minimal ${what} encoding (leading 0x00)`);
253
+ }
254
+ if (c[0] === 0xff && c[1] >= 0x80) {
255
+ throw parseError(tlv.start, `non-minimal ${what} encoding (leading 0xff)`);
256
+ }
257
+ }
258
+ const negative = (c[0] & 0x80) !== 0;
259
+ let value = null;
260
+ if (!negative && (c.byteLength < 7 || (c.byteLength === 7 && c[0] < 0x20))) {
261
+ value = 0;
262
+ for (const b of c) value = value * 256 + b;
263
+ }
264
+ return { bytes: c, value, negative };
265
+ }
266
+
267
+ /**
268
+ * OBJECT IDENTIFIER to dotted string. Sub-identifiers are base-128 and must be minimal: a leading
269
+ * 0x80 continuation byte is a second spelling of the same OID, and two spellings of one identity
270
+ * is how "unknown critical extension" checks get bypassed.
271
+ * @param {Uint8Array} bytes
272
+ * @param {Tlv} tlv
273
+ * @param {string} [what]
274
+ * @returns {string} dotted form, e.g. '2.5.29.15'
275
+ */
276
+ export function readOid(bytes, tlv, what = 'OBJECT IDENTIFIER') {
277
+ expectTlv(tlv, { tag: TAG.OID, constructed: false }, what);
278
+ const c = content(bytes, tlv);
279
+ if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what}`);
280
+ const arcs = [];
281
+ let value = 0;
282
+ let inSub = false;
283
+ for (let i = 0; i < c.byteLength; i++) {
284
+ const b = c[i];
285
+ if (!inSub && b === 0x80) {
286
+ throw parseError(tlv.contentStart + i, `non-minimal sub-identifier in ${what} (leading 0x80)`);
287
+ }
288
+ inSub = true;
289
+ if (value > 0x3ffffffffffff) {
290
+ throw parseError(tlv.contentStart + i, `sub-identifier in ${what} too large, refusing`);
291
+ }
292
+ value = value * 128 + (b & 0x7f);
293
+ if ((b & 0x80) === 0) {
294
+ arcs.push(value);
295
+ value = 0;
296
+ inSub = false;
297
+ }
298
+ }
299
+ if (inSub) throw parseError(tlv.end, `truncated sub-identifier in ${what}`);
300
+ const first = arcs[0];
301
+ // The first two arcs share one sub-identifier: 40*X+Y, with X capped at 2.
302
+ const x = first < 40 ? 0 : first < 80 ? 1 : 2;
303
+ const y = first - x * 40;
304
+ return [x, y, ...arcs.slice(1)].join('.');
305
+ }
306
+
307
+ /**
308
+ * BIT STRING: unused-bit count + payload. DER additionally requires the unused bits themselves to
309
+ * be zero — a nonzero padding bit is another two-spellings ambiguity.
310
+ * @param {Uint8Array} bytes
311
+ * @param {Tlv} tlv
312
+ * @param {string} [what]
313
+ * @returns {{ unusedBits: number, bytes: Uint8Array }}
314
+ */
315
+ export function readBitString(bytes, tlv, what = 'BIT STRING') {
316
+ expectTlv(tlv, { tag: TAG.BIT_STRING, constructed: false }, what);
317
+ const c = content(bytes, tlv);
318
+ if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what} (missing unused-bits byte)`);
319
+ const unusedBits = c[0];
320
+ if (unusedBits > 7) {
321
+ throw parseError(tlv.contentStart, `${what} unused-bits ${unusedBits} > 7`);
322
+ }
323
+ if (c.byteLength === 1 && unusedBits !== 0) {
324
+ throw parseError(tlv.contentStart, `${what} with no payload must have 0 unused bits`);
325
+ }
326
+ if (unusedBits > 0 && (c[c.byteLength - 1] & ((1 << unusedBits) - 1)) !== 0) {
327
+ throw parseError(tlv.end - 1, `${what} padding bits are not zero`);
328
+ }
329
+ return { unusedBits, bytes: c.subarray(1) };
330
+ }
331
+
332
+ /**
333
+ * BOOLEAN. DER: content is exactly one byte, 0x00 or 0xff.
334
+ * @param {Uint8Array} bytes
335
+ * @param {Tlv} tlv
336
+ * @param {string} [what]
337
+ * @returns {boolean}
338
+ */
339
+ export function readBoolean(bytes, tlv, what = 'BOOLEAN') {
340
+ expectTlv(tlv, { tag: TAG.BOOLEAN, constructed: false }, what);
341
+ const c = content(bytes, tlv);
342
+ if (c.byteLength !== 1) throw parseError(tlv.start, `${what} must be 1 byte, got ${c.byteLength}`);
343
+ if (c[0] !== 0x00 && c[0] !== 0xff) {
344
+ throw parseError(tlv.contentStart, `${what} must be 0x00 or 0xff in DER, got 0x${c[0].toString(16)}`);
345
+ }
346
+ return c[0] === 0xff;
347
+ }
348
+
349
+ const isDigit = (b) => b >= 0x30 && b <= 0x39;
350
+
351
+ function timeError(tlv, kind, text) {
352
+ return parseError(tlv.start, `malformed ${kind} "${text}"`);
353
+ }
354
+
355
+ /** Days per month, with February resolved against the Gregorian leap rule. */
356
+ function daysInMonth(y, m) {
357
+ if (m === 2) return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28;
358
+ return [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
359
+ }
360
+
361
+ function toEpochMs(tlv, kind, text, y, mo, d, h, mi, s) {
362
+ if (mo < 1 || mo > 12) throw timeError(tlv, kind, text);
363
+ if (d < 1 || d > daysInMonth(y, mo)) throw timeError(tlv, kind, text);
364
+ // 60 would be a leap second; Date.UTC silently rolls it into the next minute, which is a
365
+ // different instant than encoded. RFC 5280 profiles do not emit leap seconds, so reject.
366
+ if (h > 23 || mi > 59 || s > 59) throw timeError(tlv, kind, text);
367
+ return Date.UTC(y, mo - 1, d, h, mi, s);
368
+ }
369
+
370
+ /**
371
+ * UTCTime, RFC 5280 profile: exactly YYMMDDHHMMSSZ. Seconds are mandatory, the zone is mandatory
372
+ * and must be Z, and fractional seconds do not exist in this profile. The two-digit year pivots
373
+ * at 50: 50..99 map to 19xx, 00..49 to 20xx (RFC 5280 s4.1.2.5.1).
374
+ * @param {Uint8Array} bytes
375
+ * @param {Tlv} tlv
376
+ * @param {string} [what]
377
+ * @returns {number} epoch ms UTC
378
+ */
379
+ export function readUtcTime(bytes, tlv, what = 'UTCTime') {
380
+ expectTlv(tlv, { tag: TAG.UTC_TIME, constructed: false }, what);
381
+ const c = content(bytes, tlv);
382
+ let text = '';
383
+ for (const b of c) text += String.fromCharCode(b);
384
+ if (c.byteLength !== 13 || c[12] !== 0x5a /* Z */) throw timeError(tlv, what, text);
385
+ for (let i = 0; i < 12; i++) if (!isDigit(c[i])) throw timeError(tlv, what, text);
386
+ const n = (i) => (c[i] - 0x30) * 10 + (c[i + 1] - 0x30);
387
+ const yy = n(0);
388
+ const year = yy >= 50 ? 1900 + yy : 2000 + yy;
389
+ return toEpochMs(tlv, what, text, year, n(2), n(4), n(6), n(8), n(10));
390
+ }
391
+
392
+ /**
393
+ * GeneralizedTime, RFC 5280 profile: exactly YYYYMMDDHHMMSSZ. Fractional seconds are explicitly
394
+ * forbidden by RFC 5280 s4.1.2.5.2, and allowing them would give one instant many encodings.
395
+ * @param {Uint8Array} bytes
396
+ * @param {Tlv} tlv
397
+ * @param {string} [what]
398
+ * @returns {number} epoch ms UTC
399
+ */
400
+ export function readGeneralizedTime(bytes, tlv, what = 'GeneralizedTime') {
401
+ expectTlv(tlv, { tag: TAG.GENERALIZED_TIME, constructed: false }, what);
402
+ const c = content(bytes, tlv);
403
+ let text = '';
404
+ for (const b of c) text += String.fromCharCode(b);
405
+ if (c.byteLength !== 15 || c[14] !== 0x5a /* Z */) throw timeError(tlv, what, text);
406
+ for (let i = 0; i < 14; i++) if (!isDigit(c[i])) throw timeError(tlv, what, text);
407
+ const n = (i) => (c[i] - 0x30) * 10 + (c[i + 1] - 0x30);
408
+ const year = n(0) * 100 + n(2);
409
+ return toEpochMs(tlv, what, text, year, n(4), n(6), n(8), n(10), n(12));
410
+ }
411
+
412
+ /**
413
+ * Either time type, as used by Validity and by name-constraint-free consumers.
414
+ * @param {Uint8Array} bytes
415
+ * @param {Tlv} tlv
416
+ * @param {string} [what]
417
+ * @returns {number} epoch ms UTC
418
+ */
419
+ export function readTime(bytes, tlv, what = 'Time') {
420
+ if (tlv.tag === TAG.UTC_TIME) return readUtcTime(bytes, tlv, what);
421
+ if (tlv.tag === TAG.GENERALIZED_TIME) return readGeneralizedTime(bytes, tlv, what);
422
+ throw parseError(tlv.start,
423
+ `expected UTCTime or GeneralizedTime for ${what}, got ${tagName(tlv.cls, tlv.tag)}`);
424
+ }
425
+
426
+ const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
427
+
428
+ /**
429
+ * Directory string types. Each type's alphabet is enforced — a PrintableString smuggling bytes
430
+ * outside its charset is two parsers disagreeing about one name.
431
+ *
432
+ * TeletexString is decoded as Latin-1: its real charset (T.61) is a negotiation-dependent mess
433
+ * that no CA has honoured in decades, and Latin-1 is the universal de-facto reading.
434
+ * @param {Uint8Array} bytes
435
+ * @param {Tlv} tlv
436
+ * @param {string} [what]
437
+ * @returns {string}
438
+ */
439
+ export function readString(bytes, tlv, what = 'string') {
440
+ const c = content(bytes, tlv);
441
+ const at = (i) => tlv.contentStart + i;
442
+ switch (tlv.tag) {
443
+ case TAG.UTF8_STRING: {
444
+ try {
445
+ return utf8Decoder.decode(c);
446
+ } catch {
447
+ throw parseError(tlv.start, `invalid UTF-8 in ${what}`);
448
+ }
449
+ }
450
+ case TAG.PRINTABLE_STRING: {
451
+ let s = '';
452
+ for (let i = 0; i < c.byteLength; i++) {
453
+ const b = c[i];
454
+ const ok =
455
+ (b >= 0x41 && b <= 0x5a) || (b >= 0x61 && b <= 0x7a) || (b >= 0x30 && b <= 0x39) ||
456
+ b === 0x20 || (b >= 0x27 && b <= 0x2f && b !== 0x2a) || b === 0x3a || b === 0x3d || b === 0x3f;
457
+ if (!ok) {
458
+ throw parseError(at(i), `byte 0x${b.toString(16)} outside PrintableString alphabet in ${what}`);
459
+ }
460
+ s += String.fromCharCode(b);
461
+ }
462
+ return s;
463
+ }
464
+ case TAG.IA5_STRING:
465
+ case TAG.VISIBLE_STRING:
466
+ case TAG.NUMERIC_STRING: {
467
+ let s = '';
468
+ for (let i = 0; i < c.byteLength; i++) {
469
+ const b = c[i];
470
+ if (b > 0x7f) throw parseError(at(i), `non-ASCII byte 0x${b.toString(16)} in ${what}`);
471
+ if (tlv.tag === TAG.NUMERIC_STRING && !(isDigit(b) || b === 0x20)) {
472
+ throw parseError(at(i), `byte 0x${b.toString(16)} outside NumericString alphabet in ${what}`);
473
+ }
474
+ s += String.fromCharCode(b);
475
+ }
476
+ return s;
477
+ }
478
+ case TAG.TELETEX_STRING: {
479
+ let s = '';
480
+ for (const b of c) s += String.fromCharCode(b);
481
+ return s;
482
+ }
483
+ case TAG.BMP_STRING: {
484
+ if (c.byteLength % 2 !== 0) throw parseError(tlv.start, `odd-length BMPString in ${what}`);
485
+ let s = '';
486
+ for (let i = 0; i < c.byteLength; i += 2) {
487
+ const unit = (c[i] << 8) | c[i + 1];
488
+ // BMPString is UCS-2: surrogate code units have no meaning and are rejected rather than
489
+ // passed through where they could re-pair into unexpected characters downstream.
490
+ if (unit >= 0xd800 && unit <= 0xdfff) {
491
+ throw parseError(at(i), `surrogate code unit in BMPString ${what}`);
492
+ }
493
+ s += String.fromCharCode(unit);
494
+ }
495
+ return s;
496
+ }
497
+ default:
498
+ throw parseError(tlv.start, `unsupported string type ${tagName(tlv.cls, tlv.tag)} for ${what}`);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * ECDSA-Sig-Value (SEQUENCE of two INTEGERs) to the fixed-width r||s form WebCrypto verifies.
504
+ *
505
+ * This lives here, once, because both users of it are checking signatures: the certificate path
506
+ * builder and the TLS CertificateVerify. TLS transmits ECDSA signatures in DER while WebCrypto
507
+ * accepts only the P1363 concatenation, and a round-trip test that signs and verifies with
508
+ * WebCrypto agrees with itself while failing against every real server — so this conversion is
509
+ * exactly the sort of thing that must have one implementation and not two.
510
+ *
511
+ * Any malformation is an invalid signature; there is no "close enough" for signature bytes.
512
+ *
513
+ * @param {Uint8Array} sig DER ECDSA-Sig-Value
514
+ * @param {number} orderLen byte width of the curve order (32 / 48 / 66)
515
+ * @param {(why: string) => Error} onInvalid builds the caller's own error type
516
+ * @returns {Uint8Array} r||s, each half left-padded to orderLen
517
+ */
518
+ export function ecdsaDerToRaw(sig, orderLen, onInvalid) {
519
+ // Every failure below, DER-level or structural, is reported through the caller's error factory.
520
+ // Letting a CERT_PARSE escape from here would put a certificate-shaped error in front of a
521
+ // caller checking a TLS handshake signature — the wrong taxonomy for the wrong layer.
522
+ let kids;
523
+ try {
524
+ const seq = readAll(sig, 'ECDSA-Sig-Value');
525
+ expectTlv(seq, { tag: TAG.SEQUENCE, constructed: true }, 'ECDSA-Sig-Value');
526
+ kids = children(sig, seq, 'ECDSA-Sig-Value');
527
+ } catch (e) {
528
+ throw onInvalid(e?.message ?? String(e));
529
+ }
530
+ if (kids.length !== 2) throw onInvalid(`expected { r, s }, got ${kids.length} fields`);
531
+
532
+ let r;
533
+ let s;
534
+ try {
535
+ r = readInteger(sig, kids[0], 'r');
536
+ s = readInteger(sig, kids[1], 's');
537
+ } catch (e) {
538
+ throw onInvalid(e?.message ?? String(e));
539
+ }
540
+ const out = new Uint8Array(orderLen * 2);
541
+ for (const [i, part] of [r, s].entries()) {
542
+ const label = i === 0 ? 'r' : 's';
543
+ if (part.negative) throw onInvalid(`${label} is negative`);
544
+ // readInteger guarantees minimal form, so at most one leading zero (the sign byte) remains.
545
+ let b = part.bytes;
546
+ if (b.length > 1 && b[0] === 0x00) b = b.subarray(1);
547
+ if (b.length > orderLen) throw onInvalid(`${label} is wider than the curve order`);
548
+ out.set(b, orderLen * (i + 1) - b.length);
549
+ }
550
+ return out;
551
+ }