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,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only hashes reachable through this schedule; see the module comment for why the type
|
|
3
|
+
* refuses the rest of WebCrypto's registry.
|
|
4
|
+
* @typedef {'SHA-256' | 'SHA-384'} ScheduleHash
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @param {ScheduleHash} hash
|
|
8
|
+
* @returns {number} digest length in bytes; throws for any other hash name
|
|
9
|
+
*/
|
|
10
|
+
export function hashLength(hash: ScheduleHash): number;
|
|
11
|
+
/**
|
|
12
|
+
* HMAC-Hash(key, data) for a single message. For repeated MACs under one key, import once with
|
|
13
|
+
* importHmacKey and call crypto.subtle.sign directly instead.
|
|
14
|
+
* @param {ScheduleHash} hash
|
|
15
|
+
* @param {Uint8Array} keyBytes
|
|
16
|
+
* @param {Uint8Array} data
|
|
17
|
+
* @returns {Promise<Uint8Array>}
|
|
18
|
+
*/
|
|
19
|
+
export function hmac(hash: ScheduleHash, keyBytes: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
20
|
+
/**
|
|
21
|
+
* HKDF-Extract(salt, IKM) = HMAC-Hash(salt, IKM). RFC 5869 s2.2.
|
|
22
|
+
* @param {ScheduleHash} hash
|
|
23
|
+
* @param {Uint8Array} salt
|
|
24
|
+
* @param {Uint8Array} ikm
|
|
25
|
+
* @returns {Promise<Uint8Array>}
|
|
26
|
+
*/
|
|
27
|
+
export function hkdfExtract(hash: ScheduleHash, salt: Uint8Array, ikm: Uint8Array): Promise<Uint8Array>;
|
|
28
|
+
/**
|
|
29
|
+
* HKDF-Expand(PRK, info, L). RFC 5869 s2.3: T(i) = HMAC(PRK, T(i-1) || info || i), i in 1..N.
|
|
30
|
+
* @param {ScheduleHash} hash
|
|
31
|
+
* @param {Uint8Array} prk
|
|
32
|
+
* @param {Uint8Array} info
|
|
33
|
+
* @param {number} length
|
|
34
|
+
* @returns {Promise<Uint8Array>}
|
|
35
|
+
*/
|
|
36
|
+
export function hkdfExpand(hash: ScheduleHash, prk: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
|
|
37
|
+
/**
|
|
38
|
+
* The HkdfLabel struct from RFC 8446 s7.1, exported separately so the tests can compare our
|
|
39
|
+
* encoding byte-for-byte against the `info` fields printed in the RFC 8448 traces:
|
|
40
|
+
*
|
|
41
|
+
* struct {
|
|
42
|
+
* uint16 length;
|
|
43
|
+
* opaque label<7..255>; // "tls13 " + Label
|
|
44
|
+
* opaque context<0..255>;
|
|
45
|
+
* } HkdfLabel;
|
|
46
|
+
*
|
|
47
|
+
* @param {string} label label WITHOUT the "tls13 " prefix
|
|
48
|
+
* @param {Uint8Array} context
|
|
49
|
+
* @param {number} length
|
|
50
|
+
* @returns {Uint8Array}
|
|
51
|
+
*/
|
|
52
|
+
export function hkdfLabel(label: string, context: Uint8Array, length: number): Uint8Array;
|
|
53
|
+
/**
|
|
54
|
+
* HKDF-Expand-Label(Secret, Label, Context, Length). RFC 8446 s7.1.
|
|
55
|
+
* @param {ScheduleHash} hash
|
|
56
|
+
* @param {Uint8Array} secret
|
|
57
|
+
* @param {string} label
|
|
58
|
+
* @param {Uint8Array} context
|
|
59
|
+
* @param {number} length
|
|
60
|
+
* @returns {Promise<Uint8Array>}
|
|
61
|
+
*/
|
|
62
|
+
export function hkdfExpandLabel(hash: ScheduleHash, secret: Uint8Array, label: string, context: Uint8Array, length: number): Promise<Uint8Array>;
|
|
63
|
+
/**
|
|
64
|
+
* Derive-Secret(Secret, Label, Messages) — the transcript is passed already hashed.
|
|
65
|
+
* @param {ScheduleHash} hash
|
|
66
|
+
* @param {Uint8Array} secret
|
|
67
|
+
* @param {string} label
|
|
68
|
+
* @param {Uint8Array} transcriptHash
|
|
69
|
+
* @returns {Promise<Uint8Array>}
|
|
70
|
+
*/
|
|
71
|
+
export function deriveSecret(hash: ScheduleHash, secret: Uint8Array, label: string, transcriptHash: Uint8Array): Promise<Uint8Array>;
|
|
72
|
+
/**
|
|
73
|
+
* @param {ScheduleHash} hash
|
|
74
|
+
* @returns {Promise<Uint8Array>}
|
|
75
|
+
*/
|
|
76
|
+
export function emptyHash(hash: ScheduleHash): Promise<Uint8Array>;
|
|
77
|
+
/**
|
|
78
|
+
* Early Secret = HKDF-Extract(salt: 0, IKM: PSK or zeros).
|
|
79
|
+
* @param {ScheduleHash} hash
|
|
80
|
+
* @param {Uint8Array | null} [psk]
|
|
81
|
+
* @returns {Promise<Uint8Array>}
|
|
82
|
+
*/
|
|
83
|
+
export function earlySecret(hash: ScheduleHash, psk?: Uint8Array | null): Promise<Uint8Array>;
|
|
84
|
+
/**
|
|
85
|
+
* Handshake Secret = HKDF-Extract(Derive-Secret(early, "derived", ""), ECDHE).
|
|
86
|
+
* @param {ScheduleHash} hash
|
|
87
|
+
* @param {Uint8Array} early
|
|
88
|
+
* @param {Uint8Array} ecdheShared
|
|
89
|
+
* @returns {Promise<Uint8Array>}
|
|
90
|
+
*/
|
|
91
|
+
export function deriveHandshakeSecret(hash: ScheduleHash, early: Uint8Array, ecdheShared: Uint8Array): Promise<Uint8Array>;
|
|
92
|
+
/**
|
|
93
|
+
* Master Secret = HKDF-Extract(Derive-Secret(handshake, "derived", ""), 0).
|
|
94
|
+
* @param {ScheduleHash} hash
|
|
95
|
+
* @param {Uint8Array} handshakeSecret
|
|
96
|
+
* @returns {Promise<Uint8Array>}
|
|
97
|
+
*/
|
|
98
|
+
export function deriveMasterSecret(hash: ScheduleHash, handshakeSecret: Uint8Array): Promise<Uint8Array>;
|
|
99
|
+
/**
|
|
100
|
+
* {client,server}_handshake_traffic_secret. Transcript: ClientHello..ServerHello.
|
|
101
|
+
* @param {ScheduleHash} hash
|
|
102
|
+
* @param {Uint8Array} handshakeSecret
|
|
103
|
+
* @param {Uint8Array} transcriptHash
|
|
104
|
+
* @returns {Promise<{ client: Uint8Array, server: Uint8Array }>}
|
|
105
|
+
*/
|
|
106
|
+
export function handshakeTrafficSecrets(hash: ScheduleHash, handshakeSecret: Uint8Array, transcriptHash: Uint8Array): Promise<{
|
|
107
|
+
client: Uint8Array;
|
|
108
|
+
server: Uint8Array;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* {client,server}_application_traffic_secret_0 and (unless suppressed) exporter_master_secret.
|
|
112
|
+
*
|
|
113
|
+
* `exporter` defaults on so the RFC 8448 vectors and any exporter user get the full set, but the
|
|
114
|
+
* handshake driver passes false: this package exposes no TLS-exporter interface, so deriving
|
|
115
|
+
* exporter_master_secret on every connection is one HKDF-Expand-Label of provably dead work on
|
|
116
|
+
* the hot path. Suppressing it there removes that work without changing any observable behaviour.
|
|
117
|
+
* @param {ScheduleHash} hash
|
|
118
|
+
* @param {Uint8Array} masterSecret
|
|
119
|
+
* @param {Uint8Array} transcriptHash
|
|
120
|
+
* @param {{ exporter?: boolean }} [opts]
|
|
121
|
+
* @returns {Promise<{ client: Uint8Array, server: Uint8Array, exporterMaster?: Uint8Array }>}
|
|
122
|
+
*/
|
|
123
|
+
export function applicationTrafficSecrets(hash: ScheduleHash, masterSecret: Uint8Array, transcriptHash: Uint8Array, { exporter }?: {
|
|
124
|
+
exporter?: boolean;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
client: Uint8Array;
|
|
127
|
+
server: Uint8Array;
|
|
128
|
+
exporterMaster?: Uint8Array;
|
|
129
|
+
}>;
|
|
130
|
+
/**
|
|
131
|
+
* resumption_master_secret. Transcript: ClientHello..client Finished.
|
|
132
|
+
* @param {ScheduleHash} hash
|
|
133
|
+
* @param {Uint8Array} masterSecret
|
|
134
|
+
* @param {Uint8Array} transcriptHash
|
|
135
|
+
* @returns {Promise<Uint8Array>}
|
|
136
|
+
*/
|
|
137
|
+
export function resumptionMasterSecret(hash: ScheduleHash, masterSecret: Uint8Array, transcriptHash: Uint8Array): Promise<Uint8Array>;
|
|
138
|
+
/**
|
|
139
|
+
* The PSK a NewSessionTicket names: HKDF-Expand-Label(res master, "resumption", nonce).
|
|
140
|
+
* @param {ScheduleHash} hash
|
|
141
|
+
* @param {Uint8Array} resumptionMaster
|
|
142
|
+
* @param {Uint8Array} ticketNonce
|
|
143
|
+
* @returns {Promise<Uint8Array>}
|
|
144
|
+
*/
|
|
145
|
+
export function resumptionPsk(hash: ScheduleHash, resumptionMaster: Uint8Array, ticketNonce: Uint8Array): Promise<Uint8Array>;
|
|
146
|
+
/**
|
|
147
|
+
* binder_key = Derive-Secret(Early Secret, "res binder", "") — RFC 8446 s7.1, the resumption
|
|
148
|
+
* variant. The "ext binder" sibling for externally provisioned PSKs is deliberately absent:
|
|
149
|
+
* this package mints PSKs only from NewSessionTicket, so an external-PSK binder key would be
|
|
150
|
+
* dead code with a security label on it. The binder value itself is finishedVerifyData over
|
|
151
|
+
* this key (RFC 8446 s4.2.11.2: "the PskBinderEntry is computed in the same way as the
|
|
152
|
+
* Finished message but with the BaseKey being the binder_key").
|
|
153
|
+
* @param {ScheduleHash} hash
|
|
154
|
+
* @param {Uint8Array} early the Early Secret extracted from the PSK being offered
|
|
155
|
+
* @returns {Promise<Uint8Array>}
|
|
156
|
+
*/
|
|
157
|
+
export function resumptionBinderKey(hash: ScheduleHash, early: Uint8Array): Promise<Uint8Array>;
|
|
158
|
+
/**
|
|
159
|
+
* finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length). RFC 8446 s4.4.4.
|
|
160
|
+
* @param {ScheduleHash} hash
|
|
161
|
+
* @param {Uint8Array} trafficSecret
|
|
162
|
+
* @returns {Promise<Uint8Array>}
|
|
163
|
+
*/
|
|
164
|
+
export function finishedKey(hash: ScheduleHash, trafficSecret: Uint8Array): Promise<Uint8Array>;
|
|
165
|
+
/**
|
|
166
|
+
* verify_data = HMAC(finished_key, Transcript-Hash).
|
|
167
|
+
* @param {ScheduleHash} hash
|
|
168
|
+
* @param {Uint8Array} trafficSecret
|
|
169
|
+
* @param {Uint8Array} transcriptHash
|
|
170
|
+
* @returns {Promise<Uint8Array>}
|
|
171
|
+
*/
|
|
172
|
+
export function finishedVerifyData(hash: ScheduleHash, trafficSecret: Uint8Array, transcriptHash: Uint8Array): Promise<Uint8Array>;
|
|
173
|
+
/**
|
|
174
|
+
* [sender]_write_key and [sender]_write_iv from a traffic secret. RFC 8446 s7.3.
|
|
175
|
+
* @param {ScheduleHash} hash
|
|
176
|
+
* @param {Uint8Array} trafficSecret
|
|
177
|
+
* @param {number} keyLen
|
|
178
|
+
* @param {number} ivLen
|
|
179
|
+
* @returns {Promise<{ key: Uint8Array, iv: Uint8Array }>}
|
|
180
|
+
*/
|
|
181
|
+
export function trafficKeys(hash: ScheduleHash, trafficSecret: Uint8Array, keyLen: number, ivLen: number): Promise<{
|
|
182
|
+
key: Uint8Array;
|
|
183
|
+
iv: Uint8Array;
|
|
184
|
+
}>;
|
|
185
|
+
/**
|
|
186
|
+
* application_traffic_secret_N+1 for KeyUpdate. RFC 8446 s7.2.
|
|
187
|
+
* @param {ScheduleHash} hash
|
|
188
|
+
* @param {Uint8Array} trafficSecret
|
|
189
|
+
* @returns {Promise<Uint8Array>}
|
|
190
|
+
*/
|
|
191
|
+
export function nextTrafficSecret(hash: ScheduleHash, trafficSecret: Uint8Array): Promise<Uint8Array>;
|
|
192
|
+
/**
|
|
193
|
+
* PRF(secret, label, seed) = P_<hash>(secret, label + seed), where
|
|
194
|
+
* P_hash(secret, seed) = HMAC(secret, A(1) + seed) + HMAC(secret, A(2) + seed) + ...
|
|
195
|
+
* with A(0) = seed, A(i) = HMAC(secret, A(i-1)).
|
|
196
|
+
*
|
|
197
|
+
* TLS 1.2 replaced the 1.1 MD5/SHA-1 split PRF with a single suite-selected hash, which for
|
|
198
|
+
* every suite in constants.js is SHA-256 or SHA-384.
|
|
199
|
+
*
|
|
200
|
+
* @param {ScheduleHash} hash
|
|
201
|
+
* @param {Uint8Array} secret
|
|
202
|
+
* @param {string} label ASCII label, e.g. 'master secret'
|
|
203
|
+
* @param {Uint8Array} seed
|
|
204
|
+
* @param {number} length
|
|
205
|
+
* @returns {Promise<Uint8Array>}
|
|
206
|
+
*/
|
|
207
|
+
export function prf12(hash: ScheduleHash, secret: Uint8Array, label: string, seed: Uint8Array, length: number): Promise<Uint8Array>;
|
|
208
|
+
/**
|
|
209
|
+
* master_secret = PRF(pre_master, "master secret", client_random + server_random)[0..47].
|
|
210
|
+
* @param {ScheduleHash} hash
|
|
211
|
+
* @param {Uint8Array} preMaster
|
|
212
|
+
* @param {Uint8Array} clientRandom
|
|
213
|
+
* @param {Uint8Array} serverRandom
|
|
214
|
+
* @returns {Promise<Uint8Array>}
|
|
215
|
+
*/
|
|
216
|
+
export function masterSecret12(hash: ScheduleHash, preMaster: Uint8Array, clientRandom: Uint8Array, serverRandom: Uint8Array): Promise<Uint8Array>;
|
|
217
|
+
/**
|
|
218
|
+
* RFC 7627 extended master secret: the seed is the session hash (transcript through
|
|
219
|
+
* ClientKeyExchange) instead of the two randoms, which binds the master secret to the full
|
|
220
|
+
* handshake and kills the triple-handshake attack. This is the variant the handshake layer
|
|
221
|
+
* must offer and prefer; a server that refuses the extension still gets masterSecret12, but
|
|
222
|
+
* that acceptance is deliberately flagged by the handshake layer as a weaker session.
|
|
223
|
+
* @param {ScheduleHash} hash
|
|
224
|
+
* @param {Uint8Array} preMaster
|
|
225
|
+
* @param {Uint8Array} sessionHash
|
|
226
|
+
* @returns {Promise<Uint8Array>}
|
|
227
|
+
*/
|
|
228
|
+
export function extendedMasterSecret12(hash: ScheduleHash, preMaster: Uint8Array, sessionHash: Uint8Array): Promise<Uint8Array>;
|
|
229
|
+
/**
|
|
230
|
+
* key_block = PRF(master, "key expansion", server_random + client_random). RFC 5246 s6.3.
|
|
231
|
+
* Note the randoms swap order relative to the master secret derivation — that asymmetry is in
|
|
232
|
+
* the RFC, and the tests pin it. AEAD suites have no MAC keys (macLen 0), so the block is
|
|
233
|
+
* client_write_key + server_write_key + client_write_IV + server_write_IV, where the "IV" is
|
|
234
|
+
* the 4-byte implicit GCM salt of RFC 5288.
|
|
235
|
+
*
|
|
236
|
+
* @param {ScheduleHash} hash
|
|
237
|
+
* @param {Uint8Array} master
|
|
238
|
+
* @param {Uint8Array} clientRandom
|
|
239
|
+
* @param {Uint8Array} serverRandom
|
|
240
|
+
* @param {{ keyLen: number, fixedIvLen: number, macLen?: number }} lens
|
|
241
|
+
* @returns {Promise<{ clientWriteKey: Uint8Array, serverWriteKey: Uint8Array,
|
|
242
|
+
* clientWriteIv: Uint8Array, serverWriteIv: Uint8Array,
|
|
243
|
+
* clientWriteMacKey?: Uint8Array, serverWriteMacKey?: Uint8Array }>} MAC keys present only
|
|
244
|
+
* when macLen > 0, which no AEAD suite ever passes
|
|
245
|
+
*/
|
|
246
|
+
export function keyBlock12(hash: ScheduleHash, master: Uint8Array, clientRandom: Uint8Array, serverRandom: Uint8Array, lens: {
|
|
247
|
+
keyLen: number;
|
|
248
|
+
fixedIvLen: number;
|
|
249
|
+
macLen?: number;
|
|
250
|
+
}): Promise<{
|
|
251
|
+
clientWriteKey: Uint8Array;
|
|
252
|
+
serverWriteKey: Uint8Array;
|
|
253
|
+
clientWriteIv: Uint8Array;
|
|
254
|
+
serverWriteIv: Uint8Array;
|
|
255
|
+
clientWriteMacKey?: Uint8Array;
|
|
256
|
+
serverWriteMacKey?: Uint8Array;
|
|
257
|
+
}>;
|
|
258
|
+
/**
|
|
259
|
+
* verify_data = PRF(master, "client finished" | "server finished", Hash(messages))[0..11].
|
|
260
|
+
* 12 bytes for every suite this package negotiates (RFC 5246 s7.4.9).
|
|
261
|
+
* @param {ScheduleHash} hash
|
|
262
|
+
* @param {Uint8Array} master
|
|
263
|
+
* @param {'client finished' | 'server finished'} label
|
|
264
|
+
* @param {Uint8Array} transcriptHash
|
|
265
|
+
* @returns {Promise<Uint8Array>}
|
|
266
|
+
*/
|
|
267
|
+
export function verifyData12(hash: ScheduleHash, master: Uint8Array, label: "client finished" | "server finished", transcriptHash: Uint8Array): Promise<Uint8Array>;
|
|
268
|
+
/**
|
|
269
|
+
* The only hashes reachable through this schedule; see the module comment for why the type
|
|
270
|
+
* refuses the rest of WebCrypto's registry.
|
|
271
|
+
*/
|
|
272
|
+
export type ScheduleHash = "SHA-256" | "SHA-384";
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A complete handshake message off the wire. `raw` includes the 4-byte header, which is the
|
|
3
|
+
* form the transcript hash consumes.
|
|
4
|
+
* @typedef {object} HandshakeMessage
|
|
5
|
+
* @property {number} type
|
|
6
|
+
* @property {Uint8Array} body
|
|
7
|
+
* @property {Uint8Array} raw
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Keys for one direction: a TLS 1.3 traffic `secret` (key and IV derived per RFC 8446 s7.3,
|
|
11
|
+
* KeyUpdate rotation possible) or raw TLS 1.2 key_block slices (no forward rotation).
|
|
12
|
+
* @typedef {{ cipher: number, secret: Uint8Array }
|
|
13
|
+
* | { cipher: number, key: Uint8Array, iv: Uint8Array }} DirectionKeys
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} RecordLayerOptions
|
|
17
|
+
* @property {number} [maxHandshakeMessage] per-message cap; certificate chains dominate sizing
|
|
18
|
+
* @property {number} [maxKeyUpdates] received KeyUpdates before we call it a flood
|
|
19
|
+
* @property {number} [shutdownGraceMs] how long a courtesy close_notify or fatal alert may
|
|
20
|
+
* block shutdown before being abandoned, default 2000; see _shutdown()
|
|
21
|
+
* @property {null | ((type: number, length: number) => number)} [padding] extra zero bytes per
|
|
22
|
+
* record, TLS 1.3 only
|
|
23
|
+
* @property {null | ((msg: HandshakeMessage) => void | Promise<void>)} [onPostHandshake]
|
|
24
|
+
* NewSessionTicket consumer; default is to discard
|
|
25
|
+
*/
|
|
26
|
+
export class RecordLayer {
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('./connect.js').ByteDuplex} duplex ciphertext transport
|
|
29
|
+
* @param {RecordLayerOptions} [opts]
|
|
30
|
+
*/
|
|
31
|
+
constructor({ readable, writable }: import("./connect.js").ByteDuplex, opts?: RecordLayerOptions);
|
|
32
|
+
_r: ByteReader;
|
|
33
|
+
_w: ByteWriter;
|
|
34
|
+
_maxHandshakeMessage: number;
|
|
35
|
+
_maxKeyUpdates: number;
|
|
36
|
+
_shutdownGraceMs: number;
|
|
37
|
+
_padding: ((type: number, length: number) => number) | null;
|
|
38
|
+
_onPostHandshake: ((msg: HandshakeMessage) => void | Promise<void>) | null;
|
|
39
|
+
_version: number;
|
|
40
|
+
/** @typedef {null | { aead: import('./aead.js').Aead, seq: bigint, cipher: number,
|
|
41
|
+
* hash: import('./keyschedule.js').ScheduleHash,
|
|
42
|
+
* secret: Uint8Array | null }} DirectionState */
|
|
43
|
+
/** @type {DirectionState} */
|
|
44
|
+
_send: {
|
|
45
|
+
aead: import("./aead.js").Aead;
|
|
46
|
+
seq: bigint;
|
|
47
|
+
cipher: number;
|
|
48
|
+
hash: import("./keyschedule.js").ScheduleHash;
|
|
49
|
+
secret: Uint8Array | null;
|
|
50
|
+
} | null;
|
|
51
|
+
/** @type {DirectionState} */
|
|
52
|
+
_recv: {
|
|
53
|
+
aead: import("./aead.js").Aead;
|
|
54
|
+
seq: bigint;
|
|
55
|
+
cipher: number;
|
|
56
|
+
hash: import("./keyschedule.js").ScheduleHash;
|
|
57
|
+
secret: Uint8Array | null;
|
|
58
|
+
} | null;
|
|
59
|
+
/** Handshake reassembly. Chunk list, not one growing buffer, so a peer drip-feeding a
|
|
60
|
+
* message in tiny records costs O(records), not O(records^2).
|
|
61
|
+
* @type {Uint8Array[]} */
|
|
62
|
+
_hsChunks: Uint8Array[];
|
|
63
|
+
_hsLen: number;
|
|
64
|
+
/** @type {{ type: number, len: number } | null} header of the message being assembled */
|
|
65
|
+
_hsHeader: {
|
|
66
|
+
type: number;
|
|
67
|
+
len: number;
|
|
68
|
+
} | null;
|
|
69
|
+
_handshakeComplete: boolean;
|
|
70
|
+
_closedByPeer: boolean;
|
|
71
|
+
_closedLocally: boolean;
|
|
72
|
+
/** @type {TlsError | null} first protocol error; sticky */
|
|
73
|
+
_fatal: TlsError | null;
|
|
74
|
+
_anyRecordWritten: boolean;
|
|
75
|
+
_reading: boolean;
|
|
76
|
+
/** All wire emission is funnelled through this chain. Two interleaved writers would
|
|
77
|
+
* reorder — or worse, reuse — AEAD sequence numbers, and with GCM a single reused nonce
|
|
78
|
+
* forfeits the key, so serialization here is a security control, not a convenience. */
|
|
79
|
+
_writeChain: Promise<void>;
|
|
80
|
+
_ignoredCcs: number;
|
|
81
|
+
_ignoredAlerts: number;
|
|
82
|
+
_emptyStreak: number;
|
|
83
|
+
_keyUpdatesReceived: number;
|
|
84
|
+
/**
|
|
85
|
+
* Pin the negotiated version. Chooses CCS semantics (1.3: compatibility noise to ignore;
|
|
86
|
+
* 1.2: a real key-change signal surfaced as `{ ccs: true }`), alert strictness, and AEAD
|
|
87
|
+
* framing. Must happen before any keys are installed.
|
|
88
|
+
* @param {number} version `TLS12` (0x0303) or `TLS13` (0x0304); anything else throws
|
|
89
|
+
*/
|
|
90
|
+
setVersion(version: number): void;
|
|
91
|
+
get version(): number;
|
|
92
|
+
get handshakeComplete(): boolean;
|
|
93
|
+
/** The handshake driver calls this after the Finished exchange. Gates CCS and KeyUpdate. */
|
|
94
|
+
markHandshakeComplete(): void;
|
|
95
|
+
/**
|
|
96
|
+
* Install (or replace) the NewSessionTicket consumer after construction. Exists because the
|
|
97
|
+
* consumer needs secrets that do not exist when the record layer is built — the resumption
|
|
98
|
+
* master secret is derived from the transcript through the client Finished — so the driver
|
|
99
|
+
* wires it in at handshake completion. An exception it throws surfaces on the read path and
|
|
100
|
+
* fails the connection, which is the correct fate for a peer whose post-handshake messages do
|
|
101
|
+
* not parse.
|
|
102
|
+
* @param {null | ((msg: HandshakeMessage) => void | Promise<void>)} fn
|
|
103
|
+
*/
|
|
104
|
+
setPostHandshake(fn: null | ((msg: HandshakeMessage) => void | Promise<void>)): void;
|
|
105
|
+
/**
|
|
106
|
+
* Install send-direction protection. Pass `secret` (a TLS 1.3 traffic secret; key and IV are
|
|
107
|
+
* derived per RFC 8446 s7.3, and KeyUpdate rotation becomes possible) or raw `key`+`iv`
|
|
108
|
+
* (TLS 1.2 key_block slices, which have no forward rotation). Sequence numbers reset.
|
|
109
|
+
* @param {DirectionKeys} keys
|
|
110
|
+
* @returns {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
setSendKeys({ cipher, secret, key, iv }: DirectionKeys): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Install receive-direction protection. Refuses if a partially reassembled handshake
|
|
115
|
+
* message is pending: RFC 8446 s5.1 forbids a handshake message from spanning a key change,
|
|
116
|
+
* and enforcing it here — at the only point where the receive cipher can change — covers
|
|
117
|
+
* both driver-installed keys and KeyUpdate rotation with a single check.
|
|
118
|
+
* @param {DirectionKeys} keys
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
setReceiveKeys({ cipher, secret, key, iv }: DirectionKeys): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* @param {{ cipher: number, secret?: Uint8Array, key?: Uint8Array, iv?: Uint8Array }} keys
|
|
124
|
+
* @returns {Promise<NonNullable<DirectionState>>}
|
|
125
|
+
*/
|
|
126
|
+
_makeState({ cipher, secret, key, iv }: {
|
|
127
|
+
cipher: number;
|
|
128
|
+
secret?: Uint8Array;
|
|
129
|
+
key?: Uint8Array;
|
|
130
|
+
iv?: Uint8Array;
|
|
131
|
+
}): Promise<NonNullable<DirectionState>>;
|
|
132
|
+
/**
|
|
133
|
+
* Next handshake message during the handshake phase.
|
|
134
|
+
* Returns `{ type, body, raw }` (raw includes the 4-byte header, ready for the transcript),
|
|
135
|
+
* `{ ccs: true }` in TLS 1.2 mode when the peer's change_cipher_spec arrives, or `null` if
|
|
136
|
+
* the peer closed cleanly (which mid-handshake the driver should treat as failure).
|
|
137
|
+
* @returns {Promise<HandshakeMessage | { ccs: true } | null>}
|
|
138
|
+
*/
|
|
139
|
+
nextHandshakeMessage(): Promise<HandshakeMessage | {
|
|
140
|
+
ccs: true;
|
|
141
|
+
} | null>;
|
|
142
|
+
/**
|
|
143
|
+
* Next application data chunk, or null at clean close_notify EOF. Post-handshake handshake
|
|
144
|
+
* messages (KeyUpdate, NewSessionTicket) are consumed transparently here.
|
|
145
|
+
* @returns {Promise<Uint8Array | null>}
|
|
146
|
+
*/
|
|
147
|
+
readAppData(): Promise<Uint8Array | null>;
|
|
148
|
+
/**
|
|
149
|
+
* @template T
|
|
150
|
+
* @param {() => Promise<T>} fn
|
|
151
|
+
* @returns {Promise<T>}
|
|
152
|
+
*/
|
|
153
|
+
_guardedRead<T>(fn: () => Promise<T>): Promise<T>;
|
|
154
|
+
/**
|
|
155
|
+
* One protocol event: a complete handshake message, an app-data chunk, a 1.2 CCS, or close.
|
|
156
|
+
* All the "ignore and keep reading" cases (compat CCS, warning alerts, empty app records)
|
|
157
|
+
* loop in here, each behind a flood cap.
|
|
158
|
+
* @returns {Promise<{ kind: 'close' } | { kind: 'ccs' } | { kind: 'data', bytes: Uint8Array }
|
|
159
|
+
* | { kind: 'handshake', msgType: number, body: Uint8Array, raw: Uint8Array }>}
|
|
160
|
+
*/
|
|
161
|
+
_nextEvent(): Promise<{
|
|
162
|
+
kind: "close";
|
|
163
|
+
} | {
|
|
164
|
+
kind: "ccs";
|
|
165
|
+
} | {
|
|
166
|
+
kind: "data";
|
|
167
|
+
bytes: Uint8Array;
|
|
168
|
+
} | {
|
|
169
|
+
kind: "handshake";
|
|
170
|
+
msgType: number;
|
|
171
|
+
body: Uint8Array;
|
|
172
|
+
raw: Uint8Array;
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Complete message off the reassembly buffer, if one is there.
|
|
176
|
+
* @returns {{ kind: 'handshake', msgType: number, body: Uint8Array, raw: Uint8Array } | null}
|
|
177
|
+
*/
|
|
178
|
+
_takeHandshakeMessage(): {
|
|
179
|
+
kind: "handshake";
|
|
180
|
+
msgType: number;
|
|
181
|
+
body: Uint8Array;
|
|
182
|
+
raw: Uint8Array;
|
|
183
|
+
} | null;
|
|
184
|
+
/**
|
|
185
|
+
* Read one record off the wire and reduce it to (inner type, plaintext), or null once the
|
|
186
|
+
* peer has said close_notify. Handles decryption, the plaintext/ciphertext legality rules,
|
|
187
|
+
* and TLS 1.3 compatibility CCS.
|
|
188
|
+
* @returns {Promise<{ type: number, data: Uint8Array } | null>}
|
|
189
|
+
*/
|
|
190
|
+
_nextPlaintextRecord(): Promise<{
|
|
191
|
+
type: number;
|
|
192
|
+
data: Uint8Array;
|
|
193
|
+
} | null>;
|
|
194
|
+
/**
|
|
195
|
+
* @param {Uint8Array} data
|
|
196
|
+
* @returns {'close' | 'ignored'} or throws for fatal alerts
|
|
197
|
+
*/
|
|
198
|
+
_handleAlert(data: Uint8Array): "close" | "ignored";
|
|
199
|
+
/**
|
|
200
|
+
* KeyUpdate and NewSessionTicket arriving under application keys.
|
|
201
|
+
* @param {{ msgType: number, body: Uint8Array, raw: Uint8Array }} msg
|
|
202
|
+
*/
|
|
203
|
+
_postHandshakeMessage({ msgType, body, raw }: {
|
|
204
|
+
msgType: number;
|
|
205
|
+
body: Uint8Array;
|
|
206
|
+
raw: Uint8Array;
|
|
207
|
+
}): Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Write one or more complete handshake messages, coalescing them into as few records as
|
|
210
|
+
* possible (the ClientHello flight and the 1.2 client second flight benefit) and
|
|
211
|
+
* fragmenting anything over 2^14.
|
|
212
|
+
* @param {Uint8Array | Uint8Array[]} messages
|
|
213
|
+
*/
|
|
214
|
+
writeHandshake(messages: Uint8Array | Uint8Array[]): Promise<void>;
|
|
215
|
+
/**
|
|
216
|
+
* Write application data, fragmented to the record size limit.
|
|
217
|
+
* @param {Uint8Array} bytes
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
220
|
+
writeAppData(bytes: Uint8Array): Promise<void>;
|
|
221
|
+
/** The one-byte compatibility (1.3) or key-change (1.2) CCS record. Always plaintext. */
|
|
222
|
+
writeChangeCipherSpec(): Promise<void>;
|
|
223
|
+
/**
|
|
224
|
+
* Post-handshake KeyUpdate initiated by us: send under current keys, then rotate our send
|
|
225
|
+
* chain. With `requestPeer` the peer must answer and rotate its own send keys too.
|
|
226
|
+
* @param {{ requestPeer?: boolean }} [opts]
|
|
227
|
+
* @returns {Promise<void>}
|
|
228
|
+
*/
|
|
229
|
+
updateKeys({ requestPeer }?: {
|
|
230
|
+
requestPeer?: boolean;
|
|
231
|
+
}): Promise<void>;
|
|
232
|
+
/**
|
|
233
|
+
* @param {number} level `ALERT_LEVEL.warning` (1) or `ALERT_LEVEL.fatal` (2)
|
|
234
|
+
* @param {number} desc alert description byte, per `ALERT_DESC`
|
|
235
|
+
* @returns {Promise<void>}
|
|
236
|
+
*/
|
|
237
|
+
sendAlert(level: number, desc: number): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Clean shutdown: close_notify, then close the transport write side.
|
|
240
|
+
* @returns {Promise<void>}
|
|
241
|
+
*/
|
|
242
|
+
close(): Promise<void>;
|
|
243
|
+
/**
|
|
244
|
+
* Shutdown, bounded.
|
|
245
|
+
*
|
|
246
|
+
* The alert is a courtesy: a peer that has stopped reading will never see it, and its write can
|
|
247
|
+
* therefore never complete once the transport's buffer fills. Awaiting that without a bound
|
|
248
|
+
* hangs close() forever on the ordinary path, and hangs abort() on the failure path — where it
|
|
249
|
+
* also swallows the error the caller was about to be given, turning a diagnosable failure into a
|
|
250
|
+
* request that simply never returns. So the courtesy gets a deadline and is then abandoned.
|
|
251
|
+
*
|
|
252
|
+
* Alert and FIN are one chained task so a single deadline covers both; queueing them separately
|
|
253
|
+
* would let a stalled alert consume one budget and the FIN another.
|
|
254
|
+
* @param {number} level
|
|
255
|
+
* @param {number} desc
|
|
256
|
+
*/
|
|
257
|
+
_shutdown(level: number, desc: number): Promise<void>;
|
|
258
|
+
/**
|
|
259
|
+
* Abort: send a fatal alert naming why, then close. A peer left to time out on a dead
|
|
260
|
+
* connection is an interop bug of ours, not a neutral choice.
|
|
261
|
+
* @param {number} [desc] alert description byte, default internal_error
|
|
262
|
+
* @returns {Promise<void>}
|
|
263
|
+
*/
|
|
264
|
+
abort(desc?: number): Promise<void>;
|
|
265
|
+
/**
|
|
266
|
+
* Application-data face of the connection as a {readable, writable} pair, for stacking the
|
|
267
|
+
* HTTP layer on top exactly like it would stack on a raw socket.
|
|
268
|
+
* @returns {import('./connect.js').ByteDuplex}
|
|
269
|
+
*/
|
|
270
|
+
plaintextDuplex(): import("./connect.js").ByteDuplex;
|
|
271
|
+
_assertWritable(): void;
|
|
272
|
+
/**
|
|
273
|
+
* Serialize a wire-writing task behind every previously enqueued one.
|
|
274
|
+
* @template T
|
|
275
|
+
* @param {() => Promise<T>} fn
|
|
276
|
+
* @returns {Promise<T>}
|
|
277
|
+
*/
|
|
278
|
+
_enqueueWrite<T>(fn: () => Promise<T>): Promise<T>;
|
|
279
|
+
/**
|
|
280
|
+
* @param {number} type
|
|
281
|
+
* @param {Uint8Array} bytes
|
|
282
|
+
*/
|
|
283
|
+
_writeFragmented(type: number, bytes: Uint8Array): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* Encrypt-and-frame one fragment. Only ever runs inside the write chain.
|
|
286
|
+
* @param {number} type
|
|
287
|
+
* @param {Uint8Array} chunk
|
|
288
|
+
*/
|
|
289
|
+
_emit(type: number, chunk: Uint8Array): Promise<void>;
|
|
290
|
+
/**
|
|
291
|
+
* @param {number} type
|
|
292
|
+
* @param {Uint8Array} body
|
|
293
|
+
*/
|
|
294
|
+
_writeRecord(type: number, body: Uint8Array): Promise<void>;
|
|
295
|
+
/** Derive application_traffic_secret_N+1 and swap the send state. Runs inside the chain. */
|
|
296
|
+
_rotateSend(): Promise<void>;
|
|
297
|
+
/**
|
|
298
|
+
* Record the first fatal error, tell the peer (best-effort), throw. Never returns.
|
|
299
|
+
* @param {string} code
|
|
300
|
+
* @param {string} message
|
|
301
|
+
* @param {Record<string, unknown>} [detail]
|
|
302
|
+
* @param {number} [alertDesc]
|
|
303
|
+
* @returns {never}
|
|
304
|
+
*/
|
|
305
|
+
_fail(code: string, message: string, detail?: Record<string, unknown>, alertDesc?: number): never;
|
|
306
|
+
/**
|
|
307
|
+
* Fire-and-forget: awaiting a write here could park the failure path behind transport
|
|
308
|
+
* backpressure, and the error must reach our caller no matter what the peer does. The
|
|
309
|
+
* write chain still orders it before the transport close.
|
|
310
|
+
* @param {number} desc
|
|
311
|
+
*/
|
|
312
|
+
_sendAlertBestEffort(desc: number): void;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* A complete handshake message off the wire. `raw` includes the 4-byte header, which is the
|
|
316
|
+
* form the transcript hash consumes.
|
|
317
|
+
*/
|
|
318
|
+
export type HandshakeMessage = {
|
|
319
|
+
type: number;
|
|
320
|
+
body: Uint8Array;
|
|
321
|
+
raw: Uint8Array;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Keys for one direction: a TLS 1.3 traffic `secret` (key and IV derived per RFC 8446 s7.3,
|
|
325
|
+
* KeyUpdate rotation possible) or raw TLS 1.2 key_block slices (no forward rotation).
|
|
326
|
+
*/
|
|
327
|
+
export type DirectionKeys = {
|
|
328
|
+
cipher: number;
|
|
329
|
+
secret: Uint8Array;
|
|
330
|
+
} | {
|
|
331
|
+
cipher: number;
|
|
332
|
+
key: Uint8Array;
|
|
333
|
+
iv: Uint8Array;
|
|
334
|
+
};
|
|
335
|
+
export type RecordLayerOptions = {
|
|
336
|
+
/**
|
|
337
|
+
* per-message cap; certificate chains dominate sizing
|
|
338
|
+
*/
|
|
339
|
+
maxHandshakeMessage?: number | undefined;
|
|
340
|
+
/**
|
|
341
|
+
* received KeyUpdates before we call it a flood
|
|
342
|
+
*/
|
|
343
|
+
maxKeyUpdates?: number | undefined;
|
|
344
|
+
/**
|
|
345
|
+
* how long a courtesy close_notify or fatal alert may
|
|
346
|
+
* block shutdown before being abandoned, default 2000; see _shutdown()
|
|
347
|
+
*/
|
|
348
|
+
shutdownGraceMs?: number | undefined;
|
|
349
|
+
/**
|
|
350
|
+
* extra zero bytes per
|
|
351
|
+
* record, TLS 1.3 only
|
|
352
|
+
*/
|
|
353
|
+
padding?: ((type: number, length: number) => number) | null | undefined;
|
|
354
|
+
/**
|
|
355
|
+
* NewSessionTicket consumer; default is to discard
|
|
356
|
+
*/
|
|
357
|
+
onPostHandshake?: ((msg: HandshakeMessage) => void | Promise<void>) | null | undefined;
|
|
358
|
+
};
|
|
359
|
+
import { ByteReader } from '../util/bytes.js';
|
|
360
|
+
import { ByteWriter } from '../util/bytes.js';
|
|
361
|
+
import { TlsError } from '../errors.js';
|