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.
@@ -0,0 +1,877 @@
1
+
2
+ import fs from 'node:fs';
3
+
4
+ function writeClientRandomKeyLog(clientRandom, masterSecret, filePath) {
5
+ function toHex(u8) {
6
+ return Array.from(u8)
7
+ .map(b => b.toString(16).padStart(2, '0'))
8
+ .join('');
9
+ }
10
+
11
+ const line = `CLIENT_RANDOM ${toHex(clientRandom)} ${toHex(masterSecret)}\n`;
12
+
13
+ fs.appendFileSync(filePath, line, 'utf8');
14
+ console.log(`✅ Added CLIENT_RANDOM line to ${filePath}`);
15
+ }
16
+
17
+ import TLSSession from './tls_session.js';
18
+
19
+ import crypto from 'node:crypto';
20
+ import { Duplex } from 'node:stream';
21
+
22
+ import { TLS_CIPHER_SUITES } from './crypto.js';
23
+ import { TLS_CONTENT_TYPE as CT, TLS_ALERT_LEVEL, TLS_ALERT } from './wire.js';
24
+
25
+ import {
26
+ getAeadAlgo,
27
+ deriveKeys as tls_derive_from_tls_secrets,
28
+ getNonce as get_nonce,
29
+ encryptRecord as encrypt_tls_record,
30
+ decryptRecord as decrypt_tls_record,
31
+ parseInnerPlaintext as parse_tls_inner_plaintext,
32
+ encrypt12 as encrypt_tls12_gcm_fragment,
33
+ decrypt12 as decrypt_tls12_gcm_fragment,
34
+ deriveKeys12,
35
+ writeRecord as writeRawRecord,
36
+ } from './record.js';
37
+
38
+ // legacy_record_version (TLS 1.3 uses 0x0303 in record header)
39
+ const REC_VERSION = 0x0303;
40
+
41
+ // ==== עזרי המרה ====
42
+ function toBuf(u8){ return Buffer.isBuffer(u8) ? u8 : Buffer.from(u8 || []); }
43
+ function toU8(buf){ return (buf instanceof Uint8Array) ? buf : new Uint8Array(buf || []); }
44
+
45
+ function parseVersion(v) {
46
+ if (typeof v === 'number') return v;
47
+ if (typeof v === 'string') {
48
+ let s = v.toUpperCase().replace(/[^0-9.]/g, '');
49
+ if (s === '1.3' || s === '13') return 0x0304;
50
+ if (s === '1.2' || s === '12') return 0x0303;
51
+ if (s === '1.1' || s === '11') return 0x0302;
52
+ if (s === '1.0' || s === '10') return 0x0301;
53
+ }
54
+ return null;
55
+ }
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+ // ==== TLSSocket ====
66
+ function TLSSocket(duplex, options){
67
+ if (!(this instanceof TLSSocket)) return new TLSSocket(duplex, options);
68
+ options = options || {};
69
+
70
+ // Inherit from Duplex stream
71
+ Duplex.call(this, { allowHalfOpen: true, readableObjectMode: false, writableObjectMode: false });
72
+ const self = this;
73
+
74
+ let _ticketKeys = options.ticketKeys ? Buffer.from(options.ticketKeys) : crypto.randomBytes(48);
75
+
76
+ let context = {
77
+
78
+ options: options,
79
+
80
+ // External transport (Duplex)
81
+ transport: (duplex && typeof duplex.write === 'function') ? duplex : null,
82
+
83
+ // Internal TLSSession
84
+ session: new TLSSession({
85
+ isServer: !!options.isServer,
86
+ servername: options.servername,
87
+ ALPNProtocols: options.ALPNProtocols || null,
88
+ SNICallback: options.SNICallback || null,
89
+ ticketKeys: _ticketKeys,
90
+ session: options.session || null,
91
+ psk: options.psk || null,
92
+ rejectUnauthorized: options.rejectUnauthorized,
93
+ ca: options.ca || null,
94
+ noTickets: !!options.noTickets,
95
+ maxHandshakeSize: options.maxHandshakeSize || 0,
96
+ customExtensions: options.customExtensions || [],
97
+ requestCert: !!options.requestCert,
98
+ cert: options.cert || null,
99
+ key: options.key || null,
100
+ }),
101
+
102
+ // Handshake write
103
+ handshake_write_key: null,
104
+ handshake_write_iv: null,
105
+ handshake_write_seq: 0,
106
+ handshake_write_aead: null,
107
+
108
+ // Handshake read
109
+ handshake_read_key: null,
110
+ handshake_read_iv: null,
111
+ handshake_read_seq: 0,
112
+ handshake_read_aead: null,
113
+
114
+
115
+ // Application write
116
+ app_write_key: null,
117
+ app_write_iv: null,
118
+ app_write_seq: 0,
119
+ app_write_aead: null,
120
+
121
+ // Application read
122
+ app_read_key: null,
123
+ app_read_iv: null,
124
+ app_read_seq: 0,
125
+ app_read_aead: null,
126
+
127
+ using_app_keys: false,
128
+
129
+ remote_ccs_seen: false,
130
+ local_ccs_sent: false,
131
+
132
+ tls12_read_seq: 0,
133
+
134
+ // Buffers and queues
135
+ readBuffer: Buffer.alloc(0),
136
+ appWriteQueue: [],
137
+ pendingHandshake: [],
138
+
139
+ // General state
140
+ destroyed: false,
141
+ secureEstablished: false,
142
+ aeadAlgo: null, // set when cipher suite is negotiated
143
+
144
+ // Session ticket keys for PSK resumption
145
+ ticketKeys: _ticketKeys,
146
+
147
+ // Advanced options
148
+ maxRecordSize: options.maxRecordSize || 16384,
149
+ noTickets: !!options.noTickets,
150
+ pins: options.pins || null, // ['sha256/AAAA...'] certificate pinning
151
+ handshakeTimeout: options.handshakeTimeout || 0, // ms, 0 = no timeout
152
+ allowedCipherSuites: options.allowedCipherSuites || null, // [0x1301, ...] whitelist
153
+ certificateCallback: options.certificateCallback || null, // (info, cb) => cb(null, ctx)
154
+ handshakeTimer: null,
155
+
156
+ // legacy record version
157
+ rec_version: 0x0303
158
+ };
159
+
160
+ let session = context.session;
161
+
162
+
163
+ // === Record Layer ===
164
+ function writeRecord(type, payload){
165
+ if (!context.transport) throw new Error('No transport attached to TLSSocket');
166
+ if (context.destroyed || context.transport.destroyed || context.transport.writableEnded) return;
167
+ try { writeRawRecord(context.transport, type, payload, context.rec_version); }
168
+ catch(e){ self.emit('error', e); }
169
+ }
170
+
171
+ const MAX_RECORD_PLAINTEXT = 16384; // TLS max record size (2^14)
172
+
173
+ function writeAppData(plain){
174
+ // Fragment large writes into multiple TLS records
175
+ let maxSize = context.maxRecordSize || 16384;
176
+ if (plain.length > maxSize) {
177
+ for (let off = 0; off < plain.length; off += maxSize) {
178
+ let chunk = plain.slice(off, Math.min(off + maxSize, plain.length));
179
+ writeAppDataSingle(chunk);
180
+ }
181
+ return;
182
+ }
183
+ writeAppDataSingle(plain);
184
+ }
185
+
186
+ function writeAppDataSingle(plain){
187
+ let isTls13 = session.getVersion() === 0x0304;
188
+
189
+ if(isTls13){
190
+ if(session.getTrafficSecrets().localAppSecret!==null){
191
+ if(context.app_write_key==null || context.app_write_iv==null){
192
+ let d=tls_derive_from_tls_secrets(session.getTrafficSecrets().localAppSecret,session.getCipher());
193
+
194
+ context.app_write_key=d.key;
195
+ context.app_write_iv=d.iv;
196
+ }
197
+ }else{
198
+ return;
199
+ }
200
+
201
+ let enc1 = encrypt_tls_record(CT.APPLICATION_DATA, plain, context.app_write_key, get_nonce(context.app_write_iv,context.app_write_seq), context.aeadAlgo || getAeadAlgo(session.getCipher()));
202
+
203
+ context.app_write_seq++;
204
+
205
+ try {
206
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc1));
207
+ } catch(e){
208
+ self.emit('error', e);
209
+ }
210
+
211
+ }else{
212
+ // TLS 1.2: derive keys if needed, then encrypt with GCM
213
+ if(context.app_write_key==null || context.app_write_iv==null){
214
+ let d12 = deriveKeys12(session.getTrafficSecrets().masterSecret, session.getTrafficSecrets().localRandom, session.getTrafficSecrets().remoteRandom, session.getCipher(), session.isServer);
215
+ context.app_write_key = d12.writeKey;
216
+ context.app_write_iv = d12.writeIv;
217
+ }
218
+
219
+ let fragment = encrypt_tls12_gcm_fragment(
220
+ plain,
221
+ context.app_write_key,
222
+ context.app_write_iv,
223
+ context.app_write_seq,
224
+ CT.APPLICATION_DATA
225
+ );
226
+
227
+ context.app_write_seq++;
228
+
229
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(fragment));
230
+ }
231
+
232
+ }
233
+
234
+ function processCiphertext(body){
235
+
236
+ let out=null;
237
+ let isTls13 = session.getVersion() === 0x0304;
238
+
239
+ if(!isTls13 && context.remote_ccs_seen==true){
240
+ // TLS 1.2: decrypt with key_block derived keys
241
+ if(context.app_read_key==null || context.app_read_iv==null){
242
+
243
+ let d12 = deriveKeys12(session.getTrafficSecrets().masterSecret, session.getTrafficSecrets().localRandom, session.getTrafficSecrets().remoteRandom, session.getCipher(), session.isServer);
244
+ context.app_read_key = d12.readKey;
245
+ context.app_read_iv = d12.readIv;
246
+ context.app_write_key = d12.writeKey;
247
+ context.app_write_iv = d12.writeIv;
248
+ }
249
+
250
+ let recordType = context.using_app_keys ? 0x17 : 0x16;
251
+ out = decrypt_tls12_gcm_fragment(new Uint8Array(body), context.app_read_key, context.app_read_iv, context.tls12_read_seq, recordType);
252
+
253
+ }else if(isTls13 && context.using_app_keys==true){
254
+ // TLS 1.3: decrypt app data with app traffic secret
255
+ if(session.getTrafficSecrets().remoteAppSecret!==null){
256
+ if(context.app_read_key==null || context.app_read_iv==null){
257
+ let d=tls_derive_from_tls_secrets(session.getTrafficSecrets().remoteAppSecret,session.getCipher());
258
+ context.app_read_key=d.key;
259
+ context.app_read_iv=d.iv;
260
+ }
261
+
262
+ out = decrypt_tls_record(body, context.app_read_key, get_nonce(context.app_read_iv,context.app_read_seq), context.aeadAlgo || getAeadAlgo(session.getCipher()));
263
+ context.app_read_seq++;
264
+ }
265
+ }else if(isTls13){
266
+ // TLS 1.3: decrypt handshake with handshake traffic secret
267
+ if(session.getHandshakeSecrets().remoteSecret!==null && session.getCipher()!==null){
268
+ if(context.handshake_read_key==null || context.handshake_read_iv==null){
269
+ let d=tls_derive_from_tls_secrets(session.getHandshakeSecrets().remoteSecret,session.getCipher());
270
+ context.handshake_read_key=d.key;
271
+ context.handshake_read_iv=d.iv;
272
+ }
273
+
274
+ out = decrypt_tls_record(body, context.handshake_read_key, get_nonce(context.handshake_read_iv,context.handshake_read_seq), context.aeadAlgo || getAeadAlgo(session.getCipher()));
275
+ context.handshake_read_seq++;
276
+ }
277
+ }
278
+
279
+
280
+ if(out!==null){
281
+ let isTls13 = session.getVersion() === 0x0304;
282
+
283
+ if(isTls13){
284
+ let {type: content_type, content} = parse_tls_inner_plaintext(out);
285
+
286
+ if(content_type === CT.APPLICATION_DATA){
287
+ self.push(Buffer.from(content));
288
+
289
+ }else if(content_type === CT.HANDSHAKE){
290
+ session.message(new Uint8Array(content));
291
+
292
+ }else if(content_type === CT.ALERT){
293
+ // TODO: handle alert
294
+ }
295
+ }else{
296
+ // TLS 1.2: no inner plaintext wrapping
297
+ if(context.using_app_keys==true){
298
+ self.push(Buffer.from(out));
299
+ }else{
300
+ session.message(new Uint8Array(out));
301
+ }
302
+ }
303
+ }
304
+
305
+
306
+
307
+ }
308
+
309
+
310
+ function parseRecordsAndDispatch(){
311
+ while (context.readBuffer.length >= 5) {
312
+ let type = context.readBuffer.readUInt8(0);
313
+ let ver = context.readBuffer.readUInt16BE(1);
314
+ let len = context.readBuffer.readUInt16BE(3);
315
+ if (context.readBuffer.length < 5 + len) break;
316
+
317
+ let body = context.readBuffer.slice(5, 5+len);
318
+ context.readBuffer = context.readBuffer.slice(5+len);
319
+
320
+ if (type === CT.APPLICATION_DATA) {
321
+ processCiphertext(body);
322
+
323
+ context.tls12_read_seq++;
324
+
325
+ }else if(type === CT.HANDSHAKE){
326
+ if(context.remote_ccs_seen==true){
327
+ processCiphertext(body);
328
+ }else{
329
+ session.message(new Uint8Array(body));
330
+ }
331
+
332
+ context.tls12_read_seq++;
333
+
334
+ }else if(type === CT.CHANGE_CIPHER_SPEC){
335
+
336
+ context.tls12_read_seq=0;
337
+ context.remote_ccs_seen=true;
338
+
339
+ }else if(type === CT.ALERT ){
340
+ // Alert: 2 bytes — level (1=warning, 2=fatal), description
341
+ if(body.length >= 2){
342
+ let level = body[0];
343
+ let desc = body[1];
344
+ self.emit('alert', { level: level, description: desc });
345
+ if(desc === TLS_ALERT.CLOSE_NOTIFY){
346
+ // Peer is closing — send close_notify back and close
347
+ session.close();
348
+ if(context.transport && typeof context.transport.end === 'function'){
349
+ context.transport.end();
350
+ }
351
+ }
352
+ if(level === TLS_ALERT_LEVEL.FATAL){
353
+ // Fatal alert — close immediately
354
+ if(context.transport && typeof context.transport.destroy === 'function'){
355
+ context.transport.destroy();
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+
362
+
363
+ }
364
+ }
365
+
366
+
367
+ function bindTransport(){
368
+ if (!context.transport) return;
369
+ context.transport.on('data', function(chunk){
370
+ context.readBuffer = Buffer.concat([context.readBuffer, chunk]);
371
+ parseRecordsAndDispatch();
372
+ });
373
+ context.transport.on('error', function(err){ self.emit('error', err); });
374
+ context.transport.on('close', function(){ self.emit('close'); });
375
+ }
376
+
377
+ session.on('message', function(epoch, seq, type, data){
378
+ let buf = toBuf(data || []);
379
+
380
+ // Alert messages — send as ALERT record type
381
+ if (type === 'alert') {
382
+ let isTls13 = session.getVersion() === 0x0304;
383
+ if (isTls13 && context.using_app_keys && context.app_write_key) {
384
+ // TLS 1.3: post-handshake alerts are encrypted
385
+ let enc = encrypt_tls_record(CT.ALERT, buf, context.app_write_key, get_nonce(context.app_write_iv, context.app_write_seq), context.aeadAlgo || getAeadAlgo(session.getCipher()));
386
+ context.app_write_seq++;
387
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc));
388
+ } else {
389
+ writeRecord(CT.ALERT, buf);
390
+ }
391
+ return;
392
+ }
393
+
394
+ if (epoch === 0) {
395
+ // Cleartext handshake (ClientHello/ServerHello)
396
+ if (!context.transport) {
397
+ context.pendingHandshake.push({ type: CT.HANDSHAKE, data: buf });
398
+ return;
399
+ }
400
+ writeRecord(CT.HANDSHAKE, buf);
401
+ return;
402
+ }
403
+
404
+ if (epoch === 1) {
405
+ let isTls13 = session.getVersion() === 0x0304;
406
+
407
+ if(isTls13){
408
+ // TLS 1.3: encrypt handshake messages with handshake traffic secret
409
+ if(session.getHandshakeSecrets().localSecret!==null){
410
+
411
+ if(context.handshake_write_key==null || context.handshake_write_iv==null){
412
+ let d=tls_derive_from_tls_secrets(session.getHandshakeSecrets().localSecret,session.getCipher());
413
+
414
+ context.handshake_write_key=d.key;
415
+ context.handshake_write_iv=d.iv;
416
+ }
417
+
418
+ let enc1 = encrypt_tls_record(CT.HANDSHAKE, buf, context.handshake_write_key, get_nonce(context.handshake_write_iv,context.handshake_write_seq), context.aeadAlgo || getAeadAlgo(session.getCipher()));
419
+
420
+ context.handshake_write_seq++;
421
+
422
+ try {
423
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc1));
424
+ } catch(e){
425
+ self.emit('error', e);
426
+ }
427
+
428
+ }else{
429
+ self.emit('error', new Error('Missing handshake write keys'));
430
+ }
431
+
432
+ }else{
433
+ // TLS 1.2: send CCS first, then encrypt Finished with key_block keys
434
+ if(context.local_ccs_sent==false){
435
+ writeRecord(CT.CHANGE_CIPHER_SPEC, Buffer.from([0x01]));
436
+ context.local_ccs_sent=true;
437
+ // Reset write seq after CCS (TLS 1.2 spec)
438
+ context.app_write_seq=0;
439
+ }
440
+
441
+ // Derive keys if not yet done
442
+ if(context.app_write_key==null || context.app_write_iv==null){
443
+ let d12 = deriveKeys12(session.getTrafficSecrets().masterSecret, session.getTrafficSecrets().localRandom, session.getTrafficSecrets().remoteRandom, session.getCipher(), session.isServer);
444
+ context.app_read_key = d12.readKey;
445
+ context.app_read_iv = d12.readIv;
446
+ context.app_write_key = d12.writeKey;
447
+ context.app_write_iv = d12.writeIv;
448
+ }
449
+
450
+ let fragment = encrypt_tls12_gcm_fragment(
451
+ buf,
452
+ context.app_write_key,
453
+ context.app_write_iv,
454
+ context.app_write_seq,
455
+ CT.HANDSHAKE
456
+ );
457
+
458
+ context.app_write_seq++;
459
+
460
+ writeRecord(CT.HANDSHAKE, Buffer.from(fragment));
461
+ }
462
+
463
+ }
464
+
465
+ if (epoch === 2) {
466
+ // Post-handshake message encrypted with app keys (e.g. NewSessionTicket)
467
+ if (!context.app_write_key) {
468
+ // Derive app write keys if not yet done
469
+ let ts = session.getTrafficSecrets();
470
+ if (ts.localAppSecret) {
471
+ let d = tls_derive_from_tls_secrets(ts.localAppSecret, session.getCipher());
472
+ context.app_write_key = d.key;
473
+ context.app_write_iv = d.iv;
474
+ }
475
+ }
476
+ if (context.app_write_key) {
477
+ let algo = context.aeadAlgo || getAeadAlgo(session.getCipher());
478
+ let enc = encrypt_tls_record(CT.HANDSHAKE, buf, context.app_write_key, get_nonce(context.app_write_iv, context.app_write_seq), algo);
479
+ context.app_write_seq++;
480
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc));
481
+ }
482
+ return;
483
+ }
484
+ });
485
+
486
+
487
+ session.on('hello', function(){
488
+ context.rec_version = 0x0303; // legacy record version (both 1.2 and 1.3)
489
+
490
+ // Client already sent its preferences in ClientHello — don't override
491
+ if (!session.isServer) return;
492
+
493
+ // Parse version range from options
494
+ let maxVer = parseVersion(options.maxVersion) || 0x0304;
495
+ let minVer = parseVersion(options.minVersion) || 0x0303;
496
+
497
+ let versions = [];
498
+ if (maxVer >= 0x0304 && minVer <= 0x0304) versions.push(0x0304);
499
+ if (maxVer >= 0x0303 && minVer <= 0x0303) versions.push(0x0303);
500
+ if (versions.length === 0) versions.push(0x0303); // fallback
501
+
502
+ // Cipher suites based on supported versions
503
+ let ciphers = [];
504
+ if (versions.includes(0x0304)) {
505
+ ciphers.push(0x1301, 0x1302, 0x1303); // TLS_AES_128_GCM, TLS_AES_256_GCM, TLS_CHACHA20
506
+ }
507
+ if (versions.includes(0x0303)) {
508
+ ciphers.push(
509
+ 0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
510
+ 0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
511
+ 0xC02B, // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
512
+ 0xCCA8 // ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
513
+ );
514
+ }
515
+
516
+ // Signature algorithms: override or default
517
+ let sigalgs = [];
518
+ if (options.signatureAlgorithms) {
519
+ sigalgs = options.signatureAlgorithms;
520
+ } else {
521
+ if (versions.includes(0x0304)) {
522
+ sigalgs.push(0x0804, 0x0805, 0x0806); // RSA-PSS
523
+ sigalgs.push(0x0403, 0x0503, 0x0603); // ECDSA
524
+ }
525
+ if (versions.includes(0x0303)) {
526
+ sigalgs.push(0x0401, 0x0501, 0x0601); // RSA-PKCS1
527
+ }
528
+ }
529
+
530
+ // Groups: override or default
531
+ let groups = options.groups || [0x001d, 0x0017]; // X25519, P-256
532
+
533
+ // prioritizeChaCha: move ChaCha20 to front of cipher list
534
+ if (options.prioritizeChaCha) {
535
+ let chacha = ciphers.filter(c => c === 0x1303 || c === 0xCCA8);
536
+ let rest = ciphers.filter(c => c !== 0x1303 && c !== 0xCCA8);
537
+ ciphers = [...chacha, ...rest];
538
+ }
539
+
540
+ // allowedCipherSuites: whitelist filter
541
+ if (context.allowedCipherSuites) {
542
+ ciphers = ciphers.filter(c => context.allowedCipherSuites.includes(c));
543
+ }
544
+
545
+ // ALPN from options
546
+ let alpns = Array.isArray(options.ALPNProtocols) ? options.ALPNProtocols : [];
547
+
548
+ session.set_context({
549
+ local_supported_versions: versions,
550
+ local_supported_alpns: alpns,
551
+ local_supported_groups: groups,
552
+ local_supported_cipher_suites: ciphers,
553
+ local_supported_signature_algorithms: sigalgs,
554
+ });
555
+
556
+ // Resolve AEAD algorithm once cipher is negotiated
557
+ if (session.getCipher()) context.aeadAlgo = getAeadAlgo(session.getCipher());
558
+ });
559
+
560
+ session.on('secureConnect', function(){
561
+ context.using_app_keys=true;
562
+ context.secureEstablished=true;
563
+ context.aeadAlgo = getAeadAlgo(session.getCipher());
564
+
565
+ // Clear handshake timeout
566
+ if (context.handshakeTimer) { clearTimeout(context.handshakeTimer); context.handshakeTimer = null; }
567
+
568
+ // Certificate pinning
569
+ if (context.pins && context.pins.length > 0) {
570
+ try {
571
+ let cert = session.getPeerCertificate();
572
+ if (cert && cert.raw) {
573
+ let hash = 'sha256/' + crypto.createHash('sha256').update(cert.raw).digest('base64');
574
+ if (!context.pins.includes(hash)) {
575
+ self.emit('error', new Error('Certificate pin mismatch: ' + hash));
576
+ self.destroy();
577
+ return;
578
+ }
579
+ }
580
+ } catch(e) { /* pinning is best-effort if no raw cert */ }
581
+ }
582
+
583
+ // Emit keylog lines (SSLKEYLOGFILE compatible)
584
+ try {
585
+ let ts = session.getTrafficSecrets();
586
+ let clientRandom = Buffer.from(session.context.local_random || session.context.remote_random || []).toString('hex');
587
+ if (session.isServer) clientRandom = Buffer.from(session.context.remote_random || []).toString('hex');
588
+
589
+ if (session.getVersion() === 0x0304) {
590
+ // TLS 1.3 key log
591
+ let hs = session.getHandshakeSecrets();
592
+ if (hs.localSecret) self.emit('keylog', Buffer.from(`SERVER_HANDSHAKE_TRAFFIC_SECRET ${clientRandom} ${Buffer.from(session.isServer ? hs.localSecret : hs.remoteSecret).toString('hex')}\n`));
593
+ if (hs.remoteSecret) self.emit('keylog', Buffer.from(`CLIENT_HANDSHAKE_TRAFFIC_SECRET ${clientRandom} ${Buffer.from(session.isServer ? hs.remoteSecret : hs.localSecret).toString('hex')}\n`));
594
+ if (ts.localAppSecret) self.emit('keylog', Buffer.from(`SERVER_TRAFFIC_SECRET_0 ${clientRandom} ${Buffer.from(session.isServer ? ts.localAppSecret : ts.remoteAppSecret).toString('hex')}\n`));
595
+ if (ts.remoteAppSecret) self.emit('keylog', Buffer.from(`CLIENT_TRAFFIC_SECRET_0 ${clientRandom} ${Buffer.from(session.isServer ? ts.remoteAppSecret : ts.localAppSecret).toString('hex')}\n`));
596
+ } else {
597
+ // TLS 1.2 key log
598
+ if (ts.masterSecret) self.emit('keylog', Buffer.from(`CLIENT_RANDOM ${clientRandom} ${Buffer.from(ts.masterSecret).toString('hex')}\n`));
599
+ }
600
+ } catch(e) { /* keylog is best-effort */ }
601
+
602
+ // Flush any queued writes
603
+ while (context.appWriteQueue.length > 0) {
604
+ writeAppData(context.appWriteQueue.shift());
605
+ }
606
+
607
+ self.emit('secureConnect');
608
+ });
609
+
610
+ // Forward session ticket event (Node.js compatible)
611
+ session.on('session', function(ticketData) {
612
+ self.emit('session', ticketData);
613
+ });
614
+
615
+ // Forward handshakeMessage event (for debugging/inspection)
616
+ session.on('handshakeMessage', function(type, raw, parsed) {
617
+ self.emit('handshakeMessage', type, raw, parsed);
618
+ });
619
+
620
+ // Forward raw clienthello event (server-side, for JA3/inspection)
621
+ session.on('clienthello', function(raw, parsed) {
622
+ self.emit('clienthello', raw, parsed);
623
+ });
624
+
625
+ // Handshake timeout
626
+ if (context.handshakeTimeout > 0) {
627
+ context.handshakeTimer = setTimeout(function() {
628
+ if (!context.secureEstablished) {
629
+ self.emit('error', new Error('Handshake timeout after ' + context.handshakeTimeout + 'ms'));
630
+ self.destroy();
631
+ }
632
+ }, context.handshakeTimeout);
633
+ }
634
+
635
+ // certificateCallback: dynamic cert selection (extends SNICallback)
636
+ if (context.certificateCallback && options.isServer) {
637
+ session.on('hello', function() {
638
+ let info = {
639
+ servername: session.context.remote_sni,
640
+ version: session.context.selected_version,
641
+ ciphers: session.context.remote_supported_cipher_suites,
642
+ sigalgs: session.context.remote_supported_signature_algorithms,
643
+ groups: session.context.remote_supported_groups,
644
+ alpns: session.context.remote_supported_alpns,
645
+ };
646
+ context.certificateCallback(info, function(err, ctx) {
647
+ if (!err && ctx) {
648
+ session.set_context({
649
+ local_cert_chain: ctx.certificateChain,
650
+ cert_private_key: ctx.privateKey,
651
+ });
652
+ }
653
+ });
654
+ });
655
+ }
656
+
657
+ // KeyUpdate: swap record layer keys when TLSSession derives new secrets
658
+ session.on('keyUpdate', function(info) {
659
+ if (info.direction === 'send') {
660
+ // We updated our send secret → derive new write keys, reset seq
661
+ let d = tls_derive_from_tls_secrets(info.secret, session.getCipher());
662
+ context.app_write_key = d.key;
663
+ context.app_write_iv = d.iv;
664
+ context.app_write_seq = 0;
665
+ } else if (info.direction === 'receive') {
666
+ // Peer updated their send secret → derive new read keys, reset seq
667
+ let d = tls_derive_from_tls_secrets(info.secret, session.getCipher());
668
+ context.app_read_key = d.key;
669
+ context.app_read_iv = d.iv;
670
+ context.app_read_seq = 0;
671
+ }
672
+ self.emit('keyUpdate', info.direction);
673
+ });
674
+
675
+ // Forward certificateRequest event
676
+ session.on('certificateRequest', function(msg) {
677
+ self.emit('certificateRequest', msg);
678
+ });
679
+
680
+ // Server: automatic PSK handler (decrypts tickets with ticketKeys)
681
+ if (options.isServer) {
682
+ session.on('psk', function(identity, callback) {
683
+ // First check if user has a custom handler
684
+ if (self.listenerCount('psk') > 0) {
685
+ // Let user handle it
686
+ self.emit('psk', identity, callback);
687
+ return;
688
+ }
689
+
690
+ // Auto-decrypt ticket using ticketKeys
691
+ try {
692
+ let tk = context.ticketKeys;
693
+ let ticket_enc_key = tk.slice(0, 32);
694
+ let id = Buffer.from(identity);
695
+ if (id.length < 12 + 16) { callback(null); return; }
696
+
697
+ let iv = id.slice(0, 12);
698
+ let tag = id.slice(id.length - 16);
699
+ let ct = id.slice(12, id.length - 16);
700
+
701
+ let decipher = crypto.createDecipheriv('aes-256-gcm', ticket_enc_key, iv);
702
+ decipher.setAuthTag(tag);
703
+ let pt = decipher.update(ct);
704
+ decipher.final();
705
+
706
+ let data = JSON.parse(pt.toString());
707
+ callback({
708
+ psk: new Uint8Array(Buffer.from(data.psk, 'base64')),
709
+ cipher: data.cipher,
710
+ });
711
+ } catch(e) {
712
+ callback(null); // Decryption failed → full handshake
713
+ }
714
+ });
715
+ }
716
+
717
+ // If duplex was passed in constructor, start reading
718
+ if (context.transport) {
719
+ bindTransport();
720
+ }
721
+
722
+ // === Duplex stream implementation ===
723
+
724
+ /** Duplex _write — called by stream.write() */
725
+ self._write = function(chunk, encoding, callback) {
726
+ if (context.destroyed) { callback(new Error('Socket destroyed')); return; }
727
+ let buf = toBuf(chunk);
728
+ if (!context.using_app_keys) {
729
+ context.appWriteQueue.push(buf);
730
+ } else {
731
+ writeAppData(buf);
732
+ }
733
+ callback();
734
+ };
735
+
736
+ /** Duplex _read — data is pushed when received, no pull needed */
737
+ self._read = function() {};
738
+
739
+ // === Node-compatible API ===
740
+
741
+ /** Attach a raw transport (TCP socket) after construction. */
742
+ self.setSocket = function(duplex2){
743
+ if (!duplex2 || typeof duplex2.write !== 'function') throw new Error('setSocket expects a Duplex-like stream');
744
+ context.transport = duplex2;
745
+ bindTransport();
746
+ // Flush any pending handshake messages (e.g. ClientHello queued before transport was ready)
747
+ while (context.pendingHandshake.length > 0) {
748
+ let msg = context.pendingHandshake.shift();
749
+ writeRecord(msg.type, Buffer.from(msg.data));
750
+ }
751
+ };
752
+
753
+ /** Send close_notify and gracefully close. */
754
+ self.end = (function(originalEnd) {
755
+ return function(data, encoding, callback) {
756
+ if (context.destroyed) return this;
757
+ session.close(); // sends close_notify alert
758
+ try { context.transport && context.transport.end && context.transport.end(); } catch(e){}
759
+ return originalEnd.call(this, data, encoding, callback);
760
+ };
761
+ })(self.end);
762
+
763
+ self.destroy = (function(originalDestroy) {
764
+ return function(err) {
765
+ if (context.destroyed) return this;
766
+ context.destroyed = true;
767
+ try { context.transport && context.transport.destroy && context.transport.destroy(); } catch(e){}
768
+ return originalDestroy.call(this, err);
769
+ };
770
+ })(self.destroy);
771
+
772
+ /** Access the underlying TLSSession (for QUIC/advanced consumers). */
773
+ self.getSession = function(){ return session; };
774
+
775
+ /** Whether this connection used PSK resumption (true after secureConnect). */
776
+ Object.defineProperty(self, 'isResumed', { get: function(){ return session.isResumed; } });
777
+
778
+ /** Returns negotiated protocol string: 'TLSv1.3', 'TLSv1.2', etc. */
779
+ self.getProtocol = function(){
780
+ let v = session.getVersion();
781
+ if (v === 0x0304) return 'TLSv1.3';
782
+ if (v === 0x0303) return 'TLSv1.2';
783
+ if (v === 0x0302) return 'TLSv1.1';
784
+ if (v === 0x0301) return 'TLSv1';
785
+ return null;
786
+ };
787
+
788
+ /** Returns cipher info: { name, standardName, version }. */
789
+ self.getCipher = function(){
790
+ let code = session.getCipher();
791
+ if (code == null) return null;
792
+ let info = TLS_CIPHER_SUITES[code];
793
+ if (!info) return { name: '0x' + code.toString(16), standardName: 'unknown', version: self.getProtocol() };
794
+ return {
795
+ name: info.name || info.cipher || '0x' + code.toString(16),
796
+ standardName: info.standardName || info.cipher || 'unknown',
797
+ version: self.getProtocol()
798
+ };
799
+ };
800
+
801
+ /** Returns negotiated ALPN protocol string, or false. */
802
+ Object.defineProperty(self, 'alpnProtocol', {
803
+ get: function(){ return session.getALPN() || false; },
804
+ enumerable: true
805
+ });
806
+
807
+ /** Whether the peer certificate was validated. */
808
+ Object.defineProperty(self, 'authorized', {
809
+ get: function(){ return session.authorized; },
810
+ enumerable: true
811
+ });
812
+
813
+ /** Authorization error string, or null. */
814
+ Object.defineProperty(self, 'authorizationError', {
815
+ get: function(){ return session.authorizationError; },
816
+ enumerable: true
817
+ });
818
+
819
+ /** Whether TLS is established. */
820
+ Object.defineProperty(self, 'encrypted', {
821
+ get: function(){ return context.secureEstablished; },
822
+ enumerable: true
823
+ });
824
+
825
+ self.getPeerCertificate = function(){
826
+ let chain = session.getPeerCertificate();
827
+ if (!chain || chain.length === 0) return null;
828
+ try {
829
+ let certDer = chain[0].cert;
830
+ let x509 = new crypto.X509Certificate(certDer);
831
+ return {
832
+ subject: x509.subject,
833
+ issuer: x509.issuer,
834
+ subjectaltname: x509.subjectAltName,
835
+ valid_from: x509.validFrom,
836
+ valid_to: x509.validTo,
837
+ fingerprint: x509.fingerprint,
838
+ fingerprint256: x509.fingerprint256,
839
+ serialNumber: x509.serialNumber,
840
+ raw: certDer
841
+ };
842
+ } catch(e) {
843
+ return { raw: chain[0].cert };
844
+ }
845
+ };
846
+
847
+ // === LemonTLS-only extensions (not in Node.js tls) ===
848
+
849
+ /** Handshake duration in ms, or null if not completed. */
850
+ Object.defineProperty(self, 'handshakeDuration', {
851
+ get: function(){ return session.handshakeDuration; },
852
+ enumerable: true
853
+ });
854
+
855
+ /** JA3 fingerprint from ClientHello (server-side only). Returns { hash, raw } or null. */
856
+ self.getJA3 = function(){ return session.getJA3 ? session.getJA3() : null; };
857
+
858
+ /** ECDHE shared secret (Buffer), or null. For research/advanced use. */
859
+ self.getSharedSecret = function(){ return session.getSharedSecret ? session.getSharedSecret() : null; };
860
+
861
+ /** Full negotiation result — all selected parameters in one object. */
862
+ self.getNegotiationResult = function(){ return session.getNegotiationResult ? session.getNegotiationResult() : null; };
863
+
864
+ /** Request Key Update — refresh outgoing encryption keys (TLS 1.3 only). */
865
+ self.rekeySend = function(){ if (session.requestKeyUpdate) session.requestKeyUpdate(false); };
866
+
867
+ /** Request Key Update for both directions (TLS 1.3 only). */
868
+ self.rekeyBoth = function(){ if (session.requestKeyUpdate) session.requestKeyUpdate(true); };
869
+
870
+ return self;
871
+ }
872
+
873
+ // Inherit from Duplex
874
+ Object.setPrototypeOf(TLSSocket.prototype, Duplex.prototype);
875
+ Object.setPrototypeOf(TLSSocket, Duplex);
876
+
877
+ export default TLSSocket;