tunnelfetch 1.9.0 → 1.12.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.
@@ -13,9 +13,23 @@
13
13
  * @property {number[]} [groups] supported_groups, in preference order.
14
14
  * @property {number[]} [offerGroups] groups to send an actual key_share for. Default the first
15
15
  * supported group; a HelloRetryRequest recovers any other choice at the cost of a round trip.
16
- * @property {number[]} [ciphers] cipher suites to offer, in preference order. Every suite must be
17
- * one this package can perform; an offer it cannot honour is a dead connection the moment a
18
- * server selects it, so an unknown suite is refused here rather than on the wire.
16
+ * @property {number[]} [ciphers] cipher suites to offer, in preference order. By default every
17
+ * suite must be one this package can perform: an offer it cannot honour is a dead connection the
18
+ * moment a server selects it, so an unknown suite is refused here rather than on the wire.
19
+ * @property {number[]} [omitExtensions] extension types to leave out of the ClientHello, the
20
+ * subtractive counterpart to `extraExtensions`. `status_request` (5) is the one extension this
21
+ * package sends that curl does not, so an identity matching a sample without it needs this.
22
+ * Dropping it gives up OCSP stapling, which is the only revocation signal this package can
23
+ * consume — pairing it with `trust.revocation: 'require-staple'` is refused rather than left to
24
+ * fail every connection.
25
+ * @property {boolean} [allowUnperformableCiphers] offer suites this package cannot complete.
26
+ * For fingerprint fidelity only. Real clients offer far more than this package implements — curl
27
+ * 8.21.0 offers thirty against seven performable here, Chromium fifteen against seven — so a
28
+ * hello restricted to what it can honour carries a cipher list shorter than any real client's,
29
+ * which is exactly what a JA3 hash reads. With this set, a server that selects an unperformable
30
+ * suite fails the handshake; the first such suite sits behind the TLS 1.3 ones in both real
31
+ * lists, so a 1.3-capable server does not reach it. Knowingly trading a rare failure for an
32
+ * accurate fingerprint is a legitimate choice; making it silently is not.
19
33
  * @property {Uint8Array[]} [extraExtensions] pre-encoded ClientHello extensions, appended before
20
34
  * ordering. `extensionOrder` can only arrange extensions that were BUILT — it filters to what
21
35
  * exists and sorts that — so ordering alone cannot produce an extension this package does not
@@ -169,11 +183,31 @@ export type TlsOptions = {
169
183
  */
170
184
  offerGroups?: number[] | undefined;
171
185
  /**
172
- * cipher suites to offer, in preference order. Every suite must be
173
- * one this package can perform; an offer it cannot honour is a dead connection the moment a
174
- * server selects it, so an unknown suite is refused here rather than on the wire.
186
+ * cipher suites to offer, in preference order. By default every
187
+ * suite must be one this package can perform: an offer it cannot honour is a dead connection the
188
+ * moment a server selects it, so an unknown suite is refused here rather than on the wire.
175
189
  */
176
190
  ciphers?: number[] | undefined;
191
+ /**
192
+ * extension types to leave out of the ClientHello, the
193
+ * subtractive counterpart to `extraExtensions`. `status_request` (5) is the one extension this
194
+ * package sends that curl does not, so an identity matching a sample without it needs this.
195
+ * Dropping it gives up OCSP stapling, which is the only revocation signal this package can
196
+ * consume — pairing it with `trust.revocation: 'require-staple'` is refused rather than left to
197
+ * fail every connection.
198
+ */
199
+ omitExtensions?: number[] | undefined;
200
+ /**
201
+ * offer suites this package cannot complete.
202
+ * For fingerprint fidelity only. Real clients offer far more than this package implements — curl
203
+ * 8.21.0 offers thirty against seven performable here, Chromium fifteen against seven — so a
204
+ * hello restricted to what it can honour carries a cipher list shorter than any real client's,
205
+ * which is exactly what a JA3 hash reads. With this set, a server that selects an unperformable
206
+ * suite fails the handshake; the first such suite sits behind the TLS 1.3 ones in both real
207
+ * lists, so a 1.3-capable server does not reach it. Knowingly trading a rare failure for an
208
+ * accurate fingerprint is a legitimate choice; making it silently is not.
209
+ */
210
+ allowUnperformableCiphers?: boolean | undefined;
177
211
  /**
178
212
  * pre-encoded ClientHello extensions, appended before
179
213
  * ordering. `extensionOrder` can only arrange extensions that were BUILT — it filters to what
@@ -32,7 +32,7 @@ export function generateKeyShare(group: number, deps?: import("./connect.js").Tl
32
32
  * @returns {Promise<Uint8Array>} throws on any degenerate or malformed peer key
33
33
  */
34
34
  export function deriveSharedSecret(group: number, privateKey: CryptoKey | import("./hybrid.js").HybridPrivate, peerKey: Uint8Array, deps?: import("./connect.js").TlsDeps): Promise<Uint8Array>;
35
- export function buildClientHello({ hostname, keyShares, random, legacySessionId, ciphers, groups, sigSchemes, alpn, versions, extensionOrder, extraExtensions, psk, grease, randomBytes, }: {
35
+ export function buildClientHello({ hostname, keyShares, random, legacySessionId, ciphers, groups, sigSchemes, alpn, versions, extensionOrder, extraExtensions, omitExtensions, psk, grease, randomBytes, }: {
36
36
  hostname: any;
37
37
  keyShares: any;
38
38
  random: any;
@@ -44,6 +44,7 @@ export function buildClientHello({ hostname, keyShares, random, legacySessionId,
44
44
  versions?: number[] | undefined;
45
45
  extensionOrder?: readonly number[] | undefined;
46
46
  extraExtensions?: never[] | undefined;
47
+ omitExtensions?: never[] | undefined;
47
48
  psk?: null | undefined;
48
49
  grease?: boolean | undefined;
49
50
  randomBytes?: ((n: any) => Uint8Array<any>) | undefined;
@@ -305,6 +306,7 @@ export function checkAlpn(extensions: Map<number, Uint8Array>, offeredAlpn: stri
305
306
  * @property {string[]} [alpn] default ['http/1.1']; empty array omits the extension
306
307
  * @property {number[]} [versions] default [TLS13, TLS12]
307
308
  * @property {Uint8Array[]} [extraExtensions] pre-encoded, sent verbatim (the HRR cookie)
309
+ * @property {number[]} [omitExtensions] extension types to leave out of the hello
308
310
  * @property {{ identity: Uint8Array, obfuscatedTicketAge: number, binderLen: number }} [psk]
309
311
  * offer this resumption PSK. Encoded with a zeroed binder placeholder; the caller MUST derive
310
312
  * the real binder over `message.subarray(0, truncatedLength)` and patch it in at
@@ -432,6 +434,10 @@ export type ClientHelloOptions = {
432
434
  * pre-encoded, sent verbatim (the HRR cookie)
433
435
  */
434
436
  extraExtensions?: Uint8Array<ArrayBufferLike>[] | undefined;
437
+ /**
438
+ * extension types to leave out of the hello
439
+ */
440
+ omitExtensions?: number[] | undefined;
435
441
  /**
436
442
  * offer this resumption PSK. Encoded with a zeroed binder placeholder; the caller MUST derive
437
443
  * the real binder over `message.subarray(0, truncatedLength)` and patch it in at
@@ -74,21 +74,25 @@ export class UnexpectedEofError extends TunnelFetchError {
74
74
  * be retained beyond the caller's immediate use if memory matters.
75
75
  *
76
76
  * When the source is a byte stream — on the target runtime, a socket's readable is one — the
77
- * reader pulls with BYOB reads into large fresh views instead of taking the source's own
78
- * chunking. This is measured, not stylistic: the runtime delivers socket data in chunks of at
79
- * most 4096 bytes, ~1200 of them for a 4 MB body, and every chunk is a runtime/JS boundary
80
- * crossing; a BYOB read hands over everything the transport has buffered (up to the view size)
81
- * in one crossing, and resolves with a partial fill the instant anything at all is available,
82
- * so delivery latency is unchanged. Sources that are not byte streams (every in-process
77
+ * reader pulls with BYOB reads instead of taking the source's own chunking. This is measured, not
78
+ * stylistic: the runtime delivers socket data in chunks of at most 4096 bytes, ~1200 of them for a
79
+ * 4 MB body, and every chunk is a runtime/JS boundary crossing; a BYOB read collects several of
80
+ * them into one.
81
+ *
82
+ * How MANY it collects is the transport's decision, not the view's. A BYOB read resolves the
83
+ * instant any byte is available and never waits to fill, so the view is a ceiling that is normally
84
+ * not reached: measured over a 4 MB body, 37 KB average fill on a direct socket and 8 KB through a
85
+ * proxy, whatever the view size. Sizing the view far above that buys nothing and costs the
86
+ * allocation — see BYOB_PULL_BYTES. Sources that are not byte streams (every in-process
83
87
  * ReadableStream in this package and its tests) take the default-reader path unchanged.
84
88
  */
85
89
  export class ByteReader {
86
90
  /**
87
91
  * @param {ReadableStream<Uint8Array>} readable
88
- * @param {number} [pullBytes] size of each BYOB view pulled from the source. Tunable because it
89
- * decides how many times a body crosses the runtime boundary on the way in, and that turned out
90
- * to be the largest single cost in a large response42 ms of a 106 ms 4 MB request is socket
91
- * reads and record decryption, of which the AEAD itself is under 2 ms.
92
+ * @param {number} [pullBytes] size of each BYOB view pulled from the source. Tunable because the
93
+ * right value depends on how much the transport hands over per read, and that differs by a
94
+ * factor of four between a direct socket and a proxied onesee BYOB_PULL_BYTES for the sweep.
95
+ * Ignored on sources that are not byte streams, which take the default-reader path.
92
96
  */
93
97
  constructor(readable: ReadableStream<Uint8Array>, pullBytes?: number);
94
98
  _pullBytes: number;