mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-29 06:57:50 +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 ..crypto import rsa | ||||
| from ..extensions import markdown | ||||
| from ..network import MTProtoSender, ConnectionTcpFull | ||||
| from ..network import MTProtoSender, ConnectionTcpFull, ConnectionTcpMTProxy | ||||
| from ..sessions import Session, SQLiteSession, MemorySession | ||||
| from ..tl import TLObject, functions, types | ||||
| from ..tl.alltlobjects import LAYER | ||||
|  | @ -245,6 +245,8 @@ class TelegramBaseClient(abc.ABC): | |||
| 
 | ||||
|         assert isinstance(connection, type) | ||||
|         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 | ||||
|         # exporting clients need to create this InvokeWithLayerRequest. | ||||
|  | @ -258,7 +260,8 @@ class TelegramBaseClient(abc.ABC): | |||
|                 lang_code=lang_code, | ||||
|                 system_lang_code=system_lang_code, | ||||
|                 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( | ||||
|             self.session.server_address, | ||||
|             self.session.port, | ||||
|             self.session.dc_id, | ||||
|             loop=self._loop, | ||||
|             loggers=self._log, | ||||
|             proxy=self._proxy | ||||
|  | @ -474,6 +478,7 @@ class TelegramBaseClient(abc.ABC): | |||
|         await sender.connect(self._connection( | ||||
|             dc.ip_address, | ||||
|             dc.port, | ||||
|             dc.id, | ||||
|             loop=self._loop, | ||||
|             loggers=self._log, | ||||
|             proxy=self._proxy | ||||
|  | @ -505,6 +510,7 @@ class TelegramBaseClient(abc.ABC): | |||
|                 await sender.connect(self._connection( | ||||
|                     dc.ip_address, | ||||
|                     dc.port, | ||||
|                     dc.id, | ||||
|                     loop=self._loop, | ||||
|                     loggers=self._log, | ||||
|                     proxy=self._proxy | ||||
|  |  | |||
|  | @ -6,6 +6,6 @@ from .mtprotoplainsender import MTProtoPlainSender | |||
| from .authenticator import do_authentication | ||||
| from .mtprotosender import MTProtoSender | ||||
| from .connection import ( | ||||
|     ConnectionTcpFull, ConnectionTcpAbridged, ConnectionTcpObfuscated, | ||||
|     ConnectionTcpIntermediate, ConnectionHttp | ||||
|     ConnectionTcpFull, ConnectionTcpIntermediate, ConnectionTcpAbridged, | ||||
|     ConnectionTcpObfuscated, ConnectionTcpMTProxy, ConnectionHttp | ||||
| ) | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ from ..tl.types import ( | |||
|     ResPQ, PQInnerData, ServerDHParamsFail, ServerDHParamsOk, | ||||
|     ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail | ||||
| ) | ||||
| from .. import helpers as utils | ||||
| from .. import helpers | ||||
| from ..crypto import AES, AuthKey, Factorization, rsa | ||||
| from ..errors import SecurityError | ||||
| from ..extensions import BinaryReader | ||||
|  | @ -94,7 +94,7 @@ async def do_authentication(sender): | |||
|         'Step 2.2 answer was %s' % server_dh_params | ||||
| 
 | ||||
|     # 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 | ||||
|     ) | ||||
|     if len(server_dh_params.encrypted_answer) % 16 != 0: | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| from .tcpfull import ConnectionTcpFull | ||||
| from .tcpintermediate import ConnectionTcpIntermediate | ||||
| from .tcpabridged import ConnectionTcpAbridged | ||||
| from .tcpobfuscated import ConnectionTcpObfuscated | ||||
| from .tcpintermediate import ConnectionTcpIntermediate | ||||
| from .tcpmtproxy import ConnectionTcpMTProxy | ||||
| from .http import ConnectionHttp | ||||
|  |  | |||
|  | @ -18,9 +18,10 @@ class Connection(abc.ABC): | |||
|     ``ConnectionError``, which will raise when attempting to send if | ||||
|     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._port = port | ||||
|         self._dc_id = dc_id  # only for MTProxy, it's an abstraction leak | ||||
|         self._loop = loop | ||||
|         self._log = loggers[__name__] | ||||
|         self._proxy = proxy | ||||
|  | @ -94,12 +95,6 @@ class Connection(abc.ABC): | |||
|         if self._writer: | ||||
|             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): | ||||
|         """ | ||||
|         Sends a packet of data through this connection mode. | ||||
|  |  | |||
|  | @ -10,8 +10,9 @@ class ConnectionTcpFull(Connection): | |||
|     Default Telegram mode. Sends 12 additional bytes and | ||||
|     needs to calculate the CRC value of the packet itself. | ||||
|     """ | ||||
|     def __init__(self, ip, port, *, loop, loggers, proxy=None): | ||||
|         super().__init__(ip, port, loop=loop, loggers=loggers, proxy=proxy) | ||||
|     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||
|         super().__init__( | ||||
|             ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy) | ||||
|         self._send_counter = 0 | ||||
| 
 | ||||
|     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): | ||||
|     """ | ||||
|     Encodes the packet just like `ConnectionTcpAbridged`, but encrypts | ||||
|     every message with a randomly generated key using the | ||||
|     AES-CTR mode so the packets are harder to discern. | ||||
|     Mode that Telegram defines as "obfuscated2". Encodes the packet | ||||
|     just like `ConnectionTcpAbridged`, but encrypts every message with | ||||
|     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): | ||||
|         super().__init__(ip, port, loop=loop, loggers=loggers, proxy=proxy) | ||||
|     def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): | ||||
|         super().__init__( | ||||
|             ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy) | ||||
|         self._aes_encrypt = None | ||||
|         self._aes_decrypt = None | ||||
| 
 | ||||
|  | @ -40,14 +42,22 @@ class ConnectionTcpObfuscated(ConnectionTcpAbridged): | |||
|         random_reversed = random[55:7:-1]  # Reversed (8, len=48) | ||||
| 
 | ||||
|         # 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]) | ||||
|         decrypt_key = bytes(random_reversed[:32]) | ||||
|         decrypt_key = self._compose_key(bytes(random_reversed[:32])) | ||||
|         decrypt_iv = bytes(random_reversed[32:48]) | ||||
| 
 | ||||
|         self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_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) | ||||
|         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