mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-23 01:46:35 +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