From 8c2878fe5be1be88e8dbc2e748c5131a6a0834f1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 13 May 2020 13:36:42 +0100 Subject: [PATCH] Added JwtUtils::parse(), make ProcessLoginTask more robust --- src/network/mcpe/JwtUtils.php | 20 +++++++++++++------- src/network/mcpe/auth/ProcessLoginTask.php | 10 +++++----- src/network/mcpe/protocol/LoginPacket.php | 4 ++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index 0e5a81b0a..358da0372 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -38,22 +38,28 @@ use function strtr; final class JwtUtils{ /** - * @return mixed[] array of claims - * @phpstan-return array + * TODO: replace this result with an object + * + * @return mixed[] + * @phpstan-return array{mixed[], mixed[], string} * * @throws \UnexpectedValueException */ - public static function getClaims(string $token) : array{ + public static function parse(string $token) : array{ $v = explode(".", $token); if(count($v) !== 3){ throw new \UnexpectedValueException("Expected exactly 3 JWT parts, got " . count($v)); } - $result = json_decode(self::b64UrlDecode($v[1]), true); - if(!is_array($result)){ + $header = json_decode(self::b64UrlDecode($v[0]), true); + if(!is_array($header)){ + throw new \UnexpectedValueException("Failed to decode JWT header JSON: ". json_last_error_msg()); + } + $body = json_decode(self::b64UrlDecode($v[1]), true); + if(!is_array($body)){ throw new \UnexpectedValueException("Failed to decode JWT payload JSON: " . json_last_error_msg()); } - - return $result; + $signature = self::b64UrlDecode($v[2]); + return [$header, $body, $signature]; } public static function b64UrlEncode(string $str) : string{ diff --git a/src/network/mcpe/auth/ProcessLoginTask.php b/src/network/mcpe/auth/ProcessLoginTask.php index c997b48d7..2cc68a30f 100644 --- a/src/network/mcpe/auth/ProcessLoginTask.php +++ b/src/network/mcpe/auth/ProcessLoginTask.php @@ -36,7 +36,6 @@ use function base64_decode; use function bin2hex; use function explode; use function gmp_init; -use function json_decode; use function openssl_verify; use function str_split; use function strlen; @@ -115,7 +114,11 @@ class ProcessLoginTask extends AsyncTask{ * @throws VerifyLoginException if errors are encountered */ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{ - [$headB64, $payloadB64, $sigB64] = explode('.', $jwt); + try{ + [$headers, $claims, $plainSignature] = JwtUtils::parse($jwt); + }catch(\UnexpectedValueException $e){ + throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), 0, $e); + } if($currentPublicKey === null){ if(!$first){ @@ -123,7 +126,6 @@ class ProcessLoginTask extends AsyncTask{ } //First link, check that it is self-signed - $headers = json_decode(JwtUtils::b64UrlDecode($headB64), true); $currentPublicKey = $headers["x5u"]; } @@ -148,8 +150,6 @@ class ProcessLoginTask extends AsyncTask{ $this->authenticated = true; //we're signed into xbox live } - $claims = json_decode(JwtUtils::b64UrlDecode($payloadB64), true); - $time = time(); if(isset($claims["nbf"]) and $claims["nbf"] > $time + self::CLOCK_DRIFT_MAX){ throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooEarly"); diff --git a/src/network/mcpe/protocol/LoginPacket.php b/src/network/mcpe/protocol/LoginPacket.php index 5b5fc7594..e8c90826f 100644 --- a/src/network/mcpe/protocol/LoginPacket.php +++ b/src/network/mcpe/protocol/LoginPacket.php @@ -88,7 +88,7 @@ class LoginPacket extends DataPacket implements ServerboundPacket{ foreach($this->chainDataJwt->chain as $k => $chain){ //validate every chain element try{ - $claims = JwtUtils::getClaims($chain); + [, $claims, ] = JwtUtils::parse($chain); }catch(\UnexpectedValueException $e){ throw new PacketDecodeException($e->getMessage(), 0, $e); } @@ -117,7 +117,7 @@ class LoginPacket extends DataPacket implements ServerboundPacket{ $this->clientDataJwt = $buffer->get($buffer->getLInt()); try{ - $clientData = JwtUtils::getClaims($this->clientDataJwt); + [, $clientData, ] = JwtUtils::parse($this->clientDataJwt); }catch(\UnexpectedValueException $e){ throw new PacketDecodeException($e->getMessage(), 0, $e); }