mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-06 09:56:06 +00:00
LoginPacket: Cater for more error cases
This now doesn't crash unexpectedly at the first sign of broken data.
This commit is contained in:
@ -138,7 +138,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
$currentKey = null;
|
||||
$first = true;
|
||||
|
||||
foreach($packet->chainData["chain"] as $jwt){
|
||||
foreach($packet->chainDataJwt as $jwt){
|
||||
$this->validateToken($jwt, $currentKey, $first);
|
||||
if($first){
|
||||
$first = false;
|
||||
|
@ -26,19 +26,34 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
|
||||
use Particle\Validator\Validator;
|
||||
use pocketmine\entity\Skin;
|
||||
use pocketmine\network\mcpe\handler\SessionHandler;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_filter;
|
||||
use function base64_decode;
|
||||
use function get_class;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
use function json_last_error_msg;
|
||||
|
||||
class LoginPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::LOGIN_PACKET;
|
||||
|
||||
public const EDITION_POCKET = 0;
|
||||
|
||||
public const I_CLIENT_RANDOM_ID = 'ClientRandomId';
|
||||
public const I_SERVER_ADDRESS = 'ServerAddress';
|
||||
public const I_LANGUAGE_CODE = 'LanguageCode';
|
||||
|
||||
public const I_SKIN_ID = 'SkinId';
|
||||
public const I_SKIN_DATA = 'SkinData';
|
||||
public const I_CAPE_DATA = 'CapeData';
|
||||
public const I_GEOMETRY_NAME = 'SkinGeometryName';
|
||||
public const I_GEOMETRY_DATA = 'SkinGeometry';
|
||||
|
||||
/** @var string */
|
||||
public $username;
|
||||
/** @var int */
|
||||
@ -58,8 +73,8 @@ class LoginPacket extends DataPacket{
|
||||
/** @var Skin|null */
|
||||
public $skin;
|
||||
|
||||
/** @var array (the "chain" index contains one or more JWTs) */
|
||||
public $chainData = [];
|
||||
/** @var string[] array of encoded JWT */
|
||||
public $chainDataJwt = [];
|
||||
/** @var string */
|
||||
public $clientDataJwt;
|
||||
/** @var array decoded payload of the clientData JWT */
|
||||
@ -83,65 +98,99 @@ class LoginPacket extends DataPacket{
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->protocol = $this->getInt();
|
||||
$this->decodeConnectionRequest();
|
||||
}
|
||||
|
||||
try{
|
||||
$this->decodeConnectionRequest();
|
||||
}catch(\Throwable $e){
|
||||
if($this->protocol === ProtocolInfo::CURRENT_PROTOCOL){
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$logger = \GlobalLogger::get();
|
||||
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version " . ($this->protocol ?? "unknown") . "): " . $e->getMessage());
|
||||
foreach(Utils::printableTrace($e->getTrace()) as $line){
|
||||
$logger->debug($line);
|
||||
/**
|
||||
* @param Validator $v
|
||||
* @param string $name
|
||||
* @param $data
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
private static function validate(Validator $v, string $name, $data) : void{
|
||||
$result = $v->validate($data);
|
||||
if($result->isNotValid()){
|
||||
$messages = [];
|
||||
foreach($result->getFailures() as $f){
|
||||
$messages[] = $f->format();
|
||||
}
|
||||
throw new \UnexpectedValueException("Failed to validate '$name': " . implode(", ", $messages));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OutOfBoundsException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function decodeConnectionRequest() : void{
|
||||
$buffer = new BinaryStream($this->getString());
|
||||
|
||||
$this->chainData = json_decode($buffer->get($buffer->getLInt()), true);
|
||||
$chainData = json_decode($buffer->get($buffer->getLInt()), true);
|
||||
if(!is_array($chainData)){
|
||||
throw new \UnexpectedValueException("Failed to decode chainData JSON: " . json_last_error_msg());
|
||||
}
|
||||
|
||||
$vd = new Validator();
|
||||
$vd->required('chain')->isArray()->callback(function(array $data) : bool{
|
||||
return count($data) === 3 and count(array_filter($data, '\is_string')) === count($data);
|
||||
});
|
||||
self::validate($vd, "chainData", $chainData);
|
||||
|
||||
$this->chainDataJwt = $chainData['chain'];
|
||||
|
||||
$hasExtraData = false;
|
||||
foreach($this->chainData["chain"] as $chain){
|
||||
$webtoken = Utils::decodeJWT($chain);
|
||||
if(isset($webtoken["extraData"])){
|
||||
foreach($this->chainDataJwt as $k => $chain){
|
||||
//validate every chain element
|
||||
$claims = Utils::getJwtClaims($chain);
|
||||
if(isset($claims["extraData"])){
|
||||
if(!is_array($claims["extraData"])){
|
||||
throw new \UnexpectedValueException("'extraData' key should be an array");
|
||||
}
|
||||
if($hasExtraData){
|
||||
throw new \RuntimeException("Found 'extraData' multiple times in key chain");
|
||||
throw new \UnexpectedValueException("Found 'extraData' more than once in chainData");
|
||||
}
|
||||
$hasExtraData = true;
|
||||
if(isset($webtoken["extraData"]["displayName"])){
|
||||
$this->username = $webtoken["extraData"]["displayName"];
|
||||
}
|
||||
if(isset($webtoken["extraData"]["identity"])){
|
||||
$this->clientUUID = $webtoken["extraData"]["identity"];
|
||||
}
|
||||
if(isset($webtoken["extraData"]["XUID"])){
|
||||
$this->xuid = $webtoken["extraData"]["XUID"];
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($webtoken["identityPublicKey"])){
|
||||
$this->identityPublicKey = $webtoken["identityPublicKey"];
|
||||
$extraV = new Validator();
|
||||
$extraV->required('displayName')->string();
|
||||
$extraV->required('identity')->uuid();
|
||||
$extraV->required('XUID')->string()->digits();
|
||||
self::validate($extraV, "chain.$k.extraData", $claims['extraData']);
|
||||
|
||||
$this->username = $claims["extraData"]["displayName"];
|
||||
$this->clientUUID = $claims["extraData"]["identity"];
|
||||
$this->xuid = $claims["extraData"]["XUID"];
|
||||
}
|
||||
}
|
||||
|
||||
$this->clientDataJwt = $buffer->get($buffer->getLInt());
|
||||
$this->clientData = Utils::decodeJWT($this->clientDataJwt);
|
||||
$clientData = Utils::getJwtClaims($this->clientDataJwt);
|
||||
|
||||
$this->clientId = $this->clientData["ClientRandomId"] ?? null;
|
||||
$this->serverAddress = $this->clientData["ServerAddress"] ?? null;
|
||||
$v = new Validator();
|
||||
$v->required(self::I_CLIENT_RANDOM_ID)->integer();
|
||||
$v->required(self::I_SERVER_ADDRESS)->string();
|
||||
$v->required(self::I_LANGUAGE_CODE)->string();
|
||||
|
||||
$this->locale = $this->clientData["LanguageCode"] ?? "en_US";
|
||||
$v->required(self::I_SKIN_ID)->string();
|
||||
$v->required(self::I_SKIN_DATA)->string();
|
||||
$v->required(self::I_CAPE_DATA, null, true)->string();
|
||||
$v->required(self::I_GEOMETRY_NAME)->string();
|
||||
$v->required(self::I_GEOMETRY_DATA, null, true)->string();
|
||||
self::validate($v, 'clientData', $clientData);
|
||||
|
||||
$this->clientData = $clientData;
|
||||
|
||||
$this->clientId = $this->clientData[self::I_CLIENT_RANDOM_ID];
|
||||
$this->serverAddress = $this->clientData[self::I_SERVER_ADDRESS];
|
||||
$this->locale = $this->clientData[self::I_LANGUAGE_CODE];
|
||||
|
||||
$this->skin = new Skin(
|
||||
$this->clientData["SkinId"] ?? "",
|
||||
base64_decode($this->clientData["SkinData"] ?? ""),
|
||||
base64_decode($this->clientData["CapeData"] ?? ""),
|
||||
$this->clientData["SkinGeometryName"] ?? "",
|
||||
base64_decode($this->clientData["SkinGeometry"] ?? "")
|
||||
$this->clientData[self::I_SKIN_ID],
|
||||
base64_decode($this->clientData[self::I_SKIN_DATA]),
|
||||
base64_decode($this->clientData[self::I_CAPE_DATA]),
|
||||
$this->clientData[self::I_GEOMETRY_NAME],
|
||||
base64_decode($this->clientData[self::I_GEOMETRY_DATA])
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user