diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index e8e6da5a..a89ae829 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -20,7 +20,6 @@ class API { set_error_handler([$this, 'ExceptionErrorHandler']); $this->session = new Session($params); - $this->session->create_auth_key(); $future_salts = $this->get_future_salts(3); $this->session->log->log($future_salts); } diff --git a/src/danog/MadelineProto/Session.php b/src/danog/MadelineProto/Session.php index 837a357a..9d9c4961 100644 --- a/src/danog/MadelineProto/Session.php +++ b/src/danog/MadelineProto/Session.php @@ -19,23 +19,18 @@ class Session extends Tools { public $settings = []; - public function __construct($settings) + public function __construct($settings = []) { - // Set default settings $default_settings = [ - 'auth_key' => null, - 'server_salt' => null, - 'ip_address' => '149.154.167.50', - 'port' => '443', - 'protocol' => 'tcp_full', - 'api_id' => 25628, - 'api_hash' => '1fe17cda7d355166cdaa71f04122873c', - 'tl_schema' => [ - 'https://core.telegram.org/schema/mtproto-json', - __DIR__.'/telegram_layer55.json', - ], - 'rsa_key' => '-----BEGIN RSA PUBLIC KEY----- + 'authorization' => [ + 'auth_key' => null, + 'auth_key_id' => null, + 'temp_auth_key' => null, + 'temp_auth_key_expires_in' => 86400, + 'server_salt' => null, + 'session_id' => \phpseclib\Crypt\Random::string(8), + 'rsa_key' => '-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw @@ -43,41 +38,69 @@ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB -----END RSA PUBLIC KEY-----', - 'logging' => 1, - 'logging_param' => '/tmp/MadelineProto.log', - 'logging' => 3, + 'message_ids_limit' => 5 + ], + 'connection' => [ + 'ip_address' => '149.154.167.50', + 'port' => '443', + 'protocol' => 'tcp_full', + ], + 'app_info' => [ + 'api_id' => 25628, + 'api_hash' => '1fe17cda7d355166cdaa71f04122873c', + ], + 'tl_schema' => [ + 'layer' => 55, + 'src' => [ + __DIR__.'/TL_mtproto_v1.json', + __DIR__.'/TL_telegram_v55.json', + ], + ], + 'logging' => [ + 'logging' => 1, + 'logging_param' => '/tmp/MadelineProto.log', + 'logging' => 3, + ], + 'max_tries' => [ + 'query' => 5, + 'authorization' => 5 + ] ]; foreach ($default_settings as $key => $param) { if (!isset($settings[$key])) { $settings[$key] = $param; } + foreach ($param as $subkey => $subparam) { + if (!isset($settings[$key][$subkey])) { + $settings[$key][$subkey] = $subparam; + } + } } $this->settings = $settings; // Connect to servers - $this->sock = new Connection($this->settings['ip_address'], $this->settings['port'], $this->settings['protocol']); + $this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']); // Load rsa key - $this->key = new RSA($settings['rsa_key']); + $this->key = new RSA($settings['authorization']['rsa_key']); // Istantiate struct class $this->struct = new \danog\PHP\Struct(); // Istantiate prime class $this->PrimeModule = new PrimeModule(); // Istantiate TL class - $this->tl = new TL\TL($this->settings['tl_schema']); + $this->tl = new TL\TL($this->settings['tl_schema']['src']); // Istantiate logging class - $this->log = new Logging($this->settings['logging'], $this->settings['logging_param']); - // Set some defaults - $this->auth_key = $this->settings['auth_key']; - $this->number = 0; - $this->timedelta = 0; - $this->session_id = \phpseclib\Crypt\Random::string(8); - if (isset($this->settings['auth_key'])) { - $this->auth_key = $this->settings['auth_key']; + $this->log = new Logging($this->settings['logging']['logging'], $this->settings['logging']['logging_param']); + + $this->connection_seq_no = 0; + $this->seq_no = 0; + $this->timedelta = 0; // time delta + $this->message_ids = []; + + if (($this->settings['authorization']['auth_key'] == null) || ($this->settings['authorization']['server_salt'] == null)) { + $auth_res = $this->create_auth_key(-1); + $temp_auth_res = $this->create_auth_key($this->settings['authorization']['temp_auth_key_expires_in']); } - $this->auth_key_id = $this->auth_key ? substr(sha1($this->auth_key, true), -8) : null; - $this->MAX_RETRY = 5; - $this->AUTH_MAX_RETRY = 5; } public function __destruct() @@ -85,6 +108,23 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB unset($this->sock); } + public function add_check_message_ids($new_message_id) { + if (((int) ((time() + $this->timedelta - 300) * pow(2, 30)) * 4) > $new_message_id) { + throw new Exception("Given message id is too old."); + } + if (((int) ((time() + $this->timedelta + 30) * pow(2, 30)) * 4) < $new_message_id) { + throw new Exception("Given message id is too new."); + } + foreach ($this->message_ids as $message_id) { + if ($new_message_id <= $message_id) { + throw new Exception("Given message id is lower than or equal than the current limit (".$message_id.")."); + } + } + $this->message_ids[] = $new_message_id; + if (count($this->message_ids) > $this->settings["authorization"]["message_ids_limit"]) { + array_shift($this->message_ids); + } + } /** * Function to get hex crc32. * @@ -102,22 +142,22 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB public function send_message($message_data) { $message_id = $this->struct->pack('timedelta) * pow(2, 30)) * 4); - if (($this->auth_key == null) || ($this->server_salt == null)) { + if (($this->settings['authorization']['auth_key'] == null) || ($this->settings['authorization']['server_salt'] == null)) { $message = Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00').$message_id.$this->struct->pack('server_salt.$this->session_id.$message_id.$this->struct->pack('number, strlen($message_data)).$message_data; + $encrypted_data = $this->server_salt.$this->settings['authorization']['session_id'].$message_id.$this->struct->pack('seq_no, strlen($message_data)).$message_data; $message_key = substr(sha1($encrypted_data, true), -16); $padding = \phpseclib\Crypt\Random::string(Tools::posmod(-strlen($encrypted_data), 16)); 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); + $message = $this->settings['authorization']['auth_key_id'].$message_key.Crypt::ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); + $this->seq_no++; } - switch ($this->settings['protocol']) { + switch ($this->settings['connection']['protocol']) { case 'tcp_full': - $step1 = $this->struct->pack('number).$message; + $step1 = $this->struct->pack('connection_seq_no).$message; $step2 = $step1.$this->struct->pack('newcrc32($step1)); $this->sock->write($step2); - $this->number += 1; + $this->connection_seq_no++; break; case 'tcp_intermediate': $step1 = $this->struct->pack('settings['protocol']) { + switch ($this->settings['connection']['protocol']) { case 'tcp_full': $packet_length_data = $this->sock->read(4); if (strlen($packet_length_data) < 4) { @@ -153,7 +193,10 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB if (!($this->newcrc32($packet_length_data.substr($packet, 0, -4)) == $this->struct->unpack('struct->unpack('struct->unpack('connection_seq_no - 1) { + throw new Exception('Connection seq_no mismatch'); + } $payload = Tools::fopen_and_write('php://memory', 'rw+b', substr($packet, 4, $packet_length - 12)); break; case 'tcp_intermediate': @@ -188,34 +231,41 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $auth_key_id = fread($payload, 8); if ($auth_key_id == Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) { list($message_id, $message_length) = $this->struct->unpack('add_check_message_ids($message_id); $data = fread($payload, $message_length); - } elseif ($auth_key_id == $this->auth_key_id) { + } elseif ($auth_key_id == $this->settings['authorization']['auth_key_id']) { $message_key = fread($payload, 16); $encrypted_data = stream_get_contents($payload); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, 'from server'); - $decrypted_data = Crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv); - if (substr($decrypted_data, 0, 8) != $this->server_salt) { - throw new Exception('Server salt does not match.'); + $decrypted_data = Tools::fopen_and_write('php://memory', 'rw+b', Crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv)); + $server_salt = fread($decrypted_data, 8); + $session_id = fread($decrypted_data, 8); + if ($server_salt != $this->settings['authorization']['server_salt']) { + throw new Exception('Server salt mismatch.'); } - if (substr($decrypted_data, 8, 8) != $this->session_id) { - throw new Exception('Session id does not match.'); + if ($session_id != $this->settings['authorization']['session_id']) { + throw new Exception('Session id mismatch.'); + } + $message_id = fread($decrypted_data, 8); + $this->add_check_message_ids($message_id); + $seq_no = $this->struct->unpack('seq_no) { + throw new Exception('Seq_no mismatch'); + } + $message_data_length = $this->struct->unpack('struct->unpack('struct->unpack('MAX_RETRY) as $i) { + foreach (range(1, $this->settings['max_tries']['query']) as $i) { try { $this->send_message($this->tl->serialize_method($method, $kwargs)); $server_answer = $this->recv_message(); @@ -232,7 +282,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB throw new Exception('An error occurred while calling method '.$method.'.'); } - public function create_auth_key() + public function create_auth_key($expires_in = -1) { // Make pq request @@ -274,7 +324,11 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $q_bytes = $this->struct->pack('>I', (string) $q); $new_nonce = \phpseclib\Crypt\Random::string(32); - $data = $this->tl->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]); + if ($expires_in == -1) { + $data = $this->tl->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]); + } else { + $data = $this->tl->serialize_obj('p_q_inner_data_temp', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, "expires_in" => $expires_in]); + } $sha_digest = sha1($data, true); // Encrypt serialized object @@ -334,7 +388,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $data_with_sha = sha1($data, true).$data; $data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string(Tools::posmod(-strlen($data_with_sha), 16)); $encrypted_data = Crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv); - foreach (Tools::range(1, $this->AUTH_MAX_RETRY) as $i) { + foreach (Tools::range(1, $this->settings['max_tries']['authorization']) as $i) { $Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]); $auth_key = $g_a->powMod($b, $dh_prime); $auth_key_str = $auth_key->toBytes(); @@ -349,23 +403,23 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB if ($Set_client_DH_params_answer['server_nonce'] != $server_nonce) { throw new Exception('Handshake: wrong server nonce'); } - if ($Set_client_DH_params_answer->name == 'dh_gen_ok') { + if ($Set_client_DH_params_answer['_'] == 'dh_gen_ok') { if ($Set_client_DH_params_answer['new_nonce_hash1'] != $new_nonce_hash1) { throw new Exception('Handshake: wrong new_nonce_hash1'); } $this->log->log('Diffie Hellman key exchange processed successfully'); - $this->server_salt = substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0); - $this->auth_key = $auth_key_str; - $this->auth_key_id = substr($auth_key_sha, -8); + $server_salt = substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0); + $auth_key = $auth_key_str; + $auth_key_id = substr($auth_key_sha, -8); $this->log->log('Auth key generated'); - return 'Auth Ok'; - } elseif ($Set_client_DH_params_answer->name == 'dh_gen_retry') { + return ["auth_key" => $auth_key, "auth_key_id" => $auth_key_id, "server_salt", $server_salt]; + } elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_retry') { if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) { throw new Exception('Handshake: wrong new_nonce_hash_2'); } $this->log->log('Retry Auth'); - } elseif ($Set_client_DH_params_answer->name == 'dh_gen_fail') { + } elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_fail') { if ($Set_client_DH_params_answer['new_nonce_hash3'] != $new_nonce_hash3) { throw new Exception('Handshake: wrong new_nonce_hash_3'); } @@ -375,15 +429,16 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB throw new Exception('Response Error'); } } + throw new Exception('Auth Failed'); } public function aes_calculate($msg_key, $direction = 'to server') { $x = ($direction == 'to server') ? 0 : 8; - $sha1_a = sha1($msg_key.substr($this->auth_key, $x, ($x + 32) - $x), true); - $sha1_b = sha1(substr($this->auth_key, ($x + 32), ($x + 48) - ($x + 32)).$msg_key.substr($this->auth_key, (48 + $x), (64 + $x) - (48 + $x)), true); - $sha1_c = sha1(substr($this->auth_key, ($x + 64), ($x + 96) - ($x + 64)).$msg_key, true); - $sha1_d = sha1($msg_key.substr($this->auth_key, ($x + 96), ($x + 128) - ($x + 96)), true); + $sha1_a = sha1($msg_key.substr($this->settings['authorization']['auth_key'], $x, ($x + 32) - $x), true); + $sha1_b = sha1(substr($this->settings['authorization']['auth_key'], ($x + 32), ($x + 48) - ($x + 32)).$msg_key.substr($this->settings['authorization']['auth_key'], (48 + $x), (64 + $x) - (48 + $x)), true); + $sha1_c = sha1(substr($this->settings['authorization']['auth_key'], ($x + 64), ($x + 96) - ($x + 64)).$msg_key, true); + $sha1_d = sha1($msg_key.substr($this->settings['authorization']['auth_key'], ($x + 96), ($x + 128) - ($x + 96)), true); $aes_key = substr($sha1_a, 0, 8 - 0).substr($sha1_b, 8, 20 - 8).substr($sha1_c, 4, 16 - 4); $aes_iv = substr($sha1_a, 8, 20 - 8).substr($sha1_b, 0, 8 - 0).substr($sha1_c, 16, 20 - 16).substr($sha1_d, 0, 8 - 0); diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index 3e58c2ae..0e2812fa 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -130,11 +130,17 @@ class TL break; } } - + public function deserialize($bytes_io, $type_ = null, $subtype = null) { + return $this->deserialize_length($bytes_io, $type_, $subtype); + } + public function get_length($bytes_io, $type_ = null, $subtype = null) { + $this->deserialize_length($bytes_io, $type_, $subtype); + return ftell($bytes_io); + } /** * :type bytes_io: io.BytesIO object. */ - public function deserialize($bytes_io, $type_ = null, $subtype = null) + public function deserialize_length($bytes_io, $type_ = null, $subtype = null) { if (!(get_resource_type($bytes_io) == 'file' || get_resource_type($bytes_io) == 'stream')) { throw new Exception('An invalid bytes_io handle provided.'); @@ -189,7 +195,7 @@ class TL $count = \danog\PHP\Struct::unpack('deserialize($bytes_io, $subtype); + $x[] = $this->deserialize_length($bytes_io, $subtype); } break; default: @@ -207,11 +213,11 @@ class TL $base_boxed_types = ['Vector t', 'Int', 'Long', 'Double', 'String', 'Int128', 'Int256']; if (in_array($tl_elem->type, $base_boxed_types)) { - $x = $this->deserialize($bytes_io, $tl_elem->predicate, $subtype); + $x = $this->deserialize_length($bytes_io, $tl_elem->predicate, $subtype); } else { - $x = new TLObject($tl_elem); + $x = [ "_" => $tl_elem->predicate ]; foreach ($tl_elem->params as $arg) { - $x[$arg['name']] = $this->deserialize($bytes_io, $arg['type'], $arg['subtype']); + $x[$arg['name']] = $this->deserialize_length($bytes_io, $arg['type'], $arg['subtype']); } } break; diff --git a/src/danog/MadelineProto/TL/TLObject.php b/src/danog/MadelineProto/TL/TLObject.php deleted file mode 100644 index c1a7b063..00000000 --- a/src/danog/MadelineProto/TL/TLObject.php +++ /dev/null @@ -1,22 +0,0 @@ -. -*/ - -namespace danog\MadelineProto\TL; - -class TLObject extends \ArrayObject -{ - public function __construct($tl_elem) - { - parent::__construct(); - $this->name = $tl_elem->predicate; - } -} diff --git a/src/danog/MadelineProto/TL_schema.JSON b/src/danog/MadelineProto/TL_mtproto_v1.json similarity index 100% rename from src/danog/MadelineProto/TL_schema.JSON rename to src/danog/MadelineProto/TL_mtproto_v1.json diff --git a/src/danog/MadelineProto/telegram_layer55.json b/src/danog/MadelineProto/TL_telegram_v55.json similarity index 100% rename from src/danog/MadelineProto/telegram_layer55.json rename to src/danog/MadelineProto/TL_telegram_v55.json