quico 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +308 -70
- package/index.cjs +45 -0
- package/index.d.ts +158 -0
- package/index.js +46 -2631
- package/package.json +69 -14
- package/src/agent.js +275 -0
- package/src/client.js +439 -0
- package/src/crypto.js +519 -0
- package/src/h3.js +911 -0
- package/src/h3_client.js +317 -0
- package/src/h3_server.js +523 -0
- package/src/quic_connection.js +1238 -0
- package/src/server.js +346 -0
- package/src/streams.js +471 -0
- package/src/tls_bridge.js +211 -0
- package/src/transport.js +651 -0
- package/src/utils.js +290 -0
- package/src/webtransport.js +708 -0
- package/CHANGELOG.md +0 -25
- package/examples/webtransport_datagram_echo_server/client.html +0 -58
- package/examples/webtransport_datagram_echo_server/server.js +0 -29
- package/libs/crypto.js +0 -2107
- package/libs/h3.js +0 -1214
- package/libs/utils.js +0 -404
- package/quico.svg +0 -1
package/libs/crypto.js
DELETED
|
@@ -1,2107 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* quico: HTTP/3 and QUIC implementation for Node.js
|
|
3
|
-
* Copyright 2025 colocohen
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at:
|
|
8
|
-
*
|
|
9
|
-
* https://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* This file is part of the open-source project hosted at:
|
|
12
|
-
* https://github.com/colocohen/quico
|
|
13
|
-
*
|
|
14
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
var nobleHashes={
|
|
20
|
-
hmac: require("@noble/hashes/hmac.js")['hmac'],
|
|
21
|
-
hkdf: require("@noble/hashes/hkdf.js")['hkdf'],
|
|
22
|
-
hkdf_extract: require("@noble/hashes/hkdf.js")['extract'],
|
|
23
|
-
hkdf_expand: require("@noble/hashes/hkdf.js")['expand'],
|
|
24
|
-
sha256: require("@noble/hashes/sha2.js")['sha256'],
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
var { p256 } = require('@noble/curves/p256');
|
|
28
|
-
var { x25519 } = require('@noble/curves/ed25519');
|
|
29
|
-
var { sha256, sha384 } = require('@noble/hashes/sha2');
|
|
30
|
-
|
|
31
|
-
var crypto = require('crypto');
|
|
32
|
-
|
|
33
|
-
var { AES } = require('@stablelib/aes');
|
|
34
|
-
var { GCM } = require('@stablelib/gcm');
|
|
35
|
-
|
|
36
|
-
var x509 = require('@peculiar/x509');
|
|
37
|
-
|
|
38
|
-
var {
|
|
39
|
-
concatUint8Arrays,
|
|
40
|
-
writeVarInt,
|
|
41
|
-
readVarInt
|
|
42
|
-
} = require('./utils');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
function get_cipher_info(cipher_suite) {
|
|
49
|
-
switch (cipher_suite) {
|
|
50
|
-
case 0x1301: // TLS_AES_128_GCM_SHA256
|
|
51
|
-
return { keylen: 16, ivlen: 12, hash: sha256,str: 'sha256' };
|
|
52
|
-
case 0x1302: // TLS_AES_256_GCM_SHA384
|
|
53
|
-
return { keylen: 32, ivlen: 12, hash: sha384,str: 'sha384' };
|
|
54
|
-
case 0x1303: // TLS_CHACHA20_POLY1305_SHA256
|
|
55
|
-
return { keylen: 32, ivlen: 12, hash: sha256,str: 'sha256' };
|
|
56
|
-
default:
|
|
57
|
-
throw new Error("Unsupported cipher suite: 0x" + cipher_suite.toString(16));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function build_server_hello(server_random, public_key, session_id, cipher_suite, group) {
|
|
65
|
-
var legacy_version = [0x03, 0x03];
|
|
66
|
-
var random = Array.from(server_random);
|
|
67
|
-
var session_id_bytes = Array.from(session_id);
|
|
68
|
-
var session_id_length = session_id_bytes.length & 0xff;
|
|
69
|
-
|
|
70
|
-
var cipher_suite_bytes = [(cipher_suite >> 8) & 0xff, cipher_suite & 0xff];
|
|
71
|
-
var compression_method = [0x00];
|
|
72
|
-
|
|
73
|
-
var key = Array.from(public_key);
|
|
74
|
-
var key_length = [(key.length >> 8) & 0xff, key.length & 0xff];
|
|
75
|
-
var group_bytes = [(group >> 8) & 0xff, group & 0xff];
|
|
76
|
-
var key_exchange = [...group_bytes, ...key_length, ...key];
|
|
77
|
-
var key_share_extension = (() => {
|
|
78
|
-
var extension_type = [0x00, 0x33];
|
|
79
|
-
var extension_length = [(key_exchange.length >> 8) & 0xff, key_exchange.length & 0xff];
|
|
80
|
-
return [...extension_type, ...extension_length, ...key_exchange];
|
|
81
|
-
})();
|
|
82
|
-
|
|
83
|
-
var supported_versions_extension = [
|
|
84
|
-
0x00, 0x2b,
|
|
85
|
-
0x00, 0x02,
|
|
86
|
-
0x03, 0x04
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
var params_bytes = [
|
|
90
|
-
0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x10, 0x00, // initial_max_data = 4096
|
|
91
|
-
0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x08, 0x00 // max_packet_size = 2048
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
var extensions = [
|
|
97
|
-
...supported_versions_extension,
|
|
98
|
-
...key_share_extension
|
|
99
|
-
];
|
|
100
|
-
var extensions_length = [(extensions.length >> 8) & 0xff, extensions.length & 0xff];
|
|
101
|
-
|
|
102
|
-
var handshake_body = [
|
|
103
|
-
...legacy_version,
|
|
104
|
-
...random,
|
|
105
|
-
session_id_length,
|
|
106
|
-
...session_id_bytes,
|
|
107
|
-
...cipher_suite_bytes,
|
|
108
|
-
...compression_method,
|
|
109
|
-
...extensions_length,
|
|
110
|
-
...extensions
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
var body_length = handshake_body.length;
|
|
114
|
-
var handshake = [
|
|
115
|
-
0x02, // handshake type: ServerHello
|
|
116
|
-
(body_length >> 16) & 0xff,
|
|
117
|
-
(body_length >> 8) & 0xff,
|
|
118
|
-
body_length & 0xff,
|
|
119
|
-
...handshake_body
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
return Uint8Array.from(handshake); // ✔️ מחזיר רק Handshake Message
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
function build_quic_ext(params) {
|
|
129
|
-
var out = [];
|
|
130
|
-
|
|
131
|
-
function addParam(id, value) {
|
|
132
|
-
var id_bytes = writeVarInt(id);
|
|
133
|
-
var length_bytes, value_bytes;
|
|
134
|
-
|
|
135
|
-
if (typeof value === 'number') {
|
|
136
|
-
value_bytes = writeVarInt(value);
|
|
137
|
-
} else if (value instanceof Uint8Array) {
|
|
138
|
-
value_bytes = Array.from(value);
|
|
139
|
-
} else if (value === true) {
|
|
140
|
-
value_bytes = []; // for disable_active_migration
|
|
141
|
-
} else {
|
|
142
|
-
throw new Error('Unsupported value type for parameter ' + id);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
length_bytes = writeVarInt(value_bytes.length);
|
|
146
|
-
out.push(...id_bytes, ...length_bytes, ...value_bytes);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (params.original_destination_connection_id)
|
|
150
|
-
addParam(0x00, params.original_destination_connection_id);
|
|
151
|
-
if (params.max_idle_timeout)
|
|
152
|
-
addParam(0x01, params.max_idle_timeout);
|
|
153
|
-
if (params.stateless_reset_token)
|
|
154
|
-
addParam(0x02, params.stateless_reset_token);
|
|
155
|
-
if (params.max_udp_payload_size)
|
|
156
|
-
addParam(0x03, params.max_udp_payload_size);
|
|
157
|
-
if (params.initial_max_data)
|
|
158
|
-
addParam(0x04, params.initial_max_data);
|
|
159
|
-
if (params.initial_max_stream_data_bidi_local)
|
|
160
|
-
addParam(0x05, params.initial_max_stream_data_bidi_local);
|
|
161
|
-
if (params.initial_max_stream_data_bidi_remote)
|
|
162
|
-
addParam(0x06, params.initial_max_stream_data_bidi_remote);
|
|
163
|
-
if (params.initial_max_stream_data_uni)
|
|
164
|
-
addParam(0x07, params.initial_max_stream_data_uni);
|
|
165
|
-
if (params.initial_max_streams_bidi)
|
|
166
|
-
addParam(0x08, params.initial_max_streams_bidi);
|
|
167
|
-
if (params.initial_max_streams_uni)
|
|
168
|
-
addParam(0x09, params.initial_max_streams_uni);
|
|
169
|
-
if (params.ack_delay_exponent !== undefined)
|
|
170
|
-
addParam(0x0a, params.ack_delay_exponent);
|
|
171
|
-
if (params.max_ack_delay !== undefined)
|
|
172
|
-
addParam(0x0b, params.max_ack_delay);
|
|
173
|
-
if (params.disable_active_migration)
|
|
174
|
-
addParam(0x0c, true);
|
|
175
|
-
if (params.active_connection_id_limit)
|
|
176
|
-
addParam(0x0e, params.active_connection_id_limit);
|
|
177
|
-
if (params.initial_source_connection_id)
|
|
178
|
-
addParam(0x0f, params.initial_source_connection_id);
|
|
179
|
-
if (params.retry_source_connection_id)
|
|
180
|
-
addParam(0x10, params.retry_source_connection_id);
|
|
181
|
-
if (params.max_datagram_frame_size)
|
|
182
|
-
addParam(0x20, params.max_datagram_frame_size); // אין ערך – presence בלבד
|
|
183
|
-
if (params.web_accepted_origins) {
|
|
184
|
-
for (var i = 0; i < params.web_accepted_origins.length; i++) {
|
|
185
|
-
var origin = params.web_accepted_origins[i];
|
|
186
|
-
var origin_bytes = new TextEncoder().encode(origin);
|
|
187
|
-
addParam(0x2b603742, origin_bytes);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return new Uint8Array(out);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
function build_alpn_ext(protocol) {
|
|
199
|
-
var proto_bytes = new TextEncoder().encode(protocol);
|
|
200
|
-
var ext = new Uint8Array(2 + 1 + proto_bytes.length);
|
|
201
|
-
ext[0] = 0x00;
|
|
202
|
-
ext[1] = proto_bytes.length + 1;
|
|
203
|
-
ext[2] = proto_bytes.length;
|
|
204
|
-
ext.set(proto_bytes, 3);
|
|
205
|
-
return ext;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function build_encrypted_extensions(extensions) {
|
|
209
|
-
var ext_bytes = [];
|
|
210
|
-
for (var ext of extensions) {
|
|
211
|
-
ext_bytes.push((ext.type >> 8) & 0xff, ext.type & 0xff);
|
|
212
|
-
ext_bytes.push((ext.data.length >> 8) & 0xff, ext.data.length & 0xff);
|
|
213
|
-
ext_bytes.push(...ext.data);
|
|
214
|
-
}
|
|
215
|
-
var ext_len = ext_bytes.length;
|
|
216
|
-
var ext_len_bytes = [(ext_len >> 8) & 0xff, ext_len & 0xff];
|
|
217
|
-
var body = [...ext_len_bytes, ...ext_bytes];
|
|
218
|
-
var hs_len = body.length;
|
|
219
|
-
var header = [0x08, (hs_len >> 16) & 0xff, (hs_len >> 8) & 0xff, hs_len & 0xff];
|
|
220
|
-
return Uint8Array.from([...header, ...body]);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function build_certificate(certificates) {
|
|
224
|
-
var context = [0x00];
|
|
225
|
-
var cert_list = [];
|
|
226
|
-
for (var cert of certificates) {
|
|
227
|
-
var extensions = cert.extensions instanceof Uint8Array ? cert.extensions : new Uint8Array(0);
|
|
228
|
-
cert_list.push((cert.cert.length >> 16) & 0xff, (cert.cert.length >> 8) & 0xff, cert.cert.length & 0xff);
|
|
229
|
-
cert_list.push(...cert.cert);
|
|
230
|
-
cert_list.push((extensions.length >> 8) & 0xff, extensions.length & 0xff);
|
|
231
|
-
cert_list.push(...extensions);
|
|
232
|
-
}
|
|
233
|
-
var total_len = cert_list.length;
|
|
234
|
-
var list_len = [(total_len >> 16) & 0xff, (total_len >> 8) & 0xff, total_len & 0xff];
|
|
235
|
-
var body = [...context, ...list_len, ...cert_list];
|
|
236
|
-
var hs_len = body.length;
|
|
237
|
-
var header = [0x0b, (hs_len >> 16) & 0xff, (hs_len >> 8) & 0xff, hs_len & 0xff];
|
|
238
|
-
return Uint8Array.from([...header, ...body]);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
function build_certificate_verify(algorithm, signature) {
|
|
244
|
-
var sig_len = signature.length;
|
|
245
|
-
var total_len = 4 + sig_len;
|
|
246
|
-
var header = [
|
|
247
|
-
0x0f,
|
|
248
|
-
(total_len >> 16) & 0xff,
|
|
249
|
-
(total_len >> 8) & 0xff,
|
|
250
|
-
total_len & 0xff,
|
|
251
|
-
(algorithm >> 8) & 0xff, algorithm & 0xff,
|
|
252
|
-
(sig_len >> 8) & 0xff, sig_len & 0xff
|
|
253
|
-
];
|
|
254
|
-
return Uint8Array.from([...header, ...signature]);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function build_finished(verify_data) {
|
|
258
|
-
var length = verify_data.length;
|
|
259
|
-
var header = [0x14, (length >> 16) & 0xff, (length >> 8) & 0xff, length & 0xff];
|
|
260
|
-
return Uint8Array.from([...header, ...verify_data]);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
function handle_client_hello(parsed) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
var supported_groups = [0x001d, 0x0017]; // X25519, secp256r1
|
|
269
|
-
var supported_cipher_suites = [0x1301, 0x1302];//0x1303,
|
|
270
|
-
|
|
271
|
-
var selected_alpn=null;
|
|
272
|
-
var selected_group=null;
|
|
273
|
-
var selected_cipher=null;
|
|
274
|
-
|
|
275
|
-
var client_public_key=null;
|
|
276
|
-
|
|
277
|
-
var server_private_key=null;
|
|
278
|
-
var server_public_key=null;
|
|
279
|
-
var shared_secret=null;
|
|
280
|
-
|
|
281
|
-
for(var i in supported_cipher_suites){
|
|
282
|
-
if(parsed.cipher_suites.includes(supported_cipher_suites[i])==true){
|
|
283
|
-
selected_cipher=supported_cipher_suites[i];
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
for(var i in supported_groups){
|
|
289
|
-
if(selected_group==null){
|
|
290
|
-
for(var i2 in parsed.key_shares){
|
|
291
|
-
if(parsed.key_shares[i2].group==supported_groups[i]){
|
|
292
|
-
selected_group=parsed.key_shares[i2].group;
|
|
293
|
-
client_public_key=parsed.key_shares[i2].pubkey;
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if(selected_group!==null){
|
|
303
|
-
|
|
304
|
-
if (selected_group === 0x001d) { // X25519
|
|
305
|
-
server_private_key = crypto.randomBytes(32);
|
|
306
|
-
server_public_key = x25519.getPublicKey(server_private_key);
|
|
307
|
-
shared_secret = x25519.getSharedSecret(server_private_key, client_public_key);
|
|
308
|
-
} else if (selected_group === 0x0017) { // secp256r1 (P-256)
|
|
309
|
-
server_private_key = p256.utils.randomPrivateKey();
|
|
310
|
-
server_public_key = p256.getPublicKey(server_private_key, false);
|
|
311
|
-
var client_point = p256.ProjectivePoint.fromHex(client_public_key);
|
|
312
|
-
var shared_point = client_point.multiply(
|
|
313
|
-
BigInt('0x' + Buffer.from(server_private_key).toString('hex'))
|
|
314
|
-
);
|
|
315
|
-
shared_secret = shared_point.toRawBytes().slice(0, 32);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
selected_cipher: selected_cipher,
|
|
323
|
-
selected_group: selected_group,
|
|
324
|
-
client_public_key: client_public_key,
|
|
325
|
-
server_private_key: new Uint8Array(server_private_key),
|
|
326
|
-
server_public_key: server_public_key,
|
|
327
|
-
shared_secret: shared_secret
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
function parse_transport_parameters(buf, start) {
|
|
338
|
-
if (!(buf instanceof Uint8Array)) throw new Error("Expect Uint8Array");
|
|
339
|
-
var offset = start || 0;
|
|
340
|
-
var end = buf.length;
|
|
341
|
-
var out = {
|
|
342
|
-
web_accepted_origins: []
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
while (offset < end) {
|
|
346
|
-
// ---- מזהה הפרמטר ----
|
|
347
|
-
var idVar = readVarInt(buf, offset);
|
|
348
|
-
if (!idVar) throw new Error("Bad varint (id) at " + offset);
|
|
349
|
-
offset += idVar.byteLength;
|
|
350
|
-
var id = idVar.value;
|
|
351
|
-
|
|
352
|
-
// ---- אורך הערך ----
|
|
353
|
-
var lenVar = readVarInt(buf, offset);
|
|
354
|
-
if (!lenVar) throw new Error("Bad varint (len) at " + offset);
|
|
355
|
-
offset += lenVar.byteLength;
|
|
356
|
-
var length = lenVar.value;
|
|
357
|
-
|
|
358
|
-
if (offset + length > end) throw new Error("Truncated value for id " + id);
|
|
359
|
-
var valueBytes = buf.slice(offset, offset + length);
|
|
360
|
-
offset += length;
|
|
361
|
-
|
|
362
|
-
// ---- פענוח לפי ID ----
|
|
363
|
-
switch (id) {
|
|
364
|
-
case 0x00:
|
|
365
|
-
out.original_destination_connection_id = valueBytes;
|
|
366
|
-
break;
|
|
367
|
-
case 0x01:
|
|
368
|
-
out.max_idle_timeout = readVarInt(valueBytes, 0).value;
|
|
369
|
-
break;
|
|
370
|
-
case 0x02:
|
|
371
|
-
if (valueBytes.length !== 16) throw new Error("stateless_reset_token len≠16");
|
|
372
|
-
out.stateless_reset_token = valueBytes;
|
|
373
|
-
break;
|
|
374
|
-
case 0x03:
|
|
375
|
-
out.max_udp_payload_size = readVarInt(valueBytes, 0).value;
|
|
376
|
-
break;
|
|
377
|
-
case 0x04:
|
|
378
|
-
out.initial_max_data = readVarInt(valueBytes, 0).value;
|
|
379
|
-
break;
|
|
380
|
-
case 0x05:
|
|
381
|
-
out.initial_max_stream_data_bidi_local = readVarInt(valueBytes, 0).value;
|
|
382
|
-
break;
|
|
383
|
-
case 0x06:
|
|
384
|
-
out.initial_max_stream_data_bidi_remote = readVarInt(valueBytes, 0).value;
|
|
385
|
-
break;
|
|
386
|
-
case 0x07:
|
|
387
|
-
out.initial_max_stream_data_uni = readVarInt(valueBytes, 0).value;
|
|
388
|
-
break;
|
|
389
|
-
case 0x08:
|
|
390
|
-
out.initial_max_streams_bidi = readVarInt(valueBytes, 0).value;
|
|
391
|
-
break;
|
|
392
|
-
case 0x09:
|
|
393
|
-
out.initial_max_streams_uni = readVarInt(valueBytes, 0).value;
|
|
394
|
-
break;
|
|
395
|
-
case 0x0a:
|
|
396
|
-
out.ack_delay_exponent = readVarInt(valueBytes, 0).value;
|
|
397
|
-
break;
|
|
398
|
-
case 0x0b:
|
|
399
|
-
out.max_ack_delay = readVarInt(valueBytes, 0).value;
|
|
400
|
-
break;
|
|
401
|
-
case 0x0c:
|
|
402
|
-
if (length !== 0) throw new Error("disable_active_migration must be zero-length");
|
|
403
|
-
out.disable_active_migration = true;
|
|
404
|
-
break;
|
|
405
|
-
case 0x0e:
|
|
406
|
-
out.active_connection_id_limit = readVarInt(valueBytes, 0).value;
|
|
407
|
-
break;
|
|
408
|
-
case 0x0f:
|
|
409
|
-
out.initial_source_connection_id = valueBytes;
|
|
410
|
-
break;
|
|
411
|
-
case 0x10:
|
|
412
|
-
out.retry_source_connection_id = valueBytes;
|
|
413
|
-
break;
|
|
414
|
-
case 0x20:
|
|
415
|
-
out.max_datagram_frame_size = readVarInt(valueBytes, 0).value;
|
|
416
|
-
break;
|
|
417
|
-
case 0x11:
|
|
418
|
-
out.server_certificate_hash = valueBytes;
|
|
419
|
-
break;
|
|
420
|
-
case 0x2b603742:
|
|
421
|
-
var origin = new TextDecoder().decode(valueBytes);
|
|
422
|
-
out.web_accepted_origins.push(origin);
|
|
423
|
-
break;
|
|
424
|
-
default:
|
|
425
|
-
if (!out.unknown) out.unknown = [];
|
|
426
|
-
out.unknown.push({ id: id, bytes: valueBytes });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return out;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
function parse_tls_message(data) {
|
|
437
|
-
var view = new Uint8Array(data);
|
|
438
|
-
var type = view[0];
|
|
439
|
-
var length = (view[1] << 16) | (view[2] << 8) | view[3];
|
|
440
|
-
var body = new Uint8Array(view.buffer, view.byteOffset + 4, length);
|
|
441
|
-
return { type, length, body };
|
|
442
|
-
}
|
|
443
|
-
function parse_tls_client_hello2(body) {
|
|
444
|
-
var view = new Uint8Array(body);
|
|
445
|
-
var ptr = 0;
|
|
446
|
-
|
|
447
|
-
var legacy_version = (view[ptr++] << 8) | view[ptr++];
|
|
448
|
-
var random = view.slice(ptr, ptr + 32); ptr += 32;
|
|
449
|
-
var session_id_len = view[ptr++];
|
|
450
|
-
var session_id = view.slice(ptr, ptr + session_id_len); ptr += session_id_len;
|
|
451
|
-
|
|
452
|
-
var cipher_suites_len = (view[ptr++] << 8) | view[ptr++];
|
|
453
|
-
var cipher_suites = [];
|
|
454
|
-
for (var i = 0; i < cipher_suites_len; i += 2) {
|
|
455
|
-
var code = (view[ptr++] << 8) | view[ptr++];
|
|
456
|
-
cipher_suites.push(code);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
var compression_methods_len = view[ptr++];
|
|
460
|
-
var compression_methods = view.slice(ptr, ptr + compression_methods_len); ptr += compression_methods_len;
|
|
461
|
-
|
|
462
|
-
var extensions_len = (view[ptr++] << 8) | view[ptr++];
|
|
463
|
-
var extensions = [];
|
|
464
|
-
var ext_end = ptr + extensions_len;
|
|
465
|
-
while (ptr < ext_end) {
|
|
466
|
-
var ext_type = (view[ptr++] << 8) | view[ptr++];
|
|
467
|
-
var ext_len = (view[ptr++] << 8) | view[ptr++];
|
|
468
|
-
var ext_data = view.slice(ptr, ptr + ext_len); ptr += ext_len;
|
|
469
|
-
extensions.push({ type: ext_type, data: ext_data });
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
var sni = null;
|
|
473
|
-
var key_shares = [];
|
|
474
|
-
var supported_versions = [];
|
|
475
|
-
var supported_groups = [];
|
|
476
|
-
var signature_algorithms = [];
|
|
477
|
-
var alpn = [];
|
|
478
|
-
var max_fragment_length = null;
|
|
479
|
-
var padding = null;
|
|
480
|
-
var cookie = null;
|
|
481
|
-
var psk_key_exchange_modes = [];
|
|
482
|
-
var pre_shared_key = null;
|
|
483
|
-
var renegotiation_info = null;
|
|
484
|
-
var quic_transport_parameters = {
|
|
485
|
-
original: {},
|
|
486
|
-
initial_max_stream_data_bidi_local: undefined,
|
|
487
|
-
initial_max_data: undefined,
|
|
488
|
-
initial_max_streams_bidi: undefined,
|
|
489
|
-
idle_timeout: undefined,
|
|
490
|
-
max_packet_size: undefined,
|
|
491
|
-
ack_delay_exponent: undefined,
|
|
492
|
-
max_datagram_frame_size: undefined,
|
|
493
|
-
web_accepted_origins: undefined
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
for (var ext of extensions) {
|
|
497
|
-
var ext_view = new Uint8Array(ext.data);
|
|
498
|
-
if (ext.type === 0x00) {
|
|
499
|
-
var name_len = (ext_view[3] << 8) | ext_view[4];
|
|
500
|
-
sni = new TextDecoder().decode(ext_view.slice(5, 5 + name_len));
|
|
501
|
-
}
|
|
502
|
-
if (ext.type === 0x33) {
|
|
503
|
-
var ptr2 = 0;
|
|
504
|
-
var list_len = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
505
|
-
var end = ptr2 + list_len;
|
|
506
|
-
while (ptr2 < end) {
|
|
507
|
-
var group = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
508
|
-
var key_len = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
509
|
-
var pubkey = ext_view.slice(ptr2, ptr2 + key_len);
|
|
510
|
-
ptr2 += key_len;
|
|
511
|
-
key_shares.push({ group, pubkey });
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (ext.type === 0x2b) {
|
|
515
|
-
var len = ext_view[0];
|
|
516
|
-
for (var i = 1; i < 1 + len; i += 2) {
|
|
517
|
-
supported_versions.push((ext_view[i] << 8) | ext_view[i + 1]);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (ext.type === 0x0a) {
|
|
521
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
522
|
-
for (var i = 2; i < 2 + len; i += 2) {
|
|
523
|
-
supported_groups.push((ext_view[i] << 8) | ext_view[i + 1]);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (ext.type === 0x0d) {
|
|
527
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
528
|
-
for (var i = 2; i < 2 + len; i += 2) {
|
|
529
|
-
signature_algorithms.push((ext_view[i] << 8) | ext_view[i + 1]);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
if (ext.type === 0x10) {
|
|
533
|
-
var list_len = (ext_view[0] << 8) | ext_view[1];
|
|
534
|
-
var i = 2;
|
|
535
|
-
while (i < 2 + list_len) {
|
|
536
|
-
var name_len = ext_view[i++];
|
|
537
|
-
var proto = new TextDecoder().decode(ext_view.slice(i, i + name_len));
|
|
538
|
-
alpn.push(proto);
|
|
539
|
-
i += name_len;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
if (ext.type === 0x39) {
|
|
543
|
-
var ext_data = ext.data;
|
|
544
|
-
var ptr2 = 0;
|
|
545
|
-
while (ptr2 < ext_data.length) {
|
|
546
|
-
var idRes = readVarInt(ext_data, ptr2);
|
|
547
|
-
if (!idRes) break;
|
|
548
|
-
var id = idRes.value;
|
|
549
|
-
ptr2 += idRes.byteLength;
|
|
550
|
-
|
|
551
|
-
var lenRes = readVarInt(ext_data, ptr2);
|
|
552
|
-
if (!lenRes) break;
|
|
553
|
-
var len = lenRes.value;
|
|
554
|
-
ptr2 += lenRes.byteLength;
|
|
555
|
-
|
|
556
|
-
var value = ext_data.slice(ptr2, ptr2 + len);
|
|
557
|
-
ptr2 += len;
|
|
558
|
-
|
|
559
|
-
quic_transport_parameters.original[id] = value;
|
|
560
|
-
|
|
561
|
-
function toNumber(bytes) {
|
|
562
|
-
var n = 0;
|
|
563
|
-
for (var i = 0; i < bytes.length; i++) {
|
|
564
|
-
n = (n << 8) | bytes[i];
|
|
565
|
-
}
|
|
566
|
-
return n;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (id === 0x00) quic_transport_parameters.original_destination_connection_id = value;
|
|
570
|
-
if (id === 0x01) quic_transport_parameters.max_idle_timeout = toNumber(value);
|
|
571
|
-
if (id === 0x03) quic_transport_parameters.max_packet_size = toNumber(value);
|
|
572
|
-
if (id === 0x04) quic_transport_parameters.initial_max_data = toNumber(value);
|
|
573
|
-
if (id === 0x05) quic_transport_parameters.initial_max_stream_data_bidi_local = toNumber(value);
|
|
574
|
-
if (id === 0x08) quic_transport_parameters.initial_max_streams_bidi = toNumber(value);
|
|
575
|
-
if (id === 0x0a) quic_transport_parameters.ack_delay_exponent = toNumber(value);
|
|
576
|
-
if (id === 0x20) quic_transport_parameters.max_datagram_frame_size = toNumber(value);
|
|
577
|
-
if (id === 0x2b603742) {
|
|
578
|
-
try {
|
|
579
|
-
quic_transport_parameters.web_accepted_origins = new TextDecoder().decode(value);
|
|
580
|
-
} catch (e) {}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (ext.type === 0x01) max_fragment_length = ext_view[0];
|
|
585
|
-
if (ext.type === 0x15) padding = ext_view;
|
|
586
|
-
if (ext.type === 0x002a) {
|
|
587
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
588
|
-
cookie = ext_view.slice(2, 2 + len);
|
|
589
|
-
}
|
|
590
|
-
if (ext.type === 0x2d) {
|
|
591
|
-
var len = ext_view[0];
|
|
592
|
-
for (var i = 1; i <= len; i++) {
|
|
593
|
-
psk_key_exchange_modes.push(ext_view[i]);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (ext.type === 0x29) pre_shared_key = ext_view;
|
|
597
|
-
if (ext.type === 0xff01) renegotiation_info = ext_view;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return {
|
|
601
|
-
type: 'client_hello',
|
|
602
|
-
legacy_version,
|
|
603
|
-
random,
|
|
604
|
-
session_id,
|
|
605
|
-
cipher_suites,
|
|
606
|
-
compression_methods,
|
|
607
|
-
extensions,
|
|
608
|
-
sni,
|
|
609
|
-
key_shares,
|
|
610
|
-
supported_versions,
|
|
611
|
-
supported_groups,
|
|
612
|
-
signature_algorithms,
|
|
613
|
-
alpn,
|
|
614
|
-
max_fragment_length,
|
|
615
|
-
padding,
|
|
616
|
-
cookie,
|
|
617
|
-
psk_key_exchange_modes,
|
|
618
|
-
pre_shared_key,
|
|
619
|
-
renegotiation_info,
|
|
620
|
-
quic_transport_parameters
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
function parse_tls_client_hello(body) {
|
|
627
|
-
var view = new Uint8Array(body);
|
|
628
|
-
var ptr = 0;
|
|
629
|
-
|
|
630
|
-
var legacy_version = (view[ptr++] << 8) | view[ptr++];
|
|
631
|
-
var random = view.slice(ptr, ptr + 32); ptr += 32;
|
|
632
|
-
var session_id_len = view[ptr++];
|
|
633
|
-
var session_id = view.slice(ptr, ptr + session_id_len); ptr += session_id_len;
|
|
634
|
-
|
|
635
|
-
var cipher_suites_len = (view[ptr++] << 8) | view[ptr++];
|
|
636
|
-
var cipher_suites = [];
|
|
637
|
-
for (var i = 0; i < cipher_suites_len; i += 2) {
|
|
638
|
-
var code = (view[ptr++] << 8) | view[ptr++];
|
|
639
|
-
cipher_suites.push(code);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
var compression_methods_len = view[ptr++];
|
|
643
|
-
var compression_methods = view.slice(ptr, ptr + compression_methods_len); ptr += compression_methods_len;
|
|
644
|
-
|
|
645
|
-
var extensions_len = (view[ptr++] << 8) | view[ptr++];
|
|
646
|
-
var extensions = [];
|
|
647
|
-
var ext_end = ptr + extensions_len;
|
|
648
|
-
while (ptr < ext_end) {
|
|
649
|
-
var ext_type = (view[ptr++] << 8) | view[ptr++];
|
|
650
|
-
var ext_len = (view[ptr++] << 8) | view[ptr++];
|
|
651
|
-
var ext_data = view.slice(ptr, ptr + ext_len); ptr += ext_len;
|
|
652
|
-
extensions.push({ type: ext_type, data: ext_data });
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
var sni = null;
|
|
656
|
-
var key_shares = [];
|
|
657
|
-
var supported_versions = [];
|
|
658
|
-
var supported_groups = [];
|
|
659
|
-
var signature_algorithms = [];
|
|
660
|
-
var alpn = [];
|
|
661
|
-
var max_fragment_length = null;
|
|
662
|
-
var padding = null;
|
|
663
|
-
var cookie = null;
|
|
664
|
-
var psk_key_exchange_modes = [];
|
|
665
|
-
var pre_shared_key = null;
|
|
666
|
-
var renegotiation_info = null;
|
|
667
|
-
var quic_transport_parameters_raw = null;
|
|
668
|
-
|
|
669
|
-
for (var ext of extensions) {
|
|
670
|
-
var ext_view = new Uint8Array(ext.data);
|
|
671
|
-
if (ext.type === 0x00) { // SNI
|
|
672
|
-
var list_len = (ext_view[0] << 8) | ext_view[1];
|
|
673
|
-
var name_type = ext_view[2];
|
|
674
|
-
var name_len = (ext_view[3] << 8) | ext_view[4];
|
|
675
|
-
var name = new TextDecoder().decode(ext_view.slice(5, 5 + name_len));
|
|
676
|
-
sni = name;
|
|
677
|
-
}
|
|
678
|
-
if (ext.type === 0x33) {
|
|
679
|
-
var ptr2 = 0;
|
|
680
|
-
var list_len = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
681
|
-
var end = ptr2 + list_len;
|
|
682
|
-
while (ptr2 < end) {
|
|
683
|
-
var group = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
684
|
-
var key_len = (ext_view[ptr2++] << 8) | ext_view[ptr2++];
|
|
685
|
-
var pubkey = ext_view.slice(ptr2, ptr2 + key_len);
|
|
686
|
-
ptr2 += key_len;
|
|
687
|
-
key_shares.push({ group, pubkey });
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
if (ext.type === 0x2b) { // supported_versions
|
|
692
|
-
var len = ext_view[0];
|
|
693
|
-
for (var i = 1; i < 1 + len; i += 2) {
|
|
694
|
-
var ver = (ext_view[i] << 8) | ext_view[i + 1];
|
|
695
|
-
supported_versions.push(ver);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
if (ext.type === 0x0a) { // supported_groups
|
|
699
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
700
|
-
for (var i = 2; i < 2 + len; i += 2) {
|
|
701
|
-
supported_groups.push((ext_view[i] << 8) | ext_view[i + 1]);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (ext.type === 0x0d) { // signature_algorithms
|
|
705
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
706
|
-
for (var i = 2; i < 2 + len; i += 2) {
|
|
707
|
-
signature_algorithms.push((ext_view[i] << 8) | ext_view[i + 1]);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
if (ext.type === 0x10) { // ALPN
|
|
711
|
-
var list_len = (ext_view[0] << 8) | ext_view[1];
|
|
712
|
-
var i = 2;
|
|
713
|
-
while (i < 2 + list_len) {
|
|
714
|
-
var name_len = ext_view[i++];
|
|
715
|
-
var proto = new TextDecoder().decode(ext_view.slice(i, i + name_len));
|
|
716
|
-
alpn.push(proto);
|
|
717
|
-
i += name_len;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
if (ext.type === 0x39) { // quic_transport_parameters
|
|
721
|
-
quic_transport_parameters_raw = ext.data;
|
|
722
|
-
}
|
|
723
|
-
if (ext.type === 0x01) { // Max Fragment Length
|
|
724
|
-
max_fragment_length = ext_view[0];
|
|
725
|
-
}
|
|
726
|
-
if (ext.type === 0x15) { // Padding
|
|
727
|
-
padding = ext_view;
|
|
728
|
-
}
|
|
729
|
-
if (ext.type === 0x002a) { // Cookie
|
|
730
|
-
var len = (ext_view[0] << 8) | ext_view[1];
|
|
731
|
-
cookie = ext_view.slice(2, 2 + len);
|
|
732
|
-
}
|
|
733
|
-
if (ext.type === 0x2d) { // PSK Key Exchange Modes
|
|
734
|
-
var len = ext_view[0];
|
|
735
|
-
for (var i = 1; i <= len; i++) {
|
|
736
|
-
psk_key_exchange_modes.push(ext_view[i]);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
if (ext.type === 0x29) { // PreSharedKey (placeholder)
|
|
740
|
-
pre_shared_key = ext_view;
|
|
741
|
-
}
|
|
742
|
-
if (ext.type === 0xff01) { // Renegotiation Info
|
|
743
|
-
renegotiation_info = ext_view;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
return {
|
|
748
|
-
type: 'client_hello',
|
|
749
|
-
legacy_version,
|
|
750
|
-
random,
|
|
751
|
-
session_id,
|
|
752
|
-
cipher_suites,
|
|
753
|
-
compression_methods,
|
|
754
|
-
extensions,
|
|
755
|
-
sni,
|
|
756
|
-
key_shares,
|
|
757
|
-
supported_versions,
|
|
758
|
-
supported_groups,
|
|
759
|
-
signature_algorithms,
|
|
760
|
-
alpn,
|
|
761
|
-
max_fragment_length,
|
|
762
|
-
padding,
|
|
763
|
-
cookie,
|
|
764
|
-
psk_key_exchange_modes,
|
|
765
|
-
pre_shared_key,
|
|
766
|
-
renegotiation_info,
|
|
767
|
-
quic_transport_parameters_raw
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
////////////////////////////////
|
|
772
|
-
|
|
773
|
-
function hmac(hash, key, data) {
|
|
774
|
-
return new Uint8Array(crypto.createHmac(hash, key).update(data).digest());
|
|
775
|
-
}
|
|
776
|
-
function hkdf_extract(salt, ikm, hash_func) {
|
|
777
|
-
return nobleHashes.hkdf_extract(hash_func, ikm, salt);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
function hkdf_expand(prk, info, length, hash_func) {
|
|
782
|
-
return nobleHashes.hkdf_expand(hash_func, prk, info, length);
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
function build_hkdf_label(label, context, length) {
|
|
788
|
-
const prefix = "tls13 ";
|
|
789
|
-
const full = new TextEncoder().encode(prefix + label);
|
|
790
|
-
|
|
791
|
-
const info = new Uint8Array(
|
|
792
|
-
2 + 1 + full.length + 1 + context.length);
|
|
793
|
-
|
|
794
|
-
// length (2-bytes, BE)
|
|
795
|
-
info[0] = (length >> 8) & 0xff;
|
|
796
|
-
info[1] = length & 0xff;
|
|
797
|
-
|
|
798
|
-
// label length + bytes
|
|
799
|
-
info[2] = full.length;
|
|
800
|
-
info.set(full, 3);
|
|
801
|
-
|
|
802
|
-
// context length + bytes
|
|
803
|
-
const ctxOfs = 3 + full.length;
|
|
804
|
-
info[ctxOfs] = context.length;
|
|
805
|
-
info.set(context, ctxOfs + 1);
|
|
806
|
-
|
|
807
|
-
return info;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function hkdf_expand_label(secret, label, context, length, hash_func) {
|
|
811
|
-
const info = build_hkdf_label(label, context, length);
|
|
812
|
-
return hkdf_expand(secret, info, length, hash_func); // hash = sha384/sha256
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
function hash_transcript(messages,hash_func) {
|
|
817
|
-
var total_len = messages.reduce((sum, m) => sum + m.length, 0);
|
|
818
|
-
var total = new Uint8Array(total_len);
|
|
819
|
-
var offset = 0;
|
|
820
|
-
for (var m of messages) {
|
|
821
|
-
total.set(m, offset);
|
|
822
|
-
offset += m.length;
|
|
823
|
-
}
|
|
824
|
-
return hash_func(total);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function tls_derive_app_secrets(handshake_secret, transcript, hash_func) {
|
|
828
|
-
const hashLen = hash_func.outputLen;
|
|
829
|
-
const empty = new Uint8Array(0);
|
|
830
|
-
var zero = new Uint8Array(hash_func.outputLen);
|
|
831
|
-
|
|
832
|
-
var derived_secret = hkdf_expand_label(handshake_secret, "derived", hash_func(empty), hash_func.outputLen, hash_func);
|
|
833
|
-
var master_secret = hkdf_extract(derived_secret, zero, hash_func);
|
|
834
|
-
|
|
835
|
-
// שלב 3: חישוב hash של ה־transcript עד server Finished
|
|
836
|
-
const transcript_hash = hash_transcript(transcript, hash_func);
|
|
837
|
-
|
|
838
|
-
// שלב 4: גזירת סודות התעבורה
|
|
839
|
-
const client_app = hkdf_expand_label(master_secret, 'c ap traffic', transcript_hash, hashLen, hash_func);
|
|
840
|
-
const server_app = hkdf_expand_label(master_secret, 's ap traffic', transcript_hash, hashLen, hash_func);
|
|
841
|
-
|
|
842
|
-
return {
|
|
843
|
-
client_application_traffic_secret: client_app,
|
|
844
|
-
server_application_traffic_secret: server_app
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
function tls_derive_handshake_secrets(shared_secret, transcript, hash_func) {
|
|
850
|
-
var zero = new Uint8Array(hash_func.outputLen);
|
|
851
|
-
var empty = new Uint8Array();
|
|
852
|
-
|
|
853
|
-
var early_secret = hkdf_extract(empty, zero, hash_func); // salt, ikm
|
|
854
|
-
var derived_secret = hkdf_expand_label(early_secret, "derived", hash_func(empty), hash_func.outputLen, hash_func);
|
|
855
|
-
var handshake_secret = hkdf_extract(derived_secret, shared_secret, hash_func);
|
|
856
|
-
|
|
857
|
-
var transcript_hash = hash_transcript(transcript, hash_func);
|
|
858
|
-
|
|
859
|
-
var client_hts = hkdf_expand_label(handshake_secret, "c hs traffic", transcript_hash, hash_func.outputLen, hash_func);
|
|
860
|
-
var server_hts = hkdf_expand_label(handshake_secret, "s hs traffic", transcript_hash, hash_func.outputLen, hash_func);
|
|
861
|
-
|
|
862
|
-
return {
|
|
863
|
-
handshake_secret,
|
|
864
|
-
client_handshake_traffic_secret: client_hts,
|
|
865
|
-
server_handshake_traffic_secret: server_hts,
|
|
866
|
-
transcript_hash
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
function aead_decrypt(key, iv, packetNumber, ciphertextWithTag, aad, callback) {
|
|
871
|
-
try {
|
|
872
|
-
// יצירת nonce לפי QUIC (IV XOR packetNumber)
|
|
873
|
-
var nonce = new Uint8Array(iv.length);
|
|
874
|
-
for (var i = 0; i < iv.length; i++) {
|
|
875
|
-
var pnIndex = iv.length - 1 - i;
|
|
876
|
-
var pnByte = (packetNumber >>> (8 * i)) & 0xff;
|
|
877
|
-
nonce[pnIndex] = iv[pnIndex] ^ pnByte;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
var tag = ciphertextWithTag.slice(-16);
|
|
881
|
-
var ciphertext = ciphertextWithTag.slice(0, -16);
|
|
882
|
-
|
|
883
|
-
var algo = key.length === 32 ? 'aes-256-gcm' :
|
|
884
|
-
key.length === 16 ? 'aes-128-gcm' :
|
|
885
|
-
(() => { throw new Error("Unsupported key length: " + key.length); })();
|
|
886
|
-
|
|
887
|
-
const decipher = crypto.createDecipheriv(algo, key, nonce);
|
|
888
|
-
decipher.setAuthTag(tag);
|
|
889
|
-
decipher.setAAD(aad);
|
|
890
|
-
|
|
891
|
-
const decrypted = decipher.update(ciphertext);
|
|
892
|
-
decipher.final();
|
|
893
|
-
|
|
894
|
-
callback(null, decrypted);
|
|
895
|
-
} catch (e) {
|
|
896
|
-
callback(e);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function aes_gcm_decrypt(ciphertext, tag, key, nonce, aad) {
|
|
901
|
-
try {
|
|
902
|
-
var algo = key.length === 32 ? 'aes-256-gcm' :
|
|
903
|
-
key.length === 16 ? 'aes-128-gcm' :
|
|
904
|
-
(() => { throw new Error("Unsupported key length: " + key.length); })();
|
|
905
|
-
|
|
906
|
-
var decipher = crypto.createDecipheriv(
|
|
907
|
-
algo,
|
|
908
|
-
Buffer.from(key),
|
|
909
|
-
Buffer.from(nonce)
|
|
910
|
-
);
|
|
911
|
-
|
|
912
|
-
decipher.setAuthTag(Buffer.from(tag));
|
|
913
|
-
decipher.setAAD(Buffer.from(aad));
|
|
914
|
-
|
|
915
|
-
var decrypted = Buffer.concat([
|
|
916
|
-
decipher.update(Buffer.from(ciphertext)),
|
|
917
|
-
decipher.final()
|
|
918
|
-
]);
|
|
919
|
-
|
|
920
|
-
//console.log("✅ Decryption success!");
|
|
921
|
-
return new Uint8Array(decrypted);
|
|
922
|
-
} catch (e) {
|
|
923
|
-
return null;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
const INITIAL_SALTS = {
|
|
928
|
-
// QUIC v1 (RFC 9001)
|
|
929
|
-
0x00000001: new Uint8Array([
|
|
930
|
-
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3,
|
|
931
|
-
0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
|
|
932
|
-
0xcc, 0xbb, 0x7f, 0x0a
|
|
933
|
-
]),
|
|
934
|
-
|
|
935
|
-
// QUIC draft-29 (HTTP/3 version h3-29)
|
|
936
|
-
0xff00001d: new Uint8Array([
|
|
937
|
-
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c,
|
|
938
|
-
0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0,
|
|
939
|
-
0x43, 0x90, 0xa8, 0x99
|
|
940
|
-
]),
|
|
941
|
-
|
|
942
|
-
// QUIC draft-32 (h3-32)
|
|
943
|
-
0xff000020: new Uint8Array([
|
|
944
|
-
0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0x77,
|
|
945
|
-
0x7b, 0xe3, 0x0e, 0xbd, 0x5f, 0xa5, 0x15, 0x87,
|
|
946
|
-
0x3d, 0x8d, 0x6e, 0x67
|
|
947
|
-
]),
|
|
948
|
-
|
|
949
|
-
// Google QUIC v50 ("Q050") — נדיר יותר אבל נתמך בדפדפנים מסוימים
|
|
950
|
-
0x51303530: new Uint8Array([
|
|
951
|
-
0x69, 0x45, 0x6f, 0xbe, 0xf1, 0x6e, 0xd7, 0xdc,
|
|
952
|
-
0x48, 0x15, 0x9d, 0x98, 0xd0, 0x7f, 0x5c, 0x3c,
|
|
953
|
-
0x3d, 0x5a, 0xa7, 0x0a
|
|
954
|
-
])
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
function quic_derive_init_secrets(client_dcid, version, direction) {
|
|
958
|
-
const hash_func = sha256;
|
|
959
|
-
//console.log(version);
|
|
960
|
-
const salt = INITIAL_SALTS[version] || null;
|
|
961
|
-
if (!salt) throw new Error("Unsupported QUIC version: 0x" + version.toString(16));
|
|
962
|
-
|
|
963
|
-
const label = direction === 'read' ? 'client in' : 'server in';
|
|
964
|
-
const initial_secret = hkdf_extract(salt, client_dcid, hash_func);
|
|
965
|
-
|
|
966
|
-
const initial_secret2 = hkdf_expand_label(
|
|
967
|
-
initial_secret,
|
|
968
|
-
label,
|
|
969
|
-
new Uint8Array(0),
|
|
970
|
-
32,
|
|
971
|
-
hash_func
|
|
972
|
-
);
|
|
973
|
-
|
|
974
|
-
const key = hkdf_expand_label(initial_secret2, 'quic key', new Uint8Array(0), 16, hash_func); // AES-128-GCM
|
|
975
|
-
const iv = hkdf_expand_label(initial_secret2, 'quic iv', new Uint8Array(0), 12, hash_func);
|
|
976
|
-
const hp = hkdf_expand_label(initial_secret2, 'quic hp', new Uint8Array(0), 16, hash_func);
|
|
977
|
-
|
|
978
|
-
return { key, iv, hp };
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
function quic_derive_from_tls_secrets(traffic_secret, hash_func = sha256) {
|
|
983
|
-
if(traffic_secret){
|
|
984
|
-
const key = hkdf_expand_label(traffic_secret, 'quic key', new Uint8Array(0), 16, hash_func);
|
|
985
|
-
const iv = hkdf_expand_label(traffic_secret, 'quic iv', new Uint8Array(0), 12, hash_func);
|
|
986
|
-
const hp = hkdf_expand_label(traffic_secret, 'quic hp', new Uint8Array(0), 16, hash_func);
|
|
987
|
-
|
|
988
|
-
return { key, iv, hp };
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
function compute_nonce(iv, packetNumber) {
|
|
995
|
-
const nonce = new Uint8Array(iv); // עותק של ה־IV המקורי (12 בתים)
|
|
996
|
-
const pnBuffer = new Uint8Array(12); // 12 בתים, מיושר לימין
|
|
997
|
-
|
|
998
|
-
// הכנס את packetNumber לימין של pnBuffer
|
|
999
|
-
let n = packetNumber;
|
|
1000
|
-
for (let i = 11; n > 0 && i >= 0; i--) {
|
|
1001
|
-
pnBuffer[i] = n & 0xff;
|
|
1002
|
-
n >>= 8;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// בצע XOR בין ה־IV לבין pnBuffer
|
|
1006
|
-
for (let i = 0; i < 12; i++) {
|
|
1007
|
-
nonce[i] ^= pnBuffer[i];
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
return nonce;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
function aes_ecb_encrypt(keyBytes, plaintext) {
|
|
1015
|
-
if (keyBytes.length !== 16 && keyBytes.length !== 24 && keyBytes.length !== 32) {
|
|
1016
|
-
throw new Error("Invalid AES key size");
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
if (plaintext.length % 16 !== 0) {
|
|
1020
|
-
throw new Error("Plaintext must be a multiple of 16 bytes");
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
const cipher = crypto.createCipheriv('aes-' + (keyBytes.length * 8) + '-ecb', keyBytes, null);
|
|
1024
|
-
cipher.setAutoPadding(false);
|
|
1025
|
-
|
|
1026
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1027
|
-
return new Uint8Array(encrypted);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
function aead_encrypt(key, iv, packetNumber, plaintext, aad) {
|
|
1032
|
-
try {
|
|
1033
|
-
const algo = key.length === 32 ? 'aes-256-gcm' :
|
|
1034
|
-
key.length === 16 ? 'aes-128-gcm' :
|
|
1035
|
-
(() => { throw new Error("Unsupported key length: " + key.length); })();
|
|
1036
|
-
|
|
1037
|
-
const nonce = compute_nonce(iv, packetNumber);
|
|
1038
|
-
|
|
1039
|
-
const cipher = crypto.createCipheriv(algo, Buffer.from(key), Buffer.from(nonce));
|
|
1040
|
-
cipher.setAAD(Buffer.from(aad));
|
|
1041
|
-
|
|
1042
|
-
const encrypted = Buffer.concat([
|
|
1043
|
-
cipher.update(Buffer.from(plaintext)),
|
|
1044
|
-
cipher.final()
|
|
1045
|
-
]);
|
|
1046
|
-
const tag = cipher.getAuthTag();
|
|
1047
|
-
|
|
1048
|
-
const result = new Uint8Array(encrypted.length + tag.length);
|
|
1049
|
-
result.set(encrypted, 0);
|
|
1050
|
-
result.set(tag, encrypted.length);
|
|
1051
|
-
|
|
1052
|
-
return result;
|
|
1053
|
-
|
|
1054
|
-
} catch (e) {
|
|
1055
|
-
return null;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
function apply_header_protection(packet, pnOffset, hpKey, pnLength) {
|
|
1063
|
-
var sample = packet.slice(pnOffset + 4, pnOffset + 4 + 16);
|
|
1064
|
-
if (sample.length < 16) throw new Error("Not enough bytes for header protection sample");
|
|
1065
|
-
|
|
1066
|
-
var maskFull = aes_ecb_encrypt(hpKey, sample);
|
|
1067
|
-
var mask = maskFull.slice(0, 5);
|
|
1068
|
-
|
|
1069
|
-
var firstByte = packet[0];
|
|
1070
|
-
var isLongHeader = (firstByte & 0x80) !== 0;
|
|
1071
|
-
|
|
1072
|
-
if (isLongHeader) {
|
|
1073
|
-
packet[0] ^= (mask[0] & 0x0f); // רק 4 ביטים אחרונים
|
|
1074
|
-
} else {
|
|
1075
|
-
packet[0] ^= (mask[0] & 0x1f); // ל־Short Header
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
for (var i = 0; i < pnLength; i++) {
|
|
1079
|
-
packet[pnOffset + i] ^= mask[1 + i];
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
return packet;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
function aes128ecb(sample,hpKey) {
|
|
1089
|
-
const cipher = crypto.createCipheriv('aes-128-ecb', Buffer.from(hpKey), null);
|
|
1090
|
-
cipher.setAutoPadding(false);
|
|
1091
|
-
const input = Buffer.from(sample);
|
|
1092
|
-
return new Uint8Array(Buffer.concat([cipher.update(input), cipher.final()]));
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function expandPacketNumber(truncated, pnLen, largestReceived) {
|
|
1096
|
-
var pnWin = 1 << (pnLen * 8);
|
|
1097
|
-
var pnHalf = pnWin >>> 1;
|
|
1098
|
-
var expected = largestReceived + 1;
|
|
1099
|
-
return truncated + pnWin * Math.floor((expected - truncated + pnHalf) / pnWin);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
function decode_packet_number(array, offset, pnLength) {
|
|
1103
|
-
let value = 0;
|
|
1104
|
-
for (let i = 0; i < pnLength; i++) {
|
|
1105
|
-
value = (value << 8) | array[offset + i];
|
|
1106
|
-
}
|
|
1107
|
-
return value;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
function decode_and_expand_packet_number(array, offset, pnLength, largestReceived) {
|
|
1111
|
-
var truncated = decode_packet_number(array, offset, pnLength);
|
|
1112
|
-
return expandPacketNumber(truncated, pnLength, largestReceived);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
function remove_header_protection(array, pnOffset, hpKey, isShort) {
|
|
1116
|
-
// Step 1: קח sample של 16 בתים מתוך ה־payload אחרי pnOffset + 4
|
|
1117
|
-
var sampleOffset = pnOffset + 4;
|
|
1118
|
-
var sample = array.slice(sampleOffset, sampleOffset + 16);
|
|
1119
|
-
|
|
1120
|
-
var mask = aes128ecb(sample, hpKey).slice(0, 5); // ECB with no IV
|
|
1121
|
-
|
|
1122
|
-
// Step 2: הסר הגנה מה־first byte
|
|
1123
|
-
var firstByte = array[0];
|
|
1124
|
-
|
|
1125
|
-
if (isShort) {
|
|
1126
|
-
// Short Header: רק 5 הביטים הנמוכים מוצפנים
|
|
1127
|
-
array[0] ^= mask[0] & 0x1f;
|
|
1128
|
-
} else {
|
|
1129
|
-
// Long Header: רק 4 הביטים הנמוכים מוצפנים
|
|
1130
|
-
array[0] ^= mask[0] & 0x0f;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
// Step 3: הסר הגנה מה־packet number (pnLength נקבע מתוך הביטים עכשיו)
|
|
1134
|
-
var pnLength = (array[0] & 0x03) + 1;
|
|
1135
|
-
|
|
1136
|
-
for (var i = 0; i < pnLength; i++) {
|
|
1137
|
-
array[pnOffset + i] ^= mask[1 + i];
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
return pnLength;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
function decrypt_quic_packet(array, read_key, read_iv, read_hp, dcid, largest_pn) {
|
|
1145
|
-
if (!(array instanceof Uint8Array)) throw new Error("Invalid input");
|
|
1146
|
-
|
|
1147
|
-
const firstByte = array[0];
|
|
1148
|
-
const isShort = (firstByte & 0x80) === 0;
|
|
1149
|
-
const isLong = !isShort;
|
|
1150
|
-
|
|
1151
|
-
let keyPhase = false;
|
|
1152
|
-
let pnOffset = 0;
|
|
1153
|
-
let pnLength = 0;
|
|
1154
|
-
let aad = null;
|
|
1155
|
-
let ciphertext = null;
|
|
1156
|
-
let tag = null;
|
|
1157
|
-
let packetNumber = null;
|
|
1158
|
-
let nonce = null;
|
|
1159
|
-
|
|
1160
|
-
if (isLong) {
|
|
1161
|
-
// ---------- ניתוח Long Header ----------
|
|
1162
|
-
const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
1163
|
-
const version = view.getUint32(1);
|
|
1164
|
-
const dcidLen = array[5];
|
|
1165
|
-
|
|
1166
|
-
let offset = 6;
|
|
1167
|
-
const parsed_dcid = array.slice(offset, offset + dcidLen);
|
|
1168
|
-
offset += dcidLen;
|
|
1169
|
-
|
|
1170
|
-
const scidLen = array[offset++];
|
|
1171
|
-
const scid = array.slice(offset, offset + scidLen);
|
|
1172
|
-
offset += scidLen;
|
|
1173
|
-
|
|
1174
|
-
const typeBits = (firstByte & 0x30) >> 4;
|
|
1175
|
-
const typeMap = ['initial', '0rtt', 'handshake', 'retry'];
|
|
1176
|
-
const packetType = typeMap[typeBits];
|
|
1177
|
-
|
|
1178
|
-
if (packetType === 'initial') {
|
|
1179
|
-
const tokenLen = readVarInt(array, offset);
|
|
1180
|
-
offset += tokenLen.byteLength + tokenLen.value;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
const len = readVarInt(array, offset);
|
|
1184
|
-
offset += len.byteLength;
|
|
1185
|
-
|
|
1186
|
-
pnOffset = offset;
|
|
1187
|
-
|
|
1188
|
-
// הסרת הגנת כותרת
|
|
1189
|
-
pnLength = remove_header_protection(array, pnOffset, read_hp, false);
|
|
1190
|
-
|
|
1191
|
-
if(pnLength!==null){
|
|
1192
|
-
packetNumber = decode_and_expand_packet_number(array, pnOffset, pnLength, largest_pn);
|
|
1193
|
-
nonce = compute_nonce(read_iv, packetNumber);
|
|
1194
|
-
|
|
1195
|
-
const payloadStart = pnOffset + pnLength;
|
|
1196
|
-
const payloadLength = len.value - pnLength;
|
|
1197
|
-
const payloadEnd = payloadStart + payloadLength;
|
|
1198
|
-
|
|
1199
|
-
if (payloadEnd > array.length) throw new Error("Truncated long header packet");
|
|
1200
|
-
|
|
1201
|
-
const payload = array.slice(payloadStart, payloadEnd);
|
|
1202
|
-
if (payload.length < 16) throw new Error("Encrypted payload too short");
|
|
1203
|
-
|
|
1204
|
-
ciphertext = payload.slice(0, payload.length - 16);
|
|
1205
|
-
tag = payload.slice(payload.length - 16);
|
|
1206
|
-
aad = array.slice(0, pnOffset + pnLength);
|
|
1207
|
-
}else{
|
|
1208
|
-
return null;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
} else {
|
|
1212
|
-
// ---------- ניתוח Short Header ----------
|
|
1213
|
-
// פורמט: 1 byte header + DCID + Packet Number + Payload
|
|
1214
|
-
|
|
1215
|
-
const dcidLen = dcid.length;
|
|
1216
|
-
pnOffset = 1 + dcidLen;
|
|
1217
|
-
|
|
1218
|
-
// הסרת הגנת כותרת
|
|
1219
|
-
pnLength = remove_header_protection(array, pnOffset, read_hp, true);
|
|
1220
|
-
|
|
1221
|
-
if(pnLength!==null){
|
|
1222
|
-
keyPhase = Boolean((array[0] & 0x04) >>> 2);
|
|
1223
|
-
|
|
1224
|
-
packetNumber = decode_and_expand_packet_number(array, pnOffset, pnLength, largest_pn);
|
|
1225
|
-
nonce = compute_nonce(read_iv, packetNumber);
|
|
1226
|
-
|
|
1227
|
-
const payloadStart = pnOffset + pnLength;
|
|
1228
|
-
const payload = array.slice(payloadStart);
|
|
1229
|
-
if (payload.length < 16) throw new Error("Encrypted payload too short");
|
|
1230
|
-
|
|
1231
|
-
ciphertext = payload.slice(0, payload.length - 16);
|
|
1232
|
-
tag = payload.slice(payload.length - 16);
|
|
1233
|
-
aad = array.slice(0, pnOffset + pnLength);
|
|
1234
|
-
}else{
|
|
1235
|
-
return null;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
const plaintext = aes_gcm_decrypt(ciphertext, tag, read_key, nonce, aad);
|
|
1241
|
-
|
|
1242
|
-
return {
|
|
1243
|
-
packet_number: packetNumber,
|
|
1244
|
-
key_phase: keyPhase,
|
|
1245
|
-
plaintext
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
function extract_tls_messages_from_chunks(chunks, from_offset) {
|
|
1252
|
-
var offset = from_offset;
|
|
1253
|
-
var buffers = [];
|
|
1254
|
-
|
|
1255
|
-
// מאחדים רצף שלם של chunks מה־offset הנוכחי
|
|
1256
|
-
while (chunks[offset]) {
|
|
1257
|
-
buffers.push(chunks[offset]);
|
|
1258
|
-
offset += chunks[offset].length;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
// אם לא קיבלנו שום דבר – נחזיר ריק
|
|
1262
|
-
if (buffers.length === 0) return [];
|
|
1263
|
-
|
|
1264
|
-
var combined = concatUint8Arrays(buffers);
|
|
1265
|
-
var tls_messages = [];
|
|
1266
|
-
var i = 0;
|
|
1267
|
-
|
|
1268
|
-
while (i + 4 <= combined.length) {
|
|
1269
|
-
var msgType = combined[i];
|
|
1270
|
-
var length = (combined[i + 1] << 16) | (combined[i + 2] << 8) | combined[i + 3];
|
|
1271
|
-
|
|
1272
|
-
if (i + 4 + length > combined.length) break; // הודעה לא שלמה – עוצרים
|
|
1273
|
-
|
|
1274
|
-
var msg = combined.slice(i, i + 4 + length);
|
|
1275
|
-
tls_messages.push(msg);
|
|
1276
|
-
i += 4 + length;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// עדכון offset רק עד איפה שעברנו בפועל
|
|
1280
|
-
if (i > 0) {
|
|
1281
|
-
// מוחקים את החלקים המאוחדים מתוך chunks
|
|
1282
|
-
var cleanupOffset = from_offset;
|
|
1283
|
-
while (cleanupOffset < from_offset + i) {
|
|
1284
|
-
var chunk = chunks[cleanupOffset];
|
|
1285
|
-
delete chunks[cleanupOffset];
|
|
1286
|
-
cleanupOffset += chunk.length;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// השארית – אם קיימת – נחזיר אותה כ־chunk חדש
|
|
1290
|
-
if (i < combined.length) {
|
|
1291
|
-
var leftover = combined.slice(i);
|
|
1292
|
-
chunks[cleanupOffset] = leftover;
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// נעדכן את currentOffset
|
|
1296
|
-
from_offset += i;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
return {tls_messages,new_from_offset: from_offset};
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
function encode_version(version) {
|
|
1305
|
-
return new Uint8Array([
|
|
1306
|
-
(version >>> 24) & 0xff,
|
|
1307
|
-
(version >>> 16) & 0xff,
|
|
1308
|
-
(version >>> 8) & 0xff,
|
|
1309
|
-
version & 0xff
|
|
1310
|
-
]);
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
function build_quic_header(packetType, dcid, scid, token, lengthField, pnLen) {
|
|
1314
|
-
var hdr = [];
|
|
1315
|
-
var firstByte;
|
|
1316
|
-
|
|
1317
|
-
// שלב 1: הגדרת הביט הראשון לפי סוג הפאקט
|
|
1318
|
-
if (packetType === 'initial') {
|
|
1319
|
-
firstByte = 0xC0 | ((pnLen - 1) & 0x03); // Long Header, Initial
|
|
1320
|
-
} else if (packetType === 'handshake') {
|
|
1321
|
-
firstByte = 0xE0 | ((pnLen - 1) & 0x03); // Long Header, Handshake
|
|
1322
|
-
} else if (packetType === '0rtt') {
|
|
1323
|
-
firstByte = 0xD0 | ((pnLen - 1) & 0x03); // Long Header, 0-RTT
|
|
1324
|
-
} else if (packetType === '1rtt') {
|
|
1325
|
-
firstByte = 0x40 | ((pnLen - 1) & 0x03); // Short Header
|
|
1326
|
-
hdr.push(Uint8Array.of(firstByte));
|
|
1327
|
-
hdr.push(dcid); // ב־short header, זהו ה־Destination CID בלבד
|
|
1328
|
-
return {
|
|
1329
|
-
header: concatUint8Arrays(hdr),
|
|
1330
|
-
packetNumberOffset: hdr.reduce((sum, u8) => sum + u8.length, 0)
|
|
1331
|
-
};
|
|
1332
|
-
} else {
|
|
1333
|
-
throw new Error('Unsupported packet type: ' + packetType);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// שלב 2: Header בסיסי לכל long header
|
|
1337
|
-
hdr.push(Uint8Array.of(firstByte));
|
|
1338
|
-
hdr.push(encode_version(0x00000001)); // גרסה (4 בייטים)
|
|
1339
|
-
hdr.push(writeVarInt(dcid.length), dcid);
|
|
1340
|
-
hdr.push(writeVarInt(scid.length), scid);
|
|
1341
|
-
|
|
1342
|
-
// שלב 3: רק ל־Initial מוסיפים טוקן
|
|
1343
|
-
if (packetType === 'initial') {
|
|
1344
|
-
if (!token) token = new Uint8Array(0);
|
|
1345
|
-
hdr.push(writeVarInt(token.length), token);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// שלב 4: שדה אורך (Length), חובה
|
|
1349
|
-
hdr.push(lengthField);
|
|
1350
|
-
|
|
1351
|
-
// שלב 5: חישוב נקודת התחלה של packet number (מופיע מיד לאחר header)
|
|
1352
|
-
var header = concatUint8Arrays(hdr);
|
|
1353
|
-
return {
|
|
1354
|
-
header: header,
|
|
1355
|
-
packetNumberOffset: header.length
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
function encrypt_quic_packet(packetType, encodedFrames, writeKey, writeIv, writeHp, packetNumber, dcid, scid, token) {
|
|
1362
|
-
var pnLength;
|
|
1363
|
-
if (packetNumber <= 0xff) pnLength = 1;
|
|
1364
|
-
else if (packetNumber <= 0xffff) pnLength = 2;
|
|
1365
|
-
else if (packetNumber <= 0xffffff) pnLength = 3;
|
|
1366
|
-
else pnLength = 4;
|
|
1367
|
-
|
|
1368
|
-
var pnFull = new Uint8Array(4);
|
|
1369
|
-
pnFull[0] = (packetNumber >>> 24) & 0xff;
|
|
1370
|
-
pnFull[1] = (packetNumber >>> 16) & 0xff;
|
|
1371
|
-
pnFull[2] = (packetNumber >>> 8) & 0xff;
|
|
1372
|
-
pnFull[3] = packetNumber & 0xff;
|
|
1373
|
-
var packetNumberField = pnFull.slice(4 - pnLength);
|
|
1374
|
-
|
|
1375
|
-
var unprotectedPayloadLength = encodedFrames.length + pnLength + 16;
|
|
1376
|
-
var lengthField = writeVarInt(unprotectedPayloadLength);
|
|
1377
|
-
var headerInfo = build_quic_header(packetType, dcid, scid, token, lengthField, pnLength);
|
|
1378
|
-
|
|
1379
|
-
var header = headerInfo.header;
|
|
1380
|
-
var packetNumberOffset = headerInfo.packetNumberOffset;
|
|
1381
|
-
|
|
1382
|
-
// בונים AAD
|
|
1383
|
-
var fullHeader = concatUint8Arrays([header, packetNumberField]);
|
|
1384
|
-
|
|
1385
|
-
// ✨ הוספת padding אם צריך כדי לאפשר sample
|
|
1386
|
-
var minSampleLength = 32; // או 32 ל־ChaCha20
|
|
1387
|
-
var minTotalLength = packetNumberOffset + pnLength + minSampleLength;
|
|
1388
|
-
var fullLength = header.length + pnLength + encodedFrames.length + 16; // 16 = GCM tag
|
|
1389
|
-
|
|
1390
|
-
if (fullLength < minTotalLength) {
|
|
1391
|
-
var extraPadding = minTotalLength - (header.length + pnLength + encodedFrames.length);
|
|
1392
|
-
var padded = new Uint8Array(encodedFrames.length + extraPadding);
|
|
1393
|
-
padded.set(encodedFrames, 0);
|
|
1394
|
-
encodedFrames = padded;
|
|
1395
|
-
// חשוב! גם unprotectedPayloadLength צריך להתעדכן
|
|
1396
|
-
unprotectedPayloadLength = encodedFrames.length + pnLength + 16;
|
|
1397
|
-
lengthField = writeVarInt(unprotectedPayloadLength);
|
|
1398
|
-
headerInfo = build_quic_header(packetType, dcid, scid, token, lengthField, pnLength);
|
|
1399
|
-
header = headerInfo.header;
|
|
1400
|
-
packetNumberOffset = headerInfo.packetNumberOffset;
|
|
1401
|
-
fullHeader = concatUint8Arrays([header, packetNumberField]);
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
var ciphertext = aead_encrypt(writeKey, writeIv, packetNumber, encodedFrames, fullHeader);
|
|
1405
|
-
if (ciphertext == null) return null;
|
|
1406
|
-
|
|
1407
|
-
var fullPacket = concatUint8Arrays([
|
|
1408
|
-
header,
|
|
1409
|
-
packetNumberField,
|
|
1410
|
-
ciphertext
|
|
1411
|
-
]);
|
|
1412
|
-
|
|
1413
|
-
return apply_header_protection(fullPacket, packetNumberOffset, writeHp, pnLength);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
function encrypt_quic_packet2(packetType, encodedFrames, writeKey, writeIv, writeHp, packetNumber, dcid, scid, token) {
|
|
1417
|
-
|
|
1418
|
-
// 2. קביעת אורך packet number
|
|
1419
|
-
var pnLength;
|
|
1420
|
-
if (packetNumber <= 0xff) pnLength = 1;
|
|
1421
|
-
else if (packetNumber <= 0xffff) pnLength = 2;
|
|
1422
|
-
else if (packetNumber <= 0xffffff) pnLength = 3;
|
|
1423
|
-
else pnLength = 4;
|
|
1424
|
-
|
|
1425
|
-
// 3. חיתוך שדה ה־packet number לבתים
|
|
1426
|
-
var pnFull = new Uint8Array(4);
|
|
1427
|
-
pnFull[0] = (packetNumber >>> 24) & 0xff;
|
|
1428
|
-
pnFull[1] = (packetNumber >>> 16) & 0xff;
|
|
1429
|
-
pnFull[2] = (packetNumber >>> 8) & 0xff;
|
|
1430
|
-
pnFull[3] = packetNumber & 0xff;
|
|
1431
|
-
var packetNumberField = pnFull.slice(4 - pnLength);
|
|
1432
|
-
|
|
1433
|
-
// 4. נבנה header בלי packet number
|
|
1434
|
-
|
|
1435
|
-
var unprotectedPayloadLength = encodedFrames.length + pnLength + 16;
|
|
1436
|
-
var lengthField = writeVarInt(unprotectedPayloadLength);
|
|
1437
|
-
var headerInfo = build_quic_header(packetType, dcid, scid, token, lengthField, pnLength);
|
|
1438
|
-
|
|
1439
|
-
var header = headerInfo.header; // עד לפני packet number
|
|
1440
|
-
var packetNumberOffset = headerInfo.packetNumberOffset;
|
|
1441
|
-
|
|
1442
|
-
// 5. AAD כולל את header + packet number (לפני ההצפנה)
|
|
1443
|
-
var fullHeader = concatUint8Arrays([header, packetNumberField]);
|
|
1444
|
-
|
|
1445
|
-
// 6. הצפנת המטען
|
|
1446
|
-
var ciphertext = aead_encrypt(writeKey, writeIv, packetNumber, encodedFrames, fullHeader);
|
|
1447
|
-
if (ciphertext == null) return null;
|
|
1448
|
-
|
|
1449
|
-
// 7. בניית הפקט המלא לפני header protection
|
|
1450
|
-
var fullPacket = concatUint8Arrays([
|
|
1451
|
-
header,
|
|
1452
|
-
packetNumberField,
|
|
1453
|
-
ciphertext
|
|
1454
|
-
]);
|
|
1455
|
-
|
|
1456
|
-
// 8. החלת הגנת כותרת (XOR)
|
|
1457
|
-
return apply_header_protection(fullPacket, packetNumberOffset, writeHp, pnLength);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
function encode_quic_frames(frames) {
|
|
1464
|
-
var parts = [];
|
|
1465
|
-
var i;
|
|
1466
|
-
|
|
1467
|
-
for (i = 0; i < frames.length; i++) {
|
|
1468
|
-
var frame = frames[i];
|
|
1469
|
-
|
|
1470
|
-
if (frame.type === 'padding') {
|
|
1471
|
-
var pad = new Uint8Array(frame.length);
|
|
1472
|
-
for (var j = 0; j < pad.length; j++) pad[j] = 0x00;
|
|
1473
|
-
parts.push(pad);
|
|
1474
|
-
|
|
1475
|
-
} else if (frame.type === 'ping') {
|
|
1476
|
-
parts.push(new Uint8Array([0x01]));
|
|
1477
|
-
|
|
1478
|
-
} else if (frame.type === 'ack') {
|
|
1479
|
-
var hasECN = frame.ecn !== null && frame.ecn !== undefined;
|
|
1480
|
-
var typeByte = hasECN ? 0x03 : 0x02;
|
|
1481
|
-
|
|
1482
|
-
var b1 = writeVarInt(frame.largest); // Largest Acknowledged
|
|
1483
|
-
var b2 = writeVarInt(frame.delay); // ACK Delay
|
|
1484
|
-
var b3 = writeVarInt(frame.ranges.length); // ACK Range Count
|
|
1485
|
-
var b4 = writeVarInt(frame.firstRange != null ? frame.firstRange : 0);
|
|
1486
|
-
|
|
1487
|
-
var temp = [new Uint8Array([typeByte]), b1, b2, b3, b4];
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
for (j = 0; j < frame.ranges.length; j++) {
|
|
1491
|
-
var gap = writeVarInt(frame.ranges[j].gap); // Gap to next range
|
|
1492
|
-
var len = writeVarInt(frame.ranges[j].length); // Length of next range
|
|
1493
|
-
temp.push(gap, len);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
if (hasECN) {
|
|
1497
|
-
temp.push(
|
|
1498
|
-
writeVarInt(frame.ecn.ect0),
|
|
1499
|
-
writeVarInt(frame.ecn.ect1),
|
|
1500
|
-
writeVarInt(frame.ecn.ce)
|
|
1501
|
-
);
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
parts.push(concatUint8Arrays(temp));
|
|
1505
|
-
|
|
1506
|
-
} else if (frame.type === 'reset_stream') {
|
|
1507
|
-
var id = writeVarInt(frame.id);
|
|
1508
|
-
var err = new Uint8Array([frame.error >> 8, frame.error & 0xff]);
|
|
1509
|
-
var size = writeVarInt(frame.finalSize);
|
|
1510
|
-
parts.push(concatUint8Arrays([
|
|
1511
|
-
new Uint8Array([0x04]), id, err, size
|
|
1512
|
-
]));
|
|
1513
|
-
|
|
1514
|
-
} else if (frame.type === 'stop_sending') {
|
|
1515
|
-
var id = writeVarInt(frame.id);
|
|
1516
|
-
var err = new Uint8Array([frame.error >> 8, frame.error & 0xff]);
|
|
1517
|
-
parts.push(concatUint8Arrays([
|
|
1518
|
-
new Uint8Array([0x05]), id, err
|
|
1519
|
-
]));
|
|
1520
|
-
|
|
1521
|
-
} else if (frame.type === 'crypto') {
|
|
1522
|
-
var off = writeVarInt(frame.offset);
|
|
1523
|
-
var len = writeVarInt(frame.data.length);
|
|
1524
|
-
parts.push(concatUint8Arrays([
|
|
1525
|
-
new Uint8Array([0x06]), off, len, frame.data
|
|
1526
|
-
]));
|
|
1527
|
-
|
|
1528
|
-
} else if (frame.type === 'new_token') {
|
|
1529
|
-
var len = writeVarInt(frame.token.length);
|
|
1530
|
-
parts.push(concatUint8Arrays([
|
|
1531
|
-
new Uint8Array([0x07]), len, frame.token
|
|
1532
|
-
]));
|
|
1533
|
-
|
|
1534
|
-
} else if (frame.type === 'stream') {
|
|
1535
|
-
|
|
1536
|
-
var typeByte = 0x08;
|
|
1537
|
-
|
|
1538
|
-
var hasOffset = (frame.offset != null);
|
|
1539
|
-
var hasLen = (frame.data && frame.data.length > 0);
|
|
1540
|
-
var hasFin = !!frame.fin;
|
|
1541
|
-
|
|
1542
|
-
if (hasOffset) typeByte |= 0x04;
|
|
1543
|
-
if (hasLen) typeByte |= 0x02;
|
|
1544
|
-
if (hasFin) typeByte |= 0x01;
|
|
1545
|
-
|
|
1546
|
-
var id = writeVarInt(frame.id);
|
|
1547
|
-
var off = hasOffset ? writeVarInt(frame.offset) : new Uint8Array(0);
|
|
1548
|
-
var len = hasLen ? writeVarInt(frame.data.length) : new Uint8Array(0);
|
|
1549
|
-
|
|
1550
|
-
parts.push(concatUint8Arrays([
|
|
1551
|
-
new Uint8Array([typeByte]), id, off, len, frame.data
|
|
1552
|
-
]));
|
|
1553
|
-
|
|
1554
|
-
} else if (frame.type === 'max_data') {
|
|
1555
|
-
parts.push(concatUint8Arrays([
|
|
1556
|
-
new Uint8Array([0x09]), writeVarInt(frame.max)
|
|
1557
|
-
]));
|
|
1558
|
-
|
|
1559
|
-
} else if (frame.type === 'max_stream_data') {
|
|
1560
|
-
parts.push(concatUint8Arrays([
|
|
1561
|
-
new Uint8Array([0x0a]), writeVarInt(frame.id), writeVarInt(frame.max)
|
|
1562
|
-
]));
|
|
1563
|
-
|
|
1564
|
-
} else if (frame.type === 'max_streams_bidi' || frame.type === 'max_streams_uni') {
|
|
1565
|
-
var code = frame.type === 'max_streams_bidi' ? 0x0b : 0x0c;
|
|
1566
|
-
parts.push(concatUint8Arrays([
|
|
1567
|
-
new Uint8Array([code]), writeVarInt(frame.max)
|
|
1568
|
-
]));
|
|
1569
|
-
|
|
1570
|
-
} else if (frame.type === 'data_blocked') {
|
|
1571
|
-
parts.push(concatUint8Arrays([
|
|
1572
|
-
new Uint8Array([0x0d]), writeVarInt(frame.limit)
|
|
1573
|
-
]));
|
|
1574
|
-
|
|
1575
|
-
} else if (frame.type === 'stream_data_blocked') {
|
|
1576
|
-
parts.push(concatUint8Arrays([
|
|
1577
|
-
new Uint8Array([0x0e]), writeVarInt(frame.id), writeVarInt(frame.limit)
|
|
1578
|
-
]));
|
|
1579
|
-
|
|
1580
|
-
} else if (frame.type === 'streams_blocked_bidi') {
|
|
1581
|
-
parts.push(concatUint8Arrays([
|
|
1582
|
-
new Uint8Array([0x0f]), writeVarInt(frame.limit)
|
|
1583
|
-
]));
|
|
1584
|
-
|
|
1585
|
-
} else if (frame.type === 'streams_blocked_uni') {
|
|
1586
|
-
parts.push(concatUint8Arrays([
|
|
1587
|
-
new Uint8Array([0x10]), writeVarInt(frame.limit)
|
|
1588
|
-
]));
|
|
1589
|
-
|
|
1590
|
-
} else if (frame.type === 'new_connection_id') {
|
|
1591
|
-
parts.push(concatUint8Arrays([
|
|
1592
|
-
new Uint8Array([0x11]),
|
|
1593
|
-
writeVarInt(frame.seq),
|
|
1594
|
-
writeVarInt(frame.retire),
|
|
1595
|
-
new Uint8Array([frame.connId.length]),
|
|
1596
|
-
frame.connId,
|
|
1597
|
-
frame.token
|
|
1598
|
-
]));
|
|
1599
|
-
|
|
1600
|
-
} else if (frame.type === 'retire_connection_id') {
|
|
1601
|
-
parts.push(concatUint8Arrays([
|
|
1602
|
-
new Uint8Array([0x12]),
|
|
1603
|
-
writeVarInt(frame.seq)
|
|
1604
|
-
]));
|
|
1605
|
-
|
|
1606
|
-
} else if (frame.type === 'path_challenge' || frame.type === 'path_response') {
|
|
1607
|
-
var code = frame.type === 'path_challenge' ? 0x13 : 0x14;
|
|
1608
|
-
parts.push(concatUint8Arrays([
|
|
1609
|
-
new Uint8Array([code]), frame.data
|
|
1610
|
-
]));
|
|
1611
|
-
|
|
1612
|
-
} else if (frame.type === 'connection_close') {
|
|
1613
|
-
var code = frame.application ? 0x1d : 0x1c;
|
|
1614
|
-
var err = new Uint8Array([frame.error >> 8, frame.error & 0xff]);
|
|
1615
|
-
var ft = frame.application ? new Uint8Array(0) : writeVarInt(frame.frameType);
|
|
1616
|
-
var reason = new TextEncoder().encode(frame.reason || "");
|
|
1617
|
-
var reasonLen = writeVarInt(reason.length);
|
|
1618
|
-
parts.push(concatUint8Arrays([
|
|
1619
|
-
new Uint8Array([code]), err, ft, reasonLen, reason
|
|
1620
|
-
]));
|
|
1621
|
-
|
|
1622
|
-
} else if (frame.type === 'handshake_done') {
|
|
1623
|
-
parts.push(new Uint8Array([0x1e]));
|
|
1624
|
-
|
|
1625
|
-
} else if (frame.type === 'datagram') {
|
|
1626
|
-
var firstByte;
|
|
1627
|
-
var prefixBytes;
|
|
1628
|
-
var payload = frame.data;
|
|
1629
|
-
|
|
1630
|
-
if (frame.contextId != null) {
|
|
1631
|
-
// אם יש contextId — משתמשים ב־0x31 לפי תקן QUIC
|
|
1632
|
-
firstByte = 0x31;
|
|
1633
|
-
var contextBytes = writeVarInt(frame.contextId);
|
|
1634
|
-
prefixBytes = concatUint8Arrays([
|
|
1635
|
-
new Uint8Array([firstByte]),
|
|
1636
|
-
contextBytes
|
|
1637
|
-
]);
|
|
1638
|
-
} else {
|
|
1639
|
-
// אם אין contextId — שולחים כ־0x30 לפי תקן QUIC
|
|
1640
|
-
firstByte = 0x30;
|
|
1641
|
-
prefixBytes = new Uint8Array([firstByte]);
|
|
1642
|
-
// payload נשאר כמו שהוא — לא מוסיפים streamId!
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
parts.push(concatUint8Arrays([
|
|
1646
|
-
prefixBytes,
|
|
1647
|
-
payload
|
|
1648
|
-
]));
|
|
1649
|
-
} else {
|
|
1650
|
-
// פריים לא ידוע – נתעלם או אפשר להתריע בלוג
|
|
1651
|
-
//console.warn('Unsupported frame type:', frame.type);
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
if(parts.length==1){
|
|
1656
|
-
return parts[0];
|
|
1657
|
-
}else{
|
|
1658
|
-
return concatUint8Arrays(parts);
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
function parse_quic_frames(buf) {
|
|
1668
|
-
let offset = 0;
|
|
1669
|
-
const frames = [];
|
|
1670
|
-
const textDecoder = new TextDecoder();
|
|
1671
|
-
|
|
1672
|
-
function safeReadVarInt() {
|
|
1673
|
-
if (offset >= buf.length) return null;
|
|
1674
|
-
const res = readVarInt(buf, offset);
|
|
1675
|
-
if (!res || typeof res.byteLength !== 'number') return null;
|
|
1676
|
-
offset += res.byteLength;
|
|
1677
|
-
return res;
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
while (offset < buf.length) {
|
|
1681
|
-
const start = offset;
|
|
1682
|
-
let type = buf[offset++];
|
|
1683
|
-
|
|
1684
|
-
if (type >= 0x80) {
|
|
1685
|
-
offset--; // backtrack and read full varint
|
|
1686
|
-
const t = safeReadVarInt();
|
|
1687
|
-
if (!t) break;
|
|
1688
|
-
type = t.value;
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
if (type === 0x00) {
|
|
1692
|
-
// padding
|
|
1693
|
-
|
|
1694
|
-
} else if (type === 0x01) {
|
|
1695
|
-
frames.push({ type: 'ping' });
|
|
1696
|
-
|
|
1697
|
-
} else if ((type & 0xfe) === 0x02) {
|
|
1698
|
-
const hasECN = (type & 0x01) === 0x01;
|
|
1699
|
-
const largest = safeReadVarInt(); if (!largest) break;
|
|
1700
|
-
const delay = safeReadVarInt(); if (!delay) break;
|
|
1701
|
-
const rangeCount = safeReadVarInt(); if (!rangeCount) break;
|
|
1702
|
-
const firstRange = safeReadVarInt(); if (!firstRange) break;
|
|
1703
|
-
|
|
1704
|
-
const ranges = [];
|
|
1705
|
-
for (let i = 0; i < rangeCount.value; i++) {
|
|
1706
|
-
const gap = safeReadVarInt(); if (!gap) break;
|
|
1707
|
-
const len = safeReadVarInt(); if (!len) break;
|
|
1708
|
-
ranges.push({ gap: gap.value, length: len.value });
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
let ecn = null;
|
|
1712
|
-
if (hasECN) {
|
|
1713
|
-
const ect0 = safeReadVarInt(); if (!ect0) break;
|
|
1714
|
-
const ect1 = safeReadVarInt(); if (!ect1) break;
|
|
1715
|
-
const ce = safeReadVarInt(); if (!ce) break;
|
|
1716
|
-
ecn = { ect0: ect0.value, ect1: ect1.value, ce: ce.value };
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
frames.push({ type: 'ack', largest: largest.value, delay: delay.value, firstRange: firstRange.value, ranges, ecn });
|
|
1720
|
-
|
|
1721
|
-
} else if (type === 0x04) {
|
|
1722
|
-
const id = safeReadVarInt(); if (!id) break;
|
|
1723
|
-
if (offset + 2 > buf.length) break;
|
|
1724
|
-
const error = buf[offset++] << 8 | buf[offset++];
|
|
1725
|
-
const finalSize = safeReadVarInt(); if (!finalSize) break;
|
|
1726
|
-
frames.push({ type: 'reset_stream', id: id.value, error, finalSize: finalSize.value });
|
|
1727
|
-
|
|
1728
|
-
} else if (type === 0x05) {
|
|
1729
|
-
const id = safeReadVarInt(); if (!id) break;
|
|
1730
|
-
if (offset + 2 > buf.length) break;
|
|
1731
|
-
const error = buf[offset++] << 8 | buf[offset++];
|
|
1732
|
-
frames.push({ type: 'stop_sending', id: id.value, error });
|
|
1733
|
-
|
|
1734
|
-
} else if (type === 0x06) {
|
|
1735
|
-
const off = safeReadVarInt(); if (!off) break;
|
|
1736
|
-
const len = safeReadVarInt(); if (!len) break;
|
|
1737
|
-
if (offset + len.value > buf.length) break;
|
|
1738
|
-
const data = buf.slice(offset, offset + len.value); offset += len.value;
|
|
1739
|
-
frames.push({ type: 'crypto', offset: off.value, data });
|
|
1740
|
-
|
|
1741
|
-
} else if (type === 0x07) {
|
|
1742
|
-
const len = safeReadVarInt(); if (!len) break;
|
|
1743
|
-
if (offset + len.value > buf.length) break;
|
|
1744
|
-
const token = buf.slice(offset, offset + len.value); offset += len.value;
|
|
1745
|
-
frames.push({ type: 'new_token', token });
|
|
1746
|
-
|
|
1747
|
-
} else if ((type & 0xe0) === 0x00) {
|
|
1748
|
-
const fin = !!(type & 0x01);
|
|
1749
|
-
const lenb = !!(type & 0x02);
|
|
1750
|
-
const offb = !!(type & 0x04);
|
|
1751
|
-
|
|
1752
|
-
const stream_id = safeReadVarInt(); if (!stream_id) break;
|
|
1753
|
-
const offset_val = offb ? safeReadVarInt() : { value: 0 }; if (!offset_val) break;
|
|
1754
|
-
const length_val = lenb ? safeReadVarInt() : { value: buf.length - offset }; if (!length_val) break;
|
|
1755
|
-
|
|
1756
|
-
if (offset + length_val.value > buf.length) break;
|
|
1757
|
-
|
|
1758
|
-
const data = buf.slice(offset, offset + length_val.value); offset += length_val.value;
|
|
1759
|
-
|
|
1760
|
-
frames.push({
|
|
1761
|
-
type: 'stream',
|
|
1762
|
-
id: stream_id.value,
|
|
1763
|
-
offset: offset_val.value,
|
|
1764
|
-
fin,
|
|
1765
|
-
data
|
|
1766
|
-
});
|
|
1767
|
-
} else if (type === 0x09) {
|
|
1768
|
-
const max = safeReadVarInt(); if (!max) break;
|
|
1769
|
-
frames.push({ type: 'max_data', max: max.value });
|
|
1770
|
-
|
|
1771
|
-
} else if (type === 0x0a) {
|
|
1772
|
-
const id = safeReadVarInt(); if (!id) break;
|
|
1773
|
-
const max = safeReadVarInt(); if (!max) break;
|
|
1774
|
-
frames.push({ type: 'max_stream_data', id: id.value, max: max.value });
|
|
1775
|
-
|
|
1776
|
-
} else if (type === 0x12 || type === 0x13) {
|
|
1777
|
-
const max = safeReadVarInt(); if (!max) break;
|
|
1778
|
-
frames.push({ type: type === 0x12 ? 'max_streams_bidi' : 'max_streams_uni', max: max.value });
|
|
1779
|
-
|
|
1780
|
-
} else if (type === 0x14) {
|
|
1781
|
-
const max = safeReadVarInt(); if (!max) break;
|
|
1782
|
-
frames.push({ type: 'data_blocked', max: max.value });
|
|
1783
|
-
|
|
1784
|
-
} else if (type === 0x15) {
|
|
1785
|
-
const id = safeReadVarInt(); if (!id) break;
|
|
1786
|
-
frames.push({ type: 'stream_data_blocked', id: id.value });
|
|
1787
|
-
|
|
1788
|
-
} else if (type === 0x16 || type === 0x17) {
|
|
1789
|
-
const max = safeReadVarInt(); if (!max) break;
|
|
1790
|
-
frames.push({ type: type === 0x16 ? 'streams_blocked_bidi' : 'streams_blocked_uni', max: max.value });
|
|
1791
|
-
|
|
1792
|
-
} else if (type === 0x18) {
|
|
1793
|
-
const seq = safeReadVarInt(); if (!seq) break;
|
|
1794
|
-
const retire = safeReadVarInt(); if (!retire) break;
|
|
1795
|
-
if (offset >= buf.length) break;
|
|
1796
|
-
const len = buf[offset++];
|
|
1797
|
-
if (offset + len + 16 > buf.length) break;
|
|
1798
|
-
const connId = buf.slice(offset, offset + len); offset += len;
|
|
1799
|
-
const token = buf.slice(offset, offset + 16); offset += 16;
|
|
1800
|
-
frames.push({ type: 'new_connection_id', seq: seq.value, retire: retire.value, connId, token });
|
|
1801
|
-
|
|
1802
|
-
} else if (type === 0x19) {
|
|
1803
|
-
const seq = safeReadVarInt(); if (!seq) break;
|
|
1804
|
-
frames.push({ type: 'retire_connection_id', seq: seq.value });
|
|
1805
|
-
|
|
1806
|
-
} else if (type === 0x1a || type === 0x1b) {
|
|
1807
|
-
if (offset + 8 > buf.length) break;
|
|
1808
|
-
const data = buf.slice(offset, offset + 8); offset += 8;
|
|
1809
|
-
frames.push({ type: type === 0x1a ? 'path_challenge' : 'path_response', data });
|
|
1810
|
-
|
|
1811
|
-
} else if (type === 0x1c || type === 0x1d) {
|
|
1812
|
-
if (offset + 2 > buf.length) break;
|
|
1813
|
-
const error = buf[offset++] << 8 | buf[offset++];
|
|
1814
|
-
let frameType = null;
|
|
1815
|
-
if (type === 0x1c) {
|
|
1816
|
-
const ft = safeReadVarInt(); if (!ft) break;
|
|
1817
|
-
frameType = ft.value;
|
|
1818
|
-
}
|
|
1819
|
-
const reasonLen = safeReadVarInt(); if (!reasonLen) break;
|
|
1820
|
-
if (offset + reasonLen.value > buf.length) break;
|
|
1821
|
-
const reason = textDecoder.decode(buf.slice(offset, offset + reasonLen.value)); offset += reasonLen.value;
|
|
1822
|
-
frames.push({ type: 'connection_close', application: type === 0x1d, error, frameType, reason });
|
|
1823
|
-
|
|
1824
|
-
} else if (type === 0x1e) {
|
|
1825
|
-
frames.push({ type: 'handshake_done' });
|
|
1826
|
-
|
|
1827
|
-
} else if (type === 0x1f) {
|
|
1828
|
-
frames.push({ type: 'immediate_ack' });
|
|
1829
|
-
|
|
1830
|
-
} else if (type === 0x30 || type === 0x31) {
|
|
1831
|
-
let contextId = null;
|
|
1832
|
-
let len = null;
|
|
1833
|
-
|
|
1834
|
-
if (type === 0x31) {
|
|
1835
|
-
// קורא את context ID
|
|
1836
|
-
var cid = safeReadVarInt(buf, offset);
|
|
1837
|
-
if (!cid) break;
|
|
1838
|
-
contextId = cid.value;
|
|
1839
|
-
offset = cid.nextOffset;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
// החישוב של len מבוסס על מה שנשאר בפאקט אחרי הקריאה של contextId
|
|
1843
|
-
len = { value: buf.length - offset };
|
|
1844
|
-
|
|
1845
|
-
if (offset + len.value > buf.length) break;
|
|
1846
|
-
|
|
1847
|
-
const data = buf.slice(offset, offset + len.value);
|
|
1848
|
-
offset += len.value;
|
|
1849
|
-
|
|
1850
|
-
frames.push({
|
|
1851
|
-
type: 'datagram',
|
|
1852
|
-
contextId: contextId,
|
|
1853
|
-
data: data
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
} else if (type === 0xaf) {
|
|
1857
|
-
const seq = safeReadVarInt(); if (!seq) break;
|
|
1858
|
-
const packetTolerance = safeReadVarInt(); if (!packetTolerance) break;
|
|
1859
|
-
if (offset >= buf.length) break;
|
|
1860
|
-
const ackDelayExponent = buf[offset++];
|
|
1861
|
-
const maxAckDelay = safeReadVarInt(); if (!maxAckDelay) break;
|
|
1862
|
-
frames.push({
|
|
1863
|
-
type: 'ack_frequency',
|
|
1864
|
-
seq: seq.value,
|
|
1865
|
-
packetTolerance: packetTolerance.value,
|
|
1866
|
-
ackDelayExponent,
|
|
1867
|
-
maxAckDelay: maxAckDelay.value
|
|
1868
|
-
});
|
|
1869
|
-
|
|
1870
|
-
} else if (type >= 0x15228c00 && type <= 0x15228cff) {
|
|
1871
|
-
frames.push({ type: 'multipath_extension', frameType: type });
|
|
1872
|
-
|
|
1873
|
-
} else {
|
|
1874
|
-
frames.push({ type: 'unknown', frameType: type, offset: start });
|
|
1875
|
-
break;
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
return frames;
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
function parse_quic_packet(array, offset0 = 0) {
|
|
1888
|
-
if (!(array instanceof Uint8Array)) return null;
|
|
1889
|
-
if (offset0 >= array.length) return null;
|
|
1890
|
-
|
|
1891
|
-
const firstByte = array[offset0];
|
|
1892
|
-
const isLongHeader = (firstByte & 0x80) !== 0;
|
|
1893
|
-
|
|
1894
|
-
if (isLongHeader) {
|
|
1895
|
-
if (offset0 + 6 > array.length) return null;
|
|
1896
|
-
|
|
1897
|
-
const version = ((array[offset0+1] << 24) | (array[offset0+2] << 16) | (array[offset0+3] << 8) | array[offset0+4]) >>> 0;
|
|
1898
|
-
|
|
1899
|
-
const dcidLen = array[offset0+5];
|
|
1900
|
-
let offset = offset0 + 6;
|
|
1901
|
-
|
|
1902
|
-
if (offset + dcidLen + 1 > array.length) return null;
|
|
1903
|
-
const dcid = array.slice(offset, offset + dcidLen);
|
|
1904
|
-
offset += dcidLen;
|
|
1905
|
-
|
|
1906
|
-
const scidLen = array[offset++];
|
|
1907
|
-
if (offset + scidLen > array.length) return null;
|
|
1908
|
-
const scid = array.slice(offset, offset + scidLen);
|
|
1909
|
-
offset += scidLen;
|
|
1910
|
-
|
|
1911
|
-
// Version negotiation
|
|
1912
|
-
if (version === 0) {
|
|
1913
|
-
const supportedVersions = [];
|
|
1914
|
-
while (offset + 4 <= array.length) {
|
|
1915
|
-
const v = (array[offset] << 24) | (array[offset+1] << 16) | (array[offset+2] << 8) | array[offset+3];
|
|
1916
|
-
supportedVersions.push(v);
|
|
1917
|
-
offset += 4;
|
|
1918
|
-
}
|
|
1919
|
-
return {
|
|
1920
|
-
form: 'long',
|
|
1921
|
-
type: 'version_negotiation',
|
|
1922
|
-
version,
|
|
1923
|
-
dcid,
|
|
1924
|
-
scid,
|
|
1925
|
-
supportedVersions,
|
|
1926
|
-
totalLength: offset - offset0
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
const packetTypeBits = (firstByte & 0x30) >> 4;
|
|
1931
|
-
const typeMap = ['initial', '0rtt', 'handshake', 'retry'];
|
|
1932
|
-
const packetType = typeMap[packetTypeBits] || 'unknown';
|
|
1933
|
-
|
|
1934
|
-
if (packetType === 'retry') {
|
|
1935
|
-
const odcid = array.slice(offset);
|
|
1936
|
-
return {
|
|
1937
|
-
form: 'long',
|
|
1938
|
-
type: 'retry',
|
|
1939
|
-
version,
|
|
1940
|
-
dcid,
|
|
1941
|
-
scid,
|
|
1942
|
-
originalDestinationConnectionId: odcid,
|
|
1943
|
-
totalLength: array.length - offset0 // כל השאר
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
// === קריאה של Token אם זה Initial ===
|
|
1948
|
-
let token = null;
|
|
1949
|
-
if (packetType === 'initial') {
|
|
1950
|
-
try {
|
|
1951
|
-
const tokenLen = readVarInt(array, offset);
|
|
1952
|
-
offset += tokenLen.byteLength;
|
|
1953
|
-
if (offset + tokenLen.value > array.length) return null;
|
|
1954
|
-
token = array.slice(offset, offset + tokenLen.value);
|
|
1955
|
-
offset += tokenLen.value;
|
|
1956
|
-
} catch (e) {
|
|
1957
|
-
return null;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
// === כאן בא השלב הקריטי: לקרוא את Length ===
|
|
1962
|
-
try {
|
|
1963
|
-
const lengthInfo = readVarInt(array, offset);
|
|
1964
|
-
offset += lengthInfo.byteLength;
|
|
1965
|
-
|
|
1966
|
-
const payloadLength = lengthInfo.value;
|
|
1967
|
-
const totalLength = offset - offset0 + payloadLength;
|
|
1968
|
-
|
|
1969
|
-
if (offset0 + totalLength > array.length) return null;
|
|
1970
|
-
|
|
1971
|
-
return {
|
|
1972
|
-
form: 'long',
|
|
1973
|
-
type: packetType,
|
|
1974
|
-
version,
|
|
1975
|
-
dcid,
|
|
1976
|
-
scid,
|
|
1977
|
-
token,
|
|
1978
|
-
totalLength
|
|
1979
|
-
};
|
|
1980
|
-
} catch (e) {
|
|
1981
|
-
return null;
|
|
1982
|
-
}
|
|
1983
|
-
} else {
|
|
1984
|
-
const totalLength = array.length - offset0; // לא ניתן לדעת בדיוק, אז נניח שזה האחרון
|
|
1985
|
-
return {
|
|
1986
|
-
form: 'short',
|
|
1987
|
-
type: '1rtt',
|
|
1988
|
-
totalLength
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
function parse_quic_datagram(array) {
|
|
1994
|
-
var packets = [];
|
|
1995
|
-
var offset = 0;
|
|
1996
|
-
|
|
1997
|
-
while (offset < array.length) {
|
|
1998
|
-
var pkt = parse_quic_packet(array, offset);
|
|
1999
|
-
if (!pkt || !pkt.totalLength) break;
|
|
2000
|
-
|
|
2001
|
-
const start = offset;
|
|
2002
|
-
const end = offset + pkt.totalLength;
|
|
2003
|
-
|
|
2004
|
-
// slice רק אם חייב
|
|
2005
|
-
pkt.raw = (start === 0 && end === array.length)
|
|
2006
|
-
? array
|
|
2007
|
-
: array.slice(start, end);
|
|
2008
|
-
|
|
2009
|
-
packets.push(pkt);
|
|
2010
|
-
offset = end;
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
return packets;
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
function build_new_session_ticket(session_id_bytes, options) {
|
|
2018
|
-
var ticket_lifetime = options.lifetime || 86400;
|
|
2019
|
-
var ticket_age_add = Math.floor(Math.random() * 0xffffffff);
|
|
2020
|
-
var ticket_nonce = crypto.getRandomValues(new Uint8Array(8));
|
|
2021
|
-
var ticket = session_id_bytes;
|
|
2022
|
-
|
|
2023
|
-
var extensions = [];
|
|
2024
|
-
if (options.early_data_max_size != null) {
|
|
2025
|
-
var ed = new Uint8Array(8);
|
|
2026
|
-
ed[0] = 0x00; ed[1] = 0x2a; // early_data extension type
|
|
2027
|
-
ed[2] = 0x00; ed[3] = 0x04; // extension length
|
|
2028
|
-
ed[4] = (options.early_data_max_size >>> 24) & 0xff;
|
|
2029
|
-
ed[5] = (options.early_data_max_size >>> 16) & 0xff;
|
|
2030
|
-
ed[6] = (options.early_data_max_size >>> 8) & 0xff;
|
|
2031
|
-
ed[7] = (options.early_data_max_size) & 0xff;
|
|
2032
|
-
extensions.push(ed);
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
var ext_len = 0;
|
|
2036
|
-
for (var i = 0; i < extensions.length; i++) ext_len += extensions[i].length;
|
|
2037
|
-
var extensions_block = new Uint8Array(ext_len);
|
|
2038
|
-
for (var i = 0, offset = 0; i < extensions.length; i++) {
|
|
2039
|
-
extensions_block.set(extensions[i], offset);
|
|
2040
|
-
offset += extensions[i].length;
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
var total_len =
|
|
2044
|
-
4 + // ticket_lifetime
|
|
2045
|
-
4 + // ticket_age_add
|
|
2046
|
-
1 + ticket_nonce.length +
|
|
2047
|
-
2 + ticket.length +
|
|
2048
|
-
2 + extensions_block.length;
|
|
2049
|
-
|
|
2050
|
-
var result = new Uint8Array(total_len);
|
|
2051
|
-
var p = 0;
|
|
2052
|
-
|
|
2053
|
-
result[p++] = (ticket_lifetime >>> 24) & 0xff;
|
|
2054
|
-
result[p++] = (ticket_lifetime >>> 16) & 0xff;
|
|
2055
|
-
result[p++] = (ticket_lifetime >>> 8) & 0xff;
|
|
2056
|
-
result[p++] = (ticket_lifetime) & 0xff;
|
|
2057
|
-
|
|
2058
|
-
result[p++] = (ticket_age_add >>> 24) & 0xff;
|
|
2059
|
-
result[p++] = (ticket_age_add >>> 16) & 0xff;
|
|
2060
|
-
result[p++] = (ticket_age_add >>> 8) & 0xff;
|
|
2061
|
-
result[p++] = (ticket_age_add) & 0xff;
|
|
2062
|
-
|
|
2063
|
-
result[p++] = ticket_nonce.length;
|
|
2064
|
-
result.set(ticket_nonce, p);
|
|
2065
|
-
p += ticket_nonce.length;
|
|
2066
|
-
|
|
2067
|
-
result[p++] = (ticket.length >>> 8) & 0xff;
|
|
2068
|
-
result[p++] = (ticket.length) & 0xff;
|
|
2069
|
-
result.set(ticket, p);
|
|
2070
|
-
p += ticket.length;
|
|
2071
|
-
|
|
2072
|
-
result[p++] = (extensions_block.length >>> 8) & 0xff;
|
|
2073
|
-
result[p++] = (extensions_block.length) & 0xff;
|
|
2074
|
-
result.set(extensions_block, p);
|
|
2075
|
-
|
|
2076
|
-
return result;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
module.exports = {
|
|
2081
|
-
extract_tls_messages_from_chunks,
|
|
2082
|
-
get_cipher_info,
|
|
2083
|
-
build_certificate,
|
|
2084
|
-
decrypt_quic_packet,
|
|
2085
|
-
quic_derive_init_secrets,
|
|
2086
|
-
quic_derive_from_tls_secrets,
|
|
2087
|
-
parse_tls_message,
|
|
2088
|
-
parse_tls_client_hello,
|
|
2089
|
-
build_server_hello,
|
|
2090
|
-
tls_derive_handshake_secrets,
|
|
2091
|
-
build_quic_ext,
|
|
2092
|
-
build_encrypted_extensions,
|
|
2093
|
-
hkdf_expand_label,
|
|
2094
|
-
hmac,
|
|
2095
|
-
hash_transcript,
|
|
2096
|
-
handle_client_hello,
|
|
2097
|
-
build_certificate_verify,
|
|
2098
|
-
encode_quic_frames,
|
|
2099
|
-
encrypt_quic_packet,
|
|
2100
|
-
parse_quic_datagram,
|
|
2101
|
-
parse_quic_packet,
|
|
2102
|
-
parse_quic_frames,
|
|
2103
|
-
build_alpn_ext,
|
|
2104
|
-
build_finished,
|
|
2105
|
-
tls_derive_app_secrets,
|
|
2106
|
-
parse_transport_parameters
|
|
2107
|
-
};
|