PocketMine-MP 5.43.2 git-a137a986d01d9af23452b2e741699a770c7ae112
Loading...
Searching...
No Matches
AuthJwtHelper.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\network\mcpe\auth;
25
34use function base64_decode;
35use function time;
36
37final class AuthJwtHelper{
38
39 public const MOJANG_AUDIENCE = "api://auth-minecraft-services/multiplayer";
40
41 private const CLOCK_DRIFT_MAX = 60;
42
46 private static function checkExpiry(JwtBodyRfc7519 $claims) : void{
47 $time = time();
48 if(isset($claims->nbf) && $claims->nbf > $time + self::CLOCK_DRIFT_MAX){
49 throw new VerifyLoginException("JWT not yet valid", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooEarly());
50 }
51
52 if(isset($claims->exp) && $claims->exp < $time - self::CLOCK_DRIFT_MAX){
53 throw new VerifyLoginException("JWT expired", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooLate());
54 }
55 }
56
60 private static function validateAuthToken(string $jwt, string $signingKeyDer, ?string $issuer, string $audience, XboxAuthJwtBody|SelfSignedJwtBody $claims) : void{
61 try{
62 if(!JwtUtils::verify($jwt, $signingKeyDer, ec: $claims instanceof SelfSignedJwtBody)){
63 throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
64 }
65 }catch(JwtException $e){
66 throw new VerifyLoginException($e->getMessage(), null, 0, $e);
67 }
68
69 try{
70 [, $claimsArray, ] = JwtUtils::parse($jwt);
71 }catch(JwtException $e){
72 throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e);
73 }
74
75 $mapper = new \JsonMapper();
76 $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case
77 $mapper->bExceptionOnMissingData = true;
78 $mapper->bStrictObjectTypeChecking = true;
79 $mapper->bEnforceMapType = false;
80 $mapper->bRemoveUndefinedAttributes = true;
81
82 try{
83 $mapper->map($claimsArray, $claims);
84 }catch(\JsonMapper_Exception $e){
85 throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e);
86 }
87
88 if($issuer !== null && (!isset($claims->iss) || $claims->iss !== $issuer)){
89 throw new VerifyLoginException("Invalid JWT issuer");
90 }
91
92 if(!isset($claims->aud) || $claims->aud !== $audience){
93 throw new VerifyLoginException("Invalid JWT audience");
94 }
95
96 self::checkExpiry($claims);
97 }
98
102 public static function validateSelfSignedAuthToken(string $jwt, string $signingKeyDer, string $audience) : SelfSignedJwtBody{
103 $claims = new SelfSignedJwtBody();
104 self::validateAuthToken($jwt, $signingKeyDer, null, $audience, $claims);
105 return $claims;
106 }
107
111 public static function validateOpenIdAuthToken(string $jwt, string $signingKeyDer, string $issuer, string $audience) : XboxAuthJwtBody{
112 $claims = new XboxAuthJwtBody();
113 self::validateAuthToken($jwt, $signingKeyDer, $issuer, $audience, $claims);
114 return $claims;
115 }
116
121 public static function validateLegacyAuthToken(string $jwt, ?string $expectedKeyDer) : LegacyAuthJwtBody{
122 self::validateSelfSignedToken($jwt, $expectedKeyDer);
123
124 //TODO: this parses the JWT twice and throws away a bunch of parts, optimize this
125 [, $claimsArray, ] = JwtUtils::parse($jwt);
126
127 $mapper = new \JsonMapper();
128 $mapper->bExceptionOnUndefinedProperty = false; //we only care about the properties we're using in this case
129 $mapper->bExceptionOnMissingData = true;
130 $mapper->bStrictObjectTypeChecking = true;
131 $mapper->bEnforceMapType = false;
132 $mapper->bRemoveUndefinedAttributes = true;
133 try{
135 $claims = $mapper->map($claimsArray, new LegacyAuthJwtBody());
136 }catch(\JsonMapper_Exception $e){
137 throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e);
138 }
139
140 self::checkExpiry($claims);
141
142 return $claims;
143 }
144
148 public static function validateSelfSignedToken(string $jwt, ?string $expectedKeyDer) : void{
149 try{
150 [$headersArray, ] = JwtUtils::parse($jwt);
151 }catch(JwtException $e){
152 throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e);
153 }
154
155 $mapper = new \JsonMapper();
156 $mapper->bExceptionOnMissingData = true;
157 $mapper->bExceptionOnUndefinedProperty = true;
158 $mapper->bStrictObjectTypeChecking = true;
159 $mapper->bEnforceMapType = false;
160
161 try{
163 $headers = $mapper->map($headersArray, new SelfSignedJwtHeader());
164 }catch(\JsonMapper_Exception $e){
165 throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e);
166 }
167
168 $headerDerKey = base64_decode($headers->x5u, true);
169 if($headerDerKey === false){
170 throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u");
171 }
172 if($expectedKeyDer !== null && $headerDerKey !== $expectedKeyDer){
173 //Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway
174 throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
175 }
176
177 try{
178 if(!JwtUtils::verify($jwt, $headerDerKey, ec: true)){
179 throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
180 }
181 }catch(JwtException $e){
182 throw new VerifyLoginException($e->getMessage(), null, 0, $e);
183 }
184 }
185}
static verify(string $jwt, string $signingKeyDer, bool $ec)
Definition JwtUtils.php:180
static parse(string $token)
Definition JwtUtils.php:97
static validateLegacyAuthToken(string $jwt, ?string $expectedKeyDer)
static validateSelfSignedToken(string $jwt, ?string $expectedKeyDer)
static validateSelfSignedAuthToken(string $jwt, string $signingKeyDer, string $audience)
static validateOpenIdAuthToken(string $jwt, string $signingKeyDer, string $issuer, string $audience)