mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-29 23:17:25 +03:00 
			
		
		
		
	Initial implementation of MTProxy support (#1107)
This commit is contained in:
		
							parent
							
								
									45fdd098cc
								
							
						
					
					
						commit
						45d0ba9e2f
					
				|  | @ -9,7 +9,7 @@ from datetime import datetime, timezone | ||||||
| from .. import version, __name__ as __base_name__ | from .. import version, __name__ as __base_name__ | ||||||
| from ..crypto import rsa | from ..crypto import rsa | ||||||
| from ..extensions import markdown | from ..extensions import markdown | ||||||
| from ..network import MTProtoSender, ConnectionTcpFull | from ..network import MTProtoSender, ConnectionTcpFull, ConnectionTcpMTProxy | ||||||
| from ..sessions import Session, SQLiteSession, MemorySession | from ..sessions import Session, SQLiteSession, MemorySession | ||||||
| from ..tl import TLObject, functions, types | from ..tl import TLObject, functions, types | ||||||
| from ..tl.alltlobjects import LAYER | from ..tl.alltlobjects import LAYER | ||||||
|  | @ -245,6 +245,8 @@ class TelegramBaseClient(abc.ABC): | ||||||
| 
 | 
 | ||||||
|         assert isinstance(connection, type) |         assert isinstance(connection, type) | ||||||
|         self._connection = connection |         self._connection = connection | ||||||
|  |         init_proxy = None if connection is not ConnectionTcpMTProxy else \ | ||||||
|  |             types.InputClientProxy(*ConnectionTcpMTProxy.address_info(proxy)) | ||||||
| 
 | 
 | ||||||
|         # Used on connection. Capture the variables in a lambda since |         # Used on connection. Capture the variables in a lambda since | ||||||
|         # exporting clients need to create this InvokeWithLayerRequest. |         # exporting clients need to create this InvokeWithLayerRequest. | ||||||
|  | @ -258,7 +260,8 @@ class TelegramBaseClient(abc.ABC): | ||||||
|                 lang_code=lang_code, |                 lang_code=lang_code, | ||||||
|                 system_lang_code=system_lang_code, |                 system_lang_code=system_lang_code, | ||||||
|                 lang_pack='',  # "langPacks are for official apps only" |                 lang_pack='',  # "langPacks are for official apps only" | ||||||
|                 query=x |                 query=x, | ||||||
|  |                 proxy=init_proxy | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -345,6 +348,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|         await self._sender.connect(self._connection( |         await self._sender.connect(self._connection( | ||||||
|             self.session.server_address, |             self.session.server_address, | ||||||
|             self.session.port, |             self.session.port, | ||||||
|  |             self.session.dc_id, | ||||||
|             loop=self._loop, |             loop=self._loop, | ||||||
|             loggers=self._log, |             loggers=self._log, | ||||||
|             proxy=self._proxy |             proxy=self._proxy | ||||||
|  | @ -474,6 +478,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|         await sender.connect(self._connection( |         await sender.connect(self._connection( | ||||||
|             dc.ip_address, |             dc.ip_address, | ||||||
|             dc.port, |             dc.port, | ||||||
|  |             dc.id, | ||||||
|             loop=self._loop, |             loop=self._loop, | ||||||
|             loggers=self._log, |             loggers=self._log, | ||||||
|             proxy=self._proxy |             proxy=self._proxy | ||||||
|  | @ -505,6 +510,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|                 await sender.connect(self._connection( |                 await sender.connect(self._connection( | ||||||
|                     dc.ip_address, |                     dc.ip_address, | ||||||
|                     dc.port, |                     dc.port, | ||||||
|  |                     dc.id, | ||||||
|                     loop=self._loop, |                     loop=self._loop, | ||||||
|                     loggers=self._log, |                     loggers=self._log, | ||||||
|                     proxy=self._proxy |                     proxy=self._proxy | ||||||
|  |  | ||||||
|  | @ -6,6 +6,6 @@ from .mtprotoplainsender import MTProtoPlainSender | ||||||
| from .authenticator import do_authentication | from .authenticator import do_authentication | ||||||
| from .mtprotosender import MTProtoSender | from .mtprotosender import MTProtoSender | ||||||
| from .connection import ( | from .connection import ( | ||||||
|     ConnectionTcpFull, ConnectionTcpAbridged, ConnectionTcpObfuscated, |     ConnectionTcpFull, ConnectionTcpIntermediate, ConnectionTcpAbridged, | ||||||
|     ConnectionTcpIntermediate, ConnectionHttp |     ConnectionTcpObfuscated, ConnectionTcpMTProxy, ConnectionHttp | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from ..tl.types import ( | ||||||
|     ResPQ, PQInnerData, ServerDHParamsFail, ServerDHParamsOk, |     ResPQ, PQInnerData, ServerDHParamsFail, ServerDHParamsOk, | ||||||
|     ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail |     ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail | ||||||
| ) | ) | ||||||
| from .. import helpers as utils | from .. import helpers | ||||||
| from ..crypto import AES, AuthKey, Factorization, rsa | from ..crypto import AES, AuthKey, Factorization, rsa | ||||||
| from ..errors import SecurityError | from ..errors import SecurityError | ||||||
| from ..extensions import BinaryReader | from ..extensions import BinaryReader | ||||||
|  | @ -94,7 +94,7 @@ async def do_authentication(sender): | ||||||
|         'Step 2.2 answer was %s' % server_dh_params |         'Step 2.2 answer was %s' % server_dh_params | ||||||
| 
 | 
 | ||||||
|     # Step 3 sending: Complete DH Exchange |     # Step 3 sending: Complete DH Exchange | ||||||
|     key, iv = utils.generate_key_data_from_nonce( |     key, iv = helpers.generate_key_data_from_nonce( | ||||||
|         res_pq.server_nonce, new_nonce |         res_pq.server_nonce, new_nonce | ||||||
|     ) |     ) | ||||||
|     if len(server_dh_params.encrypted_answer) % 16 != 0: |     if len(server_dh_params.encrypted_answer) % 16 != 0: | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| from .tcpfull import ConnectionTcpFull | from .tcpfull import ConnectionTcpFull | ||||||
|  | from .tcpintermediate import ConnectionTcpIntermediate | ||||||
| from .tcpabridged import ConnectionTcpAbridged | from .tcpabridged import ConnectionTcpAbridged | ||||||
| from .tcpobfuscated import ConnectionTcpObfuscated | from .tcpobfuscated import ConnectionTcpObfuscated | ||||||
| from .tcpintermediate import ConnectionTcpIntermediate | from .tcpmtproxy import ConnectionTcpMTProxy | ||||||
| from .http import ConnectionHttp | from .http import ConnectionHttp | ||||||
|  |  | ||||||
|  | @ -18,9 +18,10 @@ class Connection(abc.ABC): | ||||||
|     ``ConnectionError``, which will raise when attempting to send if |     ``ConnectionError``, which will raise when attempting to send if | ||||||
|     the client is disconnected (includes remote disconnections). |     the client is disconnected (includes remote disconnections). | ||||||
|     """ |     """ | ||||||
|     def __init__(self, ip, port, *, loop, loggers, proxy=None): |     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||||
|         self._ip = ip |         self._ip = ip | ||||||
|         self._port = port |         self._port = port | ||||||
|  |         self._dc_id = dc_id  # only for MTProxy, it's an abstraction leak | ||||||
|         self._loop = loop |         self._loop = loop | ||||||
|         self._log = loggers[__name__] |         self._log = loggers[__name__] | ||||||
|         self._proxy = proxy |         self._proxy = proxy | ||||||
|  | @ -94,12 +95,6 @@ class Connection(abc.ABC): | ||||||
|         if self._writer: |         if self._writer: | ||||||
|             self._writer.close() |             self._writer.close() | ||||||
| 
 | 
 | ||||||
|     def clone(self): |  | ||||||
|         """ |  | ||||||
|         Creates a clone of the connection. |  | ||||||
|         """ |  | ||||||
|         return self.__class__(self._ip, self._port, loop=self._loop) |  | ||||||
| 
 |  | ||||||
|     def send(self, data): |     def send(self, data): | ||||||
|         """ |         """ | ||||||
|         Sends a packet of data through this connection mode. |         Sends a packet of data through this connection mode. | ||||||
|  |  | ||||||
|  | @ -10,8 +10,9 @@ class ConnectionTcpFull(Connection): | ||||||
|     Default Telegram mode. Sends 12 additional bytes and |     Default Telegram mode. Sends 12 additional bytes and | ||||||
|     needs to calculate the CRC value of the packet itself. |     needs to calculate the CRC value of the packet itself. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, ip, port, *, loop, loggers, proxy=None): |     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||||
|         super().__init__(ip, port, loop=loop, loggers=loggers, proxy=proxy) |         super().__init__( | ||||||
|  |             ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy) | ||||||
|         self._send_counter = 0 |         self._send_counter = 0 | ||||||
| 
 | 
 | ||||||
|     async def connect(self, timeout=None, ssl=None): |     async def connect(self, timeout=None, ssl=None): | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								telethon/network/connection/tcpmtproxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								telethon/network/connection/tcpmtproxy.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | import hashlib | ||||||
|  | 
 | ||||||
|  | from .tcpobfuscated import ConnectionTcpObfuscated | ||||||
|  | from ...crypto import AESModeCTR | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ConnectionTcpMTProxy(ConnectionTcpObfuscated): | ||||||
|  |     """ | ||||||
|  |     Wrapper around the "obfuscated2" mode that modifies it a little and allows | ||||||
|  |     user to connect to the Telegram proxy servers commonly known as MTProxy. | ||||||
|  |     Implemented very ugly due to the leaky abstractions in Telethon networking | ||||||
|  |     classes that should be refactored later (TODO). | ||||||
|  |     """ | ||||||
|  |     @staticmethod | ||||||
|  |     def address_info(proxy_info): | ||||||
|  |         if proxy_info is None: | ||||||
|  |             raise ValueError("No proxy info specified for MTProxy connection") | ||||||
|  |         return proxy_info[:2] | ||||||
|  | 
 | ||||||
|  |     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||||
|  |         proxy_host, proxy_port = self.address_info(proxy) | ||||||
|  |         super().__init__( | ||||||
|  |             proxy_host, proxy_port, dc_id, loop=loop, loggers=loggers) | ||||||
|  | 
 | ||||||
|  |         # TODO: Implement the dd-secret secure mode (adds noise to fool DPI) | ||||||
|  |         self._secret = bytes.fromhex(proxy[2]) | ||||||
|  |         if len(self._secret) != 16: | ||||||
|  |             raise ValueError( | ||||||
|  |                 "MTProxy secure mode is not implemented for now" | ||||||
|  |                 if len(self._secret) == 17 and self._secret[0] == 0xDD else | ||||||
|  |                 "MTProxy secret must be a hex-string representing 16 bytes" | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     def _compose_key(self, data): | ||||||
|  |         return hashlib.sha256(data + self._secret).digest() | ||||||
|  | 
 | ||||||
|  |     def _compose_tail(self, data): | ||||||
|  |         dc_id_bytes = self._dc_id.to_bytes(2, "little", signed=True) | ||||||
|  |         tail_bytes = super()._compose_tail(data) | ||||||
|  |         return tail_bytes[:4] + dc_id_bytes + tail_bytes[6:] | ||||||
|  | @ -7,12 +7,14 @@ from ...crypto import AESModeCTR | ||||||
| 
 | 
 | ||||||
| class ConnectionTcpObfuscated(ConnectionTcpAbridged): | class ConnectionTcpObfuscated(ConnectionTcpAbridged): | ||||||
|     """ |     """ | ||||||
|     Encodes the packet just like `ConnectionTcpAbridged`, but encrypts |     Mode that Telegram defines as "obfuscated2". Encodes the packet | ||||||
|     every message with a randomly generated key using the |     just like `ConnectionTcpAbridged`, but encrypts every message with | ||||||
|     AES-CTR mode so the packets are harder to discern. |     a randomly generated key using the AES-CTR mode so the packets are | ||||||
|  |     harder to discern. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, ip, port, *, loop, loggers, proxy=None): |     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||||
|         super().__init__(ip, port, loop=loop, loggers=loggers, proxy=proxy) |         super().__init__( | ||||||
|  |             ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy) | ||||||
|         self._aes_encrypt = None |         self._aes_encrypt = None | ||||||
|         self._aes_decrypt = None |         self._aes_decrypt = None | ||||||
| 
 | 
 | ||||||
|  | @ -40,14 +42,22 @@ class ConnectionTcpObfuscated(ConnectionTcpAbridged): | ||||||
|         random_reversed = random[55:7:-1]  # Reversed (8, len=48) |         random_reversed = random[55:7:-1]  # Reversed (8, len=48) | ||||||
| 
 | 
 | ||||||
|         # Encryption has "continuous buffer" enabled |         # Encryption has "continuous buffer" enabled | ||||||
|         encrypt_key = bytes(random[8:40]) |         encrypt_key = self._compose_key(bytes(random[8:40])) | ||||||
|         encrypt_iv = bytes(random[40:56]) |         encrypt_iv = bytes(random[40:56]) | ||||||
|         decrypt_key = bytes(random_reversed[:32]) |         decrypt_key = self._compose_key(bytes(random_reversed[:32])) | ||||||
|         decrypt_iv = bytes(random_reversed[32:48]) |         decrypt_iv = bytes(random_reversed[32:48]) | ||||||
| 
 | 
 | ||||||
|         self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_iv) |         self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_iv) | ||||||
|         self._aes_decrypt = AESModeCTR(decrypt_key, decrypt_iv) |         self._aes_decrypt = AESModeCTR(decrypt_key, decrypt_iv) | ||||||
| 
 | 
 | ||||||
|         random[56:64] = self._aes_encrypt.encrypt(bytes(random))[56:64] |         random[56:64] = self._compose_tail(bytes(random)) | ||||||
|         self._writer.write(random) |         self._writer.write(random) | ||||||
|         await self._writer.drain() |         await self._writer.drain() | ||||||
|  | 
 | ||||||
|  |     # Next functions provide the variable parts of the connection handshake. | ||||||
|  |     # This is necessary to modify obfuscated2 the way that MTProxy requires. | ||||||
|  |     def _compose_key(self, data): | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  |     def _compose_tail(self, data): | ||||||
|  |         return self._aes_encrypt.encrypt(data)[56:64] | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user