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.
- package/LICENSE +28 -0
- package/README.md +617 -0
- package/README.zh-CN.md +470 -0
- package/package.json +74 -0
- package/src/client/cookies.js +429 -0
- package/src/client/decode.js +346 -0
- package/src/client/redirect.js +249 -0
- package/src/client.js +704 -0
- package/src/errors.js +181 -0
- package/src/http1/chunked.js +289 -0
- package/src/http1/index.js +10 -0
- package/src/http1/request.js +143 -0
- package/src/http1/response.js +493 -0
- package/src/http2/connection.js +1170 -0
- package/src/http2/constants.js +129 -0
- package/src/http2/frames.js +291 -0
- package/src/http2/hpack.js +420 -0
- package/src/http2/huffman.js +203 -0
- package/src/http2/index.js +21 -0
- package/src/index.js +46 -0
- package/src/pool.js +256 -0
- package/src/proxy/direct.js +62 -0
- package/src/proxy/http-connect.js +206 -0
- package/src/proxy/index.js +197 -0
- package/src/proxy/socks5.js +344 -0
- package/src/tls/aead.js +263 -0
- package/src/tls/connect.js +407 -0
- package/src/tls/constants.js +334 -0
- package/src/tls/extensions.js +376 -0
- package/src/tls/handshake-messages.js +901 -0
- package/src/tls/handshake.js +568 -0
- package/src/tls/handshake12.js +507 -0
- package/src/tls/index.js +44 -0
- package/src/tls/keyschedule.js +473 -0
- package/src/tls/record.js +872 -0
- package/src/tls/tickets.js +145 -0
- package/src/tls/transcript.js +101 -0
- package/src/tls/wire.js +224 -0
- package/src/transport.js +296 -0
- package/src/trust/der.js +551 -0
- package/src/trust/index.js +375 -0
- package/src/trust/name.js +235 -0
- package/src/trust/ocsp.js +759 -0
- package/src/trust/path.js +595 -0
- package/src/trust/roots.js +454 -0
- package/src/trust/x509.js +902 -0
- package/src/util/bytes.js +470 -0
- package/src/util/deadline.js +266 -0
- package/src/warmup-fixture.js +85 -0
- package/src/warmup.js +243 -0
- package/types/client/cookies.d.ts +159 -0
- package/types/client/decode.d.ts +54 -0
- package/types/client/redirect.d.ts +96 -0
- package/types/client.d.ts +323 -0
- package/types/errors.d.ts +141 -0
- package/types/http1/chunked.d.ts +48 -0
- package/types/http1/index.d.ts +3 -0
- package/types/http1/request.d.ts +44 -0
- package/types/http1/response.d.ts +183 -0
- package/types/http2/connection.d.ts +282 -0
- package/types/http2/constants.d.ts +95 -0
- package/types/http2/frames.d.ts +116 -0
- package/types/http2/hpack.d.ts +99 -0
- package/types/http2/huffman.d.ts +21 -0
- package/types/http2/index.d.ts +5 -0
- package/types/index.d.ts +17 -0
- package/types/pool.d.ts +135 -0
- package/types/proxy/direct.d.ts +26 -0
- package/types/proxy/http-connect.d.ts +37 -0
- package/types/proxy/index.d.ts +62 -0
- package/types/proxy/socks5.d.ts +47 -0
- package/types/tls/aead.d.ts +67 -0
- package/types/tls/connect.d.ts +280 -0
- package/types/tls/constants.d.ts +275 -0
- package/types/tls/extensions.d.ts +195 -0
- package/types/tls/handshake-messages.d.ts +430 -0
- package/types/tls/handshake.d.ts +90 -0
- package/types/tls/handshake12.d.ts +35 -0
- package/types/tls/index.d.ts +9 -0
- package/types/tls/keyschedule.d.ts +272 -0
- package/types/tls/record.d.ts +361 -0
- package/types/tls/tickets.d.ts +66 -0
- package/types/tls/transcript.d.ts +52 -0
- package/types/tls/wire.d.ts +106 -0
- package/types/transport.d.ts +222 -0
- package/types/trust/der.d.ts +239 -0
- package/types/trust/index.d.ts +194 -0
- package/types/trust/name.d.ts +33 -0
- package/types/trust/ocsp.d.ts +138 -0
- package/types/trust/path.d.ts +139 -0
- package/types/trust/roots.d.ts +36 -0
- package/types/trust/x509.d.ts +401 -0
- package/types/util/bytes.d.ts +183 -0
- package/types/util/deadline.d.ts +133 -0
- package/types/warmup-fixture.d.ts +11 -0
- package/types/warmup.d.ts +45 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class TicketStore {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} [opts]
|
|
4
|
+
* @param {number} [opts.maxPerKey] tickets retained per key, default 2 — the number a typical
|
|
5
|
+
* server flight issues; older tickets are evicted first
|
|
6
|
+
* @param {() => number} [opts.now] epoch-ms source, injectable for tests
|
|
7
|
+
*/
|
|
8
|
+
constructor({ maxPerKey, now }?: {
|
|
9
|
+
maxPerKey?: number | undefined;
|
|
10
|
+
now?: (() => number) | undefined;
|
|
11
|
+
});
|
|
12
|
+
/** @type {Map<string, Array<StoredTicket>>} */
|
|
13
|
+
_byKey: Map<string, Array<{
|
|
14
|
+
identity: Uint8Array;
|
|
15
|
+
psk: Uint8Array;
|
|
16
|
+
hash: import("./keyschedule.js").ScheduleHash;
|
|
17
|
+
ageAdd: number;
|
|
18
|
+
/**
|
|
19
|
+
* already clamped to the seven-day ceiling
|
|
20
|
+
*/
|
|
21
|
+
lifetimeMs: number;
|
|
22
|
+
receivedAtMs: number;
|
|
23
|
+
peer: object;
|
|
24
|
+
cipherSuite: number;
|
|
25
|
+
}>>;
|
|
26
|
+
_maxPerKey: number;
|
|
27
|
+
_now: () => number;
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} StoredTicket
|
|
30
|
+
* @property {Uint8Array} identity
|
|
31
|
+
* @property {Uint8Array} psk
|
|
32
|
+
* @property {import('./keyschedule.js').ScheduleHash} hash
|
|
33
|
+
* @property {number} ageAdd
|
|
34
|
+
* @property {number} lifetimeMs already clamped to the seven-day ceiling
|
|
35
|
+
* @property {number} receivedAtMs
|
|
36
|
+
* @property {object} peer
|
|
37
|
+
* @property {number} cipherSuite
|
|
38
|
+
*/
|
|
39
|
+
/** Total tickets currently held, for tests and diagnostics. */
|
|
40
|
+
get size(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Store a captured ticket under `key`. Returns whether it was retained: a zero (or negative)
|
|
43
|
+
* lifetime is a server instruction to discard immediately and is honoured silently — that is
|
|
44
|
+
* the server's prerogative, not an error — while a structurally unusable capture (no PSK, no
|
|
45
|
+
* identity, unknown hash) throws, because the only producer is this package's own driver and
|
|
46
|
+
* a malformed capture is a bug, not an input.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} key the pool key of the connection the ticket arrived on
|
|
49
|
+
* @param {import('./connect.js').CapturedTicket} captured
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
put(key: string, captured: import("./connect.js").CapturedTicket): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Take the freshest usable ticket for `key` as a ready-to-offer PSK, or null. The ticket is
|
|
55
|
+
* removed either way it goes from here — single use — and expired tickets encountered on the
|
|
56
|
+
* way are dropped rather than offered: a server checks obfuscated_ticket_age against the
|
|
57
|
+
* lifetime it granted, and offering a stale ticket is a round trip spent being refused.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} key MUST be built from the same inputs as the connection's pool key; this
|
|
60
|
+
* equality is the entire trust story of resumption (see the module comment)
|
|
61
|
+
* @returns {import('./connect.js').ResumptionOffer | null}
|
|
62
|
+
*/
|
|
63
|
+
take(key: string): import("./connect.js").ResumptionOffer | null;
|
|
64
|
+
/** Drop everything; a closed Client must not keep credentials alive. */
|
|
65
|
+
clear(): void;
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class Transcript {
|
|
2
|
+
/**
|
|
3
|
+
* @param {import('./keyschedule.js').ScheduleHash} hash fixed once the cipher suite is known
|
|
4
|
+
* @param {{ maxBytes?: number }} [opts] transcript buffer cap, default 1 MiB
|
|
5
|
+
*/
|
|
6
|
+
constructor(hash: import("./keyschedule.js").ScheduleHash, { maxBytes }?: {
|
|
7
|
+
maxBytes?: number;
|
|
8
|
+
});
|
|
9
|
+
_hash: import("./keyschedule.js").ScheduleHash;
|
|
10
|
+
/** @type {Uint8Array[]} */
|
|
11
|
+
_chunks: Uint8Array[];
|
|
12
|
+
_len: number;
|
|
13
|
+
_maxBytes: number;
|
|
14
|
+
/** @type {Uint8Array | null} digest cache, invalidated by update() */
|
|
15
|
+
_cached: Uint8Array | null;
|
|
16
|
+
get bytesBuffered(): number;
|
|
17
|
+
/**
|
|
18
|
+
* Append raw handshake message bytes (including the 4-byte message header — the transcript
|
|
19
|
+
* is over complete Handshake structs, never record framing).
|
|
20
|
+
* @param {Uint8Array} bytes
|
|
21
|
+
*/
|
|
22
|
+
update(bytes: Uint8Array): void;
|
|
23
|
+
/**
|
|
24
|
+
* Digest of everything appended so far. Does not consume; call as often as needed.
|
|
25
|
+
* @returns {Promise<Uint8Array>}
|
|
26
|
+
*/
|
|
27
|
+
hash(): Promise<Uint8Array>;
|
|
28
|
+
/**
|
|
29
|
+
* Digest of everything appended so far PLUS `extra`, without appending it.
|
|
30
|
+
*
|
|
31
|
+
* Exists for exactly one caller: the PSK binder after a HelloRetryRequest, which is an HMAC
|
|
32
|
+
* over Transcript-Hash(message_hash(CH1) || HRR || Truncate(CH2)) (RFC 8446 s4.2.11.2). The
|
|
33
|
+
* truncated ClientHello2 must be hashed as a continuation of the real transcript but must
|
|
34
|
+
* never BECOME part of it — the transcript proper gets the full ClientHello2 with its binder,
|
|
35
|
+
* and folding the truncated form in even transiently would leave a window where the two
|
|
36
|
+
* bookkeepings disagree.
|
|
37
|
+
* @param {Uint8Array} extra
|
|
38
|
+
* @returns {Promise<Uint8Array>}
|
|
39
|
+
*/
|
|
40
|
+
hashWith(extra: Uint8Array): Promise<Uint8Array>;
|
|
41
|
+
/**
|
|
42
|
+
* HelloRetryRequest transcript substitution (RFC 8446 s4.4.1): when a ServerHello is an HRR,
|
|
43
|
+
* the transcript restarts as a synthetic handshake message
|
|
44
|
+
*
|
|
45
|
+
* message_hash(0xFE) || uint24 Hash.length || Hash(ClientHello1)
|
|
46
|
+
*
|
|
47
|
+
* so that a stateless server need only remember the hash of the first ClientHello. Call this
|
|
48
|
+
* after ClientHello1 is the only message in the transcript, before appending the HRR itself.
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
*/
|
|
51
|
+
replaceWithMessageHash(): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience: build a length-prefixed vector standalone.
|
|
3
|
+
* @param {1 | 2 | 3} lenBytes
|
|
4
|
+
* @param {Uint8Array} body
|
|
5
|
+
* @returns {Uint8Array}
|
|
6
|
+
*/
|
|
7
|
+
export function vector(lenBytes: 1 | 2 | 3, body: Uint8Array): Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Encode a handshake message: 1-byte type, 3-byte length, body.
|
|
10
|
+
* @param {number} type
|
|
11
|
+
* @param {Uint8Array} body
|
|
12
|
+
* @returns {Uint8Array}
|
|
13
|
+
*/
|
|
14
|
+
export function handshakeMessage(type: number, body: Uint8Array): Uint8Array;
|
|
15
|
+
/** Sequential reader over a byte range with hard bounds. */
|
|
16
|
+
export class Cursor {
|
|
17
|
+
/**
|
|
18
|
+
* @param {Uint8Array} bytes
|
|
19
|
+
* @param {string} what named in every error so a failure says which structure was malformed
|
|
20
|
+
*/
|
|
21
|
+
constructor(bytes: Uint8Array, what?: string);
|
|
22
|
+
bytes: Uint8Array<ArrayBufferLike>;
|
|
23
|
+
pos: number;
|
|
24
|
+
what: string;
|
|
25
|
+
get remaining(): number;
|
|
26
|
+
get done(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* @param {number} n
|
|
29
|
+
* @param {string} field
|
|
30
|
+
*/
|
|
31
|
+
_need(n: number, field: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} [field]
|
|
34
|
+
* @returns {number}
|
|
35
|
+
*/
|
|
36
|
+
u8(field?: string): number;
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} [field]
|
|
39
|
+
* @returns {number}
|
|
40
|
+
*/
|
|
41
|
+
u16(field?: string): number;
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} [field]
|
|
44
|
+
* @returns {number}
|
|
45
|
+
*/
|
|
46
|
+
u24(field?: string): number;
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} [field]
|
|
49
|
+
* @returns {number}
|
|
50
|
+
*/
|
|
51
|
+
u32(field?: string): number;
|
|
52
|
+
/**
|
|
53
|
+
* Fixed-length opaque bytes. Returns a view into the original buffer, never a copy.
|
|
54
|
+
* @param {number} n
|
|
55
|
+
* @param {string} [field]
|
|
56
|
+
* @returns {Uint8Array}
|
|
57
|
+
*/
|
|
58
|
+
take(n: number, field?: string): Uint8Array;
|
|
59
|
+
/**
|
|
60
|
+
* A vector whose length is carried in `lenBytes` (1, 2 or 3) leading octets.
|
|
61
|
+
* @param {1 | 2 | 3} lenBytes
|
|
62
|
+
* @param {string} [field]
|
|
63
|
+
* @returns {Uint8Array}
|
|
64
|
+
*/
|
|
65
|
+
vector(lenBytes: 1 | 2 | 3, field?: string): Uint8Array;
|
|
66
|
+
/**
|
|
67
|
+
* Like `vector`, but hands back a Cursor so nested structures inherit the bound.
|
|
68
|
+
* @param {1 | 2 | 3} lenBytes
|
|
69
|
+
* @param {string} [field]
|
|
70
|
+
* @returns {Cursor}
|
|
71
|
+
*/
|
|
72
|
+
sub(lenBytes: 1 | 2 | 3, field?: string): Cursor;
|
|
73
|
+
/**
|
|
74
|
+
* Assert nothing is left. Trailing data inside a length-delimited structure means our idea of
|
|
75
|
+
* the structure and the peer's disagree, which is exactly when to stop rather than guess.
|
|
76
|
+
* @param {string} [field]
|
|
77
|
+
*/
|
|
78
|
+
end(field?: string): void;
|
|
79
|
+
}
|
|
80
|
+
/** Accumulating writer. Kept dumb: correctness of lengths comes from `vector()` below. */
|
|
81
|
+
export class Builder {
|
|
82
|
+
/** @type {Uint8Array[]} */
|
|
83
|
+
parts: Uint8Array[];
|
|
84
|
+
length: number;
|
|
85
|
+
/**
|
|
86
|
+
* @param {Uint8Array} bytes
|
|
87
|
+
* @returns {this}
|
|
88
|
+
*/
|
|
89
|
+
push(bytes: Uint8Array): this;
|
|
90
|
+
/** @param {number} n @returns {this} */
|
|
91
|
+
u8(n: number): this;
|
|
92
|
+
/** @param {number} n @returns {this} */
|
|
93
|
+
u16(n: number): this;
|
|
94
|
+
/** @param {number} n @returns {this} */
|
|
95
|
+
u24(n: number): this;
|
|
96
|
+
/**
|
|
97
|
+
* Write `body` prefixed by its length in `lenBytes` octets. Taking the body as bytes rather
|
|
98
|
+
* than back-patching a placeholder means a length can never drift from what follows it.
|
|
99
|
+
* @param {1 | 2 | 3} lenBytes
|
|
100
|
+
* @param {Uint8Array} body
|
|
101
|
+
* @returns {this}
|
|
102
|
+
*/
|
|
103
|
+
vector(lenBytes: 1 | 2 | 3, body: Uint8Array): this;
|
|
104
|
+
/** @returns {Uint8Array} */
|
|
105
|
+
build(): Uint8Array;
|
|
106
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request URL reduced to what the transport dials.
|
|
3
|
+
* @typedef {object} TransportTarget
|
|
4
|
+
* @property {URL} url
|
|
5
|
+
* @property {string} hostname IPv6 unbracketed, as the socket API and SOCKS5 want it
|
|
6
|
+
* @property {number} port explicit port, or the scheme default
|
|
7
|
+
* @property {boolean} secure whether the scheme is https:
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Split a request URL into what the transport needs. Throws ConfigError on any scheme other
|
|
11
|
+
* than http: and https:.
|
|
12
|
+
* @param {string | URL} input
|
|
13
|
+
* @returns {TransportTarget}
|
|
14
|
+
*/
|
|
15
|
+
export function targetFromUrl(input: string | URL): TransportTarget;
|
|
16
|
+
/**
|
|
17
|
+
* What a Response's `tunnelfetch` detail reports about the connection, before the HTTP layer
|
|
18
|
+
* adds the per-response httpVersion and framing.
|
|
19
|
+
* @typedef {object} ConnectionInfo
|
|
20
|
+
* @property {string} url
|
|
21
|
+
* @property {boolean} proxied
|
|
22
|
+
* @property {string | null} proxy the proxy actually used, credentials omitted
|
|
23
|
+
* @property {import('./tls/connect.js').TlsSessionInfo | null} tls null for cleartext
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* A live connection carrying application bytes: the duplex, its provenance, and the deadline
|
|
27
|
+
* controller that governs it. This is what the pool stores and what sendAndReceive consumes.
|
|
28
|
+
* @typedef {object} Connection
|
|
29
|
+
* @property {ReadableStream<Uint8Array>} readable
|
|
30
|
+
* @property {WritableStream<Uint8Array>} writable
|
|
31
|
+
* @property {() => Promise<void> | void} close
|
|
32
|
+
* @property {ConnectionInfo} info
|
|
33
|
+
* @property {import('./trust/index.js').ParsedCertificate | null} [peerCertificate] TLS only;
|
|
34
|
+
* null when trust mode 'none' accepted a leaf it could not parse
|
|
35
|
+
* @property {DeadlineController} deadlines
|
|
36
|
+
* @property {boolean} ownsDeadlines whether this call created the controller (and must dispose
|
|
37
|
+
* it) rather than borrowing the caller's
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} OpenConnectionOptions
|
|
41
|
+
* @property {string | URL} url
|
|
42
|
+
* @property {import('./proxy/index.js').ConnectFn} connect injected socket factory
|
|
43
|
+
* @property {string | import('./proxy/index.js').ProxyConfig | null} [proxy]
|
|
44
|
+
* @property {import('./trust/index.js').TrustConfig} [trust] the `verify=`-style knob, passed
|
|
45
|
+
* through to the trust layer
|
|
46
|
+
* @property {DeadlineController} [deadlines] borrow the request's controller; omitting it makes
|
|
47
|
+
* this call own (and dispose) a fresh one
|
|
48
|
+
* @property {import('./tls/connect.js').TlsOptions} [tls] handshake options
|
|
49
|
+
* @property {string[]} [alpn] the ALPN protocol list to offer, newest/most-preferred first.
|
|
50
|
+
* Kept separate from `tls` so offering `h2` does not read as a user-supplied TLS option (which
|
|
51
|
+
* would disable native-fetch delegation and enter the pool key). `tls.alpn` still wins if set.
|
|
52
|
+
* @property {{ offer?: import('./tls/connect.js').ResumptionOffer | null,
|
|
53
|
+
* onTicket?: (t: import('./tls/connect.js').CapturedTicket) => void }} [resumption]
|
|
54
|
+
* session-resumption wiring, injected per connection by the Client. Separate from `tls` for
|
|
55
|
+
* the same reason `alpn` is: it must neither disable native-fetch delegation nor enter the
|
|
56
|
+
* pool key — it is not caller configuration, it is state the Client derived FROM the pool key.
|
|
57
|
+
* @property {import('./tls/connect.js').TlsDeps} [deps] injectable randomness/keygen for
|
|
58
|
+
* reproducible handshakes
|
|
59
|
+
* @property {AbortSignal} [signal]
|
|
60
|
+
* @property {{ maxProxyReplyBytes?: number }} [limits] cap on the proxy CONNECT reply head
|
|
61
|
+
* @property {number} [now] epoch ms override for certificate validity
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Open a connection carrying application bytes for `url`. Throws (ProxyError, TlsError,
|
|
65
|
+
* CertificateError, TimeoutError, ConfigError) rather than resolving with a failure value.
|
|
66
|
+
*
|
|
67
|
+
* @param {OpenConnectionOptions} args
|
|
68
|
+
* @returns {Promise<Connection>}
|
|
69
|
+
*/
|
|
70
|
+
export function openConnection({ url, connect, proxy, trust, deadlines, tls, alpn, resumption, deps, signal, limits, now, }: OpenConnectionOptions): Promise<Connection>;
|
|
71
|
+
/**
|
|
72
|
+
* The delegation decision, with the disqualifying reason spelled out so a caller's error can
|
|
73
|
+
* quote it. Discriminated on `ok` so `reason` is a string exactly when there is one.
|
|
74
|
+
* @typedef {{ ok: true, reason: null } | { ok: false, reason: string }} NativeFetchVerdict
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {object} NativeFetchQuery
|
|
78
|
+
* @property {import('./proxy/index.js').ProxyConfig | string | null} [proxy]
|
|
79
|
+
* @property {import('./trust/index.js').TrustConfig | null} [trust]
|
|
80
|
+
* @property {import('./tls/connect.js').TlsOptions | null} [tls]
|
|
81
|
+
* @property {boolean} [forceTunnel]
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Can the platform's own fetch() satisfy this request in full?
|
|
85
|
+
*
|
|
86
|
+
* Delegating to the native implementation when it can is strictly better — it is faster, does not
|
|
87
|
+
* burn metered CPU, speaks HTTP/2 and /3, and reaches origins our raw sockets are forbidden from
|
|
88
|
+
* dialling. But delegation must be decided on CAPABILITY, not merely on "is there a proxy":
|
|
89
|
+
* quietly satisfying a request that asked for a pinned certificate by handing it to an
|
|
90
|
+
* implementation using a different trust store would answer a security question the caller did
|
|
91
|
+
* not ask. So anything the native path cannot honour disqualifies it.
|
|
92
|
+
*
|
|
93
|
+
* @param {NativeFetchQuery} query
|
|
94
|
+
* @returns {NativeFetchVerdict}
|
|
95
|
+
*/
|
|
96
|
+
export function nativeFetchCanServe({ proxy, trust, tls, forceTunnel }: NativeFetchQuery): NativeFetchVerdict;
|
|
97
|
+
/**
|
|
98
|
+
* A request URL reduced to what the transport dials.
|
|
99
|
+
*/
|
|
100
|
+
export type TransportTarget = {
|
|
101
|
+
url: URL;
|
|
102
|
+
/**
|
|
103
|
+
* IPv6 unbracketed, as the socket API and SOCKS5 want it
|
|
104
|
+
*/
|
|
105
|
+
hostname: string;
|
|
106
|
+
/**
|
|
107
|
+
* explicit port, or the scheme default
|
|
108
|
+
*/
|
|
109
|
+
port: number;
|
|
110
|
+
/**
|
|
111
|
+
* whether the scheme is https:
|
|
112
|
+
*/
|
|
113
|
+
secure: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* What a Response's `tunnelfetch` detail reports about the connection, before the HTTP layer
|
|
117
|
+
* adds the per-response httpVersion and framing.
|
|
118
|
+
*/
|
|
119
|
+
export type ConnectionInfo = {
|
|
120
|
+
url: string;
|
|
121
|
+
proxied: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* the proxy actually used, credentials omitted
|
|
124
|
+
*/
|
|
125
|
+
proxy: string | null;
|
|
126
|
+
/**
|
|
127
|
+
* null for cleartext
|
|
128
|
+
*/
|
|
129
|
+
tls: import("./tls/connect.js").TlsSessionInfo | null;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* A live connection carrying application bytes: the duplex, its provenance, and the deadline
|
|
133
|
+
* controller that governs it. This is what the pool stores and what sendAndReceive consumes.
|
|
134
|
+
*/
|
|
135
|
+
export type Connection = {
|
|
136
|
+
readable: ReadableStream<Uint8Array>;
|
|
137
|
+
writable: WritableStream<Uint8Array>;
|
|
138
|
+
close: () => Promise<void> | void;
|
|
139
|
+
info: ConnectionInfo;
|
|
140
|
+
/**
|
|
141
|
+
* TLS only;
|
|
142
|
+
* null when trust mode 'none' accepted a leaf it could not parse
|
|
143
|
+
*/
|
|
144
|
+
peerCertificate?: import("./trust/index.js").ParsedCertificate | null | undefined;
|
|
145
|
+
deadlines: DeadlineController;
|
|
146
|
+
/**
|
|
147
|
+
* whether this call created the controller (and must dispose
|
|
148
|
+
* it) rather than borrowing the caller's
|
|
149
|
+
*/
|
|
150
|
+
ownsDeadlines: boolean;
|
|
151
|
+
};
|
|
152
|
+
export type OpenConnectionOptions = {
|
|
153
|
+
url: string | URL;
|
|
154
|
+
/**
|
|
155
|
+
* injected socket factory
|
|
156
|
+
*/
|
|
157
|
+
connect: import("./proxy/index.js").ConnectFn;
|
|
158
|
+
proxy?: string | import("./proxy/index.js").ProxyConfig | null | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* the `verify=`-style knob, passed
|
|
161
|
+
* through to the trust layer
|
|
162
|
+
*/
|
|
163
|
+
trust?: import("./trust/index.js").TrustConfig | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* borrow the request's controller; omitting it makes
|
|
166
|
+
* this call own (and dispose) a fresh one
|
|
167
|
+
*/
|
|
168
|
+
deadlines?: DeadlineController | undefined;
|
|
169
|
+
/**
|
|
170
|
+
* handshake options
|
|
171
|
+
*/
|
|
172
|
+
tls?: import("./tls/connect.js").TlsOptions | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* the ALPN protocol list to offer, newest/most-preferred first.
|
|
175
|
+
* Kept separate from `tls` so offering `h2` does not read as a user-supplied TLS option (which
|
|
176
|
+
* would disable native-fetch delegation and enter the pool key). `tls.alpn` still wins if set.
|
|
177
|
+
*/
|
|
178
|
+
alpn?: string[] | undefined;
|
|
179
|
+
/**
|
|
180
|
+
* session-resumption wiring, injected per connection by the Client. Separate from `tls` for
|
|
181
|
+
* the same reason `alpn` is: it must neither disable native-fetch delegation nor enter the
|
|
182
|
+
* pool key — it is not caller configuration, it is state the Client derived FROM the pool key.
|
|
183
|
+
*/
|
|
184
|
+
resumption?: {
|
|
185
|
+
offer?: import("./tls/connect.js").ResumptionOffer | null;
|
|
186
|
+
onTicket?: (t: import("./tls/connect.js").CapturedTicket) => void;
|
|
187
|
+
} | undefined;
|
|
188
|
+
/**
|
|
189
|
+
* injectable randomness/keygen for
|
|
190
|
+
* reproducible handshakes
|
|
191
|
+
*/
|
|
192
|
+
deps?: import("./tls/connect.js").TlsDeps | undefined;
|
|
193
|
+
signal?: AbortSignal | undefined;
|
|
194
|
+
/**
|
|
195
|
+
* cap on the proxy CONNECT reply head
|
|
196
|
+
*/
|
|
197
|
+
limits?: {
|
|
198
|
+
maxProxyReplyBytes?: number;
|
|
199
|
+
} | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* epoch ms override for certificate validity
|
|
202
|
+
*/
|
|
203
|
+
now?: number | undefined;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* The delegation decision, with the disqualifying reason spelled out so a caller's error can
|
|
207
|
+
* quote it. Discriminated on `ok` so `reason` is a string exactly when there is one.
|
|
208
|
+
*/
|
|
209
|
+
export type NativeFetchVerdict = {
|
|
210
|
+
ok: true;
|
|
211
|
+
reason: null;
|
|
212
|
+
} | {
|
|
213
|
+
ok: false;
|
|
214
|
+
reason: string;
|
|
215
|
+
};
|
|
216
|
+
export type NativeFetchQuery = {
|
|
217
|
+
proxy?: string | import("./proxy/index.js").ProxyConfig | null | undefined;
|
|
218
|
+
trust?: import("./trust/index.js").TrustConfig | null | undefined;
|
|
219
|
+
tls?: import("./tls/connect.js").TlsOptions | null | undefined;
|
|
220
|
+
forceTunnel?: boolean | undefined;
|
|
221
|
+
};
|
|
222
|
+
import { DeadlineController } from './util/deadline.js';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One decoded tag-length-value element, as byte ranges into the ORIGINAL buffer. Every reader
|
|
3
|
+
* in the trust layer passes these around instead of slices precisely so that signature checks
|
|
4
|
+
* always run over the peer's own bytes.
|
|
5
|
+
* @typedef {object} Tlv
|
|
6
|
+
* @property {number} cls tag class, per {@link CLS}
|
|
7
|
+
* @property {boolean} constructed
|
|
8
|
+
* @property {number} tag tag number, high-tag-number form already decoded
|
|
9
|
+
* @property {number} start offset of the first header byte
|
|
10
|
+
* @property {number} headerLen tag + length octets
|
|
11
|
+
* @property {number} contentStart
|
|
12
|
+
* @property {number} contentEnd
|
|
13
|
+
* @property {number} end one past the element; equals contentEnd for every legal DER element
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Describe a tag for error messages: "SEQUENCE", "[0]", "APPLICATION 3".
|
|
17
|
+
* @param {number} cls
|
|
18
|
+
* @param {number} tag
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function tagName(cls: number, tag: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* @param {number} offset
|
|
24
|
+
* @param {string} message
|
|
25
|
+
* @returns {CertificateError}
|
|
26
|
+
*/
|
|
27
|
+
export function parseError(offset: number, message: string): CertificateError;
|
|
28
|
+
/**
|
|
29
|
+
* Read one TLV starting at `offset`. Returns byte ranges only — content is always a subarray of
|
|
30
|
+
* the caller's original buffer, never a copy, so signatures can be verified over the same bytes
|
|
31
|
+
* the peer sent.
|
|
32
|
+
*
|
|
33
|
+
* @param {Uint8Array} bytes
|
|
34
|
+
* @param {number} offset
|
|
35
|
+
* @returns {Tlv}
|
|
36
|
+
*/
|
|
37
|
+
export function readTlv(bytes: Uint8Array, offset: number): Tlv;
|
|
38
|
+
/**
|
|
39
|
+
* Assert a TLV has the given shape, with an error naming expected vs got at the offset.
|
|
40
|
+
* @param {Tlv} tlv
|
|
41
|
+
* @param {{ cls?: number, tag: number, constructed?: boolean }} shape omitted `constructed`
|
|
42
|
+
* accepts either form
|
|
43
|
+
* @param {string} what
|
|
44
|
+
* @returns {Tlv} the same tlv, for chaining into a reader
|
|
45
|
+
*/
|
|
46
|
+
export function expectTlv(tlv: Tlv, { cls, tag, constructed }: {
|
|
47
|
+
cls?: number;
|
|
48
|
+
tag: number;
|
|
49
|
+
constructed?: boolean;
|
|
50
|
+
}, what: string): Tlv;
|
|
51
|
+
/**
|
|
52
|
+
* Read a TLV and require it to be a constructed SEQUENCE.
|
|
53
|
+
* @param {Uint8Array} bytes
|
|
54
|
+
* @param {number} offset
|
|
55
|
+
* @param {string} [what]
|
|
56
|
+
* @returns {Tlv}
|
|
57
|
+
*/
|
|
58
|
+
export function readSequence(bytes: Uint8Array, offset: number, what?: string): Tlv;
|
|
59
|
+
/**
|
|
60
|
+
* Parse the content of a constructed element into its immediate children, requiring them to fill
|
|
61
|
+
* the content exactly. Trailing bytes inside a container are as suspicious as trailing bytes
|
|
62
|
+
* after the certificate.
|
|
63
|
+
* @param {Uint8Array} bytes
|
|
64
|
+
* @param {Tlv} tlv
|
|
65
|
+
* @param {string} [what]
|
|
66
|
+
* @returns {Tlv[]}
|
|
67
|
+
*/
|
|
68
|
+
export function children(bytes: Uint8Array, tlv: Tlv, what?: string): Tlv[];
|
|
69
|
+
/**
|
|
70
|
+
* Read the single top-level element and reject trailing bytes after it.
|
|
71
|
+
* @param {Uint8Array} bytes
|
|
72
|
+
* @param {string} [what]
|
|
73
|
+
* @returns {Tlv}
|
|
74
|
+
*/
|
|
75
|
+
export function readAll(bytes: Uint8Array, what?: string): Tlv;
|
|
76
|
+
/**
|
|
77
|
+
* INTEGER: returns the raw big-endian content and, when it fits a safe JS number, the value.
|
|
78
|
+
* DER minimality: the first nine bits must not be all-zero or all-one, else a shorter encoding
|
|
79
|
+
* exists. Laxness here would let two different byte strings claim the same serial number.
|
|
80
|
+
* @param {Uint8Array} bytes
|
|
81
|
+
* @param {Tlv} tlv
|
|
82
|
+
* @param {string} [what]
|
|
83
|
+
* @returns {{ bytes: Uint8Array, value: number | null, negative: boolean }} `value` is null
|
|
84
|
+
* when the magnitude does not fit a safe number (serials routinely do not)
|
|
85
|
+
*/
|
|
86
|
+
export function readInteger(bytes: Uint8Array, tlv: Tlv, what?: string): {
|
|
87
|
+
bytes: Uint8Array;
|
|
88
|
+
value: number | null;
|
|
89
|
+
negative: boolean;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* OBJECT IDENTIFIER to dotted string. Sub-identifiers are base-128 and must be minimal: a leading
|
|
93
|
+
* 0x80 continuation byte is a second spelling of the same OID, and two spellings of one identity
|
|
94
|
+
* is how "unknown critical extension" checks get bypassed.
|
|
95
|
+
* @param {Uint8Array} bytes
|
|
96
|
+
* @param {Tlv} tlv
|
|
97
|
+
* @param {string} [what]
|
|
98
|
+
* @returns {string} dotted form, e.g. '2.5.29.15'
|
|
99
|
+
*/
|
|
100
|
+
export function readOid(bytes: Uint8Array, tlv: Tlv, what?: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* BIT STRING: unused-bit count + payload. DER additionally requires the unused bits themselves to
|
|
103
|
+
* be zero — a nonzero padding bit is another two-spellings ambiguity.
|
|
104
|
+
* @param {Uint8Array} bytes
|
|
105
|
+
* @param {Tlv} tlv
|
|
106
|
+
* @param {string} [what]
|
|
107
|
+
* @returns {{ unusedBits: number, bytes: Uint8Array }}
|
|
108
|
+
*/
|
|
109
|
+
export function readBitString(bytes: Uint8Array, tlv: Tlv, what?: string): {
|
|
110
|
+
unusedBits: number;
|
|
111
|
+
bytes: Uint8Array;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* BOOLEAN. DER: content is exactly one byte, 0x00 or 0xff.
|
|
115
|
+
* @param {Uint8Array} bytes
|
|
116
|
+
* @param {Tlv} tlv
|
|
117
|
+
* @param {string} [what]
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
export function readBoolean(bytes: Uint8Array, tlv: Tlv, what?: string): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* UTCTime, RFC 5280 profile: exactly YYMMDDHHMMSSZ. Seconds are mandatory, the zone is mandatory
|
|
123
|
+
* and must be Z, and fractional seconds do not exist in this profile. The two-digit year pivots
|
|
124
|
+
* at 50: 50..99 map to 19xx, 00..49 to 20xx (RFC 5280 s4.1.2.5.1).
|
|
125
|
+
* @param {Uint8Array} bytes
|
|
126
|
+
* @param {Tlv} tlv
|
|
127
|
+
* @param {string} [what]
|
|
128
|
+
* @returns {number} epoch ms UTC
|
|
129
|
+
*/
|
|
130
|
+
export function readUtcTime(bytes: Uint8Array, tlv: Tlv, what?: string): number;
|
|
131
|
+
/**
|
|
132
|
+
* GeneralizedTime, RFC 5280 profile: exactly YYYYMMDDHHMMSSZ. Fractional seconds are explicitly
|
|
133
|
+
* forbidden by RFC 5280 s4.1.2.5.2, and allowing them would give one instant many encodings.
|
|
134
|
+
* @param {Uint8Array} bytes
|
|
135
|
+
* @param {Tlv} tlv
|
|
136
|
+
* @param {string} [what]
|
|
137
|
+
* @returns {number} epoch ms UTC
|
|
138
|
+
*/
|
|
139
|
+
export function readGeneralizedTime(bytes: Uint8Array, tlv: Tlv, what?: string): number;
|
|
140
|
+
/**
|
|
141
|
+
* Either time type, as used by Validity and by name-constraint-free consumers.
|
|
142
|
+
* @param {Uint8Array} bytes
|
|
143
|
+
* @param {Tlv} tlv
|
|
144
|
+
* @param {string} [what]
|
|
145
|
+
* @returns {number} epoch ms UTC
|
|
146
|
+
*/
|
|
147
|
+
export function readTime(bytes: Uint8Array, tlv: Tlv, what?: string): number;
|
|
148
|
+
/**
|
|
149
|
+
* Directory string types. Each type's alphabet is enforced — a PrintableString smuggling bytes
|
|
150
|
+
* outside its charset is two parsers disagreeing about one name.
|
|
151
|
+
*
|
|
152
|
+
* TeletexString is decoded as Latin-1: its real charset (T.61) is a negotiation-dependent mess
|
|
153
|
+
* that no CA has honoured in decades, and Latin-1 is the universal de-facto reading.
|
|
154
|
+
* @param {Uint8Array} bytes
|
|
155
|
+
* @param {Tlv} tlv
|
|
156
|
+
* @param {string} [what]
|
|
157
|
+
* @returns {string}
|
|
158
|
+
*/
|
|
159
|
+
export function readString(bytes: Uint8Array, tlv: Tlv, what?: string): string;
|
|
160
|
+
/**
|
|
161
|
+
* ECDSA-Sig-Value (SEQUENCE of two INTEGERs) to the fixed-width r||s form WebCrypto verifies.
|
|
162
|
+
*
|
|
163
|
+
* This lives here, once, because both users of it are checking signatures: the certificate path
|
|
164
|
+
* builder and the TLS CertificateVerify. TLS transmits ECDSA signatures in DER while WebCrypto
|
|
165
|
+
* accepts only the P1363 concatenation, and a round-trip test that signs and verifies with
|
|
166
|
+
* WebCrypto agrees with itself while failing against every real server — so this conversion is
|
|
167
|
+
* exactly the sort of thing that must have one implementation and not two.
|
|
168
|
+
*
|
|
169
|
+
* Any malformation is an invalid signature; there is no "close enough" for signature bytes.
|
|
170
|
+
*
|
|
171
|
+
* @param {Uint8Array} sig DER ECDSA-Sig-Value
|
|
172
|
+
* @param {number} orderLen byte width of the curve order (32 / 48 / 66)
|
|
173
|
+
* @param {(why: string) => Error} onInvalid builds the caller's own error type
|
|
174
|
+
* @returns {Uint8Array} r||s, each half left-padded to orderLen
|
|
175
|
+
*/
|
|
176
|
+
export function ecdsaDerToRaw(sig: Uint8Array, orderLen: number, onInvalid: (why: string) => Error): Uint8Array;
|
|
177
|
+
export namespace TAG {
|
|
178
|
+
let BOOLEAN: number;
|
|
179
|
+
let INTEGER: number;
|
|
180
|
+
let BIT_STRING: number;
|
|
181
|
+
let OCTET_STRING: number;
|
|
182
|
+
let NULL: number;
|
|
183
|
+
let OID: number;
|
|
184
|
+
let ENUMERATED: number;
|
|
185
|
+
let UTF8_STRING: number;
|
|
186
|
+
let SEQUENCE: number;
|
|
187
|
+
let SET: number;
|
|
188
|
+
let NUMERIC_STRING: number;
|
|
189
|
+
let PRINTABLE_STRING: number;
|
|
190
|
+
let TELETEX_STRING: number;
|
|
191
|
+
let IA5_STRING: number;
|
|
192
|
+
let UTC_TIME: number;
|
|
193
|
+
let GENERALIZED_TIME: number;
|
|
194
|
+
let VISIBLE_STRING: number;
|
|
195
|
+
let BMP_STRING: number;
|
|
196
|
+
}
|
|
197
|
+
export namespace CLS {
|
|
198
|
+
let UNIVERSAL: number;
|
|
199
|
+
let APPLICATION: number;
|
|
200
|
+
let CONTEXT: number;
|
|
201
|
+
let PRIVATE: number;
|
|
202
|
+
}
|
|
203
|
+
/** The content bytes of a TLV, as a subarray of the original buffer. */
|
|
204
|
+
/** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
|
|
205
|
+
export const content: (bytes: Uint8Array, tlv: Tlv) => Uint8Array;
|
|
206
|
+
/** The full element (tag + length + content), as a subarray of the original buffer. */
|
|
207
|
+
/** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
|
|
208
|
+
export const element: (bytes: Uint8Array, tlv: Tlv) => Uint8Array;
|
|
209
|
+
/**
|
|
210
|
+
* One decoded tag-length-value element, as byte ranges into the ORIGINAL buffer. Every reader
|
|
211
|
+
* in the trust layer passes these around instead of slices precisely so that signature checks
|
|
212
|
+
* always run over the peer's own bytes.
|
|
213
|
+
*/
|
|
214
|
+
export type Tlv = {
|
|
215
|
+
/**
|
|
216
|
+
* tag class, per {@link CLS}
|
|
217
|
+
*/
|
|
218
|
+
cls: number;
|
|
219
|
+
constructed: boolean;
|
|
220
|
+
/**
|
|
221
|
+
* tag number, high-tag-number form already decoded
|
|
222
|
+
*/
|
|
223
|
+
tag: number;
|
|
224
|
+
/**
|
|
225
|
+
* offset of the first header byte
|
|
226
|
+
*/
|
|
227
|
+
start: number;
|
|
228
|
+
/**
|
|
229
|
+
* tag + length octets
|
|
230
|
+
*/
|
|
231
|
+
headerLen: number;
|
|
232
|
+
contentStart: number;
|
|
233
|
+
contentEnd: number;
|
|
234
|
+
/**
|
|
235
|
+
* one past the element; equals contentEnd for every legal DER element
|
|
236
|
+
*/
|
|
237
|
+
end: number;
|
|
238
|
+
};
|
|
239
|
+
import { CertificateError } from '../errors.js';
|