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,376 @@
1
+ // TLS extension encoding and decoding.
2
+ //
3
+ // Encoding is written as small pure functions returning bytes so the ClientHello is testable as a
4
+ // fixed vector. Decoding refuses to be clever: an extension we did not offer coming back from the
5
+ // server is a protocol violation (RFC 8446 s4.2), and an extension appearing twice is a smuggling
6
+ // shape, so both are errors rather than last-one-wins.
7
+
8
+ import { TlsError, TlsUnsupportedError, codes, hex16 } from '../errors.js';
9
+ import { utf8, u16, u32 } from '../util/bytes.js';
10
+ import { Builder, Cursor, vector } from './wire.js';
11
+ import {
12
+ EXTENSION,
13
+ GROUP,
14
+ GROUP_NAME,
15
+ GROUP_PARAMS,
16
+ SIG_SCHEME_NAME,
17
+ SUPPORTED_GROUPS,
18
+ SUPPORTED_SIG_SCHEMES,
19
+ TLS12,
20
+ TLS13,
21
+ VERSION_NAME,
22
+ } from './constants.js';
23
+
24
+ /** Wrap a body as `extension_type || extension_data<0..2^16-1>`. */
25
+ function ext(type, body) {
26
+ return new Builder().u16(type).vector(2, body).build();
27
+ }
28
+
29
+ // ------------------------------------------------------------------ encoders (ClientHello)
30
+
31
+ /**
32
+ * server_name (RFC 6066). Only host_name (type 0) exists in practice.
33
+ * An IP literal must NOT be sent as SNI — RFC 6066 s3 forbids it, and servers that do virtual
34
+ * hosting will hand back an unrelated certificate if we do, which would look like an attack.
35
+ * @param {string} hostname
36
+ * @returns {Uint8Array | null} null for an IP literal, which sends no SNI at all
37
+ */
38
+ export function encodeServerName(hostname) {
39
+ if (isIpLiteral(hostname)) return null;
40
+ const name = utf8(hostname);
41
+ if (name.byteLength === 0 || name.byteLength > 0xffff) {
42
+ throw new TlsError(codes.CONFIG_INVALID, `server_name of ${name.byteLength} bytes is invalid`);
43
+ }
44
+ const entry = new Builder().u8(0).vector(2, name).build();
45
+ return ext(EXTENSION.server_name, vector(2, entry));
46
+ }
47
+
48
+ /**
49
+ * status_request (RFC 6066 s8): ask the server to staple an OCSP response for its certificate.
50
+ *
51
+ * The body is a CertificateStatusRequest: status_type ocsp(1), an empty responder_id_list (we
52
+ * accept whatever responder the CA authorised — a client cannot usefully narrow that), and empty
53
+ * request_extensions (no nonce: a stapled response is produced before our hello exists, so a
54
+ * nonce could never be honoured and freshness comes from thisUpdate/nextUpdate instead).
55
+ *
56
+ * Offered in every hello, for both versions: it costs 9 bytes, and a server that ignores it
57
+ * loses nothing. How the answer arrives differs by version — TLS 1.2 echoes the extension and
58
+ * sends a separate CertificateStatus message (RFC 6066 s8), TLS 1.3 attaches it to the leaf's
59
+ * CertificateEntry (RFC 8446 s4.4.2.1) — and each driver consumes its own form.
60
+ * @returns {Uint8Array}
61
+ */
62
+ export function encodeStatusRequest() {
63
+ return ext(
64
+ EXTENSION.status_request,
65
+ new Builder().u8(1).vector(2, new Uint8Array(0)).vector(2, new Uint8Array(0)).build(),
66
+ );
67
+ }
68
+
69
+ /**
70
+ * @param {number[]} versions in preference order
71
+ * @returns {Uint8Array}
72
+ */
73
+ export function encodeSupportedVersions(versions) {
74
+ const b = new Builder();
75
+ for (const v of versions) b.u16(v);
76
+ return ext(EXTENSION.supported_versions, vector(1, b.build()));
77
+ }
78
+
79
+ /**
80
+ * @param {number[]} [groups]
81
+ * @returns {Uint8Array}
82
+ */
83
+ export function encodeSupportedGroups(groups = SUPPORTED_GROUPS) {
84
+ const b = new Builder();
85
+ for (const g of groups) b.u16(g);
86
+ return ext(EXTENSION.supported_groups, vector(2, b.build()));
87
+ }
88
+
89
+ /**
90
+ * @param {number[]} [schemes]
91
+ * @returns {Uint8Array}
92
+ */
93
+ export function encodeSignatureAlgorithms(schemes = SUPPORTED_SIG_SCHEMES) {
94
+ const b = new Builder();
95
+ for (const s of schemes) b.u16(s);
96
+ return ext(EXTENSION.signature_algorithms, vector(2, b.build()));
97
+ }
98
+
99
+ /**
100
+ * key_share entries, in the same order as supported_groups.
101
+ * @param {Array<{ group: number, keyExchange: Uint8Array }>} shares the public halves only;
102
+ * private keys never reach the encoder
103
+ * @returns {Uint8Array}
104
+ */
105
+ export function encodeKeyShare(shares) {
106
+ const b = new Builder();
107
+ for (const { group, keyExchange } of shares) {
108
+ b.u16(group).vector(2, keyExchange);
109
+ }
110
+ return ext(EXTENSION.key_share, vector(2, b.build()));
111
+ }
112
+
113
+ /**
114
+ * The HelloRetryRequest response carries a bare group id with no share.
115
+ * @param {number} group
116
+ * @returns {Uint8Array}
117
+ */
118
+ export function encodeKeyShareHrr(group) {
119
+ return ext(EXTENSION.key_share, u16(group));
120
+ }
121
+
122
+ /**
123
+ * @param {string[]} protocols
124
+ * @returns {Uint8Array}
125
+ */
126
+ export function encodeAlpn(protocols) {
127
+ const b = new Builder();
128
+ for (const p of protocols) b.vector(1, utf8(p));
129
+ return ext(EXTENSION.alpn, vector(2, b.build()));
130
+ }
131
+
132
+ /**
133
+ * psk_key_exchange_modes (RFC 8446 s4.2.9), mandatory in any ClientHello that offers (or might
134
+ * later offer) pre_shared_key, and sent in every 1.3 hello regardless because some middleboxes
135
+ * reject its absence and it costs 6 bytes.
136
+ *
137
+ * Only psk_dhe_ke(1) is offered, ever. psk_ke would let a resumed connection run with no fresh
138
+ * (EC)DHE at all, so compromise of one ticket's PSK would decrypt every session resumed from it
139
+ * — the forward-secrecy property the rest of this package refuses to trade away (no RSA key
140
+ * transport for the same reason). A server honouring psk_dhe_ke must still send key_share, and
141
+ * selectServerKeyShare fails closed if it does not.
142
+ */
143
+ export function encodePskKeyExchangeModes() {
144
+ return ext(EXTENSION.psk_key_exchange_modes, vector(1, Uint8Array.from([1]))); // psk_dhe_ke
145
+ }
146
+
147
+ /**
148
+ * pre_shared_key for a ClientHello (RFC 8446 s4.2.11): one PskIdentity (the ticket plus its
149
+ * obfuscated age) and one PskBinderEntry. The binder cannot be known while the hello is being
150
+ * encoded — it is an HMAC over a transcript of the very hello it sits in, truncated just before
151
+ * the binders list — so it is emitted here as `binderLen` ZERO bytes, at the exact length the
152
+ * real binder will have, and the builder patches the real value in afterwards. RFC 8446
153
+ * s4.2.11.2 requires exactly this shape: every length field is computed as if the true binder
154
+ * were present, and only then is the binder derived and substituted.
155
+ *
156
+ * Exactly one identity is offered by design. The wire format allows a list, but this client
157
+ * only ever holds resumption PSKs and offers the newest usable ticket; a multi-PSK offer would
158
+ * multiply binder computations for a case that cannot arise here.
159
+ *
160
+ * @param {object} psk
161
+ * @param {Uint8Array} psk.identity the ticket, opaque, 1..2^16-1 bytes
162
+ * @param {number} psk.obfuscatedTicketAge uint32, already obfuscated per s4.2.11.1
163
+ * @param {number} psk.binderLen digest length of the PSK's hash
164
+ * @returns {Uint8Array}
165
+ */
166
+ export function encodePreSharedKey({ identity, obfuscatedTicketAge, binderLen }) {
167
+ if (identity.byteLength === 0 || identity.byteLength > 0xffff) {
168
+ throw new TlsError(codes.TLS_TICKET,
169
+ `PSK identity of ${identity.byteLength} bytes is outside 1..65535 and cannot be offered`,
170
+ { length: identity.byteLength });
171
+ }
172
+ const entry = new Builder().vector(2, identity).push(u32(obfuscatedTicketAge >>> 0)).build();
173
+ const binders = vector(2, vector(1, new Uint8Array(binderLen)));
174
+ return ext(EXTENSION.pre_shared_key, new Builder().vector(2, entry).push(binders).build());
175
+ }
176
+
177
+ /**
178
+ * The number of trailing ClientHello bytes occupied by the binders list this client emits: the
179
+ * 2-byte list length, the 1-byte entry length, and the binder itself. This is the truncation
180
+ * arithmetic of RFC 8446 s4.2.11.2 — the binder transcript covers the hello up to and including
181
+ * the identities, i.e. everything except these bytes — kept next to the encoder above so the
182
+ * two cannot drift apart. pre_shared_key being the LAST extension (enforced by the builder) is
183
+ * what makes "trailing bytes of the message" and "the binders list" the same thing.
184
+ * @param {number} binderLen
185
+ * @returns {number}
186
+ */
187
+ export function pskBinderTrailerLength(binderLen) {
188
+ return 2 + 1 + binderLen;
189
+ }
190
+
191
+ /**
192
+ * pre_shared_key in a ServerHello is a bare uint16 selected_identity (RFC 8446 s4.2.11).
193
+ * @param {Uint8Array} data
194
+ * @returns {number}
195
+ */
196
+ export function decodeServerPreSharedKey(data) {
197
+ const c = new Cursor(data, 'ServerHello pre_shared_key');
198
+ const selected = c.u16('selected_identity');
199
+ c.end('selected_identity');
200
+ return selected;
201
+ }
202
+
203
+ /** RFC 7627. Requesting extended master secret closes the triple-handshake hole in TLS 1.2. */
204
+ export function encodeExtendedMasterSecret() {
205
+ return ext(EXTENSION.extended_master_secret, new Uint8Array(0));
206
+ }
207
+
208
+ /**
209
+ * RFC 5746. An empty renegotiation_info says "I support secure renegotiation and have not
210
+ * renegotiated". We never renegotiate, but omitting this makes some TLS 1.2 servers reject us.
211
+ */
212
+ export function encodeRenegotiationInfo() {
213
+ return ext(EXTENSION.renegotiation_info, vector(1, new Uint8Array(0)));
214
+ }
215
+
216
+ /** RFC 8422 s5.1.2: uncompressed only. Compressed points are not implemented anywhere modern. */
217
+ export function encodeEcPointFormats() {
218
+ return ext(EXTENSION.ec_point_formats, vector(1, Uint8Array.from([0])));
219
+ }
220
+
221
+ /**
222
+ * @param {Array<Uint8Array | null>} parts nulls tolerated so conditional extensions read cleanly
223
+ * @returns {Uint8Array}
224
+ */
225
+ export function encodeExtensionBlock(parts) {
226
+ const b = new Builder();
227
+ for (const p of parts) if (p) b.push(p);
228
+ return vector(2, b.build());
229
+ }
230
+
231
+ // ------------------------------------------------------------------ decoding
232
+
233
+ /**
234
+ * Decode an extension block into a Map. Duplicates are rejected rather than folded: a repeated
235
+ * extension is never legitimate and letting the last one win is how parser-differential bugs start.
236
+ * @param {Uint8Array} bytes the block content, its outer length prefix already consumed
237
+ * @param {string} where named in errors, e.g. 'ServerHello'
238
+ * @returns {Map<number, Uint8Array>}
239
+ */
240
+ export function decodeExtensionBlock(bytes, where) {
241
+ const c = new Cursor(bytes, `${where} extensions`);
242
+ const out = new Map();
243
+ while (!c.done) {
244
+ const type = c.u16('extension_type');
245
+ const data = c.vector(2, 'extension_data');
246
+ if (out.has(type)) {
247
+ throw new TlsError(
248
+ codes.TLS_HANDSHAKE,
249
+ `${where} contains extension ${hex16(type)} twice`,
250
+ { where, extension: type },
251
+ );
252
+ }
253
+ out.set(type, data);
254
+ }
255
+ return out;
256
+ }
257
+
258
+ /**
259
+ * RFC 8446 s4.2: the server may only send extensions the client offered. Anything else means we
260
+ * and the server disagree about the negotiation, which is not a state to continue from.
261
+ * @param {Map<number, Uint8Array>} received
262
+ * @param {Set<number>} offered extension types the ClientHello carried
263
+ * @param {string} where
264
+ * @returns {void} throws TlsError on the first unoffered extension
265
+ */
266
+ export function rejectUnofferedExtensions(received, offered, where) {
267
+ for (const type of received.keys()) {
268
+ if (!offered.has(type)) {
269
+ throw new TlsError(
270
+ codes.TLS_EXTENSION_UNSUPPORTED,
271
+ `${where} contains extension ${hex16(type)} which was not offered in ClientHello`,
272
+ { where, extension: type },
273
+ );
274
+ }
275
+ }
276
+ }
277
+
278
+ /**
279
+ * supported_versions in a ServerHello is a single uint16, not a list.
280
+ * @param {Uint8Array} data
281
+ * @returns {number}
282
+ */
283
+ export function decodeSelectedVersion(data) {
284
+ const c = new Cursor(data, 'supported_versions');
285
+ const v = c.u16('selected_version');
286
+ c.end('selected_version');
287
+ return v;
288
+ }
289
+
290
+ /**
291
+ * @param {Uint8Array} data
292
+ * @param {string} where
293
+ * @returns {{ group: number, keyExchange: Uint8Array }}
294
+ */
295
+ export function decodeKeyShareEntry(data, where) {
296
+ const c = new Cursor(data, `${where} key_share`);
297
+ const group = c.u16('group');
298
+ const keyExchange = c.vector(2, 'key_exchange');
299
+ c.end('key_share entry');
300
+ return { group, keyExchange };
301
+ }
302
+
303
+ /**
304
+ * HelloRetryRequest's key_share is a bare group with no key.
305
+ * @param {Uint8Array} data
306
+ * @returns {number}
307
+ */
308
+ export function decodeKeyShareHrr(data) {
309
+ const c = new Cursor(data, 'HelloRetryRequest key_share');
310
+ const group = c.u16('selected_group');
311
+ c.end('selected_group');
312
+ return group;
313
+ }
314
+
315
+ /**
316
+ * @param {Uint8Array} data
317
+ * @returns {string} the single protocol the server selected (RFC 7301 s3.2)
318
+ */
319
+ export function decodeAlpn(data) {
320
+ const c = new Cursor(data, 'alpn');
321
+ const list = c.sub(2, 'protocol_name_list');
322
+ const name = list.vector(1, 'protocol_name');
323
+ // RFC 7301 s3.2: the server's list must contain exactly one protocol.
324
+ list.end('protocol_name_list');
325
+ c.end('alpn');
326
+ return new TextDecoder().decode(name);
327
+ }
328
+
329
+ // ------------------------------------------------------------------ validation helpers
330
+
331
+ /**
332
+ * @param {number} group
333
+ * @param {string} where
334
+ * @returns {import('./constants.js').GroupParams} throws TlsUnsupportedError when unimplemented
335
+ */
336
+ export function requireSupportedGroup(group, where) {
337
+ const params = GROUP_PARAMS[group];
338
+ if (!params) {
339
+ const name = GROUP_NAME[group] ?? 'unknown';
340
+ throw new TlsUnsupportedError(
341
+ codes.TLS_GROUP_UNSUPPORTED,
342
+ `server selected group ${hex16(group)} (${name}) in ${where}, not implemented; ` +
343
+ `offered ${SUPPORTED_GROUPS.map((g) => `${GROUP_NAME[g]}(${hex16(g)})`).join(', ')}`,
344
+ { group, groupName: name },
345
+ );
346
+ }
347
+ return params;
348
+ }
349
+
350
+ /**
351
+ * @param {number} scheme
352
+ * @returns {string}
353
+ */
354
+ export function describeSigScheme(scheme) {
355
+ return `${hex16(scheme)}${SIG_SCHEME_NAME[scheme] ? ` (${SIG_SCHEME_NAME[scheme]})` : ''}`;
356
+ }
357
+
358
+ /**
359
+ * @param {number} version
360
+ * @returns {string}
361
+ */
362
+ export function describeVersion(version) {
363
+ return `${hex16(version)}${VERSION_NAME[version] ? ` (${VERSION_NAME[version]})` : ''}`;
364
+ }
365
+
366
+ /**
367
+ * Cheap classification, not validation: decides whether a name is legal as SNI.
368
+ * @param {string} host
369
+ * @returns {boolean}
370
+ */
371
+ export function isIpLiteral(host) {
372
+ if (host.includes(':')) return true; // only IPv6 literals contain a colon in a bare authority
373
+ return /^\d{1,3}(\.\d{1,3}){3}$/.test(host);
374
+ }
375
+
376
+ export { GROUP, TLS12, TLS13 };