38 public const MOJANG_AUDIENCE =
"api://auth-minecraft-services/multiplayer";
40 private const CLOCK_DRIFT_MAX = 60;
45 private static function checkExpiry(
JwtBodyRfc7519 $claims) :
void{
47 if(isset($claims->nbf) && $claims->nbf > $time + self::CLOCK_DRIFT_MAX){
48 throw new VerifyLoginException(
"JWT not yet valid", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooEarly());
51 if(isset($claims->exp) && $claims->exp < $time - self::CLOCK_DRIFT_MAX){
52 throw new VerifyLoginException(
"JWT expired", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooLate());
61 if(!
JwtUtils::verify($jwt, $signingKeyDer, ec: false)){
62 throw new VerifyLoginException(
"Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
65 throw new VerifyLoginException($e->getMessage(),
null, 0, $e);
70 }
catch(JwtException $e){
71 throw new VerifyLoginException(
"Failed to parse JWT: " . $e->getMessage(),
null, 0, $e);
74 $mapper = new \JsonMapper();
75 $mapper->bExceptionOnUndefinedProperty =
false;
76 $mapper->bExceptionOnMissingData =
true;
77 $mapper->bStrictObjectTypeChecking =
true;
78 $mapper->bEnforceMapType =
false;
79 $mapper->bRemoveUndefinedAttributes =
true;
83 $claims = $mapper->map($claimsArray,
new XboxAuthJwtBody());
84 }
catch(\JsonMapper_Exception $e){
85 throw new VerifyLoginException(
"Invalid chain link body: " . $e->getMessage(),
null, 0, $e);
88 if(!isset($claims->iss) || $claims->iss !== $issuer){
89 throw new VerifyLoginException(
"Invalid JWT issuer");
92 if(!isset($claims->aud) || $claims->aud !== $audience){
93 throw new VerifyLoginException(
"Invalid JWT audience");
96 self::checkExpiry($claims);
105 self::validateSelfSignedToken($jwt, $expectedKeyDer);
108 [, $claimsArray, ] = JwtUtils::parse($jwt);
110 $mapper = new \JsonMapper();
111 $mapper->bExceptionOnUndefinedProperty =
false;
112 $mapper->bExceptionOnMissingData =
true;
113 $mapper->bStrictObjectTypeChecking =
true;
114 $mapper->bEnforceMapType =
false;
115 $mapper->bRemoveUndefinedAttributes =
true;
119 }
catch(\JsonMapper_Exception $e){
123 self::checkExpiry($claims);
128 public static function validateSelfSignedToken(
string $jwt, ?
string $expectedKeyDer) : void{
130 [$headersArray, ] = JwtUtils::parse($jwt);
131 }
catch(JwtException $e){
132 throw new VerifyLoginException(
"Failed to parse JWT: " . $e->getMessage(),
null, 0, $e);
135 $mapper = new \JsonMapper();
136 $mapper->bExceptionOnMissingData =
true;
137 $mapper->bExceptionOnUndefinedProperty =
true;
138 $mapper->bStrictObjectTypeChecking =
true;
139 $mapper->bEnforceMapType =
false;
143 $headers = $mapper->map($headersArray,
new SelfSignedJwtHeader());
144 }
catch(\JsonMapper_Exception $e){
145 throw new VerifyLoginException(
"Invalid JWT header: " . $e->getMessage(),
null, 0, $e);
148 $headerDerKey = base64_decode($headers->x5u,
true);
149 if($headerDerKey ===
false){
150 throw new VerifyLoginException(
"Invalid JWT public key: base64 decoding error decoding x5u");
152 if($expectedKeyDer !==
null && $headerDerKey !== $expectedKeyDer){
154 throw new VerifyLoginException(
"Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
158 if(!JwtUtils::verify($jwt, $headerDerKey, ec:
true)){
159 throw new VerifyLoginException(
"Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
161 }
catch(JwtException $e){
162 throw new VerifyLoginException($e->getMessage(),
null, 0, $e);