From d28be4eaf24a890f7ef110a51181a3d806a6acca Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 21 Jan 2022 23:05:21 +0000 Subject: [PATCH 1/7] Quick and dirty backport of encryption, preserving BC --- composer.json | 2 + composer.lock | 78 ++++++- src/pocketmine/Player.php | 57 ++++- src/pocketmine/Server.php | 3 + src/pocketmine/network/mcpe/JwtException.php | 28 +++ src/pocketmine/network/mcpe/JwtUtils.php | 211 ++++++++++++++++++ .../mcpe/PlayerNetworkSessionAdapter.php | 2 +- .../network/mcpe/RakLibInterface.php | 22 +- .../mcpe/encryption/DecryptionException.php | 28 +++ .../mcpe/encryption/EncryptionContext.php | 119 ++++++++++ .../mcpe/encryption/EncryptionUtils.php | 68 ++++++ .../mcpe/encryption/PrepareEncryptionTask.php | 96 ++++++++ src/pocketmine/resources/pocketmine.yml | 3 + 13 files changed, 711 insertions(+), 6 deletions(-) create mode 100644 src/pocketmine/network/mcpe/JwtException.php create mode 100644 src/pocketmine/network/mcpe/JwtUtils.php create mode 100644 src/pocketmine/network/mcpe/encryption/DecryptionException.php create mode 100644 src/pocketmine/network/mcpe/encryption/EncryptionContext.php create mode 100644 src/pocketmine/network/mcpe/encryption/EncryptionUtils.php create mode 100644 src/pocketmine/network/mcpe/encryption/PrepareEncryptionTask.php diff --git a/composer.json b/composer.json index 4c8b68e82..5f5bebb3f 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "php": "^8.0", "php-64bit": "*", "ext-chunkutils2": "^0.3.1", + "ext-crypto": "^0.3.1", "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", @@ -27,6 +28,7 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0", "adhocore/json-comment": "^1.1", + "fgrosse/phpasn1": "^2.3", "pocketmine/binaryutils": "^0.1.9", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.1.0", diff --git a/composer.lock b/composer.lock index f98e641d5..5711dc22f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9fe058549206174b6c62b2a41685083c", + "content-hash": "4ee772232d0936f6f9eda5d54ec2462d", "packages": [ { "name": "adhocore/json-comment", @@ -61,6 +61,81 @@ ], "time": "2021-04-09T03:06:06+00:00" }, + { + "name": "fgrosse/phpasn1", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296", + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296", + "shasum": "" + }, + "require": { + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^6.3 || ^7.0 || ^8.0" + }, + "suggest": { + "ext-bcmath": "BCmath is the fallback extension for big integer calculations", + "ext-curl": "For loading OID information from the web if they have not bee defined statically", + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" + }, + "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" + ], + "support": { + "issues": "https://github.com/fgrosse/PHPASN1/issues", + "source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0" + }, + "time": "2021-12-11T12:41:06+00:00" + }, { "name": "pocketmine/binaryutils", "version": "0.1.13", @@ -2762,6 +2837,7 @@ "php": "^8.0", "php-64bit": "*", "ext-chunkutils2": "^0.3.1", + "ext-crypto": "^0.3.1", "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 1bf11b1d6..ac8d4ba35 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -102,6 +102,8 @@ use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\ItemTypeDictionary; +use pocketmine\network\mcpe\encryption\EncryptionContext; +use pocketmine\network\mcpe\encryption\PrepareEncryptionTask; use pocketmine\network\mcpe\PlayerNetworkSessionAdapter; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; @@ -139,6 +141,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackDataInfoPacket; use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket; use pocketmine\network\mcpe\protocol\ResourcePackStackPacket; use pocketmine\network\mcpe\protocol\RespawnPacket; +use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket; use pocketmine\network\mcpe\protocol\SetTitlePacket; @@ -184,6 +187,7 @@ use pocketmine\tile\ItemFrame; use pocketmine\tile\Spawnable; use pocketmine\tile\Tile; use pocketmine\timings\Timings; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\TextFormat; use pocketmine\utils\UUID; use function abs; @@ -285,6 +289,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ /** @var DataPacket[] */ private $batchedPackets = []; + private ?EncryptionContext $cipher = null; + /** * @var int * Last measurement of player's latency in milliseconds. @@ -300,6 +306,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ /** @var bool */ private $seenLoginPacket = false; /** @var bool */ + private $awaitingEncryptionHandshake = false; + /** @var bool */ private $resourcePacksDone = false; /** @var bool */ @@ -2073,9 +2081,49 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->xuid = $xuid; } - //TODO: encryption + $identityPublicKey = base64_decode($packet->identityPublicKey, true); + if($identityPublicKey === false){ + //if this is invalid it should have borked VerifyLoginTask + throw new AssumptionFailedError("We should never have reached here if the key is invalid"); + } + + if(EncryptionContext::$ENABLED){ + $this->server->getAsyncPool()->submitTask(new PrepareEncryptionTask( + $identityPublicKey, + function(string $encryptionKey, string $handshakeJwt) : void{ + if(!$this->isConnected()){ + return; + } + + $pk = new ServerToClientHandshakePacket(); + $pk->jwt = $handshakeJwt; + $this->sendDataPacket($pk, false, true); //make sure this gets sent before encryption is enabled + + $this->awaitingEncryptionHandshake = true; + + $this->cipher = EncryptionContext::fakeGCM($encryptionKey); + + $this->server->getLogger()->debug("Enabled encryption for " . $this->username); + } + )); + }else{ + $this->processLogin(); + } + } + + /** + * @internal + */ + public function onEncryptionHandshake() : bool{ + if(!$this->awaitingEncryptionHandshake){ + return false; + } + $this->awaitingEncryptionHandshake = false; + + $this->server->getLogger()->debug("Encryption handshake completed for " . $this->username); $this->processLogin(); + return true; } /** @@ -3434,6 +3482,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ } } + /** + * @internal + */ + public function getCipher() : ?EncryptionContext{ + return $this->cipher; + } + /** * @return bool|int */ diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 48e598a8a..6cb0b3566 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\AdvancedSourceInterface; use pocketmine\network\CompressBatchedTask; +use pocketmine\network\mcpe\encryption\EncryptionContext; use pocketmine\network\mcpe\protocol\BatchPacket; use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; @@ -1406,6 +1407,8 @@ class Server{ } $this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true); + EncryptionContext::$ENABLED = (bool) $this->getProperty("network.enable-encryption", true); + $this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes(); $consoleSender = new ConsoleCommandSender(); diff --git a/src/pocketmine/network/mcpe/JwtException.php b/src/pocketmine/network/mcpe/JwtException.php new file mode 100644 index 000000000..14a3c21a2 --- /dev/null +++ b/src/pocketmine/network/mcpe/JwtException.php @@ -0,0 +1,28 @@ + gmp_strval(gmp_import($str, 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), 10); + + $sequence = new Sequence( + new Integer($convert($rString)), + new Integer($convert($sString)) + ); + + $v = openssl_verify( + $header . '.' . $body, + $sequence->getBinary(), + $signingKey, + OPENSSL_ALGO_SHA384 + ); + switch($v){ + case 0: return false; + case 1: return true; + case -1: throw new JwtException("Error verifying JWT signature: " . openssl_error_string()); + default: throw new AssumptionFailedError("openssl_verify() should only return -1, 0 or 1"); + } + } + + /** + * @phpstan-param array $header + * @phpstan-param array $claims + */ + public static function create(array $header, array $claims, \OpenSSLAsymmetricKey $signingKey) : string{ + $jwtBody = JwtUtils::b64UrlEncode(json_encode($header, JSON_THROW_ON_ERROR)) . "." . JwtUtils::b64UrlEncode(json_encode($claims, JSON_THROW_ON_ERROR)); + + openssl_sign( + $jwtBody, + $rawDerSig, + $signingKey, + OPENSSL_ALGO_SHA384 + ); + + try{ + $asnObject = Sequence::fromBinary($rawDerSig); + }catch(ParserException $e){ + throw new AssumptionFailedError("Failed to parse OpenSSL signature: " . $e->getMessage(), 0, $e); + } + if(count($asnObject) !== 2){ + throw new AssumptionFailedError("OpenSSL produced invalid signature, expected exactly 2 parts"); + } + [$r, $s] = [$asnObject[0], $asnObject[1]]; + if(!($r instanceof Integer) || !($s instanceof Integer)){ + throw new AssumptionFailedError("OpenSSL produced invalid signature, expected 2 INTEGER parts"); + } + $rString = $r->getContent(); + $sString = $s->getContent(); + + $toBinary = fn($str) => str_pad( + gmp_export(gmp_init($str, 10), 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), + 48, + "\x00", + STR_PAD_LEFT + ); + $jwtSig = JwtUtils::b64UrlEncode($toBinary($rString) . $toBinary($sString)); + + return "$jwtBody.$jwtSig"; + } + + public static function b64UrlEncode(string $str) : string{ + return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); + } + + public static function b64UrlDecode(string $str) : string{ + if(($len = strlen($str) % 4) !== 0){ + $str .= str_repeat('=', 4 - $len); + } + $decoded = base64_decode(strtr($str, '-_', '+/'), true); + if($decoded === false){ + throw new JwtException("Malformed base64url encoded payload could not be decoded"); + } + return $decoded; + } + + public static function emitDerPublicKey(\OpenSSLAsymmetricKey $opensslKey) : string{ + $details = openssl_pkey_get_details($opensslKey); + if($details === false){ + throw new AssumptionFailedError("Failed to get details from OpenSSL key resource"); + } + + /** @var string $pemKey */ + $pemKey = $details['key']; + if(preg_match("@^-----BEGIN[A-Z\d ]+PUBLIC KEY-----\n([A-Za-z\d+/\n]+)\n-----END[A-Z\d ]+PUBLIC KEY-----\n$@", $pemKey, $matches) === 1){ + $derKey = base64_decode(str_replace("\n", "", $matches[1]), true); + if($derKey !== false){ + return $derKey; + } + } + throw new AssumptionFailedError("OpenSSL resource contains invalid public key"); + } + + public static function parseDerPublicKey(string $derKey) : \OpenSSLAsymmetricKey{ + $signingKeyOpenSSL = openssl_pkey_get_public(sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", base64_encode($derKey))); + if($signingKeyOpenSSL === false){ + throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string()); + } + return $signingKeyOpenSSL; + } +} diff --git a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php index b300d3142..a56fb2ba2 100644 --- a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php +++ b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php @@ -118,7 +118,7 @@ class PlayerNetworkSessionAdapter extends NetworkSession{ } public function handleClientToServerHandshake(ClientToServerHandshakePacket $packet) : bool{ - return false; //TODO + return $this->player->onEncryptionHandshake(); } public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{ diff --git a/src/pocketmine/network/mcpe/RakLibInterface.php b/src/pocketmine/network/mcpe/RakLibInterface.php index 4097340b4..16bfcea37 100644 --- a/src/pocketmine/network/mcpe/RakLibInterface.php +++ b/src/pocketmine/network/mcpe/RakLibInterface.php @@ -45,6 +45,7 @@ use function get_class; use function implode; use function rtrim; use function spl_object_hash; +use function substr; use function unserialize; use const PTHREADS_INHERIT_CONSTANTS; @@ -55,6 +56,8 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ */ private const MCPE_RAKNET_PROTOCOL_VERSION = 10; + private const MCPE_RAKNET_PACKET_ID = "\xfe"; + /** @var Server */ private $server; @@ -163,9 +166,18 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ //get this now for blocking in case the player was closed before the exception was raised $player = $this->players[$identifier]; $address = $player->getAddress(); + try{ if($packet->buffer !== ""){ - $pk = new BatchPacket($packet->buffer); + if($packet->buffer[0] !== self::MCPE_RAKNET_PACKET_ID){ + throw new \UnexpectedValueException("Unexpected non-FE packet"); + } + + $cipher = $player->getCipher(); + $buffer = substr($packet->buffer, 1); + $buffer = $cipher !== null ? $cipher->decrypt($buffer) : $buffer; + + $pk = new BatchPacket(self::MCPE_RAKNET_PACKET_ID . $buffer); $player->handleDataPacket($pk); } }catch(\Throwable $e){ @@ -245,17 +257,21 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ } if($packet instanceof BatchPacket){ + $cipher = $player->getCipher(); + $rawBuffer = substr($packet->buffer, 1); + $buffer = self::MCPE_RAKNET_PACKET_ID . ($cipher !== null ? $cipher->encrypt($rawBuffer) : $rawBuffer); + if($needACK){ $pk = new EncapsulatedPacket(); $pk->identifierACK = $this->identifiersACK[$identifier]++; - $pk->buffer = $packet->buffer; + $pk->buffer = $buffer; $pk->reliability = PacketReliability::RELIABLE_ORDERED; $pk->orderChannel = 0; }else{ if(!isset($packet->__encapsulatedPacket)){ $packet->__encapsulatedPacket = new CachedEncapsulatedPacket; $packet->__encapsulatedPacket->identifierACK = null; - $packet->__encapsulatedPacket->buffer = $packet->buffer; + $packet->__encapsulatedPacket->buffer = $buffer; $packet->__encapsulatedPacket->reliability = PacketReliability::RELIABLE_ORDERED; $packet->__encapsulatedPacket->orderChannel = 0; } diff --git a/src/pocketmine/network/mcpe/encryption/DecryptionException.php b/src/pocketmine/network/mcpe/encryption/DecryptionException.php new file mode 100644 index 000000000..1a5e7e690 --- /dev/null +++ b/src/pocketmine/network/mcpe/encryption/DecryptionException.php @@ -0,0 +1,28 @@ +key = $encryptionKey; + + $this->decryptCipher = new Cipher($algorithm); + $this->decryptCipher->decryptInit($this->key, $iv); + + $this->encryptCipher = new Cipher($algorithm); + $this->encryptCipher->encryptInit($this->key, $iv); + } + + /** + * Returns an EncryptionContext suitable for decrypting Minecraft packets from 1.16.200 and up. + * + * MCPE uses GCM, but without the auth tag, which defeats the whole purpose of using GCM. + * GCM is just a wrapper around CTR which adds the auth tag, so CTR can replace GCM for this case. + * However, since GCM passes only the first 12 bytes of the IV followed by 0002, we must do the same for + * compatibility with MCPE. + * In PM, we could skip this and just use GCM directly (since we use OpenSSL), but this way is more portable, and + * better for developers who come digging in the PM code looking for answers. + */ + public static function fakeGCM(string $encryptionKey) : self{ + return new EncryptionContext( + $encryptionKey, + "AES-256-CTR", + substr($encryptionKey, 0, 12) . "\x00\x00\x00\x02" + ); + } + + public static function cfb8(string $encryptionKey) : self{ + return new EncryptionContext( + $encryptionKey, + "AES-256-CFB8", + substr($encryptionKey, 0, 16) + ); + } + + /** + * @throws DecryptionException + */ + public function decrypt(string $encrypted) : string{ + if(strlen($encrypted) < 9){ + throw new DecryptionException("Payload is too short"); + } + $decrypted = $this->decryptCipher->decryptUpdate($encrypted); + $payload = substr($decrypted, 0, -8); + + $packetCounter = $this->decryptCounter++; + + if(($expected = $this->calculateChecksum($packetCounter, $payload)) !== ($actual = substr($decrypted, -8))){ + throw new DecryptionException("Encrypted packet $packetCounter 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{ + $hash = openssl_digest(Binary::writeLLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true); + if($hash === false){ + throw new \RuntimeException("openssl_digest() error: " . openssl_error_string()); + } + return substr($hash, 0, 8); + } +} diff --git a/src/pocketmine/network/mcpe/encryption/EncryptionUtils.php b/src/pocketmine/network/mcpe/encryption/EncryptionUtils.php new file mode 100644 index 000000000..0ad2ebfee --- /dev/null +++ b/src/pocketmine/network/mcpe/encryption/EncryptionUtils.php @@ -0,0 +1,68 @@ + base64_encode($derPublicKey), + "alg" => "ES384" + ], + [ + "salt" => base64_encode($salt) + ], + $serverPriv + ); + } +} diff --git a/src/pocketmine/network/mcpe/encryption/PrepareEncryptionTask.php b/src/pocketmine/network/mcpe/encryption/PrepareEncryptionTask.php new file mode 100644 index 000000000..1a4b6dd2e --- /dev/null +++ b/src/pocketmine/network/mcpe/encryption/PrepareEncryptionTask.php @@ -0,0 +1,96 @@ + ["curve_name" => "secp384r1"]]); + if($serverPrivateKey === false){ + throw new \RuntimeException("openssl_pkey_new() failed: " . openssl_error_string()); + } + self::$SERVER_PRIVATE_KEY = $serverPrivateKey; + } + + $this->serverPrivateKey = igbinary_serialize(openssl_pkey_get_details(self::$SERVER_PRIVATE_KEY)); + $this->clientPub = $clientPub; + $this->storeLocal($onCompletion); + } + + public function onRun() : void{ + /** @var mixed[] $serverPrivDetails */ + $serverPrivDetails = igbinary_unserialize($this->serverPrivateKey); + $serverPriv = openssl_pkey_new($serverPrivDetails); + if($serverPriv === false) throw new AssumptionFailedError("Failed to restore server signing key from details"); + $clientPub = JwtUtils::parseDerPublicKey($this->clientPub); + $sharedSecret = EncryptionUtils::generateSharedSecret($serverPriv, $clientPub); + + $salt = random_bytes(16); + $this->aesKey = EncryptionUtils::generateKey($sharedSecret, $salt); + $this->handshakeJwt = EncryptionUtils::generateServerHandshakeJwt($serverPriv, $salt); + + @openssl_free_key($serverPriv); + @openssl_free_key($clientPub); + } + + public function onCompletion(Server $server) : void{ + /** + * @var \Closure $callback + * @phpstan-var \Closure(string $encryptionKey, string $handshakeJwt) : void $callback + */ + $callback = $this->fetchLocal(); + if($this->aesKey === null || $this->handshakeJwt === null){ + throw new AssumptionFailedError("Something strange happened here ..."); + } + $callback($this->aesKey, $this->handshakeJwt); + } +} diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index f12769bdf..68a00bd73 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -92,6 +92,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: #If > 1, it will show debug messages in the console From 1eae133118ac9e36f031a71b9d59593deceec13a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 21 Jan 2022 23:39:37 +0000 Subject: [PATCH 2/7] fixed PHPStan build --- phpstan.neon.dist | 1 + tests/phpstan/configs/actual-problems.neon | 10 ++++++++++ tests/phpstan/stubs/phpasn1.stub | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 tests/phpstan/stubs/phpasn1.stub diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 8ad20ab63..dcb2292c3 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -39,6 +39,7 @@ parameters: stubFiles: - tests/phpstan/stubs/chunkutils.stub - tests/phpstan/stubs/leveldb.stub + - tests/phpstan/stubs/phpasn1.stub - tests/phpstan/stubs/pthreads.stub reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings staticReflectionClassNamePatterns: diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 7ee1e46ed..3c3c68ec2 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -3410,6 +3410,16 @@ parameters: count: 1 path: ../../../src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php + - + message: "#^Method pocketmine\\\\network\\\\mcpe\\\\encryption\\\\EncryptionUtils\\:\\:generateKey\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/encryption/EncryptionUtils.php + + - + message: "#^Property pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask\\:\\:\\$serverPrivateKey \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/encryption/PrepareEncryptionTask.php + - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\protocol\\\\AddActorPacket\\:\\:\\$metadata \\(array\\\\) does not accept array\\\\.$#" count: 1 diff --git a/tests/phpstan/stubs/phpasn1.stub b/tests/phpstan/stubs/phpasn1.stub new file mode 100644 index 000000000..b459289ef --- /dev/null +++ b/tests/phpstan/stubs/phpasn1.stub @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FG\ASN1\Universal; + +class Integer +{ + /** + * @param int|string $value + */ + public function __construct($value){} + + /** @return int|string */ + public function getContent(){} +} From 0697c7d3164b079be93b653a77caab963a56c281 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 21 Jan 2022 23:45:49 +0000 Subject: [PATCH 3/7] Clean up according to newer php-cs-fixer --- src/pocketmine/event/block/BlockBreakEvent.php | 2 -- src/pocketmine/inventory/Inventory.php | 4 ---- src/pocketmine/level/Level.php | 4 ---- src/pocketmine/network/mcpe/NetworkBinaryStream.php | 2 -- src/pocketmine/plugin/PluginManager.php | 2 -- src/pocketmine/utils/Color.php | 2 -- src/pocketmine/utils/UUID.php | 2 -- 7 files changed, 18 deletions(-) diff --git a/src/pocketmine/event/block/BlockBreakEvent.php b/src/pocketmine/event/block/BlockBreakEvent.php index df821ee85..5c9bfefa1 100644 --- a/src/pocketmine/event/block/BlockBreakEvent.php +++ b/src/pocketmine/event/block/BlockBreakEvent.php @@ -100,8 +100,6 @@ class BlockBreakEvent extends BlockEvent implements Cancellable{ /** * Variadic hack for easy array member type enforcement. - * - * @param Item ...$drops */ public function setDropsVariadic(Item ...$drops) : void{ $this->blockDrops = $drops; diff --git a/src/pocketmine/inventory/Inventory.php b/src/pocketmine/inventory/Inventory.php index f950b73af..e7e69815e 100644 --- a/src/pocketmine/inventory/Inventory.php +++ b/src/pocketmine/inventory/Inventory.php @@ -58,8 +58,6 @@ interface Inventory{ * * Returns the Items that did not fit. * - * @param Item ...$slots - * * @return Item[] */ public function addItem(Item ...$slots) : array; @@ -73,8 +71,6 @@ interface Inventory{ * Removes the given Item from the inventory. * It will return the Items that couldn't be removed. * - * @param Item ...$slots - * * @return Item[] */ public function removeItem(Item ...$slots) : array; diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 2c503ea0d..a400221d6 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -761,8 +761,6 @@ class Level implements ChunkManager, Metadatable{ /** * @internal * - * @param Player ...$targets If empty, will send to all players in the level. - * * @return void */ public function sendTime(Player ...$targets){ @@ -2929,8 +2927,6 @@ class Level implements ChunkManager, Metadatable{ } /** - * @param Player ...$targets - * * @return void */ public function sendDifficulty(Player ...$targets){ diff --git a/src/pocketmine/network/mcpe/NetworkBinaryStream.php b/src/pocketmine/network/mcpe/NetworkBinaryStream.php index d07644f5f..3dfcfea6a 100644 --- a/src/pocketmine/network/mcpe/NetworkBinaryStream.php +++ b/src/pocketmine/network/mcpe/NetworkBinaryStream.php @@ -540,8 +540,6 @@ class NetworkBinaryStream extends BinaryStream{ /** * Writes a list of Attributes to the packet buffer using the standard format. - * - * @param Attribute ...$attributes */ public function putAttributeList(Attribute ...$attributes) : void{ $this->putUnsignedVarInt(count($attributes)); diff --git a/src/pocketmine/plugin/PluginManager.php b/src/pocketmine/plugin/PluginManager.php index 6ee6fa1ec..6bbf131fd 100644 --- a/src/pocketmine/plugin/PluginManager.php +++ b/src/pocketmine/plugin/PluginManager.php @@ -365,8 +365,6 @@ class PluginManager{ /** * Returns whether a specified API version string is considered compatible with the server's API version. - * - * @param string ...$versions */ public function isCompatibleApi(string ...$versions) : bool{ $serverString = $this->server->getApiVersion(); diff --git a/src/pocketmine/utils/Color.php b/src/pocketmine/utils/Color.php index 1522c20c1..b6f36fafa 100644 --- a/src/pocketmine/utils/Color.php +++ b/src/pocketmine/utils/Color.php @@ -109,8 +109,6 @@ class Color{ /** * Mixes the supplied list of colours together to produce a result colour. - * - * @param Color ...$colors */ public static function mix(Color ...$colors) : Color{ $count = count($colors); diff --git a/src/pocketmine/utils/UUID.php b/src/pocketmine/utils/UUID.php index b6d725d39..05b31f8b7 100644 --- a/src/pocketmine/utils/UUID.php +++ b/src/pocketmine/utils/UUID.php @@ -84,8 +84,6 @@ class UUID{ /** * Creates an UUIDv3 from binary data or list of binary data - * - * @param string ...$data */ public static function fromData(string ...$data) : UUID{ $hash = hash("md5", implode($data), true); From 09201ac14bcb876867a3fd64d904e0c21bd40a38 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 22 Jan 2022 00:24:31 +0000 Subject: [PATCH 4/7] Fixed chunk sending we can't cache the encapsulated stuff anymore because of encryption. --- .../network/mcpe/RakLibInterface.php | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/pocketmine/network/mcpe/RakLibInterface.php b/src/pocketmine/network/mcpe/RakLibInterface.php index 16bfcea37..8772a79f8 100644 --- a/src/pocketmine/network/mcpe/RakLibInterface.php +++ b/src/pocketmine/network/mcpe/RakLibInterface.php @@ -261,22 +261,11 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ $rawBuffer = substr($packet->buffer, 1); $buffer = self::MCPE_RAKNET_PACKET_ID . ($cipher !== null ? $cipher->encrypt($rawBuffer) : $rawBuffer); - if($needACK){ - $pk = new EncapsulatedPacket(); - $pk->identifierACK = $this->identifiersACK[$identifier]++; - $pk->buffer = $buffer; - $pk->reliability = PacketReliability::RELIABLE_ORDERED; - $pk->orderChannel = 0; - }else{ - if(!isset($packet->__encapsulatedPacket)){ - $packet->__encapsulatedPacket = new CachedEncapsulatedPacket; - $packet->__encapsulatedPacket->identifierACK = null; - $packet->__encapsulatedPacket->buffer = $buffer; - $packet->__encapsulatedPacket->reliability = PacketReliability::RELIABLE_ORDERED; - $packet->__encapsulatedPacket->orderChannel = 0; - } - $pk = $packet->__encapsulatedPacket; - } + $pk = new EncapsulatedPacket(); + $pk->identifierACK = $needACK ? $this->identifiersACK[$identifier]++ : null; + $pk->buffer = $buffer; + $pk->reliability = PacketReliability::RELIABLE_ORDERED; + $pk->orderChannel = 0; $this->interface->sendEncapsulated($identifier, $pk, ($needACK ? RakLib::FLAG_NEED_ACK : 0) | ($immediate ? RakLib::PRIORITY_IMMEDIATE : RakLib::PRIORITY_NORMAL)); return $pk->identifierACK; From e5a9123522e409749addb1fc845960970d62cb9f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 22 Jan 2022 00:30:05 +0000 Subject: [PATCH 5/7] PocketMine.php: require ext-crypto --- src/pocketmine/PocketMine.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index bb162ec13..55d328ef2 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -76,6 +76,7 @@ namespace pocketmine { $extensions = [ "chunkutils2" => "PocketMine ChunkUtils v2", "curl" => "cURL", + "crypto" => "php-crypto", "ctype" => "ctype", "date" => "Date", "hash" => "Hash", From e21446e5832060066099d93f9822f2ecfe8d59ad Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 22 Jan 2022 00:36:47 +0000 Subject: [PATCH 6/7] Release 3.27.0 --- changelogs/3.27.md | 15 +++++++++++++++ src/pocketmine/VersionInfo.php | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 changelogs/3.27.md diff --git a/changelogs/3.27.md b/changelogs/3.27.md new file mode 100644 index 000000000..58ed8925a --- /dev/null +++ b/changelogs/3.27.md @@ -0,0 +1,15 @@ +**For Minecraft: Bedrock Edition 1.18.0** + +### Note about API versions +Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps. +Plugin developers should **only** update their required API to this version if you need the changes in this build. + +**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do. + +# 3.27.0 +- Introduced support for protocol encryption. + - Encryption is enabled by default. + - Fixes login replay attacks. + - This may cause some performance degradation. + - Encryption can be disabled by setting `network.enable-encryption` to `false` in `pocketmine.yml`. DO NOT do this unless you understand the risks involved. +- An obsoletion notice has been added to the console during server startup. \ No newline at end of file diff --git a/src/pocketmine/VersionInfo.php b/src/pocketmine/VersionInfo.php index d2c0036be..164f4cab5 100644 --- a/src/pocketmine/VersionInfo.php +++ b/src/pocketmine/VersionInfo.php @@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){ const _VERSION_INFO_INCLUDED = true; const NAME = "PocketMine-MP"; -const BASE_VERSION = "3.26.6"; -const IS_DEVELOPMENT_BUILD = true; +const BASE_VERSION = "3.27.0"; +const IS_DEVELOPMENT_BUILD = false; const BUILD_CHANNEL = "pm3"; From 60ef2db892da66f99d66ecf1ea218c7ec878ddd4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 22 Jan 2022 00:36:48 +0000 Subject: [PATCH 7/7] 3.27.1 is next --- src/pocketmine/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pocketmine/VersionInfo.php b/src/pocketmine/VersionInfo.php index 164f4cab5..ba505addd 100644 --- a/src/pocketmine/VersionInfo.php +++ b/src/pocketmine/VersionInfo.php @@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){ const _VERSION_INFO_INCLUDED = true; const NAME = "PocketMine-MP"; -const BASE_VERSION = "3.27.0"; -const IS_DEVELOPMENT_BUILD = false; +const BASE_VERSION = "3.27.1"; +const IS_DEVELOPMENT_BUILD = true; const BUILD_CHANNEL = "pm3";