Telethon/telethon/network/authenticator.py

237 lines
8.8 KiB
Python
Raw Normal View History

2016-09-16 15:04:46 +03:00
import os
import time
from hashlib import sha1
2016-11-30 00:29:42 +03:00
from .. import helpers as utils
from ..crypto import AES, AuthKey, Factorization
from ..crypto import rsa
from ..errors import SecurityError, TypeNotFoundError
from ..extensions import BinaryReader, BinaryWriter
from ..network import MtProtoPlainSender
def do_authentication(connection, retries=5):
if not retries or retries < 0:
retries = 1
last_error = None
while retries:
try:
return _do_authentication(connection)
except (SecurityError, TypeNotFoundError, NotImplementedError) as e:
last_error = e
retries -= 1
raise last_error
2016-08-28 20:26:06 +03:00
def _do_authentication(connection):
"""Executes the authentication process with the Telegram servers.
If no error is raised, returns both the authorization key and the
time offset.
"""
sender = MtProtoPlainSender(connection)
sender.connect()
2016-08-28 20:26:06 +03:00
# Step 1 sending: PQ Request
2016-09-16 15:04:46 +03:00
nonce = os.urandom(16)
with BinaryWriter(known_length=20) as writer:
writer.write_int(0x60469778, signed=False) # Constructor number
2016-08-28 20:26:06 +03:00
writer.write(nonce)
sender.send(writer.get_bytes())
# Step 1 response: PQ Request
2016-08-30 18:40:49 +03:00
pq, pq_bytes, server_nonce, fingerprints = None, None, None, []
2016-08-28 20:26:06 +03:00
with BinaryReader(sender.receive()) as reader:
2016-08-30 18:40:49 +03:00
response_code = reader.read_int(signed=False)
2016-08-28 20:26:06 +03:00
if response_code != 0x05162463:
raise TypeNotFoundError(response_code)
2016-08-28 20:26:06 +03:00
nonce_from_server = reader.read(16)
if nonce_from_server != nonce:
raise SecurityError('Invalid nonce from server')
2016-08-28 20:26:06 +03:00
server_nonce = reader.read(16)
pq_bytes = reader.tgread_bytes()
pq = get_int(pq_bytes)
2016-08-28 20:26:06 +03:00
vector_id = reader.read_int()
if vector_id != 0x1cb5c415:
raise TypeNotFoundError(response_code)
2016-08-28 20:26:06 +03:00
fingerprints = []
fingerprint_count = reader.read_int()
for _ in range(fingerprint_count):
fingerprints.append(reader.read(8))
# Step 2 sending: DH Exchange
2016-09-16 15:04:46 +03:00
new_nonce = os.urandom(32)
2017-05-21 14:59:16 +03:00
p, q = Factorization.factorize(pq)
2016-08-28 20:26:06 +03:00
with BinaryWriter() as pq_inner_data_writer:
2016-11-30 00:29:42 +03:00
pq_inner_data_writer.write_int(
0x83c95aec, signed=False) # PQ Inner Data
pq_inner_data_writer.tgwrite_bytes(rsa.get_byte_array(pq))
pq_inner_data_writer.tgwrite_bytes(rsa.get_byte_array(min(p, q)))
pq_inner_data_writer.tgwrite_bytes(rsa.get_byte_array(max(p, q)))
2016-08-28 20:26:06 +03:00
pq_inner_data_writer.write(nonce)
pq_inner_data_writer.write(server_nonce)
pq_inner_data_writer.write(new_nonce)
# sha_digest + data + random_bytes
2016-08-30 18:40:49 +03:00
cipher_text, target_fingerprint = None, None
2016-08-28 20:26:06 +03:00
for fingerprint in fingerprints:
cipher_text = rsa.encrypt(
fingerprint,
pq_inner_data_writer.get_bytes()
)
2016-08-28 20:26:06 +03:00
if cipher_text is not None:
target_fingerprint = fingerprint
break
if cipher_text is None:
raise SecurityError(
2016-11-30 00:29:42 +03:00
'Could not find a valid key for fingerprints: {}'
.format(', '.join([repr(f) for f in fingerprints]))
)
2016-08-28 20:26:06 +03:00
with BinaryWriter() as req_dh_params_writer:
2016-11-30 00:29:42 +03:00
req_dh_params_writer.write_int(
0xd712e4be, signed=False) # Req DH Params
2016-08-28 20:26:06 +03:00
req_dh_params_writer.write(nonce)
req_dh_params_writer.write(server_nonce)
req_dh_params_writer.tgwrite_bytes(rsa.get_byte_array(min(p, q)))
req_dh_params_writer.tgwrite_bytes(rsa.get_byte_array(max(p, q)))
req_dh_params_writer.write(target_fingerprint)
2016-08-28 20:26:06 +03:00
req_dh_params_writer.tgwrite_bytes(cipher_text)
2016-08-30 18:40:49 +03:00
req_dh_params_bytes = req_dh_params_writer.get_bytes()
sender.send(req_dh_params_bytes)
2016-08-28 20:26:06 +03:00
# Step 2 response: DH Exchange
2016-08-30 18:40:49 +03:00
encrypted_answer = None
2016-08-28 20:26:06 +03:00
with BinaryReader(sender.receive()) as reader:
2016-08-30 18:40:49 +03:00
response_code = reader.read_int(signed=False)
2016-08-28 20:26:06 +03:00
if response_code == 0x79cb045d:
raise SecurityError('Server DH params fail: TODO')
2016-08-28 20:26:06 +03:00
if response_code != 0xd0e8075c:
raise TypeNotFoundError(response_code)
2016-08-28 20:26:06 +03:00
nonce_from_server = reader.read(16)
if nonce_from_server != nonce:
raise SecurityError('Invalid nonce from server')
2016-08-28 20:26:06 +03:00
server_nonce_from_server = reader.read(16)
if server_nonce_from_server != server_nonce:
raise SecurityError('Invalid server nonce from server')
2016-08-30 18:40:49 +03:00
2016-08-28 20:26:06 +03:00
encrypted_answer = reader.tgread_bytes()
# Step 3 sending: Complete DH Exchange
2017-05-21 14:59:16 +03:00
key, iv = utils.generate_key_data_from_nonce(server_nonce, new_nonce)
2016-09-03 21:34:24 +03:00
plain_text_answer = AES.decrypt_ige(encrypted_answer, key, iv)
2016-08-30 18:40:49 +03:00
g, dh_prime, ga, time_offset = None, None, None, None
2016-09-03 21:34:24 +03:00
with BinaryReader(plain_text_answer) as dh_inner_data_reader:
2017-05-21 14:59:16 +03:00
dh_inner_data_reader.read(20) # hash sum
2016-08-28 20:26:06 +03:00
code = dh_inner_data_reader.read_int(signed=False)
if code != 0xb5890dba:
raise TypeNotFoundError(code)
2016-08-28 20:26:06 +03:00
nonce_from_server1 = dh_inner_data_reader.read(16)
if nonce_from_server1 != nonce:
raise SecurityError('Invalid nonce in encrypted answer')
2016-08-28 20:26:06 +03:00
server_nonce_from_server1 = dh_inner_data_reader.read(16)
if server_nonce_from_server1 != server_nonce:
raise SecurityError('Invalid server nonce in encrypted answer')
2016-08-28 20:26:06 +03:00
g = dh_inner_data_reader.read_int()
dh_prime = get_int(dh_inner_data_reader.tgread_bytes(), signed=False)
ga = get_int(dh_inner_data_reader.tgread_bytes(), signed=False)
2016-08-28 20:26:06 +03:00
server_time = dh_inner_data_reader.read_int()
2016-09-03 21:34:24 +03:00
time_offset = server_time - int(time.time())
2016-08-28 20:26:06 +03:00
b = get_int(os.urandom(256), signed=False)
2016-08-30 18:40:49 +03:00
gb = pow(g, b, dh_prime)
gab = pow(ga, b, dh_prime)
2016-08-28 20:26:06 +03:00
# Prepare client DH Inner Data
with BinaryWriter() as client_dh_inner_data_writer:
2016-11-30 00:29:42 +03:00
client_dh_inner_data_writer.write_int(
0x6643b654, signed=False) # Client DH Inner Data
2016-08-28 20:26:06 +03:00
client_dh_inner_data_writer.write(nonce)
client_dh_inner_data_writer.write(server_nonce)
client_dh_inner_data_writer.write_long(0) # TODO retry_id
client_dh_inner_data_writer.tgwrite_bytes(rsa.get_byte_array(gb))
2016-08-28 20:26:06 +03:00
with BinaryWriter() as client_dh_inner_data_with_hash_writer:
2016-11-30 00:29:42 +03:00
client_dh_inner_data_with_hash_writer.write(
sha1(client_dh_inner_data_writer.get_bytes()).digest())
2016-11-30 00:29:42 +03:00
client_dh_inner_data_with_hash_writer.write(
client_dh_inner_data_writer.get_bytes())
client_dh_inner_data_bytes = \
client_dh_inner_data_with_hash_writer.get_bytes()
2016-08-28 20:26:06 +03:00
# Encryption
2016-11-30 00:29:42 +03:00
client_dh_inner_data_encrypted_bytes = AES.encrypt_ige(
client_dh_inner_data_bytes, key, iv)
2016-08-28 20:26:06 +03:00
# Prepare Set client DH params
with BinaryWriter() as set_client_dh_params_writer:
set_client_dh_params_writer.write_int(0xf5045f1f, signed=False)
2016-08-28 20:26:06 +03:00
set_client_dh_params_writer.write(nonce)
set_client_dh_params_writer.write(server_nonce)
2016-11-30 00:29:42 +03:00
set_client_dh_params_writer.tgwrite_bytes(
client_dh_inner_data_encrypted_bytes)
2016-08-28 20:26:06 +03:00
set_client_dh_params_bytes = set_client_dh_params_writer.get_bytes()
sender.send(set_client_dh_params_bytes)
# Step 3 response: Complete DH Exchange
with BinaryReader(sender.receive()) as reader:
# Everything read from the server, disconnect now
sender.disconnect()
2016-08-28 20:26:06 +03:00
code = reader.read_int(signed=False)
2016-08-30 18:40:49 +03:00
if code == 0x3bcbf734: # DH Gen OK
2016-08-28 20:26:06 +03:00
nonce_from_server = reader.read(16)
if nonce_from_server != nonce:
raise SecurityError('Invalid nonce from server')
2016-08-28 20:26:06 +03:00
server_nonce_from_server = reader.read(16)
if server_nonce_from_server != server_nonce:
raise SecurityError('Invalid server nonce from server')
2016-08-30 18:40:49 +03:00
2016-08-28 20:26:06 +03:00
new_nonce_hash1 = reader.read(16)
auth_key = AuthKey(rsa.get_byte_array(gab))
2016-08-28 20:26:06 +03:00
2016-11-30 00:29:42 +03:00
new_nonce_hash_calculated = auth_key.calc_new_nonce_hash(new_nonce,
1)
2016-08-28 20:26:06 +03:00
if new_nonce_hash1 != new_nonce_hash_calculated:
raise SecurityError('Invalid new nonce hash')
2016-08-28 20:26:06 +03:00
return auth_key, time_offset
elif code == 0x46dc1fb9: # DH Gen Retry
raise NotImplementedError('dh_gen_retry')
elif code == 0xa69dae02: # DH Gen Fail
raise NotImplementedError('dh_gen_fail')
else:
raise NotImplementedError('DH Gen unknown: {}'.format(hex(code)))
def get_int(byte_array, signed=True):
2017-09-04 18:10:04 +03:00
"""Gets the specified integer from its byte array.
This should be used by the authenticator,
who requires the data to be in big endian
"""
return int.from_bytes(byte_array, byteorder='big', signed=signed)