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