Initial release

The initial release contains the most basic implementation of TLSharp core.
This is also fully untested, since no test can be done until more work is done.
This commit is contained in:
Lonami
2016-08-26 12:58:53 +02:00
commit 1dac866118
16 changed files with 793 additions and 0 deletions

0
utils/__init__.py Normal file
View File

87
utils/binary_reader.py Normal file
View File

@@ -0,0 +1,87 @@
from io import BytesIO, BufferedReader
import os
class BinaryReader:
"""
Small utility class to read binary data.
Also creates a "Memory Stream" if necessary
"""
def __init__(self, data=None, stream=None):
if data:
self.stream = BytesIO(data)
elif stream:
self.stream = stream
else:
raise ValueError("Either bytes or a stream must be provided")
self.reader = BufferedReader(self.stream)
# region Reading
def read_int(self, signed=True):
return int.from_bytes(self.reader.read(4), signed=signed, byteorder='big')
def read_long(self, signed=True):
return int.from_bytes(self.reader.read(8), signed=signed, byteorder='big')
def read(self, length):
return self.reader.read(length)
def get_bytes(self):
return self.stream.getbuffer()
# endregion
# region Telegram custom reading
def tgread_bytes(self):
first_byte = self.read(1)
if first_byte == 254:
length = self.read(1) | (self.read(1) << 8) | (self.read(1) << 16)
padding = length % 4
else:
length = first_byte
padding = (length + 1) % 4
data = self.read(length)
if padding > 0:
padding = 4 - padding
self.read(padding)
return data
def tgread_string(self):
return str(self.tgread_bytes(), encoding='utf-8')
# endregion
def close(self):
self.reader.close()
# TODO Do I need to close the underlying stream?
# region Position related
def tell_position(self):
"""Tells the current position on the stream"""
return self.reader.tell()
def set_position(self, position):
"""Sets the current position on the stream"""
self.reader.seek(position)
def seek(self, offset):
"""Seeks the stream position given an offset from the current position. May be negative"""
self.reader.seek(offset, os.SEEK_CUR)
# endregion
# region with block
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# endregion

93
utils/binary_writer.py Normal file
View File

@@ -0,0 +1,93 @@
from io import BytesIO, BufferedWriter
from struct import pack
class BinaryWriter:
"""
Small utility class to write binary data.
Also creates a "Memory Stream" if necessary
"""
def __init__(self, stream=None):
if not stream:
stream = BytesIO()
self.stream = stream
self.writer = BufferedWriter(self.stream)
# region Writing
def write_byte(self, byte):
self.writer.write(pack('B', byte))
def write_int(self, integer, signed=True):
if not signed:
integer &= 0xFFFFFFFF # Ensure it's unsigned (see http://stackoverflow.com/a/30092291/4759433)
self.writer.write(pack('I', integer))
def write_long(self, long, signed=True):
if not signed:
long &= 0xFFFFFFFFFFFFFFFF
self.writer.write(pack('Q', long))
def write(self, data):
self.writer.write(data)
# endregion
# region Telegram custom writing
def tgwrite_bytes(self, data):
if len(data) < 254:
padding = (len(data) + 1) % 4
if padding != 0:
padding = 4 - padding
self.write(bytes([len(data)]))
self.write(data)
else:
padding = len(data) % 4
if padding != 0:
padding = 4 - padding
# TODO ensure that _this_ is right (it appears to be)
self.write(bytes([254]))
self.write(bytes([len(data) % 256]))
self.write(bytes([(len(data) >> 8) % 256]))
self.write(bytes([(len(data) >> 16) % 256]))
self.write(data)
""" Original:
binaryWriter.Write((byte)254);
binaryWriter.Write((byte)(bytes.Length));
binaryWriter.Write((byte)(bytes.Length >> 8));
binaryWriter.Write((byte)(bytes.Length >> 16));
"""
self.write(bytes(padding))
def tgwrite_string(self, string):
return self.tgwrite_bytes(string.encode('utf-8'))
# endregion
def flush(self):
self.writer.flush()
def close(self):
self.writer.close()
# TODO Do I need to close the underlying stream?
def get_bytes(self, flush=True):
if flush:
self.writer.flush()
self.stream.getbuffer()
# with block
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()

56
utils/helpers.py Normal file
View File

@@ -0,0 +1,56 @@
import random
from utils.binary_writer import BinaryWriter
from hashlib import sha1
def generate_random_long(signed=True):
result = random.getrandbits(64)
if not signed:
result &= 0xFFFFFFFFFFFFFFFF # Ensure it's unsigned
return result
def generate_random_bytes(count):
with BinaryWriter() as writer:
for _ in range(count):
writer.write(random.getrandbits(8))
return writer.get_bytes()
def calc_key(shared_key, msg_key, client):
x = 0 if client else 8
buffer = [0] * 48
buffer[0:16] = msg_key
buffer[16:48] = shared_key[x:x + 32]
sha1a = sha1(buffer)
buffer[0:16] = shared_key[x + 32:x + 48]
buffer[16:32] = msg_key
buffer[32:48] = shared_key[x + 48:x + 64]
sha1b = sha1(buffer)
buffer[0:32] = shared_key[x + 64:x + 96]
buffer[32:48] = msg_key
sha1c = sha1(buffer)
buffer[0:16] = msg_key
buffer[16:48] = shared_key[x + 96:x + 128]
sha1d = sha1(buffer)
key = sha1a[0:8] + sha1b[8:20] + sha1c[4:16]
iv = sha1a[8:20] + sha1b[0:8] + sha1c[16:20] + sha1d[0:8]
return key, iv
def calc_msg_key(data):
return sha1(data)[4:20]
def calc_msg_key_offset(data, offset, limit):
# TODO untested, may not be offset like this
# In the original code it was as parameters for the sha function, not slicing the array
return sha1(data[offset:offset + limit])[4:20]