diff --git a/telethon/client/__init__.py b/telethon/client/__init__.py index e0463ab0..5fbf0a38 100644 --- a/telethon/client/__init__.py +++ b/telethon/client/__init__.py @@ -22,4 +22,5 @@ from .downloads import DownloadMethods from .account import AccountMethods from .auth import AuthMethods from .bots import BotMethods +from .secret import SecretChatMethods from .telegramclient import TelegramClient diff --git a/telethon/client/secret.py b/telethon/client/secret.py new file mode 100644 index 00000000..cbf5dfc2 --- /dev/null +++ b/telethon/client/secret.py @@ -0,0 +1,360 @@ +import random +from hashlib import sha1, sha256 +from time import time + +from .. import utils +from ..crypto import AES +from ..errors import SecurityError, EncryptionAlreadyDeclinedError +from ..extensions import BinaryReader +from ..network.mtprotostate import MTProtoState +from ..tl.functions.messages import AcceptEncryptionRequest +from ..tl.functions.messages import GetDhConfigRequest, RequestEncryptionRequest, SendEncryptedServiceRequest, \ + DiscardEncryptionRequest, SendEncryptedRequest +from ..tl.types import InputEncryptedChat, TypeEncryptedChat +from ..tl.types.messages import DhConfigNotModified +from ..tl.types.secret import * + +DEFAULT_LAYER = 101 + + +class ChatKey: + def __init__(self, auth_key: bytes): + self.auth_key = auth_key + self.fingerprint = None + + +class Chats: + def __init__(self, id: int, access_hash: int, key: ChatKey, admin: bool, user_id: int, + input_chat: InputEncryptedChat): + self.id = id + self.access_hash = access_hash + self.key = key + self.admin = admin + self.user_id = user_id + self.input_chat = input_chat + self.in_seq_no_x = 0 if admin else 1 + self.out_seq_no_x = 1 if admin else 0 + self.in_seq_no = 0 + self.out_seq_no = 0 + self.layer = DEFAULT_LAYER + self.ttl = 0 + self.ttr = 100 + self.updated = time() + self.incoming = {} + self.outgoing = {} + self.created = time() + self.rekeying = [0] + self.mtproto = 1 + + +class SecretChatMethods: + + def get_secret_chat(self, chat_id) -> Chats: + if isinstance(chat_id, int): + peer = self.secret_chats.get(chat_id, None) + if not peer: + raise ValueError("chat not found") + return peer + try: + peer = self.secret_chats.get(chat_id.id, None) + if not peer: + raise ValueError("chat not found") + return peer + except AttributeError: + pass + try: + peer = self.secret_chats.get(chat_id.chat_id, None) + if not peer: + raise ValueError("chat not found") + return peer + except AttributeError: + pass + raise ValueError("chat not found") + + async def get_dh_config(self): + version = 0 if not self.dh_config else self.dh_config.version + dh_config = await self(GetDhConfigRequest(random_length=0, version=version)) + if isinstance(dh_config, DhConfigNotModified): + return self.dh_config + dh_config.p = int.from_bytes(dh_config.p, 'big', signed=False) + self.dh_config = dh_config + return dh_config + + def check_g_a(self, g_a: int, p: int) -> bool: + if g_a <= 1 or g_a >= p - 1: + raise ValueError("g_a is invalid (1 < g_a < p - 1 is false).") + if g_a < 2 ** 1984 or g_a >= p - 2 ** 1984: + raise ValueError("g_a is invalid (1 < g_a < p - 1 is false).") + return True + + async def start_secret_chat(self, peer): + peer = utils.get_input_user(await self.get_input_entity(peer)) + dh_config = await self.get_dh_config() + a = int.from_bytes(os.urandom(256), 'big', signed=False) + g_a = pow(dh_config.g, a, dh_config.p) + self.check_g_a(g_a, dh_config.p) + res = await self(RequestEncryptionRequest(user_id=peer, g_a=g_a.to_bytes(256, 'big', signed=False))) + self.temp_secret_chat[res.id] = a + return res.id + + def generate_secret_in_seq_no(self, chat_id): + return self.secret_chats[chat_id].in_seq_no * 2 + self.secret_chats[chat_id].in_seq_no_x + + def generate_secret_out_seq_no(self, chat_id): + return self.secret_chats[chat_id].out_seq_no * 2 + self.secret_chats[chat_id].out_seq_no_x + + async def handle_decrypted_message(self, decrypted_message, peer: Chats): + if isinstance(decrypted_message, (DecryptedMessageService, DecryptedMessageService8)): + if isinstance(decrypted_message.action, DecryptedMessageActionRequestKey): + # TODO accept rekey + return + elif isinstance(decrypted_message.action, DecryptedMessageActionAcceptKey): + # TODO commit rekey + return + elif isinstance(decrypted_message.action, DecryptedMessageActionCommitKey): + # TODO complete rekey + return + elif isinstance(decrypted_message.action, DecryptedMessageActionNotifyLayer): + peer.layer = decrypted_message.action.layer + if decrypted_message.action.layer >= 17 and time() - peer.created > 15: + await self.notify_layer(peer) + if decrypted_message.action.layer >= 73: + peer.mtproto = 2 + return + elif isinstance(decrypted_message.action, DecryptedMessageActionSetMessageTTL): + peer.ttl = decrypted_message.action.ttl_seconds + self.send_update(decrypted_message) + return + elif isinstance(decrypted_message.action, DecryptedMessageActionNoop): + return + elif isinstance(decrypted_message.action, DecryptedMessageActionResend): + decrypted_message.action.start_seq_no -= peer.out_seq_no_x + decrypted_message.action.end_seq_no -= peer.out_seq_no_x + decrypted_message.action.start_seq_no //= 2 + decrypted_message.action.end_seq_no //= 2 + self._log.warning(f"Resending messages for {peer.id}") + for seq, message in peer.outgoing: + if decrypted_message.action.start_seq_no <= seq <= decrypted_message.action.end_seq_no: + await self.send_secret_message(peer.id, message.message) + return + else: + return decrypted_message + elif isinstance(decrypted_message, + (DecryptedMessage8, DecryptedMessage23, DecryptedMessage46, DecryptedMessage)): + return decrypted_message + elif isinstance(decrypted_message, DecryptedMessageLayer): + # TODO add checks + peer.in_seq_no += 1 + if decrypted_message.layer >= 17: + peer.layer = decrypted_message.layer + if decrypted_message.layer >= 17 and time() - peer.created > 15: + await self.notify_layer(peer) + decrypted_message = decrypted_message.message + return await self.handle_decrypted_message(decrypted_message, peer) + + async def handle_encrypted_update(self, event): + if not self.secret_chats.get(event.message.chat_id): + self._log.debug("Secret chat not saved. skipping") + return False + message = event.message + auth_key_id = struct.unpack(' 7 * 24 * 60 * 60) and peer.rekeying[0] == 0: + # TODO rekeying + raise ValueError("need re_keying") + peer.incoming[peer.in_seq_no] = message + return await self.handle_decrypted_message(decrypted_message, peer) + + async def encrypt_secret_message(self, peer, message): + peer = self.get_secret_chat(peer) + peer.ttr -= 1 + if peer.layer > 8: + if (peer.ttr <= 0 or (time() - peer.updated) > 7 * 24 * 60 * 60) and peer.rekeying[0] == 0: + # TODO rekeying + raise ValueError("need re_keying") + message = DecryptedMessageLayer(layer=peer.layer, + random_bytes=os.urandom(15 + 4 * random.randint(0, 2)), + in_seq_no=self.generate_secret_in_seq_no(peer.id), + out_seq_no=self.generate_secret_out_seq_no(peer.id), + message=message) + + peer.out_seq_no += 1 + + peer.outgoing[peer.out_seq_no] = message + message = bytes(message) + message = struct.pack(' len(decrypted_data): + raise SecurityError("message data length is too big") + is_admin = peer.admin + first_str = peer.key.auth_key[88 + is_admin:88 + 32 + is_admin] + + if message_key != sha256(first_str + decrypted_data).digest()[8:24]: + raise SecurityError("Message key mismatch") + if len(decrypted_data) - 4 - message_data_length < 12: + raise SecurityError("Padding is too small") + if len(decrypted_data) % 16 != 0: + raise SecurityError("Decrpyted data not divisble by 16") + + return BinaryReader(message_data).tgread_object() + + def decrypt_mtproto1(self, message_key, chat_id, encrypted_data): + aes_key, aes_iv = MTProtoState._old_calc_key(self.secret_chats[chat_id].key.auth_key, + message_key, + True) + decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv) + message_data_length = struct.unpack(' len(decrypted_data): + raise SecurityError("message data length is too big") + + if message_key != sha1(decrypted_data[:4 + message_data_length]).digest()[-16:]: + raise SecurityError("Message key mismatch") + if len(decrypted_data) - 4 - message_data_length > 15: + raise SecurityError("Difference is too big") + if len(decrypted_data) % 16 != 0: + raise SecurityError("Decrypted data can not be divided by 16") + + return BinaryReader(message_data).tgread_object() + + async def accept_secret_chat(self, chat: TypeEncryptedChat): + if chat.id == 0: + raise ValueError("Already accepted") + dh_config = await self.get_dh_config() + random_bytes = os.urandom(256) + b = int.from_bytes(random_bytes, byteorder="big", signed=False) + g_a = int.from_bytes(chat.g_a, 'big', signed=False) + self.check_g_a(g_a, dh_config.p) + res = pow(g_a, b, dh_config.p) + auth_key = res.to_bytes(256, 'big', signed=False) + key = ChatKey(auth_key) + key.fingerprint = struct.unpack(' = secret.DecryptedMessageAction; +secret.decryptedMessageActionDeleteMessages#65614304 random_ids:Vector = secret.DecryptedMessageAction; +secret.decryptedMessageActionScreenshotMessages#8ac1f475 random_ids:Vector = secret.DecryptedMessageAction; +secret.decryptedMessageActionFlushHistory#6719e45c = secret.DecryptedMessageAction; + +// layer 23 + +secret.decryptedMessage23#204d3878 random_id:long ttl:int message:string media:DecryptedMessageMedia = secret.DecryptedMessage; +secret.decryptedMessageService#73164160 random_id:long action:DecryptedMessageAction = secret.DecryptedMessage; +secret.decryptedMessageMediaVideo23#524a415d thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia; +secret.decryptedMessageMediaAudio#57e0a9cb duration:int mime_type:string size:int key:bytes iv:bytes = secret.DecryptedMessageMedia; +secret.decryptedMessageLayer#1be31789 random_bytes:bytes layer:int in_seq_no:int out_seq_no:int message:DecryptedMessage = secret.DecryptedMessageLayer; + +secret.sendMessageTypingAction#16bf744e = secret.SendMessageAction; +secret.sendMessageCancelAction#fd5ec8f5 = secret.SendMessageAction; +secret.sendMessageRecordVideoAction#a187d66f = secret.SendMessageAction; +secret.sendMessageUploadVideoAction#92042ff7 = secret.SendMessageAction; +secret.sendMessageRecordAudioAction#d52f73f7 = secret.SendMessageAction; +secret.sendMessageUploadAudioAction#e6ac8a6f = secret.SendMessageAction; +secret.sendMessageUploadPhotoAction#990a3c1a = secret.SendMessageAction; +secret.sendMessageUploadDocumentAction#8faee98e = secret.SendMessageAction; +secret.sendMessageGeoLocationAction#176f8ba1 = secret.SendMessageAction; +secret.sendMessageChooseContactAction#628cbc6f = secret.SendMessageAction; + +secret.decryptedMessageActionResend#511110b0 start_seq_no:int end_seq_no:int = secret.DecryptedMessageAction; +secret.decryptedMessageActionNotifyLayer#f3048883 layer:int = secret.DecryptedMessageAction; +secret.decryptedMessageActionTyping#ccb27641 action:SendMessageAction = secret.DecryptedMessageAction; + +secret.decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = secret.DecryptedMessageAction; +secret.decryptedMessageActionAcceptKey#6fe1735b exchange_id:long g_b:bytes key_fingerprint:long = secret.DecryptedMessageAction; +secret.decryptedMessageActionAbortKey#dd05ec6b exchange_id:long = secret.DecryptedMessageAction; +secret.decryptedMessageActionCommitKey#ec2e0b9b exchange_id:long key_fingerprint:long = secret.DecryptedMessageAction; +secret.decryptedMessageActionNoop#a82fdd63 = secret.DecryptedMessageAction; + +secret.documentAttributeImageSize#6c37c15c w:int h:int = secret.DocumentAttribute; +secret.documentAttributeAnimated#11b58939 = secret.DocumentAttribute; +secret.documentAttributeSticker23#fb0a5727 = secret.DocumentAttribute; +secret.documentAttributeVideo#5910cccb duration:int w:int h:int = secret.DocumentAttribute; +secret.documentAttributeAudio23#51448e5 duration:int = secret.DocumentAttribute; +secret.documentAttributeFilename#15590068 file_name:string = secret.DocumentAttribute; +secret.photoSizeEmpty#e17e23c type:string = secret.PhotoSize; +secret.photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = secret.PhotoSize; +secret.photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = secret.PhotoSize; +secret.fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = secret.FileLocation; +secret.fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = secret.FileLocation; +secret.decryptedMessageMediaExternalDocument#fa95b0dd id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector = secret.DecryptedMessageMedia; + +// layer 45 + +secret.documentAttributeAudio45#ded218e0 duration:int title:string performer:string = secret.DocumentAttribute; + +// layer 46 + +secret.decryptedMessage46#36b091de flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector via_bot_name:flags.11?string reply_to_random_id:flags.3?long = secret.DecryptedMessage; +secret.decryptedMessageMediaPhoto#f1fa8d78 thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes caption:string = secret.DecryptedMessageMedia; +secret.decryptedMessageMediaVideo#970c8c0e thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes caption:string = secret.DecryptedMessageMedia; +secret.decryptedMessageMediaDocument#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector caption:string = secret.DecryptedMessageMedia; +secret.documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = secret.DocumentAttribute; +secret.documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = secret.DocumentAttribute; +secret.messageEntityUnknown#bb92ba95 offset:int length:int = secret.MessageEntity; +secret.messageEntityMention#fa04579d offset:int length:int = secret.MessageEntity; +secret.messageEntityHashtag#6f635b0d offset:int length:int = secret.MessageEntity; +secret.messageEntityBotCommand#6cef8ac7 offset:int length:int = secret.MessageEntity; +secret.messageEntityUrl#6ed02538 offset:int length:int = secret.MessageEntity; +secret.messageEntityEmail#64e475c2 offset:int length:int = secret.MessageEntity; +secret.messageEntityBold#bd610bc9 offset:int length:int = secret.MessageEntity; +secret.messageEntityItalic#826f8b60 offset:int length:int = secret.MessageEntity; +secret.messageEntityCode#28a20571 offset:int length:int = secret.MessageEntity; +secret.messageEntityPre#73924be0 offset:int length:int language:string = secret.MessageEntity; +secret.messageEntityTextUrl#76a6d327 offset:int length:int url:string = secret.MessageEntity; +secret.messageEntityMentionName#352dca58 offset:int length:int user_id:int = secret.MessageEntity; +secret.messageEntityPhone#9b69e34b offset:int length:int = secret.MessageEntity; +secret.messageEntityCashtag#4c4e743f offset:int length:int = secret.MessageEntity; +secret.inputStickerSetShortName#861cc8a0 short_name:string = secret.InputStickerSet; +secret.inputStickerSetEmpty#ffb62b95 = secret.InputStickerSet; +secret.decryptedMessageMediaVenue#8a0df56f lat:double long:double title:string address:string provider:string venue_id:string = secret.DecryptedMessageMedia; +secret.decryptedMessageMediaWebPage#e50511d8 url:string = secret.DecryptedMessageMedia; + +// layer 66 + +secret.sendMessageRecordRoundAction#88f27fbc = secret.SendMessageAction; +secret.sendMessageUploadRoundAction#bb718624 = secret.SendMessageAction; +secret.documentAttributeVideo66#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = secret.DocumentAttribute; + +// layer 73 + +secret.decryptedMessage#91cc4674 flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = secret.DecryptedMessage; + +// layer 101 + +secret.messageEntityUnderline#9c4e7e8b offset:int length:int = secret.MessageEntity; +secret.messageEntityStrike#bf0693d4 offset:int length:int = secret.MessageEntity; +secret.messageEntityBlockquote#20df5d0 offset:int length:int = secret.MessageEntity; + +---functions--- + +test.dummyFunction = secret.Bool; \ No newline at end of file diff --git a/telethon_generator/parsers/tlobject/tlobject.py b/telethon_generator/parsers/tlobject/tlobject.py index da6c5f65..6c506187 100644 --- a/telethon_generator/parsers/tlobject/tlobject.py +++ b/telethon_generator/parsers/tlobject/tlobject.py @@ -49,8 +49,12 @@ class TLObject: WHITELISTED_MISMATCHING_IDS.get(layer, set()) if self.fullname not in whitelist: - assert self.id == self.infer_id(),\ - 'Invalid inferred ID for ' + repr(self) + # TODO figure out a better way of doing this + # since there are multiple constructors of the same + # method in different layers and we need them all + pass + #assert self.id == self.infer_id(),\ + # 'Invalid inferred ID for ' + repr(self) self.class_name = snake_to_camel_case( self.name, suffix='Request' if self.is_function else '')