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/wire.js
ADDED
|
@@ -0,0 +1,1672 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
var {
|
|
4
|
+
concatUint8Arrays,
|
|
5
|
+
} = require('./utils');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var TLS_VERSION = {
|
|
9
|
+
TLS1_0: 0x0301,
|
|
10
|
+
TLS1_1: 0x0302,
|
|
11
|
+
TLS1_2: 0x0303,
|
|
12
|
+
TLS1_3: 0x0304
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
var TLS_MESSAGE_TYPE = {
|
|
16
|
+
CLIENT_HELLO: 1,
|
|
17
|
+
SERVER_HELLO: 2,
|
|
18
|
+
NEW_SESSION_TICKET: 4,
|
|
19
|
+
END_OF_EARLY_DATA: 5,
|
|
20
|
+
ENCRYPTED_EXTENSIONS: 8,
|
|
21
|
+
CERTIFICATE: 11,
|
|
22
|
+
SERVER_KEY_EXCHANGE: 12,
|
|
23
|
+
CERTIFICATE_REQUEST: 13,
|
|
24
|
+
SERVER_HELLO_DONE: 14,
|
|
25
|
+
CERTIFICATE_VERIFY: 15,
|
|
26
|
+
CLIENT_KEY_EXCHANGE: 16,
|
|
27
|
+
FINISHED: 20,
|
|
28
|
+
KEY_UPDATE: 24,
|
|
29
|
+
MESSAGE_HASH: 254 // HRR flow marker
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
var TLS_EXT = {
|
|
33
|
+
SERVER_NAME: 0,
|
|
34
|
+
MAX_FRAGMENT_LENGTH: 1,
|
|
35
|
+
STATUS_REQUEST: 5,
|
|
36
|
+
SUPPORTED_GROUPS: 10,
|
|
37
|
+
SIGNATURE_ALGORITHMS: 13,
|
|
38
|
+
USE_SRTP: 14,
|
|
39
|
+
HEARTBEAT: 15,
|
|
40
|
+
ALPN: 16,
|
|
41
|
+
SCT: 18,
|
|
42
|
+
CLIENT_CERT_TYPE: 19,
|
|
43
|
+
SERVER_CERT_TYPE: 20,
|
|
44
|
+
PADDING: 21,
|
|
45
|
+
PRE_SHARED_KEY: 41,
|
|
46
|
+
EARLY_DATA: 42,
|
|
47
|
+
SUPPORTED_VERSIONS: 43,
|
|
48
|
+
COOKIE: 44,
|
|
49
|
+
PSK_KEY_EXCHANGE_MODES: 45,
|
|
50
|
+
CERTIFICATE_AUTHORITIES: 47,
|
|
51
|
+
OID_FILTERS: 48,
|
|
52
|
+
POST_HANDSHAKE_AUTH: 49,
|
|
53
|
+
SIGNATURE_ALGORITHMS_CERT: 50,
|
|
54
|
+
KEY_SHARE: 51,
|
|
55
|
+
RENEGOTIATION_INFO: 0xFF01
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/* =============================== Small utils ============================== */
|
|
59
|
+
|
|
60
|
+
function toU8(x) {
|
|
61
|
+
if (x == null) return new Uint8Array(0);
|
|
62
|
+
if (x instanceof Uint8Array) return x;
|
|
63
|
+
if (typeof x === 'string') return (new TextEncoder()).encode(x);
|
|
64
|
+
return new Uint8Array(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ============================ Binary write helpers ============================ */
|
|
68
|
+
function w_u8(buf, off, v) {
|
|
69
|
+
buf[off++] = v & 0xFF;
|
|
70
|
+
return off;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function w_u16(buf, off, v) {
|
|
74
|
+
buf[off++] = (v >>> 8) & 0xFF;
|
|
75
|
+
buf[off++] = v & 0xFF;
|
|
76
|
+
return off;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function w_u24(buf, off, v) {
|
|
80
|
+
buf[off++] = (v >>> 16) & 0xFF;
|
|
81
|
+
buf[off++] = (v >>> 8) & 0xFF;
|
|
82
|
+
buf[off++] = v & 0xFF;
|
|
83
|
+
return off;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function w_bytes(buf, off, b) {
|
|
87
|
+
buf.set(b, off);
|
|
88
|
+
return off + b.length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ============================ Binary read helpers ============================ */
|
|
92
|
+
function r_u8(buf, off) {
|
|
93
|
+
return [buf[off++] >>> 0, off];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function r_u16(buf, off) {
|
|
97
|
+
var v = ((buf[off] << 8) | buf[off + 1]) >>> 0;
|
|
98
|
+
return [v, off + 2];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function r_u24(buf, off) {
|
|
102
|
+
var v = ((buf[off] << 16) | (buf[off + 1] << 8) | buf[off + 2]) >>> 0;
|
|
103
|
+
return [v, off + 3];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function r_bytes(buf, off, n) {
|
|
107
|
+
var slice;
|
|
108
|
+
if (buf instanceof Uint8Array) {
|
|
109
|
+
// חיתוך אמיתי מתוך Uint8Array
|
|
110
|
+
slice = buf.slice(off, off + n);
|
|
111
|
+
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(buf)) {
|
|
112
|
+
// Node Buffer → slice מחזיר view, אז נעשה copy ל־Uint8Array
|
|
113
|
+
var tmp = buf.slice(off, off + n);
|
|
114
|
+
slice = new Uint8Array(tmp);
|
|
115
|
+
} else if (Array.isArray(buf)) {
|
|
116
|
+
// מערך רגיל
|
|
117
|
+
var tmp = buf.slice(off, off + n);
|
|
118
|
+
slice = new Uint8Array(tmp);
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error("r_bytes: unsupported buffer type " + (typeof buf));
|
|
121
|
+
}
|
|
122
|
+
return [slice, off + n];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
/* ================================= Vectors ================================= */
|
|
127
|
+
function veclen(lenBytes, inner) {
|
|
128
|
+
var out, off = 0;
|
|
129
|
+
|
|
130
|
+
if (lenBytes === 1) {
|
|
131
|
+
out = new Uint8Array(1 + inner.length);
|
|
132
|
+
off = w_u8(out, off, inner.length);
|
|
133
|
+
off = w_bytes(out, off, inner);
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (lenBytes === 2) {
|
|
138
|
+
out = new Uint8Array(2 + inner.length);
|
|
139
|
+
off = w_u16(out, off, inner.length);
|
|
140
|
+
off = w_bytes(out, off, inner);
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (lenBytes === 3) {
|
|
145
|
+
out = new Uint8Array(3 + inner.length);
|
|
146
|
+
off = w_u24(out, off, inner.length);
|
|
147
|
+
off = w_bytes(out, off, inner);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
throw new Error('veclen only supports 1/2/3');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function readVec(buf, off, lenBytes) {
|
|
155
|
+
var n, off2 = off;
|
|
156
|
+
|
|
157
|
+
if (lenBytes === 1) {
|
|
158
|
+
[n, off2] = r_u8(buf, off2);
|
|
159
|
+
} else if (lenBytes === 2) {
|
|
160
|
+
[n, off2] = r_u16(buf, off2);
|
|
161
|
+
} else {
|
|
162
|
+
[n, off2] = r_u24(buf, off2);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
var b;
|
|
166
|
+
[b, off2] = r_bytes(buf, off2, n);
|
|
167
|
+
return [b, off2];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* =========================== Extensions registry =========================== */
|
|
171
|
+
var exts = {};
|
|
172
|
+
|
|
173
|
+
// Predeclare wanted entries
|
|
174
|
+
exts.SERVER_NAME = { encode: null, decode: null };
|
|
175
|
+
exts.SUPPORTED_VERSIONS = { encode: null, decode: null };
|
|
176
|
+
exts.SUPPORTED_GROUPS = { encode: null, decode: null };
|
|
177
|
+
exts.SIGNATURE_ALGORITHMS = { encode: null, decode: null };
|
|
178
|
+
exts.PSK_KEY_EXCHANGE_MODES = { encode: null, decode: null };
|
|
179
|
+
exts.KEY_SHARE = { encode: null, decode: null };
|
|
180
|
+
exts.ALPN = { encode: null, decode: null };
|
|
181
|
+
exts.RENEGOTIATION_INFO = { encode: null, decode: null };
|
|
182
|
+
|
|
183
|
+
/* ------------------------------ SERVER_NAME (0) ------------------------------ */
|
|
184
|
+
exts.SERVER_NAME.encode = function (value) {
|
|
185
|
+
var host = toU8(value || "");
|
|
186
|
+
|
|
187
|
+
// one name: type(1)=0, len(2), bytes
|
|
188
|
+
var inner = new Uint8Array(1 + 2 + host.length);
|
|
189
|
+
var off = 0;
|
|
190
|
+
|
|
191
|
+
off = w_u8(inner, off, 0);
|
|
192
|
+
off = w_u16(inner, off, host.length);
|
|
193
|
+
off = w_bytes(inner, off, host);
|
|
194
|
+
|
|
195
|
+
// ServerNameList is vector<2>
|
|
196
|
+
return veclen(2, inner);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
exts.SERVER_NAME.decode = function (data) {
|
|
200
|
+
var off = 0;
|
|
201
|
+
var list;
|
|
202
|
+
[list, off] = readVec(data, off, 2);
|
|
203
|
+
|
|
204
|
+
var off2 = 0;
|
|
205
|
+
var host = "";
|
|
206
|
+
|
|
207
|
+
while (off2 < list.length) {
|
|
208
|
+
var typ;
|
|
209
|
+
[typ, off2] = r_u8(list, off2);
|
|
210
|
+
|
|
211
|
+
var l;
|
|
212
|
+
[l, off2] = r_u16(list, off2);
|
|
213
|
+
|
|
214
|
+
var v;
|
|
215
|
+
[v, off2] = r_bytes(list, off2, l);
|
|
216
|
+
|
|
217
|
+
if (typ === 0) {
|
|
218
|
+
host = (new TextDecoder()).decode(v);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Return just the value (string), not {host: ...}
|
|
223
|
+
return host;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/* --------------------------- SUPPORTED_VERSIONS (43) --------------------------- */
|
|
227
|
+
exts.SUPPORTED_VERSIONS.encode = function (value) {
|
|
228
|
+
// ServerHello form: selected (number)
|
|
229
|
+
if (typeof value === 'number') {
|
|
230
|
+
var out = new Uint8Array(2);
|
|
231
|
+
var off = 0;
|
|
232
|
+
off = w_u16(out, off, value);
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ClientHello form: array of versions
|
|
237
|
+
var arr = Array.isArray(value) ? value : [TLS_VERSION.TLS1_3, TLS_VERSION.TLS1_2];
|
|
238
|
+
|
|
239
|
+
var body = new Uint8Array(1 + arr.length * 2);
|
|
240
|
+
var off2 = 0;
|
|
241
|
+
|
|
242
|
+
off2 = w_u8(body, off2, arr.length * 2);
|
|
243
|
+
for (var i = 0; i < arr.length; i++) {
|
|
244
|
+
off2 = w_u16(body, off2, arr[i]);
|
|
245
|
+
}
|
|
246
|
+
return body;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
exts.SUPPORTED_VERSIONS.decode = function (data) {
|
|
250
|
+
// ServerHello form: 2 bytes
|
|
251
|
+
if (data.length === 2) {
|
|
252
|
+
var v, off = 0;
|
|
253
|
+
[v, off] = r_u16(data, off);
|
|
254
|
+
return v; // return the selected version (number)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ClientHello form: vector<1> of versions (u16 each)
|
|
258
|
+
var off2 = 0;
|
|
259
|
+
var n;
|
|
260
|
+
[n, off2] = r_u8(data, off2);
|
|
261
|
+
|
|
262
|
+
var out = [];
|
|
263
|
+
for (var i = 0; i < n; i += 2) {
|
|
264
|
+
var vv;
|
|
265
|
+
[vv, off2] = r_u16(data, off2);
|
|
266
|
+
out.push(vv);
|
|
267
|
+
}
|
|
268
|
+
return out; // return the array directly
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/* ---------------------------- SUPPORTED_GROUPS (10) ---------------------------- */
|
|
272
|
+
exts.SUPPORTED_GROUPS.encode = function (value) {
|
|
273
|
+
var groups = Array.isArray(value) ? value : [23, 29]; // secp256r1, x25519
|
|
274
|
+
|
|
275
|
+
var body = new Uint8Array(2 + groups.length * 2);
|
|
276
|
+
var off = 0;
|
|
277
|
+
|
|
278
|
+
off = w_u16(body, off, groups.length * 2);
|
|
279
|
+
for (var i = 0; i < groups.length; i++) {
|
|
280
|
+
off = w_u16(body, off, groups[i]);
|
|
281
|
+
}
|
|
282
|
+
return body;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
exts.SUPPORTED_GROUPS.decode = function (data) {
|
|
286
|
+
var off = 0;
|
|
287
|
+
var n;
|
|
288
|
+
[n, off] = r_u16(data, off);
|
|
289
|
+
|
|
290
|
+
var out = [];
|
|
291
|
+
for (var i = 0; i < n; i += 2) {
|
|
292
|
+
var g;
|
|
293
|
+
[g, off] = r_u16(data, off);
|
|
294
|
+
out.push(g);
|
|
295
|
+
}
|
|
296
|
+
return out; // array of named groups
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/* -------------------------- SIGNATURE_ALGORITHMS (13) -------------------------- */
|
|
300
|
+
exts.SIGNATURE_ALGORITHMS.encode = function (value) {
|
|
301
|
+
var algs = Array.isArray(value) ? value : [0x0403, 0x0804, 0x0401];
|
|
302
|
+
|
|
303
|
+
var body = new Uint8Array(2 + algs.length * 2);
|
|
304
|
+
var off = 0;
|
|
305
|
+
|
|
306
|
+
off = w_u16(body, off, algs.length * 2);
|
|
307
|
+
for (var i = 0; i < algs.length; i++) {
|
|
308
|
+
off = w_u16(body, off, algs[i]);
|
|
309
|
+
}
|
|
310
|
+
return body;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
exts.SIGNATURE_ALGORITHMS.decode = function (data) {
|
|
314
|
+
var off = 0;
|
|
315
|
+
var n;
|
|
316
|
+
[n, off] = r_u16(data, off);
|
|
317
|
+
|
|
318
|
+
var out = [];
|
|
319
|
+
for (var i = 0; i < n; i += 2) {
|
|
320
|
+
var a;
|
|
321
|
+
[a, off] = r_u16(data, off);
|
|
322
|
+
out.push(a);
|
|
323
|
+
}
|
|
324
|
+
return out; // array of sigalgs (u16)
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/* ------------------------ PSK_KEY_EXCHANGE_MODES (45) ------------------------ */
|
|
328
|
+
exts.PSK_KEY_EXCHANGE_MODES.encode = function (value) {
|
|
329
|
+
var modes = Array.isArray(value) ? value : [1]; // 0=psk_ke, 1=psk_dhe_ke
|
|
330
|
+
|
|
331
|
+
var body = new Uint8Array(1 + modes.length);
|
|
332
|
+
var off = 0;
|
|
333
|
+
|
|
334
|
+
off = w_u8(body, off, modes.length);
|
|
335
|
+
for (var i = 0; i < modes.length; i++) {
|
|
336
|
+
off = w_u8(body, off, modes[i]);
|
|
337
|
+
}
|
|
338
|
+
return body;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
exts.PSK_KEY_EXCHANGE_MODES.decode = function (data) {
|
|
342
|
+
var off = 0;
|
|
343
|
+
var n;
|
|
344
|
+
[n, off] = r_u8(data, off);
|
|
345
|
+
|
|
346
|
+
var out = [];
|
|
347
|
+
for (var i = 0; i < n; i++) {
|
|
348
|
+
var m;
|
|
349
|
+
[m, off] = r_u8(data, off);
|
|
350
|
+
out.push(m);
|
|
351
|
+
}
|
|
352
|
+
return out; // array of modes (u8)
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/* --------------------------------- KEY_SHARE (51) -------------------------------- */
|
|
356
|
+
exts.KEY_SHARE.encode = function (value) {
|
|
357
|
+
// ServerHello form: { group:number, key_exchange:Uint8Array }
|
|
358
|
+
if (value && typeof value.group === 'number' && value.key_exchange) {
|
|
359
|
+
var ke = toU8(value.key_exchange);
|
|
360
|
+
|
|
361
|
+
var out = new Uint8Array(2 + 2 + ke.length);
|
|
362
|
+
var off = 0;
|
|
363
|
+
|
|
364
|
+
off = w_u16(out, off, value.group);
|
|
365
|
+
off = w_u16(out, off, ke.length);
|
|
366
|
+
off = w_bytes(out, off, ke);
|
|
367
|
+
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ClientHello form: [{ group:number, key_exchange:Uint8Array }, ...]
|
|
372
|
+
var list = Array.isArray(value) ? value : [];
|
|
373
|
+
|
|
374
|
+
var parts = [];
|
|
375
|
+
for (var i = 0; i < list.length; i++) {
|
|
376
|
+
var e = list[i];
|
|
377
|
+
var ke2 = toU8(e.key_exchange || new Uint8Array(0));
|
|
378
|
+
|
|
379
|
+
var ent = new Uint8Array(2 + 2 + ke2.length);
|
|
380
|
+
var o2 = 0;
|
|
381
|
+
|
|
382
|
+
o2 = w_u16(ent, o2, e.group >>> 0);
|
|
383
|
+
o2 = w_u16(ent, o2, ke2.length);
|
|
384
|
+
o2 = w_bytes(ent, o2, ke2);
|
|
385
|
+
|
|
386
|
+
parts.push(ent);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return veclen(2, concatUint8Arrays(parts));
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
exts.KEY_SHARE.decode = function (data) {
|
|
393
|
+
// Try ServerHello form: group(2) + len(2) + key
|
|
394
|
+
if (data.length >= 4) {
|
|
395
|
+
var g, off = 0;
|
|
396
|
+
[g, off] = r_u16(data, off);
|
|
397
|
+
|
|
398
|
+
var l;
|
|
399
|
+
[l, off] = r_u16(data, off);
|
|
400
|
+
|
|
401
|
+
if (4 + l === data.length) {
|
|
402
|
+
var ke;
|
|
403
|
+
[ke, off] = r_bytes(data, off, l);
|
|
404
|
+
// Two fields required → return object
|
|
405
|
+
return { group: g, key_exchange: ke };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ClientHello form: vector<2> of KeyShareEntry
|
|
410
|
+
var off2 = 0;
|
|
411
|
+
var listBytes;
|
|
412
|
+
[listBytes, off2] = r_u16(data, off2);
|
|
413
|
+
|
|
414
|
+
var end = off2 + listBytes;
|
|
415
|
+
var out = [];
|
|
416
|
+
|
|
417
|
+
while (off2 < end) {
|
|
418
|
+
var g2;
|
|
419
|
+
[g2, off2] = r_u16(data, off2);
|
|
420
|
+
|
|
421
|
+
var l2;
|
|
422
|
+
[l2, off2] = r_u16(data, off2);
|
|
423
|
+
|
|
424
|
+
var ke2;
|
|
425
|
+
[ke2, off2] = r_bytes(data, off2, l2);
|
|
426
|
+
|
|
427
|
+
out.push({ group: g2, key_exchange: ke2 });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return out; // array of entries
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/* ------------------------------------ ALPN (16) ----------------------------------- */
|
|
434
|
+
exts.ALPN.encode = function (value) {
|
|
435
|
+
var list = Array.isArray(value) ? value : [];
|
|
436
|
+
|
|
437
|
+
var total = 2; // vec16 length
|
|
438
|
+
var items = [];
|
|
439
|
+
|
|
440
|
+
for (var i = 0; i < list.length; i++) {
|
|
441
|
+
var p = toU8(list[i]);
|
|
442
|
+
items.push(p);
|
|
443
|
+
total += 1 + p.length;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
var out = new Uint8Array(total);
|
|
447
|
+
var off = 0;
|
|
448
|
+
|
|
449
|
+
off = w_u16(out, off, total - 2);
|
|
450
|
+
for (var j = 0; j < items.length; j++) {
|
|
451
|
+
off = w_u8(out, off, items[j].length);
|
|
452
|
+
off = w_bytes(out, off, items[j]);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return out;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
exts.ALPN.decode = function (data) {
|
|
459
|
+
var off = 0;
|
|
460
|
+
var n;
|
|
461
|
+
[n, off] = r_u16(data, off);
|
|
462
|
+
|
|
463
|
+
var end = off + n;
|
|
464
|
+
var out = [];
|
|
465
|
+
|
|
466
|
+
while (off < end) {
|
|
467
|
+
var l;
|
|
468
|
+
[l, off] = r_u8(data, off);
|
|
469
|
+
|
|
470
|
+
var v;
|
|
471
|
+
[v, off] = r_bytes(data, off, l);
|
|
472
|
+
|
|
473
|
+
out.push((new TextDecoder()).decode(v));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return out; // array of protocol strings
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
/* ----------------------------- RENEGOTIATION_INFO (FF01) ----------------------------- */
|
|
480
|
+
exts.RENEGOTIATION_INFO.encode = function (value) {
|
|
481
|
+
// value is Uint8Array of renegotiated_connection data
|
|
482
|
+
var rb = toU8(value || new Uint8Array(0));
|
|
483
|
+
return veclen(1, rb);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
exts.RENEGOTIATION_INFO.decode = function (data) {
|
|
487
|
+
var off = 0;
|
|
488
|
+
var v;
|
|
489
|
+
[v, off] = readVec(data, off, 1);
|
|
490
|
+
return v; // return raw bytes (Uint8Array)
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/* ============================= Extensions helpers ============================= */
|
|
494
|
+
function ext_name_by_code(code) {
|
|
495
|
+
// best-effort pretty name
|
|
496
|
+
for (var k in TLS_EXT) {
|
|
497
|
+
if ((TLS_EXT[k] >>> 0) === (code >>> 0)) return k;
|
|
498
|
+
}
|
|
499
|
+
return 'EXT_' + code;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function build_extensions(list) {
|
|
503
|
+
// list items may be {type:number|string, value:any, data?:Uint8Array}
|
|
504
|
+
if (!list || !list.length) {
|
|
505
|
+
var e = new Uint8Array(2);
|
|
506
|
+
w_u16(e, 0, 0);
|
|
507
|
+
return e;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
var parts = [];
|
|
511
|
+
var total = 2; // vec16
|
|
512
|
+
|
|
513
|
+
for (var i = 0; i < list.length; i++) {
|
|
514
|
+
var t = list[i].type;
|
|
515
|
+
|
|
516
|
+
// allow symbolic name e.g. 'SERVER_NAME'
|
|
517
|
+
if (typeof t === 'string') {
|
|
518
|
+
t = TLS_EXT[t];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
var payload;
|
|
522
|
+
if (list[i].data) {
|
|
523
|
+
payload = list[i].data;
|
|
524
|
+
} else {
|
|
525
|
+
// try registry
|
|
526
|
+
var regKey = ext_name_by_code(t);
|
|
527
|
+
var enc = exts[regKey] && exts[regKey].encode;
|
|
528
|
+
payload = enc ? enc(list[i].value) : new Uint8Array(0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
var rec = new Uint8Array(4 + payload.length);
|
|
532
|
+
var off = 0;
|
|
533
|
+
|
|
534
|
+
off = w_u16(rec, off, t >>> 0);
|
|
535
|
+
off = w_u16(rec, off, payload.length);
|
|
536
|
+
off = w_bytes(rec, off, payload);
|
|
537
|
+
|
|
538
|
+
parts.push(rec);
|
|
539
|
+
total += rec.length;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
var out = new Uint8Array(total);
|
|
543
|
+
var off2 = 0;
|
|
544
|
+
|
|
545
|
+
off2 = w_u16(out, off2, total - 2);
|
|
546
|
+
|
|
547
|
+
for (var j = 0; j < parts.length; j++) {
|
|
548
|
+
off2 = w_bytes(out, off2, parts[j]);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function parse_extensions(buf) {
|
|
555
|
+
var off = 0;
|
|
556
|
+
var n;
|
|
557
|
+
[n, off] = r_u16(buf, off);
|
|
558
|
+
|
|
559
|
+
var end = off + n;
|
|
560
|
+
var out = [];
|
|
561
|
+
|
|
562
|
+
while (off < end) {
|
|
563
|
+
var t;
|
|
564
|
+
[t, off] = r_u16(buf, off);
|
|
565
|
+
|
|
566
|
+
var l;
|
|
567
|
+
[l, off] = r_u16(buf, off);
|
|
568
|
+
|
|
569
|
+
var d;
|
|
570
|
+
[d, off] = r_bytes(buf, off, l);
|
|
571
|
+
|
|
572
|
+
var name = ext_name_by_code(t);
|
|
573
|
+
var dec = exts[name] && exts[name].decode;
|
|
574
|
+
var val = dec ? dec(d) : null;
|
|
575
|
+
|
|
576
|
+
out.push({ type: t, name: name, data: d, value: val });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
/* ================================ Hello I/O ================================ */
|
|
584
|
+
function build_hello(kind, params) {
|
|
585
|
+
params = params || {};
|
|
586
|
+
|
|
587
|
+
var legacy_version = TLS_VERSION.TLS1_2; // even for TLS1.3 legacy fields
|
|
588
|
+
|
|
589
|
+
var sid = toU8(params.session_id || "");
|
|
590
|
+
if (sid.length > 32) sid = sid.subarray(0, 32);
|
|
591
|
+
|
|
592
|
+
var extsBuf = build_extensions(params.extensions || []);
|
|
593
|
+
|
|
594
|
+
if (kind === 'client') {
|
|
595
|
+
var cs = params.cipher_suites || [0x1301, 0x1302, 0x1303, 0xC02F, 0xC02B];
|
|
596
|
+
|
|
597
|
+
var csBlock = new Uint8Array(2 + cs.length * 2);
|
|
598
|
+
var o = 0;
|
|
599
|
+
o = w_u16(csBlock, o, cs.length * 2);
|
|
600
|
+
for (var i = 0; i < cs.length; i++) {
|
|
601
|
+
o = w_u16(csBlock, o, cs[i]);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
var comp = params.legacy_compression || [0]; // for TLS1.3 must be [0]
|
|
605
|
+
var compBlock = new Uint8Array(1 + comp.length);
|
|
606
|
+
var oc = 0;
|
|
607
|
+
oc = w_u8(compBlock, oc, comp.length);
|
|
608
|
+
for (var j = 0; j < comp.length; j++) {
|
|
609
|
+
oc = w_u8(compBlock, oc, comp[j]);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
var out = new Uint8Array(
|
|
613
|
+
2 + 32 + 1 + sid.length + csBlock.length + compBlock.length + extsBuf.length
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
var off = 0;
|
|
617
|
+
off = w_u16(out, off, legacy_version);
|
|
618
|
+
off = w_bytes(out, off, params.random);
|
|
619
|
+
off = w_u8(out, off, sid.length);
|
|
620
|
+
off = w_bytes(out, off, sid);
|
|
621
|
+
off = w_bytes(out, off, csBlock);
|
|
622
|
+
off = w_bytes(out, off, compBlock);
|
|
623
|
+
off = w_bytes(out, off, extsBuf);
|
|
624
|
+
|
|
625
|
+
return out;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (kind === 'server') {
|
|
629
|
+
var cipher_suite = (typeof params.cipher_suite === 'number') ? params.cipher_suite : 0x1301;
|
|
630
|
+
|
|
631
|
+
var out2 = new Uint8Array(2 + 32 + 1 + sid.length + 2 + 1 + extsBuf.length);
|
|
632
|
+
var off2 = 0;
|
|
633
|
+
|
|
634
|
+
off2 = w_u16(out2, off2, legacy_version);
|
|
635
|
+
off2 = w_bytes(out2, off2, params.random);
|
|
636
|
+
off2 = w_u8(out2, off2, sid.length);
|
|
637
|
+
off2 = w_bytes(out2, off2, sid);
|
|
638
|
+
off2 = w_u16(out2, off2, cipher_suite);
|
|
639
|
+
off2 = w_u8(out2, off2, 0); // compression method = 0
|
|
640
|
+
off2 = w_bytes(out2, off2, extsBuf);
|
|
641
|
+
|
|
642
|
+
return out2;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
throw new Error('build_hello: kind must be "client" or "server"');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parse_hello(hsType, body) {
|
|
649
|
+
var isClient = (hsType === TLS_MESSAGE_TYPE.CLIENT_HELLO || hsType === 'client_hello');
|
|
650
|
+
|
|
651
|
+
var off = 0;
|
|
652
|
+
|
|
653
|
+
var legacy_version;
|
|
654
|
+
[legacy_version, off] = r_u16(body, off);
|
|
655
|
+
|
|
656
|
+
var random;
|
|
657
|
+
[random, off] = r_bytes(body, off, 32);
|
|
658
|
+
|
|
659
|
+
var sidLen;
|
|
660
|
+
[sidLen, off] = r_u8(body, off);
|
|
661
|
+
|
|
662
|
+
var session_id;
|
|
663
|
+
[session_id, off] = r_bytes(body, off, sidLen);
|
|
664
|
+
|
|
665
|
+
if (isClient) {
|
|
666
|
+
var csLen;
|
|
667
|
+
[csLen, off] = r_u16(body, off);
|
|
668
|
+
|
|
669
|
+
var csEnd = off + csLen;
|
|
670
|
+
var cipher_suites = [];
|
|
671
|
+
|
|
672
|
+
while (off < csEnd) {
|
|
673
|
+
var cs;
|
|
674
|
+
[cs, off] = r_u16(body, off);
|
|
675
|
+
cipher_suites.push(cs);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
var compLen;
|
|
679
|
+
[compLen, off] = r_u8(body, off);
|
|
680
|
+
|
|
681
|
+
var legacy_compression = [];
|
|
682
|
+
for (var i = 0; i < compLen; i++) {
|
|
683
|
+
var c;
|
|
684
|
+
[c, off] = r_u8(body, off);
|
|
685
|
+
legacy_compression.push(c);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
var extRaw = (body.length > off) ? body.subarray(off) : new Uint8Array(0);
|
|
689
|
+
var extensions = extRaw.length ? parse_extensions(extRaw) : [];
|
|
690
|
+
|
|
691
|
+
// version hint: if supported_versions includes TLS1.3, prefer it
|
|
692
|
+
var ver = legacy_version;
|
|
693
|
+
for (var k = 0; k < extensions.length; k++) {
|
|
694
|
+
var e = extensions[k];
|
|
695
|
+
if (e.type === TLS_EXT.SUPPORTED_VERSIONS && Array.isArray(e.value)) {
|
|
696
|
+
for (var t = 0; t < e.value.length; t++) {
|
|
697
|
+
if (e.value[t] === TLS_VERSION.TLS1_3) {
|
|
698
|
+
ver = TLS_VERSION.TLS1_3;
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
message: 'client_hello',
|
|
707
|
+
legacy_version: legacy_version,
|
|
708
|
+
version_hint: ver,
|
|
709
|
+
random: random,
|
|
710
|
+
session_id: session_id,
|
|
711
|
+
cipher_suites: cipher_suites,
|
|
712
|
+
legacy_compression: legacy_compression,
|
|
713
|
+
extensions: extensions
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ServerHello
|
|
718
|
+
var cipher_suite;
|
|
719
|
+
[cipher_suite, off] = r_u16(body, off);
|
|
720
|
+
|
|
721
|
+
var comp;
|
|
722
|
+
[comp, off] = r_u8(body, off);
|
|
723
|
+
|
|
724
|
+
var extRaw2 = (body.length > off) ? body.subarray(off) : new Uint8Array(0);
|
|
725
|
+
var extensions2 = extRaw2.length ? parse_extensions(extRaw2) : [];
|
|
726
|
+
|
|
727
|
+
var ver2 = legacy_version;
|
|
728
|
+
for (var z = 0; z < extensions2.length; z++) {
|
|
729
|
+
var ex = extensions2[z];
|
|
730
|
+
if (ex.type === TLS_EXT.SUPPORTED_VERSIONS && typeof ex.value === 'number') {
|
|
731
|
+
ver2 = ex.value; // selected version
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return {
|
|
736
|
+
message: 'server_hello',
|
|
737
|
+
legacy_version: legacy_version,
|
|
738
|
+
version: ver2,
|
|
739
|
+
random: random,
|
|
740
|
+
session_id: session_id,
|
|
741
|
+
cipher_suite: cipher_suite,
|
|
742
|
+
legacy_compression: comp,
|
|
743
|
+
extensions: extensions2
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function isVec2(u8){
|
|
748
|
+
if (!(u8 instanceof Uint8Array) || u8.length < 2) return false;
|
|
749
|
+
var len = (u8[0] << 8) | u8[1];
|
|
750
|
+
return u8.length === 2 + len;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* ===================== Certificate / CertificateVerify / Finished ===================== */
|
|
754
|
+
|
|
755
|
+
function build_certificate(params) {
|
|
756
|
+
// Unified API:
|
|
757
|
+
// { version: TLS_VERSION.TLS1_2 | TLS_VERSION.TLS1_3,
|
|
758
|
+
// entries?: [ { cert: Uint8Array|string, extensions?: Uint8Array|ext-list } ],
|
|
759
|
+
// request_context?: Uint8Array|string,
|
|
760
|
+
// // backward-compat:
|
|
761
|
+
// certs?: [Uint8Array|string] }
|
|
762
|
+
//
|
|
763
|
+
// TLS 1.3 → מחזיר: request_context (vec<1>) || certificate_list (vec<3> של [ cert(vec<3>) || extensions(vec<2>) ]*)
|
|
764
|
+
// TLS 1.2 → מחזיר: certificate_list (vec<3> של cert(vec<3>)*), מתעלם מ-extensions/request_context
|
|
765
|
+
|
|
766
|
+
var v = params.version || TLS_VERSION.TLS1_2;
|
|
767
|
+
|
|
768
|
+
// Normalize to entries[] always
|
|
769
|
+
var entries = Array.isArray(params.entries) ? params.entries.slice() : null;
|
|
770
|
+
if (!entries && Array.isArray(params.certs)) {
|
|
771
|
+
// backward-compat: { certs:[...] } → entries:[{cert},...]
|
|
772
|
+
entries = params.certs.map(function (c) { return { cert: c }; });
|
|
773
|
+
}
|
|
774
|
+
if (!entries) entries = [];
|
|
775
|
+
|
|
776
|
+
if (v === TLS_VERSION.TLS1_3) {
|
|
777
|
+
var ctx = toU8(params.request_context || new Uint8Array(0));
|
|
778
|
+
|
|
779
|
+
var entryParts = [];
|
|
780
|
+
for (var i = 0; i < entries.length; i++) {
|
|
781
|
+
var certBytes = toU8(entries[i].cert || new Uint8Array(0));
|
|
782
|
+
var certVec = veclen(3, certBytes); // cert_data vec<3>
|
|
783
|
+
|
|
784
|
+
var extRaw = entries[i].extensions;
|
|
785
|
+
if (Array.isArray(extRaw)) {
|
|
786
|
+
// list of ext objects → encode to vec<2>
|
|
787
|
+
extRaw = build_extensions(extRaw);
|
|
788
|
+
} else if (extRaw instanceof Uint8Array) {
|
|
789
|
+
// already raw bytes; ensure it's vec<2>
|
|
790
|
+
extRaw = isVec2 && isVec2(extRaw) ? extRaw : veclen(2, extRaw);
|
|
791
|
+
} else {
|
|
792
|
+
// no extensions
|
|
793
|
+
extRaw = veclen(2, new Uint8Array(0));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
entryParts.push(certVec, extRaw);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
var ctxVec = veclen(1, ctx);
|
|
800
|
+
var listVec = veclen(3, concatUint8Arrays(entryParts));
|
|
801
|
+
return concatUint8Arrays([ctxVec, listVec]);
|
|
802
|
+
|
|
803
|
+
} else {
|
|
804
|
+
// TLS 1.2: only certificate_list (vec<3> of cert(vec<3>)*), ignore per-entry extensions & request_context
|
|
805
|
+
var certListParts = [];
|
|
806
|
+
for (var j = 0; j < entries.length; j++) {
|
|
807
|
+
var c = toU8(entries[j].cert || new Uint8Array(0));
|
|
808
|
+
certListParts.push(veclen(3, c));
|
|
809
|
+
}
|
|
810
|
+
return veclen(3, concatUint8Arrays(certListParts));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function parse_certificate(body) {
|
|
815
|
+
// תמיד מחזיר:
|
|
816
|
+
// {
|
|
817
|
+
// version: TLS_VERSION.TLS1_2 | TLS_VERSION.TLS1_3,
|
|
818
|
+
// request_context?: Uint8Array, // רק ב-1.3
|
|
819
|
+
// entries: [ { cert: Uint8Array, extensions?: any[] } ] // ב-1.2 אין extensions
|
|
820
|
+
// }
|
|
821
|
+
|
|
822
|
+
// נסה לזהות TLS 1.3: rc(vec<1>) ואז list(vec<3> של [cert(vec<3>) || exts(vec<2>)]*)
|
|
823
|
+
if (body && body.length >= 4) {
|
|
824
|
+
let off = 0;
|
|
825
|
+
let rcLen; [rcLen, off] = r_u8(body, off); // 1 byte
|
|
826
|
+
const afterCtx = off + rcLen; // off מצביע אחרי rcLen (כלומר 1)
|
|
827
|
+
if (afterCtx + 3 <= body.length) {
|
|
828
|
+
let listLen, off2 = afterCtx; // התחלת certificate_list
|
|
829
|
+
[listLen, off2] = r_u24(body, off2); // 3 bytes
|
|
830
|
+
if (afterCtx + 3 + listLen === body.length) {
|
|
831
|
+
// נראה כמו 1.3 תקני
|
|
832
|
+
const request_context = body.subarray(off, off + rcLen);
|
|
833
|
+
off = off2;
|
|
834
|
+
const end = off2 + listLen;
|
|
835
|
+
|
|
836
|
+
const entries = [];
|
|
837
|
+
while (off < end) {
|
|
838
|
+
let certLen; [certLen, off] = r_u24(body, off); // cert_data vec<3>
|
|
839
|
+
let cert; [cert, off] = r_bytes(body, off, certLen);
|
|
840
|
+
|
|
841
|
+
let extLen; [extLen, off] = r_u16(body, off); // extensions vec<2>
|
|
842
|
+
let extRaw; [extRaw, off] = r_bytes(body, off, extLen);
|
|
843
|
+
|
|
844
|
+
const extensions = extLen ? parse_extensions(extRaw) : [];
|
|
845
|
+
entries.push({ cert, extensions });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
version: TLS_VERSION.TLS1_3,
|
|
850
|
+
request_context,
|
|
851
|
+
entries
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// אחרת: TLS 1.2 — certificate_list(vec<3>) של cert(vec<3>)* ללא הרחבות
|
|
858
|
+
let off3 = 0;
|
|
859
|
+
let listLen2; [listLen2, off3] = r_u24(body, off3);
|
|
860
|
+
|
|
861
|
+
const end2 = off3 + listLen2;
|
|
862
|
+
if (end2 !== body.length) {
|
|
863
|
+
// התאוששות קלה: אם האורך לא תואם, ננסה לפחות לא לקרוס (אפשר גם לזרוק שגיאה)
|
|
864
|
+
// throw new Error('Bad TLS1.2 Certificate length');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const entries12 = [];
|
|
868
|
+
while (off3 < Math.min(end2, body.length)) {
|
|
869
|
+
let len3; [len3, off3] = r_u24(body, off3);
|
|
870
|
+
let cert; [cert, off3] = r_bytes(body, off3, len3);
|
|
871
|
+
entries12.push({ cert }); // אין extensions ב-1.2
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
version: TLS_VERSION.TLS1_2,
|
|
876
|
+
entries: entries12
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
// CertificateVerify (TLS 1.2/1.3 share the same wire framing at this level)
|
|
882
|
+
// struct {
|
|
883
|
+
// SignatureScheme algorithm; // u16
|
|
884
|
+
// opaque signature<0..2^16-1>;
|
|
885
|
+
// } CertificateVerify;
|
|
886
|
+
function build_certificate_verify(scheme, signature) {
|
|
887
|
+
var sig = toU8(signature || new Uint8Array(0));
|
|
888
|
+
var alg = scheme >>> 0;
|
|
889
|
+
|
|
890
|
+
var out = new Uint8Array(2 + 2 + sig.length);
|
|
891
|
+
var off = 0;
|
|
892
|
+
|
|
893
|
+
off = w_u16(out, off, alg);
|
|
894
|
+
off = w_u16(out, off, sig.length);
|
|
895
|
+
off = w_bytes(out, off, sig);
|
|
896
|
+
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function parse_certificate_verify(body) {
|
|
901
|
+
var off = 0;
|
|
902
|
+
|
|
903
|
+
var alg;
|
|
904
|
+
[alg, off] = r_u16(body, off);
|
|
905
|
+
|
|
906
|
+
var slen;
|
|
907
|
+
[slen, off] = r_u16(body, off);
|
|
908
|
+
|
|
909
|
+
var sig;
|
|
910
|
+
[sig, off] = r_bytes(body, off, slen);
|
|
911
|
+
|
|
912
|
+
return { scheme: alg, signature: sig };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/* ============================ TLS 1.3 Post-Handshake ============================ */
|
|
916
|
+
// NewSessionTicket (TLS1.3)
|
|
917
|
+
// struct {
|
|
918
|
+
// uint32 ticket_lifetime;
|
|
919
|
+
// uint32 ticket_age_add;
|
|
920
|
+
// opaque ticket_nonce<0..2^8-1>;
|
|
921
|
+
// opaque ticket<1..2^16-1>;
|
|
922
|
+
// Extension extensions<0..2^16-1>;
|
|
923
|
+
// } NewSessionTicket;
|
|
924
|
+
function build_new_session_ticket(p) {
|
|
925
|
+
var lifetime = (p && p.ticket_lifetime) >>> 0;
|
|
926
|
+
var age_add = (p && p.ticket_age_add) >>> 0;
|
|
927
|
+
var nonce = toU8(p && p.ticket_nonce || new Uint8Array(0));
|
|
928
|
+
var ticket = toU8(p && p.ticket || new Uint8Array(0));
|
|
929
|
+
var extsBuf = Array.isArray(p && p.extensions) ? build_extensions(p.extensions) : (p && p.extensions) || veclen(2, new Uint8Array(0));
|
|
930
|
+
|
|
931
|
+
var out = new Uint8Array(4 + 4 + 1 + nonce.length + 2 + ticket.length + extsBuf.length);
|
|
932
|
+
var off = 0;
|
|
933
|
+
|
|
934
|
+
// u32 big-endian
|
|
935
|
+
off = w_u8(out, off, (lifetime>>>24)&0xFF);
|
|
936
|
+
off = w_u8(out, off, (lifetime>>>16)&0xFF);
|
|
937
|
+
off = w_u8(out, off, (lifetime>>>8)&0xFF);
|
|
938
|
+
off = w_u8(out, off, (lifetime)&0xFF);
|
|
939
|
+
|
|
940
|
+
off = w_u8(out, off, (age_add>>>24)&0xFF);
|
|
941
|
+
off = w_u8(out, off, (age_add>>>16)&0xFF);
|
|
942
|
+
off = w_u8(out, off, (age_add>>>8)&0xFF);
|
|
943
|
+
off = w_u8(out, off, (age_add)&0xFF);
|
|
944
|
+
|
|
945
|
+
off = w_u8(out, off, nonce.length);
|
|
946
|
+
off = w_bytes(out, off, nonce);
|
|
947
|
+
|
|
948
|
+
off = w_u16(out, off, ticket.length);
|
|
949
|
+
off = w_bytes(out, off, ticket);
|
|
950
|
+
|
|
951
|
+
off = w_bytes(out, off, extsBuf);
|
|
952
|
+
|
|
953
|
+
return out;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function parse_new_session_ticket(body) {
|
|
957
|
+
var off = 0;
|
|
958
|
+
|
|
959
|
+
var lifetime = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3]) >>> 0; off+=4;
|
|
960
|
+
var age_add = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3]) >>> 0; off+=4;
|
|
961
|
+
|
|
962
|
+
var nlen;
|
|
963
|
+
[nlen, off] = r_u8(body, off);
|
|
964
|
+
var nonce;
|
|
965
|
+
[nonce, off] = r_bytes(body, off, nlen);
|
|
966
|
+
|
|
967
|
+
var tlen;
|
|
968
|
+
[tlen, off] = r_u16(body, off);
|
|
969
|
+
var ticket;
|
|
970
|
+
[ticket, off] = r_bytes(body, off, tlen);
|
|
971
|
+
|
|
972
|
+
var extBuf = body.subarray(off);
|
|
973
|
+
var extensions = extBuf.length ? parse_extensions(extBuf) : [];
|
|
974
|
+
|
|
975
|
+
return {
|
|
976
|
+
ticket_lifetime: lifetime,
|
|
977
|
+
ticket_age_add: age_add,
|
|
978
|
+
ticket_nonce: nonce,
|
|
979
|
+
ticket: ticket,
|
|
980
|
+
extensions: extensions
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
/* ================================ CertificateRequest ================================ */
|
|
986
|
+
// TLS 1.3:
|
|
987
|
+
// struct {
|
|
988
|
+
// opaque certificate_request_context<0..2^8-1>;
|
|
989
|
+
// Extension extensions<0..2^16-1>;
|
|
990
|
+
// } CertificateRequest;
|
|
991
|
+
//
|
|
992
|
+
// TLS 1.2:
|
|
993
|
+
// struct {
|
|
994
|
+
// ClientCertificateType certificate_types<1..2^8-1>;
|
|
995
|
+
// SignatureAndHashAlgorithm signature_algorithms<2..2^16-2>; // optional in <1.2
|
|
996
|
+
// DistinguishedName certificate_authorities<0..2^16-1>; // vector of DNs, each DN is opaque<1..2^16-1>
|
|
997
|
+
// } CertificateRequest;
|
|
998
|
+
|
|
999
|
+
function build_certificate_request(params) {
|
|
1000
|
+
var v = params && params.version || TLS_VERSION.TLS1_3;
|
|
1001
|
+
|
|
1002
|
+
if (v === TLS_VERSION.TLS1_3) {
|
|
1003
|
+
var ctx = toU8((params && params.request_context) || new Uint8Array(0));
|
|
1004
|
+
var extsBuf = Array.isArray(params && params.extensions)
|
|
1005
|
+
? build_extensions(params.extensions)
|
|
1006
|
+
: (params && params.extensions) || veclen(2, new Uint8Array(0));
|
|
1007
|
+
|
|
1008
|
+
var ctxVec = veclen(1, ctx);
|
|
1009
|
+
return concatUint8Arrays([ctxVec, extsBuf]);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// TLS 1.2 / 1.0 / 1.1
|
|
1013
|
+
var typesArr = (params && params.certificate_types) || [1]; // rsa_sign(1) as default
|
|
1014
|
+
var typesBuf = new Uint8Array(typesArr.length);
|
|
1015
|
+
for (var i = 0; i < typesArr.length; i++) typesBuf[i] = typesArr[i] & 0xFF;
|
|
1016
|
+
var typesVec = veclen(1, typesBuf);
|
|
1017
|
+
|
|
1018
|
+
var sigalgs = (params && params.signature_algorithms) || [];
|
|
1019
|
+
var sigBuf = new Uint8Array(sigalgs.length * 2);
|
|
1020
|
+
var o = 0;
|
|
1021
|
+
for (var j = 0; j < sigalgs.length; j++) o = w_u16(sigBuf, o, sigalgs[j]);
|
|
1022
|
+
var sigVec = sigalgs.length ? veclen(2, sigBuf) : new Uint8Array(0);
|
|
1023
|
+
|
|
1024
|
+
var cas = (params && params.certificate_authorities) || [];
|
|
1025
|
+
var caParts = [];
|
|
1026
|
+
var caTotal = 0;
|
|
1027
|
+
for (var k = 0; k < cas.length; k++) {
|
|
1028
|
+
var dn = toU8(cas[k]);
|
|
1029
|
+
var ent = new Uint8Array(2 + dn.length);
|
|
1030
|
+
var oo = 0; oo = w_u16(ent, oo, dn.length); oo = w_bytes(ent, oo, dn);
|
|
1031
|
+
caParts.push(ent); caTotal += ent.length;
|
|
1032
|
+
}
|
|
1033
|
+
var caVec = veclen(2, caParts.length ? concatUint8Arrays(caParts) : new Uint8Array(0));
|
|
1034
|
+
|
|
1035
|
+
return concatUint8Arrays([typesVec, sigVec, caVec]);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function parse_certificate_request(body) {
|
|
1039
|
+
// Try TLS 1.3 form first: ctx<1> + extensions<2>
|
|
1040
|
+
if (body.length >= 3) {
|
|
1041
|
+
var ctxLen = body[0];
|
|
1042
|
+
if (1 + ctxLen + 2 <= body.length) {
|
|
1043
|
+
var extLen = (body[1 + ctxLen] << 8) | body[2 + ctxLen];
|
|
1044
|
+
if (1 + ctxLen + 2 + extLen === body.length) {
|
|
1045
|
+
var ctx = body.subarray(1, 1 + ctxLen);
|
|
1046
|
+
var extBuf = body.subarray(1 + ctxLen + 2);
|
|
1047
|
+
return {
|
|
1048
|
+
version: TLS_VERSION.TLS1_3,
|
|
1049
|
+
request_context: ctx,
|
|
1050
|
+
extensions: parse_extensions(extBuf)
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Otherwise TLS 1.2/1.1/1.0
|
|
1057
|
+
var off = 0;
|
|
1058
|
+
|
|
1059
|
+
var typesBytes, off1;
|
|
1060
|
+
[typesBytes, off1] = readVec(body, off, 1);
|
|
1061
|
+
off = off1;
|
|
1062
|
+
var certificate_types = [];
|
|
1063
|
+
for (var i = 0; i < typesBytes.length; i++) certificate_types.push(typesBytes[i] >>> 0);
|
|
1064
|
+
|
|
1065
|
+
var signature_algorithms = [];
|
|
1066
|
+
if (off + 2 <= body.length) {
|
|
1067
|
+
var sigLen = (body[off] << 8) | body[off + 1];
|
|
1068
|
+
if (off + 2 + sigLen <= body.length) {
|
|
1069
|
+
off += 2;
|
|
1070
|
+
var endSig = off + sigLen;
|
|
1071
|
+
while (off < endSig) {
|
|
1072
|
+
var alg;
|
|
1073
|
+
[alg, off] = r_u16(body, off);
|
|
1074
|
+
signature_algorithms.push(alg);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
var cas = [];
|
|
1080
|
+
if (off + 2 <= body.length) {
|
|
1081
|
+
var caLen;
|
|
1082
|
+
[caLen, off] = r_u16(body, off);
|
|
1083
|
+
var end = off + caLen;
|
|
1084
|
+
while (off < end) {
|
|
1085
|
+
var dnLen;
|
|
1086
|
+
[dnLen, off] = r_u16(body, off);
|
|
1087
|
+
var dn;
|
|
1088
|
+
[dn, off] = r_bytes(body, off, dnLen);
|
|
1089
|
+
cas.push(dn);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return {
|
|
1094
|
+
version: TLS_VERSION.TLS1_2,
|
|
1095
|
+
certificate_types: certificate_types,
|
|
1096
|
+
signature_algorithms: signature_algorithms,
|
|
1097
|
+
certificate_authorities: cas
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
/* ============================== TLS 1.3 HelloRetryRequest ============================== */
|
|
1104
|
+
function build_hello_retry_request(params) {
|
|
1105
|
+
// params: { cipher_suite, selected_version, selected_group, cookie?: Uint8Array|string, other_exts?: list }
|
|
1106
|
+
var rnd = TLS13_HRR_RANDOM;
|
|
1107
|
+
var sid = new Uint8Array(0);
|
|
1108
|
+
var legacy_version = TLS_VERSION.TLS1_2;
|
|
1109
|
+
|
|
1110
|
+
var extList = [];
|
|
1111
|
+
// supported_versions (selected)
|
|
1112
|
+
extList.push({ type: 'SUPPORTED_VERSIONS', value: (params && params.selected_version) || TLS_VERSION.TLS1_3 });
|
|
1113
|
+
// key_share: only selected_group (no key)
|
|
1114
|
+
if (params && params.selected_group != null) {
|
|
1115
|
+
extList.push({ type: 'KEY_SHARE', value: { selected_group: params.selected_group, key_exchange: new Uint8Array(0) } });
|
|
1116
|
+
}
|
|
1117
|
+
// cookie if supplied
|
|
1118
|
+
if (params && params.cookie) {
|
|
1119
|
+
if (!exts.COOKIE) { exts.COOKIE = { encode: function(v){ return veclen(2, toU8(v||'')); }, decode: function(d){ var off=0,v; [v,off]=readVec(d,0,2); return v; } }; }
|
|
1120
|
+
extList.push({ type: 'COOKIE', value: params.cookie });
|
|
1121
|
+
}
|
|
1122
|
+
// other extensions passthrough
|
|
1123
|
+
if (params && Array.isArray(params.other_exts)) {
|
|
1124
|
+
for (var i=0;i<params.other_exts.length;i++) extList.push(params.other_exts[i]);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
var extsBuf = build_extensions(extList);
|
|
1128
|
+
var cipher_suite = (params && typeof params.cipher_suite==='number') ? params.cipher_suite : 0x1301;
|
|
1129
|
+
|
|
1130
|
+
// Wire = legacy_version + random + sid + cipher_suite + compression(0) + extensions
|
|
1131
|
+
var out = new Uint8Array(2 + 32 + 1 + 0 + 2 + 1 + extsBuf.length);
|
|
1132
|
+
var off = 0;
|
|
1133
|
+
off = w_u16(out, off, legacy_version);
|
|
1134
|
+
off = w_bytes(out, off, rnd);
|
|
1135
|
+
off = w_u8(out, off, 0);
|
|
1136
|
+
off = w_u16(out, off, cipher_suite);
|
|
1137
|
+
off = w_u8(out, off, 0);
|
|
1138
|
+
off = w_bytes(out, off, extsBuf);
|
|
1139
|
+
return out;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* ============================== TLS 1.2 ServerKeyExchange ============================== */
|
|
1143
|
+
// We'll implement the common ECDHE form and basic DHE form.
|
|
1144
|
+
// ECDHE ServerKeyExchange:
|
|
1145
|
+
// struct {
|
|
1146
|
+
// ECParameters curve; // curve_type(1)=3, named_curve(2)
|
|
1147
|
+
// opaque ec_point<1..2^8-1>; // server's ephemeral ECDH public key
|
|
1148
|
+
// digitally-signed struct { .. } // in TLS1.2: SignatureAndHashAlgorithm(2) + signature<2>
|
|
1149
|
+
// }
|
|
1150
|
+
function build_server_key_exchange_ecdhe(p) {
|
|
1151
|
+
// p: { group:u16, public:Uint8Array|string, sig_alg:u16, signature:Uint8Array|string }
|
|
1152
|
+
var pub = toU8(p.public||u8(0));
|
|
1153
|
+
|
|
1154
|
+
var head = new Uint8Array(1+2 + 1 + pub.length);
|
|
1155
|
+
var off = 0;
|
|
1156
|
+
off = w_u8(head, off, 3); // curve_type = named_curve
|
|
1157
|
+
off = w_u16(head, off, p.group>>>0); // named group
|
|
1158
|
+
off = w_u8(head, off, pub.length); // ec_point length
|
|
1159
|
+
off = w_bytes(head, off, pub);
|
|
1160
|
+
|
|
1161
|
+
var sig = toU8(p.signature||u8(0));
|
|
1162
|
+
var sigpart = new Uint8Array(2 + 2 + sig.length);
|
|
1163
|
+
var o2 = 0;
|
|
1164
|
+
o2 = w_u16(sigpart, o2, p.sig_alg>>>0);
|
|
1165
|
+
o2 = w_u16(sigpart, o2, sig.length);
|
|
1166
|
+
o2 = w_bytes(sigpart, o2, sig);
|
|
1167
|
+
|
|
1168
|
+
return concatUint8Arrays([head, sigpart]);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function parse_server_key_exchange(body) {
|
|
1172
|
+
var off = 0;
|
|
1173
|
+
var curve_type;
|
|
1174
|
+
[curve_type, off] = r_u8(body, off);
|
|
1175
|
+
|
|
1176
|
+
if (curve_type === 3) { // named_curve (ECDHE)
|
|
1177
|
+
var group;
|
|
1178
|
+
[group, off] = r_u16(body, off);
|
|
1179
|
+
|
|
1180
|
+
var plen;
|
|
1181
|
+
[plen, off] = r_u8(body, off);
|
|
1182
|
+
|
|
1183
|
+
var pub;
|
|
1184
|
+
[pub, off] = r_bytes(body, off, plen);
|
|
1185
|
+
|
|
1186
|
+
var sig_alg;
|
|
1187
|
+
[sig_alg, off] = r_u16(body, off);
|
|
1188
|
+
|
|
1189
|
+
var slen;
|
|
1190
|
+
[slen, off] = r_u16(body, off);
|
|
1191
|
+
|
|
1192
|
+
var sig;
|
|
1193
|
+
[sig, off] = r_bytes(body, off, slen);
|
|
1194
|
+
|
|
1195
|
+
return { kex: 'ECDHE', group: group, public: pub, sig_alg: sig_alg, signature: sig };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Basic DHE: dh_p<2>, dh_g<2>, dh_Ys<2>, then SignatureAndHashAlgorithm + signature<2>
|
|
1199
|
+
var pLen;
|
|
1200
|
+
[pLen, off] = r_u16(body, off);
|
|
1201
|
+
var dh_p;
|
|
1202
|
+
[dh_p, off] = r_bytes(body, off, pLen);
|
|
1203
|
+
|
|
1204
|
+
var gLen;
|
|
1205
|
+
[gLen, off] = r_u16(body, off);
|
|
1206
|
+
var dh_g;
|
|
1207
|
+
[dh_g, off] = r_bytes(body, off, gLen);
|
|
1208
|
+
|
|
1209
|
+
var yLen;
|
|
1210
|
+
[yLen, off] = r_u16(body, off);
|
|
1211
|
+
var dh_Ys;
|
|
1212
|
+
[dh_Ys, off] = r_bytes(body, off, yLen);
|
|
1213
|
+
|
|
1214
|
+
var sig_alg2;
|
|
1215
|
+
[sig_alg2, off] = r_u16(body, off);
|
|
1216
|
+
|
|
1217
|
+
var s2len;
|
|
1218
|
+
[s2len, off] = r_u16(body, off);
|
|
1219
|
+
var sig2;
|
|
1220
|
+
[sig2, off] = r_bytes(body, off, s2len);
|
|
1221
|
+
|
|
1222
|
+
return { kex: 'DHE', dh_p: dh_p, dh_g: dh_g, dh_Ys: dh_Ys, sig_alg: sig_alg2, signature: sig2 };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/* ============================== TLS 1.2 ClientKeyExchange ============================== */
|
|
1226
|
+
// Two common forms:
|
|
1227
|
+
// 1) ECDHE: opaque ec_point<1..2^8-1>
|
|
1228
|
+
// 2) RSA : EncryptedPreMasterSecret opaque<2>
|
|
1229
|
+
function build_client_key_exchange_ecdhe(pubkey) {
|
|
1230
|
+
var p = toU8(pubkey||u8(0));
|
|
1231
|
+
return veclen(1, p);
|
|
1232
|
+
}
|
|
1233
|
+
function parse_client_key_exchange_ecdhe(body) {
|
|
1234
|
+
var off=0; var v; [v,off]=readVec(body,0,1); return v;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function build_client_key_exchange_rsa(enc_pms) {
|
|
1238
|
+
var e = toU8(enc_pms||u8(0));
|
|
1239
|
+
return veclen(2, e);
|
|
1240
|
+
}
|
|
1241
|
+
function parse_client_key_exchange_rsa(body) {
|
|
1242
|
+
var off=0; var v; [v,off]=readVec(body,0,2); return v;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/* ============================== TLS 1.2 NewSessionTicket ============================== */
|
|
1246
|
+
// struct {
|
|
1247
|
+
// uint32 ticket_lifetime_hint;
|
|
1248
|
+
// opaque ticket<0..2^16-1>;
|
|
1249
|
+
// } NewSessionTicket;
|
|
1250
|
+
function build_new_session_ticket_tls12(p) {
|
|
1251
|
+
var hint = (p && p.ticket_lifetime_hint) >>> 0;
|
|
1252
|
+
var ticket = toU8(p && p.ticket || new Uint8Array(0));
|
|
1253
|
+
var out = new Uint8Array(4 + 2 + ticket.length);
|
|
1254
|
+
var off = 0;
|
|
1255
|
+
off = w_u8(out, off, (hint>>>24)&0xFF);
|
|
1256
|
+
off = w_u8(out, off, (hint>>>16)&0xFF);
|
|
1257
|
+
off = w_u8(out, off, (hint>>>8)&0xFF);
|
|
1258
|
+
off = w_u8(out, off, (hint)&0xFF);
|
|
1259
|
+
off = w_u16(out, off, ticket.length);
|
|
1260
|
+
off = w_bytes(out, off, ticket);
|
|
1261
|
+
return out;
|
|
1262
|
+
}
|
|
1263
|
+
function parse_new_session_ticket_tls12(body) {
|
|
1264
|
+
var off=0;
|
|
1265
|
+
var hint = (body[off]<<24 | body[off+1]<<16 | body[off+2]<<8 | body[off+3])>>>0; off+=4;
|
|
1266
|
+
var tlen; [tlen,off]=r_u16(body,off); var t; [t,off]=r_bytes(body,off,tlen);
|
|
1267
|
+
return { ticket_lifetime_hint: hint, ticket: t };
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/* ============================= Handshake message ============================= */
|
|
1271
|
+
function build_message(params) {
|
|
1272
|
+
|
|
1273
|
+
var type=0;
|
|
1274
|
+
var body=null;
|
|
1275
|
+
|
|
1276
|
+
if(params.type=='server_hello'){
|
|
1277
|
+
type=TLS_MESSAGE_TYPE.SERVER_HELLO;
|
|
1278
|
+
body=build_hello('server', params);
|
|
1279
|
+
}else if(params.type=='client_hello'){
|
|
1280
|
+
type=TLS_MESSAGE_TYPE.SERVER_HELLO;
|
|
1281
|
+
body=build_hello('client', params);
|
|
1282
|
+
}else if(params.type=='encrypted_extensions'){
|
|
1283
|
+
type=TLS_MESSAGE_TYPE.ENCRYPTED_EXTENSIONS;
|
|
1284
|
+
body=build_extensions(params.extensions);
|
|
1285
|
+
}else if(params.type=='certificate'){
|
|
1286
|
+
type=TLS_MESSAGE_TYPE.CERTIFICATE;
|
|
1287
|
+
body=build_certificate(params);
|
|
1288
|
+
}else if(params.type=='certificate_verify'){
|
|
1289
|
+
type=TLS_MESSAGE_TYPE.CERTIFICATE_VERIFY;
|
|
1290
|
+
body=build_certificate_verify(params.scheme,params.signature);
|
|
1291
|
+
}else if(params.type=='finished'){
|
|
1292
|
+
type=TLS_MESSAGE_TYPE.FINISHED;
|
|
1293
|
+
body=params.data;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
var out = new Uint8Array(4 + body.length);
|
|
1297
|
+
var off = 0;
|
|
1298
|
+
|
|
1299
|
+
off = w_u8(out, off, type);
|
|
1300
|
+
off = w_u24(out, off, body.length);
|
|
1301
|
+
off = w_bytes(out, off, body);
|
|
1302
|
+
|
|
1303
|
+
return out;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function parse_message(buf) {
|
|
1307
|
+
var off = 0;
|
|
1308
|
+
var t;
|
|
1309
|
+
[t, off] = r_u8(buf, off);
|
|
1310
|
+
|
|
1311
|
+
var l;
|
|
1312
|
+
[l, off] = r_u24(buf, off);
|
|
1313
|
+
|
|
1314
|
+
var b;
|
|
1315
|
+
[b, off] = r_bytes(buf, off, l);
|
|
1316
|
+
|
|
1317
|
+
return { type: t, body: b };
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
/* ================================ Exports ================================= */
|
|
1325
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1326
|
+
module.exports = {
|
|
1327
|
+
TLS_VERSION,
|
|
1328
|
+
TLS_MESSAGE_TYPE,
|
|
1329
|
+
TLS_EXT,
|
|
1330
|
+
|
|
1331
|
+
w_u8,
|
|
1332
|
+
w_u16,
|
|
1333
|
+
w_u24,
|
|
1334
|
+
w_bytes,
|
|
1335
|
+
r_u8,
|
|
1336
|
+
r_u16,
|
|
1337
|
+
r_u24,
|
|
1338
|
+
r_bytes,
|
|
1339
|
+
veclen,
|
|
1340
|
+
readVec,
|
|
1341
|
+
|
|
1342
|
+
exts,
|
|
1343
|
+
build_extensions,
|
|
1344
|
+
parse_extensions,
|
|
1345
|
+
|
|
1346
|
+
build_message,
|
|
1347
|
+
parse_message,
|
|
1348
|
+
build_hello,
|
|
1349
|
+
parse_hello,
|
|
1350
|
+
|
|
1351
|
+
build_certificate,
|
|
1352
|
+
parse_certificate,
|
|
1353
|
+
|
|
1354
|
+
build_certificate_verify,
|
|
1355
|
+
parse_certificate_verify,
|
|
1356
|
+
|
|
1357
|
+
build_new_session_ticket,
|
|
1358
|
+
parse_new_session_ticket,
|
|
1359
|
+
|
|
1360
|
+
build_certificate_request,
|
|
1361
|
+
parse_certificate_request,
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
build_hello_retry_request,
|
|
1365
|
+
|
|
1366
|
+
build_server_key_exchange_ecdhe,
|
|
1367
|
+
parse_server_key_exchange,
|
|
1368
|
+
|
|
1369
|
+
build_client_key_exchange_ecdhe,
|
|
1370
|
+
parse_client_key_exchange_ecdhe,
|
|
1371
|
+
|
|
1372
|
+
build_client_key_exchange_rsa,
|
|
1373
|
+
parse_client_key_exchange_rsa,
|
|
1374
|
+
|
|
1375
|
+
build_new_session_ticket_tls12,
|
|
1376
|
+
parse_new_session_ticket_tls12
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
/*
|
|
1383
|
+
function build_extensions(exts){
|
|
1384
|
+
// מרכיבים את רשימת ההרחבות (ללא שדה האורך הראשי)
|
|
1385
|
+
var list = [];
|
|
1386
|
+
for (var i=0; i<exts.length; i++){
|
|
1387
|
+
var t = exts[i].type|0;
|
|
1388
|
+
var d = exts[i].data instanceof Uint8Array ? exts[i].data
|
|
1389
|
+
: (exts[i].data && exts[i].data.buffer) ? new Uint8Array(exts[i].data)
|
|
1390
|
+
: new Uint8Array(0);
|
|
1391
|
+
|
|
1392
|
+
// type (2B)
|
|
1393
|
+
list.push((t>>>8)&0xFF, t&0xFF);
|
|
1394
|
+
// len (2B)
|
|
1395
|
+
list.push((d.length>>>8)&0xFF, d.length&0xFF);
|
|
1396
|
+
// data
|
|
1397
|
+
for (var k=0; k<d.length; k++) list.push(d[k]);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// עוטפים באורך כולל דו־בתי
|
|
1401
|
+
var out = [];
|
|
1402
|
+
var L = list.length;
|
|
1403
|
+
out.push((L>>>8)&0xFF, L&0xFF);
|
|
1404
|
+
for (var j=0; j<list.length; j++) out.push(list[j]);
|
|
1405
|
+
|
|
1406
|
+
return new Uint8Array(out);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function parse_extensions(data) {
|
|
1410
|
+
var ptr = 0;
|
|
1411
|
+
if (data.length < 2) return [];
|
|
1412
|
+
|
|
1413
|
+
var totalLen = (data[ptr++] << 8) | data[ptr++];
|
|
1414
|
+
if (ptr + totalLen > data.length) totalLen = data.length - ptr; // גידור
|
|
1415
|
+
|
|
1416
|
+
var exts = [];
|
|
1417
|
+
var end = ptr + totalLen;
|
|
1418
|
+
|
|
1419
|
+
while (ptr + 4 <= end) {
|
|
1420
|
+
var type = (data[ptr++] << 8) | data[ptr++];
|
|
1421
|
+
var len = (data[ptr++] << 8) | data[ptr++];
|
|
1422
|
+
if (ptr + len > end) break; // שבור – עוצרים
|
|
1423
|
+
|
|
1424
|
+
var extData = data.subarray(ptr, ptr + len);
|
|
1425
|
+
ptr += len;
|
|
1426
|
+
|
|
1427
|
+
exts.push({ type, data: extData });
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
return exts;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
function parse_client_hello(data) {
|
|
1439
|
+
var ptr = 0;
|
|
1440
|
+
|
|
1441
|
+
var legacy_version = (data[ptr++] << 8) | data[ptr++];
|
|
1442
|
+
var random = data.slice(ptr, ptr + 32); ptr += 32;
|
|
1443
|
+
var session_id_len = data[ptr++];
|
|
1444
|
+
var session_id = data.slice(ptr, ptr + session_id_len); ptr += session_id_len;
|
|
1445
|
+
|
|
1446
|
+
var cipher_suites_len = (data[ptr++] << 8) | data[ptr++];
|
|
1447
|
+
var cipher_suites = [];
|
|
1448
|
+
for (var i = 0; i < cipher_suites_len; i += 2) {
|
|
1449
|
+
var code = (data[ptr++] << 8) | data[ptr++];
|
|
1450
|
+
cipher_suites.push(code);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
var compression_methods_len = data[ptr++];
|
|
1454
|
+
var compression_methods = data.slice(ptr, ptr + compression_methods_len); ptr += compression_methods_len;
|
|
1455
|
+
|
|
1456
|
+
var extensions_len = (data[ptr++] << 8) | data[ptr++];
|
|
1457
|
+
var extensions = [];
|
|
1458
|
+
var ext_end = ptr + extensions_len;
|
|
1459
|
+
while (ptr < ext_end) {
|
|
1460
|
+
var ext_type = (data[ptr++] << 8) | data[ptr++];
|
|
1461
|
+
var ext_len = (data[ptr++] << 8) | data[ptr++];
|
|
1462
|
+
var ext_data = data.slice(ptr, ptr + ext_len); ptr += ext_len;
|
|
1463
|
+
extensions.push({ type: ext_type, data: ext_data });
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
var sni = null;
|
|
1467
|
+
var key_shares = [];
|
|
1468
|
+
var supported_versions = [];
|
|
1469
|
+
var supported_groups = [];
|
|
1470
|
+
var signature_algorithms = [];
|
|
1471
|
+
var alpn = [];
|
|
1472
|
+
var max_fragment_length = null;
|
|
1473
|
+
var padding = null;
|
|
1474
|
+
var cookie = null;
|
|
1475
|
+
var psk_key_exchange_modes = [];
|
|
1476
|
+
var pre_shared_key = null;
|
|
1477
|
+
var renegotiation_info = null;
|
|
1478
|
+
var unknown_extensions = null;
|
|
1479
|
+
|
|
1480
|
+
for (var ext of extensions) {
|
|
1481
|
+
var ext_data = new Uint8Array(ext.data);
|
|
1482
|
+
if (ext.type === 0x00) { // SNI
|
|
1483
|
+
var list_len = (ext_data[0] << 8) | ext_data[1];
|
|
1484
|
+
var name_type = ext_data[2];
|
|
1485
|
+
var name_len = (ext_data[3] << 8) | ext_data[4];
|
|
1486
|
+
var name = new TextDecoder().decode(ext_data.slice(5, 5 + name_len));
|
|
1487
|
+
sni = name;
|
|
1488
|
+
}
|
|
1489
|
+
if (ext.type === 0x33) {
|
|
1490
|
+
var ptr2 = 0;
|
|
1491
|
+
var list_len = (ext_data[ptr2++] << 8) | ext_data[ptr2++];
|
|
1492
|
+
var end = ptr2 + list_len;
|
|
1493
|
+
while (ptr2 < end) {
|
|
1494
|
+
var group = (ext_data[ptr2++] << 8) | ext_data[ptr2++];
|
|
1495
|
+
var key_len = (ext_data[ptr2++] << 8) | ext_data[ptr2++];
|
|
1496
|
+
var pubkey = ext_data.slice(ptr2, ptr2 + key_len);
|
|
1497
|
+
ptr2 += key_len;
|
|
1498
|
+
key_shares.push({ group, pubkey });
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
}
|
|
1502
|
+
if (ext.type === 0x2b) { // supported_versions
|
|
1503
|
+
var len = ext_data[0];
|
|
1504
|
+
for (var i = 1; i < 1 + len; i += 2) {
|
|
1505
|
+
var ver = (ext_data[i] << 8) | ext_data[i + 1];
|
|
1506
|
+
supported_versions.push(ver);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (ext.type === 0x0a) { // supported_groups
|
|
1510
|
+
var len = (ext_data[0] << 8) | ext_data[1];
|
|
1511
|
+
for (var i = 2; i < 2 + len; i += 2) {
|
|
1512
|
+
supported_groups.push((ext_data[i] << 8) | ext_data[i + 1]);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
if (ext.type === 0x0d) { // signature_algorithms
|
|
1516
|
+
var len = (ext_data[0] << 8) | ext_data[1];
|
|
1517
|
+
for (var i = 2; i < 2 + len; i += 2) {
|
|
1518
|
+
signature_algorithms.push((ext_data[i] << 8) | ext_data[i + 1]);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (ext.type === 0x10) { // ALPN
|
|
1522
|
+
var list_len = (ext_data[0] << 8) | ext_data[1];
|
|
1523
|
+
var i = 2;
|
|
1524
|
+
while (i < 2 + list_len) {
|
|
1525
|
+
var name_len = ext_data[i++];
|
|
1526
|
+
var proto = new TextDecoder().decode(ext_data.slice(i, i + name_len));
|
|
1527
|
+
alpn.push(proto);
|
|
1528
|
+
i += name_len;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (ext.type === 0x39) { // quic_transport_parameters
|
|
1532
|
+
unknown_extensions = ext.data;
|
|
1533
|
+
}
|
|
1534
|
+
if (ext.type === 0x01) { // Max Fragment Length
|
|
1535
|
+
max_fragment_length = ext_data[0];
|
|
1536
|
+
}
|
|
1537
|
+
if (ext.type === 0x15) { // Padding
|
|
1538
|
+
padding = ext_data;
|
|
1539
|
+
}
|
|
1540
|
+
if (ext.type === 0x002a) { // Cookie
|
|
1541
|
+
var len = (ext_data[0] << 8) | ext_data[1];
|
|
1542
|
+
cookie = ext_data.slice(2, 2 + len);
|
|
1543
|
+
}
|
|
1544
|
+
if (ext.type === 0x2d) { // PSK Key Exchange Modes
|
|
1545
|
+
var len = ext_data[0];
|
|
1546
|
+
for (var i = 1; i <= len; i++) {
|
|
1547
|
+
psk_key_exchange_modes.push(ext_data[i]);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
if (ext.type === 0x29) { // PreSharedKey (placeholder)
|
|
1551
|
+
pre_shared_key = ext_data;
|
|
1552
|
+
}
|
|
1553
|
+
if (ext.type === 0xff01) { // Renegotiation Info
|
|
1554
|
+
renegotiation_info = ext_data;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
return {
|
|
1559
|
+
legacy_version,
|
|
1560
|
+
random,
|
|
1561
|
+
session_id,
|
|
1562
|
+
cipher_suites,
|
|
1563
|
+
compression_methods,
|
|
1564
|
+
extensions,
|
|
1565
|
+
sni,
|
|
1566
|
+
key_shares,
|
|
1567
|
+
supported_versions,
|
|
1568
|
+
supported_groups,
|
|
1569
|
+
signature_algorithms,
|
|
1570
|
+
alpn,
|
|
1571
|
+
max_fragment_length,
|
|
1572
|
+
padding,
|
|
1573
|
+
cookie,
|
|
1574
|
+
psk_key_exchange_modes,
|
|
1575
|
+
pre_shared_key,
|
|
1576
|
+
renegotiation_info,
|
|
1577
|
+
unknown_extensions
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
function build_server_hello(params){
|
|
1585
|
+
var version = params.version|0;
|
|
1586
|
+
var body = [];
|
|
1587
|
+
|
|
1588
|
+
// 1) legacy_version
|
|
1589
|
+
var legacy_version = (version === 0x0304) ? 0x0303 : 0x0303;
|
|
1590
|
+
body.push((legacy_version>>>8)&0xFF, legacy_version&0xFF);
|
|
1591
|
+
|
|
1592
|
+
// 2) random
|
|
1593
|
+
var rnd = params.server_random;
|
|
1594
|
+
for (var i=0;i<rnd.length;i++) body.push(rnd[i]);
|
|
1595
|
+
|
|
1596
|
+
// 3) legacy_session_id
|
|
1597
|
+
var sid = params.legacy_session_id || new Uint8Array(0);
|
|
1598
|
+
body.push(sid.length & 0xFF);
|
|
1599
|
+
for (var i=0;i<sid.length;i++) body.push(sid[i]);
|
|
1600
|
+
|
|
1601
|
+
// 4) cipher_suite
|
|
1602
|
+
var cs = params.cipher_suite|0;
|
|
1603
|
+
body.push((cs>>>8)&0xFF, cs&0xFF);
|
|
1604
|
+
|
|
1605
|
+
// 5) legacy_compression_method
|
|
1606
|
+
body.push(params.compression_method|0);
|
|
1607
|
+
|
|
1608
|
+
// 6) extensions
|
|
1609
|
+
var exts = [];
|
|
1610
|
+
|
|
1611
|
+
if (version === 0x0304){
|
|
1612
|
+
// --- TLS 1.3 extensions ---
|
|
1613
|
+
|
|
1614
|
+
// supported_versions (0x002b)
|
|
1615
|
+
exts.push(0x00,0x2b); // type
|
|
1616
|
+
exts.push(0x00,0x02); // len=2
|
|
1617
|
+
exts.push(0x03,0x04); // TLS1.3
|
|
1618
|
+
|
|
1619
|
+
// key_share (0x0033)
|
|
1620
|
+
var group = params.selected_group|0;
|
|
1621
|
+
var pub = params.server_key_share;
|
|
1622
|
+
var ks = [];
|
|
1623
|
+
ks.push((group>>>8)&0xFF, group&0xFF);
|
|
1624
|
+
ks.push((pub.length>>>8)&0xFF, pub.length&0xFF);
|
|
1625
|
+
for (var j=0;j<pub.length;j++) ks.push(pub[j]);
|
|
1626
|
+
|
|
1627
|
+
exts.push(0x00,0x33); // type
|
|
1628
|
+
exts.push((ks.length>>>8)&0xFF, ks.length&0xFF);
|
|
1629
|
+
for (var j=0;j<ks.length;j++) exts.push(ks[j]);
|
|
1630
|
+
|
|
1631
|
+
} else if (version === 0x0303){
|
|
1632
|
+
// --- TLS 1.2 extensions (אופציונלי) ---
|
|
1633
|
+
|
|
1634
|
+
if (params.secure_renegotiation){
|
|
1635
|
+
// renegotiation_info (0xFF01), length=1, value=0x00
|
|
1636
|
+
exts.push(0xFF,0x01);
|
|
1637
|
+
exts.push(0x00,0x01);
|
|
1638
|
+
exts.push(0x00);
|
|
1639
|
+
}
|
|
1640
|
+
if (params.extended_master_secret){
|
|
1641
|
+
// extended_master_secret (0x0017), empty
|
|
1642
|
+
exts.push(0x00,0x17);
|
|
1643
|
+
exts.push(0x00,0x00);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
if (params.extra_extensions && params.extra_extensions.length){
|
|
1648
|
+
for (var e=0;e<params.extra_extensions.length;e++){
|
|
1649
|
+
var ext = params.extra_extensions[e];
|
|
1650
|
+
var et = ext.type|0;
|
|
1651
|
+
var ed = ext.data;
|
|
1652
|
+
exts.push((et>>>8)&0xFF, et&0xFF);
|
|
1653
|
+
exts.push((ed.length>>>8)&0xFF, ed.length&0xFF);
|
|
1654
|
+
for (var k=0;k<ed.length;k++) exts.push(ed[k]);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
body.push((exts.length>>>8)&0xFF, exts.length&0xFF);
|
|
1659
|
+
for (var i=0;i<exts.length;i++) body.push(exts[i]);
|
|
1660
|
+
|
|
1661
|
+
// 7) Handshake header (ServerHello=2)
|
|
1662
|
+
var sh = [];
|
|
1663
|
+
sh.push(2); // msg_type=server_hello
|
|
1664
|
+
var len = body.length;
|
|
1665
|
+
sh.push((len>>>16)&0xFF, (len>>>8)&0xFF, len&0xFF);
|
|
1666
|
+
for (var i=0;i<body.length;i++) sh.push(body[i]);
|
|
1667
|
+
|
|
1668
|
+
return new Uint8Array(sh);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
*/
|