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,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
|
+
}
|