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/LICENSE +201 -0
- package/README.md +185 -0
- package/crypto.js +383 -0
- package/index.js +15 -0
- package/lemontls.svg +1 -0
- package/package.json +62 -0
- package/secure_context.js +196 -0
- package/tls_server.js +0 -0
- package/tls_session.js +1441 -0
- package/tls_socket.js +456 -0
- package/utils.js +88 -0
- package/wire.js +1672 -0
package/crypto.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
var { hmac: nobleHmac } = require('@noble/hashes/hmac');
|
|
2
|
+
var { hkdf, extract: hkdf_extract_noble, expand: hkdf_expand_noble } = require('@noble/hashes/hkdf');
|
|
3
|
+
var { sha256, sha384 } = require('@noble/hashes/sha2');
|
|
4
|
+
var { p256 } = require('@noble/curves/nist'); // אם תרצה להשתמש בהמשך
|
|
5
|
+
var { x25519 } = require('@noble/curves/ed25519'); // אם תרצה להשתמש בהמשך
|
|
6
|
+
|
|
7
|
+
var {
|
|
8
|
+
concatUint8Arrays,
|
|
9
|
+
} = require('./utils');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var TLS_CIPHER_SUITES = {
|
|
14
|
+
// ----------------------
|
|
15
|
+
// TLS 1.3 (RFC 8446)
|
|
16
|
+
// ----------------------
|
|
17
|
+
0x1301: { // TLS_AES_128_GCM_SHA256
|
|
18
|
+
tls: 13,
|
|
19
|
+
kex: 'TLS13',
|
|
20
|
+
sig: 'TLS13',
|
|
21
|
+
cipher: 'AES_128_GCM',
|
|
22
|
+
aead: true,
|
|
23
|
+
keylen: 16,
|
|
24
|
+
ivlen: 12,
|
|
25
|
+
hash: 'sha256'
|
|
26
|
+
},
|
|
27
|
+
0x1302: { // TLS_AES_256_GCM_SHA384
|
|
28
|
+
tls: 13,
|
|
29
|
+
kex: 'TLS13',
|
|
30
|
+
sig: 'TLS13',
|
|
31
|
+
cipher: 'AES_256_GCM',
|
|
32
|
+
aead: true,
|
|
33
|
+
keylen: 32,
|
|
34
|
+
ivlen: 12,
|
|
35
|
+
hash: 'sha384'
|
|
36
|
+
},
|
|
37
|
+
0x1303: { // TLS_CHACHA20_POLY1305_SHA256
|
|
38
|
+
tls: 13,
|
|
39
|
+
kex: 'TLS13',
|
|
40
|
+
sig: 'TLS13',
|
|
41
|
+
cipher: 'CHACHA20_POLY1305',
|
|
42
|
+
aead: true,
|
|
43
|
+
keylen: 32,
|
|
44
|
+
ivlen: 12,
|
|
45
|
+
hash: 'sha256'
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// ----------------------
|
|
49
|
+
// TLS 1.2 AEAD (GCM / CHACHA20)
|
|
50
|
+
// ----------------------
|
|
51
|
+
0xC02F: { // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
52
|
+
tls: 12,
|
|
53
|
+
kex: 'ECDHE_RSA',
|
|
54
|
+
sig: 'RSA',
|
|
55
|
+
cipher: 'AES_128_GCM',
|
|
56
|
+
aead: true,
|
|
57
|
+
keylen: 16,
|
|
58
|
+
fixed_ivlen: 4,
|
|
59
|
+
record_ivlen: 8,
|
|
60
|
+
ivlen: 12,
|
|
61
|
+
hash: 'sha256'
|
|
62
|
+
},
|
|
63
|
+
0xC030: { // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
64
|
+
tls: 12,
|
|
65
|
+
kex: 'ECDHE_RSA',
|
|
66
|
+
sig: 'RSA',
|
|
67
|
+
cipher: 'AES_256_GCM',
|
|
68
|
+
aead: true,
|
|
69
|
+
keylen: 32,
|
|
70
|
+
fixed_ivlen: 4,
|
|
71
|
+
record_ivlen: 8,
|
|
72
|
+
ivlen: 12,
|
|
73
|
+
hash: 'sha384'
|
|
74
|
+
},
|
|
75
|
+
0xC02B: { // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
76
|
+
tls: 12,
|
|
77
|
+
kex: 'ECDHE_ECDSA',
|
|
78
|
+
sig: 'ECDSA',
|
|
79
|
+
cipher: 'AES_128_GCM',
|
|
80
|
+
aead: true,
|
|
81
|
+
keylen: 16,
|
|
82
|
+
fixed_ivlen: 4,
|
|
83
|
+
record_ivlen: 8,
|
|
84
|
+
ivlen: 12,
|
|
85
|
+
hash: 'sha256'
|
|
86
|
+
},
|
|
87
|
+
0xC02C: { // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
|
88
|
+
tls: 12,
|
|
89
|
+
kex: 'ECDHE_ECDSA',
|
|
90
|
+
sig: 'ECDSA',
|
|
91
|
+
cipher: 'AES_256_GCM',
|
|
92
|
+
aead: true,
|
|
93
|
+
keylen: 32,
|
|
94
|
+
fixed_ivlen: 4,
|
|
95
|
+
record_ivlen: 8,
|
|
96
|
+
ivlen: 12,
|
|
97
|
+
hash: 'sha384'
|
|
98
|
+
},
|
|
99
|
+
0xCCA8: { // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
100
|
+
tls: 12,
|
|
101
|
+
kex: 'ECDHE_RSA',
|
|
102
|
+
sig: 'RSA',
|
|
103
|
+
cipher: 'CHACHA20_POLY1305',
|
|
104
|
+
aead: true,
|
|
105
|
+
keylen: 32,
|
|
106
|
+
fixed_ivlen: 4,
|
|
107
|
+
record_ivlen: 8,
|
|
108
|
+
ivlen: 12,
|
|
109
|
+
hash: 'sha256'
|
|
110
|
+
},
|
|
111
|
+
0xCCA9: { // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
|
112
|
+
tls: 12,
|
|
113
|
+
kex: 'ECDHE_ECDSA',
|
|
114
|
+
sig: 'ECDSA',
|
|
115
|
+
cipher: 'CHACHA20_POLY1305',
|
|
116
|
+
aead: true,
|
|
117
|
+
keylen: 32,
|
|
118
|
+
fixed_ivlen: 4,
|
|
119
|
+
record_ivlen: 8,
|
|
120
|
+
ivlen: 12,
|
|
121
|
+
hash: 'sha256'
|
|
122
|
+
},
|
|
123
|
+
0xCCAA: { // TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
124
|
+
tls: 12,
|
|
125
|
+
kex: 'DHE_RSA',
|
|
126
|
+
sig: 'RSA',
|
|
127
|
+
cipher: 'CHACHA20_POLY1305',
|
|
128
|
+
aead: true,
|
|
129
|
+
keylen: 32,
|
|
130
|
+
fixed_ivlen: 4,
|
|
131
|
+
record_ivlen: 8,
|
|
132
|
+
ivlen: 12,
|
|
133
|
+
hash: 'sha256'
|
|
134
|
+
},
|
|
135
|
+
0x009C: { // TLS_RSA_WITH_AES_128_GCM_SHA256
|
|
136
|
+
tls: 12,
|
|
137
|
+
kex: 'RSA',
|
|
138
|
+
sig: 'RSA',
|
|
139
|
+
cipher: 'AES_128_GCM',
|
|
140
|
+
aead: true,
|
|
141
|
+
keylen: 16,
|
|
142
|
+
fixed_ivlen: 4,
|
|
143
|
+
record_ivlen: 8,
|
|
144
|
+
ivlen: 12,
|
|
145
|
+
hash: 'sha256'
|
|
146
|
+
},
|
|
147
|
+
0x009D: { // TLS_RSA_WITH_AES_256_GCM_SHA384
|
|
148
|
+
tls: 12,
|
|
149
|
+
kex: 'RSA',
|
|
150
|
+
sig: 'RSA',
|
|
151
|
+
cipher: 'AES_256_GCM',
|
|
152
|
+
aead: true,
|
|
153
|
+
keylen: 32,
|
|
154
|
+
fixed_ivlen: 4,
|
|
155
|
+
record_ivlen: 8,
|
|
156
|
+
ivlen: 12,
|
|
157
|
+
hash: 'sha384'
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// ----------------------
|
|
161
|
+
// TLS 1.2 CBC (Legacy)
|
|
162
|
+
// ----------------------
|
|
163
|
+
0xC013: { // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
|
164
|
+
tls: 12,
|
|
165
|
+
kex: 'ECDHE_RSA',
|
|
166
|
+
sig: 'RSA',
|
|
167
|
+
cipher: 'AES_128_CBC',
|
|
168
|
+
aead: false,
|
|
169
|
+
keylen: 16,
|
|
170
|
+
ivlen: 16,
|
|
171
|
+
mac: 'sha1',
|
|
172
|
+
maclen: 20,
|
|
173
|
+
hash: 'sha256'
|
|
174
|
+
},
|
|
175
|
+
0xC014: { // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
|
|
176
|
+
tls: 12,
|
|
177
|
+
kex: 'ECDHE_RSA',
|
|
178
|
+
sig: 'RSA',
|
|
179
|
+
cipher: 'AES_256_CBC',
|
|
180
|
+
aead: false,
|
|
181
|
+
keylen: 32,
|
|
182
|
+
ivlen: 16,
|
|
183
|
+
mac: 'sha1',
|
|
184
|
+
maclen: 20,
|
|
185
|
+
hash: 'sha256'
|
|
186
|
+
},
|
|
187
|
+
0x003C: { // TLS_RSA_WITH_AES_128_CBC_SHA256
|
|
188
|
+
tls: 12,
|
|
189
|
+
kex: 'RSA',
|
|
190
|
+
sig: 'RSA',
|
|
191
|
+
cipher: 'AES_128_CBC',
|
|
192
|
+
aead: false,
|
|
193
|
+
keylen: 16,
|
|
194
|
+
ivlen: 16,
|
|
195
|
+
mac: 'sha256',
|
|
196
|
+
maclen: 32,
|
|
197
|
+
hash: 'sha256'
|
|
198
|
+
},
|
|
199
|
+
0x003D: { // TLS_RSA_WITH_AES_256_CBC_SHA256
|
|
200
|
+
tls: 12,
|
|
201
|
+
kex: 'RSA',
|
|
202
|
+
sig: 'RSA',
|
|
203
|
+
cipher: 'AES_256_CBC',
|
|
204
|
+
aead: false,
|
|
205
|
+
keylen: 32,
|
|
206
|
+
ivlen: 16,
|
|
207
|
+
mac: 'sha256',
|
|
208
|
+
maclen: 32,
|
|
209
|
+
hash: 'sha256'
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
// --- Hash helpers: מקבלים מחרוזת ומחזירים פונקציית hash + outputLen ---
|
|
218
|
+
function getHashFn(hashName) {
|
|
219
|
+
if (hashName === 'sha256') return sha256;
|
|
220
|
+
if (hashName === 'sha384') return sha384;
|
|
221
|
+
throw new Error('Unsupported hash: ' + hashName);
|
|
222
|
+
}
|
|
223
|
+
function getHashLen(hashName) {
|
|
224
|
+
var fn = getHashFn(hashName);
|
|
225
|
+
return fn.outputLen|0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- HMAC (עם noble) ---
|
|
229
|
+
function hmac(hashName, keyU8, dataU8) {
|
|
230
|
+
var hashFn = getHashFn(hashName);
|
|
231
|
+
return nobleHmac(hashFn, keyU8, dataU8); // Uint8Array
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- HKDF wrappers (hash כמחרוזת) ---
|
|
235
|
+
function hkdf_extract(hashName, saltU8, ikmU8) {
|
|
236
|
+
var hashFn = getHashFn(hashName);
|
|
237
|
+
// extract(hash, ikm, salt?)
|
|
238
|
+
return hkdf_extract_noble(hashFn, ikmU8, saltU8);
|
|
239
|
+
}
|
|
240
|
+
function hkdf_expand(hashName, prkU8, infoU8, length) {
|
|
241
|
+
var hashFn = getHashFn(hashName);
|
|
242
|
+
// expand(hash, prk, info, length)
|
|
243
|
+
return hkdf_expand_noble(hashFn, prkU8, infoU8, length|0);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- TLS 1.3 label builder ---
|
|
247
|
+
function build_hkdf_label(label, context, length) {
|
|
248
|
+
var prefix = 'tls13 ';
|
|
249
|
+
var enc = new TextEncoder();
|
|
250
|
+
var full = enc.encode(prefix + label);
|
|
251
|
+
var info = new Uint8Array(2 + 1 + full.length + 1 + context.length);
|
|
252
|
+
|
|
253
|
+
// length (2 bytes BE)
|
|
254
|
+
info[0] = (length >>> 8) & 0xff;
|
|
255
|
+
info[1] = (length ) & 0xff;
|
|
256
|
+
|
|
257
|
+
// label
|
|
258
|
+
info[2] = full.length;
|
|
259
|
+
info.set(full, 3);
|
|
260
|
+
|
|
261
|
+
// context
|
|
262
|
+
var ofs = 3 + full.length;
|
|
263
|
+
info[ofs] = context.length;
|
|
264
|
+
info.set(context, ofs + 1);
|
|
265
|
+
|
|
266
|
+
return info;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function hkdf_expand_label(hashName, secret, label, context, length) {
|
|
270
|
+
var info = build_hkdf_label(label, context, length|0);
|
|
271
|
+
return hkdf_expand(hashName, secret, info, length|0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// --- TLS 1.3: derive handshake secrets ---
|
|
275
|
+
function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
|
|
276
|
+
var hashFn = getHashFn(hashName);
|
|
277
|
+
|
|
278
|
+
var hashLen = hashFn.outputLen|0;
|
|
279
|
+
|
|
280
|
+
var empty = new Uint8Array(0);
|
|
281
|
+
var zeros = new Uint8Array(hashLen); // "zeros" כ־salt בגודל hashLen
|
|
282
|
+
|
|
283
|
+
// early_secret = HKDF-Extract(zeros, PSK=empty) כשאין PSK
|
|
284
|
+
var early_secret = hkdf_extract(hashName, empty, zeros);
|
|
285
|
+
|
|
286
|
+
// derived_secret = HKDF-Expand-Label(early_secret, "derived", Hash(""), Hash.length)
|
|
287
|
+
var h_empty = hashFn(empty);
|
|
288
|
+
var derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
|
|
289
|
+
|
|
290
|
+
// handshake_secret = HKDF-Extract(derived_secret, shared_secret)
|
|
291
|
+
var handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
|
|
292
|
+
|
|
293
|
+
// transcript_hash עד הנקודה הנוכחית
|
|
294
|
+
|
|
295
|
+
var transcript_hash = hashFn(transcript);
|
|
296
|
+
|
|
297
|
+
// תנועת handshake
|
|
298
|
+
var client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
|
|
299
|
+
var server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
handshake_secret: handshake_secret,
|
|
303
|
+
client_handshake_traffic_secret: client_handshake_traffic_secret,
|
|
304
|
+
server_handshake_traffic_secret: server_handshake_traffic_secret,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- TLS 1.3: derive application secrets ---
|
|
309
|
+
function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
|
|
310
|
+
var hashFn = getHashFn(hashName);
|
|
311
|
+
var hashLen = hashFn.outputLen|0;
|
|
312
|
+
|
|
313
|
+
var empty = new Uint8Array(0);
|
|
314
|
+
var zeros = new Uint8Array(hashLen);
|
|
315
|
+
|
|
316
|
+
// derived_secret = HKDF-Expand-Label(handshake_secret, "derived", Hash(""), Hash.length)
|
|
317
|
+
var h_empty = hashFn(empty);
|
|
318
|
+
var derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
|
|
319
|
+
|
|
320
|
+
// master_secret = HKDF-Extract(derived_secret, zeros)
|
|
321
|
+
var master_secret = hkdf_extract(hashName, derived_secret, zeros);
|
|
322
|
+
|
|
323
|
+
// hash של ה־transcript (עד Finished של ה־server בד"כ)
|
|
324
|
+
var transcript_hash = hashFn(transcript);
|
|
325
|
+
|
|
326
|
+
// תנועת application
|
|
327
|
+
var client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
|
|
328
|
+
var server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
client_app_traffic_secret: client_app_traffic_secret,
|
|
332
|
+
server_app_traffic_secret: server_app_traffic_secret,
|
|
333
|
+
master_secret: master_secret
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
function build_cert_verify_tbs(hashName, isServer, transcript){
|
|
339
|
+
if(isServer){
|
|
340
|
+
var label = new TextEncoder().encode("TLS 1.3, server CertificateVerify");
|
|
341
|
+
}else{
|
|
342
|
+
var label = new TextEncoder().encode("TLS 1.3, client CertificateVerify");
|
|
343
|
+
}
|
|
344
|
+
var separator = new Uint8Array([0x00]);
|
|
345
|
+
var padding = new Uint8Array(64).fill(0x20);
|
|
346
|
+
|
|
347
|
+
var hashFn = getHashFn(hashName);
|
|
348
|
+
var transcript_hash = hashFn(transcript);
|
|
349
|
+
|
|
350
|
+
return concatUint8Arrays([padding, label, separator, transcript_hash]);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
function get_handshake_finished(hashName, traffic_secret, transcript) {
|
|
355
|
+
var hashFn = getHashFn(hashName);
|
|
356
|
+
var hashLen = hashFn.outputLen|0;
|
|
357
|
+
|
|
358
|
+
var empty = new Uint8Array(0);
|
|
359
|
+
|
|
360
|
+
var finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
|
|
361
|
+
var transcript_hash = hashFn(transcript);
|
|
362
|
+
var verify_data=hmac(hashName, finished_key, transcript_hash);
|
|
363
|
+
|
|
364
|
+
return verify_data;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// --- Exports ---
|
|
368
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
369
|
+
module.exports = {
|
|
370
|
+
TLS_CIPHER_SUITES,
|
|
371
|
+
getHashFn,
|
|
372
|
+
getHashLen,
|
|
373
|
+
hmac,
|
|
374
|
+
hkdf_extract,
|
|
375
|
+
hkdf_expand,
|
|
376
|
+
build_hkdf_label,
|
|
377
|
+
hkdf_expand_label,
|
|
378
|
+
derive_handshake_traffic_secrets,
|
|
379
|
+
derive_app_traffic_secrets,
|
|
380
|
+
build_cert_verify_tbs,
|
|
381
|
+
get_handshake_finished
|
|
382
|
+
};
|
|
383
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
var TLSSession = require('./tls_session');
|
|
3
|
+
var TLSSocket = require('./tls_socket');
|
|
4
|
+
var createSecureContext = require('./secure_context');
|
|
5
|
+
//var {createServer} = require('./tls_server');
|
|
6
|
+
//var constants = require('./constants');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
TLSSession: TLSSession,
|
|
10
|
+
TLSSocket: TLSSocket,
|
|
11
|
+
createSecureContext: createSecureContext,
|
|
12
|
+
//createServer: createServer
|
|
13
|
+
//DEFAULT_CIPHERS: constants.DEFAULT_CIPHERS,
|
|
14
|
+
//DEFAULT_SIGALGS: constants.DEFAULT_SIGALGS
|
|
15
|
+
};
|
package/lemontls.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="993" height="200" stroke="none" stroke-linecap="round" stroke-linejoin="round" fill="#fff" fill-rule="evenodd"><g fill-rule="nonzero"><path d="M172.4 56.8v85.4c0 5.2-2.7 10-7 12.6l-72.6 42.8c-4.3 2.5-9.4 2.5-13.6 0L6.5 154.8c-4.3-2.6-7-7.4-7-12.6V56.8c0-5.2 2.7-10 7-12.6L79.2 1.4c4.2-2.5 9.3-2.5 13.6 0l72.6 42.8c4.3 2.6 7 7.4 7 12.6z" fill="#4f4f4f"/><path d="M94.5 35C86 41.6 86.8 49.2 87 55.8l2.4-.3-.1-3c.5 0 1-.2 1.6-.3 11.7-5.5 6.4-25 6.4-25C97.3 30 90 33.5 85 37c-4.7 3.5-4.8 9-3.8 11.2 1 1.5 2.2 2.6 3.8 3C84 40 94.5 35 94.5 35z" fill="#65cd76"/><path d="M86 55.4c-5.2.1-9.3 3-10.3 6.6-2.8 10.4-26.2 14.3-26.2 45.5s23.4 35 26.2 45.5c1 3.7 5 6.5 10.3 6.6 5.2-.1 9.3-2.8 10.3-6.6 2.8-10.4 26.2-14.3 26.2-45.5S99 72.4 96.2 62c-1-3.7-5-6.5-10.3-6.6zM66.6 91c-5 8-5 25 0 33 .6 1 .5 2.3-.4 3.2-.1.1-.4.2-.5.4-1.2.6-2.7.3-3.5-1a44 44 0 0 1 0-38.3c.8-1.2 2.3-1.6 3.5-1 1.2.8 1.6 2.3.8 3.5z" fill="#ffed24"/></g><path d="M240.7 147.3h46.6V172h-75V28.3h28.3v119zm131.5-31v7.4h-49.6q0 12.3 3 19.5 1 2.5 3.6 5 2.7 2.6 5.6 2.6 3 0 7.2-5 4.3-5 6.7-12l21.2 15q-5.6 10.3-17 18-11.4 7.6-18.3 7.6-7 0-20.2-11-13.3-11-16-17-3-6-3-30 0-24 3-30 2.8-6 16-17 13.2-11 19.4-11 6 0 19.3 11 13.3 10.8 16 17 3 6 3 29.6zm-49.6-12H346q-.7-10.8-2.2-13.6-1.5-2.8-4.7-5.4-3.2-2.7-6-2.7-2.7 0-6.2 5.4-3.5 5.5-4.3 16.3zm143.6-11.6v79.2h-27V95.5q0-3.5-3.7-7.3-3.7-3.8-6.3-3.8-2.5 0-8.3 5v82.4h-27V101q0-10.5-2.5-16.3-2.5-5.7-7.8-10L406 59q6.7 5.7 10 11.7 13.3-12 18.5-12 3 0 10 4.5 7 4.5 12.7 11 16.2-15.4 22.5-15.4 6.3 0 19 11.4 12.6 11.4 12.6 22.4v39.6q0 10.5 2.4 16.2 2.5 5.8 7.8 10l-22.5 16q-14.8-11.7-14.8-33.3V95.5q0-3.5-3.7-7.3-3.7-3.8-6-3.8-2.4 0-8.8 5.2.5 2 .5 3zm121.5 71.6q-12.6 10-18.5 10-6 0-18.6-10-12.6-10-15.5-15-4.3-7.6-4.3-33 0-25.2 4.3-32.6 3-5 15.5-15 12.6-10 18.6-10 6 0 18.5 10 12.6 10 15.5 15 4.3 7.4 4.3 32.6 0 25.3-4.3 33-3 5-15.5 15zM564 147.6q3.2 3 5 3 1.8 0 5-3 3.2-3 4.6-5.5 2.7-4.5 2.7-25.5 0-21-2.7-25.6-1.4-2.5-4.6-5.5-3.2-3-5-3-2 0-5 3-3 3-4.6 5.5-2.7 4.5-2.7 25.4 0 21 2.7 25.7 1.5 2.4 4.6 5.5zM654 93v78.7h-27V101q0-10.5-2.5-16.3-2.5-5.7-7.8-10L639.3 59q7.6 6.2 11.2 14.2 15.2-14.4 21.5-14.4 6.2 0 18.8 11.4 12.7 11.4 12.7 22.4v39.6q0 10.5 2.4 16.2 2.5 5.8 7.8 10l-22.5 15.8q-14.8-11.7-14.8-33.3V95.5q0-3.5-3.7-7.3-3.7-3.8-7-3.8-3.2 0-11.8 8.7z" fill="#4f4f4f"/><path d="M737.3 28.3H819V53h-26.6v119H764V53h-26.7V28.3zm115.4 119h46.6V172h-75V28.3h28.3v119zm52.8-10.7l22.8-15.6q3.2 9.6 10.4 17.6 7.2 8 10.2 8 3 0 6.3-2 3.4-2 5.3-4 2.6-3.3 2.6-10.3 0-7-3.2-8.6-4-3.5-16-9.2-11.8-5.8-22-12.4-10-6.7-12.8-13-2.8-6.3-2.8-16.5 0-10 4.5-17.7 4-6 17.5-16.5 13.3-10.6 19.7-10.6 11.2 0 25.8 11.8 14.6 11.8 18.5 26l-22.8 15.8q-3.3-9.6-10.5-17.7-7-8-10-8-3 0-6.5 1.8-3.5 2-5.6 4.7-2.2 2.7-2.2 9.4 0 6.7 4 9 3.3 3 15 9 30 16.4 35.3 26.7 2.2 3.7 2.2 14.7 0 11-4.5 18.5-4 6-17.4 16.5-13.3 10.6-19.7 10.6-11.3 0-25.8-11.8-14.6-11.8-18.5-26z" fill="#838383"/></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lemon-tls",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JavaScript TLS 1.3/1.2 implementation for Node.js, with full control over cryptographic keys and record layer",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"openssl",
|
|
9
|
+
"boringssl",
|
|
10
|
+
"cryptlib",
|
|
11
|
+
"rustls",
|
|
12
|
+
"low-level",
|
|
13
|
+
"https",
|
|
14
|
+
"ssl",
|
|
15
|
+
"tls",
|
|
16
|
+
"tls13",
|
|
17
|
+
"tls12",
|
|
18
|
+
"nodejs",
|
|
19
|
+
"javascript",
|
|
20
|
+
"crypto",
|
|
21
|
+
"x509",
|
|
22
|
+
"handshake",
|
|
23
|
+
"alpn",
|
|
24
|
+
"sni",
|
|
25
|
+
"hkdf",
|
|
26
|
+
"aead",
|
|
27
|
+
"quic",
|
|
28
|
+
"http3",
|
|
29
|
+
"dtls",
|
|
30
|
+
"sctp",
|
|
31
|
+
"secure",
|
|
32
|
+
"network",
|
|
33
|
+
"backend",
|
|
34
|
+
"server"
|
|
35
|
+
],
|
|
36
|
+
"author": "colocohen",
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/colocohen/lemon-tls.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/colocohen/lemon-tls/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/colocohen/lemon-tls",
|
|
46
|
+
"funding": [
|
|
47
|
+
{
|
|
48
|
+
"type": "github",
|
|
49
|
+
"url": "https://github.com/sponsors/colocohen"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "opencollective",
|
|
53
|
+
"url": "https://opencollective.com/colocohen"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@noble/curves": "^2.0.0",
|
|
58
|
+
"@noble/hashes": "^2.0.0",
|
|
59
|
+
"@stablelib/aes": "^2.0.1",
|
|
60
|
+
"@stablelib/gcm": "^2.0.1"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
function looksLikePath(x) {
|
|
6
|
+
return typeof x === 'string' && (x.indexOf('\n') === -1) && (x.length < 4096) &&
|
|
7
|
+
(x.indexOf('-----BEGIN') === -1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readMaybeFile(x) {
|
|
11
|
+
if (x == null) return null;
|
|
12
|
+
if (looksLikePath(x)) return fs.readFileSync(path.resolve(String(x)));
|
|
13
|
+
if (Buffer.isBuffer(x)) return x;
|
|
14
|
+
if (x instanceof Uint8Array) return Buffer.from(x);
|
|
15
|
+
if (typeof x === 'string') return Buffer.from(x, 'utf8');
|
|
16
|
+
throw new Error('Unsupported input type (expected path/string/Buffer/Uint8Array).');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isPEM(buf) {
|
|
20
|
+
if (!buf) return false;
|
|
21
|
+
var s = buf.toString('utf8');
|
|
22
|
+
return s.indexOf('-----BEGIN ') !== -1 && s.indexOf('-----END ') !== -1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function splitPEMBlocks(pemText) {
|
|
26
|
+
var out = [];
|
|
27
|
+
var re = /-----BEGIN ([A-Z0-9 \-]+)-----([\s\S]*?)-----END \1-----/g;
|
|
28
|
+
var m;
|
|
29
|
+
while ((m = re.exec(pemText)) !== null) {
|
|
30
|
+
var typ = m[1].trim();
|
|
31
|
+
var b64 = m[2].replace(/[\r\n\s]/g, '');
|
|
32
|
+
var derBuf = Buffer.from(b64, 'base64');
|
|
33
|
+
out.push({ type: typ, der: new Uint8Array(derBuf) });
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ensureArray(x) { return x == null ? [] : (Array.isArray(x) ? x : [x]); }
|
|
39
|
+
|
|
40
|
+
function normalizeCA(caOption) {
|
|
41
|
+
var arr = ensureArray(caOption);
|
|
42
|
+
var ders = [];
|
|
43
|
+
for (var i = 0; i < arr.length; i++) {
|
|
44
|
+
var raw = readMaybeFile(arr[i]);
|
|
45
|
+
if (!raw) continue;
|
|
46
|
+
if (isPEM(raw)) {
|
|
47
|
+
var blocks = splitPEMBlocks(raw.toString('utf8'));
|
|
48
|
+
for (var j = 0; j < blocks.length; j++) {
|
|
49
|
+
if (blocks[j].type.indexOf('CERTIFICATE') !== -1) ders.push(blocks[j].der);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
ders.push(new Uint8Array(raw));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return ders;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeX509FromDerOrPem(buf) {
|
|
59
|
+
return new crypto.X509Certificate(Buffer.from(buf));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function makePrivateKeyFromDerOrPem(buf, passphrase) {
|
|
63
|
+
if (isPEM(buf)) {
|
|
64
|
+
return crypto.createPrivateKey({ key: buf, format: 'pem', passphrase: passphrase });
|
|
65
|
+
} else {
|
|
66
|
+
var der = Buffer.from(buf), keyObj = null;
|
|
67
|
+
try {
|
|
68
|
+
keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8', passphrase: passphrase });
|
|
69
|
+
} catch (e1) {
|
|
70
|
+
try {
|
|
71
|
+
keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs1', passphrase: passphrase });
|
|
72
|
+
} catch (e2) {
|
|
73
|
+
keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'sec1', passphrase: passphrase });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return keyObj;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function exportKeyPkcs8Der(keyObj) {
|
|
81
|
+
return new Uint8Array(keyObj.export({ format: 'der', type: 'pkcs8' }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function u8eq(a,b){ if (a.length!==b.length) return false; for (var i=0;i<a.length;i++) if (a[i]!==b[i]) return false; return true; }
|
|
85
|
+
function dedupeDerArray(arr) {
|
|
86
|
+
var out = [];
|
|
87
|
+
for (var i=0;i<arr.length;i++) {
|
|
88
|
+
var keep = true;
|
|
89
|
+
for (var j=0;j<out.length;j++) { if (u8eq(arr[i], out[j])) { keep = false; break; } }
|
|
90
|
+
if (keep) out.push(arr[i]);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* createSecureContext(options)
|
|
97
|
+
* key/cert/ca יכולים להיות: path | Buffer | Uint8Array | string(PEM)
|
|
98
|
+
* מחזיר:
|
|
99
|
+
* - certificateChain: [{ cert: Uint8Array }, ...] // leaf תחילה, אח"כ intermediates
|
|
100
|
+
* - privateKey: Uint8Array // PKCS#8 DER
|
|
101
|
+
* - ca: Uint8Array[] // Trust store (לא משולח ללקוח)
|
|
102
|
+
* - ocsp: Uint8Array|null
|
|
103
|
+
* - ticketKeys: Uint8Array|null
|
|
104
|
+
* - certObjs, keyObj (עזרי debug/לוגיקה)
|
|
105
|
+
*/
|
|
106
|
+
function createSecureContext(options) {
|
|
107
|
+
if (!options) options = {};
|
|
108
|
+
|
|
109
|
+
// --- תעודות (כולל שרשרת בתוך cert אם קיימת) ---
|
|
110
|
+
var certBlocksDer = [];
|
|
111
|
+
var certObjs = [];
|
|
112
|
+
if (options.cert != null) {
|
|
113
|
+
var cRaw = readMaybeFile(options.cert);
|
|
114
|
+
if (isPEM(cRaw)) {
|
|
115
|
+
var blocks = splitPEMBlocks(cRaw.toString('utf8'));
|
|
116
|
+
for (var i=0;i<blocks.length;i++) {
|
|
117
|
+
if (blocks[i].type.indexOf('CERTIFICATE') !== -1) {
|
|
118
|
+
certBlocksDer.push(blocks[i].der);
|
|
119
|
+
certObjs.push(makeX509FromDerOrPem(blocks[i].der));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
var der = new Uint8Array(cRaw);
|
|
124
|
+
certBlocksDer.push(der);
|
|
125
|
+
certObjs.push(makeX509FromDerOrPem(der));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- מפתח פרטי ---
|
|
130
|
+
var keyObj = null;
|
|
131
|
+
var privateKey = null;
|
|
132
|
+
if (options.key != null) {
|
|
133
|
+
var kRaw = readMaybeFile(options.key);
|
|
134
|
+
keyObj = makePrivateKeyFromDerOrPem(kRaw, options.passphrase);
|
|
135
|
+
privateKey = exportKeyPkcs8Der(keyObj);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// אימות בסיסי (כאשר לא משתמשים ב־PFX)
|
|
139
|
+
if (!options.pfx) {
|
|
140
|
+
if (certBlocksDer.length === 0) throw new Error('createSecureContext: missing cert.');
|
|
141
|
+
if (!privateKey) throw new Error('createSecureContext: missing private key.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- CA (Trust store) ---
|
|
145
|
+
var ca = normalizeCA(options.ca);
|
|
146
|
+
|
|
147
|
+
// --- OCSP stapling (אופציונלי) ---
|
|
148
|
+
var ocsp = null;
|
|
149
|
+
if (options.ocsp != null) ocsp = new Uint8Array(readMaybeFile(options.ocsp));
|
|
150
|
+
|
|
151
|
+
// --- Ticket keys (אופציונלי) ---
|
|
152
|
+
var ticketKeys = null;
|
|
153
|
+
if (options.ticketKeys != null) ticketKeys = new Uint8Array(readMaybeFile(options.ticketKeys));
|
|
154
|
+
// אפשרות: ליצור מפתחי ברירת מחדל כאן אם תרצה
|
|
155
|
+
|
|
156
|
+
// --- בניית שרשרת לשיגור ללקוח (leaf → intermediates) ---
|
|
157
|
+
var chainDer = dedupeDerArray(certBlocksDer);
|
|
158
|
+
var certificateChain = [];
|
|
159
|
+
for (var c=0;c<chainDer.length;c++) {
|
|
160
|
+
certificateChain.push({ cert: chainDer[c] });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// מידע עזר לזיהוי סוג המפתח הציבורי של ה-leaf
|
|
164
|
+
var leafPublicKeyType = null;
|
|
165
|
+
if (certObjs.length > 0 && certObjs[0].publicKey) {
|
|
166
|
+
try { leafPublicKeyType = certObjs[0].publicKey.asymmetricKeyType || null; } catch (e) { leafPublicKeyType = null; }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
// חומר לשכבת ההנדשייק/רקורד:
|
|
171
|
+
certificateChain: certificateChain, // [{ cert: DER(Uint8Array) }, ...]
|
|
172
|
+
privateKey: privateKey, // PKCS#8 DER (Uint8Array)
|
|
173
|
+
ca: ca, // Trust store (DER)
|
|
174
|
+
ocsp: ocsp, // DER (אם הוגדר)
|
|
175
|
+
ticketKeys: ticketKeys, // Uint8Array (אם הוגדר)
|
|
176
|
+
|
|
177
|
+
// עזרי debug/לוגיקה:
|
|
178
|
+
certObjs: certObjs, // [X509Certificate...]
|
|
179
|
+
keyObj: keyObj, // KeyObject
|
|
180
|
+
leafPublicKeyType: leafPublicKeyType,
|
|
181
|
+
|
|
182
|
+
// פרמטרים פרוטוקוליים (אחסון; אתה מפרש בזמן ה-handshake):
|
|
183
|
+
minVersion: String(options.minVersion || 'TLSv1.2'),
|
|
184
|
+
maxVersion: String(options.maxVersion || 'TLSv1.3'),
|
|
185
|
+
ciphers: options.ciphers || null,
|
|
186
|
+
sigalgs: options.sigalgs || null,
|
|
187
|
+
ecdhCurve: options.ecdhCurve || null,
|
|
188
|
+
honorCipherOrder: !!options.honorCipherOrder,
|
|
189
|
+
|
|
190
|
+
// תמיכה ב־PFX אם תרצה לטפל בזה בשכבה אחרת:
|
|
191
|
+
pfx: options.pfx ? new Uint8Array(readMaybeFile(options.pfx)) : null,
|
|
192
|
+
passphrase: options.passphrase ? String(options.passphrase) : null
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = createSecureContext;
|
package/tls_server.js
ADDED
|
File without changes
|