2016-06-23 23:51:08 +02:00
|
|
|
|
<?php
|
2016-08-06 12:14:40 +02:00
|
|
|
|
|
2016-07-14 15:15:50 +02:00
|
|
|
|
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
|
|
|
|
require_once 'libpy2php.php';
|
|
|
|
|
require_once 'os_path.php';
|
|
|
|
|
require_once 'crypt.php';
|
|
|
|
|
require_once 'prime.php';
|
|
|
|
|
require_once 'TL.php';
|
2016-07-15 13:17:53 +02:00
|
|
|
|
require_once 'vendor/autoload.php';
|
2016-07-30 16:14:25 +02:00
|
|
|
|
$struct = new \danog\PHP\StructClass();
|
2016-07-13 21:39:24 +02:00
|
|
|
|
/**
|
|
|
|
|
* Function to get hex crc32
|
2016-07-14 15:15:50 +02:00
|
|
|
|
* :param data: Data to encode.
|
2016-07-13 21:39:24 +02:00
|
|
|
|
*/
|
2016-07-14 15:15:50 +02:00
|
|
|
|
function newcrc32($data)
|
|
|
|
|
{
|
|
|
|
|
return hexdec(hash('crc32b', $data));
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-13 21:39:24 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
/**
|
2016-07-13 21:39:24 +02:00
|
|
|
|
* Function to dump the hex version of a string.
|
2016-08-05 01:04:03 +02:00
|
|
|
|
*
|
2016-08-05 01:03:45 +02:00
|
|
|
|
* @param $what What to dump.
|
2016-06-23 23:51:08 +02:00
|
|
|
|
*/
|
2016-07-15 15:01:32 +02:00
|
|
|
|
function hex_dump(...$what)
|
2016-07-14 15:15:50 +02:00
|
|
|
|
{
|
2016-07-19 11:56:05 +02:00
|
|
|
|
foreach ($what as $w) {
|
2016-07-15 15:01:32 +02:00
|
|
|
|
var_dump(bin2hex($w));
|
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
}
|
2016-07-18 17:11:37 +02:00
|
|
|
|
/**
|
|
|
|
|
* len.
|
|
|
|
|
*
|
|
|
|
|
* Get the length of a string or of an array
|
|
|
|
|
*
|
|
|
|
|
* @param $input String or array to parse
|
|
|
|
|
*
|
|
|
|
|
* @return int with the length
|
|
|
|
|
**/
|
|
|
|
|
function len($input)
|
|
|
|
|
{
|
|
|
|
|
if (is_array($input)) {
|
2016-07-19 11:56:05 +02:00
|
|
|
|
return count($input);
|
2016-06-28 03:10:15 +02:00
|
|
|
|
}
|
2016-07-19 11:56:05 +02:00
|
|
|
|
|
2016-07-18 17:11:37 +02:00
|
|
|
|
return strlen($input);
|
|
|
|
|
}
|
2016-07-13 21:39:24 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function to visualize byte streams. Split into bytes, print to console.
|
2016-07-14 15:15:50 +02:00
|
|
|
|
* :param bs: BYTE STRING.
|
2016-07-13 21:39:24 +02:00
|
|
|
|
*/
|
2016-07-14 15:15:50 +02:00
|
|
|
|
function vis($bs)
|
|
|
|
|
{
|
2016-07-13 21:39:24 +02:00
|
|
|
|
$bs = str_split($bs);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$symbols_in_one_line = 8;
|
2016-07-13 21:39:24 +02:00
|
|
|
|
$n = floor(len($bs) / $symbols_in_one_line);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$i = 0;
|
|
|
|
|
foreach (pyjslib_range($n) as $i) {
|
2016-07-14 15:15:50 +02:00
|
|
|
|
echo $i * $symbols_in_one_line.' | '.implode(' ',
|
|
|
|
|
array_map(function ($el) {
|
|
|
|
|
return bin2hex($el);
|
|
|
|
|
}, array_slice($bs, $i * $symbols_in_one_line, ($i + 1) * $symbols_in_one_line))
|
|
|
|
|
).PHP_EOL;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-18 17:11:37 +02:00
|
|
|
|
if (len($bs) % $symbols_in_one_line != 0) {
|
2016-07-14 15:15:50 +02:00
|
|
|
|
echo($i + 1) * $symbols_in_one_line.' | '.implode(' ',
|
|
|
|
|
array_map(function ($el) {
|
|
|
|
|
return bin2hex($el);
|
|
|
|
|
}, array_slice($bs, ($i + 1) * $symbols_in_one_line))
|
|
|
|
|
).PHP_EOL;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-26 15:28:39 +02:00
|
|
|
|
/**
|
2016-07-18 17:11:37 +02:00
|
|
|
|
* posmod(numeric,numeric) : numeric
|
2016-07-19 11:56:05 +02:00
|
|
|
|
* Works just like the % (modulus) operator, only returns always a postive number.
|
2016-06-26 15:28:39 +02:00
|
|
|
|
*/
|
2016-07-19 11:56:05 +02:00
|
|
|
|
function posmod($a, $b)
|
|
|
|
|
{
|
2016-07-18 17:11:37 +02:00
|
|
|
|
$resto = $a % $b;
|
2016-07-19 11:56:05 +02:00
|
|
|
|
if ($resto < 0) {
|
|
|
|
|
$resto += abs($b);
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-18 17:11:37 +02:00
|
|
|
|
return $resto;
|
2016-06-26 15:28:39 +02:00
|
|
|
|
}
|
2016-06-24 23:47:10 +02:00
|
|
|
|
|
2016-07-14 15:15:50 +02:00
|
|
|
|
function fread_all($handle)
|
|
|
|
|
{
|
2016-06-27 16:52:28 +02:00
|
|
|
|
$pos = ftell($handle);
|
|
|
|
|
fseek($handle, 0);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$content = fread($handle, fstat($handle)['size']);
|
2016-06-27 16:52:28 +02:00
|
|
|
|
fseek($handle, $pos);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-27 16:52:28 +02:00
|
|
|
|
return $content;
|
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
function fopen_and_write($filename, $mode, $data)
|
|
|
|
|
{
|
|
|
|
|
$handle = fopen($filename, $mode);
|
|
|
|
|
fwrite($handle, $data);
|
|
|
|
|
rewind($handle);
|
|
|
|
|
|
|
|
|
|
return $handle;
|
2016-06-28 03:37:04 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
function string2bin($string)
|
|
|
|
|
{
|
2016-07-14 12:43:38 +02:00
|
|
|
|
$res = null;
|
2016-07-14 15:15:50 +02:00
|
|
|
|
foreach (explode('\\', $string) as $s) {
|
2016-07-18 17:11:37 +02:00
|
|
|
|
if ($s != null && strlen($s) == 3) {
|
2016-07-14 12:43:38 +02:00
|
|
|
|
$res .= hex2bin(substr($s, 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-07-14 12:43:38 +02:00
|
|
|
|
return $res;
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
/**
|
2016-07-14 15:15:50 +02:00
|
|
|
|
* Manages TCP Transport. encryption and message frames.
|
2016-06-23 23:51:08 +02:00
|
|
|
|
*/
|
2016-07-14 15:15:50 +02:00
|
|
|
|
class Session
|
|
|
|
|
{
|
|
|
|
|
public function __construct($ip, $port, $auth_key = null, $server_salt = null)
|
|
|
|
|
{
|
|
|
|
|
$this->sock = fsockopen('tcp://'.$ip.':'.$port);
|
|
|
|
|
if (!(get_resource_type($this->sock) == 'file' || get_resource_type($this->sock) == 'stream')) {
|
|
|
|
|
throw new Exception("Couldn't connect to socket.");
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$this->number = 0;
|
|
|
|
|
$this->timedelta = 0;
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$this->session_id = \phpseclib\Crypt\Random::string(8);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$this->auth_key = $auth_key;
|
2016-07-15 15:01:32 +02:00
|
|
|
|
$this->auth_key_id = $this->auth_key ? substr(sha1($this->auth_key, true), -8) : null;
|
2016-06-27 22:52:07 +02:00
|
|
|
|
stream_set_timeout($this->sock, 5);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$this->MAX_RETRY = 5;
|
|
|
|
|
$this->AUTH_MAX_RETRY = 5;
|
2016-07-14 12:43:38 +02:00
|
|
|
|
$this->struct = new \danog\PHP\Struct();
|
2016-07-18 18:43:50 +02:00
|
|
|
|
$this->PrimeModule = new PrimeModule();
|
2016-07-14 14:22:46 +02:00
|
|
|
|
try {
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$this->tl = new TL('https://core.telegram.org/schema/mtproto-json');
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
$this->tl = new TL(__DIR__.'/TL_schema.JSON');
|
2016-07-14 14:22:46 +02:00
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
|
|
|
|
public function __destruct()
|
|
|
|
|
{
|
2016-06-26 14:53:16 +02:00
|
|
|
|
fclose($this->sock);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
/**
|
|
|
|
|
* Forming the message frame and sending message to server
|
2016-07-14 15:15:50 +02:00
|
|
|
|
* :param message: byte string to send.
|
2016-06-23 23:51:08 +02:00
|
|
|
|
*/
|
2016-07-14 15:15:50 +02:00
|
|
|
|
public function send_message($message_data)
|
|
|
|
|
{
|
2016-07-30 16:14:25 +02:00
|
|
|
|
$message_id = $this->struct->pack('<Q', (int) ((time() + $this->timedelta) * pow(2, 30)) * 4);
|
2016-06-28 03:10:15 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
if (($this->auth_key == null) || ($this->server_salt == null)) {
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$message = string2bin('\x00\x00\x00\x00\x00\x00\x00\x00').$message_id.$this->struct->pack('<I', strlen($message_data)).$message_data;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
} else {
|
2016-07-14 12:43:38 +02:00
|
|
|
|
$encrypted_data =
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$this->server_salt.$this->session_id.$message_id.$this->struct->pack('<II', $this->number, strlen($message_data)).$message_data;
|
2016-07-15 15:01:32 +02:00
|
|
|
|
$message_key = substr(sha1($encrypted_data, true), -16);
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$padding = \phpseclib\Crypt\Random::string(posmod(-strlen($encrypted_data), 16));
|
2016-07-14 15:15:50 +02:00
|
|
|
|
echo strlen($encrypted_data.$padding).PHP_EOL;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
list($aes_key, $aes_iv) = $this->aes_calculate($message_key);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$message = $this->auth_key_id.$message_key.crypt::ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$step1 = $this->struct->pack('<II', (strlen($message) + 12), $this->number).$message;
|
|
|
|
|
$step2 = $step1.$this->struct->pack('<I', newcrc32($step1));
|
2016-06-26 14:53:16 +02:00
|
|
|
|
fwrite($this->sock, $step2);
|
2016-07-14 12:43:38 +02:00
|
|
|
|
$this->number += 1;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
/**
|
|
|
|
|
* Reading socket and receiving message from server. Check the CRC32.
|
|
|
|
|
*/
|
2016-07-14 15:15:50 +02:00
|
|
|
|
public function recv_message()
|
|
|
|
|
{
|
2016-06-26 14:53:16 +02:00
|
|
|
|
$packet_length_data = fread($this->sock, 4);
|
2016-07-14 14:22:46 +02:00
|
|
|
|
if (len($packet_length_data) < 4) {
|
2016-06-27 18:53:15 +02:00
|
|
|
|
throw new Exception('Nothing in the socket!');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-15 15:01:32 +02:00
|
|
|
|
$packet_length = $this->struct->unpack('<I', $packet_length_data)[0];
|
2016-08-06 20:48:33 +02:00
|
|
|
|
var_dump($packet_length);
|
2016-06-26 14:53:16 +02:00
|
|
|
|
$packet = fread($this->sock, ($packet_length - 4));
|
2016-07-15 15:01:32 +02:00
|
|
|
|
if (!(newcrc32($packet_length_data.substr($packet, 0, -4)) == $this->struct->unpack('<I', substr($packet, -4))[0])) {
|
2016-06-27 18:53:15 +02:00
|
|
|
|
throw new Exception('CRC32 was not correct!');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-15 15:01:32 +02:00
|
|
|
|
$x = $this->struct->unpack('<I', substr($packet, 0, 4));
|
|
|
|
|
$auth_key_id = substr($packet, 4, 8);
|
2016-07-14 14:22:46 +02:00
|
|
|
|
if ($auth_key_id == string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
|
2016-07-15 15:01:32 +02:00
|
|
|
|
list($message_id, $message_length) = $this->struct->unpack('<8sI', substr($packet, 12, 12));
|
2016-07-14 14:22:46 +02:00
|
|
|
|
$data = substr($packet, 24, (24 + $message_length) - 24);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
} elseif ($auth_key_id == $this->auth_key_id) {
|
2016-07-14 14:22:46 +02:00
|
|
|
|
$message_key = substr($packet, 12, 28 - 12);
|
|
|
|
|
$encrypted_data = substr($packet, 28, -4 - 28);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, 'from server');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$decrypted_data = crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv);
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if (substr($decrypted_data, 0, 8) != $this->server_salt) {
|
|
|
|
|
throw new Exception('Server salt does not match.');
|
|
|
|
|
}
|
|
|
|
|
if (substr($decrypted_data, 8, 8) != $this->session_id) {
|
|
|
|
|
throw new Exception('Session id does not match.');
|
|
|
|
|
}
|
2016-07-14 14:22:46 +02:00
|
|
|
|
$message_id = substr($decrypted_data, 16, 24 - 16);
|
|
|
|
|
$seq_no = $this->struct->unpack('<I', substr($decrypted_data, 24, 28 - 24)) [0];
|
|
|
|
|
$message_data_length = $this->struct->unpack('<I', substr($decrypted_data, 28, 32 - 28)) [0];
|
|
|
|
|
$data = substr($decrypted_data, 32, (32 + $message_data_length) - 32);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
} else {
|
2016-06-27 18:53:15 +02:00
|
|
|
|
throw new Exception('Got unknown auth_key id');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
return $data;
|
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
|
|
|
|
public function method_call($method, $kwargs)
|
|
|
|
|
{
|
2016-08-06 00:56:47 +02:00
|
|
|
|
//var_dump($kwargs);
|
2016-07-14 14:22:46 +02:00
|
|
|
|
foreach (range(1, $this->MAX_RETRY) as $i) {
|
2016-06-23 23:51:08 +02:00
|
|
|
|
try {
|
2016-07-14 14:22:46 +02:00
|
|
|
|
$this->send_message($this->tl->serialize_method($method, $kwargs));
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$server_answer = $this->recv_message();
|
2016-07-14 15:15:50 +02:00
|
|
|
|
} catch (Exception $e) {
|
2016-07-29 17:19:34 +02:00
|
|
|
|
echo 'An error occurred while calling method '.$method.': '.$e->getMessage().PHP_EOL.'Stack trace:'.$e->getTraceAsString().PHP_EOL.'Retrying to call method...'.PHP_EOL.PHP_EOL;
|
2016-06-23 23:51:08 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-08-06 12:14:28 +02:00
|
|
|
|
if ($server_answer == null) {
|
2016-08-06 12:14:40 +02:00
|
|
|
|
throw new Exception('An error occurred while calling method '.$method.'.');
|
2016-08-06 12:14:28 +02:00
|
|
|
|
}
|
2016-08-06 12:14:40 +02:00
|
|
|
|
|
2016-07-14 15:15:50 +02:00
|
|
|
|
return $this->tl->deserialize(fopen_and_write('php://memory', 'rw+b', $server_answer));
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-08-06 12:14:40 +02:00
|
|
|
|
throw new Exception('An error occurred while calling method '.$method.'.');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
|
|
|
|
public function create_auth_key()
|
|
|
|
|
{
|
2016-08-07 21:11:46 +02:00
|
|
|
|
// Load the RSA key
|
2016-08-06 19:00:39 +02:00
|
|
|
|
$f = file_get_contents(__DIR__.'/rsa.pub');
|
|
|
|
|
$key = new \phpseclib\Crypt\RSA();
|
|
|
|
|
$key->load($f);
|
2016-08-06 20:48:33 +02:00
|
|
|
|
|
2016-08-07 21:11:46 +02:00
|
|
|
|
// Make pq request
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$nonce = \phpseclib\Crypt\Random::string(16);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Requesting pq');
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$ResPQ = $this->method_call('req_pq', ['nonce' => $nonce]);
|
2016-08-07 21:11:46 +02:00
|
|
|
|
$server_nonce = $ResPQ['server_nonce'];
|
2016-08-06 12:14:28 +02:00
|
|
|
|
if ($ResPQ['nonce'] !== $nonce) {
|
2016-08-06 12:14:40 +02:00
|
|
|
|
throw new Exception('Handshake: wrong nonce');
|
2016-08-06 12:14:28 +02:00
|
|
|
|
}
|
2016-08-06 02:50:19 +02:00
|
|
|
|
$public_key_fingerprint = (int) $ResPQ['server_public_key_fingerprints'][0];
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$pq_bytes = $ResPQ['pq'];
|
2016-08-07 21:11:46 +02:00
|
|
|
|
var_dump(
|
|
|
|
|
(int)$this->struct->unpack("<q", substr(sha1($this->tl->serialize_param('bytes', $key->modulus->toBytes()) . $this->tl->serialize_param('bytes', $key->exponent->toBytes()), true), -8))[0],
|
|
|
|
|
$public_key_fingerprint
|
|
|
|
|
);
|
2016-07-30 17:17:15 +02:00
|
|
|
|
|
2016-08-07 21:11:46 +02:00
|
|
|
|
// Compute p and q
|
2016-07-21 13:12:51 +02:00
|
|
|
|
$pq = new \phpseclib\Math\BigInteger($pq_bytes, 256);
|
2016-07-18 18:43:50 +02:00
|
|
|
|
list($p, $q) = $this->PrimeModule->primefactors($pq);
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$p = new \phpseclib\Math\BigInteger($p);
|
|
|
|
|
$q = new \phpseclib\Math\BigInteger($q);
|
|
|
|
|
if ($p->compare($q) > 0) {
|
2016-06-23 23:51:08 +02:00
|
|
|
|
list($p, $q) = [$q, $p];
|
|
|
|
|
}
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if (!(($pq->equals($p->multiply($q))) && ($p < $q))) {
|
|
|
|
|
throw new Exception("Handshake: couldn't compute p or q.");
|
|
|
|
|
}
|
2016-08-07 21:11:46 +02:00
|
|
|
|
|
|
|
|
|
|
2016-08-06 00:17:58 +02:00
|
|
|
|
pyjslib_printnl(sprintf('Factorization %s = %s * %s', $pq, $p, $q));
|
2016-08-07 21:11:46 +02:00
|
|
|
|
|
2016-08-06 12:14:28 +02:00
|
|
|
|
$p_bytes = $this->struct->pack('>Q', (string) $p);
|
|
|
|
|
$q_bytes = $this->struct->pack('>Q', (string) $q);
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$new_nonce = \phpseclib\Crypt\Random::string(32);
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$data = $this->tl->serialize_obj('p_q_inner_data', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce]);
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$sha_digest = sha1($data, true);
|
2016-08-06 12:14:28 +02:00
|
|
|
|
$random_bytes = \phpseclib\Crypt\Random::string(255 - strlen($data) - strlen($sha_digest));
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$to_encrypt = $sha_digest.$data.$random_bytes;
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$encrypted_data = $key->_raw_encrypt($to_encrypt);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Starting Diffie Hellman key exchange');
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$server_dh_params = $this->method_call('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $public_key_fingerprint, 'encrypted_data' => $encrypted_data]);
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($nonce != $server_dh_params['nonce']) {
|
|
|
|
|
throw new Exception('Handshake: wrong nonce.');
|
|
|
|
|
}
|
|
|
|
|
if ($server_nonce != $server_dh_params['server_nonce']) {
|
|
|
|
|
throw new Exception('Handshake: wrong server nonce.');
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$encrypted_answer = $server_dh_params['encrypted_answer'];
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$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);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$answer_with_hash = crypt::ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
|
2016-08-06 00:42:18 +02:00
|
|
|
|
$answer_hash = substr($answer_with_hash, 0, 20);
|
|
|
|
|
$answer = substr($answer_with_hash, 20);
|
|
|
|
|
$server_DH_inner_data = deserialize(fopen_and_write('php://memory', 'rw+b', $answer));
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($nonce != $server_DH_inner_data['nonce']) {
|
|
|
|
|
throw new Exception('Handshake: wrong nonce');
|
|
|
|
|
}
|
|
|
|
|
if ($server_nonce != $server_DH_inner_data['server_nonce']) {
|
|
|
|
|
throw new Exception('Handshake: wrong server nonce');
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$dh_prime_str = $server_DH_inner_data['dh_prime'];
|
|
|
|
|
$g = $server_DH_inner_data['g'];
|
|
|
|
|
$g_a_str = $server_DH_inner_data['g_a'];
|
|
|
|
|
$server_time = $server_DH_inner_data['server_time'];
|
2016-06-27 16:52:28 +02:00
|
|
|
|
$this->timedelta = ($server_time - time());
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl(sprintf('Server-client time delta = %.1f s', $this->timedelta));
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$dh_prime = $this->struct->unpack('>Q', $dh_prime_str);
|
|
|
|
|
$g_a = $this->struct->unpack('>Q', $g_a_str);
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if (!$this->PrimeModule->isprime($dh_prime)) {
|
|
|
|
|
throw new Exception("Handshake: dh_prime isn't a prime.");
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$retry_id = 0;
|
2016-08-06 00:17:58 +02:00
|
|
|
|
$b_str = \phpseclib\Crypt\Random::string(256);
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$b = $this->struct->unpack('>Q', $b_str);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$g_b = pow($g, $b, $dh_prime);
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$g_b_str = $this->struct->pack('>Q', $g_b);
|
2016-08-06 00:42:18 +02:00
|
|
|
|
$data = serialize_obj(['client_DH_inner_data'], ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'retry_id' => $retry_id, 'g_b' => $g_b_str]);
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$data_with_sha = sha1($data, true).$data;
|
|
|
|
|
$data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string(posmod(-strlen($data_with_sha), 16));
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$encrypted_data = crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
|
|
|
|
|
foreach (pyjslib_range(1, $this->AUTH_MAX_RETRY) as $i) {
|
2016-07-14 15:15:50 +02:00
|
|
|
|
$Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$auth_key = pow($g_a, $b, $dh_prime);
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$auth_key_str = $this->struct->pack('>Q', $auth_key);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$auth_key_sha = sha1($auth_key_str, true);
|
2016-08-06 00:42:18 +02:00
|
|
|
|
$auth_key_aux_hash = substr($auth_key_sha, 0, 8);
|
|
|
|
|
$new_nonce_hash1 = substr(sha1($new_nonce.''.$auth_key_aux_hash, true), -16);
|
|
|
|
|
$new_nonce_hash2 = substr(sha1($new_nonce.''.$auth_key_aux_hash, true), -16);
|
|
|
|
|
$new_nonce_hash3 = substr(sha1($new_nonce.''.$auth_key_aux_hash, true), -16);
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($Set_client_DH_params_answer['nonce'] != $nonce) {
|
|
|
|
|
throw new Exception('Handshake: wrong nonce.');
|
|
|
|
|
}
|
|
|
|
|
if ($Set_client_DH_params_answer['server_nonce'] != $server_nonce) {
|
|
|
|
|
throw new Exception('Handshake: wrong server nonce');
|
|
|
|
|
}
|
2016-08-06 00:42:18 +02:00
|
|
|
|
if ($Set_client_DH_params_answer->name == 'dh_gen_ok') {
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($Set_client_DH_params_answer['new_nonce_hash1'] != $new_nonce_hash1) {
|
|
|
|
|
throw new Exception('Handshake: wrong new_nonce_hash1');
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Diffie Hellman key exchange processed successfully');
|
2016-08-06 00:42:18 +02:00
|
|
|
|
$this->server_salt = new strxor(substr($new_nonce, 0, 8 - 0), substr($server_nonce, 0, 8 - 0));
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$this->auth_key = $auth_key_str;
|
2016-08-06 00:42:18 +02:00
|
|
|
|
$this->auth_key_id = substr($auth_key_sha, -8);
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Auth key generated');
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
return 'Auth Ok';
|
2016-08-06 12:14:28 +02:00
|
|
|
|
} elseif ($Set_client_DH_params_answer->name == 'dh_gen_retry') {
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
|
|
|
|
|
throw new Exception('Handshake: wrong new_nonce_hash_2');
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Retry Auth');
|
2016-08-06 12:14:28 +02:00
|
|
|
|
} elseif ($Set_client_DH_params_answer->name == 'dh_gen_fail') {
|
2016-08-06 12:14:40 +02:00
|
|
|
|
if ($Set_client_DH_params_answer['new_nonce_hash3'] != $new_nonce_hash3) {
|
|
|
|
|
throw new Exception('Handshake: wrong new_nonce_hash_3');
|
|
|
|
|
}
|
2016-06-23 23:51:08 +02:00
|
|
|
|
pyjslib_printnl('Auth Failed');
|
2016-06-27 18:53:15 +02:00
|
|
|
|
throw new Exception('Auth Failed');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
} else {
|
2016-06-27 18:53:15 +02:00
|
|
|
|
throw new Exception('Response Error');
|
2016-06-23 23:51:08 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
|
|
|
|
public function aes_calculate($msg_key, $direction = 'to server')
|
|
|
|
|
{
|
2016-06-23 23:51:08 +02:00
|
|
|
|
$x = ($direction == 'to server') ? 0 : 8;
|
2016-08-06 00:45:25 +02:00
|
|
|
|
$sha1_a = sha1($msg_key.substr($this->auth_key, $x, ($x + 32) - $x), true);
|
|
|
|
|
$sha1_b = sha1(substr($this->auth_key, ($x + 32), ($x + 48) - ($x + 32)).$msg_key.substr($this->auth_key, (48 + $x), (64 + $x) - (48 + $x)), true);
|
|
|
|
|
$sha1_c = sha1(substr($this->auth_key, ($x + 64), ($x + 96) - ($x + 64)).$msg_key, true);
|
|
|
|
|
$sha1_d = sha1($msg_key.substr($this->auth_key, ($x + 96), ($x + 128) - ($x + 96)), true);
|
|
|
|
|
$aes_key = substr($sha1_a, 0, 8 - 0).substr($sha1_b, 8, 20 - 8).substr($sha1_c, 4, 16 - 4);
|
|
|
|
|
$aes_iv = substr($sha1_a, 8, 20 - 8).substr($sha1_b, 0, 8 - 0).substr($sha1_c, 16, 20 - 16).substr($sha1_d, 0, 8 - 0);
|
2016-07-14 15:15:50 +02:00
|
|
|
|
|
2016-06-23 23:51:08 +02:00
|
|
|
|
return [$aes_key, $aes_iv];
|
|
|
|
|
}
|
|
|
|
|
}
|