mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-05 01:16:15 +00:00
Use OpenSSL for ECDH during client login, drop mdanter/ecc (#4328)
This brings a significant performance improvement to login sequence handling, reducing CPU cost of `PrepareEncryptionTask` by over 90% and `ProcessLoginTask` by over 60%. It also allows us to shed a dependency.
This commit is contained in:
@ -23,9 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\auth;
|
||||
|
||||
use FG\ASN1\Exception\ParserException;
|
||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
||||
use pocketmine\lang\KnownTranslationKeys;
|
||||
use pocketmine\network\mcpe\JwtException;
|
||||
use pocketmine\network\mcpe\JwtUtils;
|
||||
@ -35,6 +32,8 @@ use pocketmine\scheduler\AsyncTask;
|
||||
use function base64_decode;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
use function openssl_error_string;
|
||||
use function openssl_free_key;
|
||||
use function time;
|
||||
|
||||
class ProcessLoginTask extends AsyncTask{
|
||||
@ -65,12 +64,12 @@ class ProcessLoginTask extends AsyncTask{
|
||||
/** @var bool */
|
||||
private $authRequired;
|
||||
|
||||
/** @var PublicKeyInterface|null */
|
||||
/** @var string|null */
|
||||
private $clientPublicKey = null;
|
||||
|
||||
/**
|
||||
* @param string[] $chainJwts
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, ?string $error, ?PublicKeyInterface $clientPublicKey) : void $onCompletion
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPublicKey) : void $onCompletion
|
||||
*/
|
||||
public function __construct(array $chainJwts, string $clientDataJwt, bool $authRequired, \Closure $onCompletion){
|
||||
$this->storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion);
|
||||
@ -88,7 +87,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
}
|
||||
}
|
||||
|
||||
private function validateChain() : PublicKeyInterface{
|
||||
private function validateChain() : string{
|
||||
/** @var string[] $chain */
|
||||
$chain = igbinary_unserialize($this->chain);
|
||||
|
||||
@ -107,7 +106,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
|
||||
$this->validateToken($this->clientDataJwt, $currentKey);
|
||||
|
||||
return (new DerPublicKeySerializer())->parse(base64_decode($clientKey, true));
|
||||
return $clientKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,38 +131,36 @@ class ProcessLoginTask extends AsyncTask{
|
||||
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$headerDerKey = base64_decode($headers->x5u, true);
|
||||
if($headerDerKey === false){
|
||||
throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u");
|
||||
}
|
||||
|
||||
if($currentPublicKey === null){
|
||||
if(!$first){
|
||||
throw new VerifyLoginException("%" . KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_MISSINGKEY);
|
||||
}
|
||||
|
||||
//First link, check that it is self-signed
|
||||
$currentPublicKey = $headers->x5u;
|
||||
}elseif($headers->x5u !== $currentPublicKey){
|
||||
}elseif($headerDerKey !== $currentPublicKey){
|
||||
//Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway
|
||||
throw new VerifyLoginException("%" . KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE);
|
||||
}
|
||||
|
||||
$derPublicKeySerializer = new DerPublicKeySerializer();
|
||||
$rawPublicKey = base64_decode($currentPublicKey, true);
|
||||
if($rawPublicKey === false){
|
||||
throw new VerifyLoginException("Failed to decode base64'd public key");
|
||||
try{
|
||||
$signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey);
|
||||
}catch(JwtException $e){
|
||||
throw new VerifyLoginException("Invalid JWT public key: " . openssl_error_string());
|
||||
}
|
||||
try{
|
||||
$signingKey = $derPublicKeySerializer->parse($rawPublicKey);
|
||||
}catch(\RuntimeException | ParserException $e){
|
||||
throw new VerifyLoginException("Failed to parse DER public key: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
try{
|
||||
if(!JwtUtils::verify($jwt, $signingKey)){
|
||||
if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){
|
||||
throw new VerifyLoginException("%" . KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE);
|
||||
}
|
||||
}catch(JwtException $e){
|
||||
throw new VerifyLoginException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){
|
||||
openssl_free_key($signingKeyOpenSSL);
|
||||
|
||||
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){
|
||||
$this->authenticated = true; //we're signed into xbox live
|
||||
}
|
||||
|
||||
@ -188,13 +185,19 @@ class ProcessLoginTask extends AsyncTask{
|
||||
throw new VerifyLoginException("%" . KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_TOOLATE);
|
||||
}
|
||||
|
||||
$currentPublicKey = $claims->identityPublicKey ?? null; //if there are further links, the next link should be signed with this
|
||||
if(isset($claims->identityPublicKey)){
|
||||
$identityPublicKey = base64_decode($claims->identityPublicKey, true);
|
||||
if($identityPublicKey === false){
|
||||
throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding");
|
||||
}
|
||||
$currentPublicKey = $identityPublicKey; //if there are further links, the next link should be signed with this
|
||||
}
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
/**
|
||||
* @var \Closure $callback
|
||||
* @phpstan-var \Closure(bool, bool, ?string, ?PublicKeyInterface) : void $callback
|
||||
* @phpstan-var \Closure(bool, bool, ?string, ?string) : void $callback
|
||||
*/
|
||||
$callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION);
|
||||
$callback($this->authenticated, $this->authRequired, $this->error, $this->clientPublicKey);
|
||||
|
Reference in New Issue
Block a user