mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-26 11:23:46 +03:00
Create auth_key if not present
This commit is contained in:
parent
a940e2e9a2
commit
df895a94ab
|
@ -2,7 +2,7 @@
|
||||||
This module contains several classes regarding network, low level connection
|
This module contains several classes regarding network, low level connection
|
||||||
with Telegram's servers and the protocol used (TCP full, abridged, etc.).
|
with Telegram's servers and the protocol used (TCP full, abridged, etc.).
|
||||||
"""
|
"""
|
||||||
from .mtproto_plain_sender import MtProtoPlainSender
|
from .mtprotoplainsender import MTProtoPlainSender
|
||||||
from .authenticator import do_authentication
|
from .authenticator import do_authentication
|
||||||
from .mtprotosender import MTProtoSender
|
from .mtprotosender import MTProtoSender
|
||||||
from .connection import (
|
from .connection import (
|
||||||
|
|
|
@ -14,56 +14,24 @@ from .. import helpers as utils
|
||||||
from ..crypto import AES, AuthKey, Factorization, rsa
|
from ..crypto import AES, AuthKey, Factorization, rsa
|
||||||
from ..errors import SecurityError
|
from ..errors import SecurityError
|
||||||
from ..extensions import BinaryReader
|
from ..extensions import BinaryReader
|
||||||
from ..network import MtProtoPlainSender
|
|
||||||
from ..tl.functions import (
|
from ..tl.functions import (
|
||||||
ReqPqMultiRequest, ReqDHParamsRequest, SetClientDHParamsRequest
|
ReqPqMultiRequest, ReqDHParamsRequest, SetClientDHParamsRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_authentication(connection, retries=5):
|
async def do_authentication(sender):
|
||||||
"""
|
|
||||||
Performs the authentication steps on the given connection.
|
|
||||||
Raises an error if all attempts fail.
|
|
||||||
|
|
||||||
:param connection: the connection to be used (must be connected).
|
|
||||||
:param retries: how many times should we retry on failure.
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if not retries or retries < 0:
|
|
||||||
retries = 1
|
|
||||||
|
|
||||||
last_error = None
|
|
||||||
while retries:
|
|
||||||
try:
|
|
||||||
return _do_authentication(connection)
|
|
||||||
except (SecurityError, AssertionError, NotImplementedError) as e:
|
|
||||||
last_error = e
|
|
||||||
retries -= 1
|
|
||||||
raise last_error
|
|
||||||
|
|
||||||
|
|
||||||
def _do_authentication(connection):
|
|
||||||
"""
|
"""
|
||||||
Executes the authentication process with the Telegram servers.
|
Executes the authentication process with the Telegram servers.
|
||||||
|
|
||||||
:param connection: the connection to be used (must be connected).
|
:param sender: a connected `MTProtoPlainSender`.
|
||||||
:return: returns a (authorization key, time offset) tuple.
|
:return: returns a (authorization key, time offset) tuple.
|
||||||
"""
|
"""
|
||||||
sender = MtProtoPlainSender(connection)
|
|
||||||
|
|
||||||
# Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
# Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
||||||
req_pq_request = ReqPqMultiRequest(
|
nonce = int.from_bytes(os.urandom(16), 'big', signed=True)
|
||||||
nonce=int.from_bytes(os.urandom(16), 'big', signed=True)
|
res_pq = await sender.send(ReqPqMultiRequest(nonce))
|
||||||
)
|
assert isinstance(res_pq, ResPQ)
|
||||||
sender.send(bytes(req_pq_request))
|
|
||||||
with BinaryReader(sender.receive()) as reader:
|
|
||||||
req_pq_request.on_response(reader)
|
|
||||||
|
|
||||||
res_pq = req_pq_request.result
|
if res_pq.nonce != nonce:
|
||||||
if not isinstance(res_pq, ResPQ):
|
|
||||||
raise AssertionError(res_pq)
|
|
||||||
|
|
||||||
if res_pq.nonce != req_pq_request.nonce:
|
|
||||||
raise SecurityError('Invalid nonce from server')
|
raise SecurityError('Invalid nonce from server')
|
||||||
|
|
||||||
pq = get_int(res_pq.pq)
|
pq = get_int(res_pq.pq)
|
||||||
|
@ -96,20 +64,14 @@ def _do_authentication(connection):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
req_dh_params = ReqDHParamsRequest(
|
server_dh_params = await sender.send(ReqDHParamsRequest(
|
||||||
nonce=res_pq.nonce,
|
nonce=res_pq.nonce,
|
||||||
server_nonce=res_pq.server_nonce,
|
server_nonce=res_pq.server_nonce,
|
||||||
p=p, q=q,
|
p=p, q=q,
|
||||||
public_key_fingerprint=target_fingerprint,
|
public_key_fingerprint=target_fingerprint,
|
||||||
encrypted_data=cipher_text
|
encrypted_data=cipher_text
|
||||||
)
|
))
|
||||||
sender.send(bytes(req_dh_params))
|
|
||||||
|
|
||||||
# Step 2 response: DH Exchange
|
|
||||||
with BinaryReader(sender.receive()) as reader:
|
|
||||||
req_dh_params.on_response(reader)
|
|
||||||
|
|
||||||
server_dh_params = req_dh_params.result
|
|
||||||
if isinstance(server_dh_params, ServerDHParamsFail):
|
if isinstance(server_dh_params, ServerDHParamsFail):
|
||||||
raise SecurityError('Server DH params fail: TODO')
|
raise SecurityError('Server DH params fail: TODO')
|
||||||
|
|
||||||
|
@ -168,18 +130,12 @@ def _do_authentication(connection):
|
||||||
client_dh_encrypted = AES.encrypt_ige(client_dh_inner_hashed, key, iv)
|
client_dh_encrypted = AES.encrypt_ige(client_dh_inner_hashed, key, iv)
|
||||||
|
|
||||||
# Prepare Set client DH params
|
# Prepare Set client DH params
|
||||||
set_client_dh = SetClientDHParamsRequest(
|
dh_gen = await sender.send(SetClientDHParamsRequest(
|
||||||
nonce=res_pq.nonce,
|
nonce=res_pq.nonce,
|
||||||
server_nonce=res_pq.server_nonce,
|
server_nonce=res_pq.server_nonce,
|
||||||
encrypted_data=client_dh_encrypted,
|
encrypted_data=client_dh_encrypted,
|
||||||
)
|
))
|
||||||
sender.send(bytes(set_client_dh))
|
|
||||||
|
|
||||||
# Step 3 response: Complete DH Exchange
|
|
||||||
with BinaryReader(sender.receive()) as reader:
|
|
||||||
set_client_dh.on_response(reader)
|
|
||||||
|
|
||||||
dh_gen = set_client_dh.result
|
|
||||||
if isinstance(dh_gen, DhGenOk):
|
if isinstance(dh_gen, DhGenOk):
|
||||||
if dh_gen.nonce != res_pq.nonce:
|
if dh_gen.nonce != res_pq.nonce:
|
||||||
raise SecurityError('Invalid nonce from server')
|
raise SecurityError('Invalid nonce from server')
|
||||||
|
|
|
@ -9,12 +9,11 @@ from ..errors import BrokenAuthKeyError
|
||||||
from ..extensions import BinaryReader
|
from ..extensions import BinaryReader
|
||||||
|
|
||||||
|
|
||||||
class MtProtoPlainSender:
|
class MTProtoPlainSender:
|
||||||
"""
|
"""
|
||||||
MTProto Mobile Protocol plain sender
|
MTProto Mobile Protocol plain sender
|
||||||
(https://core.telegram.org/mtproto/description#unencrypted-messages)
|
(https://core.telegram.org/mtproto/description#unencrypted-messages)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
"""
|
"""
|
||||||
Initializes the MTProto plain sender.
|
Initializes the MTProto plain sender.
|
||||||
|
@ -26,43 +25,27 @@ class MtProtoPlainSender:
|
||||||
self._last_msg_id = 0
|
self._last_msg_id = 0
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
|
|
||||||
def connect(self):
|
async def send(self, request):
|
||||||
"""Connects to Telegram's servers."""
|
|
||||||
self._connection.connect()
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
"""Disconnects from Telegram's servers."""
|
|
||||||
self._connection.close()
|
|
||||||
|
|
||||||
def send(self, data):
|
|
||||||
"""
|
"""
|
||||||
Sends a plain packet (auth_key_id = 0) containing the
|
Sends and receives the result for the given request.
|
||||||
given message body (data).
|
|
||||||
|
|
||||||
:param data: the data to be sent.
|
|
||||||
"""
|
"""
|
||||||
self._connection.send(
|
body = bytes(request)
|
||||||
struct.pack('<QQi', 0, self._get_new_msg_id(), len(data)) + data
|
msg_id = self._get_new_msg_id()
|
||||||
|
await self._connection.send(
|
||||||
|
struct.pack('<QQi', 0, msg_id, len(body)) + body
|
||||||
)
|
)
|
||||||
|
|
||||||
def receive(self):
|
body = await self._connection.recv()
|
||||||
"""
|
|
||||||
Receives a plain packet from the network.
|
|
||||||
|
|
||||||
:return: the response body.
|
|
||||||
"""
|
|
||||||
body = self._connection.recv()
|
|
||||||
if body == b'l\xfe\xff\xff': # -404 little endian signed
|
if body == b'l\xfe\xff\xff': # -404 little endian signed
|
||||||
# Broken authorization, must reset the auth key
|
# Broken authorization, must reset the auth key
|
||||||
raise BrokenAuthKeyError()
|
raise BrokenAuthKeyError()
|
||||||
|
|
||||||
with BinaryReader(body) as reader:
|
with BinaryReader(body) as reader:
|
||||||
reader.read_long() # auth_key_id
|
assert reader.read_long() == 0 # auth_key_id
|
||||||
reader.read_long() # msg_id
|
assert reader.read_long() > msg_id # msg_id
|
||||||
message_length = reader.read_int()
|
assert reader.read_int() # length
|
||||||
|
# No need to read "length" bytes first, just read the object
|
||||||
response = reader.read(message_length)
|
return reader.tgread_object()
|
||||||
return response
|
|
||||||
|
|
||||||
def _get_new_msg_id(self):
|
def _get_new_msg_id(self):
|
||||||
"""Generates a new message ID based on the current time since epoch."""
|
"""Generates a new message ID based on the current time since epoch."""
|
|
@ -1,9 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from . import MTProtoPlainSender, authenticator
|
||||||
from .connection import ConnectionTcpFull
|
from .connection import ConnectionTcpFull
|
||||||
from .. import helpers, utils
|
from .. import helpers, utils
|
||||||
from ..errors import BadMessageError, TypeNotFoundError, rpc_message_to_error
|
from ..errors import (
|
||||||
|
BadMessageError, TypeNotFoundError, BrokenAuthKeyError, SecurityError,
|
||||||
|
rpc_message_to_error
|
||||||
|
)
|
||||||
from ..extensions import BinaryReader
|
from ..extensions import BinaryReader
|
||||||
from ..tl import TLMessage, MessageContainer, GzipPacked
|
from ..tl import TLMessage, MessageContainer, GzipPacked
|
||||||
from ..tl.functions.auth import LogOutRequest
|
from ..tl.functions.auth import LogOutRequest
|
||||||
|
@ -100,6 +104,13 @@ class MTProtoSender:
|
||||||
async with self._send_lock:
|
async with self._send_lock:
|
||||||
await self._connection.connect(ip, port)
|
await self._connection.connect(ip, port)
|
||||||
self._user_connected = True
|
self._user_connected = True
|
||||||
|
|
||||||
|
# TODO Handle SecurityError, AssertionError, NotImplementedError
|
||||||
|
if self.session.auth_key is None:
|
||||||
|
plain = MTProtoPlainSender(self._connection)
|
||||||
|
self.session.auth_key, self.session.time_offset =\
|
||||||
|
await authenticator.do_authentication(plain)
|
||||||
|
|
||||||
self._send_loop_handle = asyncio.ensure_future(self._send_loop())
|
self._send_loop_handle = asyncio.ensure_future(self._send_loop())
|
||||||
self._recv_loop_handle = asyncio.ensure_future(self._recv_loop())
|
self._recv_loop_handle = asyncio.ensure_future(self._recv_loop())
|
||||||
|
|
||||||
|
@ -214,11 +225,20 @@ class MTProtoSender:
|
||||||
body = await self._connection.recv()
|
body = await self._connection.recv()
|
||||||
|
|
||||||
# TODO Check salt, session_id and sequence_number
|
# TODO Check salt, session_id and sequence_number
|
||||||
message, remote_msg_id, remote_seq = helpers.unpack_message(
|
try:
|
||||||
self.session, body)
|
message, remote_msg_id, remote_seq =\
|
||||||
|
helpers.unpack_message(self.session, body)
|
||||||
with BinaryReader(message) as reader:
|
except (BrokenAuthKeyError, BufferError):
|
||||||
await self._process_message(remote_msg_id, remote_seq, reader)
|
# TODO Are these temporary or do we need a new key?
|
||||||
|
pass
|
||||||
|
except SecurityError:
|
||||||
|
# TODO Can we safely ignore these? Has the message
|
||||||
|
# been decoded correctly?
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
with BinaryReader(message) as reader:
|
||||||
|
await self._process_message(
|
||||||
|
remote_msg_id, remote_seq, reader)
|
||||||
|
|
||||||
# Response Handlers
|
# Response Handlers
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user