Implemented MTProto v2 in secret chats

This commit is contained in:
Daniil Gentili 2017-12-16 18:08:11 +00:00
parent 2ab82c3a70
commit 8c4ad60f84
12 changed files with 193 additions and 50 deletions

View File

@ -76,12 +76,20 @@ $message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sem
if (!isset($MadelineProto->programmed_call)) { if (!isset($MadelineProto->programmed_call)) {
$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 = new \danog\MadelineProto\API($settings);
$m->import_authorization($MadelineProto->export_authorization()); $m->import_authorization($MadelineProto->export_authorization());
*/ */
$MadelineProto->serialize();
$times = []; $times = [];
$calls = []; $calls = [];
$users = []; $users = [];
@ -133,6 +141,37 @@ $users = [];
\danog\MadelineProto\Logger::log([$update]); \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 $offset = $update['update_id'] + 1; // Just like in the bot API, the offset must be set to the last update_id
switch ($update['update']['_']) { 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': case 'updateNewMessage':
include 'songs.php'; include 'songs.php';
if ($update['update']['message']['out'] || $update['update']['message']['to_id']['_'] !== 'peerUser' || !isset($update['update']['message']['from_id'])) { 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;
} }

View File

@ -80,12 +80,12 @@ while (true) {
/*case 'updateNewChannelMessage': /*case 'updateNewChannelMessage':
if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') continue; if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') continue;
$MadelineProto->messages->sendMessage(['peer' => $update['update']['message']['to_id'], 'message' => $update['update']['message']['message']]); $MadelineProto->messages->sendMessage(['peer' => $update['update']['message']['to_id'], 'message' => $update['update']['message']['message']]);
break;*/ break;
case 'updateNewMessage': case 'updateNewMessage':
if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') { if ($update['update']['message']['out'] || $update['update']['message']['message'] === '') {
continue; continue;
} }
break; break;*/
case 'updateNewEncryptedMessage': case 'updateNewEncryptedMessage':
//var_dump($MadelineProto->download_to_dir($update['update']['message'], '.')); //var_dump($MadelineProto->download_to_dir($update['update']['message'], '.'));
if (isset($sent[$update['update']['message']['chat_id']])) { if (isset($sent[$update['update']['message']['chat_id']])) {

View File

@ -21,6 +21,7 @@ class API extends APIFactory
public function ___construct($params = []) public function ___construct($params = [])
{ {
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
set_exception_handler(['\danog\MadelineProto\Serialization', 'serialize_all']);
if (is_string($params)) { if (is_string($params)) {
if (file_exists($params)) { if (file_exists($params)) {
$this->session = $params; $this->session = $params;
@ -68,6 +69,7 @@ class API extends APIFactory
$this->API = $unserialized->API; $this->API = $unserialized->API;
$this->APIFactory(); $this->APIFactory();
} }
Serialization::$instances[spl_object_hash($unserialized)] = $unserialized;
return; return;
} }
@ -82,11 +84,16 @@ class API extends APIFactory
//\danog\MadelineProto\Logger::log(['Getting future salts...'], Logger::ULTRA_VERBOSE); //\danog\MadelineProto\Logger::log(['Getting future salts...'], Logger::ULTRA_VERBOSE);
//$this->future_salts = $this->get_future_salts(['num' => 3]); //$this->future_salts = $this->get_future_salts(['num' => 3]);
\danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['madelineproto_ready']], Logger::NOTICE); \danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['madelineproto_ready']], Logger::NOTICE);
Serialization::$instances[spl_object_hash($this)] = $this;
} }
public function __wakeup() public function __wakeup()
{ {
//if (method_exists($this->API, 'wakeup')) $this->API = $this->API->wakeup(); //if (method_exists($this->API, 'wakeup')) $this->API = $this->API->wakeup();
Serialization::$instances[spl_object_hash($this)] = $this;
$this->APIFactory(); $this->APIFactory();
} }

View File

@ -46,7 +46,7 @@ class MTProto
/* /*
const V = 71; const V = 71;
*/ */
const V = 80; const V = 82;
const NOT_LOGGED_IN = 0; const NOT_LOGGED_IN = 0;
const WAITING_CODE = 1; const WAITING_CODE = 1;
@ -219,9 +219,6 @@ class MTProto
$this->rsa_keys[$key->fp] = $key; $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 * Define some needed numbers for BigInteger
@ -237,9 +234,15 @@ class MTProto
$this->twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328'); $this->twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
$this->twoe2048 = new \phpseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656'); $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->connect_to_all_dcs();
$this->datacenter->curdc = 2; $this->datacenter->curdc = 2;
if (!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) { if (!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) {
try { try {
$nearest_dc = $this->method_call('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]); $nearest_dc = $this->method_call('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]);
@ -269,6 +272,8 @@ class MTProto
public function __wakeup() public function __wakeup()
{ {
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
set_exception_handler(['\danog\MadelineProto\Serialization', 'serialize_all']);
$this->setup_logger(); $this->setup_logger();
if (\danog\MadelineProto\Logger::$has_thread && is_object(\Thread::getCurrentThread())) { if (\danog\MadelineProto\Logger::$has_thread && is_object(\Thread::getCurrentThread())) {
return; return;
@ -319,7 +324,9 @@ class MTProto
foreach ($this->channels_state as $key => $state) { foreach ($this->channels_state as $key => $state) {
$this->channels_state[$key]['sync_loading'] = false; $this->channels_state[$key]['sync_loading'] = false;
} }
$force = false; $force = false;
$this->reset_session(); $this->reset_session();
if (!isset($this->v) || $this->v !== self::V) { if (!isset($this->v) || $this->v !== self::V) {
\danog\MadelineProto\Logger::log([\danog\MadelineProto\Lang::$current_lang['serialization_ofd']], Logger::WARNING); \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) { foreach ($this->full_chats as $id => $full) {
$this->full_chats[$id] = ['full' => $full['full'], 'last_update' => $full['last_update']]; $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) { foreach ($settings['connection_settings'] as $key => &$connection) {
if (!is_array($connection)) { if (!is_array($connection)) {
continue; continue;
@ -353,6 +367,11 @@ class MTProto
$this->dh_config = ['version' => 0]; $this->dh_config = ['version' => 0];
$this->__construct($settings); $this->__construct($settings);
$force = true; $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']) { if (!$this->settings['updates']['handle_old_updates']) {
$this->channels_state = []; $this->channels_state = [];

View File

@ -14,9 +14,9 @@ namespace danog\MadelineProto\MTProtoTools;
trait Crypt 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_a = hash('sha256', $msg_key.substr($auth_key, $x, 36), true);
$sha256_b = hash('sha256', substr($auth_key, 40 + $x, 36).$msg_key, 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); $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]; 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_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_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); $sha1_c = sha1(substr($auth_key, 64 + $x, 32).$msg_key, true);

View File

@ -74,7 +74,7 @@ trait MessageHandler
} elseif ($auth_key_id === $this->datacenter->sockets[$datacenter]->temp_auth_key['id']) { } elseif ($auth_key_id === $this->datacenter->sockets[$datacenter]->temp_auth_key['id']) {
$message_key = substr($payload, 8, 16); $message_key = substr($payload, 8, 16);
$encrypted_data = substr($payload, 24); $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); $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
/* /*
$server_salt = substr($decrypted_data, 0, 8); $server_salt = substr($decrypted_data, 0, 8);

View File

@ -41,7 +41,7 @@ trait AuthKeyHandler
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8); $key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16); $key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20); $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']); $g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->check_G($g_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]); $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_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20); $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->notify_layer($params['id']);
$this->handle_pending_updates(); $this->handle_pending_updates();
\danog\MadelineProto\Logger::log(['Secret chat '.$params['id'].' completed successfully!'], \danog\MadelineProto\Logger::NOTICE); \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) 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; return;
} }
\danog\MadelineProto\Logger::log(['Committing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE); \danog\MadelineProto\Logger::log(['Committing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);

View File

@ -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->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 = $this->pack_unsigned_int(strlen($message)).$message;
if ($this->secret_chats[$chat_id]['mtproto'] === 2) {
$padding = $this->posmod(-strlen($message), 16);
if ($padding < 12) {
$padding += 16;
}
$message .= $this->random($padding);
$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); $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'); 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->random($this->posmod(-strlen($message), 16));
}
$message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.$this->ige_encrypt($message, $aes_key, $aes_iv); $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); $auth_key_id = substr($message['message']['bytes'], 0, 8);
$old = false; $old = false;
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']) { 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 (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']) { if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint']) {
$this->discard_secret_chat($message['message']['chat_id']); $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']); throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['fingerprint_mismatch']);
} }
} }
$message_key = substr($message['message']['bytes'], 8, 16); $message_key = substr($message['message']['bytes'], 8, 16);
$encrypted_data = substr($message['message']['bytes'], 24); $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); if ($this->secret_chats[$message['message']['chat_id']]['mtproto'] === 2) {
$message_data_length = unpack('V', substr($decrypted_data, 0, 4))[1]; \danog\MadelineProto\Logger::log(['Trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...'], \danog\MadelineProto\Logger::NOTICE);
if ($message_data_length > strlen($decrypted_data)) { try {
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']); $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' => '']); $deserialized = $this->deserialize($message_data, ['type' => '']);
$this->secret_chats[$message['message']['chat_id']]['ttr']--; $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) { 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']); unset($message['message']['bytes']);
$message['message']['decrypted_message'] = $deserialized; $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']; $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); $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;
}
} }

View File

@ -45,6 +45,7 @@ trait ResponseHandler
if ($update['message']['decrypted_message']['action']['layer'] >= 17 && time() - $this->secret_chats[$update['message']['chat_id']]['created'] > 15) { 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']); $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; return;
@ -53,12 +54,20 @@ trait ResponseHandler
return; return;
case 'decryptedMessageActionNoop':
return;
case 'decryptedMessageActionResend': 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) { 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']) { 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']); //throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']);
// $this->send_encrypted_message($update['message']['chat_id'], $update['message']['decrypted_message']); $this->method_call('messages.sendEncrypted', ['peer' => $update['message']['chat_id'], 'message' => $update['message']['decrypted_message']], ['datacenter' => $this->datacenter->curdc]);
} }
} }

View File

@ -17,6 +17,11 @@ namespace danog\MadelineProto;
*/ */
class Serialization 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. * Serialize API class.
* *
@ -26,7 +31,7 @@ class Serialization
* *
* @return number * @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) { if (isset($instance->API->setdem) && $instance->API->setdem) {
$instance->API->setdem = false; $instance->API->setdem = false;
@ -56,7 +61,7 @@ class Serialization
* *
* @return API * @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($filename)) {
if (!file_exists($lock = $filename.'.lock')) { if (!file_exists($lock = $filename.'.lock')) {
@ -102,6 +107,7 @@ class Serialization
if ($unserialized instanceof \danog\MadelineProto\API) { if ($unserialized instanceof \danog\MadelineProto\API) {
$unserialized->session = $filename; $unserialized->session = $filename;
} }
self::$instances[spl_object_hash($unserialized)] = $unserialized;
return $unserialized; return $unserialized;
} }

View File

@ -183,11 +183,6 @@ trait TL
$this->encrypted_layer = max($this->encrypted_layer, $elem['layer']); $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'])) { if (isset($files['td']) && isset($files['telegram'])) {
foreach ($this->td_constructors->by_id as $id => $data) { foreach ($this->td_constructors->by_id as $id => $data) {

View File

@ -71,3 +71,6 @@ documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = Docume
===66=== ===66===
documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute; 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<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = DecryptedMessage;