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
|
@@ -0,0 +1,2204 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
TLS_CIPHER_SUITES,
|
|
6
|
+
build_cert_verify_tbs,
|
|
7
|
+
get_handshake_finished,
|
|
8
|
+
tls12_prf,
|
|
9
|
+
derive_handshake_traffic_secrets,
|
|
10
|
+
derive_app_traffic_secrets,
|
|
11
|
+
derive_resumption_master_secret,
|
|
12
|
+
derive_psk,
|
|
13
|
+
derive_binder_key,
|
|
14
|
+
compute_psk_binder,
|
|
15
|
+
derive_handshake_traffic_secrets_psk,
|
|
16
|
+
hkdf_expand_label,
|
|
17
|
+
getHashFn,
|
|
18
|
+
} from './crypto.js';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
concatUint8Arrays,
|
|
22
|
+
arraysEqual,
|
|
23
|
+
uint8Equal
|
|
24
|
+
} from './utils.js';
|
|
25
|
+
|
|
26
|
+
import * as wire from './wire.js';
|
|
27
|
+
|
|
28
|
+
// Extracted modules
|
|
29
|
+
import { pick_scheme, sign_with_scheme } from './session/signing.js';
|
|
30
|
+
import createSecureContext from './secure_context.js';
|
|
31
|
+
import { x25519_get_public_key, x25519_get_shared_secret, p256_generate_keypair, p256_get_shared_secret } from './session/ecdh.js';
|
|
32
|
+
import { build_tls_message, parse_tls_message } from './session/message.js';
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function TLSSession(options){
|
|
36
|
+
if (!(this instanceof TLSSession)) return new TLSSession(options);
|
|
37
|
+
options = options || {};
|
|
38
|
+
|
|
39
|
+
const ev = new EventEmitter();
|
|
40
|
+
|
|
41
|
+
let context = {
|
|
42
|
+
state: 'new', //new | negotiating | ...
|
|
43
|
+
isServer: !!options.isServer,
|
|
44
|
+
rejectUnauthorized: options.rejectUnauthorized !== false, // default true
|
|
45
|
+
ca: options.ca || null, // CA certificates (PEM strings or Buffers)
|
|
46
|
+
|
|
47
|
+
SNICallback: options.SNICallback || null,
|
|
48
|
+
ticketKeys: options.ticketKeys || null, // 48 bytes for ticket encryption
|
|
49
|
+
|
|
50
|
+
// Advanced options
|
|
51
|
+
maxHandshakeSize: options.maxHandshakeSize || 0, // 0 = no limit
|
|
52
|
+
customExtensions: options.customExtensions || [], // [{type:0xNN, data:Uint8Array}]
|
|
53
|
+
handshakeBytes: 0,
|
|
54
|
+
handshakeStartTime: null,
|
|
55
|
+
handshakeEndTime: null,
|
|
56
|
+
rawClientHello: null, // saved for JA3/JA4 and 'clienthello' event
|
|
57
|
+
|
|
58
|
+
//local stuff...
|
|
59
|
+
local_sni: options.servername || null,
|
|
60
|
+
local_session_id: null,
|
|
61
|
+
|
|
62
|
+
local_random: null,
|
|
63
|
+
local_extensions: [],
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
local_supported_versions: [],
|
|
67
|
+
local_supported_alpns: [],
|
|
68
|
+
|
|
69
|
+
local_supported_cipher_suites: [],
|
|
70
|
+
local_supported_signature_algorithms: [],
|
|
71
|
+
local_supported_groups: [],
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
//remote stuff...
|
|
75
|
+
remote_sni: null,
|
|
76
|
+
remote_session_id: null,
|
|
77
|
+
|
|
78
|
+
remote_random: null,
|
|
79
|
+
remote_extensions: [],
|
|
80
|
+
|
|
81
|
+
remote_supported_versions: [],
|
|
82
|
+
remote_supported_alpns: [],
|
|
83
|
+
|
|
84
|
+
remote_supported_cipher_suites: [],
|
|
85
|
+
remote_supported_signature_algorithms: [],
|
|
86
|
+
remote_supported_groups: [],
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
//selected stuff...
|
|
90
|
+
selected_extensions: [],
|
|
91
|
+
selected_sni: null,
|
|
92
|
+
selected_session_id: null,// for TLS 1.2 only
|
|
93
|
+
|
|
94
|
+
selected_version: null,
|
|
95
|
+
selected_alpn: null,
|
|
96
|
+
selected_cipher_suite: null,
|
|
97
|
+
selected_signature_algorithm: null,
|
|
98
|
+
selected_group: null,
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
local_key_groups: {},
|
|
103
|
+
remote_key_groups: {},
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
ecdhe_shared_secret: null,
|
|
107
|
+
base_secret: null,
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
transcript: [],
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
//both
|
|
114
|
+
hello_sent: false,
|
|
115
|
+
finished_sent: false,
|
|
116
|
+
cert_sent: false,
|
|
117
|
+
|
|
118
|
+
//1.2 only
|
|
119
|
+
key_exchange_sent: false,
|
|
120
|
+
hello_done_sent: false,
|
|
121
|
+
remote_hello_done: false,
|
|
122
|
+
use_extended_master_secret: false,
|
|
123
|
+
|
|
124
|
+
//1.3 only
|
|
125
|
+
encrypted_exts_sent: false,
|
|
126
|
+
cert_verify_sent: false,
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
message_sent_seq: 0,
|
|
130
|
+
|
|
131
|
+
remote_finished: null,
|
|
132
|
+
expected_remote_finished: null,
|
|
133
|
+
remote_finished_ok: false,
|
|
134
|
+
local_finished_data: null, // saved for getFinished()
|
|
135
|
+
remote_finished_data: null, // saved for getPeerFinished()
|
|
136
|
+
|
|
137
|
+
remote_handshake_traffic_secret: null,
|
|
138
|
+
local_handshake_traffic_secret: null,
|
|
139
|
+
|
|
140
|
+
remote_app_traffic_secret: null,
|
|
141
|
+
local_app_traffic_secret: null,
|
|
142
|
+
|
|
143
|
+
local_cert_chain: null,
|
|
144
|
+
remote_cert_chain: null,
|
|
145
|
+
peerAuthorized: false,
|
|
146
|
+
authorizationError: null,
|
|
147
|
+
|
|
148
|
+
selected_cert: null,
|
|
149
|
+
|
|
150
|
+
cert_private_key: null,
|
|
151
|
+
|
|
152
|
+
// Client certificate authentication
|
|
153
|
+
requestCert: !!options.requestCert, // server: send CertificateRequest?
|
|
154
|
+
clientCert: options.cert || null, // client: cert to send if requested
|
|
155
|
+
clientKey: options.key || null, // client: private key for CertificateVerify
|
|
156
|
+
certificateRequested: false, // client: server sent CertificateRequest?
|
|
157
|
+
certificateRequestSent: false, // server: we sent CertificateRequest?
|
|
158
|
+
certificateRequestContext: null,
|
|
159
|
+
certificateRequestSigAlgs: [],
|
|
160
|
+
clientCertSent: false,
|
|
161
|
+
|
|
162
|
+
// HelloRetryRequest
|
|
163
|
+
helloRetried: false, // true if HRR was sent/received
|
|
164
|
+
|
|
165
|
+
// TLS 1.3 resumption
|
|
166
|
+
tls13_master_secret: null,
|
|
167
|
+
resumption_master_secret: null,
|
|
168
|
+
ticket_nonce_counter: 0,
|
|
169
|
+
session_ticket_sent: false,
|
|
170
|
+
noTickets: !!options.noTickets,
|
|
171
|
+
psk_offered: null, // client: { identity, psk, cipher } offered in ClientHello
|
|
172
|
+
psk_accepted: false, // server accepted PSK → abbreviated handshake
|
|
173
|
+
isResumed: false, // true if PSK was accepted
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function process_income_message(data){
|
|
177
|
+
|
|
178
|
+
// Track handshake start time
|
|
179
|
+
if (context.handshakeStartTime === null) context.handshakeStartTime = Date.now();
|
|
180
|
+
|
|
181
|
+
// Track handshake size and enforce limit
|
|
182
|
+
context.handshakeBytes += data.length;
|
|
183
|
+
if (context.maxHandshakeSize > 0 && context.handshakeBytes > context.maxHandshakeSize) {
|
|
184
|
+
ev.emit('error', new Error('Handshake size exceeded maxHandshakeSize (' + context.maxHandshakeSize + ')'));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let message = parse_tls_message(data);
|
|
189
|
+
|
|
190
|
+
// Emit 'handshakeMessage' hook for every message
|
|
191
|
+
ev.emit('handshakeMessage', message.type, data, message);
|
|
192
|
+
|
|
193
|
+
if((context.isServer==false && message.type=='server_hello') || (context.isServer==true && message.type=='client_hello')){
|
|
194
|
+
|
|
195
|
+
context.transcript.push(data);
|
|
196
|
+
|
|
197
|
+
// Save raw ClientHello + emit event (server side)
|
|
198
|
+
if (context.isServer && message.type === 'client_hello') {
|
|
199
|
+
context.rawClientHello = data;
|
|
200
|
+
ev.emit('clienthello', data, message);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Detect extended_master_secret (type 23 / 0x0017) from remote hello
|
|
204
|
+
// Server: detects from ClientHello. Client: detects from ServerHello.
|
|
205
|
+
if (Array.isArray(message.extensions)) {
|
|
206
|
+
for (let ei = 0; ei < message.extensions.length; ei++) {
|
|
207
|
+
if (message.extensions[ei].type === 0x0017) {
|
|
208
|
+
context.use_extended_master_secret = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Server: detect and validate PSK BEFORE set_context (so reactive loop sees psk_accepted)
|
|
215
|
+
if (context.isServer && message.pre_shared_key && message.pre_shared_key.identities && message.pre_shared_key.identities.length > 0) {
|
|
216
|
+
let pskIdentity = message.pre_shared_key.identities[0];
|
|
217
|
+
let pskBinder = message.pre_shared_key.binders ? message.pre_shared_key.binders[0] : null;
|
|
218
|
+
|
|
219
|
+
let pskResult = null;
|
|
220
|
+
ev.emit('psk', pskIdentity.identity, function(result) {
|
|
221
|
+
pskResult = result;
|
|
222
|
+
});
|
|
223
|
+
if (pskResult && pskResult.psk) {
|
|
224
|
+
let pskCipher = pskResult.cipher || 0x1301;
|
|
225
|
+
let hashName = TLS_CIPHER_SUITES[pskCipher] ? TLS_CIPHER_SUITES[pskCipher].hash : 'sha256';
|
|
226
|
+
let binder_key = derive_binder_key(hashName, pskResult.psk, false);
|
|
227
|
+
|
|
228
|
+
let hashLen = getHashFn(hashName).outputLen;
|
|
229
|
+
let bindersSize = 2 + 1 + hashLen;
|
|
230
|
+
let truncatedCH = data.slice(0, data.length - bindersSize);
|
|
231
|
+
let expectedBinder = compute_psk_binder(hashName, binder_key, truncatedCH);
|
|
232
|
+
|
|
233
|
+
let binderOk = pskBinder && expectedBinder.length === pskBinder.length;
|
|
234
|
+
if (binderOk) {
|
|
235
|
+
for (let bi = 0; bi < expectedBinder.length; bi++) {
|
|
236
|
+
if (expectedBinder[bi] !== pskBinder[bi]) { binderOk = false; break; }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (binderOk) {
|
|
241
|
+
context.psk_accepted = true;
|
|
242
|
+
context.isResumed = true;
|
|
243
|
+
context.psk_offered = {
|
|
244
|
+
psk: pskResult.psk instanceof Uint8Array ? pskResult.psk : new Uint8Array(pskResult.psk),
|
|
245
|
+
cipher: pskCipher,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Client: detect if server accepted PSK from ServerHello (BEFORE set_context)
|
|
252
|
+
if (!context.isServer && message.pre_shared_key && typeof message.pre_shared_key.selected === 'number') {
|
|
253
|
+
if (context.psk_offered) {
|
|
254
|
+
context.psk_accepted = true;
|
|
255
|
+
context.isResumed = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Client: detect HelloRetryRequest (ServerHello with magic random)
|
|
260
|
+
if (!context.isServer && message.random && uint8Equal(message.random, wire.TLS13_HRR_RANDOM)) {
|
|
261
|
+
context.helloRetried = true;
|
|
262
|
+
|
|
263
|
+
// Get cipher from HRR to determine hash
|
|
264
|
+
let hrrCipher = null;
|
|
265
|
+
if (message.cipher_suites && message.cipher_suites.length > 0) hrrCipher = message.cipher_suites[0];
|
|
266
|
+
else if (message.cipher_suite) hrrCipher = message.cipher_suite;
|
|
267
|
+
if (!hrrCipher) hrrCipher = 0x1301;
|
|
268
|
+
let hashName = TLS_CIPHER_SUITES[hrrCipher] ? TLS_CIPHER_SUITES[hrrCipher].hash : 'sha256';
|
|
269
|
+
|
|
270
|
+
// Replace transcript: CH1 → message_hash
|
|
271
|
+
let ch1_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
|
|
272
|
+
let message_hash = wire.build_message(wire.TLS_MESSAGE_TYPE.MESSAGE_HASH, ch1_hash);
|
|
273
|
+
context.transcript = [message_hash, data]; // message_hash + HRR
|
|
274
|
+
|
|
275
|
+
// Find the requested group from HRR extensions
|
|
276
|
+
let requestedGroup = null;
|
|
277
|
+
if (message.supported_groups && message.supported_groups.length > 0) {
|
|
278
|
+
requestedGroup = message.supported_groups[0];
|
|
279
|
+
} else if (message.key_groups && message.key_groups.length > 0) {
|
|
280
|
+
requestedGroup = message.key_groups[0].group;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (requestedGroup) {
|
|
284
|
+
// Generate key for the requested group
|
|
285
|
+
let newKeyGroup = null;
|
|
286
|
+
if (requestedGroup === 0x001d) {
|
|
287
|
+
let pk = new Uint8Array(crypto.randomBytes(32));
|
|
288
|
+
let pub = x25519_get_public_key(pk);
|
|
289
|
+
newKeyGroup = { group: requestedGroup, public_key: pub, private_key: pk };
|
|
290
|
+
context.local_key_groups[requestedGroup] = { public_key: pub, private_key: pk };
|
|
291
|
+
} else if (requestedGroup === 0x0017) {
|
|
292
|
+
let kp = p256_generate_keypair();
|
|
293
|
+
newKeyGroup = { group: requestedGroup, public_key: kp.public_key, private_key: kp.private_key };
|
|
294
|
+
context.local_key_groups[requestedGroup] = { public_key: kp.public_key, private_key: kp.private_key };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (newKeyGroup) {
|
|
298
|
+
// Build and send new ClientHello (CH2) with key_share for requested group
|
|
299
|
+
let extensions = [
|
|
300
|
+
{ type: 'SUPPORTED_VERSIONS', value: context.local_supported_versions },
|
|
301
|
+
{ type: 'SUPPORTED_GROUPS', value: context.local_supported_groups },
|
|
302
|
+
{ type: 'KEY_SHARE', value: [{ group: requestedGroup, key_exchange: newKeyGroup.public_key }] },
|
|
303
|
+
{ type: 'SIGNATURE_ALGORITHMS', value: context.local_supported_signature_algorithms.length > 0 ?
|
|
304
|
+
context.local_supported_signature_algorithms : [0x0804, 0x0805, 0x0806, 0x0403, 0x0503, 0x0603, 0x0401, 0x0501, 0x0601] },
|
|
305
|
+
{ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
|
|
306
|
+
{ type: 23, data: new Uint8Array(0) },
|
|
307
|
+
];
|
|
308
|
+
if (context.local_sni) extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
|
|
309
|
+
|
|
310
|
+
let ch2 = build_tls_message({
|
|
311
|
+
type: 'client_hello',
|
|
312
|
+
version: 0x0303,
|
|
313
|
+
random: context.local_random,
|
|
314
|
+
session_id: context.local_session_id,
|
|
315
|
+
cipher_suite: context.local_supported_cipher_suites,
|
|
316
|
+
extensions: extensions,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
context.transcript.push(ch2);
|
|
320
|
+
ev.emit('message', 0, context.message_sent_seq, 'hello', ch2);
|
|
321
|
+
context.message_sent_seq++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Don't process HRR as a regular ServerHello
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
set_context({
|
|
330
|
+
remote_random: message.random || null,
|
|
331
|
+
remote_sni: message.sni || null,
|
|
332
|
+
remote_session_id: message.session_id || null,
|
|
333
|
+
remote_supported_versions: message.supported_versions || [],
|
|
334
|
+
remote_supported_alpns: message.alpn || [],
|
|
335
|
+
remote_supported_cipher_suites: message.cipher_suites || [],
|
|
336
|
+
remote_supported_signature_algorithms: message.signature_algorithms || [],
|
|
337
|
+
remote_supported_groups: message.supported_groups || [],
|
|
338
|
+
remote_extensions: message.extensions || [],
|
|
339
|
+
add_remote_key_groups: message.key_groups || []
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
ev.emit('hello');
|
|
343
|
+
|
|
344
|
+
if(context.isServer==true){
|
|
345
|
+
if(typeof context.SNICallback=='function'){
|
|
346
|
+
context.SNICallback(context.remote_sni, function (err, creds) {
|
|
347
|
+
if (!err && creds) {
|
|
348
|
+
set_context({
|
|
349
|
+
local_cert_chain: creds.certificateChain,
|
|
350
|
+
cert_private_key: creds.privateKey
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
}else if(message.type=='client_key_exchange' || message.type=='server_key_exchange'){
|
|
360
|
+
|
|
361
|
+
context.transcript.push(data);
|
|
362
|
+
|
|
363
|
+
if ([0xC02F,0xC02B,0xC030,0xC02C,0xC013,0xC014,0xC009,0xC00A].includes(context.selected_cipher_suite)==true) {//ECDHE
|
|
364
|
+
|
|
365
|
+
// ServerKeyExchange carries the group; ClientKeyExchange does not (server already chose it)
|
|
366
|
+
let kex_group = message.group || context.selected_group;
|
|
367
|
+
|
|
368
|
+
set_context({
|
|
369
|
+
add_remote_key_groups: [
|
|
370
|
+
{
|
|
371
|
+
group: kex_group,
|
|
372
|
+
public_key: message.public_key
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
}else if ([0x009E,0x009F,0x0033,0x0039,0x0067,0x006B].includes(context.selected_cipher_suite)==true) {//DHE
|
|
378
|
+
|
|
379
|
+
let client_dh_y=message.body.slice(2);
|
|
380
|
+
|
|
381
|
+
}else if ([0x002F,0x0035,0x003C,0x003D,0x0005,0x000A].includes(context.selected_cipher_suite)==true) {//RSA
|
|
382
|
+
|
|
383
|
+
let enc_pms=message.body.slice(2);
|
|
384
|
+
|
|
385
|
+
}else if ([0xC004,0xC005,0xC00B,0xC00C].includes(context.selected_cipher_suite)==true) {//ECDH
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
}else if(message.type=='server_hello_done'){
|
|
390
|
+
|
|
391
|
+
context.transcript.push(data);
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
set_context({
|
|
395
|
+
remote_hello_done: true,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
}else if(message.type=='encrypted_extensions'){
|
|
399
|
+
|
|
400
|
+
context.transcript.push(data);
|
|
401
|
+
|
|
402
|
+
set_context({
|
|
403
|
+
remote_supported_groups: message.supported_groups || [],
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
}else if(message.type=='certificate'){
|
|
407
|
+
|
|
408
|
+
context.transcript.push(data);
|
|
409
|
+
|
|
410
|
+
set_context({
|
|
411
|
+
remote_cert_chain: message.entries,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Validate peer certificate
|
|
415
|
+
validatePeerCertificate();
|
|
416
|
+
if (context.rejectUnauthorized && !context.peerAuthorized) {
|
|
417
|
+
sendAlert(2, 42); // fatal, bad_certificate
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
}else if(message.type=='certificate_verify'){
|
|
422
|
+
|
|
423
|
+
context.transcript.push(data);
|
|
424
|
+
|
|
425
|
+
}else if(message.type=='finished'){
|
|
426
|
+
|
|
427
|
+
set_context({
|
|
428
|
+
remote_finished: message.body
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
}else if(message.type=='new_session_ticket'){
|
|
432
|
+
|
|
433
|
+
// Client receives NewSessionTicket from server (post-handshake)
|
|
434
|
+
if(!context.isServer && context.resumption_master_secret){
|
|
435
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
436
|
+
let psk = derive_psk(hashName, context.resumption_master_secret, message.ticket_nonce);
|
|
437
|
+
|
|
438
|
+
ev.emit('session', {
|
|
439
|
+
ticket: message.ticket,
|
|
440
|
+
ticket_nonce: message.ticket_nonce,
|
|
441
|
+
psk: psk,
|
|
442
|
+
cipher: context.selected_cipher_suite,
|
|
443
|
+
lifetime: message.ticket_lifetime,
|
|
444
|
+
age_add: message.ticket_age_add,
|
|
445
|
+
maxEarlyDataSize: 0,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
}else if(message.type=='key_update'){
|
|
450
|
+
|
|
451
|
+
// Peer is updating their traffic secret (we update our read key)
|
|
452
|
+
if(context.state==='connected' && context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
453
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
454
|
+
let hashLen = getHashLen(hashName);
|
|
455
|
+
let newRemoteSecret = hkdf_expand_label(hashName, context.remote_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
456
|
+
context.remote_app_traffic_secret = newRemoteSecret;
|
|
457
|
+
ev.emit('keyUpdate', { direction: 'receive', secret: newRemoteSecret });
|
|
458
|
+
|
|
459
|
+
// If peer requested us to update too
|
|
460
|
+
if(message.request_update === 1){
|
|
461
|
+
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
462
|
+
context.local_app_traffic_secret = newLocalSecret;
|
|
463
|
+
|
|
464
|
+
// Send our KeyUpdate (not requesting back)
|
|
465
|
+
let ku_data = build_tls_message({ type: 'key_update', request_update: 0 });
|
|
466
|
+
ev.emit('message', 2, context.message_sent_seq, 'key_update', ku_data);
|
|
467
|
+
context.message_sent_seq++;
|
|
468
|
+
|
|
469
|
+
ev.emit('keyUpdate', { direction: 'send', secret: newLocalSecret });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
}else if(message.type=='certificate_request'){
|
|
474
|
+
|
|
475
|
+
// Server is requesting a client certificate (TLS 1.3)
|
|
476
|
+
if(!context.isServer){
|
|
477
|
+
context.transcript.push(data);
|
|
478
|
+
context.certificateRequested = true;
|
|
479
|
+
context.certificateRequestContext = message.certificate_request_context || new Uint8Array(0);
|
|
480
|
+
context.certificateRequestSigAlgs = message.signature_algorithms || [];
|
|
481
|
+
ev.emit('certificateRequest', message);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
function set_context(options){
|
|
490
|
+
let has_changed=false;
|
|
491
|
+
|
|
492
|
+
if (options && typeof options === 'object'){
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
if('local_supported_versions' in options){
|
|
496
|
+
if(arraysEqual(options.local_supported_versions,context.local_supported_versions)==false){
|
|
497
|
+
context.local_supported_versions=options.local_supported_versions;
|
|
498
|
+
has_changed=true;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if('local_supported_cipher_suites' in options){
|
|
503
|
+
if(arraysEqual(options.local_supported_cipher_suites,context.local_supported_cipher_suites)==false){
|
|
504
|
+
context.local_supported_cipher_suites=options.local_supported_cipher_suites;
|
|
505
|
+
has_changed=true;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if('local_supported_alpns' in options){
|
|
510
|
+
if(arraysEqual(options.local_supported_alpns,context.local_supported_alpns)==false){
|
|
511
|
+
context.local_supported_alpns=options.local_supported_alpns;
|
|
512
|
+
has_changed=true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if('local_supported_groups' in options){
|
|
517
|
+
if(arraysEqual(options.local_supported_groups,context.local_supported_groups)==false){
|
|
518
|
+
context.local_supported_groups=options.local_supported_groups;
|
|
519
|
+
has_changed=true;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if('local_supported_signature_algorithms' in options){
|
|
524
|
+
if(arraysEqual(options.local_supported_signature_algorithms,context.local_supported_signature_algorithms)==false){
|
|
525
|
+
context.local_supported_signature_algorithms=options.local_supported_signature_algorithms;
|
|
526
|
+
has_changed=true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if('local_extensions' in options){
|
|
531
|
+
if(arraysEqual(options.local_extensions,context.local_extensions)==false){
|
|
532
|
+
context.local_extensions=options.local_extensions;
|
|
533
|
+
has_changed=true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if('remote_supported_versions' in options){
|
|
538
|
+
if(arraysEqual(options.remote_supported_versions,context.remote_supported_versions)==false){
|
|
539
|
+
context.remote_supported_versions=options.remote_supported_versions;
|
|
540
|
+
has_changed=true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if('remote_supported_cipher_suites' in options){
|
|
545
|
+
if(arraysEqual(options.remote_supported_cipher_suites,context.remote_supported_cipher_suites)==false){
|
|
546
|
+
context.remote_supported_cipher_suites=options.remote_supported_cipher_suites;
|
|
547
|
+
has_changed=true;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if('remote_supported_alpns' in options){
|
|
552
|
+
if(arraysEqual(options.remote_supported_alpns,context.remote_supported_alpns)==false){
|
|
553
|
+
context.remote_supported_alpns=options.remote_supported_alpns;
|
|
554
|
+
has_changed=true;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if('remote_supported_groups' in options){
|
|
559
|
+
if(arraysEqual(options.remote_supported_groups,context.remote_supported_groups)==false){
|
|
560
|
+
context.remote_supported_groups=options.remote_supported_groups;
|
|
561
|
+
has_changed=true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if('remote_supported_signature_algorithms' in options){
|
|
566
|
+
if(arraysEqual(options.remote_supported_signature_algorithms,context.remote_supported_signature_algorithms)==false){
|
|
567
|
+
context.remote_supported_signature_algorithms=options.remote_supported_signature_algorithms;
|
|
568
|
+
has_changed=true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if('remote_extensions' in options){
|
|
573
|
+
if(arraysEqual(options.remote_extensions,context.remote_extensions)==false){
|
|
574
|
+
context.remote_extensions=options.remote_extensions;
|
|
575
|
+
has_changed=true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if('remote_sni' in options){
|
|
580
|
+
if(options.remote_sni!==context.remote_sni){
|
|
581
|
+
context.remote_sni=options.remote_sni;
|
|
582
|
+
has_changed=true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if('remote_session_id' in options){
|
|
587
|
+
if(!uint8Equal(options.remote_session_id, context.remote_session_id)){
|
|
588
|
+
context.remote_session_id=options.remote_session_id;
|
|
589
|
+
has_changed=true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if('remote_random' in options){
|
|
594
|
+
if(!uint8Equal(options.remote_random, context.remote_random)){
|
|
595
|
+
context.remote_random=options.remote_random;
|
|
596
|
+
has_changed=true;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
if('add_local_key_groups' in options){
|
|
604
|
+
for(let i in options['add_local_key_groups']){
|
|
605
|
+
|
|
606
|
+
let group=options['add_local_key_groups'][i].group;
|
|
607
|
+
if(group in context.local_key_groups==false){
|
|
608
|
+
context.local_key_groups[group]={
|
|
609
|
+
public_key: null,
|
|
610
|
+
private_key: null
|
|
611
|
+
};
|
|
612
|
+
has_changed=true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if(context.local_key_groups[group].public_key==null && options['add_local_key_groups'][i].public_key!==null){
|
|
616
|
+
context.local_key_groups[group].public_key=options['add_local_key_groups'][i].public_key;
|
|
617
|
+
has_changed=true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if(context.local_key_groups[group].private_key==null && options['add_local_key_groups'][i].private_key!==null){
|
|
621
|
+
context.local_key_groups[group].private_key=options['add_local_key_groups'][i].private_key;
|
|
622
|
+
has_changed=true;
|
|
623
|
+
|
|
624
|
+
if(context.local_supported_groups.indexOf(Number(group))<0){
|
|
625
|
+
context.local_supported_groups.push(Number(group));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
if('add_remote_key_groups' in options){
|
|
634
|
+
for(let i in options['add_remote_key_groups']){
|
|
635
|
+
|
|
636
|
+
let group=options['add_remote_key_groups'][i].group;
|
|
637
|
+
if(group in context.remote_key_groups==false){
|
|
638
|
+
context.remote_key_groups[group]={
|
|
639
|
+
public_key: null,
|
|
640
|
+
};
|
|
641
|
+
has_changed=true;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if(context.remote_key_groups[group].public_key==null && options['add_remote_key_groups'][i].public_key!==null){
|
|
645
|
+
context.remote_key_groups[group].public_key=options['add_remote_key_groups'][i].public_key;
|
|
646
|
+
has_changed=true;
|
|
647
|
+
|
|
648
|
+
if(context.remote_supported_groups.indexOf(Number(group))<0){
|
|
649
|
+
context.remote_supported_groups.push(Number(group));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if('remote_cert_chain' in options){
|
|
657
|
+
if(context.remote_cert_chain==null || arraysEqual(options.remote_cert_chain,context.remote_cert_chain)==false){
|
|
658
|
+
context.remote_cert_chain=options.remote_cert_chain;
|
|
659
|
+
has_changed=true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if('remote_hello_done' in options){
|
|
664
|
+
if(options.remote_hello_done!==context.remote_hello_done){
|
|
665
|
+
context.remote_hello_done=options.remote_hello_done;
|
|
666
|
+
has_changed=true;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if('key_exchange_sent' in options){
|
|
671
|
+
if(options.key_exchange_sent!==context.key_exchange_sent){
|
|
672
|
+
context.key_exchange_sent=options.key_exchange_sent;
|
|
673
|
+
has_changed=true;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
//selected stuff...
|
|
679
|
+
|
|
680
|
+
if('selected_version' in options){
|
|
681
|
+
if(options.selected_version!==context.selected_version){
|
|
682
|
+
context.selected_version=options.selected_version;
|
|
683
|
+
has_changed=true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if('selected_cipher_suite' in options){
|
|
688
|
+
if(options.selected_cipher_suite!==context.selected_cipher_suite){
|
|
689
|
+
context.selected_cipher_suite=options.selected_cipher_suite;
|
|
690
|
+
has_changed=true;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if('selected_alpn' in options){
|
|
695
|
+
if(options.selected_alpn!==context.selected_alpn){
|
|
696
|
+
context.selected_alpn=options.selected_alpn;
|
|
697
|
+
has_changed=true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if('selected_group' in options){
|
|
702
|
+
if(options.selected_group!==context.selected_group){
|
|
703
|
+
context.selected_group=options.selected_group;
|
|
704
|
+
has_changed=true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if('selected_signature_algorithm' in options){
|
|
709
|
+
if(options.selected_signature_algorithm!==context.selected_signature_algorithm){
|
|
710
|
+
context.selected_signature_algorithm=options.selected_signature_algorithm;
|
|
711
|
+
has_changed=true;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if('selected_extensions' in options){
|
|
716
|
+
if(arraysEqual(options.selected_extensions,context.selected_extensions)==false){
|
|
717
|
+
context.selected_extensions=options.selected_extensions;
|
|
718
|
+
has_changed=true;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if('selected_sni' in options){
|
|
723
|
+
if(options.selected_sni!==context.selected_sni){
|
|
724
|
+
context.selected_sni=options.selected_sni;
|
|
725
|
+
has_changed=true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if('selected_session_id' in options){
|
|
730
|
+
if(!uint8Equal(options.selected_session_id, context.selected_session_id)){
|
|
731
|
+
context.selected_session_id=options.selected_session_id;
|
|
732
|
+
has_changed=true;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if('ecdhe_shared_secret' in options){
|
|
737
|
+
if(context.ecdhe_shared_secret==null && options.ecdhe_shared_secret!==null){
|
|
738
|
+
context.ecdhe_shared_secret=options.ecdhe_shared_secret;
|
|
739
|
+
has_changed=true;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if('base_secret' in options){
|
|
744
|
+
// base_secret transitions: null → handshake_secret → null (after app secrets derived)
|
|
745
|
+
if(options.base_secret !== context.base_secret){
|
|
746
|
+
context.base_secret=options.base_secret;
|
|
747
|
+
has_changed=true;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if('tls13_master_secret' in options){
|
|
752
|
+
if(context.tls13_master_secret==null && options.tls13_master_secret!==null){
|
|
753
|
+
context.tls13_master_secret=options.tls13_master_secret;
|
|
754
|
+
has_changed=true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
if('remote_handshake_traffic_secret' in options){
|
|
760
|
+
if(context.remote_handshake_traffic_secret==null && options.remote_handshake_traffic_secret!==null){
|
|
761
|
+
context.remote_handshake_traffic_secret=options.remote_handshake_traffic_secret;
|
|
762
|
+
has_changed=true;
|
|
763
|
+
if(context.local_handshake_traffic_secret!==null){
|
|
764
|
+
ev.emit('handshakeSecrets', context.local_handshake_traffic_secret, context.remote_handshake_traffic_secret);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if('local_handshake_traffic_secret' in options){
|
|
770
|
+
if(context.local_handshake_traffic_secret==null && options.local_handshake_traffic_secret!==null){
|
|
771
|
+
context.local_handshake_traffic_secret=options.local_handshake_traffic_secret;
|
|
772
|
+
has_changed=true;
|
|
773
|
+
if(context.remote_handshake_traffic_secret!==null){
|
|
774
|
+
ev.emit('handshakeSecrets', context.local_handshake_traffic_secret, context.remote_handshake_traffic_secret);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if('remote_app_traffic_secret' in options){
|
|
780
|
+
if(context.remote_app_traffic_secret==null && options.remote_app_traffic_secret!==null){
|
|
781
|
+
context.remote_app_traffic_secret=options.remote_app_traffic_secret;
|
|
782
|
+
has_changed=true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if('local_app_traffic_secret' in options){
|
|
787
|
+
if(context.local_app_traffic_secret==null && options.local_app_traffic_secret!==null){
|
|
788
|
+
context.local_app_traffic_secret=options.local_app_traffic_secret;
|
|
789
|
+
has_changed=true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
if('local_cert_chain' in options){
|
|
796
|
+
if(context.local_cert_chain==null && options.local_cert_chain!==null){
|
|
797
|
+
context.local_cert_chain=options.local_cert_chain;
|
|
798
|
+
has_changed=true;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if('cert_private_key' in options){
|
|
803
|
+
if(context.cert_private_key==null && options.cert_private_key!==null){
|
|
804
|
+
context.cert_private_key=options.cert_private_key;
|
|
805
|
+
has_changed=true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if('expected_remote_finished' in options){
|
|
810
|
+
if(context.expected_remote_finished==null && options.expected_remote_finished!==null){
|
|
811
|
+
context.expected_remote_finished=options.expected_remote_finished;
|
|
812
|
+
has_changed=true;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if('remote_finished' in options){
|
|
817
|
+
if(context.remote_finished==null && options.remote_finished!==null){
|
|
818
|
+
context.remote_finished=options.remote_finished;
|
|
819
|
+
has_changed=true;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if('remote_finished_ok' in options){
|
|
824
|
+
if(context.remote_finished_ok!==options.remote_finished_ok){
|
|
825
|
+
context.remote_finished_ok=options.remote_finished_ok;
|
|
826
|
+
has_changed=true;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
if(has_changed==true){
|
|
837
|
+
|
|
838
|
+
let params_to_set = {};
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
//select version...
|
|
846
|
+
if (context.selected_version == null && context.local_supported_versions.length > 0 && context.remote_supported_versions.length > 0) {
|
|
847
|
+
for (let i = 0; i < context.local_supported_versions.length; i++) {
|
|
848
|
+
let v = context.local_supported_versions[i] | 0;
|
|
849
|
+
for (let j = 0; j < context.remote_supported_versions.length; j++) {
|
|
850
|
+
if ((context.remote_supported_versions[j] | 0) == v) {
|
|
851
|
+
params_to_set['selected_version'] = v;
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if ('selected_version' in params_to_set==true && params_to_set.selected_version !== null) break;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if('selected_version' in params_to_set==false || params_to_set.selected_version==null){
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
//select selected_cipher...
|
|
863
|
+
if (context.selected_cipher_suite == null && context.local_supported_cipher_suites.length > 0 && context.remote_supported_cipher_suites.length > 0) {
|
|
864
|
+
|
|
865
|
+
for (let i2 = 0; i2 < context.local_supported_cipher_suites.length; i2++) {
|
|
866
|
+
let cs = context.local_supported_cipher_suites[i2] | 0;
|
|
867
|
+
for (let j2 = 0; j2 < context.remote_supported_cipher_suites.length; j2++) {
|
|
868
|
+
|
|
869
|
+
if ((context.remote_supported_cipher_suites[j2] | 0) == cs) {
|
|
870
|
+
params_to_set['selected_cipher_suite'] = cs;
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if ('selected_cipher_suite' in params_to_set==true && params_to_set.selected_cipher_suite !== null) break;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if('selected_cipher_suite' in params_to_set==false || params_to_set.selected_cipher_suite==null){
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
//select alpn...
|
|
882
|
+
if (context.selected_alpn == null && context.local_supported_alpns && context.remote_supported_alpns) {
|
|
883
|
+
// iterate local list by preference order
|
|
884
|
+
for (let a = 0; a < context.local_supported_alpns.length; a++) {
|
|
885
|
+
let cand = context.local_supported_alpns[a];
|
|
886
|
+
for (let b = 0; b < context.remote_supported_alpns.length; b++) {
|
|
887
|
+
if (context.remote_supported_alpns[b] === cand) {
|
|
888
|
+
params_to_set['selected_alpn'] = cand;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if ('selected_alpn' in params_to_set==true && params_to_set.selected_alpn !== null) break;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
//select sni...
|
|
897
|
+
if (context.selected_sni == null && context.remote_sni!==null) {
|
|
898
|
+
params_to_set['selected_sni'] = context.remote_sni || null;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
//select selected_session_id... (tls 1.2 only)
|
|
902
|
+
if (context.selected_session_id == null) {
|
|
903
|
+
params_to_set['selected_session_id'] = context.remote_session_id || new Uint8Array(0);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
//select group...
|
|
908
|
+
if (context.selected_group == null){
|
|
909
|
+
if(context.local_supported_groups.length > 0 && context.remote_supported_groups.length > 0) {
|
|
910
|
+
for (let i = 0; i < context.local_supported_groups.length; i++) {
|
|
911
|
+
if(context.remote_supported_groups.indexOf(context.local_supported_groups[i])>=0){
|
|
912
|
+
params_to_set['selected_group'] = context.local_supported_groups[i];
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
//create the key by the group if dont have...
|
|
922
|
+
if(context.selected_group !== null && context.selected_group in context.local_key_groups==false){
|
|
923
|
+
|
|
924
|
+
if (context.selected_group === 0x001d) {
|
|
925
|
+
|
|
926
|
+
const private_key = new Uint8Array(crypto.randomBytes(32));
|
|
927
|
+
let public_key = x25519_get_public_key(private_key);
|
|
928
|
+
|
|
929
|
+
params_to_set['add_local_key_groups']=[
|
|
930
|
+
{
|
|
931
|
+
group: context.selected_group,
|
|
932
|
+
private_key: private_key,
|
|
933
|
+
public_key: public_key
|
|
934
|
+
}
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
} else if (context.selected_group === 0x0017) {
|
|
938
|
+
|
|
939
|
+
let kp = p256_generate_keypair();
|
|
940
|
+
let private_key = kp.private_key;
|
|
941
|
+
let public_key = kp.public_key;
|
|
942
|
+
|
|
943
|
+
params_to_set['add_local_key_groups']=[
|
|
944
|
+
{
|
|
945
|
+
group: context.selected_group,
|
|
946
|
+
private_key: private_key,
|
|
947
|
+
public_key: public_key
|
|
948
|
+
}
|
|
949
|
+
];
|
|
950
|
+
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
//get shared_secret...
|
|
957
|
+
if(context.selected_group !== null && context.ecdhe_shared_secret == null && context.selected_group in context.local_key_groups==true && context.selected_group in context.remote_key_groups==true){
|
|
958
|
+
|
|
959
|
+
//check we have remote public key and local private key...
|
|
960
|
+
if(context.remote_key_groups[context.selected_group].public_key!==null && context.local_key_groups[context.selected_group].private_key!==null){
|
|
961
|
+
|
|
962
|
+
let remote_public_key=context.remote_key_groups[context.selected_group].public_key;
|
|
963
|
+
let local_private_key=context.local_key_groups[context.selected_group].private_key;
|
|
964
|
+
|
|
965
|
+
if (context.selected_group === 0x001d) { // X25519
|
|
966
|
+
|
|
967
|
+
let ecdhe_shared_secret = x25519_get_shared_secret(local_private_key, remote_public_key);
|
|
968
|
+
|
|
969
|
+
params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
|
|
970
|
+
|
|
971
|
+
} else if (context.selected_group === 0x0017) { // secp256r1 (P-256)
|
|
972
|
+
|
|
973
|
+
let ecdhe_shared_secret = p256_get_shared_secret(local_private_key, remote_public_key);
|
|
974
|
+
|
|
975
|
+
params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
|
|
976
|
+
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
if(context.isServer==true){
|
|
987
|
+
|
|
988
|
+
// HelloRetryRequest: if we selected a group but client didn't send a key_share for it
|
|
989
|
+
if(context.hello_sent==false && !context.helloRetried && context.selected_version === wire.TLS_VERSION.TLS1_3 &&
|
|
990
|
+
context.selected_group !== null && context.selected_cipher_suite !== null &&
|
|
991
|
+
!(context.selected_group in context.remote_key_groups)){
|
|
992
|
+
|
|
993
|
+
context.helloRetried = true;
|
|
994
|
+
|
|
995
|
+
// Replace transcript with message_hash (RFC 8446 §4.4.1)
|
|
996
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
997
|
+
let ch1_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
|
|
998
|
+
let message_hash = wire.build_message(wire.TLS_MESSAGE_TYPE.MESSAGE_HASH, ch1_hash);
|
|
999
|
+
context.transcript = [message_hash];
|
|
1000
|
+
|
|
1001
|
+
// Build and send HRR (it's a ServerHello with magic random)
|
|
1002
|
+
let hrr_body = wire.build_hello_retry_request({
|
|
1003
|
+
cipher_suite: context.selected_cipher_suite,
|
|
1004
|
+
selected_version: wire.TLS_VERSION.TLS1_3,
|
|
1005
|
+
selected_group: context.selected_group,
|
|
1006
|
+
});
|
|
1007
|
+
let hrr_data = wire.build_message(wire.TLS_MESSAGE_TYPE.SERVER_HELLO, hrr_body);
|
|
1008
|
+
context.transcript.push(hrr_data);
|
|
1009
|
+
|
|
1010
|
+
ev.emit('message', 0, context.message_sent_seq, 'hello_retry_request', hrr_data);
|
|
1011
|
+
context.message_sent_seq++;
|
|
1012
|
+
|
|
1013
|
+
// Reset for second ClientHello
|
|
1014
|
+
context.remote_random = null;
|
|
1015
|
+
context.remote_extensions = [];
|
|
1016
|
+
context.remote_supported_versions = [];
|
|
1017
|
+
context.remote_supported_cipher_suites = [];
|
|
1018
|
+
context.remote_supported_signature_algorithms = [];
|
|
1019
|
+
|
|
1020
|
+
// Don't proceed to ServerHello — wait for new ClientHello
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
let can_send_hello=false;
|
|
1024
|
+
|
|
1025
|
+
if(context.hello_sent==false){
|
|
1026
|
+
|
|
1027
|
+
if(context.selected_version!==null && context.selected_cipher_suite!==null && context.selected_session_id!==null){
|
|
1028
|
+
if(context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
1029
|
+
if(context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
|
|
1030
|
+
can_send_hello=true;
|
|
1031
|
+
}
|
|
1032
|
+
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
|
|
1033
|
+
can_send_hello=true;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if(can_send_hello==true){
|
|
1039
|
+
|
|
1040
|
+
if(context.local_random==null){
|
|
1041
|
+
context.local_random=new Uint8Array(crypto.randomBytes(32));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
let build_message_params=null;
|
|
1045
|
+
|
|
1046
|
+
if(context.selected_version==wire.TLS_VERSION.TLS1_3){
|
|
1047
|
+
|
|
1048
|
+
let shExtensions = [
|
|
1049
|
+
{
|
|
1050
|
+
type: 'SUPPORTED_VERSIONS',
|
|
1051
|
+
value: wire.TLS_VERSION.TLS1_3
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
type: 'KEY_SHARE',
|
|
1055
|
+
value: {
|
|
1056
|
+
group: context.selected_group,
|
|
1057
|
+
key_exchange: context.local_key_groups[context.selected_group].public_key
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
];
|
|
1061
|
+
|
|
1062
|
+
// PSK accepted → include PRE_SHARED_KEY with selected identity index
|
|
1063
|
+
if (context.psk_accepted) {
|
|
1064
|
+
shExtensions.push({ type: 'PRE_SHARED_KEY', value: { selected: 0 } });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
build_message_params={
|
|
1068
|
+
type: 'server_hello',
|
|
1069
|
+
version: context.selected_version,
|
|
1070
|
+
random: context.local_random,
|
|
1071
|
+
session_id: context.remote_session_id,
|
|
1072
|
+
cipher_suite: context.selected_cipher_suite,
|
|
1073
|
+
extensions: shExtensions
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
}else if(context.selected_version==wire.TLS_VERSION.TLS1_2){
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
// TLS 1.2 ServerHello: no SUPPORTED_VERSIONS or KEY_SHARE.
|
|
1081
|
+
// Include renegotiation_info (empty for initial handshake) and extended_master_secret.
|
|
1082
|
+
// ALPN (type=16) — optional, echoes selected protocol.
|
|
1083
|
+
|
|
1084
|
+
let ext_list = [
|
|
1085
|
+
{ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) }
|
|
1086
|
+
];
|
|
1087
|
+
|
|
1088
|
+
// Only echo extended_master_secret if client sent it
|
|
1089
|
+
if (context.use_extended_master_secret) {
|
|
1090
|
+
ext_list.push({ type: 23, data: new Uint8Array(0) });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (context.alpn_selected) {
|
|
1094
|
+
// RFC 7301: ServerHello echoes a single selected protocol
|
|
1095
|
+
ext_list.push({ type: 'ALPN', value: [ String(context.alpn_selected) ] });
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
build_message_params = {
|
|
1099
|
+
type: 'server_hello',
|
|
1100
|
+
version: context.selected_version,
|
|
1101
|
+
random: context.local_random,
|
|
1102
|
+
session_id: context.remote_session_id || new Uint8Array(0), // echo client session_id
|
|
1103
|
+
cipher_suite: context.selected_cipher_suite, // e.g. 0xC02F
|
|
1104
|
+
// compression_method always 0
|
|
1105
|
+
extensions: ext_list
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if(build_message_params!==null){
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
let message_data = build_tls_message(build_message_params);
|
|
1118
|
+
|
|
1119
|
+
context.transcript.push(message_data);
|
|
1120
|
+
|
|
1121
|
+
context.hello_sent=true;
|
|
1122
|
+
|
|
1123
|
+
ev.emit('message',0,context.message_sent_seq,'hello',message_data);
|
|
1124
|
+
|
|
1125
|
+
context.message_sent_seq++;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
}else{
|
|
1130
|
+
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
//get base_secret
|
|
1138
|
+
if (context.base_secret==null && context.selected_cipher_suite !== null){
|
|
1139
|
+
if(context.selected_version == wire.TLS_VERSION.TLS1_3 && (context.ecdhe_shared_secret !== null)){
|
|
1140
|
+
|
|
1141
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
1142
|
+
let result;
|
|
1143
|
+
if (context.psk_accepted && context.psk_offered && context.psk_offered.psk) {
|
|
1144
|
+
// PSK + ECDHE key schedule
|
|
1145
|
+
result = derive_handshake_traffic_secrets_psk(hashName, context.psk_offered.psk, context.ecdhe_shared_secret, concatUint8Arrays(context.transcript));
|
|
1146
|
+
} else {
|
|
1147
|
+
// Standard key schedule (no PSK)
|
|
1148
|
+
result = derive_handshake_traffic_secrets(hashName, context.ecdhe_shared_secret, concatUint8Arrays(context.transcript));
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
params_to_set['base_secret']=result.handshake_secret;
|
|
1152
|
+
|
|
1153
|
+
if(context.isServer==true){
|
|
1154
|
+
params_to_set['remote_handshake_traffic_secret']=result.client_handshake_traffic_secret;
|
|
1155
|
+
params_to_set['local_handshake_traffic_secret']=result.server_handshake_traffic_secret;
|
|
1156
|
+
}else{
|
|
1157
|
+
params_to_set['local_handshake_traffic_secret']=result.client_handshake_traffic_secret;
|
|
1158
|
+
params_to_set['remote_handshake_traffic_secret']=result.server_handshake_traffic_secret;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2 && context.local_random!==null && context.remote_random!==null){
|
|
1162
|
+
if(context.ecdhe_shared_secret !== null){
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
let server_random, client_random;
|
|
1166
|
+
if(context.isServer==true){
|
|
1167
|
+
server_random=context.local_random;
|
|
1168
|
+
client_random=context.remote_random;
|
|
1169
|
+
}else{
|
|
1170
|
+
server_random=context.remote_random;
|
|
1171
|
+
client_random=context.local_random;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if(context.use_extended_master_secret){
|
|
1175
|
+
// RFC 7627: extended master secret uses transcript hash through ClientKeyExchange.
|
|
1176
|
+
// Server: CKE just arrived, transcript is complete.
|
|
1177
|
+
// Client: must wait until CKE is sent and in transcript.
|
|
1178
|
+
if(context.isServer || context.key_exchange_sent){
|
|
1179
|
+
let hashFn = getHashFn(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1180
|
+
|
|
1181
|
+
let transcript_hash = hashFn(concatUint8Arrays(context.transcript));
|
|
1182
|
+
|
|
1183
|
+
let master_secret = tls12_prf(context.ecdhe_shared_secret, "extended master secret", transcript_hash, 48, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1184
|
+
|
|
1185
|
+
params_to_set['base_secret']=master_secret;
|
|
1186
|
+
}
|
|
1187
|
+
}else{
|
|
1188
|
+
let master_secret = tls12_prf(context.ecdhe_shared_secret, "master secret", concatUint8Arrays([client_random, server_random]), 48, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1189
|
+
|
|
1190
|
+
params_to_set['base_secret']=master_secret;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
//send encrypted_extensions...
|
|
1205
|
+
if (context.isServer==true && context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
1206
|
+
if(context.encrypted_exts_sent==false && context.hello_sent==true && context.local_handshake_traffic_secret!==null){
|
|
1207
|
+
|
|
1208
|
+
let extensions=[];
|
|
1209
|
+
if(context.selected_alpn!==null){
|
|
1210
|
+
extensions.push({ type: 'ALPN', value: [context.selected_alpn] });
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
for(let i in context.local_extensions){
|
|
1215
|
+
extensions.push(context.local_extensions[i]);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
let message_data = build_tls_message({
|
|
1219
|
+
type: 'encrypted_extensions',
|
|
1220
|
+
extensions: extensions
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
context.transcript.push(message_data);
|
|
1224
|
+
|
|
1225
|
+
context.encrypted_exts_sent=true;
|
|
1226
|
+
|
|
1227
|
+
ev.emit('message',1,context.message_sent_seq,'encrypted_extensions',message_data);
|
|
1228
|
+
|
|
1229
|
+
context.message_sent_seq++;
|
|
1230
|
+
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
//send certificate... (skip for PSK resumption — no cert needed)
|
|
1236
|
+
// But first: send CertificateRequest if requestCert is set (TLS 1.3 only, between EE and Cert)
|
|
1237
|
+
if(context.isServer==true && context.requestCert==true && !context.certificateRequestSent && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null && context.selected_version === wire.TLS_VERSION.TLS1_3 && !context.psk_accepted){
|
|
1238
|
+
let cr_data = build_tls_message({
|
|
1239
|
+
type: 'certificate_request',
|
|
1240
|
+
certificate_request_context: new Uint8Array(0),
|
|
1241
|
+
signature_algorithms: context.local_supported_signature_algorithms,
|
|
1242
|
+
});
|
|
1243
|
+
context.transcript.push(cr_data);
|
|
1244
|
+
context.certificateRequestSent = true;
|
|
1245
|
+
ev.emit('message', 1, context.message_sent_seq, 'certificate_request', cr_data);
|
|
1246
|
+
context.message_sent_seq++;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if(context.isServer==true && context.cert_sent==false && context.local_cert_chain!==null && !context.psk_accepted){
|
|
1250
|
+
if((context.selected_version === wire.TLS_VERSION.TLS1_3 && context.encrypted_exts_sent==true && context.local_handshake_traffic_secret!==null) || (context.selected_version === wire.TLS_VERSION.TLS1_2 && context.hello_sent==true)){
|
|
1251
|
+
|
|
1252
|
+
let message_data = build_tls_message({
|
|
1253
|
+
type: 'certificate',
|
|
1254
|
+
version: context.selected_version,
|
|
1255
|
+
entries: context.local_cert_chain
|
|
1256
|
+
});
|
|
1257
|
+
context.transcript.push(message_data);
|
|
1258
|
+
|
|
1259
|
+
context.cert_sent=true;
|
|
1260
|
+
|
|
1261
|
+
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
1262
|
+
ev.emit('message',1,context.message_sent_seq,'certificate',message_data);
|
|
1263
|
+
}else{
|
|
1264
|
+
ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
context.message_sent_seq++;
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
//send certificate verify...
|
|
1279
|
+
if (context.isServer==true && context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
1280
|
+
if(context.cert_sent==true && context.cert_verify_sent==false && context.local_cert_chain!==null && context.local_handshake_traffic_secret!==null && context.selected_cipher_suite!==null){
|
|
1281
|
+
|
|
1282
|
+
let tbs_data = build_cert_verify_tbs(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,true,concatUint8Arrays(context.transcript));
|
|
1283
|
+
|
|
1284
|
+
let cert_private_key_obj = crypto.createPrivateKey({
|
|
1285
|
+
key: Buffer.from(context.cert_private_key),
|
|
1286
|
+
format: 'der',
|
|
1287
|
+
type: 'pkcs8',
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
const SIG = {
|
|
1291
|
+
ECDSA_P256_SHA256: 0x0403,
|
|
1292
|
+
ECDSA_P384_SHA384: 0x0503,
|
|
1293
|
+
ECDSA_P521_SHA512: 0x0603,
|
|
1294
|
+
RSA_PSS_SHA256: 0x0804,
|
|
1295
|
+
RSA_PSS_SHA384: 0x0805,
|
|
1296
|
+
RSA_PSS_SHA512: 0x0806,
|
|
1297
|
+
ED25519: 0x0807,
|
|
1298
|
+
ED448: 0x0808
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
let candidates=[];
|
|
1302
|
+
if (cert_private_key_obj.asymmetricKeyType === 'ed25519') candidates.push(SIG.ED25519);
|
|
1303
|
+
if (cert_private_key_obj.asymmetricKeyType === 'ed448') candidates.push(SIG.ED448);
|
|
1304
|
+
if (cert_private_key_obj.asymmetricKeyType === 'rsa') candidates.push(SIG.RSA_PSS_SHA256, SIG.RSA_PSS_SHA384, SIG.RSA_PSS_SHA512); // TLS 1.3: PSS only
|
|
1305
|
+
|
|
1306
|
+
if (cert_private_key_obj.asymmetricKeyType === 'ec') {
|
|
1307
|
+
let c = (cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails.namedCurve) || '';
|
|
1308
|
+
if (c === 'prime256v1') candidates.push(SIG.ECDSA_P256_SHA256);
|
|
1309
|
+
if (c === 'secp384r1') candidates.push(SIG.ECDSA_P384_SHA384);
|
|
1310
|
+
if (c === 'secp521r1') candidates.push(SIG.ECDSA_P521_SHA512);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
let preference_order = [
|
|
1315
|
+
SIG.ED25519,
|
|
1316
|
+
SIG.ED448,
|
|
1317
|
+
SIG.ECDSA_P256_SHA256,
|
|
1318
|
+
SIG.ECDSA_P384_SHA384,
|
|
1319
|
+
SIG.ECDSA_P521_SHA512,
|
|
1320
|
+
SIG.RSA_PSS_SHA256,
|
|
1321
|
+
SIG.RSA_PSS_SHA384,
|
|
1322
|
+
SIG.RSA_PSS_SHA512
|
|
1323
|
+
];
|
|
1324
|
+
|
|
1325
|
+
let selected_scheme = null;
|
|
1326
|
+
for (let s of preference_order) {
|
|
1327
|
+
if (context.remote_supported_signature_algorithms.includes(s)==true && candidates.includes(s)==true) {
|
|
1328
|
+
selected_scheme = s;
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
let sig_data=null;
|
|
1334
|
+
|
|
1335
|
+
switch (selected_scheme) {
|
|
1336
|
+
case SIG.ED25519:
|
|
1337
|
+
sig_data = new Uint8Array(crypto.sign(null, tbs_data, cert_private_key_obj));
|
|
1338
|
+
break;
|
|
1339
|
+
|
|
1340
|
+
case SIG.ECDSA_P256_SHA256:
|
|
1341
|
+
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, cert_private_key_obj));
|
|
1342
|
+
break;
|
|
1343
|
+
|
|
1344
|
+
case SIG.ECDSA_P384_SHA384:
|
|
1345
|
+
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, cert_private_key_obj));
|
|
1346
|
+
break;
|
|
1347
|
+
|
|
1348
|
+
case SIG.ECDSA_P521_SHA512:
|
|
1349
|
+
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, cert_private_key_obj));
|
|
1350
|
+
break;
|
|
1351
|
+
|
|
1352
|
+
case SIG.RSA_PSS_SHA256:
|
|
1353
|
+
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, {
|
|
1354
|
+
key: cert_private_key_obj,
|
|
1355
|
+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
1356
|
+
saltLength: 32
|
|
1357
|
+
}));
|
|
1358
|
+
break;
|
|
1359
|
+
|
|
1360
|
+
case SIG.RSA_PSS_SHA384:
|
|
1361
|
+
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, {
|
|
1362
|
+
key: cert_private_key_obj,
|
|
1363
|
+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
1364
|
+
saltLength: 48
|
|
1365
|
+
}));
|
|
1366
|
+
break;
|
|
1367
|
+
|
|
1368
|
+
case SIG.RSA_PSS_SHA512:
|
|
1369
|
+
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, {
|
|
1370
|
+
key: cert_private_key_obj,
|
|
1371
|
+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
1372
|
+
saltLength: 64
|
|
1373
|
+
}));
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
if(sig_data){
|
|
1380
|
+
|
|
1381
|
+
let message_data = build_tls_message({
|
|
1382
|
+
type: 'certificate_verify',
|
|
1383
|
+
scheme: selected_scheme,
|
|
1384
|
+
signature: sig_data
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
context.transcript.push(message_data);
|
|
1390
|
+
|
|
1391
|
+
context.cert_verify_sent=true;
|
|
1392
|
+
|
|
1393
|
+
ev.emit('message',1,context.message_sent_seq,'certificate_verify',message_data);
|
|
1394
|
+
|
|
1395
|
+
context.message_sent_seq++;
|
|
1396
|
+
}else{
|
|
1397
|
+
|
|
1398
|
+
//..
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
// client/server key exchange - 1.2 only...
|
|
1412
|
+
if (context.key_exchange_sent == false && context.selected_version == wire.TLS_VERSION.TLS1_2) {
|
|
1413
|
+
if(context.selected_group!==null && context.selected_group in context.local_key_groups==true && context.local_key_groups[context.selected_group].public_key!==null){
|
|
1414
|
+
|
|
1415
|
+
if (context.isServer==false && context.remote_hello_done==true) {
|
|
1416
|
+
|
|
1417
|
+
let public_key = context.local_key_groups[context.selected_group].public_key;
|
|
1418
|
+
|
|
1419
|
+
let message_data = build_tls_message({
|
|
1420
|
+
type: 'client_key_exchange',
|
|
1421
|
+
public_key: public_key,
|
|
1422
|
+
});
|
|
1423
|
+
context.transcript.push(message_data);
|
|
1424
|
+
|
|
1425
|
+
// Set via params_to_set to trigger re-evaluation (EMS needs this)
|
|
1426
|
+
params_to_set['key_exchange_sent'] = true;
|
|
1427
|
+
|
|
1428
|
+
ev.emit('message', 0, context.message_sent_seq, 'client_key_exchange', message_data);
|
|
1429
|
+
|
|
1430
|
+
context.message_sent_seq++;
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
}else if (context.isServer==true && context.cert_sent == true) {
|
|
1434
|
+
|
|
1435
|
+
// Build ServerECDHParams + sign (curve_type | namedcurve | ec_point)
|
|
1436
|
+
// curve_type=3 (named_curve)
|
|
1437
|
+
|
|
1438
|
+
let public_key = context.local_key_groups[context.selected_group].public_key;
|
|
1439
|
+
|
|
1440
|
+
let params_head = wire.build_server_ecdh_params(context.selected_group,public_key);
|
|
1441
|
+
|
|
1442
|
+
let tbs_data = concatUint8Arrays([ context.remote_random, context.local_random, params_head ]);
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
let cert_private_key_obj = crypto.createPrivateKey({
|
|
1446
|
+
key: Buffer.from(context.cert_private_key),
|
|
1447
|
+
format: 'der',
|
|
1448
|
+
type: 'pkcs8'
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
let scheme12 = pick_scheme(wire.TLS_VERSION.TLS1_2, cert_private_key_obj, context.remote_supported_signature_algorithms);
|
|
1452
|
+
|
|
1453
|
+
let sig_data = sign_with_scheme(wire.TLS_VERSION.TLS1_2, scheme12, tbs_data, cert_private_key_obj);
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
let message_data = build_tls_message({
|
|
1457
|
+
type: 'server_key_exchange',
|
|
1458
|
+
group: context.selected_group,
|
|
1459
|
+
public_key: public_key,
|
|
1460
|
+
sig_alg: scheme12,
|
|
1461
|
+
signature: sig_data
|
|
1462
|
+
});
|
|
1463
|
+
context.transcript.push(message_data);
|
|
1464
|
+
|
|
1465
|
+
context.key_exchange_sent = true;
|
|
1466
|
+
|
|
1467
|
+
ev.emit('message', 0, context.message_sent_seq, 'server_key_exchange', message_data);
|
|
1468
|
+
|
|
1469
|
+
context.message_sent_seq++;
|
|
1470
|
+
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
//server hello done - 1.2 only...
|
|
1476
|
+
if(context.isServer==true && context.selected_version == wire.TLS_VERSION.TLS1_2){
|
|
1477
|
+
if(context.hello_done_sent==false && context.key_exchange_sent==true){
|
|
1478
|
+
|
|
1479
|
+
let message_data = build_tls_message({
|
|
1480
|
+
type: 'server_hello_done'});
|
|
1481
|
+
context.transcript.push(message_data);
|
|
1482
|
+
|
|
1483
|
+
context.hello_done_sent=true;
|
|
1484
|
+
|
|
1485
|
+
ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
|
|
1486
|
+
|
|
1487
|
+
context.message_sent_seq++;
|
|
1488
|
+
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
//send finished...
|
|
1495
|
+
// Client: send Certificate + CertificateVerify before Finished (if server requested)
|
|
1496
|
+
if(context.isServer==false && context.certificateRequested && !context.clientCertSent &&
|
|
1497
|
+
context.remote_finished_ok==true && context.local_handshake_traffic_secret!==null){
|
|
1498
|
+
context.clientCertSent = true;
|
|
1499
|
+
|
|
1500
|
+
if(context.clientCert && context.clientKey){
|
|
1501
|
+
// Send client certificate
|
|
1502
|
+
let certCtx = createSecureContext({ key: context.clientKey, cert: context.clientCert });
|
|
1503
|
+
let cert_data = build_tls_message({
|
|
1504
|
+
type: 'certificate',
|
|
1505
|
+
version: wire.TLS_VERSION.TLS1_3,
|
|
1506
|
+
entries: certCtx.certificateChain,
|
|
1507
|
+
certificate_request_context: context.certificateRequestContext || new Uint8Array(0),
|
|
1508
|
+
});
|
|
1509
|
+
context.transcript.push(cert_data);
|
|
1510
|
+
ev.emit('message', 1, context.message_sent_seq, 'certificate', cert_data);
|
|
1511
|
+
context.message_sent_seq++;
|
|
1512
|
+
|
|
1513
|
+
// Send CertificateVerify
|
|
1514
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
1515
|
+
let transcript_hash = getHashFn(hashName)(concatUint8Arrays(context.transcript));
|
|
1516
|
+
let scheme = pick_scheme(context.certificateRequestSigAlgs.length > 0 ? context.certificateRequestSigAlgs : context.local_supported_signature_algorithms, certCtx.privateKey);
|
|
1517
|
+
let signature = sign_with_scheme(scheme, certCtx.privateKey, transcript_hash, false);
|
|
1518
|
+
let cv_data = build_tls_message({
|
|
1519
|
+
type: 'certificate_verify',
|
|
1520
|
+
scheme: scheme,
|
|
1521
|
+
signature: signature,
|
|
1522
|
+
});
|
|
1523
|
+
context.transcript.push(cv_data);
|
|
1524
|
+
ev.emit('message', 1, context.message_sent_seq, 'certificate_verify', cv_data);
|
|
1525
|
+
context.message_sent_seq++;
|
|
1526
|
+
} else {
|
|
1527
|
+
// No client cert — send empty certificate
|
|
1528
|
+
let cert_data = build_tls_message({
|
|
1529
|
+
type: 'certificate',
|
|
1530
|
+
version: wire.TLS_VERSION.TLS1_3,
|
|
1531
|
+
entries: [],
|
|
1532
|
+
certificate_request_context: context.certificateRequestContext || new Uint8Array(0),
|
|
1533
|
+
});
|
|
1534
|
+
context.transcript.push(cert_data);
|
|
1535
|
+
ev.emit('message', 1, context.message_sent_seq, 'certificate', cert_data);
|
|
1536
|
+
context.message_sent_seq++;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Note: TLS 1.3 uses local_handshake_traffic_secret for Finished (not base_secret).
|
|
1541
|
+
// base_secret may be null after app secrets are derived, so we also check handshake secret.
|
|
1542
|
+
if (context.finished_sent==false && context.selected_cipher_suite!==null && (context.base_secret!==null || context.local_handshake_traffic_secret!==null)){
|
|
1543
|
+
|
|
1544
|
+
if(context.selected_version === wire.TLS_VERSION.TLS1_3 && context.local_handshake_traffic_secret!==null){
|
|
1545
|
+
|
|
1546
|
+
if((context.isServer==false && context.remote_finished_ok==true && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || (context.isServer==true && context.cert_verify_sent==true && context.local_cert_chain!==null) || (context.isServer==true && context.psk_accepted==true && context.encrypted_exts_sent==true)){
|
|
1547
|
+
|
|
1548
|
+
let finished_data=get_handshake_finished(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,context.local_handshake_traffic_secret,concatUint8Arrays(context.transcript));
|
|
1549
|
+
context.local_finished_data = finished_data;
|
|
1550
|
+
|
|
1551
|
+
let message_data = build_tls_message({
|
|
1552
|
+
type: 'finished',
|
|
1553
|
+
data: finished_data
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
context.transcript.push(message_data);
|
|
1557
|
+
|
|
1558
|
+
context.finished_sent=true;
|
|
1559
|
+
|
|
1560
|
+
ev.emit('message',1,context.message_sent_seq,'finished',message_data);
|
|
1561
|
+
|
|
1562
|
+
context.message_sent_seq++;
|
|
1563
|
+
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
|
|
1567
|
+
|
|
1568
|
+
if((context.isServer==true && context.remote_finished_ok==true) || (context.isServer==false && context.key_exchange_sent==true)){
|
|
1569
|
+
|
|
1570
|
+
let hashFn = getHashFn(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1571
|
+
let transcript_hash = hashFn(concatUint8Arrays(context.transcript));
|
|
1572
|
+
|
|
1573
|
+
let finished_data;
|
|
1574
|
+
if(context.isServer==true){
|
|
1575
|
+
finished_data=tls12_prf(context.base_secret, "server finished", transcript_hash, 12, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1576
|
+
}else{
|
|
1577
|
+
finished_data=tls12_prf(context.base_secret, "client finished", transcript_hash, 12, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1578
|
+
}
|
|
1579
|
+
context.local_finished_data = finished_data;
|
|
1580
|
+
|
|
1581
|
+
let message_data = build_tls_message({
|
|
1582
|
+
type: 'finished',
|
|
1583
|
+
data: finished_data
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
context.transcript.push(message_data);
|
|
1587
|
+
|
|
1588
|
+
context.finished_sent=true;
|
|
1589
|
+
|
|
1590
|
+
ev.emit('message',1,context.message_sent_seq,'finished',message_data);
|
|
1591
|
+
|
|
1592
|
+
context.message_sent_seq++;
|
|
1593
|
+
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
//get app traffic secret...
|
|
1601
|
+
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
|
|
1602
|
+
if(context.base_secret!==null && context.local_app_traffic_secret==null && context.remote_app_traffic_secret==null){
|
|
1603
|
+
|
|
1604
|
+
if((context.isServer==true && context.finished_sent==true && context.remote_finished_ok==false) || (context.isServer==false && context.finished_sent==false && context.remote_finished_ok==true)){
|
|
1605
|
+
|
|
1606
|
+
let result2 = derive_app_traffic_secrets(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash, context.base_secret, concatUint8Arrays(context.transcript));
|
|
1607
|
+
|
|
1608
|
+
// Save master_secret for resumption before clearing
|
|
1609
|
+
params_to_set['tls13_master_secret'] = result2.master_secret;
|
|
1610
|
+
params_to_set['base_secret']=null;
|
|
1611
|
+
|
|
1612
|
+
if (context.isServer === true) {
|
|
1613
|
+
params_to_set['local_app_traffic_secret'] = result2.server_app_traffic_secret;
|
|
1614
|
+
params_to_set['remote_app_traffic_secret'] = result2.client_app_traffic_secret;
|
|
1615
|
+
} else {
|
|
1616
|
+
params_to_set['local_app_traffic_secret'] = result2.client_app_traffic_secret;
|
|
1617
|
+
params_to_set['remote_app_traffic_secret'] = result2.server_app_traffic_secret;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
//expected_remote_finished...
|
|
1625
|
+
if (context.expected_remote_finished==null && context.selected_cipher_suite!==null){
|
|
1626
|
+
|
|
1627
|
+
if(context.selected_version == wire.TLS_VERSION.TLS1_3 && context.remote_handshake_traffic_secret!==null){
|
|
1628
|
+
|
|
1629
|
+
if((context.isServer==true && context.finished_sent==true) || (context.isServer==false && context.remote_finished !== null)){
|
|
1630
|
+
|
|
1631
|
+
params_to_set['expected_remote_finished']=get_handshake_finished(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,context.remote_handshake_traffic_secret,concatUint8Arrays(context.transcript));
|
|
1632
|
+
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2 && context.base_secret!==null){
|
|
1636
|
+
|
|
1637
|
+
if(context.remote_finished!==null){
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
let hashFn = getHashFn(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1641
|
+
let transcript_hash = hashFn(concatUint8Arrays(context.transcript));
|
|
1642
|
+
|
|
1643
|
+
if(context.isServer==true){
|
|
1644
|
+
params_to_set['expected_remote_finished']=tls12_prf(context.base_secret, "client finished", transcript_hash, 12, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1645
|
+
}else{
|
|
1646
|
+
params_to_set['expected_remote_finished']=tls12_prf(context.base_secret, "server finished", transcript_hash, 12, TLS_CIPHER_SUITES[context.selected_cipher_suite].hash);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
//compare finished to expected...
|
|
1662
|
+
if(context.remote_finished_ok==false && context.remote_finished!==null && context.expected_remote_finished!==null){
|
|
1663
|
+
|
|
1664
|
+
if(uint8Equal(context.remote_finished, context.expected_remote_finished)==true){
|
|
1665
|
+
|
|
1666
|
+
let message_data = build_tls_message({
|
|
1667
|
+
type: 'finished',
|
|
1668
|
+
data: context.remote_finished
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
context.transcript.push(message_data);
|
|
1672
|
+
|
|
1673
|
+
params_to_set['remote_finished_ok']=true;
|
|
1674
|
+
|
|
1675
|
+
context.remote_finished_data = context.remote_finished;
|
|
1676
|
+
context.remote_finished=null;
|
|
1677
|
+
context.expected_remote_finished=null;
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
}else{
|
|
1682
|
+
context.remote_finished=null;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
|
|
1690
|
+
if(context.state!=='connected' && context.remote_finished_ok==true && ((context.selected_version === wire.TLS_VERSION.TLS1_3 && context.local_app_traffic_secret!==null && context.remote_app_traffic_secret!==null) || context.selected_version === wire.TLS_VERSION.TLS1_2)){
|
|
1691
|
+
context.state='connected';
|
|
1692
|
+
context.handshakeEndTime = Date.now();
|
|
1693
|
+
ev.emit('secureConnect');
|
|
1694
|
+
|
|
1695
|
+
// TLS 1.3: compute resumption_master_secret (both client and server need it)
|
|
1696
|
+
if (context.selected_version === wire.TLS_VERSION.TLS1_3 && context.tls13_master_secret && !context.resumption_master_secret) {
|
|
1697
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
1698
|
+
context.resumption_master_secret = derive_resumption_master_secret(
|
|
1699
|
+
hashName, context.tls13_master_secret, concatUint8Arrays(context.transcript)
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// TLS 1.3 server: send NewSessionTicket
|
|
1704
|
+
if (context.selected_version === wire.TLS_VERSION.TLS1_3 && context.isServer && !context.session_ticket_sent && !context.noTickets && context.resumption_master_secret) {
|
|
1705
|
+
context.session_ticket_sent = true;
|
|
1706
|
+
|
|
1707
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
1708
|
+
let ticket_nonce = new Uint8Array([context.ticket_nonce_counter++]);
|
|
1709
|
+
let psk = derive_psk(hashName, context.resumption_master_secret, ticket_nonce);
|
|
1710
|
+
let ticket_age_add = crypto.randomBytes(4).readUInt32BE(0);
|
|
1711
|
+
let ticket_lifetime = 7200; // 2 hours
|
|
1712
|
+
|
|
1713
|
+
// Ticket = encrypted PSK + metadata using ticketKeys (or random fallback)
|
|
1714
|
+
// Format: iv(12) || encrypted_json || tag(16)
|
|
1715
|
+
// ticketKeys: 48 bytes = name(16) + aes_key(16) + hmac_key(16)
|
|
1716
|
+
// We use first 32 bytes as AES-256-GCM key for simplicity
|
|
1717
|
+
let tk = context.ticketKeys || crypto.randomBytes(48);
|
|
1718
|
+
let ticket_enc_key = tk.length >= 32 ? tk.slice(0, 32) : Buffer.concat([tk, crypto.randomBytes(32 - tk.length)]);
|
|
1719
|
+
let ticket_iv = crypto.randomBytes(12);
|
|
1720
|
+
let ticket_plaintext = Buffer.from(JSON.stringify({
|
|
1721
|
+
psk: Buffer.from(psk).toString('base64'),
|
|
1722
|
+
cipher: context.selected_cipher_suite,
|
|
1723
|
+
age_add: ticket_age_add,
|
|
1724
|
+
created: Date.now()
|
|
1725
|
+
}));
|
|
1726
|
+
let ticket_cipher = crypto.createCipheriv('aes-256-gcm', ticket_enc_key, ticket_iv);
|
|
1727
|
+
let ticket_ct = ticket_cipher.update(ticket_plaintext);
|
|
1728
|
+
ticket_cipher.final();
|
|
1729
|
+
let ticket_tag = ticket_cipher.getAuthTag();
|
|
1730
|
+
let ticket = Buffer.concat([ticket_iv, ticket_ct, ticket_tag]);
|
|
1731
|
+
|
|
1732
|
+
let nst_data = wire.build_message(wire.TLS_MESSAGE_TYPE.NEW_SESSION_TICKET,
|
|
1733
|
+
wire.build_new_session_ticket({
|
|
1734
|
+
ticket_lifetime: ticket_lifetime,
|
|
1735
|
+
ticket_age_add: ticket_age_add,
|
|
1736
|
+
ticket_nonce: ticket_nonce,
|
|
1737
|
+
ticket: new Uint8Array(ticket),
|
|
1738
|
+
extensions: []
|
|
1739
|
+
})
|
|
1740
|
+
);
|
|
1741
|
+
|
|
1742
|
+
ev.emit('message', 2, context.message_sent_seq, 'new_session_ticket', nst_data);
|
|
1743
|
+
context.message_sent_seq++;
|
|
1744
|
+
|
|
1745
|
+
ev.emit('session', {
|
|
1746
|
+
ticket: new Uint8Array(ticket),
|
|
1747
|
+
ticket_nonce: ticket_nonce,
|
|
1748
|
+
psk: psk,
|
|
1749
|
+
cipher: context.selected_cipher_suite,
|
|
1750
|
+
lifetime: ticket_lifetime,
|
|
1751
|
+
age_add: ticket_age_add,
|
|
1752
|
+
maxEarlyDataSize: 0,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
|
|
1759
|
+
set_context(params_to_set);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
function validatePeerCertificate() {
|
|
1765
|
+
if (!context.remote_cert_chain || context.remote_cert_chain.length === 0) {
|
|
1766
|
+
context.authorizationError = 'NO_PEER_CERTIFICATE';
|
|
1767
|
+
context.peerAuthorized = false;
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
try {
|
|
1772
|
+
// Parse the leaf certificate (first in chain)
|
|
1773
|
+
let certDer = context.remote_cert_chain[0].cert;
|
|
1774
|
+
let x509 = new crypto.X509Certificate(certDer);
|
|
1775
|
+
|
|
1776
|
+
// Check validity dates
|
|
1777
|
+
let now = new Date();
|
|
1778
|
+
if (now < new Date(x509.validFrom)) {
|
|
1779
|
+
context.authorizationError = 'CERT_NOT_YET_VALID';
|
|
1780
|
+
context.peerAuthorized = false;
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (now > new Date(x509.validTo)) {
|
|
1784
|
+
context.authorizationError = 'CERT_HAS_EXPIRED';
|
|
1785
|
+
context.peerAuthorized = false;
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// Check hostname (client-side only, when SNI is set)
|
|
1790
|
+
if (!context.isServer && context.local_sni) {
|
|
1791
|
+
if (!x509.checkHost(context.local_sni)) {
|
|
1792
|
+
context.authorizationError = 'ERR_TLS_CERT_ALTNAME_INVALID';
|
|
1793
|
+
context.peerAuthorized = false;
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// Verify against CA if provided
|
|
1799
|
+
if (context.ca) {
|
|
1800
|
+
let cas = Array.isArray(context.ca) ? context.ca : [context.ca];
|
|
1801
|
+
let verified = false;
|
|
1802
|
+
for (let i = 0; i < cas.length; i++) {
|
|
1803
|
+
try {
|
|
1804
|
+
let caX509 = new crypto.X509Certificate(cas[i]);
|
|
1805
|
+
if (x509.checkIssued(caX509) && x509.verify(caX509.publicKey)) {
|
|
1806
|
+
verified = true;
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
} catch(e) { /* try next CA */ }
|
|
1810
|
+
}
|
|
1811
|
+
if (!verified) {
|
|
1812
|
+
context.authorizationError = 'UNABLE_TO_VERIFY_LEAF_SIGNATURE';
|
|
1813
|
+
context.peerAuthorized = false;
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// All checks passed
|
|
1819
|
+
context.peerAuthorized = true;
|
|
1820
|
+
context.authorizationError = null;
|
|
1821
|
+
|
|
1822
|
+
} catch(e) {
|
|
1823
|
+
context.authorizationError = e.message || 'CERTIFICATE_PARSE_ERROR';
|
|
1824
|
+
context.peerAuthorized = false;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
|
|
1829
|
+
function sendAlert(level, description) {
|
|
1830
|
+
let alertData = new Uint8Array([level, description]);
|
|
1831
|
+
// Epoch 0 for alerts during/before handshake
|
|
1832
|
+
let epoch = (context.state === 'connected') ? 2 : 0;
|
|
1833
|
+
ev.emit('message', epoch, 0, 'alert', alertData);
|
|
1834
|
+
ev.emit('alert', { level: level, description: description });
|
|
1835
|
+
if (level === 2) {
|
|
1836
|
+
// Fatal alert — session is dead
|
|
1837
|
+
context.state = 'error';
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function close(){
|
|
1842
|
+
if (context.state === 'closed') return;
|
|
1843
|
+
// Send close_notify (warning level, description 0)
|
|
1844
|
+
sendAlert(1, 0);
|
|
1845
|
+
context.state = 'closed';
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
if(context.isServer==false){
|
|
1850
|
+
setTimeout(function(){
|
|
1851
|
+
|
|
1852
|
+
if(context.local_random==null){
|
|
1853
|
+
context.local_random=new Uint8Array(crypto.randomBytes(32));
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
if(context.local_session_id==null){
|
|
1857
|
+
context.local_session_id=new Uint8Array(crypto.randomBytes(32));
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
// Support both TLS 1.3 and 1.2 (server picks the best)
|
|
1861
|
+
if(context.local_supported_cipher_suites.length<=0){
|
|
1862
|
+
context.local_supported_cipher_suites=[
|
|
1863
|
+
// TLS 1.3
|
|
1864
|
+
0x1301, // TLS_AES_128_GCM_SHA256
|
|
1865
|
+
0x1302, // TLS_AES_256_GCM_SHA384
|
|
1866
|
+
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
|
1867
|
+
// TLS 1.2 ECDHE
|
|
1868
|
+
0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
1869
|
+
0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
1870
|
+
0xC02B, // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
1871
|
+
0xCCA8, // ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
1872
|
+
];
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
if(context.local_supported_groups.length<=0){
|
|
1876
|
+
context.local_supported_groups=[0x001d, 0x0017]; // X25519, P-256
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if(context.local_supported_versions.length<=0){
|
|
1880
|
+
context.local_supported_versions=[0x0304, 0x0303]; // TLS 1.3, TLS 1.2
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// Generate X25519 keypair for key_share (TLS 1.3) and ECDHE (TLS 1.2)
|
|
1884
|
+
const private_key = new Uint8Array(crypto.randomBytes(32));
|
|
1885
|
+
let public_key = x25519_get_public_key(private_key);
|
|
1886
|
+
|
|
1887
|
+
context.local_key_groups[0x001d]={
|
|
1888
|
+
public_key: public_key,
|
|
1889
|
+
private_key: private_key
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
let extensions = [
|
|
1893
|
+
{
|
|
1894
|
+
type: 'SUPPORTED_VERSIONS',
|
|
1895
|
+
value: context.local_supported_versions
|
|
1896
|
+
},
|
|
1897
|
+
{
|
|
1898
|
+
type: 'SUPPORTED_GROUPS',
|
|
1899
|
+
value: context.local_supported_groups
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
type: 'KEY_SHARE',
|
|
1903
|
+
value: [{
|
|
1904
|
+
group: 0x001d,
|
|
1905
|
+
key_exchange: public_key
|
|
1906
|
+
}]
|
|
1907
|
+
},
|
|
1908
|
+
{
|
|
1909
|
+
type: 'SIGNATURE_ALGORITHMS',
|
|
1910
|
+
value: [
|
|
1911
|
+
// TLS 1.3 (PSS + ECDSA)
|
|
1912
|
+
0x0804, 0x0805, 0x0806,
|
|
1913
|
+
0x0403, 0x0503, 0x0603,
|
|
1914
|
+
0x0807, 0x0808,
|
|
1915
|
+
// TLS 1.2 (PKCS1)
|
|
1916
|
+
0x0401, 0x0501, 0x0601
|
|
1917
|
+
]
|
|
1918
|
+
},
|
|
1919
|
+
// TLS 1.2 compatibility
|
|
1920
|
+
{ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
|
|
1921
|
+
{ type: 23, data: new Uint8Array(0) } // extended_master_secret
|
|
1922
|
+
];
|
|
1923
|
+
|
|
1924
|
+
// Add SNI if servername was provided
|
|
1925
|
+
if (context.local_sni) {
|
|
1926
|
+
extensions.unshift({ type: 'SERVER_NAME', value: context.local_sni });
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// PSK resumption: check if session/psk was provided
|
|
1930
|
+
let pskData = options.session || options.psk || null;
|
|
1931
|
+
let message_data;
|
|
1932
|
+
|
|
1933
|
+
if (pskData && pskData.psk && pskData.ticket && pskData.cipher) {
|
|
1934
|
+
// Add PSK key exchange modes (psk_dhe_ke = 1)
|
|
1935
|
+
extensions.push({ type: 'PSK_KEY_EXCHANGE_MODES', value: [1] });
|
|
1936
|
+
|
|
1937
|
+
// Save PSK for later verification
|
|
1938
|
+
context.psk_offered = {
|
|
1939
|
+
identity: pskData.ticket,
|
|
1940
|
+
psk: pskData.psk instanceof Uint8Array ? pskData.psk : new Uint8Array(pskData.psk),
|
|
1941
|
+
cipher: pskData.cipher,
|
|
1942
|
+
age_add: pskData.age_add || 0,
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
// Compute obfuscated ticket age
|
|
1946
|
+
let ticketAge = pskData.lifetime ? Math.min((Date.now() - (pskData.created || Date.now())) / 1000, pskData.lifetime) * 1000 : 0;
|
|
1947
|
+
let obfuscatedAge = ((ticketAge + (pskData.age_add || 0)) & 0xFFFFFFFF) >>> 0;
|
|
1948
|
+
|
|
1949
|
+
// Build ClientHello with placeholder binder to compute truncated hash
|
|
1950
|
+
let hashName = TLS_CIPHER_SUITES[pskData.cipher] ? TLS_CIPHER_SUITES[pskData.cipher].hash : 'sha256';
|
|
1951
|
+
let hashLen = getHashFn(hashName).outputLen;
|
|
1952
|
+
let placeholderBinder = new Uint8Array(hashLen);
|
|
1953
|
+
|
|
1954
|
+
let pskExt = {
|
|
1955
|
+
type: 'PRE_SHARED_KEY',
|
|
1956
|
+
value: {
|
|
1957
|
+
identities: [{ identity: pskData.ticket, age: obfuscatedAge }],
|
|
1958
|
+
binders: [placeholderBinder]
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
extensions.push(pskExt); // MUST be last
|
|
1962
|
+
|
|
1963
|
+
// Build the full message with placeholder
|
|
1964
|
+
let build_message_params = {
|
|
1965
|
+
type: 'client_hello',
|
|
1966
|
+
version: 0x0303,
|
|
1967
|
+
random: context.local_random,
|
|
1968
|
+
session_id: context.local_session_id,
|
|
1969
|
+
cipher_suite: context.local_supported_cipher_suites,
|
|
1970
|
+
extensions: extensions
|
|
1971
|
+
};
|
|
1972
|
+
let tempMessage = build_tls_message(build_message_params);
|
|
1973
|
+
|
|
1974
|
+
// Truncation point: message length - binders vec (2 + 1 + hashLen)
|
|
1975
|
+
let bindersSize = 2 + 1 + hashLen;
|
|
1976
|
+
let truncatedMessage = tempMessage.slice(0, tempMessage.length - bindersSize);
|
|
1977
|
+
|
|
1978
|
+
// Compute real binder
|
|
1979
|
+
let binder_key = derive_binder_key(hashName, context.psk_offered.psk, false);
|
|
1980
|
+
let binder = compute_psk_binder(hashName, binder_key, truncatedMessage);
|
|
1981
|
+
|
|
1982
|
+
// Rebuild with real binder
|
|
1983
|
+
pskExt.value.binders = [binder];
|
|
1984
|
+
message_data = build_tls_message(build_message_params);
|
|
1985
|
+
|
|
1986
|
+
} else {
|
|
1987
|
+
// No PSK — standard ClientHello
|
|
1988
|
+
let build_message_params = {
|
|
1989
|
+
type: 'client_hello',
|
|
1990
|
+
version: 0x0303,
|
|
1991
|
+
random: context.local_random,
|
|
1992
|
+
session_id: context.local_session_id,
|
|
1993
|
+
cipher_suite: context.local_supported_cipher_suites,
|
|
1994
|
+
extensions: extensions
|
|
1995
|
+
};
|
|
1996
|
+
message_data = build_tls_message(build_message_params);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
context.transcript.push(message_data);
|
|
2000
|
+
|
|
2001
|
+
context.hello_sent=true;
|
|
2002
|
+
|
|
2003
|
+
ev.emit('message',0,context.message_sent_seq,'hello',message_data);
|
|
2004
|
+
|
|
2005
|
+
context.message_sent_seq++;
|
|
2006
|
+
|
|
2007
|
+
},0);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
let api = {
|
|
2013
|
+
/**
|
|
2014
|
+
* Raw context object. Advanced users (QUIC, DTLS) can read/write
|
|
2015
|
+
* any internal state directly. Use convenience getters below when possible.
|
|
2016
|
+
*/
|
|
2017
|
+
context: context,
|
|
2018
|
+
|
|
2019
|
+
/** Whether this session is server-side. */
|
|
2020
|
+
isServer: context.isServer,
|
|
2021
|
+
|
|
2022
|
+
/** Whether this connection used PSK resumption (true after secureConnect if PSK was accepted). */
|
|
2023
|
+
get isResumed() { return context.isResumed; },
|
|
2024
|
+
|
|
2025
|
+
/** Register an event listener.
|
|
2026
|
+
* Events:
|
|
2027
|
+
* 'hello' — fired when remote Hello is received. Server should
|
|
2028
|
+
* call set_context() with local preferences here.
|
|
2029
|
+
* 'message' — (epoch, seq, type, data) handshake/alert message ready to send.
|
|
2030
|
+
* epoch 0=cleartext, 1=handshake-encrypted, 2=app-encrypted.
|
|
2031
|
+
* type: 'hello'|'finished'|'alert'|etc.
|
|
2032
|
+
* The caller must frame this into a TLS record.
|
|
2033
|
+
* 'alert' — ({level, description}) TLS alert sent or received.
|
|
2034
|
+
* 'secureConnect' — handshake complete, app data can flow.
|
|
2035
|
+
*/
|
|
2036
|
+
on: function(name, fn){ ev.on(name, fn); },
|
|
2037
|
+
off: function(name, fn){ ev.off(name, fn); },
|
|
2038
|
+
|
|
2039
|
+
/** Feed an incoming handshake message (without record header). */
|
|
2040
|
+
message: process_income_message,
|
|
2041
|
+
|
|
2042
|
+
/** Set negotiation parameters. See context fields for available keys. */
|
|
2043
|
+
set_context: set_context,
|
|
2044
|
+
|
|
2045
|
+
/** Close the session (sends close_notify alert). */
|
|
2046
|
+
close: close,
|
|
2047
|
+
|
|
2048
|
+
/** Send a TLS alert. level: 1=warning, 2=fatal. See wire.TLS_ALERT for descriptions. */
|
|
2049
|
+
sendAlert: sendAlert,
|
|
2050
|
+
|
|
2051
|
+
// ---- Convenience getters ----
|
|
2052
|
+
|
|
2053
|
+
/** Returns the negotiated TLS version (e.g. 0x0303 for TLS 1.2, 0x0304 for TLS 1.3), or null. */
|
|
2054
|
+
getVersion: function(){
|
|
2055
|
+
return context.selected_version;
|
|
2056
|
+
},
|
|
2057
|
+
|
|
2058
|
+
/** Returns the negotiated cipher suite code (e.g. 0x1301, 0xC02F), or null. */
|
|
2059
|
+
getCipher: function(){
|
|
2060
|
+
return context.selected_cipher_suite;
|
|
2061
|
+
},
|
|
2062
|
+
|
|
2063
|
+
/** Returns the negotiated ALPN protocol string (e.g. 'h2'), or null. */
|
|
2064
|
+
getALPN: function(){
|
|
2065
|
+
return context.alpn_selected || null;
|
|
2066
|
+
},
|
|
2067
|
+
|
|
2068
|
+
/** Returns the remote certificate chain, or null. */
|
|
2069
|
+
getPeerCertificate: function(){
|
|
2070
|
+
return context.remote_cert_chain || null;
|
|
2071
|
+
},
|
|
2072
|
+
|
|
2073
|
+
/** Whether the peer certificate passed validation. */
|
|
2074
|
+
get authorized() { return context.peerAuthorized; },
|
|
2075
|
+
|
|
2076
|
+
/** The authorization error string, or null if authorized. */
|
|
2077
|
+
get authorizationError() { return context.authorizationError; },
|
|
2078
|
+
|
|
2079
|
+
/** Returns traffic secrets for record-layer key derivation.
|
|
2080
|
+
* Individual fields are null until negotiated.
|
|
2081
|
+
* TLS 1.3: use localAppSecret/remoteAppSecret after secureConnect.
|
|
2082
|
+
* TLS 1.2: use masterSecret + randoms after key exchange.
|
|
2083
|
+
*/
|
|
2084
|
+
getTrafficSecrets: function(){
|
|
2085
|
+
return {
|
|
2086
|
+
isServer: context.isServer,
|
|
2087
|
+
version: context.selected_version,
|
|
2088
|
+
cipher: context.selected_cipher_suite,
|
|
2089
|
+
// TLS 1.3
|
|
2090
|
+
localAppSecret: context.local_app_traffic_secret,
|
|
2091
|
+
remoteAppSecret: context.remote_app_traffic_secret,
|
|
2092
|
+
// TLS 1.2
|
|
2093
|
+
masterSecret: context.base_secret,
|
|
2094
|
+
localRandom: context.local_random,
|
|
2095
|
+
remoteRandom: context.remote_random,
|
|
2096
|
+
};
|
|
2097
|
+
},
|
|
2098
|
+
|
|
2099
|
+
/** Returns handshake traffic secrets (available during handshake, before secureConnect). */
|
|
2100
|
+
getHandshakeSecrets: function(){
|
|
2101
|
+
return {
|
|
2102
|
+
localSecret: context.local_handshake_traffic_secret,
|
|
2103
|
+
remoteSecret: context.remote_handshake_traffic_secret,
|
|
2104
|
+
cipher: context.selected_cipher_suite,
|
|
2105
|
+
};
|
|
2106
|
+
},
|
|
2107
|
+
|
|
2108
|
+
exportKeyingMaterial: function(length, label, context_value){
|
|
2109
|
+
if (!context.local_app_traffic_secret || !context.selected_cipher_suite) return new Uint8Array(0);
|
|
2110
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
2111
|
+
let hashFn = getHashFn(hashName);
|
|
2112
|
+
let ctx_hash = hashFn(context_value || new Uint8Array(0));
|
|
2113
|
+
return hkdf_expand_label(hashName, context.local_app_traffic_secret, label || 'exporter', ctx_hash, length || 32);
|
|
2114
|
+
},
|
|
2115
|
+
|
|
2116
|
+
/** Returns the local Finished verify_data (Buffer), or null. */
|
|
2117
|
+
getFinished: function(){
|
|
2118
|
+
return context.local_finished_data ? Buffer.from(context.local_finished_data) : null;
|
|
2119
|
+
},
|
|
2120
|
+
|
|
2121
|
+
/** Returns the peer Finished verify_data (Buffer), or null. */
|
|
2122
|
+
getPeerFinished: function(){
|
|
2123
|
+
return context.remote_finished_data ? Buffer.from(context.remote_finished_data) : null;
|
|
2124
|
+
},
|
|
2125
|
+
|
|
2126
|
+
/** Returns the ECDHE shared secret (Uint8Array), or null. For research/advanced use. */
|
|
2127
|
+
getSharedSecret: function(){
|
|
2128
|
+
return context.ecdhe_shared_secret ? Buffer.from(context.ecdhe_shared_secret) : null;
|
|
2129
|
+
},
|
|
2130
|
+
|
|
2131
|
+
/** Handshake duration in ms, or null if not completed. */
|
|
2132
|
+
get handshakeDuration() {
|
|
2133
|
+
if (context.handshakeStartTime && context.handshakeEndTime)
|
|
2134
|
+
return context.handshakeEndTime - context.handshakeStartTime;
|
|
2135
|
+
return null;
|
|
2136
|
+
},
|
|
2137
|
+
|
|
2138
|
+
/** Full negotiation result — all selected parameters in one object. */
|
|
2139
|
+
getNegotiationResult: function(){
|
|
2140
|
+
let cipherInfo = context.selected_cipher_suite ? TLS_CIPHER_SUITES[context.selected_cipher_suite] : null;
|
|
2141
|
+
return {
|
|
2142
|
+
version: context.selected_version,
|
|
2143
|
+
versionName: context.selected_version === 0x0304 ? 'TLSv1.3' : context.selected_version === 0x0303 ? 'TLSv1.2' : null,
|
|
2144
|
+
cipher: context.selected_cipher_suite,
|
|
2145
|
+
cipherName: cipherInfo ? cipherInfo.name : null,
|
|
2146
|
+
group: context.selected_group,
|
|
2147
|
+
groupName: context.selected_group === 0x001d ? 'X25519' : context.selected_group === 0x0017 ? 'P-256' : null,
|
|
2148
|
+
signatureAlgorithm: context.selected_signature_algorithm,
|
|
2149
|
+
alpn: context.selected_alpn,
|
|
2150
|
+
sni: context.selected_sni || context.local_sni,
|
|
2151
|
+
resumed: context.isResumed,
|
|
2152
|
+
helloRetried: context.helloRetried,
|
|
2153
|
+
handshakeDuration: context.handshakeEndTime && context.handshakeStartTime ? context.handshakeEndTime - context.handshakeStartTime : null,
|
|
2154
|
+
};
|
|
2155
|
+
},
|
|
2156
|
+
|
|
2157
|
+
/** Compute JA3 fingerprint from the ClientHello (server-side only).
|
|
2158
|
+
* Returns { hash, raw } or null if no ClientHello available.
|
|
2159
|
+
* JA3 = md5(SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats)
|
|
2160
|
+
*/
|
|
2161
|
+
getJA3: function(){
|
|
2162
|
+
if (!context.rawClientHello) return null;
|
|
2163
|
+
try {
|
|
2164
|
+
let hello = parse_tls_message(context.rawClientHello);
|
|
2165
|
+
|
|
2166
|
+
let version = hello.client_version || 0x0303;
|
|
2167
|
+
let ciphers = (hello.cipher_suites || []).filter(c => (c & 0x0F0F) !== 0x0A0A).join('-');
|
|
2168
|
+
let extensions = (hello.extensions || []).map(e => e.type).filter(t => t !== 0x0A0A).join('-');
|
|
2169
|
+
let curves = (hello.supported_groups || []).filter(g => (g & 0x0F0F) !== 0x0A0A).join('-');
|
|
2170
|
+
let pointFormats = (hello.ec_point_formats || [0]).join('-');
|
|
2171
|
+
|
|
2172
|
+
let raw = [version, ciphers, extensions, curves, pointFormats].join(',');
|
|
2173
|
+
let hash = crypto.createHash('md5').update(raw).digest('hex');
|
|
2174
|
+
return { hash, raw };
|
|
2175
|
+
} catch(e) { return null; }
|
|
2176
|
+
},
|
|
2177
|
+
|
|
2178
|
+
/** Request a TLS 1.3 Key Update. requestPeer=true means ask the other side to update too. */
|
|
2179
|
+
requestKeyUpdate: function(requestPeer){
|
|
2180
|
+
if (context.state !== 'connected' || context.selected_version !== wire.TLS_VERSION.TLS1_3) return;
|
|
2181
|
+
let hashName = TLS_CIPHER_SUITES[context.selected_cipher_suite].hash;
|
|
2182
|
+
let hashLen = getHashLen(hashName);
|
|
2183
|
+
|
|
2184
|
+
// Derive new local traffic secret
|
|
2185
|
+
let newLocalSecret = hkdf_expand_label(hashName, context.local_app_traffic_secret, 'traffic upd', new Uint8Array(0), hashLen);
|
|
2186
|
+
context.local_app_traffic_secret = newLocalSecret;
|
|
2187
|
+
|
|
2188
|
+
// Send KeyUpdate message
|
|
2189
|
+
let ku_data = build_tls_message({ type: 'key_update', request_update: requestPeer ? 1 : 0 });
|
|
2190
|
+
ev.emit('message', 2, context.message_sent_seq, 'key_update', ku_data);
|
|
2191
|
+
context.message_sent_seq++;
|
|
2192
|
+
|
|
2193
|
+
ev.emit('keyUpdate', { direction: 'send', secret: newLocalSecret });
|
|
2194
|
+
},
|
|
2195
|
+
};
|
|
2196
|
+
|
|
2197
|
+
for (let k in api) if (Object.prototype.hasOwnProperty.call(api,k)) this[k] = api[k];
|
|
2198
|
+
// Re-define dynamic getters (the for-in loop flattens them to values)
|
|
2199
|
+
Object.defineProperty(this, 'isResumed', { get: function() { return context.isResumed; }, configurable: true, enumerable: true });
|
|
2200
|
+
return this;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
export default TLSSession;
|
|
2204
|
+
|