diff --git a/readthedocs/misc/v2-migration-guide.rst b/readthedocs/misc/v2-migration-guide.rst index e1621fb4..7e7a2302 100644 --- a/readthedocs/misc/v2-migration-guide.rst +++ b/readthedocs/misc/v2-migration-guide.rst @@ -249,3 +249,9 @@ The TelegramClient is no longer made out of mixins If you were relying on any of the individual mixins that made up the client, such as ``UserMethods`` inside the ``telethon.client`` subpackage, those are now gone. There is a single ``TelegramClient`` class now, containing everything you need. + + +CdnDecrypter has been removed +----------------------------- + +It was not really working and was more intended to be an implementation detail than anything else. diff --git a/telethon/_client/telegrambaseclient.py b/telethon/_client/telegrambaseclient.py index d8d333f0..35507edd 100644 --- a/telethon/_client/telegrambaseclient.py +++ b/telethon/_client/telegrambaseclient.py @@ -428,31 +428,26 @@ def _auth_key_callback(self: 'TelegramClient', auth_key): self.session.save() -async def _get_dc(self: 'TelegramClient', dc_id, cdn=False): +async def _get_dc(self: 'TelegramClient', dc_id): """Gets the Data Center (DC) associated to 'dc_id'""" cls = self.__class__ if not cls._config: cls._config = await self(_tl.fn.help.GetConfig()) - if cdn and not self._cdn_config: - cls._cdn_config = await self(_tl.fn.help.GetCdnConfig()) - for pk in cls._cdn_config.public_keys: - rsa.add_key(pk.public_key) - try: return next( dc for dc in cls._config.dc_options if dc.id == dc_id - and bool(dc.ipv6) == self._use_ipv6 and bool(dc.cdn) == cdn + and bool(dc.ipv6) == self._use_ipv6 and not dc.cdn ) except StopIteration: self._log[__name__].warning( - 'Failed to get DC %s (cdn = %s) with use_ipv6 = %s; retrying ignoring IPv6 check', - dc_id, cdn, self._use_ipv6 + 'Failed to get DC %swith use_ipv6 = %s; retrying ignoring IPv6 check', + dc_id, self._use_ipv6 ) return next( dc for dc in cls._config.dc_options - if dc.id == dc_id and bool(dc.cdn) == cdn + if dc.id == dc_id and not dc.cdn ) async def _create_exported_sender(self: 'TelegramClient', dc_id): @@ -538,29 +533,3 @@ async def _clean_exported_senders(self: 'TelegramClient'): # Disconnect should never raise await sender.disconnect() state.mark_disconnected() - -async def _get_cdn_client(self: 'TelegramClient', cdn_redirect): - """Similar to ._borrow_exported_client, but for CDNs""" - # TODO Implement - raise NotImplementedError - session = self._exported_sessions.get(cdn_redirect.dc_id) - if not session: - dc = await _get_dc(self, cdn_redirect.dc_id, cdn=True) - session = self.session.clone() - await session.set_dc(dc.id, dc.ip_address, dc.port) - self._exported_sessions[cdn_redirect.dc_id] = session - - self._log[__name__].info('Creating new CDN client') - client = TelegramBaseClient( - session, self.api_id, self.api_hash, - proxy=self._sender.connection.conn.proxy, - timeout=self._sender.connection.get_timeout() - ) - - # This will make use of the new RSA keys for this specific CDN. - # - # We won't be calling GetConfig because it's only called - # when needed by ._get_dc, and also it's static so it's likely - # set already. Avoid invoking non-CDN methods by not syncing updates. - client.connect(_sync_updates=False) - return client diff --git a/telethon/_client/telegramclient.py b/telethon/_client/telegramclient.py index 30c2811a..cb644dd2 100644 --- a/telethon/_client/telegramclient.py +++ b/telethon/_client/telegramclient.py @@ -2761,7 +2761,6 @@ class TelegramClient: # Cached server configuration (with .dc_options), can be "global" _config = None - _cdn_config = None def __init__( self: 'TelegramClient', diff --git a/telethon/_crypto/__init__.py b/telethon/_crypto/__init__.py index 69be1da8..f10c9ad7 100644 --- a/telethon/_crypto/__init__.py +++ b/telethon/_crypto/__init__.py @@ -7,4 +7,3 @@ from .aes import AES from .aesctr import AESModeCTR from .authkey import AuthKey from .factorization import Factorization -from .cdndecrypter import CdnDecrypter diff --git a/telethon/_crypto/cdndecrypter.py b/telethon/_crypto/cdndecrypter.py deleted file mode 100644 index 73a568d1..00000000 --- a/telethon/_crypto/cdndecrypter.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -This module holds the CdnDecrypter utility class. -""" -from hashlib import sha256 - -from .. import _tl -from .._crypto import AESModeCTR -from ..errors import CdnFileTamperedError - - -class CdnDecrypter: - """ - Used when downloading a file results in a 'FileCdnRedirect' to - both prepare the redirect, decrypt the file as it downloads, and - ensure the file hasn't been tampered. https://core.telegram.org/cdn - """ - def __init__(self, cdn_client, file_token, cdn_aes, cdn_file_hashes): - """ - Initializes the CDN decrypter. - - :param cdn_client: a client connected to a CDN. - :param file_token: the token of the file to be used. - :param cdn_aes: the AES CTR used to decrypt the file. - :param cdn_file_hashes: the hashes the decrypted file must match. - """ - self.client = cdn_client - self.file_token = file_token - self.cdn_aes = cdn_aes - self.cdn_file_hashes = cdn_file_hashes - - @staticmethod - async def prepare_decrypter(client, cdn_client, cdn_redirect): - """ - Prepares a new CDN decrypter. - - :param client: a TelegramClient connected to the main servers. - :param cdn_client: a new client connected to the CDN. - :param cdn_redirect: the redirect file object that caused this call. - :return: (CdnDecrypter, first chunk file data) - """ - cdn_aes = AESModeCTR( - key=cdn_redirect.encryption_key, - # 12 first bytes of the IV..4 bytes of the offset (0, big endian) - iv=cdn_redirect.encryption_iv[:12] + bytes(4) - ) - - # We assume that cdn_redirect.cdn_file_hashes are ordered by offset, - # and that there will be enough of these to retrieve the whole file. - decrypter = CdnDecrypter( - cdn_client, cdn_redirect.file_token, - cdn_aes, cdn_redirect.cdn_file_hashes - ) - - cdn_file = await cdn_client(_tl.fn.upload.GetCdnFile( - file_token=cdn_redirect.file_token, - offset=cdn_redirect.cdn_file_hashes[0].offset, - limit=cdn_redirect.cdn_file_hashes[0].limit - )) - if isinstance(cdn_file, _tl.upload.CdnFileReuploadNeeded): - # We need to use the original client here - await client(_tl.fn.upload.ReuploadCdnFile( - file_token=cdn_redirect.file_token, - request_token=cdn_file.request_token - )) - - # We want to always return a valid upload.CdnFile - cdn_file = decrypter.get_file() - else: - cdn_file.bytes = decrypter.cdn_aes.encrypt(cdn_file.bytes) - cdn_hash = decrypter.cdn_file_hashes.pop(0) - decrypter.check(cdn_file.bytes, cdn_hash) - - return decrypter, cdn_file - - def get_file(self): - """ - Calls GetCdnFile and decrypts its bytes. - Also ensures that the file hasn't been tampered. - - :return: the CdnFile result. - """ - if self.cdn_file_hashes: - cdn_hash = self.cdn_file_hashes.pop(0) - cdn_file = self.client(_tl.fn.upload.GetCdnFile( - self.file_token, cdn_hash.offset, cdn_hash.limit - )) - cdn_file.bytes = self.cdn_aes.encrypt(cdn_file.bytes) - self.check(cdn_file.bytes, cdn_hash) - else: - cdn_file = _tl.upload.CdnFile(bytes(0)) - - return cdn_file - - @staticmethod - def check(data, cdn_hash): - """ - Checks the integrity of the given data. - Raises CdnFileTamperedError if the integrity check fails. - - :param data: the data to be hashed. - :param cdn_hash: the expected hash. - """ - if sha256(data).digest() != cdn_hash.hash: - raise CdnFileTamperedError()