diff --git a/src/danog/MadelineProto/Stream/Async/Stream.php b/src/danog/MadelineProto/Stream/Async/Stream.php index d88add72..63b6fe5b 100644 --- a/src/danog/MadelineProto/Stream/Async/Stream.php +++ b/src/danog/MadelineProto/Stream/Async/Stream.php @@ -20,7 +20,6 @@ namespace danog\MadelineProto\Stream\Async; use Amp\Promise; use danog\MadelineProto\Stream\ConnectionContext; -use danog\MadelineProto\Tools; /** * Generic stream helper trait. @@ -31,8 +30,6 @@ use danog\MadelineProto\Tools; */ trait Stream { - use Tools; - public function connect(ConnectionContext $ctx, string $header = ''): Promise { return \danog\MadelineProto\Tools::call($this->connectGenerator($ctx, $header)); diff --git a/src/danog/MadelineProto/Stream/Common/CtrStream.php b/src/danog/MadelineProto/Stream/Common/CtrStream.php new file mode 100644 index 00000000..c21484b7 --- /dev/null +++ b/src/danog/MadelineProto/Stream/Common/CtrStream.php @@ -0,0 +1,196 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\Stream\Common; + +use Amp\Promise; +use Amp\Socket\EncryptableSocket; +use danog\MadelineProto\Stream\Async\Buffer; +use danog\MadelineProto\Stream\Async\BufferedStream; +use danog\MadelineProto\Stream\BufferedProxyStreamInterface; +use danog\MadelineProto\Stream\BufferInterface; +use danog\MadelineProto\Stream\ConnectionContext; +use danog\MadelineProto\Stream\RawStreamInterface; +use danog\MadelineProto\Stream\StreamInterface; +use phpseclib3\Crypt\AES; + +/** + * AES CTR stream wrapper. + * + * Manages AES CTR encryption/decryption + * + * @author Daniil Gentili + */ +class CtrStream implements BufferedProxyStreamInterface, BufferInterface +{ + use Buffer; + use BufferedStream; + private $encrypt; + private $decrypt; + private $stream; + private $write_buffer; + private $read_buffer; + private $extra; + private $append = ''; + private $append_after = 0; + + /** + * Connect to stream. + * + * @param ConnectionContext $ctx The connection context + * + * @return \Generator + */ + public function connectGenerator(ConnectionContext $ctx, string $header = ''): \Generator + { + $this->encrypt = new \phpseclib3\Crypt\AES('ctr'); + $this->encrypt->enableContinuousBuffer(); + $this->encrypt->setKey($this->extra['encrypt']['key']); + $this->encrypt->setIV($this->extra['encrypt']['iv']); + + $this->decrypt = new \phpseclib3\Crypt\AES('ctr'); + $this->decrypt->enableContinuousBuffer(); + $this->decrypt->setKey($this->extra['decrypt']['key']); + $this->decrypt->setIV($this->extra['decrypt']['iv']); + + $this->stream = yield $ctx->getStream($header); + } + + /** + * Async close. + * + * @return Promise + */ + public function disconnect() + { + return $this->stream->disconnect(); + } + + /** + * Get write buffer asynchronously. + * + * @param int $length Length of data that is going to be written to the write buffer + * + * @return Generator + */ + public function getWriteBufferGenerator(int $length, string $append = ''): \Generator + { + $this->write_buffer = yield $this->stream->getWriteBuffer($length); + if (\strlen($append)) { + $this->append = $append; + $this->append_after = $length - \strlen($append); + } + + return $this; + } + + /** + * Get read buffer asynchronously. + * + * @param int $length Length of payload, as detected by this layer + * + * @return Generator + */ + public function getReadBufferGenerator(&$length): \Generator + { + $this->read_buffer = yield $this->stream->getReadBuffer($l); + + return $this; + } + + /** + * Decrypts read data asynchronously. + * + * @param Promise $promise Promise that resolves with a string when new data is available or `null` if the stream has closed. + * + * @throws PendingReadError Thrown if another read operation is still pending. + * + * @return Generator That resolves with a string when the provided promise is resolved and the data is decrypted + */ + public function bufferReadGenerator(int $length): \Generator + { + return @$this->decrypt->encrypt(yield $this->read_buffer->bufferRead($length)); + } + + /** + * Writes data to the stream. + * + * @param string $data Bytes to write. + * + * @throws ClosedException If the stream has already been closed. + * + * @return Promise Succeeds once the data has been successfully written to the stream. + */ + public function bufferWrite(string $data): Promise + { + if ($this->append_after) { + $this->append_after -= \strlen($data); + if ($this->append_after === 0) { + $data .= $this->append; + $this->append = ''; + } elseif ($this->append_after < 0) { + $this->append_after = 0; + $this->append = ''; + + throw new \danog\MadelineProto\Exception('Tried to send too much out of frame data, cannot append'); + } + } + + return $this->write_buffer->bufferWrite(@$this->encrypt->encrypt($data)); + } + + /** + * Set obfuscation keys/IVs + * + * @param array $data Keys + * + * @return void + */ + public function setExtra($data) + { + $this->extra = $data; + } + + /** + * {@inheritdoc} + * + * @return EncryptableSocket + */ + public function getSocket(): EncryptableSocket + { + return $this->stream->getSocket(); + } + + public function getPlainStream(): RawStreamInterface + { + return $this->stream; + } + public function getEncryptor(): AES + { + return $this->encrypt; + } + public function getDecryptor(): AES + { + return $this->decrypt; + } + + public static function getName(): string + { + return __CLASS__; + } +} diff --git a/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php b/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php index 1b8b2574..d2970670 100644 --- a/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php +++ b/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php @@ -22,7 +22,9 @@ use Amp\Promise; use Amp\Socket\EncryptableSocket; use danog\MadelineProto\Stream\Async\Buffer; use danog\MadelineProto\Stream\Async\BufferedStream; +use danog\MadelineProto\Stream\Async\Stream; use danog\MadelineProto\Stream\BufferedProxyStreamInterface; +use danog\MadelineProto\Stream\Common\CtrStream; use danog\MadelineProto\Stream\ConnectionContext; /** @@ -34,16 +36,10 @@ use danog\MadelineProto\Stream\ConnectionContext; */ class ObfuscatedStream implements BufferedProxyStreamInterface { - use Buffer; - use BufferedStream; - private $encrypt; - private $decrypt; + use Stream; + private $stream; - private $write_buffer; - private $read_buffer; private $extra; - private $append = ''; - private $append_after = 0; /** * Connect to stream. @@ -79,19 +75,25 @@ class ObfuscatedStream implements BufferedProxyStreamInterface $keyRev = \hash('sha256', $keyRev.$this->extra['secret'], true); } - $this->encrypt = new \phpseclib3\Crypt\AES('ctr'); - $this->encrypt->enableContinuousBuffer(); - $this->encrypt->setKey($key); - $this->encrypt->setIV(\substr($random, 40, 16)); + $iv = \substr($random, 40, 16); + $ivRev = \substr($reversed, 40, 16); - $this->decrypt = new \phpseclib3\Crypt\AES('ctr'); - $this->decrypt->enableContinuousBuffer(); - $this->decrypt->setKey($keyRev); - $this->decrypt->setIV(\substr($reversed, 40, 16)); + $this->stream = new CtrStream; + $this->stream->setExtra([ + 'encrypt' => [ + 'key' => $key, + 'iv' => $iv + ], + 'decrypt' => [ + 'key' => $keyRev, + 'iv' => $ivRev + ] + ]); + yield $this->stream->connect($ctx); - $random = \substr_replace($random, \substr(@$this->encrypt->encrypt($random), 56, 8), 56, 8); - - $this->stream = yield $ctx->getStream($random); + $random = \substr_replace($random, \substr(@$this->stream->getEncryptor()->encrypt($random), 56, 8), 56, 8); + + yield $this->stream->getPlainStream()->write($random); } /** @@ -104,78 +106,31 @@ class ObfuscatedStream implements BufferedProxyStreamInterface return $this->stream->disconnect(); } - /** - * Get write buffer asynchronously. - * - * @param int $length Length of data that is going to be written to the write buffer - * - * @return Generator - */ - public function getWriteBufferGenerator(int $length, string $append = ''): \Generator - { - $this->write_buffer = yield $this->stream->getWriteBuffer($length); - if (\strlen($append)) { - $this->append = $append; - $this->append_after = $length - \strlen($append); - } - - return $this; - } - + /** * Get read buffer asynchronously. * * @param int $length Length of payload, as detected by this layer * - * @return Generator + * @return Promise */ - public function getReadBufferGenerator(&$length): \Generator + public function getReadBuffer(&$length): Promise { - $this->read_buffer = yield $this->stream->getReadBuffer($l); - - return $this; + return $this->stream->getReadBuffer($length); } /** - * Decrypts read data asynchronously. + * Get write buffer asynchronously. * - * @param Promise $promise Promise that resolves with a string when new data is available or `null` if the stream has closed. + * @param int $length Total length of data that is going to be piped in the buffer * - * @throws PendingReadError Thrown if another read operation is still pending. - * - * @return Generator That resolves with a string when the provided promise is resolved and the data is decrypted + * @return Promise */ - public function bufferReadGenerator(int $length): \Generator + public function getWriteBuffer(int $length, string $append = ''): Promise { - return @$this->decrypt->encrypt(yield $this->read_buffer->bufferRead($length)); + return $this->stream->getWriteBuffer($length, $append); } - /** - * Writes data to the stream. - * - * @param string $data Bytes to write. - * - * @throws ClosedException If the stream has already been closed. - * - * @return Promise Succeeds once the data has been successfully written to the stream. - */ - public function bufferWrite(string $data): Promise - { - if ($this->append_after) { - $this->append_after -= \strlen($data); - if ($this->append_after === 0) { - $data .= $this->append; - $this->append = ''; - } elseif ($this->append_after < 0) { - $this->append_after = 0; - $this->append = ''; - - throw new \danog\MadelineProto\Exception('Tried to send too much out of frame data, cannot append'); - } - } - - return $this->write_buffer->bufferWrite(@$this->encrypt->encrypt($data)); - } /** * Does nothing. diff --git a/src/danog/MadelineProto/Stream/Transport/WsStream.php b/src/danog/MadelineProto/Stream/Transport/WsStream.php index d008c250..b1635677 100644 --- a/src/danog/MadelineProto/Stream/Transport/WsStream.php +++ b/src/danog/MadelineProto/Stream/Transport/WsStream.php @@ -72,7 +72,9 @@ class WsStream implements RawStreamInterface, ProxyStreamInterface $this->stream = yield ($this->connector ?? connector())->connect($handshake, $ctx->getCancellationToken()); - yield $this->write($header); + if (\strlen($header)) { + yield $this->write($header); + } } /**