storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion); $this->packet = $packet; $this->authRequired = $authRequired; } public function onRun() : void{ try{ $this->clientPublicKey = $this->validateChain(); $this->error = null; }catch(VerifyLoginException $e){ $this->error = $e->getMessage(); } } private function validateChain() : PublicKeyInterface{ $packet = $this->packet; $currentKey = null; $first = true; foreach($packet->chainDataJwt->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)); } /** * @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, true))), 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 + self::CLOCK_DRIFT_MAX){ throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooEarly"); } if(isset($claims["exp"]) and $claims["exp"] < $time - self::CLOCK_DRIFT_MAX){ 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 static function b64UrlDecode(string $str) : string{ if(($len = strlen($str) % 4) !== 0){ $str .= str_repeat('=', 4 - $len); } return base64_decode(strtr($str, '-_', '+/'), true); } public function onCompletion() : void{ /** * @var \Closure $callback * @phpstan-var \Closure(bool, bool, ?string, ?PublicKeyInterface) : void $callback */ $callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION); $callback($this->authenticated, $this->authRequired, $this->error, $this->clientPublicKey); } }