lemon-tls 0.1.0 → 0.2.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/README.md +432 -122
- package/index.cjs +28 -0
- package/index.d.ts +283 -0
- package/index.js +70 -15
- package/lemontls.svg +1 -1
- package/package.json +120 -62
- package/src/compat.js +235 -0
- package/src/crypto.js +580 -0
- package/src/record.js +232 -0
- package/{secure_context.js → src/secure_context.js} +196 -196
- package/src/session/ecdh.js +61 -0
- package/src/session/message.js +203 -0
- package/src/session/signing.js +129 -0
- package/src/tls_session.js +2204 -0
- package/src/tls_socket.js +877 -0
- package/{utils.js → src/utils.js} +100 -87
- package/{wire.js → src/wire.js} +1499 -1672
- package/crypto.js +0 -383
- package/tls_server.js +0 -0
- package/tls_session.js +0 -1441
- package/tls_socket.js +0 -456
package/src/crypto.js
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
concatUint8Arrays
|
|
4
|
+
} from './utils.js';
|
|
5
|
+
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// ============================================================
|
|
10
|
+
// Cipher suite registry
|
|
11
|
+
// ============================================================
|
|
12
|
+
|
|
13
|
+
const TLS_CIPHER_SUITES = {
|
|
14
|
+
// ----------------------
|
|
15
|
+
// TLS 1.3 (RFC 8446)
|
|
16
|
+
// ----------------------
|
|
17
|
+
0x1301: { // TLS_AES_128_GCM_SHA256
|
|
18
|
+
name: 'TLS_AES_128_GCM_SHA256', standardName: 'TLS_AES_128_GCM_SHA256',
|
|
19
|
+
tls: 13,
|
|
20
|
+
kex: 'TLS13',
|
|
21
|
+
sig: 'TLS13',
|
|
22
|
+
cipher: 'AES_128_GCM',
|
|
23
|
+
aead: true,
|
|
24
|
+
keylen: 16,
|
|
25
|
+
ivlen: 12,
|
|
26
|
+
hash: 'sha256'
|
|
27
|
+
},
|
|
28
|
+
0x1302: { // TLS_AES_256_GCM_SHA384
|
|
29
|
+
name: 'TLS_AES_256_GCM_SHA384', standardName: 'TLS_AES_256_GCM_SHA384',
|
|
30
|
+
tls: 13,
|
|
31
|
+
kex: 'TLS13',
|
|
32
|
+
sig: 'TLS13',
|
|
33
|
+
cipher: 'AES_256_GCM',
|
|
34
|
+
aead: true,
|
|
35
|
+
keylen: 32,
|
|
36
|
+
ivlen: 12,
|
|
37
|
+
hash: 'sha384'
|
|
38
|
+
},
|
|
39
|
+
0x1303: { // TLS_CHACHA20_POLY1305_SHA256
|
|
40
|
+
name: 'TLS_CHACHA20_POLY1305_SHA256', standardName: 'TLS_CHACHA20_POLY1305_SHA256',
|
|
41
|
+
tls: 13,
|
|
42
|
+
kex: 'TLS13',
|
|
43
|
+
sig: 'TLS13',
|
|
44
|
+
cipher: 'CHACHA20_POLY1305',
|
|
45
|
+
aead: true,
|
|
46
|
+
keylen: 32,
|
|
47
|
+
ivlen: 12,
|
|
48
|
+
hash: 'sha256'
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// ----------------------
|
|
52
|
+
// TLS 1.2 AEAD (GCM / CHACHA20)
|
|
53
|
+
// ----------------------
|
|
54
|
+
0xC02F: { // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
55
|
+
name: 'ECDHE-RSA-AES128-GCM-SHA256', standardName: 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
|
|
56
|
+
tls: 12,
|
|
57
|
+
kex: 'ECDHE_RSA',
|
|
58
|
+
sig: 'RSA',
|
|
59
|
+
cipher: 'AES_128_GCM',
|
|
60
|
+
aead: true,
|
|
61
|
+
keylen: 16,
|
|
62
|
+
fixed_ivlen: 4,
|
|
63
|
+
record_ivlen: 8,
|
|
64
|
+
ivlen: 12,
|
|
65
|
+
hash: 'sha256'
|
|
66
|
+
},
|
|
67
|
+
0xC030: { // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
68
|
+
name: 'ECDHE-RSA-AES256-GCM-SHA384', standardName: 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
|
|
69
|
+
tls: 12,
|
|
70
|
+
kex: 'ECDHE_RSA',
|
|
71
|
+
sig: 'RSA',
|
|
72
|
+
cipher: 'AES_256_GCM',
|
|
73
|
+
aead: true,
|
|
74
|
+
keylen: 32,
|
|
75
|
+
fixed_ivlen: 4,
|
|
76
|
+
record_ivlen: 8,
|
|
77
|
+
ivlen: 12,
|
|
78
|
+
hash: 'sha384'
|
|
79
|
+
},
|
|
80
|
+
0xC02B: { // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
81
|
+
name: 'ECDHE-ECDSA-AES128-GCM-SHA256', standardName: 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
|
|
82
|
+
tls: 12,
|
|
83
|
+
kex: 'ECDHE_ECDSA',
|
|
84
|
+
sig: 'ECDSA',
|
|
85
|
+
cipher: 'AES_128_GCM',
|
|
86
|
+
aead: true,
|
|
87
|
+
keylen: 16,
|
|
88
|
+
fixed_ivlen: 4,
|
|
89
|
+
record_ivlen: 8,
|
|
90
|
+
ivlen: 12,
|
|
91
|
+
hash: 'sha256'
|
|
92
|
+
},
|
|
93
|
+
0xC02C: { // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
|
94
|
+
name: 'ECDHE-ECDSA-AES256-GCM-SHA384', standardName: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
|
|
95
|
+
tls: 12,
|
|
96
|
+
kex: 'ECDHE_ECDSA',
|
|
97
|
+
sig: 'ECDSA',
|
|
98
|
+
cipher: 'AES_256_GCM',
|
|
99
|
+
aead: true,
|
|
100
|
+
keylen: 32,
|
|
101
|
+
fixed_ivlen: 4,
|
|
102
|
+
record_ivlen: 8,
|
|
103
|
+
ivlen: 12,
|
|
104
|
+
hash: 'sha384'
|
|
105
|
+
},
|
|
106
|
+
0xCCA8: { // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
107
|
+
name: 'ECDHE-RSA-CHACHA20-POLY1305', standardName: 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256',
|
|
108
|
+
tls: 12,
|
|
109
|
+
kex: 'ECDHE_RSA',
|
|
110
|
+
sig: 'RSA',
|
|
111
|
+
cipher: 'CHACHA20_POLY1305',
|
|
112
|
+
aead: true,
|
|
113
|
+
keylen: 32,
|
|
114
|
+
fixed_ivlen: 12,
|
|
115
|
+
record_ivlen: 0,
|
|
116
|
+
ivlen: 12,
|
|
117
|
+
hash: 'sha256'
|
|
118
|
+
},
|
|
119
|
+
0xCCA9: { // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
|
120
|
+
name: 'ECDHE-ECDSA-CHACHA20-POLY1305', standardName: 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256',
|
|
121
|
+
tls: 12,
|
|
122
|
+
kex: 'ECDHE_ECDSA',
|
|
123
|
+
sig: 'ECDSA',
|
|
124
|
+
cipher: 'CHACHA20_POLY1305',
|
|
125
|
+
aead: true,
|
|
126
|
+
keylen: 32,
|
|
127
|
+
fixed_ivlen: 12,
|
|
128
|
+
record_ivlen: 0,
|
|
129
|
+
ivlen: 12,
|
|
130
|
+
hash: 'sha256'
|
|
131
|
+
},
|
|
132
|
+
0xCCAA: { // TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
133
|
+
tls: 12,
|
|
134
|
+
kex: 'DHE_RSA',
|
|
135
|
+
sig: 'RSA',
|
|
136
|
+
cipher: 'CHACHA20_POLY1305',
|
|
137
|
+
aead: true,
|
|
138
|
+
keylen: 32,
|
|
139
|
+
fixed_ivlen: 4,
|
|
140
|
+
record_ivlen: 8,
|
|
141
|
+
ivlen: 12,
|
|
142
|
+
hash: 'sha256'
|
|
143
|
+
},
|
|
144
|
+
0x009C: { // TLS_RSA_WITH_AES_128_GCM_SHA256
|
|
145
|
+
name: 'AES128-GCM-SHA256', standardName: 'TLS_RSA_WITH_AES_128_GCM_SHA256',
|
|
146
|
+
tls: 12,
|
|
147
|
+
kex: 'RSA',
|
|
148
|
+
sig: 'RSA',
|
|
149
|
+
cipher: 'AES_128_GCM',
|
|
150
|
+
aead: true,
|
|
151
|
+
keylen: 16,
|
|
152
|
+
fixed_ivlen: 4,
|
|
153
|
+
record_ivlen: 8,
|
|
154
|
+
ivlen: 12,
|
|
155
|
+
hash: 'sha256'
|
|
156
|
+
},
|
|
157
|
+
0x009D: { // TLS_RSA_WITH_AES_256_GCM_SHA384
|
|
158
|
+
name: 'AES256-GCM-SHA384', standardName: 'TLS_RSA_WITH_AES_256_GCM_SHA384',
|
|
159
|
+
tls: 12,
|
|
160
|
+
kex: 'RSA',
|
|
161
|
+
sig: 'RSA',
|
|
162
|
+
cipher: 'AES_256_GCM',
|
|
163
|
+
aead: true,
|
|
164
|
+
keylen: 32,
|
|
165
|
+
fixed_ivlen: 4,
|
|
166
|
+
record_ivlen: 8,
|
|
167
|
+
ivlen: 12,
|
|
168
|
+
hash: 'sha384'
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// ----------------------
|
|
172
|
+
// TLS 1.2 CBC (Legacy)
|
|
173
|
+
// ----------------------
|
|
174
|
+
0xC013: { // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
|
175
|
+
tls: 12,
|
|
176
|
+
kex: 'ECDHE_RSA',
|
|
177
|
+
sig: 'RSA',
|
|
178
|
+
cipher: 'AES_128_CBC',
|
|
179
|
+
aead: false,
|
|
180
|
+
keylen: 16,
|
|
181
|
+
ivlen: 16,
|
|
182
|
+
mac: 'sha1',
|
|
183
|
+
maclen: 20,
|
|
184
|
+
hash: 'sha256'
|
|
185
|
+
},
|
|
186
|
+
0xC014: { // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
|
|
187
|
+
tls: 12,
|
|
188
|
+
kex: 'ECDHE_RSA',
|
|
189
|
+
sig: 'RSA',
|
|
190
|
+
cipher: 'AES_256_CBC',
|
|
191
|
+
aead: false,
|
|
192
|
+
keylen: 32,
|
|
193
|
+
ivlen: 16,
|
|
194
|
+
mac: 'sha1',
|
|
195
|
+
maclen: 20,
|
|
196
|
+
hash: 'sha256'
|
|
197
|
+
},
|
|
198
|
+
0x003C: { // TLS_RSA_WITH_AES_128_CBC_SHA256
|
|
199
|
+
tls: 12,
|
|
200
|
+
kex: 'RSA',
|
|
201
|
+
sig: 'RSA',
|
|
202
|
+
cipher: 'AES_128_CBC',
|
|
203
|
+
aead: false,
|
|
204
|
+
keylen: 16,
|
|
205
|
+
ivlen: 16,
|
|
206
|
+
mac: 'sha256',
|
|
207
|
+
maclen: 32,
|
|
208
|
+
hash: 'sha256'
|
|
209
|
+
},
|
|
210
|
+
0x003D: { // TLS_RSA_WITH_AES_256_CBC_SHA256
|
|
211
|
+
tls: 12,
|
|
212
|
+
kex: 'RSA',
|
|
213
|
+
sig: 'RSA',
|
|
214
|
+
cipher: 'AES_256_CBC',
|
|
215
|
+
aead: false,
|
|
216
|
+
keylen: 32,
|
|
217
|
+
ivlen: 16,
|
|
218
|
+
mac: 'sha256',
|
|
219
|
+
maclen: 32,
|
|
220
|
+
hash: 'sha256'
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
// ============================================================
|
|
226
|
+
// Hash helpers
|
|
227
|
+
// Drop-in for noble: callable as hashFn(data) with .outputLen
|
|
228
|
+
// ============================================================
|
|
229
|
+
|
|
230
|
+
function makeHashFn(algorithm, outputLen) {
|
|
231
|
+
let fn = function (data) {
|
|
232
|
+
return new Uint8Array(crypto.createHash(algorithm).update(data).digest());
|
|
233
|
+
};
|
|
234
|
+
fn.outputLen = outputLen;
|
|
235
|
+
return fn;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let sha256 = makeHashFn('sha256', 32);
|
|
239
|
+
let sha384 = makeHashFn('sha384', 48);
|
|
240
|
+
|
|
241
|
+
function getHashFn(hashName) {
|
|
242
|
+
if (hashName === 'sha256') return sha256;
|
|
243
|
+
if (hashName === 'sha384') return sha384;
|
|
244
|
+
throw new Error('Unsupported hash: ' + hashName);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getHashLen(hashName) {
|
|
248
|
+
return getHashFn(hashName).outputLen | 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
// ============================================================
|
|
253
|
+
// HMAC
|
|
254
|
+
// ============================================================
|
|
255
|
+
|
|
256
|
+
function hmac(hashName, keyU8, dataU8) {
|
|
257
|
+
return new Uint8Array(
|
|
258
|
+
crypto.createHmac(hashName, keyU8).update(dataU8).digest()
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
// ============================================================
|
|
264
|
+
// HKDF Extract / Expand (RFC 5869)
|
|
265
|
+
// ============================================================
|
|
266
|
+
|
|
267
|
+
function hkdf_extract(hashName, saltU8, ikmU8) {
|
|
268
|
+
// RFC 5869 section 2.2: if salt not provided, set to HashLen zeros
|
|
269
|
+
let hashLen = getHashLen(hashName);
|
|
270
|
+
let salt = (saltU8.length === 0) ? Buffer.alloc(hashLen) : saltU8;
|
|
271
|
+
return new Uint8Array(
|
|
272
|
+
crypto.createHmac(hashName, salt).update(ikmU8).digest()
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function hkdf_expand(hashName, prkU8, infoU8, length) {
|
|
277
|
+
let hashLen = getHashLen(hashName);
|
|
278
|
+
let N = Math.ceil(length / hashLen);
|
|
279
|
+
let output = Buffer.alloc(N * hashLen);
|
|
280
|
+
let prev = Buffer.alloc(0);
|
|
281
|
+
|
|
282
|
+
for (let i = 1; i <= N; i++) {
|
|
283
|
+
let h = crypto.createHmac(hashName, prkU8);
|
|
284
|
+
h.update(prev);
|
|
285
|
+
h.update(infoU8);
|
|
286
|
+
h.update(Buffer.from([i]));
|
|
287
|
+
prev = h.digest();
|
|
288
|
+
prev.copy(output, (i - 1) * hashLen);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return new Uint8Array(output.subarray(0, length));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
// ============================================================
|
|
296
|
+
// TLS 1.3 HKDF-Expand-Label (RFC 8446 section 7.1)
|
|
297
|
+
// ============================================================
|
|
298
|
+
|
|
299
|
+
function build_hkdf_label(label, context, length) {
|
|
300
|
+
let prefix = 'tls13 ';
|
|
301
|
+
let enc = new TextEncoder();
|
|
302
|
+
let full = enc.encode(prefix + label);
|
|
303
|
+
const info = new Uint8Array(2 + 1 + full.length + 1 + context.length);
|
|
304
|
+
|
|
305
|
+
info[0] = (length >>> 8) & 0xff;
|
|
306
|
+
info[1] = (length ) & 0xff;
|
|
307
|
+
info[2] = full.length;
|
|
308
|
+
info.set(full, 3);
|
|
309
|
+
|
|
310
|
+
let ofs = 3 + full.length;
|
|
311
|
+
info[ofs] = context.length;
|
|
312
|
+
info.set(context, ofs + 1);
|
|
313
|
+
|
|
314
|
+
return info;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function hkdf_expand_label(hashName, secret, label, context, length) {
|
|
318
|
+
let info = build_hkdf_label(label, context, length | 0);
|
|
319
|
+
return hkdf_expand(hashName, secret, info, length | 0);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
// ============================================================
|
|
324
|
+
// TLS 1.3: derive handshake traffic secrets
|
|
325
|
+
// ============================================================
|
|
326
|
+
|
|
327
|
+
function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
|
|
328
|
+
let hashFn = getHashFn(hashName);
|
|
329
|
+
let hashLen = hashFn.outputLen | 0;
|
|
330
|
+
const empty = new Uint8Array(0);
|
|
331
|
+
const zeros = new Uint8Array(hashLen);
|
|
332
|
+
|
|
333
|
+
let early_secret = hkdf_extract(hashName, empty, zeros);
|
|
334
|
+
let h_empty = hashFn(empty);
|
|
335
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
336
|
+
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
337
|
+
let transcript_hash = hashFn(transcript);
|
|
338
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
339
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
handshake_secret: handshake_secret,
|
|
343
|
+
client_handshake_traffic_secret: client_handshake_traffic_secret,
|
|
344
|
+
server_handshake_traffic_secret: server_handshake_traffic_secret,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
// ============================================================
|
|
350
|
+
// TLS 1.3: derive application traffic secrets
|
|
351
|
+
// ============================================================
|
|
352
|
+
|
|
353
|
+
function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
|
|
354
|
+
let hashFn = getHashFn(hashName);
|
|
355
|
+
let hashLen = hashFn.outputLen | 0;
|
|
356
|
+
const empty = new Uint8Array(0);
|
|
357
|
+
const zeros = new Uint8Array(hashLen);
|
|
358
|
+
|
|
359
|
+
let h_empty = hashFn(empty);
|
|
360
|
+
let derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
|
|
361
|
+
let master_secret = hkdf_extract(hashName, derived_secret, zeros);
|
|
362
|
+
let transcript_hash = hashFn(transcript);
|
|
363
|
+
let client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
|
|
364
|
+
let server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
client_app_traffic_secret: client_app_traffic_secret,
|
|
368
|
+
server_app_traffic_secret: server_app_traffic_secret,
|
|
369
|
+
master_secret: master_secret
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
// ============================================================
|
|
375
|
+
// TLS 1.3: resumption master secret (RFC 8446 §7.1)
|
|
376
|
+
// ============================================================
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Derive the resumption_master_secret from the master_secret.
|
|
380
|
+
* transcript = all handshake messages including both Finished.
|
|
381
|
+
*/
|
|
382
|
+
function derive_resumption_master_secret(hashName, master_secret, transcript) {
|
|
383
|
+
let hashFn = getHashFn(hashName);
|
|
384
|
+
let hashLen = hashFn.outputLen | 0;
|
|
385
|
+
let transcript_hash = hashFn(transcript);
|
|
386
|
+
return hkdf_expand_label(hashName, master_secret, 'res master', transcript_hash, hashLen);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Derive PSK from a resumption_master_secret + ticket_nonce.
|
|
391
|
+
* Used by the server when creating a ticket, and by the client when resuming.
|
|
392
|
+
*/
|
|
393
|
+
function derive_psk(hashName, resumption_master_secret, ticket_nonce) {
|
|
394
|
+
let hashLen = getHashFn(hashName).outputLen | 0;
|
|
395
|
+
return hkdf_expand_label(hashName, resumption_master_secret, 'resumption', ticket_nonce, hashLen);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Derive the binder_key for PSK binders in ClientHello.
|
|
400
|
+
* For resumption PSK, label is "res binder".
|
|
401
|
+
* For external PSK, label is "ext binder".
|
|
402
|
+
*/
|
|
403
|
+
function derive_binder_key(hashName, psk, isExternal) {
|
|
404
|
+
let hashFn = getHashFn(hashName);
|
|
405
|
+
let hashLen = hashFn.outputLen | 0;
|
|
406
|
+
const empty = new Uint8Array(0);
|
|
407
|
+
const zeros = new Uint8Array(hashLen);
|
|
408
|
+
|
|
409
|
+
let early_secret = hkdf_extract(hashName, empty, psk);
|
|
410
|
+
let h_empty = hashFn(empty);
|
|
411
|
+
let label = isExternal ? 'ext binder' : 'res binder';
|
|
412
|
+
return hkdf_expand_label(hashName, early_secret, label, h_empty, hashLen);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Compute a PSK binder value.
|
|
417
|
+
* binder_key = derive_binder_key(...)
|
|
418
|
+
* transcript = ClientHello up to (but not including) the binders list.
|
|
419
|
+
*/
|
|
420
|
+
function compute_psk_binder(hashName, binder_key, truncated_transcript) {
|
|
421
|
+
let hashFn = getHashFn(hashName);
|
|
422
|
+
let transcript_hash = hashFn(truncated_transcript);
|
|
423
|
+
return hmac(hashName, binder_key, transcript_hash);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Derive handshake secrets for a PSK-based handshake (with ECDHE).
|
|
428
|
+
* Uses the PSK as input to early_secret instead of zeros.
|
|
429
|
+
*/
|
|
430
|
+
function derive_handshake_traffic_secrets_psk(hashName, psk, shared_secret, transcript) {
|
|
431
|
+
let hashFn = getHashFn(hashName);
|
|
432
|
+
let hashLen = hashFn.outputLen | 0;
|
|
433
|
+
const empty = new Uint8Array(0);
|
|
434
|
+
|
|
435
|
+
let early_secret = hkdf_extract(hashName, empty, psk);
|
|
436
|
+
let h_empty = hashFn(empty);
|
|
437
|
+
let derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
438
|
+
let handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
439
|
+
let transcript_hash = hashFn(transcript);
|
|
440
|
+
let client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
441
|
+
let server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
handshake_secret: handshake_secret,
|
|
445
|
+
client_handshake_traffic_secret: client_handshake_traffic_secret,
|
|
446
|
+
server_handshake_traffic_secret: server_handshake_traffic_secret,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
// ============================================================
|
|
452
|
+
// TLS 1.2 PRF (RFC 5246 section 5)
|
|
453
|
+
// ============================================================
|
|
454
|
+
|
|
455
|
+
function tls12_prf(secret, labelStr, seed, outLen, hashName) {
|
|
456
|
+
let label = new TextEncoder().encode(labelStr);
|
|
457
|
+
let fullSeed = concatUint8Arrays([label, seed]);
|
|
458
|
+
|
|
459
|
+
let a = fullSeed;
|
|
460
|
+
let out = new Uint8Array(0);
|
|
461
|
+
|
|
462
|
+
while (out.length < outLen) {
|
|
463
|
+
a = hmac(hashName, secret, a);
|
|
464
|
+
let block = hmac(hashName, secret, concatUint8Arrays([a, fullSeed]));
|
|
465
|
+
const tmp = new Uint8Array(out.length + block.length);
|
|
466
|
+
tmp.set(out, 0);
|
|
467
|
+
tmp.set(block, out.length);
|
|
468
|
+
out = tmp;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return out.slice(0, outLen);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
// ============================================================
|
|
476
|
+
// TLS 1.2: key_block from master_secret
|
|
477
|
+
// ============================================================
|
|
478
|
+
|
|
479
|
+
function tls_derive_from_master_secret_tls12(master_secret, server_random, client_random, cipher_suite) {
|
|
480
|
+
let p = TLS_CIPHER_SUITES[cipher_suite];
|
|
481
|
+
if (!p || p.tls !== 12) throw new Error('cipher suite not TLS 1.2 or not mapped');
|
|
482
|
+
|
|
483
|
+
let hashName = p.hash;
|
|
484
|
+
let macLen = p.aead ? 0 : (p.maclen || 0);
|
|
485
|
+
let ivFromKbLen = p.aead ? (p.fixed_ivlen || 0) : 0;
|
|
486
|
+
let need = (2 * macLen) + (2 * p.keylen) + (2 * ivFromKbLen);
|
|
487
|
+
|
|
488
|
+
let key_block = tls12_prf(
|
|
489
|
+
master_secret,
|
|
490
|
+
"key expansion",
|
|
491
|
+
concatUint8Arrays([server_random, client_random]),
|
|
492
|
+
need,
|
|
493
|
+
hashName
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
let off = 0;
|
|
497
|
+
let c_mac = null, s_mac = null;
|
|
498
|
+
|
|
499
|
+
if (!p.aead && macLen > 0) {
|
|
500
|
+
c_mac = key_block.slice(off, off + macLen); off += macLen;
|
|
501
|
+
s_mac = key_block.slice(off, off + macLen); off += macLen;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
let c_key = key_block.slice(off, off + p.keylen); off += p.keylen;
|
|
505
|
+
let s_key = key_block.slice(off, off + p.keylen); off += p.keylen;
|
|
506
|
+
|
|
507
|
+
let c_iv_salt = ivFromKbLen ? key_block.slice(off, off + ivFromKbLen) : null; off += ivFromKbLen;
|
|
508
|
+
let s_iv_salt = ivFromKbLen ? key_block.slice(off, off + ivFromKbLen) : null; off += ivFromKbLen;
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
client_mac: c_mac,
|
|
512
|
+
server_mac: s_mac,
|
|
513
|
+
client_key: c_key,
|
|
514
|
+
server_key: s_key,
|
|
515
|
+
client_iv: c_iv_salt,
|
|
516
|
+
server_iv: s_iv_salt,
|
|
517
|
+
aead: !!p.aead,
|
|
518
|
+
cipher: p.cipher,
|
|
519
|
+
prf_hash: hashName,
|
|
520
|
+
key_len: p.keylen,
|
|
521
|
+
fixed_ivlen: ivFromKbLen,
|
|
522
|
+
record_ivlen: p.aead ? (p.record_ivlen || 0) : 16
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
// ============================================================
|
|
528
|
+
// TLS 1.3 CertificateVerify — to-be-signed construction
|
|
529
|
+
// ============================================================
|
|
530
|
+
|
|
531
|
+
function build_cert_verify_tbs(hashName, isServer, transcript) {
|
|
532
|
+
let label = new TextEncoder().encode(
|
|
533
|
+
isServer ? "TLS 1.3, server CertificateVerify" : "TLS 1.3, client CertificateVerify"
|
|
534
|
+
);
|
|
535
|
+
const separator = new Uint8Array([0x00]);
|
|
536
|
+
const padding = new Uint8Array(64).fill(0x20);
|
|
537
|
+
let transcript_hash = getHashFn(hashName)(transcript);
|
|
538
|
+
|
|
539
|
+
return concatUint8Arrays([padding, label, separator, transcript_hash]);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
// ============================================================
|
|
544
|
+
// TLS 1.3 Finished verify_data
|
|
545
|
+
// ============================================================
|
|
546
|
+
|
|
547
|
+
function get_handshake_finished(hashName, traffic_secret, transcript) {
|
|
548
|
+
let hashLen = getHashLen(hashName);
|
|
549
|
+
const empty = new Uint8Array(0);
|
|
550
|
+
let finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
|
|
551
|
+
let transcript_hash = getHashFn(hashName)(transcript);
|
|
552
|
+
return hmac(hashName, finished_key, transcript_hash);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
// ============================================================
|
|
557
|
+
// Exports — identical API surface
|
|
558
|
+
// ============================================================
|
|
559
|
+
|
|
560
|
+
export {
|
|
561
|
+
TLS_CIPHER_SUITES,
|
|
562
|
+
getHashFn,
|
|
563
|
+
getHashLen,
|
|
564
|
+
hmac,
|
|
565
|
+
hkdf_extract,
|
|
566
|
+
hkdf_expand,
|
|
567
|
+
build_hkdf_label,
|
|
568
|
+
hkdf_expand_label,
|
|
569
|
+
tls_derive_from_master_secret_tls12,
|
|
570
|
+
tls12_prf,
|
|
571
|
+
derive_handshake_traffic_secrets,
|
|
572
|
+
derive_app_traffic_secrets,
|
|
573
|
+
derive_resumption_master_secret,
|
|
574
|
+
derive_psk,
|
|
575
|
+
derive_binder_key,
|
|
576
|
+
compute_psk_binder,
|
|
577
|
+
derive_handshake_traffic_secrets_psk,
|
|
578
|
+
build_cert_verify_tbs,
|
|
579
|
+
get_handshake_finished
|
|
580
|
+
};
|