mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-13 09:19:42 +00:00
Extract more general-purpose logic to JwtUtils
this code could now be reused for creating custom login packets.
This commit is contained in:
parent
161ac468f3
commit
d7eb4f9651
@ -23,17 +23,39 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\network\mcpe;
|
namespace pocketmine\network\mcpe;
|
||||||
|
|
||||||
|
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
|
||||||
|
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||||
|
use Mdanter\Ecc\Crypto\Signature\Signature;
|
||||||
|
use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
|
||||||
|
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
|
||||||
|
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
||||||
|
use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer;
|
||||||
|
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
|
||||||
|
use pocketmine\network\mcpe\auth\VerifyLoginException;
|
||||||
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use function base64_decode;
|
use function base64_decode;
|
||||||
use function base64_encode;
|
use function base64_encode;
|
||||||
|
use function bin2hex;
|
||||||
use function count;
|
use function count;
|
||||||
use function explode;
|
use function explode;
|
||||||
|
use function gmp_init;
|
||||||
|
use function gmp_strval;
|
||||||
|
use function hex2bin;
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function json_decode;
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
use function json_last_error_msg;
|
use function json_last_error_msg;
|
||||||
|
use function openssl_error_string;
|
||||||
|
use function openssl_sign;
|
||||||
|
use function openssl_verify;
|
||||||
use function rtrim;
|
use function rtrim;
|
||||||
|
use function str_pad;
|
||||||
use function str_repeat;
|
use function str_repeat;
|
||||||
|
use function str_split;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
use function strtr;
|
use function strtr;
|
||||||
|
use const OPENSSL_ALGO_SHA384;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
final class JwtUtils{
|
final class JwtUtils{
|
||||||
|
|
||||||
@ -62,6 +84,57 @@ final class JwtUtils{
|
|||||||
return [$header, $body, $signature];
|
return [$header, $body, $signature];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
*/
|
||||||
|
public static function verify(string $jwt, PublicKeyInterface $signingKey) : bool{
|
||||||
|
$parts = explode('.', $jwt);
|
||||||
|
if(count($parts) !== 3){
|
||||||
|
throw new \UnexpectedValueException("Expected exactly 3 JWT parts, got " . count($parts));
|
||||||
|
}
|
||||||
|
[$header, $body, $signature] = $parts;
|
||||||
|
|
||||||
|
$plainSignature = self::b64UrlDecode($signature);
|
||||||
|
if(strlen($plainSignature) !== 96){
|
||||||
|
throw new VerifyLoginException("JWT signature has unexpected length, expected 96, got " . strlen($plainSignature));
|
||||||
|
}
|
||||||
|
|
||||||
|
[$rString, $sString] = str_split($plainSignature, 48);
|
||||||
|
$sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16));
|
||||||
|
|
||||||
|
$v = openssl_verify(
|
||||||
|
$header . '.' . $body,
|
||||||
|
(new DerSignatureSerializer())->serialize($sig),
|
||||||
|
(new PemPublicKeySerializer(new DerPublicKeySerializer()))->serialize($signingKey),
|
||||||
|
OPENSSL_ALGO_SHA384
|
||||||
|
);
|
||||||
|
switch($v){
|
||||||
|
case 0: return false;
|
||||||
|
case 1: return true;
|
||||||
|
case -1: throw new \UnexpectedValueException("Error verifying JWT signature: " . openssl_error_string());
|
||||||
|
default: throw new AssumptionFailedError("openssl_verify() should only return -1, 0 or 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(array $header, array $claims, PrivateKeyInterface $signingKey) : string{
|
||||||
|
$jwtBody = JwtUtils::b64UrlEncode(json_encode($header)) . "." . JwtUtils::b64UrlEncode(json_encode($claims));
|
||||||
|
|
||||||
|
openssl_sign(
|
||||||
|
$jwtBody,
|
||||||
|
$sig,
|
||||||
|
(new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($signingKey),
|
||||||
|
OPENSSL_ALGO_SHA384
|
||||||
|
);
|
||||||
|
|
||||||
|
$decodedSig = (new DerSignatureSerializer())->parse($sig);
|
||||||
|
$jwtSig = JwtUtils::b64UrlEncode(
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
|
||||||
|
return "$jwtBody.$jwtSig";
|
||||||
|
}
|
||||||
|
|
||||||
public static function b64UrlEncode(string $str) : string{
|
public static function b64UrlEncode(string $str) : string{
|
||||||
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
|
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
|
||||||
}
|
}
|
||||||
|
@ -25,24 +25,12 @@ namespace pocketmine\network\mcpe\auth;
|
|||||||
|
|
||||||
use FG\ASN1\Exception\ParserException;
|
use FG\ASN1\Exception\ParserException;
|
||||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||||
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\Signature\DerSignatureSerializer;
|
|
||||||
use pocketmine\network\mcpe\JwtUtils;
|
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 pocketmine\utils\AssumptionFailedError;
|
|
||||||
use function base64_decode;
|
use function base64_decode;
|
||||||
use function bin2hex;
|
|
||||||
use function count;
|
|
||||||
use function explode;
|
|
||||||
use function gmp_init;
|
|
||||||
use function openssl_verify;
|
|
||||||
use function str_split;
|
|
||||||
use function strlen;
|
|
||||||
use function time;
|
use function time;
|
||||||
use const OPENSSL_ALGO_SHA384;
|
|
||||||
|
|
||||||
class ProcessLoginTask extends AsyncTask{
|
class ProcessLoginTask extends AsyncTask{
|
||||||
private const TLS_KEY_ON_COMPLETION = "completion";
|
private const TLS_KEY_ON_COMPLETION = "completion";
|
||||||
@ -117,7 +105,7 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
*/
|
*/
|
||||||
private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{
|
private function validateToken(string $jwt, ?string &$currentPublicKey, bool $first = false) : void{
|
||||||
try{
|
try{
|
||||||
[$headers, $claims, $plainSignature] = JwtUtils::parse($jwt);
|
[$headers, $claims, ] = JwtUtils::parse($jwt);
|
||||||
}catch(\UnexpectedValueException $e){
|
}catch(\UnexpectedValueException $e){
|
||||||
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), 0, $e);
|
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
@ -131,13 +119,6 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
$currentPublicKey = $headers["x5u"];
|
$currentPublicKey = $headers["x5u"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strlen($plainSignature) !== 96){
|
|
||||||
throw new VerifyLoginException("JWT signature has unexpected length, expected 96, got " . strlen($plainSignature));
|
|
||||||
}
|
|
||||||
|
|
||||||
[$rString, $sString] = str_split($plainSignature, 48);
|
|
||||||
$sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16));
|
|
||||||
|
|
||||||
$derPublicKeySerializer = new DerPublicKeySerializer();
|
$derPublicKeySerializer = new DerPublicKeySerializer();
|
||||||
$rawPublicKey = base64_decode($currentPublicKey, true);
|
$rawPublicKey = base64_decode($currentPublicKey, true);
|
||||||
if($rawPublicKey === false){
|
if($rawPublicKey === false){
|
||||||
@ -149,17 +130,12 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
throw new VerifyLoginException("Failed to parse DER public key: " . $e->getMessage(), 0, $e);
|
throw new VerifyLoginException("Failed to parse DER public key: " . $e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rawParts = explode('.', $jwt);
|
try{
|
||||||
if(count($rawParts) !== 3) throw new AssumptionFailedError("Parts count should be 3 as verified by JwtUtils::parse()");
|
if(!JwtUtils::verify($jwt, $signingKey)){
|
||||||
$v = openssl_verify(
|
throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.badSignature");
|
||||||
$rawParts[0] . '.' . $rawParts[1],
|
}
|
||||||
(new DerSignatureSerializer())->serialize($sig),
|
}catch(\UnexpectedValueException $e){
|
||||||
(new PemPublicKeySerializer($derPublicKeySerializer))->serialize($signingKey),
|
throw new VerifyLoginException($e->getMessage(), 0, $e);
|
||||||
OPENSSL_ALGO_SHA384
|
|
||||||
);
|
|
||||||
|
|
||||||
if($v !== 1){
|
|
||||||
throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.badSignature");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){
|
if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){
|
||||||
|
@ -25,17 +25,12 @@ namespace pocketmine\network\mcpe\encryption;
|
|||||||
|
|
||||||
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
|
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
|
||||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||||
use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
|
|
||||||
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 pocketmine\network\mcpe\JwtUtils;
|
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 openssl_digest;
|
use function openssl_digest;
|
||||||
use function openssl_sign;
|
|
||||||
use function str_pad;
|
use function str_pad;
|
||||||
|
|
||||||
final class EncryptionUtils{
|
final class EncryptionUtils{
|
||||||
@ -53,23 +48,15 @@ final class EncryptionUtils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{
|
public static function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{
|
||||||
$jwtBody = JwtUtils::b64UrlEncode(json_encode([
|
return JwtUtils::create(
|
||||||
"x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())),
|
[
|
||||||
"alg" => "ES384"
|
"x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())),
|
||||||
])
|
"alg" => "ES384"
|
||||||
) . "." . JwtUtils::b64UrlEncode(json_encode([
|
],
|
||||||
"salt" => base64_encode($salt)
|
[
|
||||||
])
|
"salt" => base64_encode($salt)
|
||||||
);
|
],
|
||||||
|
$serverPriv
|
||||||
openssl_sign($jwtBody, $sig, (new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($serverPriv), OPENSSL_ALGO_SHA384);
|
|
||||||
|
|
||||||
$decodedSig = (new DerSignatureSerializer())->parse($sig);
|
|
||||||
$jwtSig = JwtUtils::b64UrlEncode(
|
|
||||||
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))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return "$jwtBody.$jwtSig";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user