From 2bf676411247c35ae89c3070b7dee449939c2bd4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 31 Jul 2018 15:54:18 +0100 Subject: [PATCH] Implemented network encryption (#2343) For those who fuss about performance, you can disable the `network.enable-encryption` option to use sessions without encryption. --- .travis.yml | 1 + composer.json | 3 + composer.lock | 145 ++++++++++- src/pocketmine/Player.php | 25 +- src/pocketmine/PocketMine.php | 2 + src/pocketmine/Server.php | 3 + src/pocketmine/network/mcpe/NetworkCipher.php | 83 +++++++ .../network/mcpe/NetworkSession.php | 34 ++- .../network/mcpe/ProcessLoginTask.php | 229 ++++++++++++++++++ .../network/mcpe/VerifyLoginTask.php | 154 ------------ .../mcpe/handler/HandshakeSessionHandler.php | 45 ++++ .../mcpe/handler/SimpleSessionHandler.php | 5 - src/pocketmine/resources/pocketmine.yml | 3 + 13 files changed, 556 insertions(+), 176 deletions(-) create mode 100644 src/pocketmine/network/mcpe/NetworkCipher.php create mode 100644 src/pocketmine/network/mcpe/ProcessLoginTask.php delete mode 100644 src/pocketmine/network/mcpe/VerifyLoginTask.php create mode 100644 src/pocketmine/network/mcpe/handler/HandshakeSessionHandler.php diff --git a/.travis.yml b/.travis.yml index 4196f2c2e..c4b30098e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ php: before_script: # - pecl install channel://pecl.php.net/pthreads-3.1.6 - echo | pecl install channel://pecl.php.net/yaml-2.0.2 + - pecl install channel://pecl.php.net/crypto-0.3.1 - git clone https://github.com/pmmp/pthreads.git - cd pthreads - git checkout c8cfacda84f21032d6014b53e72bf345ac901dac diff --git a/composer.json b/composer.json index 205b60ee8..f4aa2af78 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,10 @@ "php-64bit": "*", "ext-bcmath": "*", "ext-curl": "*", + "ext-crypto": "^0.3.1", "ext-ctype": "*", "ext-date": "*", + "ext-gmp": "*", "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", @@ -24,6 +26,7 @@ "ext-yaml": ">=2.0.0", "ext-zip": "*", "ext-zlib": ">=1.2.11", + "mdanter/ecc": "^0.5.0", "pocketmine/raklib": "^0.12.0", "pocketmine/spl": "^0.3.0", "pocketmine/binaryutils": "^0.1.0", diff --git a/composer.lock b/composer.lock index 4809e8a22..70f70ee97 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,149 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2670b9e2a730ff758909be8b9e9d609a", + "content-hash": "85fe0f9aca77848b0f753dbc28815c79", "packages": [ + { + "name": "fgrosse/phpasn1", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "0e27e71e3d0a8d5c3f1471d727fd9574d920f33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/0e27e71e3d0a8d5c3f1471d727fd9574d920f33c", + "reference": "0e27e71e3d0a8d5c3f1471d727fd9574d920f33c", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.3", + "satooshi/php-coveralls": "dev-master" + }, + "suggest": { + "php-curl": "For loading OID information from the web if they have not bee defined statically" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "time": "2017-12-17T10:21:51+00:00" + }, + { + "name": "mdanter/ecc", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpecc/phpecc.git", + "reference": "ed5c6d496f5310de1b7071b8375fafa3559a1344" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpecc/phpecc/zipball/ed5c6d496f5310de1b7071b8375fafa3559a1344", + "reference": "ed5c6d496f5310de1b7071b8375fafa3559a1344", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "fgrosse/phpasn1": "v2.0.x", + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "~2", + "symfony/yaml": "~2.6|~3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mdanter\\Ecc\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matyas Danter", + "homepage": "http://matejdanter.com/", + "role": "Author" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io", + "homepage": "http://aztech.io", + "role": "Maintainer" + }, + { + "name": "Thomas Kerin", + "email": "afk11@users.noreply.github.com", + "role": "Maintainer" + } + ], + "description": "PHP Elliptic Curve Cryptography library", + "homepage": "https://github.com/phpecc/phpecc", + "keywords": [ + "Diffie", + "ECDSA", + "Hellman", + "curve", + "ecdh", + "elliptic", + "nistp192", + "nistp224", + "nistp256", + "nistp384", + "nistp521", + "phpecc", + "secp256k1", + "secp256r1" + ], + "time": "2017-10-09T16:05:37+00:00" + }, { "name": "pocketmine/binaryutils", "version": "0.1.0", @@ -235,8 +376,10 @@ "php-64bit": "*", "ext-bcmath": "*", "ext-curl": "*", + "ext-crypto": "^0.3.1", "ext-ctype": "*", "ext-date": "*", + "ext-gmp": "*", "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 1be619e4f..ff3ee37a7 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -124,7 +124,7 @@ use pocketmine\network\mcpe\protocol\types\ContainerIds; use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\UpdateAttributesPacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; -use pocketmine\network\mcpe\VerifyLoginTask; +use pocketmine\network\mcpe\ProcessLoginTask; use pocketmine\permission\PermissibleBase; use pocketmine\permission\PermissionAttachment; use pocketmine\permission\PermissionAttachmentInfo; @@ -1795,17 +1795,18 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ } if(!$packet->skipVerification){ - $this->server->getAsyncPool()->submitTask(new VerifyLoginTask($this, $packet)); + $this->server->getAsyncPool()->submitTask(new ProcessLoginTask($this, $packet)); }else{ - $this->onVerifyCompleted(true, null); + $this->setAuthenticationStatus(true, null); + $this->networkSession->onLoginSuccess(); } return true; } - public function onVerifyCompleted(bool $authenticated, ?string $error) : void{ + public function setAuthenticationStatus(bool $authenticated, ?string $error) : bool{ if($this->closed){ - return; + return false; } if($authenticated and $this->xuid === ""){ @@ -1814,14 +1815,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ if($error !== null){ $this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.invalidSession", [$error])); - return; + + return false; } $this->authenticated = $authenticated; if(!$this->authenticated){ if($this->server->requiresAuthentication() and $this->kick("disconnectionScreen.notAuthenticated", false)){ //use kick to allow plugins to cancel this - return; + return false; } $this->server->getLogger()->debug($this->getName() . " is NOT logged into Xbox Live"); @@ -1833,21 +1835,22 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->server->getLogger()->debug($this->getName() . " is logged into Xbox Live"); } - //TODO: encryption - foreach($this->server->getLoggedInPlayers() as $p){ if($p !== $this and ($p->iusername === $this->iusername or $this->getUniqueId()->equals($p->getUniqueId()))){ if(!$p->kick("logged in from another location")){ $this->close($this->getLeaveMessage(), "Logged in from another location"); - return; + return false; } } } + return true; + } + + public function onLoginSuccess() : void{ $this->loggedIn = true; $this->server->onPlayerLogin($this); - $this->networkSession->onLoginSuccess(); } public function _actuallyConstruct(){ diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 0afd772f6..dd99a3fa1 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -77,8 +77,10 @@ namespace pocketmine { $extensions = [ "bcmath" => "BC Math", "curl" => "cURL", + "crypto" => "php-crypto", "ctype" => "ctype", "date" => "Date", + "gmp" => "GMP", "hash" => "Hash", "json" => "JSON", "mbstring" => "Multibyte String", diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index d900c99cb..17c2309c6 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -71,6 +71,7 @@ use pocketmine\nbt\tag\ShortTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\AdvancedNetworkInterface; use pocketmine\network\mcpe\CompressBatchedTask; +use pocketmine\network\mcpe\NetworkCipher; use pocketmine\network\mcpe\NetworkCompression; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketStream; @@ -1544,6 +1545,8 @@ class Server{ } $this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true); + NetworkCipher::$ENABLED = (bool) $this->getProperty("network.enable-encryption", true); + $this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true); $this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20); $this->alwaysTickPlayers = (bool) $this->getProperty("level-settings.always-tick-players", false); diff --git a/src/pocketmine/network/mcpe/NetworkCipher.php b/src/pocketmine/network/mcpe/NetworkCipher.php new file mode 100644 index 000000000..eb4c5f1aa --- /dev/null +++ b/src/pocketmine/network/mcpe/NetworkCipher.php @@ -0,0 +1,83 @@ +key = $encryptionKey; + $iv = substr($this->key, 0, 16); + + $this->decryptCipher = new Cipher(self::ENCRYPTION_SCHEME); + $this->decryptCipher->decryptInit($this->key, $iv); + + $this->encryptCipher = new Cipher(self::ENCRYPTION_SCHEME); + $this->encryptCipher->encryptInit($this->key, $iv); + } + + public function decrypt($encrypted){ + if(strlen($encrypted) < 9){ + throw new \InvalidArgumentException("Payload is too short"); + } + $decrypted = $this->decryptCipher->decryptUpdate($encrypted); + $payload = substr($decrypted, 0, -8); + + if(($expected = $this->calculateChecksum($this->decryptCounter++, $payload)) !== ($actual = substr($decrypted, -8))){ + throw new \InvalidArgumentException("Encrypted payload has invalid checksum (expected " . bin2hex($expected) . ", got " . bin2hex($actual) . ")"); + } + + return $payload; + } + + public function encrypt(string $payload) : string{ + return $this->encryptCipher->encryptUpdate($payload . $this->calculateChecksum($this->encryptCounter++, $payload)); + } + + private function calculateChecksum(int $counter, string $payload) : string{ + return substr(hash(self::CHECKSUM_ALGO, Binary::writeLLong($counter) . $payload . $this->key, true), 0, 8); + } +} diff --git a/src/pocketmine/network/mcpe/NetworkSession.php b/src/pocketmine/network/mcpe/NetworkSession.php index acad83e3e..993749b29 100644 --- a/src/pocketmine/network/mcpe/NetworkSession.php +++ b/src/pocketmine/network/mcpe/NetworkSession.php @@ -27,6 +27,7 @@ use pocketmine\event\player\PlayerCreationEvent; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\event\server\DataPacketSendEvent; use pocketmine\network\mcpe\handler\DeathSessionHandler; +use pocketmine\network\mcpe\handler\HandshakeSessionHandler; use pocketmine\network\mcpe\handler\LoginSessionHandler; use pocketmine\network\mcpe\handler\PreSpawnSessionHandler; use pocketmine\network\mcpe\handler\ResourcePacksSessionHandler; @@ -36,14 +37,13 @@ use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\DisconnectPacket; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\PlayStatusPacket; +use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\NetworkInterface; use pocketmine\Player; use pocketmine\Server; use pocketmine\timings\Timings; class NetworkSession{ - - /** @var Server */ private $server; /** @var Player */ @@ -63,6 +63,9 @@ class NetworkSession{ /** @var bool */ private $connected = true; + /** @var NetworkCipher */ + private $cipher; + public function __construct(Server $server, NetworkInterface $interface, string $ip, int $port){ $this->server = $server; $this->interface = $interface; @@ -142,7 +145,15 @@ class NetworkSession{ return; } - //TODO: decryption if enabled + if($this->cipher !== null){ + try{ + $payload = $this->cipher->decrypt($payload); + }catch(\InvalidArgumentException $e){ + $this->server->getLogger()->debug("Encrypted packet from " . $this->ip . " " . $this->port . ": " . bin2hex($payload)); + $this->disconnect("Packet decryption error: " . $e->getMessage()); + return; + } + } try{ $stream = new PacketStream(NetworkCompression::decompress($payload)); @@ -151,6 +162,7 @@ class NetworkSession{ $this->disconnect("Compressed packet batch decode error (incompatible game version?)", false); return; } + while(!$stream->feof() and $this->connected){ $this->handleDataPacket(PacketPool::getPacket($stream->getString())); } @@ -193,7 +205,9 @@ class NetworkSession{ } public function sendEncoded(string $payload, bool $immediate = false) : void{ - //TODO: encryption + if($this->cipher !== null){ + $payload = $this->cipher->encrypt($payload); + } $this->interface->putPacket($this, $payload, $immediate); } @@ -262,13 +276,23 @@ class NetworkSession{ $this->player = null; } - //TODO: onEnableEncryption() step + public function enableEncryption(string $encryptionKey, string $handshakeJwt) : void{ + $pk = new ServerToClientHandshakePacket(); + $pk->jwt = $handshakeJwt; + $this->sendDataPacket($pk, true); //make sure this gets sent before encryption is enabled + + $this->cipher = new NetworkCipher($encryptionKey); + + $this->setHandler(new HandshakeSessionHandler($this)); + $this->server->getLogger()->debug("Enabled encryption for $this->ip $this->port"); + } public function onLoginSuccess() : void{ $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->sendDataPacket($pk); + $this->player->onLoginSuccess(); $this->setHandler(new ResourcePacksSessionHandler($this->player, $this, $this->server->getResourcePackManager())); } diff --git a/src/pocketmine/network/mcpe/ProcessLoginTask.php b/src/pocketmine/network/mcpe/ProcessLoginTask.php new file mode 100644 index 000000000..75f19cb81 --- /dev/null +++ b/src/pocketmine/network/mcpe/ProcessLoginTask.php @@ -0,0 +1,229 @@ +storeLocal($player); + $this->packet = $packet; + $this->useEncryption = $useEncryption; + if($useEncryption){ + if(self::$SERVER_PRIVATE_KEY === null){ + self::$SERVER_PRIVATE_KEY = EccFactory::getNistCurves()->generator384()->createPrivateKey(); + } + + $this->serverPrivateKey = self::$SERVER_PRIVATE_KEY; + } + } + + public function onRun() : void{ + try{ + $clientPub = $this->validateChain(); + }catch(VerifyLoginException $e){ + $this->error = $e->getMessage(); + return; + } + + if($this->useEncryption){ + $serverPriv = $this->serverPrivateKey; + $salt = random_bytes(16); + $sharedSecret = $serverPriv->createExchange($clientPub)->calculateSharedKey(); + + $this->aesKey = hash('sha256', $salt . hex2bin(str_pad(gmp_strval($sharedSecret, 16), 96, "0", STR_PAD_LEFT)), true); + $this->handshakeJwt = $this->generateServerHandshakeJwt($serverPriv, $salt); + } + + $this->error = null; + } + + private function validateChain() : PublicKeyInterface{ + $packet = $this->packet; + + $currentKey = null; + $first = true; + + foreach($packet->chainData["chain"] as $jwt){ + $this->validateToken($jwt, $currentKey, $first); + if($first){ + $first = false; + } + } + + /** @var string $clientKey */ + $clientKey = $currentKey; + + $this->validateToken($packet->clientDataJwt, $currentKey); + + return (new DerPublicKeySerializer())->parse(base64_decode($clientKey, true)); + } + + /** + * @param string $jwt + * @param null|string $currentPublicKey + * @param bool $first + * + * @throws VerifyLoginException if errors are encountered + */ + private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{ + [$headB64, $payloadB64, $sigB64] = explode('.', $jwt); + + if($currentPublicKey === null){ + if(!$first){ + throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.missingKey"); + } + + //First link, check that it is self-signed + $headers = json_decode(self::b64UrlDecode($headB64), true); + $currentPublicKey = $headers["x5u"]; + } + + $plainSignature = self::b64UrlDecode($sigB64); + assert(strlen($plainSignature) === 96); + [$rString, $sString] = str_split($plainSignature, 48); + $sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16)); + + $derSerializer = new DerPublicKeySerializer(); + $v = openssl_verify( + "$headB64.$payloadB64", + (new DerSignatureSerializer())->serialize($sig), + (new PemPublicKeySerializer($derSerializer))->serialize($derSerializer->parse(base64_decode($currentPublicKey))), + OPENSSL_ALGO_SHA384 + ); + + if($v !== 1){ + throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.badSignature"); + } + + if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){ + $this->authenticated = true; //we're signed into xbox live + } + + $claims = json_decode(self::b64UrlDecode($payloadB64), true); + + $time = time(); + if(isset($claims["nbf"]) and $claims["nbf"] > $time){ + throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooEarly"); + } + + if(isset($claims["exp"]) and $claims["exp"] < $time){ + throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooLate"); + } + + $currentPublicKey = $claims["identityPublicKey"] ?? null; //if there are further links, the next link should be signed with this + } + + private function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{ + $jwtBody = self::b64UrlEncode(json_encode([ + "x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())), + "alg" => "ES384" + ]) + ) . "." . self::b64UrlEncode(json_encode([ + "salt" => base64_encode($salt) + ]) + ); + + openssl_sign($jwtBody, $sig, (new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($serverPriv), OPENSSL_ALGO_SHA384); + + $decodedSig = (new DerSignatureSerializer())->parse($sig); + $jwtSig = self::b64UrlEncode( + hex2bin(str_pad(gmp_strval($decodedSig->getR(), 16), 96, "0", STR_PAD_LEFT)) . + hex2bin(str_pad(gmp_strval($decodedSig->getS(), 16), 96, "0", STR_PAD_LEFT)) + ); + + return "$jwtBody.$jwtSig"; + } + + private static function b64UrlDecode(string $str) : string{ + return base64_decode(strtr($str, '-_', '+/'), true); + } + + private static function b64UrlEncode(string $str) : string{ + return strtr(base64_encode($str), '+/', '-_'); + } + + public function onCompletion(Server $server) : void{ + /** @var Player $player */ + $player = $this->fetchLocal(); + if($player->isClosed()){ + $server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified"); + }elseif($player->setAuthenticationStatus($this->authenticated, $this->error)){ + if(!$this->useEncryption){ + $player->getNetworkSession()->onLoginSuccess(); + }else{ + $player->getNetworkSession()->enableEncryption($this->aesKey, $this->handshakeJwt); + } + } + } +} diff --git a/src/pocketmine/network/mcpe/VerifyLoginTask.php b/src/pocketmine/network/mcpe/VerifyLoginTask.php deleted file mode 100644 index c151fda0f..000000000 --- a/src/pocketmine/network/mcpe/VerifyLoginTask.php +++ /dev/null @@ -1,154 +0,0 @@ -storeLocal($player); - $this->packet = $packet; - } - - public function onRun() : void{ - $packet = $this->packet; //Get it in a local variable to make sure it stays unserialized - - try{ - $currentKey = null; - $first = true; - - foreach($packet->chainData["chain"] as $jwt){ - $this->validateToken($jwt, $currentKey, $first); - $first = false; - } - - $this->validateToken($packet->clientDataJwt, $currentKey); - - $this->error = null; - }catch(VerifyLoginException $e){ - $this->error = $e->getMessage(); - } - } - - /** - * @param string $jwt - * @param null|string $currentPublicKey - * @param bool $first - * - * @throws VerifyLoginException if errors are encountered - */ - private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{ - [$headB64, $payloadB64, $sigB64] = explode('.', $jwt); - - $headers = json_decode(base64_decode(strtr($headB64, '-_', '+/'), true), true); - - if($currentPublicKey === null){ - if(!$first){ - throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.missingKey"); - } - - //First link, check that it is self-signed - $currentPublicKey = $headers["x5u"]; - } - - $plainSignature = base64_decode(strtr($sigB64, '-_', '+/'), true); - - //OpenSSL wants a DER-encoded signature, so we extract R and S from the plain signature and crudely serialize it. - - assert(strlen($plainSignature) === 96); - - [$rString, $sString] = str_split($plainSignature, 48); - - $rString = ltrim($rString, "\x00"); - if(ord($rString{0}) >= 128){ //Would be considered signed, pad it with an extra zero - $rString = "\x00" . $rString; - } - - $sString = ltrim($sString, "\x00"); - if(ord($sString{0}) >= 128){ //Would be considered signed, pad it with an extra zero - $sString = "\x00" . $sString; - } - - //0x02 = Integer ASN.1 tag - $sequence = "\x02" . chr(strlen($rString)) . $rString . "\x02" . chr(strlen($sString)) . $sString; - //0x30 = Sequence ASN.1 tag - $derSignature = "\x30" . chr(strlen($sequence)) . $sequence; - - $v = openssl_verify("$headB64.$payloadB64", $derSignature, "-----BEGIN PUBLIC KEY-----\n" . $currentPublicKey . "\n-----END PUBLIC KEY-----\n", OPENSSL_ALGO_SHA384); - if($v !== 1){ - throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.badSignature"); - } - - if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){ - $this->authenticated = true; //we're signed into xbox live - } - - $claims = json_decode(base64_decode(strtr($payloadB64, '-_', '+/'), true), true); - - $time = time(); - if(isset($claims["nbf"]) and $claims["nbf"] > $time){ - throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooEarly"); - } - - if(isset($claims["exp"]) and $claims["exp"] < $time){ - throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooLate"); - } - - $currentPublicKey = $claims["identityPublicKey"] ?? null; //if there are further links, the next link should be signed with this - } - - public function onCompletion(Server $server) : void{ - /** @var Player $player */ - $player = $this->fetchLocal(); - if($player->isClosed()){ - $server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified"); - }else{ - $player->onVerifyCompleted($this->authenticated, $this->error); - } - } -} diff --git a/src/pocketmine/network/mcpe/handler/HandshakeSessionHandler.php b/src/pocketmine/network/mcpe/handler/HandshakeSessionHandler.php new file mode 100644 index 000000000..50a39557b --- /dev/null +++ b/src/pocketmine/network/mcpe/handler/HandshakeSessionHandler.php @@ -0,0 +1,45 @@ +session = $session; + } + + public function handleClientToServerHandshake(ClientToServerHandshakePacket $packet) : bool{ + $this->session->onLoginSuccess(); + return true; + } +} diff --git a/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php b/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php index ca080fd29..a30f247a9 100644 --- a/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php +++ b/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php @@ -30,7 +30,6 @@ use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\BossEventPacket; -use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket; use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket; use pocketmine\network\mcpe\protocol\CommandRequestPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; @@ -75,10 +74,6 @@ class SimpleSessionHandler extends SessionHandler{ $this->player = $player; } - public function handleClientToServerHandshake(ClientToServerHandshakePacket $packet) : bool{ - return false; //TODO - } - public function handleText(TextPacket $packet) : bool{ if($packet->type === TextPacket::TYPE_CHAT){ return $this->player->chat($packet->message); diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index 739f5194d..efb943c2d 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -97,6 +97,9 @@ network: #Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be #fragmented or split into smaller parts. Clients can request MTU sizes up to but not more than this number. max-mtu-size: 1492 + #Enable encryption of Minecraft network traffic. This has an impact on performance, but prevents hackers from stealing sessions and pretending to be other players. + #DO NOT DISABLE THIS unless you understand the risks involved. + enable-encryption: true debug: #To enable assertion execution, set zend.assertions in your php.ini to 1