mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-30 23:29:54 +00:00
consolidate some JWT handling into one class
This commit is contained in:
parent
5d154e43a9
commit
ed757c7207
66
src/network/mcpe/JwtUtils.php
Normal file
66
src/network/mcpe/JwtUtils.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\network\mcpe;
|
||||||
|
|
||||||
|
final class JwtUtils{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[] array of claims
|
||||||
|
* @phpstan-return array<string, mixed>
|
||||||
|
*
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
*/
|
||||||
|
public static function getClaims(string $token) : array{
|
||||||
|
$v = explode(".", $token);
|
||||||
|
if(count($v) !== 3){
|
||||||
|
throw new \UnexpectedValueException("Expected exactly 3 JWT parts, got " . count($v));
|
||||||
|
}
|
||||||
|
$payloadB64 = $v[1];
|
||||||
|
$payloadJSON = self::b64UrlDecode($payloadB64);
|
||||||
|
if($payloadJSON === false){
|
||||||
|
throw new \UnexpectedValueException("Invalid base64 JWT payload");
|
||||||
|
}
|
||||||
|
$result = json_decode($payloadJSON, true);
|
||||||
|
if(!is_array($result)){
|
||||||
|
throw new \UnexpectedValueException("Failed to decode JWT payload JSON: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function b64UrlEncode(string $str) : string{
|
||||||
|
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function b64UrlDecode(string $str) : string{
|
||||||
|
if(($len = strlen($str) % 4) !== 0){
|
||||||
|
$str .= str_repeat('=', 4 - $len);
|
||||||
|
}
|
||||||
|
$decoded = base64_decode(strtr($str, '-_', '+/'), true);
|
||||||
|
if($decoded === false){
|
||||||
|
throw new \UnexpectedValueException("Malformed base64url encoded payload could not be decoded");
|
||||||
|
}
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ use Mdanter\Ecc\Crypto\Signature\Signature;
|
|||||||
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
||||||
use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer;
|
use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer;
|
||||||
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
|
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
|
||||||
|
use pocketmine\network\mcpe\JwtUtils;
|
||||||
use pocketmine\network\mcpe\protocol\LoginPacket;
|
use pocketmine\network\mcpe\protocol\LoginPacket;
|
||||||
use pocketmine\scheduler\AsyncTask;
|
use pocketmine\scheduler\AsyncTask;
|
||||||
use function assert;
|
use function assert;
|
||||||
@ -124,11 +125,11 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//First link, check that it is self-signed
|
//First link, check that it is self-signed
|
||||||
$headers = json_decode(self::b64UrlDecode($headB64), true);
|
$headers = json_decode(JwtUtils::b64UrlDecode($headB64), true);
|
||||||
$currentPublicKey = $headers["x5u"];
|
$currentPublicKey = $headers["x5u"];
|
||||||
}
|
}
|
||||||
|
|
||||||
$plainSignature = self::b64UrlDecode($sigB64);
|
$plainSignature = JwtUtils::b64UrlDecode($sigB64);
|
||||||
assert(strlen($plainSignature) === 96);
|
assert(strlen($plainSignature) === 96);
|
||||||
[$rString, $sString] = str_split($plainSignature, 48);
|
[$rString, $sString] = str_split($plainSignature, 48);
|
||||||
$sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16));
|
$sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16));
|
||||||
@ -149,7 +150,7 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
$this->authenticated = true; //we're signed into xbox live
|
$this->authenticated = true; //we're signed into xbox live
|
||||||
}
|
}
|
||||||
|
|
||||||
$claims = json_decode(self::b64UrlDecode($payloadB64), true);
|
$claims = json_decode(JwtUtils::b64UrlDecode($payloadB64), true);
|
||||||
|
|
||||||
$time = time();
|
$time = time();
|
||||||
if(isset($claims["nbf"]) and $claims["nbf"] > $time + self::CLOCK_DRIFT_MAX){
|
if(isset($claims["nbf"]) and $claims["nbf"] > $time + self::CLOCK_DRIFT_MAX){
|
||||||
@ -163,13 +164,6 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
$currentPublicKey = $claims["identityPublicKey"] ?? null; //if there are further links, the next link should be signed with this
|
$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{
|
public function onCompletion() : void{
|
||||||
/**
|
/**
|
||||||
* @var \Closure $callback
|
* @var \Closure $callback
|
||||||
|
@ -29,15 +29,14 @@ use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
|
|||||||
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
|
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
|
||||||
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
||||||
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
|
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
|
||||||
|
use pocketmine\network\mcpe\JwtUtils;
|
||||||
use function base64_encode;
|
use function base64_encode;
|
||||||
use function gmp_strval;
|
use function gmp_strval;
|
||||||
use function hex2bin;
|
use function hex2bin;
|
||||||
use function json_encode;
|
use function json_encode;
|
||||||
use function openssl_digest;
|
use function openssl_digest;
|
||||||
use function openssl_sign;
|
use function openssl_sign;
|
||||||
use function rtrim;
|
|
||||||
use function str_pad;
|
use function str_pad;
|
||||||
use function strtr;
|
|
||||||
|
|
||||||
final class EncryptionUtils{
|
final class EncryptionUtils{
|
||||||
|
|
||||||
@ -45,10 +44,6 @@ final class EncryptionUtils{
|
|||||||
//NOOP
|
//NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function b64UrlEncode(string $str) : string{
|
|
||||||
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function generateSharedSecret(PrivateKeyInterface $localPriv, PublicKeyInterface $remotePub) : \GMP{
|
public static function generateSharedSecret(PrivateKeyInterface $localPriv, PublicKeyInterface $remotePub) : \GMP{
|
||||||
return $localPriv->createExchange($remotePub)->calculateSharedKey();
|
return $localPriv->createExchange($remotePub)->calculateSharedKey();
|
||||||
}
|
}
|
||||||
@ -58,11 +53,11 @@ final class EncryptionUtils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{
|
public static function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{
|
||||||
$jwtBody = self::b64UrlEncode(json_encode([
|
$jwtBody = JwtUtils::b64UrlEncode(json_encode([
|
||||||
"x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())),
|
"x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())),
|
||||||
"alg" => "ES384"
|
"alg" => "ES384"
|
||||||
])
|
])
|
||||||
) . "." . self::b64UrlEncode(json_encode([
|
) . "." . JwtUtils::b64UrlEncode(json_encode([
|
||||||
"salt" => base64_encode($salt)
|
"salt" => base64_encode($salt)
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@ -70,11 +65,11 @@ final class EncryptionUtils{
|
|||||||
openssl_sign($jwtBody, $sig, (new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($serverPriv), OPENSSL_ALGO_SHA384);
|
openssl_sign($jwtBody, $sig, (new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($serverPriv), OPENSSL_ALGO_SHA384);
|
||||||
|
|
||||||
$decodedSig = (new DerSignatureSerializer())->parse($sig);
|
$decodedSig = (new DerSignatureSerializer())->parse($sig);
|
||||||
$jwtSig = self::b64UrlEncode(
|
$jwtSig = JwtUtils::b64UrlEncode(
|
||||||
hex2bin(str_pad(gmp_strval($decodedSig->getR(), 16), 96, "0", STR_PAD_LEFT)) .
|
hex2bin(str_pad(gmp_strval($decodedSig->getR(), 16), 96, "0", STR_PAD_LEFT)) .
|
||||||
hex2bin(str_pad(gmp_strval($decodedSig->getS(), 16), 96, "0", STR_PAD_LEFT))
|
hex2bin(str_pad(gmp_strval($decodedSig->getS(), 16), 96, "0", STR_PAD_LEFT))
|
||||||
);
|
);
|
||||||
|
|
||||||
return "$jwtBody.$jwtSig";
|
return "$jwtBody.$jwtSig";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,13 @@ namespace pocketmine\network\mcpe\protocol;
|
|||||||
|
|
||||||
#include <rules/DataPacket.h>
|
#include <rules/DataPacket.h>
|
||||||
|
|
||||||
|
use pocketmine\network\mcpe\JwtUtils;
|
||||||
use pocketmine\network\mcpe\protocol\serializer\NetworkBinaryStream;
|
use pocketmine\network\mcpe\protocol\serializer\NetworkBinaryStream;
|
||||||
use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
|
use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
|
||||||
use pocketmine\network\mcpe\protocol\types\login\ClientData;
|
use pocketmine\network\mcpe\protocol\types\login\ClientData;
|
||||||
use pocketmine\network\mcpe\protocol\types\login\JwtChain;
|
use pocketmine\network\mcpe\protocol\types\login\JwtChain;
|
||||||
use pocketmine\utils\BinaryDataException;
|
use pocketmine\utils\BinaryDataException;
|
||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
use pocketmine\utils\Utils;
|
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function json_decode;
|
use function json_decode;
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class LoginPacket extends DataPacket implements ServerboundPacket{
|
|||||||
foreach($this->chainDataJwt->chain as $k => $chain){
|
foreach($this->chainDataJwt->chain as $k => $chain){
|
||||||
//validate every chain element
|
//validate every chain element
|
||||||
try{
|
try{
|
||||||
$claims = Utils::getJwtClaims($chain);
|
$claims = JwtUtils::getClaims($chain);
|
||||||
}catch(\UnexpectedValueException $e){
|
}catch(\UnexpectedValueException $e){
|
||||||
throw new PacketDecodeException($e->getMessage(), 0, $e);
|
throw new PacketDecodeException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ class LoginPacket extends DataPacket implements ServerboundPacket{
|
|||||||
|
|
||||||
$this->clientDataJwt = $buffer->get($buffer->getLInt());
|
$this->clientDataJwt = $buffer->get($buffer->getLInt());
|
||||||
try{
|
try{
|
||||||
$clientData = Utils::getJwtClaims($this->clientDataJwt);
|
$clientData = JwtUtils::getClaims($this->clientDataJwt);
|
||||||
}catch(\UnexpectedValueException $e){
|
}catch(\UnexpectedValueException $e){
|
||||||
throw new PacketDecodeException($e->getMessage(), 0, $e);
|
throw new PacketDecodeException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
|
@ -361,30 +361,6 @@ class Utils{
|
|||||||
return $hash;
|
return $hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[] array of claims
|
|
||||||
* @phpstan-return array<string, mixed>
|
|
||||||
*
|
|
||||||
* @throws \UnexpectedValueException
|
|
||||||
*/
|
|
||||||
public static function getJwtClaims(string $token) : array{
|
|
||||||
$v = explode(".", $token);
|
|
||||||
if(count($v) !== 3){
|
|
||||||
throw new \UnexpectedValueException("Expected exactly 3 JWT parts, got " . count($v));
|
|
||||||
}
|
|
||||||
$payloadB64 = $v[1];
|
|
||||||
$payloadJSON = base64_decode(strtr($payloadB64, '-_', '+/'), true);
|
|
||||||
if($payloadJSON === false){
|
|
||||||
throw new \UnexpectedValueException("Invalid base64 JWT payload");
|
|
||||||
}
|
|
||||||
$result = json_decode($payloadJSON, true);
|
|
||||||
if(!is_array($result)){
|
|
||||||
throw new \UnexpectedValueException("Failed to decode JWT payload JSON: " . json_last_error_msg());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param object $value
|
* @param object $value
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user