mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-02 11:10:18 +03:00
Add basic secret chats
This commit is contained in:
parent
d09f6a50b0
commit
3808793f98
|
@ -22,4 +22,5 @@ from .downloads import DownloadMethods
|
||||||
from .account import AccountMethods
|
from .account import AccountMethods
|
||||||
from .auth import AuthMethods
|
from .auth import AuthMethods
|
||||||
from .bots import BotMethods
|
from .bots import BotMethods
|
||||||
|
from .secret import SecretChatMethods
|
||||||
from .telegramclient import TelegramClient
|
from .telegramclient import TelegramClient
|
||||||
|
|
360
telethon/client/secret.py
Normal file
360
telethon/client/secret.py
Normal file
|
@ -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('<q', message.bytes[:8])[0]
|
||||||
|
peer = self.get_secret_chat(message.chat_id)
|
||||||
|
if not peer.key.fingerprint or \
|
||||||
|
auth_key_id != peer.key.fingerprint:
|
||||||
|
await self.close_secret_chat(message.chat_id)
|
||||||
|
raise ValueError("Key fingerprint mismatch. Chat closed")
|
||||||
|
|
||||||
|
message_key = message.bytes[8:24]
|
||||||
|
encrypted_data = message.bytes[24:]
|
||||||
|
if peer.mtproto == 2:
|
||||||
|
try:
|
||||||
|
decrypted_message = self.decrypt_mtproto2(bytes.fromhex(message_key.hex()), message.chat_id,
|
||||||
|
bytes.fromhex(encrypted_data.hex()))
|
||||||
|
except Exception as e:
|
||||||
|
decrypted_message = self.decrypt_mtproto1(bytes.fromhex(message_key.hex()), message.chat_id,
|
||||||
|
bytes.fromhex(encrypted_data.hex()))
|
||||||
|
peer.mtproto = 1
|
||||||
|
self._log.debug(f"Used MTProto 1 with chat {message.chat_id}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
decrypted_message = self.decrypt_mtproto1(bytes.fromhex(message_key.hex()), message.chat_id,
|
||||||
|
bytes.fromhex(encrypted_data.hex()))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
decrypted_message = self.decrypt_mtproto2(bytes.fromhex(message_key.hex()), message.chat_id,
|
||||||
|
bytes.fromhex(encrypted_data.hex()))
|
||||||
|
peer.mtproto = 2
|
||||||
|
self._log.debug(f"Used MTProto 2 with chat {message.chat_id}")
|
||||||
|
peer.ttr -= 1
|
||||||
|
if (peer.ttr <= 0 or (time() - peer.updated) > 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('<I', len(message)) + message
|
||||||
|
if peer.mtproto == 2:
|
||||||
|
padding = (16 - len(message) % 16) % 16
|
||||||
|
if padding < 12:
|
||||||
|
padding += 16
|
||||||
|
message += os.urandom(padding)
|
||||||
|
is_admin = (0 if peer.admin else 8)
|
||||||
|
first_str = peer.key.auth_key[88 + is_admin:88 + 32 + is_admin]
|
||||||
|
message_key = sha256(first_str + message).digest()[8:24]
|
||||||
|
aes_key, aes_iv = MTProtoState._calc_key(peer.key.auth_key, message_key,
|
||||||
|
peer.admin)
|
||||||
|
else:
|
||||||
|
message_key = sha1(message).digest()[-16:]
|
||||||
|
aes_key, aes_iv = MTProtoState._old_calc_key(peer.key.auth_key, message_key,
|
||||||
|
True)
|
||||||
|
padding = (16 - len(message) % 16) % 16
|
||||||
|
message += os.urandom(padding)
|
||||||
|
message = struct.pack('<q', peer.key.fingerprint) + message_key + AES.encrypt_ige(bytes.fromhex(message.hex()),
|
||||||
|
aes_key,
|
||||||
|
aes_iv)
|
||||||
|
return message
|
||||||
|
|
||||||
|
async def send_secret_message(self, peer_id, message, ttl=0, reply_to_id=None):
|
||||||
|
peer = self.get_secret_chat(peer_id)
|
||||||
|
if peer.layer == 8:
|
||||||
|
message = DecryptedMessage8(os.urandom(8), message, DecryptedMessageMediaEmpty())
|
||||||
|
elif peer.layer == 46:
|
||||||
|
message = DecryptedMessage46(ttl, message, reply_to_random_id=reply_to_id)
|
||||||
|
else:
|
||||||
|
message = DecryptedMessage(ttl, message, reply_to_random_id=reply_to_id)
|
||||||
|
data = await self.encrypt_secret_message(peer_id, message)
|
||||||
|
res = await self(
|
||||||
|
SendEncryptedRequest(peer=peer.input_chat, data=data))
|
||||||
|
return res
|
||||||
|
|
||||||
|
async def notify_layer(self, peer):
|
||||||
|
if isinstance(peer, int):
|
||||||
|
peer = self.secret_chats[peer]
|
||||||
|
else:
|
||||||
|
peer = self.secret_chats[peer.id]
|
||||||
|
if peer.layer == 8:
|
||||||
|
return
|
||||||
|
message = DecryptedMessageService8(action=DecryptedMessageActionNotifyLayer(
|
||||||
|
layer=min(DEFAULT_LAYER, peer.layer)), random_bytes=os.urandom(15 + 4 * random.randint(0, 2)))
|
||||||
|
data = await self.encrypt_secret_message(peer.id, message)
|
||||||
|
return await self(
|
||||||
|
SendEncryptedServiceRequest(peer=InputEncryptedChat(peer.id, peer.access_hash),
|
||||||
|
data=data))
|
||||||
|
|
||||||
|
async def close_secret_chat(self, peer):
|
||||||
|
|
||||||
|
if self.secret_chats.get(peer.id, None):
|
||||||
|
del self.secret_chats[peer]
|
||||||
|
if self.temp_secret_chat.get(peer.id, None):
|
||||||
|
del self.temp_secret_chat[peer.id]
|
||||||
|
try:
|
||||||
|
await self(DiscardEncryptionRequest(peer.id))
|
||||||
|
except EncryptionAlreadyDeclinedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def decrypt_mtproto2(self, message_key, chat_id, encrypted_data):
|
||||||
|
peer = self.get_secret_chat(chat_id)
|
||||||
|
|
||||||
|
aes_key, aes_iv = MTProtoState._calc_key(self.secret_chats[chat_id].key.auth_key,
|
||||||
|
message_key,
|
||||||
|
not self.secret_chats[chat_id].admin)
|
||||||
|
|
||||||
|
decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv)
|
||||||
|
message_data_length = struct.unpack('<I', decrypted_data[:4])[0]
|
||||||
|
message_data = decrypted_data[4:message_data_length + 4]
|
||||||
|
if message_data_length > 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('<I', decrypted_data[:4])[0]
|
||||||
|
message_data = decrypted_data[4:message_data_length + 4]
|
||||||
|
if message_data_length > 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('<q', sha1(key.auth_key).digest()[-8:])[0]
|
||||||
|
input_peer = InputEncryptedChat(chat_id=chat.id, access_hash=chat.access_hash)
|
||||||
|
secret_chat = Chats(chat.id, chat.access_hash, key, admin=False, user_id=chat.admin_id, input_chat=input_peer)
|
||||||
|
self.secret_chats[chat.id] = secret_chat
|
||||||
|
g_b = pow(dh_config.g, b, dh_config.p)
|
||||||
|
self.check_g_a(g_b, dh_config.p)
|
||||||
|
result = await self(
|
||||||
|
AcceptEncryptionRequest(input_peer, g_b=g_b.to_bytes(256, 'big', signed=False),
|
||||||
|
key_fingerprint=key.fingerprint))
|
||||||
|
await self.notify_layer(chat)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def finish_secret_chat_creation(self, chat):
|
||||||
|
dh_config = await self.get_dh_config()
|
||||||
|
g_a_or_b = int.from_bytes(chat.g_a_or_b, "big", signed=False)
|
||||||
|
self.check_g_a(g_a_or_b, dh_config.p)
|
||||||
|
auth_key = pow(g_a_or_b, self.temp_secret_chat[chat.id], dh_config.p).to_bytes(256, "big", signed=False)
|
||||||
|
del self.temp_secret_chat[chat.id]
|
||||||
|
key = ChatKey(auth_key)
|
||||||
|
key.fingerprint = struct.unpack('<q', sha1(key.auth_key).digest()[-8:])[0]
|
||||||
|
if key.fingerprint != chat.key_fingerprint:
|
||||||
|
raise ValueError("Wrong fingerprint")
|
||||||
|
key.visualization_orig = sha1(key.auth_key).digest()[16:]
|
||||||
|
key.visualization_46 = sha256(key.auth_key).digest()[20:]
|
||||||
|
input_peer = InputEncryptedChat(chat_id=chat.id, access_hash=chat.access_hash)
|
||||||
|
self.secret_chats[chat.id] = Chats(
|
||||||
|
chat.id,
|
||||||
|
chat.access_hash,
|
||||||
|
key,
|
||||||
|
True,
|
||||||
|
chat.participant_id,
|
||||||
|
input_peer
|
||||||
|
)
|
||||||
|
await self.notify_layer(chat)
|
|
@ -362,6 +362,13 @@ class TelegramBaseClient(abc.ABC):
|
||||||
# A place to store if channels are a megagroup or not (see `edit_admin`)
|
# A place to store if channels are a megagroup or not (see `edit_admin`)
|
||||||
self._megagroup_cache = {}
|
self._megagroup_cache = {}
|
||||||
|
|
||||||
|
# Secret chats
|
||||||
|
self.temp_secret_chat = {}
|
||||||
|
self.secret_chats = {}
|
||||||
|
self.dh_config = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Properties
|
# region Properties
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
from . import (
|
from . import (
|
||||||
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
|
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
|
||||||
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
|
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
|
||||||
MessageParseMethods, UserMethods, TelegramBaseClient
|
MessageParseMethods, UserMethods, TelegramBaseClient, SecretChatMethods
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TelegramClient(
|
class TelegramClient(
|
||||||
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
|
AccountMethods, AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
|
||||||
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
|
BotMethods, MessageMethods, UploadMethods, ButtonMethods, UpdateMethods,
|
||||||
MessageParseMethods, UserMethods, TelegramBaseClient
|
MessageParseMethods, UserMethods, TelegramBaseClient, SecretChatMethods
|
||||||
):
|
):
|
||||||
pass
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .newmessage import NewMessage
|
||||||
from .userupdate import UserUpdate
|
from .userupdate import UserUpdate
|
||||||
from .callbackquery import CallbackQuery
|
from .callbackquery import CallbackQuery
|
||||||
from .inlinequery import InlineQuery
|
from .inlinequery import InlineQuery
|
||||||
|
from .secret import SecretChat
|
||||||
|
|
||||||
_HANDLERS_ATTRIBUTE = '__tl.handlers'
|
_HANDLERS_ATTRIBUTE = '__tl.handlers'
|
||||||
|
|
||||||
|
|
74
telethon/events/secret.py
Normal file
74
telethon/events/secret.py
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
from .common import EventBuilder, EventCommon, name_inner_event
|
||||||
|
from ..tl import types
|
||||||
|
|
||||||
|
|
||||||
|
@name_inner_event
|
||||||
|
class SecretChat(EventBuilder):
|
||||||
|
def __init__(self, to_finish=False, to_accept=False, to_decrypt=False):
|
||||||
|
self.to_finish = to_finish
|
||||||
|
self.to_accept = to_accept
|
||||||
|
self.to_decrypt = to_decrypt
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(cls, update, others=None, self_id=None):
|
||||||
|
if isinstance(update, types.UpdateEncryption):
|
||||||
|
if isinstance(update.chat, types.EncryptedChat):
|
||||||
|
return cls.Event(update,
|
||||||
|
to_finish=True)
|
||||||
|
elif isinstance(update.chat, types.EncryptedChatRequested):
|
||||||
|
return cls.Event(update, to_accept=True)
|
||||||
|
elif isinstance(update, types.UpdateNewEncryptedMessage):
|
||||||
|
return cls.Event(update, to_decrypt=True)
|
||||||
|
|
||||||
|
class Event(EventCommon):
|
||||||
|
def __init__(self, update, to_finish=False, to_accept=False, to_decrypt=False):
|
||||||
|
if isinstance(update, types.UpdateEncryption):
|
||||||
|
super().__init__(chat_peer=update.chat)
|
||||||
|
else:
|
||||||
|
super().__init__(chat_peer=update.message.chat_id)
|
||||||
|
self.original_update = update
|
||||||
|
self.to_finish = to_finish
|
||||||
|
self.to_accept = to_accept
|
||||||
|
self.to_decrypt = to_decrypt
|
||||||
|
self.decrypted_message = None
|
||||||
|
|
||||||
|
def _set_client(self, client):
|
||||||
|
self._chat_peer = None
|
||||||
|
super()._set_client(client)
|
||||||
|
|
||||||
|
async def finish(self):
|
||||||
|
return await self._client.finish_secret_chat_creation(self.original_update.chat)
|
||||||
|
|
||||||
|
async def accept(self):
|
||||||
|
return await self._client.accept_secret_chat(self.original_update.chat)
|
||||||
|
|
||||||
|
async def decrypt(self):
|
||||||
|
self.decrypted_message = await self._client.handle_encrypted_update(self.original_update)
|
||||||
|
return self.decrypted_message
|
||||||
|
|
||||||
|
async def reply(self, message, ttl=0):
|
||||||
|
if not self.decrypted_message:
|
||||||
|
await self.decrypt()
|
||||||
|
return await self._client.send_secret_message(self.original_update.message.chat_id, message, ttl,
|
||||||
|
self.decrypted_message.random_id)
|
||||||
|
|
||||||
|
async def respond(self, message, ttl=0):
|
||||||
|
return await self._client.send_secret_message(self.original_update.message.chat_id, message, ttl)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def filter(self, event):
|
||||||
|
event = event.original_update
|
||||||
|
if isinstance(event, types.UpdateEncryption):
|
||||||
|
if isinstance(event.chat, types.EncryptedChat):
|
||||||
|
if not self.to_finish:
|
||||||
|
return
|
||||||
|
elif isinstance(event.chat, types.EncryptedChatRequested):
|
||||||
|
if not self.to_accept:
|
||||||
|
return
|
||||||
|
elif isinstance(event, types.UpdateNewEncryptedMessage):
|
||||||
|
if not self.to_decrypt:
|
||||||
|
return
|
||||||
|
|
||||||
|
return super().filter(event)
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
from hashlib import sha256
|
from hashlib import sha256, sha1
|
||||||
|
|
||||||
from ..crypto import AES
|
from ..crypto import AES
|
||||||
from ..errors import SecurityError, InvalidBufferError
|
from ..errors import SecurityError, InvalidBufferError
|
||||||
|
@ -59,6 +59,25 @@ class MTProtoState:
|
||||||
"""
|
"""
|
||||||
message.msg_id = self._get_new_msg_id()
|
message.msg_id = self._get_new_msg_id()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _old_calc_key(auth_key, msg_key, client):
|
||||||
|
"""
|
||||||
|
Calculate the key based on Telegram guidelines for MTProto 1,
|
||||||
|
specifying whether it's the client or not. See
|
||||||
|
https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector
|
||||||
|
"""
|
||||||
|
x = 0 if client else 8
|
||||||
|
|
||||||
|
sha1a = sha1(msg_key + auth_key[x:x + 32]).digest()
|
||||||
|
sha1b = sha1(auth_key[x + 32:x + 48] + msg_key + auth_key[x + 48:x + 64]).digest()
|
||||||
|
sha1c = sha1(auth_key[x + 64:x + 96] + msg_key).digest()
|
||||||
|
sha1d = sha1(msg_key + auth_key[x + 96:x + 128]).digest()
|
||||||
|
|
||||||
|
aes_key = sha1a[0:8] + sha1b[8:20] + sha1c[4:16]
|
||||||
|
aes_iv = sha1a[8:20] + sha1b[0:8] + sha1c[16:20] + sha1d[0:8]
|
||||||
|
|
||||||
|
return aes_key, aes_iv
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _calc_key(auth_key, msg_key, client):
|
def _calc_key(auth_key, msg_key, client):
|
||||||
"""
|
"""
|
||||||
|
|
109
telethon_generator/data/secret.tl
Normal file
109
telethon_generator/data/secret.tl
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// layer 8
|
||||||
|
|
||||||
|
secret.decryptedMessage8#1f814f1f random_id:long random_bytes:bytes message:string media:DecryptedMessageMedia = secret.DecryptedMessage;
|
||||||
|
secret.decryptedMessageService8#aa48327d random_id:long random_bytes:bytes action:DecryptedMessageAction = secret.DecryptedMessage;
|
||||||
|
secret.decryptedMessageMediaEmpty#89f5c4a = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageMediaPhoto23#32798a8c thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageMediaVideo8#4cee6ef3 thumb:bytes thumb_w:int thumb_h:int duration:int w:int h:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageMediaGeoPoint#35480a59 lat:double long:double = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageMediaContact#588a0a97 phone_number:string first_name:string last_name:string user_id:int = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageActionSetMessageTTL#a1733aec ttl_seconds:int = secret.DecryptedMessageAction;
|
||||||
|
secret.decryptedMessageMediaDocument23#b095434b thumb:bytes thumb_w:int thumb_h:int file_name:string mime_type:string size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageMediaAudio8#6080758f duration:int size:int key:bytes iv:bytes = secret.DecryptedMessageMedia;
|
||||||
|
secret.decryptedMessageActionReadMessages#c4f40be random_ids:Vector<long> = secret.DecryptedMessageAction;
|
||||||
|
secret.decryptedMessageActionDeleteMessages#65614304 random_ids:Vector<long> = secret.DecryptedMessageAction;
|
||||||
|
secret.decryptedMessageActionScreenshotMessages#8ac1f475 random_ids:Vector<long> = 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<DocumentAttribute> = 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<MessageEntity> 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<DocumentAttribute> 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<MessageEntity> 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;
|
|
@ -49,8 +49,12 @@ class TLObject:
|
||||||
WHITELISTED_MISMATCHING_IDS.get(layer, set())
|
WHITELISTED_MISMATCHING_IDS.get(layer, set())
|
||||||
|
|
||||||
if self.fullname not in whitelist:
|
if self.fullname not in whitelist:
|
||||||
assert self.id == self.infer_id(),\
|
# TODO figure out a better way of doing this
|
||||||
'Invalid inferred ID for ' + repr(self)
|
# 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.class_name = snake_to_camel_case(
|
||||||
self.name, suffix='Request' if self.is_function else '')
|
self.name, suffix='Request' if self.is_function else '')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user