teleproto 1.225.2 → 1.225.3

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/Version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "1.225.2";
1
+ export declare const version = "1.225.3";
package/Version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- exports.version = "1.225.2";
4
+ exports.version = "1.225.3";
@@ -85,6 +85,22 @@ export declare class TelegramClient extends TelegramBaseClient {
85
85
  * @return boolean (true of authorized else false)
86
86
  */
87
87
  checkAuthorization(): Promise<boolean>;
88
+ /**
89
+ * Logs out the currently authenticated user/bot. Invalidates the session
90
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
91
+ * local session data so a subsequent `session.save()` returns nothing.
92
+ *
93
+ * After this call the client is disconnected and event handlers stop
94
+ * firing; reconnecting requires going through `start`/`signInUser` again.
95
+ *
96
+ * @returns `true` if the server-side log out succeeded, `false` if the
97
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
98
+ * @example
99
+ * ```ts
100
+ * await client.logOut();
101
+ * ```
102
+ */
103
+ logOut(): Promise<boolean>;
88
104
  /**
89
105
  * Logs in as a user. Should only be used when not already logged in.<br/>
90
106
  * This method will send a code when needed.<br/>
@@ -127,6 +127,24 @@ class TelegramClient extends telegramBaseClient_1.TelegramBaseClient {
127
127
  checkAuthorization() {
128
128
  return authMethods.checkAuthorization(this);
129
129
  }
130
+ /**
131
+ * Logs out the currently authenticated user/bot. Invalidates the session
132
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
133
+ * local session data so a subsequent `session.save()` returns nothing.
134
+ *
135
+ * After this call the client is disconnected and event handlers stop
136
+ * firing; reconnecting requires going through `start`/`signInUser` again.
137
+ *
138
+ * @returns `true` if the server-side log out succeeded, `false` if the
139
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
140
+ * @example
141
+ * ```ts
142
+ * await client.logOut();
143
+ * ```
144
+ */
145
+ logOut() {
146
+ return authMethods.logOut(this);
147
+ }
130
148
  /**
131
149
  * Logs in as a user. Should only be used when not already logged in.<br/>
132
150
  * This method will send a code when needed.<br/>
package/client/auth.d.ts CHANGED
@@ -115,6 +115,8 @@ export declare function start(client: TelegramClient, authParams: UserAuthParams
115
115
  /** @hidden */
116
116
  export declare function checkAuthorization(client: TelegramClient): Promise<boolean>;
117
117
  /** @hidden */
118
+ export declare function logOut(client: TelegramClient): Promise<boolean>;
119
+ /** @hidden */
118
120
  export declare function signInUser(client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams): Promise<Api.TypeUser>;
119
121
  /** @hidden */
120
122
  export declare function signInUserWithQrCode(client: TelegramClient, apiCredentials: ApiCredentials, authParams: QrCodeAuthParams): Promise<Api.TypeUser>;
package/client/auth.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.start = start;
37
37
  exports.checkAuthorization = checkAuthorization;
38
+ exports.logOut = logOut;
38
39
  exports.signInUser = signInUser;
39
40
  exports.signInUserWithQrCode = signInUserWithQrCode;
40
41
  exports.sendCode = sendCode;
@@ -75,6 +76,20 @@ async function checkAuthorization(client) {
75
76
  }
76
77
  }
77
78
  /** @hidden */
79
+ async function logOut(client) {
80
+ let success = true;
81
+ try {
82
+ await client.invoke(new tl_1.Api.auth.LogOut());
83
+ }
84
+ catch (e) {
85
+ client._log.warn("auth.LogOut failed: " + e.message);
86
+ success = false;
87
+ }
88
+ await client.disconnect();
89
+ await client.session.delete();
90
+ return success;
91
+ }
92
+ /** @hidden */
78
93
  async function signInUser(client, apiCredentials, authParams) {
79
94
  let phoneNumber = "";
80
95
  let phoneCodeHash = "";
@@ -1,9 +1,6 @@
1
1
  import { ObfuscatedConnection } from "./Connection";
2
2
  import { AbridgedPacketCodec } from "./TCPAbridged";
3
3
  import { Logger, PromisedNetSockets } from "../../extensions";
4
- /**
5
- * Base proxy configuration shared by all proxy types.
6
- */
7
4
  interface BasicProxyInterface {
8
5
  /** Proxy server IP address. */
9
6
  ip: string;
@@ -38,9 +35,13 @@ interface BasicProxyInterface {
38
35
  */
39
36
  export type MTProxyType = BasicProxyInterface & {
40
37
  /**
41
- * The proxy secret (hex or base64 encoded).
42
- * Must decode to exactly 16 bytes. Secrets prefixed with `dd` (fake-TLS)
43
- * are also supported the leading byte is stripped automatically.
38
+ * The proxy secret (hex or base64 encoded). Three formats are accepted:
39
+ * - 16-byte plain secret.
40
+ * - 17 bytes with a leading `dd` byte random-padding mode.
41
+ * - A leading `ee` byte + 16 bytes of secret + UTF-8 SNI domain bytes —
42
+ * fake-TLS mode. The connection is wrapped in a spoofed TLS 1.2
43
+ * handshake and all MTProxy bytes are tunneled inside TLS
44
+ * application_data records.
44
45
  */
45
46
  secret: string;
46
47
  /** Must be `true` to identify this as an MTProxy configuration. */
@@ -50,12 +51,11 @@ export type MTProxyType = BasicProxyInterface & {
50
51
  * Configuration for SOCKS4/SOCKS5 proxy connections.
51
52
  *
52
53
  * SOCKS proxies are handled at the socket level via the `socks` package and
53
- * work transparently with any connection type (Full, Abridged, Obfuscated).
54
- * SOCKS5 supports username/password authentication.
54
+ * work transparently with any connection type. SOCKS5 supports username/
55
+ * password authentication.
55
56
  *
56
57
  * @example
57
58
  * ```ts
58
- * // SOCKS5 with authentication
59
59
  * const client = new TelegramClient(session, apiId, apiHash, {
60
60
  * proxy: {
61
61
  * socksType: 5,
@@ -65,15 +65,6 @@ export type MTProxyType = BasicProxyInterface & {
65
65
  * password: "pass",
66
66
  * },
67
67
  * });
68
- *
69
- * // SOCKS4 (no authentication)
70
- * const client = new TelegramClient(session, apiId, apiHash, {
71
- * proxy: {
72
- * socksType: 4,
73
- * ip: "127.0.0.1",
74
- * port: 1080,
75
- * },
76
- * });
77
68
  * ```
78
69
  */
79
70
  export type SocksProxyType = BasicProxyInterface & {
@@ -81,32 +72,10 @@ export type SocksProxyType = BasicProxyInterface & {
81
72
  socksType: 4 | 5;
82
73
  };
83
74
  /**
84
- * Union type for all supported proxy configurations.
85
- *
86
- * Pass to `TelegramClientParams.proxy` to route all connections through a proxy.
87
- *
88
- * - {@link MTProxyType} — Telegram MTProxy (obfuscated, secret-based)
89
- * - {@link SocksProxyType} — SOCKS4/SOCKS5 (general-purpose, optional auth)
75
+ * Union type for all supported proxy configurations. Pass to
76
+ * `TelegramClientParams.proxy` to route all connections through a proxy.
90
77
  */
91
78
  export type ProxyInterface = MTProxyType | SocksProxyType;
92
- /**
93
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
94
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
95
- * @internal
96
- */
97
- declare class MTProxyIO {
98
- header?: Buffer;
99
- private connection;
100
- private _encrypt?;
101
- private _decrypt?;
102
- private _packetClass;
103
- private _secret;
104
- private _dcId;
105
- constructor(connection: TCPMTProxy);
106
- initHeader(): Promise<void>;
107
- read(n: number): Promise<Buffer<any>>;
108
- write(data: Buffer): void;
109
- }
110
79
  interface TCPMTProxyInterfaceParams {
111
80
  ip: string;
112
81
  port: number;
@@ -116,25 +85,47 @@ interface TCPMTProxyInterfaceParams {
116
85
  socket: typeof PromisedNetSockets;
117
86
  }
118
87
  /**
119
- * Connection that routes traffic through a Telegram MTProxy server.
88
+ * Generates the MTProxy obfuscation header and wraps every subsequent
89
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
90
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
91
+ * reversed copy supplies the server→client side.
120
92
  *
121
- * Connects to the proxy address (not Telegram directly), performs the
122
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
123
- * all MTProto traffic through the proxy with AES-CTR encryption.
93
+ * @internal
94
+ */
95
+ declare class MTProxyIO {
96
+ header?: Buffer;
97
+ private readonly stream;
98
+ private readonly packetCodec;
99
+ private readonly secret;
100
+ private readonly dcId;
101
+ private encryptor?;
102
+ private decryptor?;
103
+ constructor(connection: TCPMTProxy);
104
+ initHeader(): Promise<void>;
105
+ read(n: number): Promise<Buffer>;
106
+ write(data: Buffer): void;
107
+ }
108
+ /**
109
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
110
+ * to the proxy address (not Telegram directly), performs the MTProxy
111
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
112
+ * encryption. When the secret carries a fake-TLS prefix the underlying
113
+ * socket is first wrapped in a {@link FakeTlsSocket}.
124
114
  *
125
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
126
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
115
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
116
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
127
117
  * and the client will select the correct connection class automatically.
128
118
  */
129
119
  export declare class TCPMTProxy extends ObfuscatedConnection {
130
120
  ObfuscatedIO: typeof MTProxyIO;
131
121
  _secret: Buffer;
132
- constructor({ ip, port, dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
122
+ _fakeTlsDomain?: string;
123
+ constructor({ dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
124
+ _initConn(): Promise<void>;
133
125
  }
134
126
  /**
135
- * MTProxy connection using the Abridged packet codec.
136
- *
137
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
127
+ * MTProxy connection using the Abridged packet codec — automatically selected
128
+ * when `proxy.MTProxy` is `true`.
138
129
  */
139
130
  export declare class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
140
131
  PacketCodecClass: typeof AbridgedPacketCodec;
@@ -1,136 +1,403 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectionTCPMTProxyAbridged = exports.TCPMTProxy = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const Connection_1 = require("./Connection");
5
6
  const TCPAbridged_1 = require("./TCPAbridged");
6
7
  const Helpers_1 = require("../../Helpers");
7
8
  const CTR_1 = require("../../crypto/CTR");
9
+ const SECRET_LEN = 16;
10
+ const PREFIX_FAKE_TLS = 0xee;
11
+ const PREFIX_DD_PADDING = 0xdd;
12
+ function decodeSecret(input) {
13
+ return /^[0-9a-f]+$/i.test(input)
14
+ ? Buffer.from(input, "hex")
15
+ : Buffer.from(input, "base64");
16
+ }
17
+ function parseProxySecret(input) {
18
+ if (!input) {
19
+ throw new Error("MTProxy: secret is required");
20
+ }
21
+ const raw = decodeSecret(input);
22
+ if (raw.length > 1 + SECRET_LEN && raw[0] === PREFIX_FAKE_TLS) {
23
+ return {
24
+ key: Buffer.from(raw.subarray(1, 1 + SECRET_LEN)),
25
+ fakeTlsDomain: raw.subarray(1 + SECRET_LEN).toString("utf8"),
26
+ };
27
+ }
28
+ if (raw.length === 1 + SECRET_LEN && raw[0] === PREFIX_DD_PADDING) {
29
+ return { key: Buffer.from(raw.subarray(1)) };
30
+ }
31
+ if (raw.length === SECRET_LEN) {
32
+ return { key: raw };
33
+ }
34
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, ` +
35
+ `${1 + SECRET_LEN} with 0xdd prefix, ` +
36
+ `or 0xee + ${SECRET_LEN} bytes + domain — got ${raw.length} bytes`);
37
+ }
38
+ const OBF_HEADER_LEN = 64;
39
+ const OBF_TAG_OFFSET = 56;
40
+ const OBF_DC_OFFSET = 60;
41
+ /** Header prefixes that collide with HTTP or other protocol sniffers. */
42
+ const FORBIDDEN_HEADER_PREFIXES = [
43
+ Buffer.from("50567247", "hex"),
44
+ Buffer.from("474554", "hex"),
45
+ Buffer.from("504f5354", "hex"),
46
+ Buffer.from("eeeeeeee", "hex"),
47
+ ];
48
+ function pickObfuscationHeader() {
49
+ while (true) {
50
+ const candidate = (0, Helpers_1.generateRandomBytes)(OBF_HEADER_LEN);
51
+ if (candidate[0] === 0xef)
52
+ continue;
53
+ if (candidate.subarray(4, 8).equals(Buffer.alloc(4)))
54
+ continue;
55
+ const head4 = candidate.subarray(0, 4);
56
+ if (FORBIDDEN_HEADER_PREFIXES.some((p) => p.equals(head4)))
57
+ continue;
58
+ return candidate;
59
+ }
60
+ }
8
61
  /**
9
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
10
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
62
+ * Generates the MTProxy obfuscation header and wraps every subsequent
63
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
64
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
65
+ * reversed copy supplies the server→client side.
66
+ *
11
67
  * @internal
12
68
  */
13
69
  class MTProxyIO {
14
70
  constructor(connection) {
15
- this.header = undefined;
16
- this.connection = connection.socket;
17
- this._packetClass =
71
+ this.stream = connection.socket;
72
+ this.packetCodec =
18
73
  connection.PacketCodecClass;
19
- this._secret = connection._secret;
20
- this._dcId = connection._dcId;
74
+ this.secret = connection._secret;
75
+ this.dcId = connection._dcId;
21
76
  }
22
77
  async initHeader() {
23
- let secret = this._secret;
24
- const isDD = secret.length == 17 && secret[0] == 0xdd;
25
- secret = isDD ? secret.slice(1) : secret;
26
- if (secret.length != 16) {
27
- throw new Error("MTProxy secret must be a hex-string representing 16 bytes");
28
- }
29
- const keywords = [
30
- Buffer.from("50567247", "hex"),
31
- Buffer.from("474554", "hex"),
32
- Buffer.from("504f5354", "hex"),
33
- Buffer.from("eeeeeeee", "hex"),
34
- ];
35
- let random;
36
- // eslint-disable-next-line no-constant-condition
37
- while (true) {
38
- random = (0, Helpers_1.generateRandomBytes)(64);
39
- if (random[0] !== 0xef &&
40
- !random.slice(4, 8).equals(Buffer.alloc(4))) {
41
- let ok = true;
42
- for (const key of keywords) {
43
- if (key.equals(random.slice(0, 4))) {
44
- ok = false;
45
- break;
46
- }
47
- }
48
- if (ok) {
49
- break;
50
- }
51
- }
78
+ if (this.secret.length !== SECRET_LEN) {
79
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, got ${this.secret.length}`);
52
80
  }
53
- random = random.toJSON().data;
54
- const randomReversed = Buffer.from(random.slice(8, 56)).reverse();
55
- // Encryption has "continuous buffer" enabled
56
- const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(random.slice(8, 40)), secret]));
57
- const encryptIv = Buffer.from(random.slice(40, 56));
58
- const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(randomReversed.slice(0, 32)), secret]));
59
- const decryptIv = Buffer.from(randomReversed.slice(32, 48));
60
- const encryptor = new CTR_1.CTR(encryptKey, encryptIv);
61
- const decryptor = new CTR_1.CTR(decryptKey, decryptIv);
62
- random = Buffer.concat([
63
- Buffer.from(random.slice(0, 56)),
64
- this._packetClass.obfuscateTag,
65
- Buffer.from(random.slice(60)),
66
- ]);
67
- const dcIdBytes = Buffer.alloc(2);
68
- dcIdBytes.writeInt8(this._dcId, 0);
69
- random = Buffer.concat([
70
- Buffer.from(random.slice(0, 60)),
71
- dcIdBytes,
72
- Buffer.from(random.slice(62)),
73
- ]);
74
- random = Buffer.concat([
75
- Buffer.from(random.slice(0, 56)),
76
- Buffer.from(encryptor.encrypt(random).slice(56, 64)),
77
- Buffer.from(random.slice(64)),
78
- ]);
79
- this.header = random;
80
- this._encrypt = encryptor;
81
- this._decrypt = decryptor;
81
+ const header = pickObfuscationHeader();
82
+ const reversed = Buffer.from(header.subarray(8, 56)).reverse();
83
+ const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([header.subarray(8, 40), this.secret]));
84
+ const encryptIv = Buffer.from(header.subarray(40, 56));
85
+ const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([reversed.subarray(0, 32), this.secret]));
86
+ const decryptIv = Buffer.from(reversed.subarray(32, 48));
87
+ this.encryptor = new CTR_1.CTR(encryptKey, encryptIv);
88
+ this.decryptor = new CTR_1.CTR(decryptKey, decryptIv);
89
+ // Stamp protocol tag and DC id (low byte first, byte 61 zeroed —
90
+ // matches gramjs / official client behavior, even for negative DC ids).
91
+ this.packetCodec.obfuscateTag.copy(header, OBF_TAG_OFFSET);
92
+ header.writeInt8(this.dcId, OBF_DC_OFFSET);
93
+ header[OBF_DC_OFFSET + 1] = 0;
94
+ // Re-encrypt the stamped tail in place — the CTR counter ends up
95
+ // advanced by all 64 bytes, matching the server's view.
96
+ const encryptedTail = this.encryptor
97
+ .encrypt(header)
98
+ .subarray(OBF_TAG_OFFSET, OBF_HEADER_LEN);
99
+ encryptedTail.copy(header, OBF_TAG_OFFSET);
100
+ this.header = header;
82
101
  }
83
102
  async read(n) {
84
- const data = await this.connection.readExactly(n);
85
- return this._decrypt.encrypt(data);
103
+ const data = await this.stream.readExactly(n);
104
+ return this.decryptor.encrypt(data);
86
105
  }
87
106
  write(data) {
88
- this.connection.write(this._encrypt.encrypt(data));
107
+ this.stream.write(this.encryptor.encrypt(data));
108
+ }
109
+ }
110
+ const TLS_RECORD_HEADER_LEN = 5;
111
+ const TLS_RECORD_MAX_PAYLOAD = 16384;
112
+ const TLS_RECORD_HANDSHAKE = 0x16;
113
+ const TLS_RECORD_CHANGE_CIPHER_SPEC = 0x14;
114
+ const TLS_RECORD_APPLICATION_DATA = 0x17;
115
+ const TLS_APP_DATA_PREFIX = Buffer.from([0x17, 0x03, 0x03]);
116
+ const FAKE_TLS_HELLO_SIZE = 517;
117
+ const FAKE_TLS_HELLO_RANDOM_OFFSET = 11;
118
+ const FAKE_TLS_HELLO_RANDOM_LEN = 32;
119
+ /** 5-byte TLS record + 4-byte handshake header + 2-byte version = 114. */
120
+ const FAKE_TLS_FIXED_HEADER_SIZE = 114;
121
+ const FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET = 28;
122
+ const GREASE = 0x0a;
123
+ async function readTlsRecord(stream) {
124
+ const hdr = await stream.readExactly(TLS_RECORD_HEADER_LEN);
125
+ const type = hdr[0];
126
+ const len = hdr.readUInt16BE(3);
127
+ const payload = await stream.readExactly(len);
128
+ return { type, payload };
129
+ }
130
+ function tlsRecordTypeName(type) {
131
+ switch (type) {
132
+ case TLS_RECORD_HANDSHAKE:
133
+ return "Handshake";
134
+ case TLS_RECORD_CHANGE_CIPHER_SPEC:
135
+ return "ChangeCipherSpec";
136
+ case TLS_RECORD_APPLICATION_DATA:
137
+ return "ApplicationData";
138
+ default:
139
+ return `0x${type.toString(16).padStart(2, "0")}`;
140
+ }
141
+ }
142
+ function tlsExtension(type, body) {
143
+ const content = Buffer.isBuffer(body) ? body : Buffer.from(body);
144
+ const out = Buffer.alloc(4 + content.length);
145
+ out.writeUInt16BE(type, 0);
146
+ out.writeUInt16BE(content.length, 2);
147
+ content.copy(out, 4);
148
+ return out;
149
+ }
150
+ function sniExtension(domain) {
151
+ const dom = Buffer.from(domain, "utf8");
152
+ const body = Buffer.alloc(5 + dom.length);
153
+ body.writeUInt16BE(dom.length + 3, 0);
154
+ body.writeUInt8(0x00, 2);
155
+ body.writeUInt16BE(dom.length, 3);
156
+ dom.copy(body, 5);
157
+ return tlsExtension(0x0000, body);
158
+ }
159
+ function keyShareExtension() {
160
+ const x25519Pub = (0, Helpers_1.generateRandomBytes)(32);
161
+ const entries = Buffer.concat([
162
+ Buffer.from([GREASE, GREASE, 0x00, 0x01, 0x00]),
163
+ Buffer.from([0x00, 0x1d, 0x00, 0x20]),
164
+ x25519Pub,
165
+ ]);
166
+ const body = Buffer.alloc(2 + entries.length);
167
+ body.writeUInt16BE(entries.length, 0);
168
+ entries.copy(body, 2);
169
+ return tlsExtension(0x0033, body);
170
+ }
171
+ /**
172
+ * Input-independent extensions in Chrome 105+ order. SNI and key_share are
173
+ * spliced in at runtime (see `buildClientHello`). Layout is intentionally
174
+ * stable for JA3 fingerprint similarity.
175
+ */
176
+ const STATIC_EXTENSIONS = [
177
+ tlsExtension(0x0a0a, []),
178
+ tlsExtension(0x0017, []),
179
+ tlsExtension(0xff01, [0x00]),
180
+ tlsExtension(0x000a, [
181
+ 0x00, 0x08, GREASE, GREASE, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
182
+ ]),
183
+ tlsExtension(0x000b, [0x01, 0x00]),
184
+ tlsExtension(0x0023, []),
185
+ tlsExtension(0x0010, [
186
+ 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31,
187
+ 0x2e, 0x31,
188
+ ]),
189
+ tlsExtension(0x0005, [0x01, 0x00, 0x00, 0x00, 0x00]),
190
+ tlsExtension(0x000d, [
191
+ 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05,
192
+ 0x05, 0x01, 0x08, 0x06, 0x06, 0x01,
193
+ ]),
194
+ tlsExtension(0x0012, []),
195
+ tlsExtension(0x002d, [0x01, 0x01]),
196
+ tlsExtension(0x002b, [0x06, GREASE, GREASE, 0x03, 0x04, 0x03, 0x03]),
197
+ tlsExtension(0x001b, [0x02, 0x00, 0x02]),
198
+ tlsExtension(0x0a0a, [0x00]),
199
+ ];
200
+ const STATIC_EXTENSIONS_LEN = STATIC_EXTENSIONS.reduce((s, e) => s + e.length, 0);
201
+ const CIPHER_SUITES = Buffer.from([
202
+ GREASE, GREASE,
203
+ 0x13, 0x01, 0x13, 0x02, 0x13, 0x03,
204
+ 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
205
+ 0xcc, 0xa9, 0xcc, 0xa8,
206
+ 0xc0, 0x13, 0xc0, 0x14,
207
+ 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35,
208
+ ]);
209
+ /**
210
+ * Builds the 517-byte Chrome-like TLS 1.2 ClientHello. The 32-byte `random`
211
+ * field at offset 11 is left zeroed — the caller stamps it with the
212
+ * HMAC-derived value before sending.
213
+ */
214
+ function buildClientHello(domain, sessionId) {
215
+ const sni = sniExtension(domain);
216
+ const keyShare = keyShareExtension();
217
+ let extensionsLen = STATIC_EXTENSIONS_LEN + sni.length + keyShare.length;
218
+ const paddingPayloadLen = FAKE_TLS_HELLO_SIZE - FAKE_TLS_FIXED_HEADER_SIZE - extensionsLen - 4;
219
+ if (paddingPayloadLen < 0) {
220
+ throw new Error(`FakeTLS: domain too long (${domain.length} bytes); ClientHello overflows by ${-paddingPayloadLen} bytes`);
221
+ }
222
+ const padding = tlsExtension(0x0015, Buffer.alloc(paddingPayloadLen));
223
+ extensionsLen += padding.length;
224
+ // SNI follows the leading GREASE; key_share follows the SCT extension.
225
+ const KEY_SHARE_INSERT_AFTER = 11;
226
+ const extensions = [
227
+ STATIC_EXTENSIONS[0],
228
+ sni,
229
+ ...STATIC_EXTENSIONS.slice(1, KEY_SHARE_INSERT_AFTER + 1),
230
+ keyShare,
231
+ ...STATIC_EXTENSIONS.slice(KEY_SHARE_INSERT_AFTER + 1),
232
+ padding,
233
+ ];
234
+ const hello = Buffer.concat([
235
+ Buffer.from([0x16, 0x03, 0x01, 0x02, 0x00]),
236
+ Buffer.from([0x01, 0x00, 0x01, 0xfc]),
237
+ Buffer.from([0x03, 0x03]),
238
+ Buffer.alloc(FAKE_TLS_HELLO_RANDOM_LEN),
239
+ Buffer.from([sessionId.length]),
240
+ sessionId,
241
+ Buffer.from([
242
+ (CIPHER_SUITES.length >> 8) & 0xff,
243
+ CIPHER_SUITES.length & 0xff,
244
+ ]),
245
+ CIPHER_SUITES,
246
+ Buffer.from([0x01, 0x00]),
247
+ Buffer.from([(extensionsLen >> 8) & 0xff, extensionsLen & 0xff]),
248
+ ...extensions,
249
+ ]);
250
+ if (hello.length !== FAKE_TLS_HELLO_SIZE) {
251
+ throw new Error(`FakeTLS: ClientHello size mismatch — got ${hello.length}, expected ${FAKE_TLS_HELLO_SIZE}`);
89
252
  }
253
+ return hello;
90
254
  }
91
255
  /**
92
- * Connection that routes traffic through a Telegram MTProxy server.
256
+ * Wraps a {@link PromisedNetSockets} in MTProto fake-TLS framing. On
257
+ * {@link handshake}: sends a Chrome-like ClientHello with the random field
258
+ * stamped to `HMAC-SHA256(secret, hello)` (last 4 bytes XOR'd with unix
259
+ * time), then verifies the server's random equals
260
+ * `HMAC-SHA256(secret, client_random || server_response_zeroed)`. Post-
261
+ * handshake, all bytes ride inside TLS application_data records.
93
262
  *
94
- * Connects to the proxy address (not Telegram directly), performs the
95
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
96
- * all MTProto traffic through the proxy with AES-CTR encryption.
263
+ * Exposes only the subset of {@link PromisedNetSockets} consumed by
264
+ * {@link MTProxyIO} and the {@link ObfuscatedConnection} base class so it
265
+ * can be hot-swapped in place of the raw socket.
97
266
  *
98
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
99
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
267
+ * @internal
268
+ */
269
+ class FakeTlsSocket {
270
+ constructor(inner, secret, domain) {
271
+ this.clientRandom = Buffer.alloc(0);
272
+ this.readBuf = Buffer.alloc(0);
273
+ if (secret.length !== SECRET_LEN) {
274
+ throw new Error(`FakeTLS: secret must be ${SECRET_LEN} bytes, got ${secret.length}`);
275
+ }
276
+ this.inner = inner;
277
+ this.secret = secret;
278
+ this.domain = domain;
279
+ }
280
+ async handshake() {
281
+ const hello = this.sendClientHello();
282
+ this.inner.write(hello);
283
+ await this.readAndVerifyServerHandshake();
284
+ }
285
+ write(data) {
286
+ for (let off = 0; off < data.length; off += TLS_RECORD_MAX_PAYLOAD) {
287
+ const chunkLen = Math.min(TLS_RECORD_MAX_PAYLOAD, data.length - off);
288
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
289
+ TLS_APP_DATA_PREFIX.copy(hdr, 0);
290
+ hdr.writeUInt16BE(chunkLen, 3);
291
+ this.inner.write(Buffer.concat([hdr, data.subarray(off, off + chunkLen)]));
292
+ }
293
+ }
294
+ async readExactly(n) {
295
+ while (this.readBuf.length < n) {
296
+ const { type, payload } = await readTlsRecord(this.inner);
297
+ if (type !== TLS_RECORD_APPLICATION_DATA)
298
+ continue;
299
+ this.readBuf = Buffer.concat([this.readBuf, payload]);
300
+ }
301
+ const out = Buffer.from(this.readBuf.subarray(0, n));
302
+ this.readBuf = this.readBuf.subarray(n);
303
+ return out;
304
+ }
305
+ async close() {
306
+ await this.inner.close();
307
+ }
308
+ sendClientHello() {
309
+ const sessionId = (0, Helpers_1.generateRandomBytes)(32);
310
+ const hello = buildClientHello(this.domain, sessionId);
311
+ const stamp = (0, node_crypto_1.createHmac)("sha256", this.secret).update(hello).digest();
312
+ const now = Math.floor(Date.now() / 1000) >>> 0;
313
+ const lastWord = (stamp.readUInt32BE(FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET) ^ now) >>>
314
+ 0;
315
+ stamp.writeUInt32BE(lastWord, FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET);
316
+ stamp.copy(hello, FAKE_TLS_HELLO_RANDOM_OFFSET);
317
+ this.clientRandom = stamp;
318
+ return hello;
319
+ }
320
+ // Expects the canonical 3-record server response. Anything else fails
321
+ // loudly rather than silently corrupting the downstream MTProxy stream.
322
+ async readAndVerifyServerHandshake() {
323
+ const expectedTypes = [
324
+ TLS_RECORD_HANDSHAKE,
325
+ TLS_RECORD_CHANGE_CIPHER_SPEC,
326
+ TLS_RECORD_APPLICATION_DATA,
327
+ ];
328
+ const chunks = [];
329
+ let serverRandomOffset = -1;
330
+ let offset = 0;
331
+ for (let i = 0; i < expectedTypes.length; i++) {
332
+ const { type, payload } = await readTlsRecord(this.inner);
333
+ if (type !== expectedTypes[i]) {
334
+ throw new Error(`FakeTLS: expected ${tlsRecordTypeName(expectedTypes[i])} at record ${i}, got ${tlsRecordTypeName(type)}`);
335
+ }
336
+ if (i === 0) {
337
+ serverRandomOffset = offset + FAKE_TLS_HELLO_RANDOM_OFFSET;
338
+ }
339
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
340
+ hdr[0] = type;
341
+ hdr[1] = 0x03;
342
+ hdr[2] = 0x03;
343
+ hdr.writeUInt16BE(payload.length, 3);
344
+ chunks.push(hdr, payload);
345
+ offset += TLS_RECORD_HEADER_LEN + payload.length;
346
+ }
347
+ const serverResp = Buffer.concat(chunks);
348
+ const serverRandom = Buffer.from(serverResp.subarray(serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN));
349
+ serverResp.fill(0, serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN);
350
+ const expected = (0, node_crypto_1.createHmac)("sha256", this.secret)
351
+ .update(this.clientRandom)
352
+ .update(serverResp)
353
+ .digest();
354
+ if (!expected.equals(serverRandom)) {
355
+ throw new Error("FakeTLS: server HMAC verification failed");
356
+ }
357
+ }
358
+ }
359
+ /**
360
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
361
+ * to the proxy address (not Telegram directly), performs the MTProxy
362
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
363
+ * encryption. When the secret carries a fake-TLS prefix the underlying
364
+ * socket is first wrapped in a {@link FakeTlsSocket}.
365
+ *
366
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
367
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
100
368
  * and the client will select the correct connection class automatically.
101
369
  */
102
370
  class TCPMTProxy extends Connection_1.ObfuscatedConnection {
103
- constructor({ ip, port, dcId, loggers, proxy, socket, }) {
371
+ constructor({ dcId, loggers, proxy, socket, }) {
104
372
  super({
105
373
  ip: proxy.ip,
106
374
  port: proxy.port,
107
- dcId: dcId,
108
- loggers: loggers,
109
- socket: socket,
110
- proxy: proxy,
375
+ dcId,
376
+ loggers,
377
+ socket,
378
+ proxy,
111
379
  });
112
380
  this.ObfuscatedIO = MTProxyIO;
113
381
  if (!("MTProxy" in proxy)) {
114
- throw new Error("This connection only supports MPTProxies");
115
- }
116
- if (!proxy.secret) {
117
- throw new Error("You need to provide the secret for the MTProxy");
382
+ throw new Error("This connection only supports MTProxies");
118
383
  }
119
- if (proxy.secret && proxy.secret.match(/^[0-9a-f]+$/i)) {
120
- // probably hex
121
- this._secret = Buffer.from(proxy.secret, "hex");
122
- }
123
- else {
124
- // probably b64
125
- this._secret = Buffer.from(proxy.secret, "base64");
384
+ const parsed = parseProxySecret(proxy.secret);
385
+ this._secret = parsed.key;
386
+ this._fakeTlsDomain = parsed.fakeTlsDomain;
387
+ }
388
+ async _initConn() {
389
+ if (this._fakeTlsDomain) {
390
+ const tls = new FakeTlsSocket(this.socket, this._secret, this._fakeTlsDomain);
391
+ await tls.handshake();
392
+ this.socket = tls;
126
393
  }
394
+ await super._initConn();
127
395
  }
128
396
  }
129
397
  exports.TCPMTProxy = TCPMTProxy;
130
398
  /**
131
- * MTProxy connection using the Abridged packet codec.
132
- *
133
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
399
+ * MTProxy connection using the Abridged packet codec — automatically selected
400
+ * when `proxy.MTProxy` is `true`.
134
401
  */
135
402
  class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
136
403
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teleproto",
3
- "version": "1.225.2",
3
+ "version": "1.225.3",
4
4
  "description": "Telegram MTProto API client library written in TypeScript",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -63,7 +63,7 @@ export declare abstract class Session {
63
63
  * to suit several purposes (e.g. user only provided its ID or wishes
64
64
  * to use a cached username to avoid extra RPC).
65
65
  */
66
- abstract getInputEntity(key: EntityLike): Api.TypeInputPeer;
66
+ abstract getInputEntity(key: EntityLike): Api.TypeInputPeer | Promise<Api.TypeInputPeer>;
67
67
  /**
68
68
  * Returns an ID of the takeout process initialized for this session,
69
69
  * or `None` if there's no were any unfinished takeout requests.
@@ -97,10 +97,10 @@ export declare abstract class Session {
97
97
  */
98
98
  abstract save(): void;
99
99
  /**
100
- * Called upon client.log_out(). Should delete the stored
100
+ * Called upon client.logOut(). Should delete the stored
101
101
  * information from disk since it's not valid anymore.
102
102
  */
103
- abstract delete(): void;
103
+ abstract delete(): void | Promise<void>;
104
104
  /**
105
105
  * Processes the input ``TLObject`` or ``list`` and saves
106
106
  * whatever information is relevant (e.g., ID or access hash).
@@ -115,7 +115,15 @@ class MemorySession extends Abstract_1.Session {
115
115
  close() { }
116
116
  save() { }
117
117
  async load() { }
118
- delete() { }
118
+ delete() {
119
+ this._authKey = undefined;
120
+ this._dcId = 0;
121
+ this._serverAddress = undefined;
122
+ this._port = undefined;
123
+ this._dcAuthKeys.clear();
124
+ this._entities.clear();
125
+ this._updateStates = {};
126
+ }
119
127
  _entityValuesToRow(id, hash, username, phone, name) {
120
128
  // While this is a simple implementation it might be overrode by,
121
129
  // other classes so they don't need to implement the plural form
@@ -24,6 +24,7 @@ export declare class StoreSession extends MemorySession {
24
24
  get testServers(): boolean;
25
25
  set authKey(value: AuthKey | undefined);
26
26
  get authKey(): AuthKey | undefined;
27
+ delete(): void;
27
28
  processEntities(tlo: any): void;
28
29
  getEntityRowsById(id: string | bigInt.BigInteger, exact?: boolean): any;
29
30
  }
@@ -113,6 +113,10 @@ class StoreSession extends Memory_1.MemorySession {
113
113
  get authKey() {
114
114
  return this._authKey;
115
115
  }
116
+ delete() {
117
+ this.store.clearAll();
118
+ super.delete();
119
+ }
116
120
  processEntities(tlo) {
117
121
  const rows = this._entitiesToRows(tlo);
118
122
  if (!rows) {
@@ -28,5 +28,6 @@ export declare class StringSession extends MemorySession {
28
28
  */
29
29
  static decode(x: string): Buffer<ArrayBuffer>;
30
30
  load(): Promise<void>;
31
+ delete(): void;
31
32
  save(): string;
32
33
  }
@@ -88,6 +88,10 @@ class StringSession extends Memory_1.MemorySession {
88
88
  await this._authKey.setKey(this._key);
89
89
  }
90
90
  }
91
+ delete() {
92
+ this._key = undefined;
93
+ super.delete();
94
+ }
91
95
  save() {
92
96
  if (!this.authKey || !this.serverAddress || !this.port) {
93
97
  return "";