diff --git a/magna.php b/magna.php index 56e62fce..f5c565ff 100755 --- a/magna.php +++ b/magna.php @@ -76,12 +76,20 @@ $message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sem if (!isset($MadelineProto->programmed_call)) { $MadelineProto->programmed_call = []; } +$MadelineProto->session = 'session.madeline'; + $inputEncryptedFilePhoto = $MadelineProto->upload_encrypted('tests/faust.jpg', 'fausticorn.jpg'); // This gets an inputFile object with file name magic + $inputEncryptedFileGif = $MadelineProto->upload_encrypted('tests/pony.mp4'); + $inputEncryptedFileSticker = $MadelineProto->upload_encrypted('tests/lel.webp'); + $inputEncryptedFileDocument = $MadelineProto->upload_encrypted('tests/60', 'magic'); // This gets an inputFile object with file name magic + $inputEncryptedFileVideo = $MadelineProto->upload_encrypted('tests/swing.mp4'); + $inputEncryptedFileAudio = $MadelineProto->upload_encrypted('tests/mosconi.mp3'); -echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL; echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL; /* $m = new \danog\MadelineProto\API($settings); $m->import_authorization($MadelineProto->export_authorization()); */ + +$MadelineProto->serialize(); $times = []; $calls = []; $users = []; @@ -133,6 +141,37 @@ $users = []; \danog\MadelineProto\Logger::log([$update]); $offset = $update['update_id'] + 1; // Just like in the bot API, the offset must be set to the last update_id switch ($update['update']['_']) { + case 'updateNewEncryptedMessage': + $secret = $update['update']['message']['chat_id']; + $secret_media = []; + + // Photo uploaded as document, secret chat + $secret_media['document_photo'] = ['peer' => $secret, 'file' => $inputEncryptedFilePhoto, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/faust.jpg'), 'caption' => 'This file was uploaded using MadelineProto', 'key' => $inputEncryptedFilePhoto['key'], 'iv' => $inputEncryptedFilePhoto['iv'], 'file_name' => 'faust.jpg', 'size' => filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeImageSize', 'w' => 1280, 'h' => 914]]]]]; + + // Photo, secret chat + $secret_media['photo'] = ['peer' => $secret, 'file' => $inputEncryptedFilePhoto, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaPhoto', 'thumb' => file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'caption' => 'This file was uploaded using MadelineProto', 'key' => $inputEncryptedFilePhoto['key'], 'iv' => $inputEncryptedFilePhoto['iv'], 'size' => filesize('tests/faust.jpg'), 'w' => 1280, 'h' => 914]]]; + + // GIF, secret chat + $secret_media['gif'] = ['peer' => $secret, 'file' => $inputEncryptedFileGif, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/pony.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/pony.mp4'), 'caption' => 'test', 'key' => $inputEncryptedFileGif['key'], 'iv' => $inputEncryptedFileGif['iv'], 'file_name' => 'pony.mp4', 'size' => filesize('tests/faust.jpg'), 'attributes' => [['_' => 'documentAttributeAnimated']]]]]; + + // Sticker, secret chat + $secret_media['sticker'] = ['peer' => $secret, 'file' => $inputEncryptedFileSticker, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/lel.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/lel.webp'), 'caption' => 'test', 'key' => $inputEncryptedFileSticker['key'], 'iv' => $inputEncryptedFileSticker['iv'], 'file_name' => 'lel.webp', 'size' => filesize('tests/lel.webp'), 'attributes' => [['_' => 'documentAttributeSticker', 'alt' => 'LEL', 'stickerset' => ['_' => 'inputStickerSetEmpty']]]]]]; + + // Document, secrey chat + $secret_media['document'] = ['peer' => $secret, 'file' => $inputEncryptedFileDocument, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => 'magic/magic', 'caption' => 'test', 'key' => $inputEncryptedFileDocument['key'], 'iv' => $inputEncryptedFileDocument['iv'], 'file_name' => 'magic.magic', 'size' => filesize('tests/60'), 'attributes' => [['_' => 'documentAttributeFilename', 'file_name' => 'fairy']]]]]; + + // Video, secret chat + $secret_media['video'] = ['peer' => $secret, 'file' => $inputEncryptedFileVideo, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/swing.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/swing.mp4'), 'caption' => 'test', 'key' => $inputEncryptedFileVideo['key'], 'iv' => $inputEncryptedFileVideo['iv'], 'file_name' => 'swing.mp4', 'size' => filesize('tests/swing.mp4'), 'attributes' => [['_' => 'documentAttributeVideo', 'duration' => 5, 'w' => 1280, 'h' => 720]]]]]; + + // audio, secret chat + $secret_media['audio'] = ['peer' => $secret, 'file' => $inputEncryptedFileAudio, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'key' => $inputEncryptedFileAudio['key'], 'iv' => $inputEncryptedFileAudio['iv'], 'file_name' => 'mosconi.mp3', 'size' => filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => false, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]]; + + $secret_media['voice'] = ['peer' => $secret, 'file' => $inputEncryptedFileAudio, 'message' => ['_' => 'decryptedMessage', 'ttl' => 0, 'message' => '', 'media' => ['_' => 'decryptedMessageMediaDocument', 'thumb' => file_get_contents('tests/faust.preview.jpg'), 'thumb_w' => 90, 'thumb_h' => 90, 'mime_type' => mime_content_type('tests/mosconi.mp3'), 'caption' => 'test', 'key' => $inputEncryptedFileAudio['key'], 'iv' => $inputEncryptedFileAudio['iv'], 'file_name' => 'mosconi.mp3', 'size' => filesize('tests/mosconi.mp3'), 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => true, 'duration' => 1, 'title' => 'AH NON LO SO IO', 'performer' => 'IL DIO GERMANO MOSCONI']]]]]; + + foreach ($secret_media as $type => $smessage) { + $type = $MadelineProto->messages->sendEncryptedFile($smessage); + } + break; case 'updateNewMessage': include 'songs.php'; if ($update['update']['message']['out'] || $update['update']['message']['to_id']['_'] !== 'peerUser' || !isset($update['update']['message']['from_id'])) { @@ -232,5 +271,4 @@ Propic art by @magnaluna on deviantart.", 'parse_mode' => 'Markdown']); } } - echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL; } diff --git a/secret_bot.php b/secret_bot.php index fe762c95..817b15a3 100755 --- a/secret_bot.php +++ b/secret_bot.php @@ -80,12 +80,12 @@ while (true) { /*case 'updateNewChannelMessage': if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') continue; $MadelineProto->messages->sendMessage(['peer' => $update['update']['message']['to_id'], 'message' => $update['update']['message']['message']]); - break;*/ + break; case 'updateNewMessage': if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') { continue; } - break; + break;*/ case 'updateNewEncryptedMessage': //var_dump($MadelineProto->download_to_dir($update['update']['message'], '.')); if (isset($sent[$update['update']['message']['chat_id']])) { diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index 77790ce8..0f69a822 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -21,6 +21,7 @@ class API extends APIFactory public function ___construct($params = []) { set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); + set_exception_handler(['\danog\MadelineProto\Serialization', 'serialize_all']); if (is_string($params)) { if (file_exists($params)) { $this->session = $params; @@ -68,6 +69,7 @@ class API extends APIFactory $this->API = $unserialized->API; $this->APIFactory(); } + Serialization::$instances[spl_object_hash($unserialized)] = $unserialized; return; } @@ -82,11 +84,16 @@ class API extends APIFactory //\danog\MadelineProto\Logger::log(['Getting future salts...'], Logger::ULTRA_VERBOSE); //$this->future_salts = $this->get_future_salts(['num' => 3]); \danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['madelineproto_ready']], Logger::NOTICE); + + Serialization::$instances[spl_object_hash($this)] = $this; + + } public function __wakeup() { //if (method_exists($this->API, 'wakeup')) $this->API = $this->API->wakeup(); + Serialization::$instances[spl_object_hash($this)] = $this; $this->APIFactory(); } diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index e9be9a1f..e50ad26b 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -46,7 +46,7 @@ class MTProto /* const V = 71; */ - const V = 80; + const V = 82; const NOT_LOGGED_IN = 0; const WAITING_CODE = 1; @@ -219,9 +219,6 @@ class MTProto $this->rsa_keys[$key->fp] = $key; } - // Istantiate TL class - \danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['TL_translation']], Logger::ULTRA_VERBOSE); - $this->construct_TL($this->settings['tl_schema']['src']); /* * *********************************************************************** * Define some needed numbers for BigInteger @@ -237,9 +234,15 @@ class MTProto $this->twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328'); $this->twoe2048 = new \phpseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656'); + \danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['TL_translation']], Logger::ULTRA_VERBOSE); + $this->construct_TL($this->settings['tl_schema']['src']); + + // Istantiate TL class $this->connect_to_all_dcs(); $this->datacenter->curdc = 2; + + if (!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) { try { $nearest_dc = $this->method_call('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]); @@ -269,6 +272,8 @@ class MTProto public function __wakeup() { set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); + set_exception_handler(['\danog\MadelineProto\Serialization', 'serialize_all']); + $this->setup_logger(); if (\danog\MadelineProto\Logger::$has_thread && is_object(\Thread::getCurrentThread())) { return; @@ -319,7 +324,9 @@ class MTProto foreach ($this->channels_state as $key => $state) { $this->channels_state[$key]['sync_loading'] = false; } + $force = false; + $this->reset_session(); if (!isset($this->v) || $this->v !== self::V) { \danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['serialization_ofd']], Logger::WARNING); @@ -334,6 +341,13 @@ class MTProto foreach ($this->full_chats as $id => $full) { $this->full_chats[$id] = ['full' => $full['full'], 'last_update' => $full['last_update']]; } + foreach ($this->secret_chats as $key => &$chat) { + if ($chat['layer'] >= 73) { + $chat['mtproto'] = 2; + } else { + $chat['mtproto'] = 1; + } + } foreach ($settings['connection_settings'] as $key => &$connection) { if (!is_array($connection)) { continue; @@ -353,6 +367,11 @@ class MTProto $this->dh_config = ['version' => 0]; $this->__construct($settings); $force = true; + foreach ($this->secret_chats as $chat => $data) { + try { + if (isset($this->secret_chats[$chat]) && $this->secret_chats[$chat]['InputEncryptedChat'] !== NULL) $this->notify_layer($chat); + } catch (\danog\MadelineProto\RPCErrorException $e) {} + } } if (!$this->settings['updates']['handle_old_updates']) { $this->channels_state = []; diff --git a/src/danog/MadelineProto/MTProtoTools/Crypt.php b/src/danog/MadelineProto/MTProtoTools/Crypt.php index 489a4894..c865b5e0 100644 --- a/src/danog/MadelineProto/MTProtoTools/Crypt.php +++ b/src/danog/MadelineProto/MTProtoTools/Crypt.php @@ -14,9 +14,9 @@ namespace danog\MadelineProto\MTProtoTools; trait Crypt { - public function aes_calculate($msg_key, $auth_key, $direction = 'to server') + public function aes_calculate($msg_key, $auth_key, $to_server = true) { - $x = ($direction === 'to server') ? 0 : 8; + $x = $to_server ? 0 : 8; $sha256_a = hash('sha256', $msg_key.substr($auth_key, $x, 36), true); $sha256_b = hash('sha256', substr($auth_key, 40 + $x, 36).$msg_key, true); $aes_key = substr($sha256_a, 0, 8).substr($sha256_b, 8, 16).substr($sha256_a, 24, 8); @@ -25,9 +25,9 @@ trait Crypt return [$aes_key, $aes_iv]; } - public function old_aes_calculate($msg_key, $auth_key, $direction = 'to server') + public function old_aes_calculate($msg_key, $auth_key, $to_server = true) { - $x = ($direction === 'to server') ? 0 : 8; + $x = $to_server ? 0 : 8; $sha1_a = sha1($msg_key.substr($auth_key, $x, 32), true); $sha1_b = sha1(substr($auth_key, 32 + $x, 16).$msg_key.substr($auth_key, 48 + $x, 16), true); $sha1_c = sha1(substr($auth_key, 64 + $x, 32).$msg_key, true); diff --git a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php index 777d1507..e8e78f15 100644 --- a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php @@ -74,7 +74,7 @@ trait MessageHandler } elseif ($auth_key_id === $this->datacenter->sockets[$datacenter]->temp_auth_key['id']) { $message_key = substr($payload, 8, 16); $encrypted_data = substr($payload, 24); - list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key'], 'from server'); + list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key'], false); $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); /* $server_salt = substr($decrypted_data, 0, 8); diff --git a/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php b/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php index 3f4addf7..20ba0a37 100644 --- a/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php +++ b/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php @@ -41,7 +41,7 @@ trait AuthKeyHandler $key['fingerprint'] = substr(sha1($key['auth_key'], true), -8); $key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16); $key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20); - $this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]]; + $this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0], 'key_x' => 'from server', 'mtproto' => 1]; $g_b = $dh_config['g']->powMod($b, $dh_config['p']); $this->check_G($g_b, $dh_config['p']); $this->method_call('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']], ['datacenter' => $this->datacenter->curdc]); @@ -96,7 +96,7 @@ trait AuthKeyHandler } $key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16); $key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20); - $this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]]; + $this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0], 'key_x' => 'to server', 'mtproto' => 1]; $this->notify_layer($params['id']); $this->handle_pending_updates(); \danog\MadelineProto\Logger::log(['Secret chat '.$params['id'].' completed successfully!'], \danog\MadelineProto\Logger::NOTICE); @@ -168,7 +168,8 @@ trait AuthKeyHandler public function commit_rekey($chat, $params) { - if ($this->secret_chats[$chat]['rekeying'][0] !== 1) { + if ($this->secret_chats[$chat]['rekeying'][0] !== 1 || !isset($this->temp_rekeyed_secret_chats[$params['exchange_id']])) { + $this->secret_chats[$chat]['rekeying'] = [0]; return; } \danog\MadelineProto\Logger::log(['Committing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE); diff --git a/src/danog/MadelineProto/SecretChats/MessageHandler.php b/src/danog/MadelineProto/SecretChats/MessageHandler.php index 0cc17842..e49bc94b 100644 --- a/src/danog/MadelineProto/SecretChats/MessageHandler.php +++ b/src/danog/MadelineProto/SecretChats/MessageHandler.php @@ -37,10 +37,21 @@ trait MessageHandler $message = $this->serialize_object(['type' => $constructor = $this->secret_chats[$chat_id]['layer'] === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer'], $message, $constructor, $this->secret_chats[$chat_id]['layer']); $message = $this->pack_unsigned_int(strlen($message)).$message; - $message_key = substr(sha1($message, true), -16); - list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], 'to server'); + if ($this->secret_chats[$chat_id]['mtproto'] === 2) { + $padding = $this->posmod(-strlen($message), 16); + if ($padding < 12) { + $padding += 16; + } + $message .= $this->random($padding); - $message .= $this->random($this->posmod(-strlen($message), 16)); + $message_key = substr(hash('sha256', substr($this->secret_chats[$chat_id]['key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 0 : 8), 32).$message, true), 8, 16); + list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], $this->secret_chats[$chat_id]['admin']); + } else { + $message_key = substr(sha1($message, true), -16); + list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], true); + + $message .= $this->random($this->posmod(-strlen($message), 16)); + } $message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.$this->ige_encrypt($message, $aes_key, $aes_iv); @@ -57,7 +68,6 @@ trait MessageHandler $auth_key_id = substr($message['message']['bytes'], 0, 8); $old = false; if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']) { - //var_dump($auth_key_id, $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']); if (isset($this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint'])) { if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint']) { $this->discard_secret_chat($message['message']['chat_id']); @@ -71,26 +81,35 @@ trait MessageHandler throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['fingerprint_mismatch']); } } + $message_key = substr($message['message']['bytes'], 8, 16); $encrypted_data = substr($message['message']['bytes'], 24); - list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->secret_chats[$message['message']['chat_id']][$old ? 'old_key' : 'key']['auth_key'], 'to server'); - $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); - $message_data_length = unpack('V', substr($decrypted_data, 0, 4))[1]; - if ($message_data_length > strlen($decrypted_data)) { - throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']); + + if ($this->secret_chats[$message['message']['chat_id']]['mtproto'] === 2) { + \danog\MadelineProto\Logger::log(['Trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + try { + $message_data = $this->try_mtproto_v2_decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data); + \danog\MadelineProto\Logger::log(['MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + } catch (\danog\MadelineProto\SecurityException $e) { + \danog\MadelineProto\Logger::log(['MTProto v2 decryption failed with message '.$e->getMessage().', trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + $message_data = $this->try_mtproto_v1_decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data); + \danog\MadelineProto\Logger::log(['MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + $this->secret_chats[$message['message']['chat_id']]['mtproto'] = 1; + } + } else { + \danog\MadelineProto\Logger::log(['Trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + try { + $message_data = $this->try_mtproto_v1_decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data); + \danog\MadelineProto\Logger::log(['MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + } catch (\danog\MadelineProto\SecurityException $e) { + \danog\MadelineProto\Logger::log(['MTProto v1 decryption failed with message '.$e->getMessage().', trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + $message_data = $this->try_mtproto_v2_decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data); + \danog\MadelineProto\Logger::log(['MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE); + + $this->secret_chats[$message['message']['chat_id']]['mtproto'] = 2; + } } - if ((strlen($decrypted_data) - 4) - $message_data_length > 15) { - throw new \danog\MadelineProto\SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big'); - } - - if (strlen($decrypted_data) % 16 != 0) { - throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16']); - } - $message_data = substr($decrypted_data, 4, $message_data_length); - if ($message_key != substr(sha1(substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) { - throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']); - } $deserialized = $this->deserialize($message_data, ['type' => '']); $this->secret_chats[$message['message']['chat_id']]['ttr']--; if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'][0] === 0) { @@ -98,12 +117,58 @@ trait MessageHandler } unset($message['message']['bytes']); $message['message']['decrypted_message'] = $deserialized; - if ($test = true) { - //var_dump($message); - } $this->secret_chats[$message['message']['chat_id']]['incoming'][$this->secret_chats[$message['message']['chat_id']]['in_seq_no']] = $message['message']; - //var_dump($message); $this->handle_decrypted_update($message); } + public function try_mtproto_v1_decrypt($message_key, $chat_id, $old, $encrypted_data) { + list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], true); + $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); + + $message_data_length = unpack('V', substr($decrypted_data, 0, 4))[1]; + $message_data = substr($decrypted_data, 4, $message_data_length); + if ($message_data_length > strlen($decrypted_data)) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']); + } + + if ($message_key != substr(sha1(substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']); + } + if ((strlen($decrypted_data) - 4) - $message_data_length > 15) { + throw new \danog\MadelineProto\SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big'); + } + if (strlen($decrypted_data) % 16 != 0) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16']); + } + + return $message_data; + } + public function try_mtproto_v2_decrypt($message_key, $chat_id, $old, $encrypted_data) { + list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], !$this->secret_chats[$chat_id]['admin']); + $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); + + $message_data_length = unpack('V', substr($decrypted_data, 0, 4))[1]; + $message_data = substr($decrypted_data, 4, $message_data_length); + if ($message_data_length > strlen($decrypted_data)) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']); + } + + if ($message_key != substr(hash('sha256', substr($this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 8 : 0), 32).$decrypted_data, true), 8, 16)) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']); + } + + if ((strlen($decrypted_data) - 4) - $message_data_length < 12) { + throw new \danog\MadelineProto\SecurityException('padding is too small'); + } + + if ((strlen($decrypted_data) - 4) - $message_data_length > 1024) { + throw new \danog\MadelineProto\SecurityException('padding is too big'); + } + if (strlen($decrypted_data) % 16 != 0) { + throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16']); + } + + return $message_data; + } + } diff --git a/src/danog/MadelineProto/SecretChats/ResponseHandler.php b/src/danog/MadelineProto/SecretChats/ResponseHandler.php index 2e0cd3c7..3845ec12 100644 --- a/src/danog/MadelineProto/SecretChats/ResponseHandler.php +++ b/src/danog/MadelineProto/SecretChats/ResponseHandler.php @@ -45,6 +45,7 @@ trait ResponseHandler if ($update['message']['decrypted_message']['action']['layer'] >= 17 && time() - $this->secret_chats[$update['message']['chat_id']]['created'] > 15) { $this->notify_layer($update['message']['chat_id']); } + if ($update['message']['decrypted_message']['action']['layer'] >= 73) $this->secret_chats[$update['message']['chat_id']]['mtproto'] = 2; return; @@ -53,12 +54,20 @@ trait ResponseHandler return; + case 'decryptedMessageActionNoop': + + return; + case 'decryptedMessageActionResend': + $update['message']['decrypted_message']['action']['start_seq_no'] -= $this->secret_chats[$update['message']['chat_id']]['out_seq_no_x']; + $update['message']['decrypted_message']['action']['end_seq_no'] -= $this->secret_chats[$update['message']['chat_id']]['out_seq_no_x']; + $update['message']['decrypted_message']['action']['start_seq_no'] /= 2; + $update['message']['decrypted_message']['action']['end_seq_no'] /= 2; + \danog\MadelineProto\Logger::log(['Resending messages for secret chat '.$update['message']['chat_id']], \danog\MadelineProto\Logger::WARNING); foreach ($this->secret_chats[$update['message']['chat_id']]['outgoing'] as $seq => $message) { - $seq--; if ($seq >= $update['message']['decrypted_message']['action']['start_seq_no'] && $seq <= $update['message']['decrypted_message']['action']['end_seq_no']) { - throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']); - // $this->send_encrypted_message($update['message']['chat_id'], $update['message']['decrypted_message']); + //throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']); + $this->method_call('messages.sendEncrypted', ['peer' => $update['message']['chat_id'], 'message' => $update['message']['decrypted_message']], ['datacenter' => $this->datacenter->curdc]); } } diff --git a/src/danog/MadelineProto/Serialization.php b/src/danog/MadelineProto/Serialization.php index c40b181b..d0fd4e51 100644 --- a/src/danog/MadelineProto/Serialization.php +++ b/src/danog/MadelineProto/Serialization.php @@ -17,6 +17,11 @@ namespace danog\MadelineProto; */ class Serialization { + static public $instances = []; + static public function serialize_all($exception) { + echo $exception.PHP_EOL; + foreach (self::$instances as $instance) { if (isset($instance->session)) $instance->serialize(); } + } /** * Serialize API class. * @@ -26,7 +31,7 @@ class Serialization * * @return number */ - public static function serialize($filename, $instance, $force = false) + static public function serialize($filename, $instance, $force = false) { if (isset($instance->API->setdem) && $instance->API->setdem) { $instance->API->setdem = false; @@ -56,7 +61,7 @@ class Serialization * * @return API */ - public static function deserialize($filename, $no_updates = false) + static public function deserialize($filename, $no_updates = false) { if (file_exists($filename)) { if (!file_exists($lock = $filename.'.lock')) { @@ -102,6 +107,7 @@ class Serialization if ($unserialized instanceof \danog\MadelineProto\API) { $unserialized->session = $filename; } + self::$instances[spl_object_hash($unserialized)] = $unserialized; return $unserialized; } diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index 2a3a87f5..54ccef11 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -183,11 +183,6 @@ trait TL $this->encrypted_layer = max($this->encrypted_layer, $elem['layer']); } } - if ($this->encrypted_layer != $orig && isset($this->secret_chats)) { - foreach ($this->secret_chats as $chat => $data) { - $this->notify_layer($chat); - } - } } if (isset($files['td']) && isset($files['telegram'])) { foreach ($this->td_constructors->by_id as $id => $data) { diff --git a/src/danog/MadelineProto/TL_secret.tl b/src/danog/MadelineProto/TL_secret.tl index c3424dc2..3ff3f13d 100644 --- a/src/danog/MadelineProto/TL_secret.tl +++ b/src/danog/MadelineProto/TL_secret.tl @@ -71,3 +71,6 @@ documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = Docume ===66=== documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute; + +===73=== +decryptedMessage#91cc4674 flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = DecryptedMessage;