lemon-tls 0.1.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/tls_socket.js ADDED
@@ -0,0 +1,456 @@
1
+
2
+ var TLSSession = require('./tls_session');
3
+
4
+ var { AES } = require('@stablelib/aes');
5
+ var { GCM } = require('@stablelib/gcm');
6
+
7
+ var {
8
+ TLS_CIPHER_SUITES,
9
+ hkdf_expand_label
10
+ } = require('./crypto');
11
+
12
+
13
+
14
+ function Emitter(){
15
+ var listeners = {};
16
+ return {
17
+ on: function(name, fn){ (listeners[name] = listeners[name] || []).push(fn); },
18
+ emit: function(name){
19
+ var args = Array.prototype.slice.call(arguments, 1);
20
+ var arr = listeners[name] || [];
21
+ for (var i=0;i<arr.length;i++){ try{ arr[i].apply(null, args); }catch(e){} }
22
+ }
23
+ };
24
+ }
25
+
26
+ // TLS ContentType
27
+ var CT = { CHANGE_CIPHER_SPEC:20, ALERT:21, HANDSHAKE:22, APPLICATION_DATA:23 };
28
+ // legacy_record_version בשדה כותרת הרשומה (TLS 1.3 שומר 0x0303)
29
+ var REC_VERSION = 0x0303;
30
+
31
+ // ==== עזרי המרה ====
32
+ function toBuf(u8){ return Buffer.isBuffer(u8) ? u8 : Buffer.from(u8 || []); }
33
+ function toU8(buf){ return (buf instanceof Uint8Array) ? buf : new Uint8Array(buf || []); }
34
+
35
+
36
+
37
+ function tls_derive_from_tls_secrets(traffic_secret, cipher_suite){
38
+
39
+ var empty = new Uint8Array(0);
40
+
41
+ var key = hkdf_expand_label(TLS_CIPHER_SUITES[cipher_suite].hash, traffic_secret, 'key', empty, TLS_CIPHER_SUITES[cipher_suite].keylen);
42
+
43
+ var iv = hkdf_expand_label(TLS_CIPHER_SUITES[cipher_suite].hash, traffic_secret, 'iv', empty, 12);
44
+
45
+ return {
46
+ key: key,
47
+ iv: iv,
48
+ };
49
+ }
50
+
51
+
52
+ function get_nonce(iv,seq) {
53
+
54
+ var seq_buf = new Uint8Array(12); // all zero
55
+ var view = new DataView(seq_buf.buffer);
56
+ view.setBigUint64(4, BigInt(seq)); // offset 4, 64-bit BE
57
+
58
+ var nonce = new Uint8Array(12);
59
+ for (var i = 0; i < 12; i++) {
60
+ nonce[i] = iv[i] ^ seq_buf[i];
61
+ }
62
+
63
+ return nonce;
64
+ }
65
+
66
+ function encrypt_tls_record(innerType, plaintext, key, nonce) {
67
+ const aes = new AES(key);
68
+ const gcm = new GCM(aes);
69
+
70
+ // TLSInnerPlaintext = content || content_type || padding(0x00…)
71
+ const full_plaintext = new Uint8Array(plaintext.length + 1);
72
+ full_plaintext.set(plaintext);
73
+ full_plaintext[plaintext.length] = innerType; // ← אל תשכח לבחור נכון
74
+
75
+ // AAD = [ 0x17, 0x03, 0x03, len_hi, len_lo ]
76
+ // len = ciphertext.length כולל tag = full_plaintext.length + 16 (ב-GCM)
77
+ const aad = new Uint8Array(5);
78
+ aad[0] = 0x17;
79
+ aad[1] = 0x03; aad[2] = 0x03;
80
+ const recLen = full_plaintext.length + 16; // tag=16
81
+ aad[3] = (recLen >>> 8) & 0xff;
82
+ aad[4] = (recLen ) & 0xff;
83
+
84
+ // הצפנה אחת עם AAD הנכון
85
+ const ciphertext = gcm.seal(nonce, full_plaintext, aad); // אמור להחזיר ct||tag
86
+ return ciphertext;
87
+ }
88
+
89
+
90
+
91
+ function decrypt_tls_record(ciphertext, key, nonce) {
92
+ // הקמה: AES + GCM
93
+ var aes = new AES(key);
94
+ var gcm = new GCM(aes);
95
+
96
+ // AAD לפי TLS 1.3: 0x17 (application_data), גרסה 0x0303, ואורך ה-ciphertext
97
+ var aad = new Uint8Array(5);
98
+ aad[0] = 0x17; // record type תמיד 0x17 אחרי הצפנה
99
+ aad[1] = 0x03; aad[2] = 0x03; // "גרסת" הרשומה (TLS 1.2 בפועל לשכבת הרשומה)
100
+ var len = ciphertext.length;
101
+ aad[3] = (len >> 8) & 0xff;
102
+ aad[4] = len & 0xff;
103
+
104
+ // פתיחה (אימות + פענוח). אם ה-tag לא תקף, תחזור null/undefined לפי המימוש
105
+ var full_plaintext = gcm.open(nonce, ciphertext, aad);
106
+ if (!full_plaintext) {
107
+ throw new Error('GCM authentication failed (bad tag)');
108
+ }
109
+
110
+ return full_plaintext;
111
+ }
112
+
113
+ function parse_tls_inner_plaintext(full_plaintext) {
114
+ var j = full_plaintext.length - 1;
115
+ while (j >= 0 && full_plaintext[j] === 0x00) { j--; }
116
+ if (j < 0) throw new Error('Malformed TLSInnerPlaintext (no content type)');
117
+
118
+ var content_type = full_plaintext[j];
119
+ var content = full_plaintext.slice(0, j); // חיתוך אמיתי
120
+ return { content_type: content_type, content: content };
121
+ }
122
+
123
+
124
+ // ==== TLSSocket ====
125
+ function TLSSocket(duplex, options){
126
+ if (!(this instanceof TLSSocket)) return new TLSSocket(duplex, options);
127
+ options = options || {};
128
+
129
+ var ev = Emitter();
130
+
131
+ var context = {
132
+
133
+ options: options,
134
+
135
+ // transport (Duplex) שמחובר מבחוץ
136
+ transport: (duplex && typeof duplex.write === 'function') ? duplex : null,
137
+
138
+ // TLSSession פנימי בלבד
139
+ session: new TLSSession({
140
+ isServer: !!options.isServer,
141
+ servername: options.servername,
142
+ ALPNProtocols: options.ALPNProtocols || null,
143
+ SNICallback: options.SNICallback || null
144
+ }),
145
+
146
+ // Handshake write
147
+ handshake_write_key: null,
148
+ handshake_write_iv: null,
149
+ handshake_write_seq: 0,
150
+ handshake_write_aead: null,
151
+
152
+ // Handshake read
153
+ handshake_read_key: null,
154
+ handshake_read_iv: null,
155
+ handshake_read_seq: 0,
156
+ handshake_read_aead: null,
157
+
158
+
159
+ // Application write
160
+ app_write_key: null,
161
+ app_write_iv: null,
162
+ app_write_seq: 0,
163
+ app_write_aead: null,
164
+
165
+ // Application read
166
+ app_read_key: null,
167
+ app_read_iv: null,
168
+ app_read_seq: 0,
169
+ app_read_aead: null,
170
+
171
+ using_app_keys: false,
172
+
173
+
174
+
175
+
176
+ // באפרים ותורים
177
+ readBuffer: Buffer.alloc(0),
178
+ appWriteQueue: [],
179
+
180
+ // מצבים כלליים
181
+ destroyed: false,
182
+ secureEstablished: false,
183
+
184
+ // legacy record version (TLS1.3)
185
+ rec_version: 0x0303
186
+ };
187
+
188
+
189
+ // === שכבת הרשומות (Record Layer) ===
190
+ function writeRecord(type, payload){
191
+ if (!context.transport) throw new Error('No transport attached to TLSSocket');
192
+ var rec = Buffer.allocUnsafe(5 + payload.length);
193
+ rec.writeUInt8(type, 0);
194
+ rec.writeUInt16BE(context.rec_version, 1);
195
+ rec.writeUInt16BE(payload.length, 3);
196
+ payload.copy(rec, 5);
197
+ try { context.transport.write(rec); } catch(e){ ev.emit('error', e); }
198
+ }
199
+
200
+ function writeAppData(plain){
201
+ //console.log('...');
202
+
203
+ if(context.session.context.server_app_traffic_secret!==null){
204
+ if(context.app_write_key==null || context.app_write_iv==null){
205
+ var d=tls_derive_from_tls_secrets(context.session.context.server_app_traffic_secret,context.session.context.selected_cipher_suite);
206
+
207
+ context.app_write_key=d.key;
208
+ context.app_write_iv=d.iv;
209
+ }
210
+ }else{
211
+ //console.log('no key yet...');
212
+ }
213
+
214
+ var enc1 = encrypt_tls_record(CT.APPLICATION_DATA,plain, context.app_write_key, get_nonce(context.app_write_iv,context.app_write_seq));
215
+
216
+ context.app_write_seq++;
217
+
218
+ try {
219
+
220
+ //console.log(enc1);
221
+
222
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc1));
223
+ } catch(e){
224
+ ev.emit('error', e);
225
+ }
226
+
227
+ }
228
+
229
+ function processCiphertext(body){
230
+
231
+ var out=null;
232
+
233
+ if(context.using_app_keys==true){
234
+
235
+ if(context.session.context.client_app_traffic_secret!==null){
236
+ if(context.app_read_key==null || context.app_read_iv==null){
237
+ var d=tls_derive_from_tls_secrets(context.session.context.client_app_traffic_secret,context.session.context.selected_cipher_suite);
238
+
239
+ context.app_read_key=d.key;
240
+ context.app_read_iv=d.iv;
241
+ }
242
+
243
+ out = decrypt_tls_record(body, context.app_read_key, get_nonce(context.app_read_iv,context.app_read_seq));
244
+
245
+ context.app_read_seq++;
246
+
247
+ }else{
248
+ //...
249
+ }
250
+ }else{
251
+
252
+
253
+ if(context.session.context.client_handshake_traffic_secret!==null){
254
+ if(context.handshake_read_key==null || context.handshake_read_iv==null){
255
+ var d=tls_derive_from_tls_secrets(context.session.context.client_handshake_traffic_secret,context.session.context.selected_cipher_suite);
256
+
257
+ context.handshake_read_key=d.key;
258
+ context.handshake_read_iv=d.iv;
259
+ }
260
+
261
+ out = decrypt_tls_record(body, context.handshake_read_key, get_nonce(context.handshake_read_iv,context.handshake_read_seq));
262
+
263
+ context.handshake_read_seq++;
264
+
265
+ }else{
266
+ //...
267
+ }
268
+ }
269
+
270
+
271
+ if(out!==null){
272
+ var {content_type, content} = parse_tls_inner_plaintext(out);
273
+
274
+ if (content_type === CT.HANDSHAKE || content_type === CT.ALERT) {
275
+ //var cls = usingApp ? 2 : 1; // 1=handshake-keys, 2=app-keys
276
+ try { context.session.message(new Uint8Array(content)); } catch(e){ ev.emit('error', e); }
277
+ return;
278
+ }
279
+
280
+ if (content_type === CT.APPLICATION_DATA) { ev.emit('data', content); return; }
281
+ if (content_type === CT.CHANGE_CIPHER_SPEC) { return; }
282
+ }
283
+
284
+
285
+
286
+ }
287
+
288
+
289
+ function parseRecordsAndDispatch(){
290
+ while (context.readBuffer.length >= 5) {
291
+ var type = context.readBuffer.readUInt8(0);
292
+ var ver = context.readBuffer.readUInt16BE(1);
293
+ var len = context.readBuffer.readUInt16BE(3);
294
+ if (context.readBuffer.length < 5 + len) break;
295
+
296
+ var body = context.readBuffer.slice(5, 5+len);
297
+ context.readBuffer = context.readBuffer.slice(5+len);
298
+
299
+ if (type === CT.APPLICATION_DATA) {
300
+ try { processCiphertext(body); } catch(e){ ev.emit('error', e); }
301
+ continue;
302
+ }
303
+
304
+ if (type === CT.HANDSHAKE || type === CT.ALERT || type === CT.CHANGE_CIPHER_SPEC) {
305
+ try { context.session.message(new Uint8Array(body)); } catch(e){ ev.emit('error', e); }
306
+ continue;
307
+ }
308
+ }
309
+ }
310
+
311
+
312
+ function bindTransport(){
313
+ if (!context.transport) return;
314
+ context.transport.on('data', function(chunk){
315
+ context.readBuffer = Buffer.concat([context.readBuffer, chunk]);
316
+ parseRecordsAndDispatch();
317
+ });
318
+ context.transport.on('error', function(err){ ev.emit('error', err); });
319
+ context.transport.on('close', function(){ ev.emit('close'); });
320
+ }
321
+
322
+ context.session.on('message', function(epoch, seq, type, data){
323
+ var buf = toBuf(data || []);
324
+
325
+ if (epoch === 0) {
326
+ // ברור (ClientHello/ServerHello/CCS/Alert מוקדם)
327
+ writeRecord(CT.HANDSHAKE, buf);
328
+ return;
329
+ }
330
+
331
+ if (epoch === 1) {
332
+
333
+ //need to create it...
334
+ if(context.session.context.server_handshake_traffic_secret!==null){
335
+
336
+ if(context.handshake_write_key==null || context.handshake_write_iv==null){
337
+ var d=tls_derive_from_tls_secrets(context.session.context.server_handshake_traffic_secret,context.session.context.selected_cipher_suite);
338
+
339
+ context.handshake_write_key=d.key;
340
+ context.handshake_write_iv=d.iv;
341
+ }
342
+
343
+
344
+ var enc1 = encrypt_tls_record(CT.HANDSHAKE, buf, context.handshake_write_key, get_nonce(context.handshake_write_iv,context.handshake_write_seq));
345
+
346
+ context.handshake_write_seq++;
347
+
348
+ try {
349
+ //var enc1 = aeadEncrypt(context.handshake_write_key, context.handshake_write_iv, TLS_CIPHER_SUITES[context.session.context.selected_cipher_suite].cipher, context.handshake_write_seq, 0x0304, CT.HANDSHAKE, buf);
350
+
351
+ //console.log(enc1);
352
+
353
+ writeRecord(CT.APPLICATION_DATA, Buffer.from(enc1));
354
+ } catch(e){
355
+ ev.emit('error', e);
356
+ }
357
+
358
+ }else{
359
+ ev.emit('error', new Error('Missing handshake write keys'));
360
+ }
361
+ }
362
+
363
+ if (epoch === 2) {
364
+ // Post-Handshake מוצפן (inner_type=HANDSHAKE) תחת מפתחות Application
365
+ if (!context.application_write) { ev.emit('error', new Error('Missing application write keys')); return; }
366
+ try {
367
+ var enc2 = aeadEncrypt(context.application_write, CT.HANDSHAKE, buf);
368
+ writeRecord(CT.APPLICATION_DATA, enc2);
369
+ } catch(e){ ev.emit('error', e); }
370
+ return;
371
+ }
372
+ });
373
+
374
+
375
+ context.session.on('hello', function(info){
376
+
377
+ context.rec_version = 0x0303; // TLS 1.3 legacy record version
378
+
379
+ context.session.set_context({
380
+
381
+ local_versions: [0x0304],
382
+ local_alpns: ['http/1.1'],
383
+ local_groups: [0x001d, 0x0017, 0x0018],
384
+ local_cipher_suites: [
385
+ 0x1301,
386
+ 0x1302,
387
+ 0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
388
+ 0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
389
+ 0xCCA8 // ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (אם מימשת)
390
+ ],
391
+
392
+ // ---- אלגוריתמי חתימה (TLS 1.2 → RSA-PKCS1, לא PSS) ----
393
+ // 0x0401 = rsa_pkcs1_sha256, 0x0501 = rsa_pkcs1_sha384, 0x0601 = rsa_pkcs1_sha512
394
+ local_signature_algorithms: [0x0401, 0x0501, 0x0601],
395
+ // אופציונלי (לטובת חלק מהלקוחות): אותו דבר גם ל-signature_algorithms_cert
396
+ local_signature_algorithms_cert: [0x0401, 0x0501, 0x0601],
397
+
398
+ //local_cert_chain: [{ cert: new Uint8Array(cert.raw)}],
399
+ //cert_private_key: new Uint8Array(private_key_der)
400
+ });
401
+ });
402
+
403
+ context.session.on('secureConnect', function(){
404
+ context.using_app_keys=true;
405
+ ev.emit('secureConnect');
406
+ });
407
+
408
+ // אם הועבר duplex בבנאי — להתחיל לקלוט
409
+ if (context.transport) {
410
+ bindTransport();
411
+ }
412
+
413
+ // === API ציבורי (ללא חשיפת session) ===
414
+ var api = {
415
+ on: function(name, fn){ ev.on(name, fn); },
416
+
417
+ setSocket: function(duplex2){
418
+ if (!duplex2 || typeof duplex2.write !== 'function') throw new Error('setSocket expects a Duplex-like stream');
419
+ context.transport = duplex2;
420
+ bindTransport();
421
+ },
422
+
423
+ write: function(data){
424
+ if (context.destroyed) return false;
425
+ var buf = toBuf(data);
426
+ if (!context.using_app_keys) { context.appWriteQueue.push(buf); return true; }
427
+ return writeAppData(buf);
428
+ },
429
+
430
+ end: function(data){
431
+ if (context.destroyed) return;
432
+ if (typeof data !== 'undefined' && data !== null) api.write(data);
433
+ try { context.transport && context.transport.end && context.transport.end(); } catch(e){}
434
+ },
435
+
436
+ destroy: function(){
437
+ if (context.destroyed) return;
438
+ context.destroyed = true;
439
+ try { context.transport && context.transport.destroy && context.transport.destroy(); } catch(e){}
440
+ },
441
+
442
+ getCipher: function(){
443
+ var cs = context.session && context.session.context && context.session.context.selected_cipher_suite;
444
+ return { name: cs || 'TLS_AES_128_GCM_SHA256', version: 'TLSv1.3' };
445
+ },
446
+ getPeerCertificate: function(){ return null; },
447
+ authorized: function(){ return true; }
448
+ };
449
+
450
+ for (var k in api) if (Object.prototype.hasOwnProperty.call(api,k)) this[k] = api[k];
451
+
452
+
453
+ return this;
454
+ }
455
+
456
+ module.exports = TLSSocket;
package/utils.js ADDED
@@ -0,0 +1,88 @@
1
+
2
+ function concatUint8Arrays(arrays) {
3
+ var totalLength = 0;
4
+ for (var i = 0; i < arrays.length; i++) {
5
+ totalLength += arrays[i].length;
6
+ }
7
+
8
+ var result = new Uint8Array(totalLength);
9
+ var offset = 0;
10
+
11
+ for (var i = 0; i < arrays.length; i++) {
12
+ result.set(arrays[i], offset);
13
+ offset += arrays[i].length;
14
+ }
15
+
16
+ return result;
17
+ }
18
+
19
+ function arraybufferEqual(buf1, buf2) {
20
+ //if (buf1 === buf2) {
21
+ //return true;
22
+ //}
23
+
24
+ if (buf1.byteLength !== buf2.byteLength) {
25
+ return false;
26
+ }
27
+
28
+ var view1 = new DataView(buf1);
29
+ var view2 = new DataView(buf2);
30
+
31
+ for (let i = 0; i < buf1.byteLength; i++) {
32
+ if (view1.getUint8(i) !== view2.getUint8(i)) {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ return true;
38
+ }
39
+
40
+ function arraysEqual(a, b) {
41
+ //if (a === b) return true;
42
+ if (a == null || b == null) return false;
43
+ if (a.length !== b.length) return false;
44
+
45
+ // If you don't care about the order of the elements inside
46
+ // the array, you should sort both arrays here.
47
+ // Please note that calling sort on an array will modify that array.
48
+ // you might want to clone your array first.
49
+
50
+ for (var i = 0; i < a.length; ++i) {
51
+ if(typeof a[i] !== 'undefined' && typeof b[i] !== 'undefined' && a[i]!==null && b[i]!==null && typeof a[i].byteLength == 'number' && typeof b[i].byteLength == 'number'){
52
+ if(arraybufferEqual(a[i],b[i])==false){
53
+ return false;
54
+ }
55
+ }else{
56
+ if(typeof a[i]=='string' && typeof b[i]=='string'){
57
+ if (a[i] !== b[i]){
58
+ return false;
59
+ }
60
+ }else if(a[i].constructor==RegExp && typeof b[i]=='string'){
61
+ if(a[i].test(b[i])==false){
62
+ return false;
63
+ }
64
+ }else if(typeof a[i]=='string' && b[i].constructor==RegExp){
65
+ if(b[i].test(a[i])==false){
66
+ return false;
67
+ }
68
+ //}else if(a[i] instanceof Object && b[i] instanceof Object && Object.keys(a[i]).length>0 && Object.keys(b[i]).length>0){
69
+ //if(_this.objectEquals(a[i],b[i])==false){
70
+ // return false;
71
+ //}
72
+ }else{
73
+ if (a[i] !== b[i]){
74
+ return false;
75
+ }
76
+ }
77
+
78
+ }
79
+ }
80
+ return true;
81
+ };
82
+
83
+
84
+ module.exports = {
85
+ concatUint8Arrays,
86
+ arraybufferEqual,
87
+ arraysEqual
88
+ };