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,473 @@
1
+ // The TLS key schedule: HKDF (RFC 5869), the TLS 1.3 schedule (RFC 8446 s7.1), and the
2
+ // TLS 1.2 PRF (RFC 5246 s5, RFC 7627).
3
+ //
4
+ // Everything here runs on WebCrypto (`crypto.subtle`) and nothing else, because this package
5
+ // must run where `node:crypto` does not exist. WebCrypto does ship an "HKDF" algorithm, but it
6
+ // is deliberately not used: it fuses Extract and Expand into one deriveBits call and never
7
+ // reveals the intermediate PRK, while the TLS 1.3 schedule threads that PRK through the next
8
+ // Extract as an independent secret (early -> handshake -> master). So Extract and Expand are
9
+ // implemented from their RFC 5869 definitions over HMAC, and verified against the RFC 5869
10
+ // appendix-A vectors plus the complete RFC 8448 section 3 trace.
11
+ //
12
+ // Only SHA-256 and SHA-384 are accepted, because those are the only hashes any negotiable
13
+ // suite in constants.js uses. SHA-1 exists in WebCrypto but is not reachable through here.
14
+
15
+ import { TlsError, codes } from '../errors.js';
16
+ import { concat, u8, u16, utf8 } from '../util/bytes.js';
17
+
18
+ const EMPTY = new Uint8Array(0);
19
+
20
+ /** Digest lengths for the only hashes any negotiable cipher suite selects. */
21
+ const HASH_PARAMS = {
22
+ 'SHA-256': { len: 32 },
23
+ 'SHA-384': { len: 48 },
24
+ };
25
+
26
+ /**
27
+ * The only hashes reachable through this schedule; see the module comment for why the type
28
+ * refuses the rest of WebCrypto's registry.
29
+ * @typedef {'SHA-256' | 'SHA-384'} ScheduleHash
30
+ */
31
+
32
+ /**
33
+ * @param {ScheduleHash} hash
34
+ * @returns {number} digest length in bytes; throws for any other hash name
35
+ */
36
+ export function hashLength(hash) {
37
+ const p = HASH_PARAMS[hash];
38
+ if (!p) {
39
+ throw new TlsError(codes.CONFIG_INVALID, `hash ${JSON.stringify(hash)} is not one of ` +
40
+ `SHA-256/SHA-384; no negotiable cipher suite uses it`, { hash });
41
+ }
42
+ return p.len;
43
+ }
44
+
45
+ const zeros = (n) => new Uint8Array(n);
46
+
47
+ /**
48
+ * Import a raw HMAC key. The WebCrypto spec forbids importing a zero-length HMAC key, but HMAC
49
+ * itself zero-pads the key to the block size, so an empty key and a key of HashLen zero bytes
50
+ * produce the identical MAC. RFC 5869 defines the default salt as HashLen zeros for the same
51
+ * reason, so the substitution is exact, not an approximation.
52
+ *
53
+ * Separated from the sign step so a caller that MACs many messages under one key — HKDF-Expand
54
+ * and the TLS 1.2 PRF both iterate HMAC with a fixed key — imports the key once rather than once
55
+ * per block. Measured on the edge, importKey is ~4µs and sign ~3µs, so hoisting it roughly halves
56
+ * per-block cost in those loops.
57
+ * @param {ScheduleHash} hash
58
+ * @param {Uint8Array} keyBytes
59
+ * @returns {Promise<CryptoKey>}
60
+ */
61
+ async function importHmacKey(hash, keyBytes) {
62
+ const raw = keyBytes.byteLength === 0 ? zeros(hashLength(hash)) : keyBytes;
63
+ return crypto.subtle.importKey('raw', raw, { name: 'HMAC', hash }, false, ['sign']);
64
+ }
65
+
66
+ /**
67
+ * HMAC-Hash(key, data) for a single message. For repeated MACs under one key, import once with
68
+ * importHmacKey and call crypto.subtle.sign directly instead.
69
+ * @param {ScheduleHash} hash
70
+ * @param {Uint8Array} keyBytes
71
+ * @param {Uint8Array} data
72
+ * @returns {Promise<Uint8Array>}
73
+ */
74
+ export async function hmac(hash, keyBytes, data) {
75
+ const key = await importHmacKey(hash, keyBytes);
76
+ return new Uint8Array(await crypto.subtle.sign('HMAC', key, data));
77
+ }
78
+
79
+ /**
80
+ * HKDF-Extract(salt, IKM) = HMAC-Hash(salt, IKM). RFC 5869 s2.2.
81
+ * @param {ScheduleHash} hash
82
+ * @param {Uint8Array} salt
83
+ * @param {Uint8Array} ikm
84
+ * @returns {Promise<Uint8Array>}
85
+ */
86
+ export async function hkdfExtract(hash, salt, ikm) {
87
+ return hmac(hash, salt, ikm);
88
+ }
89
+
90
+ /**
91
+ * HKDF-Expand(PRK, info, L). RFC 5869 s2.3: T(i) = HMAC(PRK, T(i-1) || info || i), i in 1..N.
92
+ * @param {ScheduleHash} hash
93
+ * @param {Uint8Array} prk
94
+ * @param {Uint8Array} info
95
+ * @param {number} length
96
+ * @returns {Promise<Uint8Array>}
97
+ */
98
+ export async function hkdfExpand(hash, prk, info, length) {
99
+ const len = hashLength(hash);
100
+ const n = Math.ceil(length / len);
101
+ if (!Number.isInteger(length) || length < 1 || n > 255) {
102
+ throw new TlsError(codes.CONFIG_INVALID, `HKDF-Expand length ${length} outside 1..${255 * len}`,
103
+ { length });
104
+ }
105
+ // One import for every block T(1..n): the PRK is fixed across the whole expansion.
106
+ const key = await importHmacKey(hash, prk);
107
+ const out = new Uint8Array(length);
108
+ let t = EMPTY;
109
+ for (let i = 1, o = 0; i <= n; i++, o += len) {
110
+ t = new Uint8Array(await crypto.subtle.sign('HMAC', key, concat([t, info, u8(i)])));
111
+ out.set(t.subarray(0, Math.min(len, length - o)), o);
112
+ }
113
+ return out;
114
+ }
115
+
116
+ /**
117
+ * The HkdfLabel struct from RFC 8446 s7.1, exported separately so the tests can compare our
118
+ * encoding byte-for-byte against the `info` fields printed in the RFC 8448 traces:
119
+ *
120
+ * struct {
121
+ * uint16 length;
122
+ * opaque label<7..255>; // "tls13 " + Label
123
+ * opaque context<0..255>;
124
+ * } HkdfLabel;
125
+ *
126
+ * @param {string} label label WITHOUT the "tls13 " prefix
127
+ * @param {Uint8Array} context
128
+ * @param {number} length
129
+ * @returns {Uint8Array}
130
+ */
131
+ export function hkdfLabel(label, context, length) {
132
+ const full = utf8('tls13 ' + label);
133
+ if (full.byteLength < 7 || full.byteLength > 255) {
134
+ throw new TlsError(codes.CONFIG_INVALID, `HkdfLabel label is ${full.byteLength} bytes, ` +
135
+ 'outside 7..255', { label });
136
+ }
137
+ if (context.byteLength > 255) {
138
+ throw new TlsError(codes.CONFIG_INVALID, `HkdfLabel context is ${context.byteLength} bytes, ` +
139
+ 'over 255', { contextLength: context.byteLength });
140
+ }
141
+ if (length < 1 || length > 0xffff) {
142
+ throw new TlsError(codes.CONFIG_INVALID, `HkdfLabel output length ${length} outside 1..65535`,
143
+ { length });
144
+ }
145
+ return concat([u16(length), u8(full.byteLength), full, u8(context.byteLength), context]);
146
+ }
147
+
148
+ /**
149
+ * HKDF-Expand-Label(Secret, Label, Context, Length). RFC 8446 s7.1.
150
+ * @param {ScheduleHash} hash
151
+ * @param {Uint8Array} secret
152
+ * @param {string} label
153
+ * @param {Uint8Array} context
154
+ * @param {number} length
155
+ * @returns {Promise<Uint8Array>}
156
+ */
157
+ export async function hkdfExpandLabel(hash, secret, label, context, length) {
158
+ return hkdfExpand(hash, secret, hkdfLabel(label, context, length), length);
159
+ }
160
+
161
+ /**
162
+ * Derive-Secret(Secret, Label, Messages) — the transcript is passed already hashed.
163
+ * @param {ScheduleHash} hash
164
+ * @param {Uint8Array} secret
165
+ * @param {string} label
166
+ * @param {Uint8Array} transcriptHash
167
+ * @returns {Promise<Uint8Array>}
168
+ */
169
+ export async function deriveSecret(hash, secret, label, transcriptHash) {
170
+ return hkdfExpandLabel(hash, secret, label, transcriptHash, hashLength(hash));
171
+ }
172
+
173
+ /** Hash of the empty string, used by the two "derived" steps. Cached: it is a constant. */
174
+ const emptyHashCache = new Map();
175
+ /**
176
+ * @param {ScheduleHash} hash
177
+ * @returns {Promise<Uint8Array>}
178
+ */
179
+ export async function emptyHash(hash) {
180
+ hashLength(hash);
181
+ let d = emptyHashCache.get(hash);
182
+ if (!d) {
183
+ d = new Uint8Array(await crypto.subtle.digest(hash, EMPTY));
184
+ emptyHashCache.set(hash, d);
185
+ }
186
+ return d;
187
+ }
188
+
189
+ // ---------------------------------------------------------------- TLS 1.3 schedule (s7.1)
190
+ //
191
+ // 0
192
+ // |
193
+ // v
194
+ // PSK -> HKDF-Extract = Early Secret
195
+ // |
196
+ // +--> Derive-Secret(., "derived", "")
197
+ // v
198
+ // ECDHE -> HKDF-Extract = Handshake Secret
199
+ // +--> "c hs traffic" / "s hs traffic" (transcript: ClientHello..ServerHello)
200
+ // +--> Derive-Secret(., "derived", "")
201
+ // v
202
+ // 0 -> HKDF-Extract = Master Secret
203
+ // +--> "c ap traffic" / "s ap traffic" / "exp master"
204
+ // (transcript: ClientHello..server Finished)
205
+ // +--> "res master" (transcript: ClientHello..client Finished)
206
+
207
+ /**
208
+ * Early Secret = HKDF-Extract(salt: 0, IKM: PSK or zeros).
209
+ * @param {ScheduleHash} hash
210
+ * @param {Uint8Array | null} [psk]
211
+ * @returns {Promise<Uint8Array>}
212
+ */
213
+ export async function earlySecret(hash, psk) {
214
+ return hkdfExtract(hash, EMPTY, psk ?? zeros(hashLength(hash)));
215
+ }
216
+
217
+ /**
218
+ * Handshake Secret = HKDF-Extract(Derive-Secret(early, "derived", ""), ECDHE).
219
+ * @param {ScheduleHash} hash
220
+ * @param {Uint8Array} early
221
+ * @param {Uint8Array} ecdheShared
222
+ * @returns {Promise<Uint8Array>}
223
+ */
224
+ export async function deriveHandshakeSecret(hash, early, ecdheShared) {
225
+ const derived = await deriveSecret(hash, early, 'derived', await emptyHash(hash));
226
+ return hkdfExtract(hash, derived, ecdheShared);
227
+ }
228
+
229
+ /**
230
+ * Master Secret = HKDF-Extract(Derive-Secret(handshake, "derived", ""), 0).
231
+ * @param {ScheduleHash} hash
232
+ * @param {Uint8Array} handshakeSecret
233
+ * @returns {Promise<Uint8Array>}
234
+ */
235
+ export async function deriveMasterSecret(hash, handshakeSecret) {
236
+ const derived = await deriveSecret(hash, handshakeSecret, 'derived', await emptyHash(hash));
237
+ return hkdfExtract(hash, derived, zeros(hashLength(hash)));
238
+ }
239
+
240
+ /**
241
+ * {client,server}_handshake_traffic_secret. Transcript: ClientHello..ServerHello.
242
+ * @param {ScheduleHash} hash
243
+ * @param {Uint8Array} handshakeSecret
244
+ * @param {Uint8Array} transcriptHash
245
+ * @returns {Promise<{ client: Uint8Array, server: Uint8Array }>}
246
+ */
247
+ export async function handshakeTrafficSecrets(hash, handshakeSecret, transcriptHash) {
248
+ return {
249
+ client: await deriveSecret(hash, handshakeSecret, 'c hs traffic', transcriptHash),
250
+ server: await deriveSecret(hash, handshakeSecret, 's hs traffic', transcriptHash),
251
+ };
252
+ }
253
+
254
+ /**
255
+ * {client,server}_application_traffic_secret_0 and (unless suppressed) exporter_master_secret.
256
+ *
257
+ * `exporter` defaults on so the RFC 8448 vectors and any exporter user get the full set, but the
258
+ * handshake driver passes false: this package exposes no TLS-exporter interface, so deriving
259
+ * exporter_master_secret on every connection is one HKDF-Expand-Label of provably dead work on
260
+ * the hot path. Suppressing it there removes that work without changing any observable behaviour.
261
+ * @param {ScheduleHash} hash
262
+ * @param {Uint8Array} masterSecret
263
+ * @param {Uint8Array} transcriptHash
264
+ * @param {{ exporter?: boolean }} [opts]
265
+ * @returns {Promise<{ client: Uint8Array, server: Uint8Array, exporterMaster?: Uint8Array }>}
266
+ */
267
+ export async function applicationTrafficSecrets(hash, masterSecret, transcriptHash, { exporter = true } = {}) {
268
+ const out = {
269
+ client: await deriveSecret(hash, masterSecret, 'c ap traffic', transcriptHash),
270
+ server: await deriveSecret(hash, masterSecret, 's ap traffic', transcriptHash),
271
+ };
272
+ if (exporter) out.exporterMaster = await deriveSecret(hash, masterSecret, 'exp master', transcriptHash);
273
+ return out;
274
+ }
275
+
276
+ /**
277
+ * resumption_master_secret. Transcript: ClientHello..client Finished.
278
+ * @param {ScheduleHash} hash
279
+ * @param {Uint8Array} masterSecret
280
+ * @param {Uint8Array} transcriptHash
281
+ * @returns {Promise<Uint8Array>}
282
+ */
283
+ export async function resumptionMasterSecret(hash, masterSecret, transcriptHash) {
284
+ return deriveSecret(hash, masterSecret, 'res master', transcriptHash);
285
+ }
286
+
287
+ /**
288
+ * The PSK a NewSessionTicket names: HKDF-Expand-Label(res master, "resumption", nonce).
289
+ * @param {ScheduleHash} hash
290
+ * @param {Uint8Array} resumptionMaster
291
+ * @param {Uint8Array} ticketNonce
292
+ * @returns {Promise<Uint8Array>}
293
+ */
294
+ export async function resumptionPsk(hash, resumptionMaster, ticketNonce) {
295
+ return hkdfExpandLabel(hash, resumptionMaster, 'resumption', ticketNonce, hashLength(hash));
296
+ }
297
+
298
+ /**
299
+ * binder_key = Derive-Secret(Early Secret, "res binder", "") — RFC 8446 s7.1, the resumption
300
+ * variant. The "ext binder" sibling for externally provisioned PSKs is deliberately absent:
301
+ * this package mints PSKs only from NewSessionTicket, so an external-PSK binder key would be
302
+ * dead code with a security label on it. The binder value itself is finishedVerifyData over
303
+ * this key (RFC 8446 s4.2.11.2: "the PskBinderEntry is computed in the same way as the
304
+ * Finished message but with the BaseKey being the binder_key").
305
+ * @param {ScheduleHash} hash
306
+ * @param {Uint8Array} early the Early Secret extracted from the PSK being offered
307
+ * @returns {Promise<Uint8Array>}
308
+ */
309
+ export async function resumptionBinderKey(hash, early) {
310
+ return deriveSecret(hash, early, 'res binder', await emptyHash(hash));
311
+ }
312
+
313
+ /**
314
+ * finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length). RFC 8446 s4.4.4.
315
+ * @param {ScheduleHash} hash
316
+ * @param {Uint8Array} trafficSecret
317
+ * @returns {Promise<Uint8Array>}
318
+ */
319
+ export async function finishedKey(hash, trafficSecret) {
320
+ return hkdfExpandLabel(hash, trafficSecret, 'finished', EMPTY, hashLength(hash));
321
+ }
322
+
323
+ /**
324
+ * verify_data = HMAC(finished_key, Transcript-Hash).
325
+ * @param {ScheduleHash} hash
326
+ * @param {Uint8Array} trafficSecret
327
+ * @param {Uint8Array} transcriptHash
328
+ * @returns {Promise<Uint8Array>}
329
+ */
330
+ export async function finishedVerifyData(hash, trafficSecret, transcriptHash) {
331
+ return hmac(hash, await finishedKey(hash, trafficSecret), transcriptHash);
332
+ }
333
+
334
+ /**
335
+ * [sender]_write_key and [sender]_write_iv from a traffic secret. RFC 8446 s7.3.
336
+ * @param {ScheduleHash} hash
337
+ * @param {Uint8Array} trafficSecret
338
+ * @param {number} keyLen
339
+ * @param {number} ivLen
340
+ * @returns {Promise<{ key: Uint8Array, iv: Uint8Array }>}
341
+ */
342
+ export async function trafficKeys(hash, trafficSecret, keyLen, ivLen) {
343
+ return {
344
+ key: await hkdfExpandLabel(hash, trafficSecret, 'key', EMPTY, keyLen),
345
+ iv: await hkdfExpandLabel(hash, trafficSecret, 'iv', EMPTY, ivLen),
346
+ };
347
+ }
348
+
349
+ /**
350
+ * application_traffic_secret_N+1 for KeyUpdate. RFC 8446 s7.2.
351
+ * @param {ScheduleHash} hash
352
+ * @param {Uint8Array} trafficSecret
353
+ * @returns {Promise<Uint8Array>}
354
+ */
355
+ export async function nextTrafficSecret(hash, trafficSecret) {
356
+ return hkdfExpandLabel(hash, trafficSecret, 'traffic upd', EMPTY, hashLength(hash));
357
+ }
358
+
359
+ // ---------------------------------------------------------------- TLS 1.2 PRF (RFC 5246 s5)
360
+
361
+ /**
362
+ * PRF(secret, label, seed) = P_<hash>(secret, label + seed), where
363
+ * P_hash(secret, seed) = HMAC(secret, A(1) + seed) + HMAC(secret, A(2) + seed) + ...
364
+ * with A(0) = seed, A(i) = HMAC(secret, A(i-1)).
365
+ *
366
+ * TLS 1.2 replaced the 1.1 MD5/SHA-1 split PRF with a single suite-selected hash, which for
367
+ * every suite in constants.js is SHA-256 or SHA-384.
368
+ *
369
+ * @param {ScheduleHash} hash
370
+ * @param {Uint8Array} secret
371
+ * @param {string} label ASCII label, e.g. 'master secret'
372
+ * @param {Uint8Array} seed
373
+ * @param {number} length
374
+ * @returns {Promise<Uint8Array>}
375
+ */
376
+ export async function prf12(hash, secret, label, seed, length) {
377
+ if (!Number.isInteger(length) || length < 1) {
378
+ throw new TlsError(codes.CONFIG_INVALID, `PRF length ${length} is not a positive integer`,
379
+ { length });
380
+ }
381
+ const labelSeed = concat([utf8(label), seed]);
382
+ // One import for the whole P_hash iteration: the secret is the HMAC key throughout.
383
+ const key = await importHmacKey(hash, secret);
384
+ const mac = async (data) => new Uint8Array(await crypto.subtle.sign('HMAC', key, data));
385
+ const out = new Uint8Array(length);
386
+ let a = labelSeed;
387
+ for (let o = 0; o < length; o += hashLength(hash)) {
388
+ a = await mac(a);
389
+ const t = await mac(concat([a, labelSeed]));
390
+ out.set(t.subarray(0, Math.min(t.byteLength, length - o)), o);
391
+ }
392
+ return out;
393
+ }
394
+
395
+ /**
396
+ * master_secret = PRF(pre_master, "master secret", client_random + server_random)[0..47].
397
+ * @param {ScheduleHash} hash
398
+ * @param {Uint8Array} preMaster
399
+ * @param {Uint8Array} clientRandom
400
+ * @param {Uint8Array} serverRandom
401
+ * @returns {Promise<Uint8Array>}
402
+ */
403
+ export async function masterSecret12(hash, preMaster, clientRandom, serverRandom) {
404
+ return prf12(hash, preMaster, 'master secret', concat([clientRandom, serverRandom]), 48);
405
+ }
406
+
407
+ /**
408
+ * RFC 7627 extended master secret: the seed is the session hash (transcript through
409
+ * ClientKeyExchange) instead of the two randoms, which binds the master secret to the full
410
+ * handshake and kills the triple-handshake attack. This is the variant the handshake layer
411
+ * must offer and prefer; a server that refuses the extension still gets masterSecret12, but
412
+ * that acceptance is deliberately flagged by the handshake layer as a weaker session.
413
+ * @param {ScheduleHash} hash
414
+ * @param {Uint8Array} preMaster
415
+ * @param {Uint8Array} sessionHash
416
+ * @returns {Promise<Uint8Array>}
417
+ */
418
+ export async function extendedMasterSecret12(hash, preMaster, sessionHash) {
419
+ return prf12(hash, preMaster, 'extended master secret', sessionHash, 48);
420
+ }
421
+
422
+ /**
423
+ * key_block = PRF(master, "key expansion", server_random + client_random). RFC 5246 s6.3.
424
+ * Note the randoms swap order relative to the master secret derivation — that asymmetry is in
425
+ * the RFC, and the tests pin it. AEAD suites have no MAC keys (macLen 0), so the block is
426
+ * client_write_key + server_write_key + client_write_IV + server_write_IV, where the "IV" is
427
+ * the 4-byte implicit GCM salt of RFC 5288.
428
+ *
429
+ * @param {ScheduleHash} hash
430
+ * @param {Uint8Array} master
431
+ * @param {Uint8Array} clientRandom
432
+ * @param {Uint8Array} serverRandom
433
+ * @param {{ keyLen: number, fixedIvLen: number, macLen?: number }} lens
434
+ * @returns {Promise<{ clientWriteKey: Uint8Array, serverWriteKey: Uint8Array,
435
+ * clientWriteIv: Uint8Array, serverWriteIv: Uint8Array,
436
+ * clientWriteMacKey?: Uint8Array, serverWriteMacKey?: Uint8Array }>} MAC keys present only
437
+ * when macLen > 0, which no AEAD suite ever passes
438
+ */
439
+ export async function keyBlock12(hash, master, clientRandom, serverRandom, lens) {
440
+ const { keyLen, fixedIvLen, macLen = 0 } = lens;
441
+ const need = 2 * macLen + 2 * keyLen + 2 * fixedIvLen;
442
+ const block = await prf12(hash, master, 'key expansion', concat([serverRandom, clientRandom]),
443
+ need);
444
+ let o = 0;
445
+ const take = (n) => block.subarray(o, (o += n));
446
+ const clientWriteMacKey = take(macLen);
447
+ const serverWriteMacKey = take(macLen);
448
+ const out = {
449
+ clientWriteKey: take(keyLen),
450
+ serverWriteKey: take(keyLen),
451
+ clientWriteIv: take(fixedIvLen),
452
+ serverWriteIv: take(fixedIvLen),
453
+ };
454
+ if (macLen > 0) Object.assign(out, { clientWriteMacKey, serverWriteMacKey });
455
+ return out;
456
+ }
457
+
458
+ /**
459
+ * verify_data = PRF(master, "client finished" | "server finished", Hash(messages))[0..11].
460
+ * 12 bytes for every suite this package negotiates (RFC 5246 s7.4.9).
461
+ * @param {ScheduleHash} hash
462
+ * @param {Uint8Array} master
463
+ * @param {'client finished' | 'server finished'} label
464
+ * @param {Uint8Array} transcriptHash
465
+ * @returns {Promise<Uint8Array>}
466
+ */
467
+ export async function verifyData12(hash, master, label, transcriptHash) {
468
+ if (label !== 'client finished' && label !== 'server finished') {
469
+ throw new TlsError(codes.CONFIG_INVALID, `verify_data label ${JSON.stringify(label)} is not ` +
470
+ "'client finished' or 'server finished'", { label });
471
+ }
472
+ return prf12(hash, master, label, transcriptHash, 12);
473
+ }