lemon-tls 0.1.1 → 0.2.1

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