MadelineProto/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php

740 lines
42 KiB
PHP

<?php
/**
* AuthKeyHandler module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Http\Client\Request;
use danog\MadelineProto\DataCenterConnection;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\MTProto\AuthKey;
use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\MTProto\TempAuthKey;
use danog\PrimeModule;
use tgseclib\Math\BigInteger;
/**
* Manages the creation of the authorization key.
*
* https://core.telegram.org/mtproto/auth_key
* https://core.telegram.org/mtproto/samples-auth_key
*/
trait AuthKeyHandler
{
/**
* DCs currently initing authorization.
*
* @var array<bool>
*/
private $init_auth_dcs = [];
/**
* Currently initing authorization?
*
* @var boolean
*/
private $pending_auth = false;
/**
* Create authorization key.
*
* @param int $expires_in Expiry date of auth key, -1 for permanent auth key
* @param string $datacenter DC ID
*
* @internal
*
* @return \Generator<AuthKey>
*/
public function createAuthKey(int $expires_in, string $datacenter): \Generator
{
$connection = $this->datacenter->getAuthConnection($datacenter);
$cdn = $connection->isCDN();
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
/**
* ***********************************************************************
* Make pq request, DH exchange initiation.
*
* @method req_pq
*
* @param [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* ]
*
* @return ResPQ [
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $pq : This is a representation of a natural number (in binary big endian format). This number is the product of two different odd prime numbers
* Vector long $server_public_key_fingerprints : This is a list of public RSA key fingerprints
* ]
*/
$nonce = \danog\MadelineProto\Tools::random(16);
$ResPQ = yield from $connection->methodCallAsyncRead($req_pq, ['nonce' => $nonce]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
*/
if ($ResPQ['nonce'] !== $nonce) {
throw new \danog\MadelineProto\SecurityException('wrong nonce');
}
/*
* ***********************************************************************
* Find our key in the server_public_key_fingerprints vector
*/
foreach ($cdn ? \array_merge($this->cdn_rsa_keys, $this->rsa_keys) : $this->rsa_keys as $curkey) {
if (\in_array($curkey->fp, $ResPQ['server_public_key_fingerprints'])) {
$key = $curkey;
}
}
if (!isset($key)) {
throw new \danog\MadelineProto\SecurityException("Couldn't find any of our keys in the server_public_key_fingerprints vector.");
}
$pq_bytes = $ResPQ['pq'];
$server_nonce = $ResPQ['server_nonce'];
/*
* ***********************************************************************
* Compute p and q
*/
$pq = (string) new BigInteger((string) $pq_bytes, 256);
foreach ([
'auto_single',
'native_single_cpp',
'python_single_alt',
'python_single',
'native_single',
'wolfram'
] as $method) {
$this->logger->logger("Factorizing with $method");
$q = new BigInteger(0);
try {
if ($method === 'wolfram') {
$p = new BigInteger(yield from $this->wolframSingle($pq));
} else {
$p = new BigInteger(@PrimeModule::$method($pq));
}
} catch (\Throwable $e) {
$this->logger->logger("While factorizing with $method: $e");
}
if (!$p->equals(\danog\MadelineProto\Magic::$zero)) {
$q = $pq->divide($p)[0];
if ($p->compare($q) > 0) {
list($p, $q) = [$q, $p];
}
}
if ($pq->equals($p->multiply($q))) {
break;
}
}
if (!$pq->equals($p->multiply($q))) {
throw new \danog\MadelineProto\SecurityException("Couldn't compute p and q, install prime.madelineproto.xyz to fix. Original pq: {$pq}, computed p: {$p}, computed q: {$q}, computed pq: ".$p->multiply($q));
}
$this->logger->logger('Factorization '.$pq.' = '.$p.' * '.$q, \danog\MadelineProto\Logger::VERBOSE);
/*
* ***********************************************************************
* Serialize object for req_DH_params
*/
$p_bytes = $p->toBytes();
$q_bytes = $q->toBytes();
$new_nonce = \danog\MadelineProto\Tools::random(32);
$data_unserialized = ['_' => 'p_q_inner_data'.($expires_in < 0 ? '' : '_temp'), 'pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in, 'dc' => \preg_replace('|_.*|', '', $datacenter)];
$p_q_inner_data = (yield from $this->TL->serializeObject(['type' => ''], $data_unserialized, 'p_q_inner_data'));
/*
* ***********************************************************************
* Encrypt serialized object
*/
$sha_digest = \sha1($p_q_inner_data, true);
$random_bytes = \danog\MadelineProto\Tools::random(255 - \strlen($p_q_inner_data) - \strlen($sha_digest));
$to_encrypt = $sha_digest.$p_q_inner_data.$random_bytes;
$encrypted_data = $key->encrypt($to_encrypt);
$this->logger->logger('Starting Diffie Hellman key exchange', \danog\MadelineProto\Logger::VERBOSE);
/*
* ***********************************************************************
* Starting Diffie Hellman key exchange, Server authentication
* @method req_DH_params
* @param [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $p : The value of BigInteger
* string $q : The value of BigInteger
* long $public_key_fingerprint : This is our key in the server_public_key_fingerprints vector
* string $encrypted_data
* ]
* @return Server_DH_Params [
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $new_nonce_hash : Return this value if server responds with server_DH_params_fail
* string $encrypted_answer : Return this value if server responds with server_DH_params_ok
* ]
*/
//
$server_dh_params = yield from $connection->methodCallAsyncRead('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $key->fp, 'encrypted_data' => $encrypted_data]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
*/
if ($nonce != $server_dh_params['nonce']) {
throw new \danog\MadelineProto\SecurityException('wrong nonce.');
}
/*
* ***********************************************************************
* Check if server_nonce and new server_nonce are the same
*/
if ($server_nonce != $server_dh_params['server_nonce']) {
throw new \danog\MadelineProto\SecurityException('wrong server nonce.');
}
/*
* ***********************************************************************
* Check valid new nonce hash if return from server
* new nonce hash return in server_DH_params_fail
*/
if (isset($server_dh_params['new_nonce_hash']) && \substr(\sha1($new_nonce), -32) != $server_dh_params['new_nonce_hash']) {
throw new \danog\MadelineProto\SecurityException('wrong new nonce hash.');
}
/*
* ***********************************************************************
* Get key, iv and decrypt answer
*/
$encrypted_answer = $server_dh_params['encrypted_answer'];
$tmp_aes_key = \sha1($new_nonce.$server_nonce, true).\substr(\sha1($server_nonce.$new_nonce, true), 0, 12);
$tmp_aes_iv = \substr(\sha1($server_nonce.$new_nonce, true), 12, 8).\sha1($new_nonce.$new_nonce, true).\substr($new_nonce, 0, 4);
$answer_with_hash = Crypt::igeDecrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
/*
* ***********************************************************************
* Separate answer and hash
*/
$answer_hash = \substr($answer_with_hash, 0, 20);
$answer = \substr($answer_with_hash, 20);
/*
* ***********************************************************************
* Deserialize answer
* @return Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int $g
* string $dh_prime
* string $g_a
* int $server_time
* ]
*/
$server_DH_inner_data = $this->TL->deserialize($answer, ['type' => '']);
/*
* ***********************************************************************
* Do some checks
*/
$server_DH_inner_data_length = $this->TL->getLength($answer);
if (\sha1(\substr($answer, 0, $server_DH_inner_data_length), true) != $answer_hash) {
throw new \danog\MadelineProto\SecurityException('answer_hash mismatch.');
}
if ($nonce != $server_DH_inner_data['nonce']) {
throw new \danog\MadelineProto\SecurityException('wrong nonce');
}
if ($server_nonce != $server_DH_inner_data['server_nonce']) {
throw new \danog\MadelineProto\SecurityException('wrong server nonce');
}
$g = new BigInteger($server_DH_inner_data['g']);
$g_a = new BigInteger((string) $server_DH_inner_data['g_a'], 256);
$dh_prime = new BigInteger((string) $server_DH_inner_data['dh_prime'], 256);
/*
* ***********************************************************************
* Time delta
*/
$server_time = $server_DH_inner_data['server_time'];
$connection->time_delta = $server_time - \time();
$this->logger->logger(\sprintf('Server-client time delta = %.1f s', $connection->time_delta), \danog\MadelineProto\Logger::VERBOSE);
$this->checkPG($dh_prime, $g);
$this->checkG($g_a, $dh_prime);
for ($retry_id = 0; $retry_id <= $this->settings['max_tries']['authorization']; $retry_id++) {
$this->logger->logger('Generating b...', \danog\MadelineProto\Logger::VERBOSE);
$b = new BigInteger(\danog\MadelineProto\Tools::random(256), 256);
$this->logger->logger('Generating g_b...', \danog\MadelineProto\Logger::VERBOSE);
$g_b = $g->powMod($b, $dh_prime);
$this->checkG($g_b, $dh_prime);
/*
* ***********************************************************************
* Check validity of g_b
* 1 < g_b < dh_prime - 1
*/
$this->logger->logger('Executing g_b check...', \danog\MadelineProto\Logger::VERBOSE);
if ($g_b->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g_b->compare($dh_prime->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g_b is invalid (1 < g_b < dh_prime - 1 is false).');
}
$this->logger->logger('Preparing client_DH_inner_data...', \danog\MadelineProto\Logger::VERBOSE);
$g_b_str = $g_b->toBytes();
/*
* ***********************************************************************
* serialize client_DH_inner_data
* @method client_DH_inner_data
* @param Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* long $retry_id : First attempt
* string $g_b : g^b mod dh_prime
* ]
*/
$data = (yield from $this->TL->serializeObject(['type' => ''], ['_' => 'client_DH_inner_data', 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'retry_id' => $retry_id, 'g_b' => $g_b_str], 'client_DH_inner_data'));
/*
* ***********************************************************************
* encrypt client_DH_inner_data
*/
$data_with_sha = \sha1($data, true).$data;
$data_with_sha_padded = $data_with_sha.\danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($data_with_sha), 16));
$encrypted_data = Crypt::igeEncrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
$this->logger->logger('Executing set_client_DH_params...', \danog\MadelineProto\Logger::VERBOSE);
/*
* ***********************************************************************
* Send set_client_DH_params query
* @method set_client_DH_params
* @param Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $encrypted_data
* ]
* @return Set_client_DH_params_answer [
* string $_ : This value is dh_gen_ok, dh_gen_retry OR dh_gen_fail
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int128 $new_nonce_hash1 : Return this value if server responds with dh_gen_ok
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_retry
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_fail
* ]
*/
$Set_client_DH_params_answer = yield from $connection->methodCallAsyncRead('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]);
/*
* ***********************************************************************
* Generate auth_key
*/
$this->logger->logger('Generating authorization key...', \danog\MadelineProto\Logger::VERBOSE);
$auth_key = $g_a->powMod($b, $dh_prime);
$auth_key_str = $auth_key->toBytes();
$auth_key_sha = \sha1($auth_key_str, true);
$auth_key_aux_hash = \substr($auth_key_sha, 0, 8);
$new_nonce_hash1 = \substr(\sha1($new_nonce.\chr(1).$auth_key_aux_hash, true), -16);
$new_nonce_hash2 = \substr(\sha1($new_nonce.\chr(2).$auth_key_aux_hash, true), -16);
$new_nonce_hash3 = \substr(\sha1($new_nonce.\chr(3).$auth_key_aux_hash, true), -16);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
*/
if ($Set_client_DH_params_answer['nonce'] != $nonce) {
throw new \danog\MadelineProto\SecurityException('wrong nonce.');
}
/*
* ***********************************************************************
* Check if server_nonce and new server_nonce are the same
*/
if ($Set_client_DH_params_answer['server_nonce'] != $server_nonce) {
throw new \danog\MadelineProto\SecurityException('wrong server nonce');
}
/*
* ***********************************************************************
* Check Set_client_DH_params_answer type
*/
switch ($Set_client_DH_params_answer['_']) {
case 'dh_gen_ok':
if ($Set_client_DH_params_answer['new_nonce_hash1'] != $new_nonce_hash1) {
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash1');
}
$this->logger->logger('Diffie Hellman key exchange processed successfully!', \danog\MadelineProto\Logger::VERBOSE);
$key = $expires_in < 0 ? new PermAuthKey() : new TempAuthKey();
if ($expires_in >= 0) {
$key->expires(\time() + $expires_in);
}
$key->setServerSalt(\substr($new_nonce, 0, 8) ^ \substr($server_nonce, 0, 8));
$key->setAuthKey($auth_key_str);
$this->logger->logger('Auth key generated', \danog\MadelineProto\Logger::NOTICE);
return $key;
case 'dh_gen_retry':
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash_2');
}
//repeat foreach
$this->logger->logger('Retrying Auth', \danog\MadelineProto\Logger::VERBOSE);
break;
case 'dh_gen_fail':
if ($Set_client_DH_params_answer['new_nonce_hash3'] != $new_nonce_hash3) {
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash_3');
}
$this->logger->logger('Auth Failed', \danog\MadelineProto\Logger::WARNING);
break 2;
default:
throw new \danog\MadelineProto\SecurityException('Response Error');
break;
}
}
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.\basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.\basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$req_pq = $req_pq === 'req_pq_multi' ? 'req_pq' : 'req_pq_multi';
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} catch (\Throwable $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e.PHP_EOL.' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
}
}
if (!$cdn) {
throw new \danog\MadelineProto\SecurityException('Auth Failed');
}
}
/**
* Check validity of g_a parameters.
*
* @param BigInteger $g_a
* @param BigInteger $p
*
* @internal
*
* @return bool
*/
public function checkG(BigInteger $g_a, BigInteger $p): bool
{
/*
* ***********************************************************************
* Check validity of g_a
* 1 < g_a < p - 1
*/
$this->logger->logger('Executing g_a check (1/2)...', \danog\MadelineProto\Logger::VERBOSE);
if ($g_a->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g_a is invalid (1 < g_a < p - 1 is false).');
}
$this->logger->logger('Executing g_a check (2/2)...', \danog\MadelineProto\Logger::VERBOSE);
if ($g_a->compare(\danog\MadelineProto\Magic::$twoe1984) < 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$twoe1984)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g_a is invalid (2^1984 < g_a < p - 2^1984 is false).');
}
return true;
}
/**
* Check validity of p and g parameters.
*
* @param BigInteger $p
* @param BigInteger $g
*
* @internal
*
* @return boolean
*/
public function checkPG(BigInteger $p, BigInteger $g): bool
{
/*
* ***********************************************************************
* Check validity of dh_prime
* Is it a prime?
*/
$this->logger->logger('Executing p/g checks (1/2)...', \danog\MadelineProto\Logger::VERBOSE);
if (!$p->isPrime()) {
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime (p isn't a prime).");
}
/*
* ***********************************************************************
* Check validity of p
* Is (p - 1) / 2 a prime?
*
* Almost always fails
*/
/*
$this->logger->logger('Executing p/g checks (2/3)...', \danog\MadelineProto\Logger::VERBOSE);
if (!$p->subtract(\danog\MadelineProto\Magic::$one)->divide(\danog\MadelineProto\Magic::$two)[0]->isPrime()) {
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime ((p - 1) / 2 isn't a prime).");
}
*/
/*
* ***********************************************************************
* Check validity of p
* 2^2047 < p < 2^2048
*/
$this->logger->logger('Executing p/g checks (2/2)...', \danog\MadelineProto\Logger::VERBOSE);
if ($p->compare(\danog\MadelineProto\Magic::$twoe2047) <= 0 || $p->compare(\danog\MadelineProto\Magic::$twoe2048) >= 0) {
throw new \danog\MadelineProto\SecurityException("g isn't a safe 2048-bit prime (2^2047 < p < 2^2048 is false).");
}
/*
* ***********************************************************************
* Check validity of g
* 1 < g < p - 1
*/
$this->logger->logger('Executing g check...', \danog\MadelineProto\Logger::VERBOSE);
if ($g->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g is invalid (1 < g < p - 1 is false).');
}
return true;
}
/**
* Get diffie-hellman configuration.
*
* @internal
*
* @return \Generator<array>
*/
public function getDhConfig(): \Generator
{
$dh_config = yield from $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
$this->logger->logger('DH configuration not modified', \danog\MadelineProto\Logger::VERBOSE);
return $this->dh_config;
}
$dh_config['p'] = new BigInteger((string) $dh_config['p'], 256);
$dh_config['g'] = new BigInteger($dh_config['g']);
$this->checkPG($dh_config['p'], $dh_config['g']);
return $this->dh_config = $dh_config;
}
/**
* Bind temporary and permanent auth keys.
*
* @param integer $expires_in Date of expiry for binding
* @param string $datacenter DC ID
*
* @internal
*
* @return \Generator<bool>
*/
public function bindTempAuthKey(int $expires_in, string $datacenter): \Generator
{
$datacenterConnection = $this->datacenter->getDataCenterConnection($datacenter);
$connection = $datacenterConnection->getAuthConnection();
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger('Binding authorization keys...', \danog\MadelineProto\Logger::VERBOSE);
$nonce = \danog\MadelineProto\Tools::random(8);
$expires_at = \time() + $expires_in;
$temp_auth_key_id = $datacenterConnection->getTempAuthKey()->getID();
$perm_auth_key_id = $datacenterConnection->getPermAuthKey()->getID();
$temp_session_id = $connection->session_id;
$message_data = (yield from $this->TL->serializeObject(['type' => ''], ['_' => 'bind_auth_key_inner', 'nonce' => $nonce, 'temp_auth_key_id' => $temp_auth_key_id, 'perm_auth_key_id' => $perm_auth_key_id, 'temp_session_id' => $temp_session_id, 'expires_at' => $expires_at], 'bindTempAuthKey_inner'));
$message_id = $connection->msgIdHandler->generateMessageId();
$seq_no = 0;
$encrypted_data = \danog\MadelineProto\Tools::random(16).$message_id.\pack('VV', $seq_no, \strlen($message_data)).$message_data;
$message_key = \substr(\sha1($encrypted_data, true), -16);
$padding = \danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = Crypt::oldAesCalculate($message_key, $datacenterConnection->getPermAuthKey()->getAuthKey());
$encrypted_message = $datacenterConnection->getPermAuthKey()->getID().$message_key.Crypt::igeEncrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$res = yield from $connection->methodCallAsyncRead('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['msg_id' => $message_id]);
if ($res === true) {
$this->logger->logger('Bound temporary and permanent authorization keys, DC '.$datacenter, \danog\MadelineProto\Logger::NOTICE);
$datacenterConnection->bind();
$datacenterConnection->flush();
return true;
}
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
}
}
throw new \danog\MadelineProto\SecurityException('An error occurred while binding temporary and permanent authorization keys.');
}
/**
* Factorize number asynchronously using the wolfram API.
*
* @param string|integer $what Number to factorize
*
* @return \Generator<string|bool>
*/
private function wolframSingle($what): \Generator
{
$code = (yield from $this->datacenter->fileGetContents('http://www.wolframalpha.com/api/v1/code'));
$query = 'Do prime factorization of '.$what;
$params = ['async' => true, 'banners' => 'raw', 'debuggingdata' => false, 'format' => 'moutput', 'formattimeout' => 8, 'input' => $query, 'output' => 'JSON', 'proxycode' => \json_decode($code, true)['code']];
$url = 'https://www.wolframalpha.com/input/json.jsp?'.\http_build_query($params);
$request = new Request($url);
$request->setHeader('referer', 'https://www.wolframalpha.com/input/?i='.\urlencode($query));
$res = \json_decode(yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer(), true);
if (!isset($res['queryresult']['pods'])) {
return false;
}
$fres = false;
foreach ($res['queryresult']['pods'] as $cur) {
if ($cur['id'] === 'Divisors') {
$fres = \explode(', ', \preg_replace(["/{\\d+, /", "/, \\d+}\$/"], '', $cur['subpods'][0]['moutput']));
break;
}
}
if (\is_array($fres)) {
$fres = $fres[0];
$newval = \intval($fres);
if (\is_int($newval)) {
$fres = $newval;
}
return $fres;
}
return false;
}
/**
* Asynchronously create, bind and check auth keys for all DCs.
*
* @internal
*
* @return \Generator
*/
public function initAuthorization(): \Generator
{
if ($this->pending_auth) {
$this->logger("Pending auth, not initing auth");
return;
}
$this->logger("Initing authorization...");
$initing = $this->initing_authorization;
$this->initing_authorization = true;
try {
$dcs = [];
$postpone = [];
foreach ($this->datacenter->getDataCenterConnections() as $id => $socket) {
if (!$socket->hasCtx()) {
continue;
}
if ($socket->isMedia()) {
$oid = \intval($id);
if (isset($dcs[$oid])) {
$postpone[$id] = $socket;
}
continue;
}
yield $socket->waitGetConnection();
if (isset($this->init_auth_dcs[$id])) {
$this->pending_auth = true;
continue;
}
$dcs[$id] = function () use ($id, $socket) {
return $this->initAuthorizationSocket($id, $socket);
};
}
if ($dcs) {
$first = \array_shift($dcs)();
yield $first;
}
foreach ($dcs as $id => &$dc) {
$dc = $dc();
}
yield \danog\MadelineProto\Tools::all($dcs);
foreach ($postpone as $id => $socket) {
yield from $this->initAuthorizationSocket($id, $socket);
}
if ($this->pending_auth && empty($this->init_auth_dcs)) {
$this->pending_auth = false;
yield from $this->initAuthorization();
}
} finally {
$this->pending_auth = false;
$this->initing_authorization = $initing;
}
}
/**
* Init auth keys for single DC.
*
* @param string $id DC ID
* @param DataCenterConnection $socket DC object
*
* @internal
*
* @return \Generator
*/
public function initAuthorizationSocket(string $id, DataCenterConnection $socket): \Generator
{
$this->logger("Initing authorization DC {$id}...");
$this->init_auth_dcs[$id] = true;
$connection = $socket->getAuthConnection();
try {
$socket->createSession();
$cdn = $socket->isCDN();
$media = $socket->isMedia();
if (!$socket->hasTempAuthKey() || !$socket->hasPermAuthKey() || !$socket->isBound()) {
if (!$socket->hasPermAuthKey() && !$cdn && !$media) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setPermAuthKey(yield from $this->createAuthKey(-1, $id));
//$socket->authorized(false);
}
if ($media) {
$socket->link(\intval($id));
if ($socket->hasTempAuthKey()) {
return;
}
}
if ($this->datacenter->getDataCenterConnection($id)->getSettings()['pfs']) {
if (!$cdn) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
//$authorized = $socket->authorized;
//$socket->authorized = false;
$socket->setTempAuthKey(null);
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
yield from $this->bindTempAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$this->config = yield from $connection->methodCallAsyncRead('help.getConfig', []);
yield from $this->syncAuthorization($id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
} else {
if (!$cdn) {
$socket->bind(false);
$this->config = yield from $connection->methodCallAsyncRead('help.getConfig', []);
yield from $this->syncAuthorization($id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
}
} elseif (!$cdn) {
yield from $this->syncAuthorization($id);
}
} finally {
$this->logger("Done initing authorization DC {$id}");
unset($this->init_auth_dcs[$id]);
}
}
/**
* Sync authorization data between DCs.
*
* @param string $id DC ID
*
* @internal
*
* @return \Generator
*/
public function syncAuthorization(string $id): \Generator
{
if (!$this->datacenter->has($id)) {
return false;
}
$socket = $this->datacenter->getDataCenterConnection($id);
if ($this->authorized === MTProto::LOGGED_IN && !$socket->isAuthorized()) {
foreach ($this->datacenter->getDataCenterConnections() as $authorized_dc_id => $authorized_socket) {
if ($this->authorized_dc !== -1 && $authorized_dc_id !== $this->authorized_dc) {
continue;
}
if ($authorized_socket->hasTempAuthKey() && $authorized_socket->hasPermAuthKey() && $authorized_socket->isAuthorized() && $this->authorized === MTProto::LOGGED_IN && !$socket->isAuthorized() && !$authorized_socket->isCDN()) {
try {
$this->logger->logger('Trying to copy authorization from dc '.$authorized_dc_id.' to dc '.$id);
$exported_authorization = yield from $this->methodCallAsyncRead('auth.exportAuthorization', ['dc_id' => \preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$authorization = yield from $this->methodCallAsyncRead('auth.importAuthorization', $exported_authorization, ['datacenter' => $id]);
$socket->authorized(true);
break;
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('Failure while syncing authorization from DC '.$authorized_dc_id.' to DC '.$id.': '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('Failure while syncing authorization from DC '.$authorized_dc_id.' to DC '.$id.': '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
if ($e->rpc === 'DC_ID_INVALID') {
break;
}
}
// Turns out this DC isn't authorized after all
}
}
}
}
}