MadelineProto/mtproto.php

318 lines
15 KiB
PHP
Raw Normal View History

2016-06-23 23:51:08 +02:00
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'libpy2php');
require_once ('libpy2php.php');
require_once ('os_path.php');
require_once ('crypt.php');
require_once ('prime.php');
require_once ('TL.php');
function newcrc32($data) {
2016-06-28 03:10:15 +02:00
return hexdec(hash("crc32b", $data));
2016-06-23 23:51:08 +02:00
}
/**
* Function to visualize byte streams. Split into bytes, print to console.
* :param bs: BYTE STRING
*/
2016-06-28 03:10:15 +02:00
define('BIG_ENDIAN', pack('L', 1) === pack('N', 1));
2016-06-28 03:40:31 +02:00
function hex_dump($what) { var_dump(bin2hex($what)); };
2016-06-28 03:37:04 +02:00
function file_slice($data, $offset, $length) {
$packet_sliced_handle = fopen_and_write("php://memory", "w+b", $data);
$size = fstat($packet_sliced_handle)["size"];
if($offset < 0) $offset = $size + $offset;
if($length < 0) $length = $size + $length;
if($length == null) $length = $size;
fseek($packet_sliced_handle, $offset);
$packet_sliced = fread($packet_sliced_handle, $length);
fclose($packet_sliced_handle);
return $packet_sliced;
}
2016-06-27 17:08:10 +02:00
function pack_le($format, ...$n) {
$res = "";
foreach ($n as $key => $curn) {
if (BIG_ENDIAN) {
$res .= strrev(pack($format[$key], $curn));
2016-06-28 03:10:15 +02:00
} else $res .= pack($format[$key], $curn);
2016-06-27 16:52:28 +02:00
}
2016-06-27 17:08:10 +02:00
return $res;
2016-06-27 16:52:28 +02:00
}
2016-06-28 03:10:15 +02:00
function pack_be($format, ...$n) {
$res = "";
foreach ($n as $key => $curn) {
if (!BIG_ENDIAN) {
$res .= strrev(pack($format[$key], $curn));
} else $res .= pack($format[$key], $curn);
}
return $res;
}
function unpack_le($format, $data) {
if (BIG_ENDIAN) {
return unpack(strrev($format), strrev($data));
} else return unpack($format, $data);
}
function unpack_be($format, $data) {
if (!BIG_ENDIAN) {
return unpack($format, strrev($data));
} else return unpack($format, $data);
}
2016-06-23 23:51:08 +02:00
function vis($bs) {
$bs = bytearray($bs);
$symbols_in_one_line = 8;
2016-06-28 03:10:15 +02:00
$n = floor(strlen($bs) / $symbols_in_one_line);
2016-06-23 23:51:08 +02:00
$i = 0;
foreach (pyjslib_range($n) as $i) {
2016-06-27 18:53:15 +02:00
pyjslib_printnl(
join(pyjslib_str(($i * $symbols_in_one_line)) . ' | ' . ' ',
2016-06-24 23:47:10 +02:00
array_map(function($el) { return "%02X" % $el; }, array_slice($bs,$i*$symbols_in_one_line, ($i+1)*$symbols_in_one_line))
2016-06-23 23:51:08 +02:00
));
}
2016-06-28 03:10:15 +02:00
if (!(((strlen($bs) % $symbols_in_one_line) == 0))) {
2016-06-27 18:53:15 +02:00
pyjslib_printnl(join(pyjslib_str((($i + 1) * $symbols_in_one_line)) . ' | ' . ' ',
2016-06-24 23:47:10 +02:00
array_map(function($el) { return "%02X" % $el; }, array_slice($bs, ($i+1)*$symbols_in_one_line), null)
2016-06-23 23:51:08 +02:00
) . '
');
}
}
2016-06-26 15:28:39 +02:00
/**
* bytes_to_long(string) : long
* Convert a byte string to a long integer.
* This is (essentially) the inverse of long_to_bytes().
*/
function bytes_to_long($s) {
$acc = 0;
2016-06-28 03:10:15 +02:00
$length = strlen($s);
2016-06-26 15:28:39 +02:00
if (($length % 4)) {
$extra = (4 - ($length % 4));
$s = (($b('') * $extra) + $s);
$length = ($length + $extra);
}
foreach( pyjslib_range(0, $length, 4) as $i ) {
2016-06-27 16:52:28 +02:00
$acc = ($acc << 32 + unpack('I', array_slice($s, $i, ($i + 4) - $i))[0]);
2016-06-26 15:28:39 +02:00
}
return $acc;
}
2016-06-24 23:47:10 +02:00
2016-06-27 16:52:28 +02:00
function fread_all($handle) {
$pos = ftell($handle);
fseek($handle, 0);
$content = fread($handle, fstat($handle)["size"]);
fseek($handle, $pos);
return $content;
}
2016-06-28 03:37:04 +02:00
function fopen_and_write($filename, $mode, $data) {
$handle = fopen($filename, $mode);
fwrite($handle, $data);
rewind($handle);
return $handle;
}
2016-06-26 15:28:39 +02:00
/**
* long_to_bytes(n:long, blocksize:int) : string
* Convert a long integer to a byte string.
* If optional blocksize is given and greater than zero, pad the front of the
* byte string with binary zeros so that the length is a multiple of
* blocksize.
*/
function long_to_bytes($n,$blocksize=0) {
$s = $b('');
$n = long($n);
while (($n > 0)) {
2016-06-28 03:10:15 +02:00
$s = (pack('I', $n & 4294967295) + $s);
2016-06-26 15:28:39 +02:00
$n = $n >> 32;
}
2016-06-28 03:10:15 +02:00
foreach( pyjslib_range(strlen($s)) as $i ) {
2016-06-26 15:28:39 +02:00
if (($s[$i] != $b('')[0])) {
break;
}
}
$s = array_slice($s, $i, null);
2016-06-28 03:10:15 +02:00
if (($blocksize > 0) && (strlen($s) % $blocksize)) {
$s = ((($blocksize - (strlen($s) % $blocksize)) * $b('')) + $s);
2016-06-26 15:28:39 +02:00
}
return $s;
}
2016-06-23 23:51:08 +02:00
/**
* Manages TCP Transport. encryption and message frames
*/
class Session {
function __construct($ip, $port, $auth_key = null, $server_salt = null) {
2016-06-27 22:52:07 +02:00
$this->sock = fsockopen("tcp://".$ip.":".$port);
2016-06-23 23:51:08 +02:00
$this->number = 0;
$this->timedelta = 0;
2016-06-26 14:53:16 +02:00
$this->session_id = random_bytes(8);
2016-06-23 23:51:08 +02:00
$this->auth_key = $auth_key;
$this->auth_key_id = $this->auth_key ? array_slice(sha1($this->auth_key, true), -8, null) : null;
2016-06-27 22:52:07 +02:00
stream_set_timeout($this->sock, 5);
2016-06-23 23:51:08 +02:00
$this->MAX_RETRY = 5;
$this->AUTH_MAX_RETRY = 5;
}
2016-06-27 22:52:07 +02:00
function __destruct() {
2016-06-26 14:53:16 +02:00
fclose($this->sock);
2016-06-23 23:51:08 +02:00
}
/**
* Forming the message frame and sending message to server
* :param message: byte string to send
*/
function send_message($message_data) {
2016-06-27 16:52:28 +02:00
$message_id = pack_le('Q', (pyjslib_int(((time() + $this->timedelta) * pow(2, 30))) * 4));
2016-06-28 03:10:15 +02:00
2016-06-23 23:51:08 +02:00
if (($this->auth_key == null) || ($this->server_salt == null)) {
2016-06-28 03:10:15 +02:00
$message = '' . $message_id . pack_le('I', strlen($message_data)) . $message_data;
2016-06-23 23:51:08 +02:00
} else {
2016-06-28 03:10:15 +02:00
$encrypted_data = (((($this->server_salt + $this->session_id) + $message_id) + pack_le('II', $this->number, strlen($message_data))) + $message_data);
2016-06-23 23:51:08 +02:00
$message_key = array_slice(sha1($encrypted_data, true), -16, null);
2016-06-28 03:10:15 +02:00
$padding = random_bytes((-strlen($encrypted_data) % 16));
pyjslib_printnl(strlen(($encrypted_data + $padding)));
2016-06-23 23:51:08 +02:00
list($aes_key, $aes_iv) = $this->aes_calculate($message_key);
$message = (($this->auth_key_id + $message_key) + crypt::ige_encrypt(($encrypted_data + $padding), $aes_key, $aes_iv));
}
2016-06-28 03:10:15 +02:00
$step1 = pack_le('II', (strlen($message) + 12), $this->number) . $message;
2016-06-27 22:52:07 +02:00
$step2 = $step1 . pack_le('I', newcrc32($step1));
2016-06-26 14:53:16 +02:00
fwrite($this->sock, $step2);
2016-06-23 23:51:08 +02:00
$this->number+= 1;
}
/**
* Reading socket and receiving message from server. Check the CRC32.
*/
function recv_message() {
2016-06-26 14:53:16 +02:00
$packet_length_data = fread($this->sock, 4);
2016-06-28 03:10:15 +02:00
if (strlen($packet_length_data) < 4) {
2016-06-27 18:53:15 +02:00
throw new Exception('Nothing in the socket!');
2016-06-23 23:51:08 +02:00
}
2016-06-28 03:10:15 +02:00
$packet_length = unpack_le('I', $packet_length_data)[1];
2016-06-26 14:53:16 +02:00
$packet = fread($this->sock, ($packet_length - 4));
2016-06-28 03:40:31 +02:00
if (!((var_dump(newcrc32(($packet_length_data + file_slice($packet, 0, -4 - 0)))) == var_dump(unpack_le('I', file_slice($packet, -4, null))) [1]))) {
2016-06-27 18:53:15 +02:00
throw new Exception('CRC32 was not correct!');
2016-06-23 23:51:08 +02:00
}
2016-06-28 03:37:04 +02:00
$x = unpack('I', file_slice($packet, null, 4));
$auth_key_id = file_slice($packet, 4, 12 - 4);
2016-06-23 23:51:08 +02:00
if (($auth_key_id == '')) {
2016-06-27 16:52:28 +02:00
list($message_id, $message_length) = unpack('8sI', array_slice($packet, 12, 24 - 12));
2016-06-23 23:51:08 +02:00
$data = array_slice($packet, 24, (24 + $message_length) - 24);
} else if (($auth_key_id == $this->auth_key_id)) {
$message_key = array_slice($packet, 12, 28 - 12);
$encrypted_data = array_slice($packet, 28, -4 - 28);
list($aes_key, $aes_iv) = py2php_kwargs_method_call($this, 'aes_calculate', [$message_key], ["direction" => 'from server']);
$decrypted_data = crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv);
assert((array_slice($decrypted_data, 0, 8 - 0) == $this->server_salt));
assert((array_slice($decrypted_data, 8, 16 - 8) == $this->session_id));
$message_id = array_slice($decrypted_data, 16, 24 - 16);
2016-06-27 16:52:28 +02:00
$seq_no = unpack('I', array_slice($decrypted_data, 24, 28 - 24)) [0];
$message_data_length = unpack('I', array_slice($decrypted_data, 28, 32 - 28)) [0];
2016-06-23 23:51:08 +02:00
$data = array_slice($decrypted_data, 32, (32 + $message_data_length) - 32);
} else {
2016-06-27 18:53:15 +02:00
throw new Exception('Got unknown auth_key id');
2016-06-23 23:51:08 +02:00
}
return $data;
}
function method_call($method, $kwargs) {
foreach (pyjslib_range(1, $this->MAX_RETRY) as $i) {
try {
2016-06-27 16:52:28 +02:00
//var_dump(py2php_kwargs_function_call('serialize_method', [$method], $kwargs));
2016-06-27 22:52:07 +02:00
$this->send_message(serialize_method($method, $kwargs));
2016-06-23 23:51:08 +02:00
$server_answer = $this->recv_message();
}
catch(Exception $e) {
2016-06-27 16:52:28 +02:00
echo $e->getMessage();
2016-06-23 23:51:08 +02:00
pyjslib_printnl('Retry call method');
continue;
}
2016-06-28 03:37:04 +02:00
return deserialize(fopen_and_write("php://memory", "w+b", $server_answer));
2016-06-23 23:51:08 +02:00
}
}
function create_auth_key() {
2016-06-26 14:53:16 +02:00
$nonce = random_bytes(16);
2016-06-28 03:10:15 +02:00
//$nonce = "a";
2016-06-23 23:51:08 +02:00
pyjslib_printnl('Requesting pq');
2016-06-27 22:52:07 +02:00
$ResPQ = $this->method_call('req_pq', ["nonce" => $nonce]);
2016-06-23 23:51:08 +02:00
$server_nonce = $ResPQ['server_nonce'];
$public_key_fingerprint = $ResPQ['server_public_key_fingerprints'][0];
$pq_bytes = $ResPQ['pq'];
2016-06-26 15:28:39 +02:00
$pq = bytes_to_long($pq_bytes);
list($p, $q) = primefactors($pq);
2016-06-23 23:51:08 +02:00
if (($p > $q)) {
list($p, $q) = [$q, $p];
}
assert((($p * $q) == $pq) && ($p < $q));
pyjslib_printnl(sprintf('Factorization %d = %d * %d', [$pq, $p, $q]));
2016-06-26 15:28:39 +02:00
$p_bytes = long_to_bytes($p);
$q_bytes = long_to_bytes($q);
2016-06-27 18:53:15 +02:00
$f = pyjslib_open(__DIR__ . '/rsa.pub');
2016-06-23 23:51:08 +02:00
$key = RSA::importKey($f->read());
2016-06-26 14:53:16 +02:00
$new_nonce = random_bytes(32);
2016-06-27 16:52:28 +02:00
$data = py2php_kwargs_function_call('serialize_obj', ['p_q_inner_data'], ["pq" => $pq_bytes, "p" => $p_bytes, "q" => $q_bytes, "nonce" => $nonce, "server_nonce" => $server_nonce, "new_nonce" => $new_nonce]);
2016-06-23 23:51:08 +02:00
$sha_digest = sha($data, true);
2016-06-28 03:10:15 +02:00
$random_bytes = random_bytes(((255 - strlen($data)) - strlen($sha_digest)));
2016-06-23 23:51:08 +02:00
$to_encrypt = (($sha_digest + $data) + $random_bytes);
$encrypted_data = $key->encrypt($to_encrypt, 0) [0];
pyjslib_printnl('Starting Diffie Hellman key exchange');
2016-06-27 22:52:07 +02:00
$server_dh_params = $this->method_call('req_DH_params', ["nonce" => $nonce, "server_nonce" => $server_nonce, "p" => $p_bytes, "q" => $q_bytes, "public_key_fingerprint" => $public_key_fingerprint, "encrypted_data" => $encrypted_data]);
2016-06-23 23:51:08 +02:00
assert(($nonce == $server_dh_params['nonce']));
assert(($server_nonce == $server_dh_params['server_nonce']));
$encrypted_answer = $server_dh_params['encrypted_answer'];
$tmp_aes_key = (sha1(($new_nonce + $server_nonce), true) + array_slice(sha1(($server_nonce + $new_nonce), true), 0, 12 - 0));
$tmp_aes_iv = ((array_slice(sha1(($server_nonce + $new_nonce), true), 12, 20 - 12) + sha1(($new_nonce + $new_nonce), true)) + array_slice($new_nonce, 0, 4 - 0));
$answer_with_hash = crypt::ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
$answer_hash = array_slice($answer_with_hash, null, 20);
$answer = array_slice($answer_with_hash, 20, null);
2016-06-27 16:52:28 +02:00
$server_DH_inner_data = deserialize(io::BytesIO($answer));
2016-06-23 23:51:08 +02:00
assert(($nonce == $server_DH_inner_data['nonce']));
assert(($server_nonce == $server_DH_inner_data['server_nonce']));
$dh_prime_str = $server_DH_inner_data['dh_prime'];
$g = $server_DH_inner_data['g'];
$g_a_str = $server_DH_inner_data['g_a'];
$server_time = $server_DH_inner_data['server_time'];
2016-06-27 16:52:28 +02:00
$this->timedelta = ($server_time - time());
2016-06-23 23:51:08 +02:00
pyjslib_printnl(sprintf('Server-client time delta = %.1f s', $this->timedelta));
$dh_prime = new bytes_to_long($dh_prime_str);
$g_a = new bytes_to_long($g_a_str);
assert(prime::isprime($dh_prime));
$retry_id = 0;
2016-06-26 14:53:16 +02:00
$b_str = random_bytes(256);
2016-06-23 23:51:08 +02:00
$b = new bytes_to_long($b_str);
$g_b = pow($g, $b, $dh_prime);
$g_b_str = new long_to_bytes($g_b);
2016-06-27 16:52:28 +02:00
$data = py2php_kwargs_function_call('serialize_obj', ['client_DH_inner_data'], ["nonce" => $nonce, "server_nonce" => $server_nonce, "retry_id" => $retry_id, "g_b" => $g_b_str]);
2016-06-23 23:51:08 +02:00
$data_with_sha = (sha1($data, true) + $data);
2016-06-28 03:10:15 +02:00
$data_with_sha_padded = ($data_with_sha + random_bytes((-strlen($data_with_sha) % 16)));
2016-06-23 23:51:08 +02:00
$encrypted_data = crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
foreach (pyjslib_range(1, $this->AUTH_MAX_RETRY) as $i) {
2016-06-27 22:52:07 +02:00
$Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ["nonce" => $nonce, "server_nonce" => $server_nonce, "encrypted_data" => $encrypted_data]);
2016-06-23 23:51:08 +02:00
$auth_key = pow($g_a, $b, $dh_prime);
$auth_key_str = new long_to_bytes($auth_key);
$auth_key_sha = sha1($auth_key_str, true);
$auth_key_aux_hash = array_slice($auth_key_sha, null, 8);
$new_nonce_hash1 = array_slice(sha1($new_nonce . '' . $auth_key_aux_hash, true), -16, null);
$new_nonce_hash2 = array_slice(sha1($new_nonce . '' . $auth_key_aux_hash, true), -16, null);
$new_nonce_hash3 = array_slice(sha1($new_nonce . '' . $auth_key_aux_hash, true), -16, null);
assert(($Set_client_DH_params_answer['nonce'] == $nonce));
assert(($Set_client_DH_params_answer['server_nonce'] == $server_nonce));
if (($Set_client_DH_params_answer->name == 'dh_gen_ok')) {
assert(($Set_client_DH_params_answer['new_nonce_hash1'] == $new_nonce_hash1));
pyjslib_printnl('Diffie Hellman key exchange processed successfully');
$this->server_salt = new strxor(array_slice($new_nonce, 0, 8 - 0), array_slice($server_nonce, 0, 8 - 0));
$this->auth_key = $auth_key_str;
$this->auth_key_id = array_slice($auth_key_sha, -8, null);
pyjslib_printnl('Auth key generated');
return 'Auth Ok';
} else if (($Set_client_DH_params_answer->name == 'dh_gen_retry')) {
assert(($Set_client_DH_params_answer['new_nonce_hash2'] == $new_nonce_hash2));
pyjslib_printnl('Retry Auth');
} else if (($Set_client_DH_params_answer->name == 'dh_gen_fail')) {
assert(($Set_client_DH_params_answer['new_nonce_hash3'] == $new_nonce_hash3));
pyjslib_printnl('Auth Failed');
2016-06-27 18:53:15 +02:00
throw new Exception('Auth Failed');
2016-06-23 23:51:08 +02:00
} else {
2016-06-27 18:53:15 +02:00
throw new Exception('Response Error');
2016-06-23 23:51:08 +02:00
}
}
}
function aes_calculate($msg_key, $direction = 'to server') {
$x = ($direction == 'to server') ? 0 : 8;
$sha1_a = sha1(($msg_key + array_slice($this->auth_key, $x, ($x + 32) - $x)), true);
$sha1_b = sha1(((array_slice($this->auth_key, ($x + 32), ($x + 48) - ($x + 32)) + $msg_key) + array_slice($this->auth_key, (48 + $x), (64 + $x) - (48 + $x))), true);
$sha1_c = sha1((array_slice($this->auth_key, ($x + 64), ($x + 96) - ($x + 64)) + $msg_key))->digest();
$sha1_d = sha1(($msg_key + array_slice($this->auth_key, ($x + 96), ($x + 128) - ($x + 96))))->digest();
$aes_key = ((array_slice($sha1_a, 0, 8 - 0) + array_slice($sha1_b, 8, 20 - 8)) + array_slice($sha1_c, 4, 16 - 4));
$aes_iv = (((array_slice($sha1_a, 8, 20 - 8) + array_slice($sha1_b, 0, 8 - 0)) + array_slice($sha1_c, 16, 20 - 16)) + array_slice($sha1_d, 0, 8 - 0));
return [$aes_key, $aes_iv];
}
}