@mtcute/core 0.29.7 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/client.cjs +8 -0
  2. package/client.js +8 -0
  3. package/highlevel/client.d.cts +67 -7
  4. package/highlevel/client.d.ts +67 -7
  5. package/highlevel/methods/chats/join-chat.cjs +38 -16
  6. package/highlevel/methods/chats/join-chat.d.cts +20 -6
  7. package/highlevel/methods/chats/join-chat.d.ts +20 -6
  8. package/highlevel/methods/chats/join-chat.js +38 -16
  9. package/highlevel/methods/chats/save-draft.d.cts +4 -1
  10. package/highlevel/methods/chats/save-draft.d.ts +4 -1
  11. package/highlevel/methods/files/normalize-input-media.cjs +8 -0
  12. package/highlevel/methods/files/normalize-input-media.js +9 -1
  13. package/highlevel/methods/messages/create-rich-streaming-draft.cjs +39 -0
  14. package/highlevel/methods/messages/create-rich-streaming-draft.d.cts +42 -0
  15. package/highlevel/methods/messages/create-rich-streaming-draft.d.ts +42 -0
  16. package/highlevel/methods/messages/create-rich-streaming-draft.js +34 -0
  17. package/highlevel/methods/messages/normalize-rich-message.cjs +157 -0
  18. package/highlevel/methods/messages/normalize-rich-message.d.cts +10 -0
  19. package/highlevel/methods/messages/normalize-rich-message.d.ts +10 -0
  20. package/highlevel/methods/messages/normalize-rich-message.js +152 -0
  21. package/highlevel/methods/messages/send-rich-message.cjs +49 -0
  22. package/highlevel/methods/messages/send-rich-message.d.cts +38 -0
  23. package/highlevel/methods/messages/send-rich-message.d.ts +38 -0
  24. package/highlevel/methods/messages/send-rich-message.js +44 -0
  25. package/highlevel/methods.d.cts +5 -0
  26. package/highlevel/methods.d.ts +5 -0
  27. package/highlevel/storage/service/peers.cjs +1 -0
  28. package/highlevel/storage/service/peers.js +1 -0
  29. package/highlevel/types/bots/inline-message/factories.cjs +14 -0
  30. package/highlevel/types/bots/inline-message/factories.d.cts +9 -1
  31. package/highlevel/types/bots/inline-message/factories.d.ts +9 -1
  32. package/highlevel/types/bots/inline-message/factories.js +14 -0
  33. package/highlevel/types/bots/inline-message/types.d.cts +17 -1
  34. package/highlevel/types/bots/inline-message/types.d.ts +17 -1
  35. package/highlevel/types/files/utils.d.cts +1 -1
  36. package/highlevel/types/files/utils.d.ts +1 -1
  37. package/highlevel/types/media/input-media/types.d.cts +1 -1
  38. package/highlevel/types/media/input-media/types.d.ts +1 -1
  39. package/highlevel/types/messages/index.d.cts +1 -0
  40. package/highlevel/types/messages/index.d.ts +1 -0
  41. package/highlevel/types/messages/message.cjs +5 -0
  42. package/highlevel/types/messages/message.d.cts +3 -0
  43. package/highlevel/types/messages/message.d.ts +3 -0
  44. package/highlevel/types/messages/message.js +5 -0
  45. package/highlevel/types/messages/rich/index.d.cts +4 -0
  46. package/highlevel/types/messages/rich/index.d.ts +4 -0
  47. package/highlevel/types/messages/rich/inner.cjs +219 -0
  48. package/highlevel/types/messages/rich/inner.d.cts +127 -0
  49. package/highlevel/types/messages/rich/inner.d.ts +127 -0
  50. package/highlevel/types/messages/rich/inner.js +214 -0
  51. package/highlevel/types/messages/rich/rich-message.cjs +50 -0
  52. package/highlevel/types/messages/rich/rich-message.d.cts +18 -0
  53. package/highlevel/types/messages/rich/rich-message.d.ts +18 -0
  54. package/highlevel/types/messages/rich/rich-message.js +45 -0
  55. package/highlevel/types/messages/rich/types.d.cts +76 -0
  56. package/highlevel/types/messages/rich/types.d.ts +76 -0
  57. package/highlevel/types/peers/full-chat.cjs +6 -0
  58. package/highlevel/types/peers/full-chat.d.cts +2 -0
  59. package/highlevel/types/peers/full-chat.d.ts +2 -0
  60. package/highlevel/types/peers/full-chat.js +6 -0
  61. package/highlevel/types/peers/user.cjs +4 -0
  62. package/highlevel/types/peers/user.d.cts +2 -0
  63. package/highlevel/types/peers/user.d.ts +2 -0
  64. package/highlevel/types/peers/user.js +4 -0
  65. package/highlevel/types/updates/user-typing-update.cjs +2 -0
  66. package/highlevel/types/updates/user-typing-update.js +2 -0
  67. package/highlevel/updates/manager.cjs +195 -0
  68. package/highlevel/updates/manager.js +195 -0
  69. package/highlevel/utils/entities.cjs +176 -0
  70. package/highlevel/utils/entities.d.cts +18 -0
  71. package/highlevel/utils/entities.d.ts +18 -0
  72. package/highlevel/utils/entities.js +177 -1
  73. package/highlevel/utils/inspectable.cjs +1 -0
  74. package/highlevel/utils/inspectable.d.cts +1 -0
  75. package/highlevel/utils/inspectable.d.ts +1 -0
  76. package/highlevel/utils/inspectable.js +1 -0
  77. package/index.cjs +4 -0
  78. package/index.js +4 -0
  79. package/methods.cjs +4 -0
  80. package/methods.js +4 -0
  81. package/network/flood-control.cjs +149 -0
  82. package/network/flood-control.d.cts +79 -0
  83. package/network/flood-control.d.ts +79 -0
  84. package/network/flood-control.js +144 -0
  85. package/network/flood-control.test.d.cts +1 -0
  86. package/network/flood-control.test.d.ts +1 -0
  87. package/network/mtproto-session.cjs +1 -21
  88. package/network/mtproto-session.d.cts +1 -5
  89. package/network/mtproto-session.d.ts +1 -5
  90. package/network/mtproto-session.js +2 -22
  91. package/network/mtproxy/_fake-tls.cjs +169 -107
  92. package/network/mtproxy/_fake-tls.js +169 -107
  93. package/network/mtproxy/_fake-tls.test.d.cts +1 -0
  94. package/network/mtproxy/_fake-tls.test.d.ts +1 -0
  95. package/network/multi-session-connection.cjs +26 -16
  96. package/network/multi-session-connection.d.cts +1 -1
  97. package/network/multi-session-connection.d.ts +1 -1
  98. package/network/multi-session-connection.js +26 -16
  99. package/network/multi-session-connection.test.d.cts +1 -0
  100. package/network/multi-session-connection.test.d.ts +1 -0
  101. package/network/network-manager.cjs +3 -2
  102. package/network/network-manager.d.cts +12 -0
  103. package/network/network-manager.d.ts +12 -0
  104. package/network/network-manager.js +3 -2
  105. package/network/persistent-connection.cjs +25 -1
  106. package/network/persistent-connection.d.cts +5 -0
  107. package/network/persistent-connection.d.ts +5 -0
  108. package/network/persistent-connection.js +25 -1
  109. package/network/session-connection.cjs +5 -10
  110. package/network/session-connection.d.cts +0 -2
  111. package/network/session-connection.d.ts +0 -2
  112. package/network/session-connection.js +5 -10
  113. package/package.json +1 -1
  114. package/tl/api-schema.json +1 -1
  115. package/tl/binary/reader.cjs +182 -26
  116. package/tl/binary/reader.js +182 -26
  117. package/tl/binary/writer.cjs +437 -20
  118. package/tl/binary/writer.js +437 -20
  119. package/tl/compat/reader.cjs +23 -0
  120. package/tl/compat/reader.js +23 -0
  121. package/tl/index.d.cts +425 -14
  122. package/tl/index.d.ts +425 -14
  123. package/tl/inner-tl.cjs +22 -8
  124. package/tl/inner-tl.js +22 -8
  125. package/types/utils.d.cts +4 -0
  126. package/types/utils.d.ts +4 -0
  127. package/utils/binary/compat.cjs +79 -0
  128. package/utils/binary/compat.js +79 -0
  129. package/utils/index.d.cts +1 -0
  130. package/utils/index.d.ts +1 -0
  131. package/utils/long-utils.cjs +10 -0
  132. package/utils/long-utils.js +10 -0
  133. package/utils.cjs +9 -0
  134. package/utils.js +11 -2
@@ -8,6 +8,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  const io = require("@fuman/io");
9
9
  const utils = require("@fuman/utils");
10
10
  const MAX_TLS_PACKET_LENGTH = 2878;
11
+ const MAX_TLS_HELLO_SIZE = 4096;
11
12
  const KEY_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
12
13
  const QUAD_RES_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
13
14
  const QUAD_RES_POW = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6n;
@@ -34,7 +35,11 @@ function _isQuadraticResidue(a) {
34
35
  return r === 1n;
35
36
  }
36
37
  function executeTlsOperations(h) {
37
- h.string([22, 3, 1, 2, 0, 1, 0, 1, 252, 3, 3]);
38
+ h.string([22, 3, 1]);
39
+ h.beginScope();
40
+ h.string([1, 0]);
41
+ h.beginScope();
42
+ h.string([3, 3]);
38
43
  h.zero(32);
39
44
  h.string([32]);
40
45
  h.random(32);
@@ -72,67 +77,34 @@ function executeTlsOperations(h) {
72
77
  0,
73
78
  53,
74
79
  1,
75
- 0,
76
- 1,
77
- 147
80
+ 0
78
81
  ]);
79
- h.grease(2);
80
- h.string([0, 0, 0, 0]);
81
- h.beginScope();
82
82
  h.beginScope();
83
- h.string([0]);
84
- h.beginScope();
85
- h.domain();
86
- h.endScope();
87
- h.endScope();
88
- h.endScope();
89
- h.string([0, 23, 0, 0, 255, 1, 0, 1, 0, 0, 10, 0, 10, 0, 8]);
90
- h.grease(4);
91
- h.string(
92
- [
93
- 0,
94
- 29,
95
- 0,
96
- 23,
97
- 0,
98
- 24,
99
- 0,
100
- 11,
101
- 0,
102
- 2,
103
- 1,
104
- 0,
105
- 0,
106
- 35,
107
- 0,
108
- 0,
109
- 0,
110
- 16,
111
- 0,
112
- 14,
113
- 0,
114
- 12,
115
- 2,
116
- 104,
117
- 50,
118
- 8,
119
- 104,
120
- 116,
121
- 116,
122
- 112,
123
- 47,
124
- 49,
125
- 46,
126
- 49,
127
- 0,
128
- 5,
129
- 0,
130
- 5,
131
- 1,
132
- 0,
133
- 0,
134
- 0,
135
- 0,
83
+ h.grease(2);
84
+ h.string([0, 0]);
85
+ h.permutation([
86
+ (w) => {
87
+ w.string([0, 0]);
88
+ w.beginScope();
89
+ w.beginScope();
90
+ w.string([0]);
91
+ w.beginScope();
92
+ w.domain();
93
+ w.endScope();
94
+ w.endScope();
95
+ w.endScope();
96
+ },
97
+ (w) => w.string([0, 5, 0, 5, 1, 0, 0, 0, 0]),
98
+ // status_request
99
+ (w) => {
100
+ w.string([0, 10, 0, 12, 0, 10]);
101
+ w.grease(4);
102
+ w.string([17, 236, 0, 29, 0, 23, 0, 24]);
103
+ },
104
+ (w) => w.string([0, 11, 0, 2, 1, 0]),
105
+ // ec_point_formats
106
+ (w) => w.string([
107
+ // signature_algorithms
136
108
  0,
137
109
  13,
138
110
  0,
@@ -154,27 +126,76 @@ function executeTlsOperations(h) {
154
126
  8,
155
127
  6,
156
128
  6,
157
- 1,
129
+ 1
130
+ ]),
131
+ (w) => w.string([
132
+ // application_layer_protocol_negotiation (ALPN)
158
133
  0,
159
- 18,
160
- 0,
161
- 0,
162
- 0,
163
- 51,
134
+ 16,
164
135
  0,
165
- 43,
136
+ 14,
166
137
  0,
167
- 41
168
- ]
169
- );
170
- h.grease(4);
171
- h.string([0, 1, 0, 0, 29, 0, 32]);
172
- h.key();
173
- h.string([0, 45, 0, 2, 1, 1, 0, 43, 0, 11, 10]);
174
- h.grease(6);
175
- h.string([3, 4, 3, 3, 3, 2, 3, 1, 0, 27, 0, 3, 2, 0, 2]);
138
+ 12,
139
+ 2,
140
+ 104,
141
+ 50,
142
+ 8,
143
+ 104,
144
+ 116,
145
+ 116,
146
+ 112,
147
+ 47,
148
+ 49,
149
+ 46,
150
+ 49
151
+ ]),
152
+ (w) => w.string([0, 18, 0, 0]),
153
+ // signed_certificate_timestamp
154
+ (w) => w.string([0, 23, 0, 0]),
155
+ // extended_master_secret
156
+ (w) => w.string([0, 27, 0, 3, 2, 0, 2]),
157
+ // compress_certificate
158
+ (w) => w.string([0, 35, 0, 0]),
159
+ // session_ticket
160
+ (w) => {
161
+ w.string([0, 43, 0, 7, 6]);
162
+ w.grease(6);
163
+ w.string([3, 4, 3, 3]);
164
+ },
165
+ (w) => w.string([0, 45, 0, 2, 1, 1]),
166
+ // psk_key_exchange_modes
167
+ (w) => {
168
+ w.string([0, 51, 4, 239, 4, 237]);
169
+ w.grease(4);
170
+ w.string([0, 1, 0, 17, 236, 4, 192]);
171
+ w.mlKem768Key();
172
+ w.key();
173
+ w.string([0, 29, 0, 32]);
174
+ w.key();
175
+ },
176
+ (w) => w.string([68, 205, 0, 5, 0, 3, 2, 104, 50]),
177
+ // application_settings (ALPS)
178
+ (w) => {
179
+ w.string([254, 13]);
180
+ w.beginScope();
181
+ w.string([0, 0, 1, 0, 1]);
182
+ w.random(1);
183
+ w.string([0, 32]);
184
+ w.random(32);
185
+ w.beginScope();
186
+ w.echPayload();
187
+ w.endScope();
188
+ w.endScope();
189
+ },
190
+ (w) => w.string([255, 1, 0, 1, 0])
191
+ // renegotiation_info
192
+ ]);
176
193
  h.grease(3);
177
- h.string([0, 1, 0, 0, 21]);
194
+ h.string([0, 1, 0]);
195
+ h.padding();
196
+ h.endScope();
197
+ h.endScope();
198
+ h.endScope();
178
199
  }
179
200
  function initGrease(crypto, size) {
180
201
  const buf = crypto.randomBytes(size);
@@ -189,12 +210,12 @@ function initGrease(crypto, size) {
189
210
  return buf;
190
211
  }
191
212
  class TlsHelloWriter {
192
- constructor(crypto, size, domain) {
213
+ constructor(crypto, domain, grease) {
193
214
  this.crypto = crypto;
194
215
  this._domain = domain;
195
- this.buf = utils.u8.alloc(size);
216
+ this.buf = utils.u8.alloc(MAX_TLS_HELLO_SIZE);
196
217
  this.dv = utils.typed.toDataView(this.buf);
197
- this._grease = initGrease(this.crypto, 7);
218
+ this._grease = grease ?? initGrease(this.crypto, 7);
198
219
  }
199
220
  buf;
200
221
  dv;
@@ -235,6 +256,24 @@ class TlsHelloWriter {
235
256
  }
236
257
  }
237
258
  }
259
+ // fake X25519MLKEM768 hybrid keyshare's ml-kem-768 part: 384 coefficients
260
+ // (mod 3329) packed 3 bytes per 2 coefficients, followed by 32 random bytes
261
+ mlKem768Key() {
262
+ const rnd = this.crypto.randomBytes(384 * 8);
263
+ const dv = utils.typed.toDataView(rnd);
264
+ for (let i = 0; i < 384; i++) {
265
+ const a = dv.getUint32(i * 8, true) % 3329;
266
+ const b = dv.getUint32(i * 8 + 4, true) % 3329;
267
+ this.buf[this.pos++] = a & 255;
268
+ this.buf[this.pos++] = (a >> 8) + ((b & 15) << 4);
269
+ this.buf[this.pos++] = b >> 4;
270
+ }
271
+ this.random(32);
272
+ }
273
+ // GREASE ECH payload: random bytes of length 144/176/208/240
274
+ echPayload() {
275
+ this.random(this._randomInt(4) * 32 + 144);
276
+ }
238
277
  beginScope() {
239
278
  this._scopes.push(this.pos);
240
279
  this.pos += 2;
@@ -244,29 +283,55 @@ class TlsHelloWriter {
244
283
  if (begin === void 0) {
245
284
  throw new Error("endScope called without beginScope");
246
285
  }
247
- const end = this.pos;
248
- const size = end - begin - 2;
249
- this.dv.setUint16(begin, size);
286
+ this.dv.setUint16(begin, this.pos - begin - 2);
250
287
  }
251
- async finish(secret) {
252
- const padSize = 515 - this.pos;
253
- const unixTime = ~~(Date.now() / 1e3);
288
+ // render each part into its own buffer, then emit them in random order —
289
+ // mimics chrome's per-connection tls extension shuffling
290
+ permutation(parts) {
291
+ const rendered = parts.map((fn) => {
292
+ const sub = new TlsHelloWriter(this.crypto, this._domain, this._grease);
293
+ fn(sub);
294
+ return sub.buf.subarray(0, sub.pos);
295
+ });
296
+ for (let i = rendered.length - 1; i > 0; i--) {
297
+ const j = this._randomInt(i + 1);
298
+ const tmp = rendered[i];
299
+ rendered[i] = rendered[j];
300
+ rendered[j] = tmp;
301
+ }
302
+ for (const part of rendered) {
303
+ this.string(part);
304
+ }
305
+ }
306
+ // tls padding extension, only emitted for hellos smaller than 513 bytes
307
+ padding() {
308
+ if (this.pos >= 513) return;
309
+ const size = 513 - this.pos;
310
+ this.string([0, 21]);
254
311
  this.beginScope();
255
- this.zero(padSize);
312
+ this.zero(size);
256
313
  this.endScope();
257
- const hash = await this.crypto.hmacSha256(this.buf, secret);
314
+ }
315
+ _randomInt(max) {
316
+ return utils.typed.toDataView(this.crypto.randomBytes(4)).getUint32(0, true) % max;
317
+ }
318
+ async finish(secret) {
319
+ const data = this.buf.subarray(0, this.pos);
320
+ const unixTime = ~~(Date.now() / 1e3);
321
+ const hash = await this.crypto.hmacSha256(data, secret);
258
322
  const dv = utils.typed.toDataView(hash);
259
323
  const old = dv.getInt32(28, true);
260
324
  dv.setInt32(28, old ^ unixTime, true);
261
- this.buf.set(hash, 11);
262
- return this.buf;
325
+ data.set(hash, 11);
326
+ return data;
263
327
  }
264
328
  }
265
329
  async function generateFakeTlsHeader(domain, secret, crypto) {
266
- const writer = new TlsHelloWriter(crypto, 517, domain);
330
+ const writer = new TlsHelloWriter(crypto, domain);
267
331
  executeTlsOperations(writer);
268
332
  return writer.finish(secret);
269
333
  }
334
+ const EMPTY_BYTES = io.Bytes.from(utils.u8.empty);
270
335
  class FakeTlsPacketCodec {
271
336
  // protected _stream: Buffer = Buffer.alloc(0)
272
337
  constructor(_inner) {
@@ -301,23 +366,20 @@ class FakeTlsPacketCodec {
301
366
  }
302
367
  async decode(reader, eof) {
303
368
  if (eof) return null;
304
- if (reader.available < 5) return null;
305
- const header = reader.readSync(3);
306
- if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
307
- throw new Error("Invalid TLS header");
308
- }
309
- const length = io.read.uint16be(reader);
310
- if (length < reader.available - 5) {
311
- reader.rewind(5);
312
- return null;
313
- }
314
- const packet = reader.readSync(length);
315
- const inner = await this._inner.decode(io.Bytes.from(packet), eof);
316
- if (!inner) {
317
- reader.rewind(5 + length);
318
- return null;
369
+ while (reader.available >= 5) {
370
+ const header = reader.readSync(3);
371
+ if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
372
+ throw new Error("Invalid TLS header");
373
+ }
374
+ const length = io.read.uint16be(reader);
375
+ if (reader.available < length) {
376
+ reader.rewind(5);
377
+ break;
378
+ }
379
+ const inner = await this._inner.decode(io.Bytes.from(reader.readSync(length)), eof);
380
+ if (inner) return inner;
319
381
  }
320
- return inner;
382
+ return this._inner.decode(EMPTY_BYTES, eof);
321
383
  }
322
384
  reset() {
323
385
  this._isFirstTls = true;
@@ -1,6 +1,7 @@
1
1
  import { Bytes, read } from "@fuman/io";
2
2
  import { u8, typed, bigint } from "@fuman/utils";
3
3
  const MAX_TLS_PACKET_LENGTH = 2878;
4
+ const MAX_TLS_HELLO_SIZE = 4096;
4
5
  const KEY_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
5
6
  const QUAD_RES_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn;
6
7
  const QUAD_RES_POW = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6n;
@@ -27,7 +28,11 @@ function _isQuadraticResidue(a) {
27
28
  return r === 1n;
28
29
  }
29
30
  function executeTlsOperations(h) {
30
- h.string([22, 3, 1, 2, 0, 1, 0, 1, 252, 3, 3]);
31
+ h.string([22, 3, 1]);
32
+ h.beginScope();
33
+ h.string([1, 0]);
34
+ h.beginScope();
35
+ h.string([3, 3]);
31
36
  h.zero(32);
32
37
  h.string([32]);
33
38
  h.random(32);
@@ -65,67 +70,34 @@ function executeTlsOperations(h) {
65
70
  0,
66
71
  53,
67
72
  1,
68
- 0,
69
- 1,
70
- 147
73
+ 0
71
74
  ]);
72
- h.grease(2);
73
- h.string([0, 0, 0, 0]);
74
- h.beginScope();
75
75
  h.beginScope();
76
- h.string([0]);
77
- h.beginScope();
78
- h.domain();
79
- h.endScope();
80
- h.endScope();
81
- h.endScope();
82
- h.string([0, 23, 0, 0, 255, 1, 0, 1, 0, 0, 10, 0, 10, 0, 8]);
83
- h.grease(4);
84
- h.string(
85
- [
86
- 0,
87
- 29,
88
- 0,
89
- 23,
90
- 0,
91
- 24,
92
- 0,
93
- 11,
94
- 0,
95
- 2,
96
- 1,
97
- 0,
98
- 0,
99
- 35,
100
- 0,
101
- 0,
102
- 0,
103
- 16,
104
- 0,
105
- 14,
106
- 0,
107
- 12,
108
- 2,
109
- 104,
110
- 50,
111
- 8,
112
- 104,
113
- 116,
114
- 116,
115
- 112,
116
- 47,
117
- 49,
118
- 46,
119
- 49,
120
- 0,
121
- 5,
122
- 0,
123
- 5,
124
- 1,
125
- 0,
126
- 0,
127
- 0,
128
- 0,
76
+ h.grease(2);
77
+ h.string([0, 0]);
78
+ h.permutation([
79
+ (w) => {
80
+ w.string([0, 0]);
81
+ w.beginScope();
82
+ w.beginScope();
83
+ w.string([0]);
84
+ w.beginScope();
85
+ w.domain();
86
+ w.endScope();
87
+ w.endScope();
88
+ w.endScope();
89
+ },
90
+ (w) => w.string([0, 5, 0, 5, 1, 0, 0, 0, 0]),
91
+ // status_request
92
+ (w) => {
93
+ w.string([0, 10, 0, 12, 0, 10]);
94
+ w.grease(4);
95
+ w.string([17, 236, 0, 29, 0, 23, 0, 24]);
96
+ },
97
+ (w) => w.string([0, 11, 0, 2, 1, 0]),
98
+ // ec_point_formats
99
+ (w) => w.string([
100
+ // signature_algorithms
129
101
  0,
130
102
  13,
131
103
  0,
@@ -147,27 +119,76 @@ function executeTlsOperations(h) {
147
119
  8,
148
120
  6,
149
121
  6,
150
- 1,
122
+ 1
123
+ ]),
124
+ (w) => w.string([
125
+ // application_layer_protocol_negotiation (ALPN)
151
126
  0,
152
- 18,
153
- 0,
154
- 0,
155
- 0,
156
- 51,
127
+ 16,
157
128
  0,
158
- 43,
129
+ 14,
159
130
  0,
160
- 41
161
- ]
162
- );
163
- h.grease(4);
164
- h.string([0, 1, 0, 0, 29, 0, 32]);
165
- h.key();
166
- h.string([0, 45, 0, 2, 1, 1, 0, 43, 0, 11, 10]);
167
- h.grease(6);
168
- h.string([3, 4, 3, 3, 3, 2, 3, 1, 0, 27, 0, 3, 2, 0, 2]);
131
+ 12,
132
+ 2,
133
+ 104,
134
+ 50,
135
+ 8,
136
+ 104,
137
+ 116,
138
+ 116,
139
+ 112,
140
+ 47,
141
+ 49,
142
+ 46,
143
+ 49
144
+ ]),
145
+ (w) => w.string([0, 18, 0, 0]),
146
+ // signed_certificate_timestamp
147
+ (w) => w.string([0, 23, 0, 0]),
148
+ // extended_master_secret
149
+ (w) => w.string([0, 27, 0, 3, 2, 0, 2]),
150
+ // compress_certificate
151
+ (w) => w.string([0, 35, 0, 0]),
152
+ // session_ticket
153
+ (w) => {
154
+ w.string([0, 43, 0, 7, 6]);
155
+ w.grease(6);
156
+ w.string([3, 4, 3, 3]);
157
+ },
158
+ (w) => w.string([0, 45, 0, 2, 1, 1]),
159
+ // psk_key_exchange_modes
160
+ (w) => {
161
+ w.string([0, 51, 4, 239, 4, 237]);
162
+ w.grease(4);
163
+ w.string([0, 1, 0, 17, 236, 4, 192]);
164
+ w.mlKem768Key();
165
+ w.key();
166
+ w.string([0, 29, 0, 32]);
167
+ w.key();
168
+ },
169
+ (w) => w.string([68, 205, 0, 5, 0, 3, 2, 104, 50]),
170
+ // application_settings (ALPS)
171
+ (w) => {
172
+ w.string([254, 13]);
173
+ w.beginScope();
174
+ w.string([0, 0, 1, 0, 1]);
175
+ w.random(1);
176
+ w.string([0, 32]);
177
+ w.random(32);
178
+ w.beginScope();
179
+ w.echPayload();
180
+ w.endScope();
181
+ w.endScope();
182
+ },
183
+ (w) => w.string([255, 1, 0, 1, 0])
184
+ // renegotiation_info
185
+ ]);
169
186
  h.grease(3);
170
- h.string([0, 1, 0, 0, 21]);
187
+ h.string([0, 1, 0]);
188
+ h.padding();
189
+ h.endScope();
190
+ h.endScope();
191
+ h.endScope();
171
192
  }
172
193
  function initGrease(crypto, size) {
173
194
  const buf = crypto.randomBytes(size);
@@ -182,12 +203,12 @@ function initGrease(crypto, size) {
182
203
  return buf;
183
204
  }
184
205
  class TlsHelloWriter {
185
- constructor(crypto, size, domain) {
206
+ constructor(crypto, domain, grease) {
186
207
  this.crypto = crypto;
187
208
  this._domain = domain;
188
- this.buf = u8.alloc(size);
209
+ this.buf = u8.alloc(MAX_TLS_HELLO_SIZE);
189
210
  this.dv = typed.toDataView(this.buf);
190
- this._grease = initGrease(this.crypto, 7);
211
+ this._grease = grease ?? initGrease(this.crypto, 7);
191
212
  }
192
213
  buf;
193
214
  dv;
@@ -228,6 +249,24 @@ class TlsHelloWriter {
228
249
  }
229
250
  }
230
251
  }
252
+ // fake X25519MLKEM768 hybrid keyshare's ml-kem-768 part: 384 coefficients
253
+ // (mod 3329) packed 3 bytes per 2 coefficients, followed by 32 random bytes
254
+ mlKem768Key() {
255
+ const rnd = this.crypto.randomBytes(384 * 8);
256
+ const dv = typed.toDataView(rnd);
257
+ for (let i = 0; i < 384; i++) {
258
+ const a = dv.getUint32(i * 8, true) % 3329;
259
+ const b = dv.getUint32(i * 8 + 4, true) % 3329;
260
+ this.buf[this.pos++] = a & 255;
261
+ this.buf[this.pos++] = (a >> 8) + ((b & 15) << 4);
262
+ this.buf[this.pos++] = b >> 4;
263
+ }
264
+ this.random(32);
265
+ }
266
+ // GREASE ECH payload: random bytes of length 144/176/208/240
267
+ echPayload() {
268
+ this.random(this._randomInt(4) * 32 + 144);
269
+ }
231
270
  beginScope() {
232
271
  this._scopes.push(this.pos);
233
272
  this.pos += 2;
@@ -237,29 +276,55 @@ class TlsHelloWriter {
237
276
  if (begin === void 0) {
238
277
  throw new Error("endScope called without beginScope");
239
278
  }
240
- const end = this.pos;
241
- const size = end - begin - 2;
242
- this.dv.setUint16(begin, size);
279
+ this.dv.setUint16(begin, this.pos - begin - 2);
243
280
  }
244
- async finish(secret) {
245
- const padSize = 515 - this.pos;
246
- const unixTime = ~~(Date.now() / 1e3);
281
+ // render each part into its own buffer, then emit them in random order —
282
+ // mimics chrome's per-connection tls extension shuffling
283
+ permutation(parts) {
284
+ const rendered = parts.map((fn) => {
285
+ const sub = new TlsHelloWriter(this.crypto, this._domain, this._grease);
286
+ fn(sub);
287
+ return sub.buf.subarray(0, sub.pos);
288
+ });
289
+ for (let i = rendered.length - 1; i > 0; i--) {
290
+ const j = this._randomInt(i + 1);
291
+ const tmp = rendered[i];
292
+ rendered[i] = rendered[j];
293
+ rendered[j] = tmp;
294
+ }
295
+ for (const part of rendered) {
296
+ this.string(part);
297
+ }
298
+ }
299
+ // tls padding extension, only emitted for hellos smaller than 513 bytes
300
+ padding() {
301
+ if (this.pos >= 513) return;
302
+ const size = 513 - this.pos;
303
+ this.string([0, 21]);
247
304
  this.beginScope();
248
- this.zero(padSize);
305
+ this.zero(size);
249
306
  this.endScope();
250
- const hash = await this.crypto.hmacSha256(this.buf, secret);
307
+ }
308
+ _randomInt(max) {
309
+ return typed.toDataView(this.crypto.randomBytes(4)).getUint32(0, true) % max;
310
+ }
311
+ async finish(secret) {
312
+ const data = this.buf.subarray(0, this.pos);
313
+ const unixTime = ~~(Date.now() / 1e3);
314
+ const hash = await this.crypto.hmacSha256(data, secret);
251
315
  const dv = typed.toDataView(hash);
252
316
  const old = dv.getInt32(28, true);
253
317
  dv.setInt32(28, old ^ unixTime, true);
254
- this.buf.set(hash, 11);
255
- return this.buf;
318
+ data.set(hash, 11);
319
+ return data;
256
320
  }
257
321
  }
258
322
  async function generateFakeTlsHeader(domain, secret, crypto) {
259
- const writer = new TlsHelloWriter(crypto, 517, domain);
323
+ const writer = new TlsHelloWriter(crypto, domain);
260
324
  executeTlsOperations(writer);
261
325
  return writer.finish(secret);
262
326
  }
327
+ const EMPTY_BYTES = Bytes.from(u8.empty);
263
328
  class FakeTlsPacketCodec {
264
329
  // protected _stream: Buffer = Buffer.alloc(0)
265
330
  constructor(_inner) {
@@ -294,23 +359,20 @@ class FakeTlsPacketCodec {
294
359
  }
295
360
  async decode(reader, eof) {
296
361
  if (eof) return null;
297
- if (reader.available < 5) return null;
298
- const header = reader.readSync(3);
299
- if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
300
- throw new Error("Invalid TLS header");
301
- }
302
- const length = read.uint16be(reader);
303
- if (length < reader.available - 5) {
304
- reader.rewind(5);
305
- return null;
306
- }
307
- const packet = reader.readSync(length);
308
- const inner = await this._inner.decode(Bytes.from(packet), eof);
309
- if (!inner) {
310
- reader.rewind(5 + length);
311
- return null;
362
+ while (reader.available >= 5) {
363
+ const header = reader.readSync(3);
364
+ if (header[0] !== 23 || header[1] !== 3 || header[2] !== 3) {
365
+ throw new Error("Invalid TLS header");
366
+ }
367
+ const length = read.uint16be(reader);
368
+ if (reader.available < length) {
369
+ reader.rewind(5);
370
+ break;
371
+ }
372
+ const inner = await this._inner.decode(Bytes.from(reader.readSync(length)), eof);
373
+ if (inner) return inner;
312
374
  }
313
- return inner;
375
+ return this._inner.decode(EMPTY_BYTES, eof);
314
376
  }
315
377
  reset() {
316
378
  this._isFirstTls = true;
@@ -0,0 +1 @@
1
+ export {};