mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-06 11:57:10 +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:
parent
83016a97bd
commit
0eb4231b51
@ -32,7 +32,7 @@
|
||||
"ext-zlib": ">=1.2.11",
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "^1.1",
|
||||
"mdanter/ecc": "^1.0",
|
||||
"fgrosse/phpasn1": "^2.3",
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"pocketmine/bedrock-protocol": "dev-master#88ae308a03e8e61ccfdddd42623efabe9e772b42",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
|
78
composer.lock
generated
78
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7c8ebad0871e3c90e8894476e3c5b925",
|
||||
"content-hash": "49005f17832ef5949b4a2ac04cd1ee93",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -192,82 +192,6 @@
|
||||
},
|
||||
"time": "2021-04-24T19:01:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mdanter/ecc",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpecc/phpecc.git",
|
||||
"reference": "34e2eec096bf3dcda814e8f66dd91ae87a2db7cd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpecc/phpecc/zipball/34e2eec096bf3dcda814e8f66dd91ae87a2db7cd",
|
||||
"reference": "34e2eec096bf3dcda814e8f66dd91ae87a2db7cd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gmp": "*",
|
||||
"fgrosse/phpasn1": "^2.0",
|
||||
"php": "^7.0||^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0||^8.0||^9.0",
|
||||
"squizlabs/php_codesniffer": "^2.0",
|
||||
"symfony/yaml": "^2.6|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mdanter\\Ecc\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matyas Danter",
|
||||
"homepage": "http://matejdanter.com/",
|
||||
"role": "Author"
|
||||
},
|
||||
{
|
||||
"name": "Thibaud Fabre",
|
||||
"email": "thibaud@aztech.io",
|
||||
"homepage": "http://aztech.io",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Thomas Kerin",
|
||||
"email": "afk11@users.noreply.github.com",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "PHP Elliptic Curve Cryptography library",
|
||||
"homepage": "https://github.com/phpecc/phpecc",
|
||||
"keywords": [
|
||||
"Diffie",
|
||||
"ECDSA",
|
||||
"Hellman",
|
||||
"curve",
|
||||
"ecdh",
|
||||
"elliptic",
|
||||
"nistp192",
|
||||
"nistp224",
|
||||
"nistp256",
|
||||
"nistp384",
|
||||
"nistp521",
|
||||
"phpecc",
|
||||
"secp256k1",
|
||||
"secp256r1"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpecc/phpecc/issues",
|
||||
"source": "https://github.com/phpecc/phpecc/tree/v1.0.0"
|
||||
},
|
||||
"time": "2021-01-16T19:42:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "netresearch/jsonmapper",
|
||||
"version": "v4.0.0",
|
||||
|
@ -46,6 +46,7 @@ parameters:
|
||||
- tests/phpstan/stubs/JsonMapper.stub
|
||||
- tests/phpstan/stubs/pthreads.stub
|
||||
- tests/phpstan/stubs/leveldb.stub
|
||||
- tests/phpstan/stubs/phpasn1.stub
|
||||
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
|
||||
staticReflectionClassNamePatterns:
|
||||
- "#^COM$#"
|
||||
|
@ -23,36 +23,39 @@ declare(strict_types=1);
|
||||
|
||||
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 FG\ASN1\Exception\ParserException;
|
||||
use FG\ASN1\Universal\Integer;
|
||||
use FG\ASN1\Universal\Sequence;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function base64_decode;
|
||||
use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function gmp_export;
|
||||
use function gmp_import;
|
||||
use function gmp_init;
|
||||
use function gmp_strval;
|
||||
use function hex2bin;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function json_last_error_msg;
|
||||
use function openssl_error_string;
|
||||
use function openssl_pkey_get_details;
|
||||
use function openssl_pkey_get_public;
|
||||
use function openssl_sign;
|
||||
use function openssl_verify;
|
||||
use function preg_match;
|
||||
use function rtrim;
|
||||
use function sprintf;
|
||||
use function str_pad;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function str_split;
|
||||
use function strlen;
|
||||
use function strtr;
|
||||
use const GMP_BIG_ENDIAN;
|
||||
use const GMP_MSW_FIRST;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
@ -94,9 +97,11 @@ final class JwtUtils{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $signingKey
|
||||
*
|
||||
* @throws JwtException
|
||||
*/
|
||||
public static function verify(string $jwt, PublicKeyInterface $signingKey) : bool{
|
||||
public static function verify(string $jwt, $signingKey) : bool{
|
||||
[$header, $body, $signature] = self::split($jwt);
|
||||
|
||||
$plainSignature = self::b64UrlDecode($signature);
|
||||
@ -105,12 +110,17 @@ final class JwtUtils{
|
||||
}
|
||||
|
||||
[$rString, $sString] = str_split($plainSignature, 48);
|
||||
$sig = new Signature(gmp_init(bin2hex($rString), 16), gmp_init(bin2hex($sString), 16));
|
||||
$convert = fn(string $str) => gmp_strval(gmp_import($str, 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), 10);
|
||||
|
||||
$sequence = new Sequence(
|
||||
new Integer($convert($rString)),
|
||||
new Integer($convert($sString))
|
||||
);
|
||||
|
||||
$v = openssl_verify(
|
||||
$header . '.' . $body,
|
||||
(new DerSignatureSerializer())->serialize($sig),
|
||||
(new PemPublicKeySerializer(new DerPublicKeySerializer()))->serialize($signingKey),
|
||||
$sequence->getBinary(),
|
||||
$signingKey,
|
||||
OPENSSL_ALGO_SHA384
|
||||
);
|
||||
switch($v){
|
||||
@ -122,24 +132,43 @@ final class JwtUtils{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $signingKey
|
||||
*
|
||||
* @phpstan-param array<string, mixed> $header
|
||||
* @phpstan-param array<string, mixed> $claims
|
||||
*/
|
||||
public static function create(array $header, array $claims, PrivateKeyInterface $signingKey) : string{
|
||||
$jwtBody = JwtUtils::b64UrlEncode(json_encode($header)) . "." . JwtUtils::b64UrlEncode(json_encode($claims));
|
||||
public static function create(array $header, array $claims, $signingKey) : string{
|
||||
$jwtBody = JwtUtils::b64UrlEncode(json_encode($header, JSON_THROW_ON_ERROR)) . "." . JwtUtils::b64UrlEncode(json_encode($claims, JSON_THROW_ON_ERROR));
|
||||
|
||||
openssl_sign(
|
||||
$jwtBody,
|
||||
$sig,
|
||||
(new PemPrivateKeySerializer(new DerPrivateKeySerializer()))->serialize($signingKey),
|
||||
$rawDerSig,
|
||||
$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))
|
||||
try{
|
||||
$asnObject = Sequence::fromBinary($rawDerSig);
|
||||
}catch(ParserException $e){
|
||||
throw new AssumptionFailedError("Failed to parse OpenSSL signature: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
if(count($asnObject) !== 2){
|
||||
throw new AssumptionFailedError("OpenSSL produced invalid signature, expected exactly 2 parts");
|
||||
}
|
||||
[$r, $s] = [$asnObject[0], $asnObject[1]];
|
||||
if(!($r instanceof Integer) || !($s instanceof Integer)){
|
||||
throw new AssumptionFailedError("OpenSSL produced invalid signature, expected 2 INTEGER parts");
|
||||
}
|
||||
$rString = $r->getContent();
|
||||
$sString = $s->getContent();
|
||||
|
||||
$toBinary = fn($str) => str_pad(
|
||||
gmp_export(gmp_init($str, 10), 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST),
|
||||
48,
|
||||
"\x00",
|
||||
STR_PAD_LEFT
|
||||
);
|
||||
$jwtSig = JwtUtils::b64UrlEncode($toBinary($rString) . $toBinary($sString));
|
||||
|
||||
return "$jwtBody.$jwtSig";
|
||||
}
|
||||
@ -158,4 +187,35 @@ final class JwtUtils{
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $opensslKey
|
||||
*/
|
||||
public static function emitDerPublicKey($opensslKey) : string{
|
||||
$details = openssl_pkey_get_details($opensslKey);
|
||||
if($details === false){
|
||||
throw new AssumptionFailedError("Failed to get details from OpenSSL key resource");
|
||||
}
|
||||
|
||||
/** @var string $pemKey */
|
||||
$pemKey = $details['key'];
|
||||
if(preg_match("@^-----BEGIN[A-Z\d ]+PUBLIC KEY-----\n([A-Za-z\d+/\n]+)\n-----END[A-Z\d ]+PUBLIC KEY-----\n$@", $pemKey, $matches) === 1){
|
||||
$derKey = base64_decode(str_replace("\n", "", $matches[1]), true);
|
||||
if($derKey !== false){
|
||||
return $derKey;
|
||||
}
|
||||
}
|
||||
throw new AssumptionFailedError("OpenSSL resource contains invalid public key");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public static function parseDerPublicKey(string $derKey){
|
||||
$signingKeyOpenSSL = openssl_pkey_get_public(sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", base64_encode($derKey)));
|
||||
if($signingKeyOpenSSL === false){
|
||||
throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string());
|
||||
}
|
||||
return $signingKeyOpenSSL;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||
use pocketmine\data\bedrock\EffectIdMap;
|
||||
use pocketmine\entity\Attribute;
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
@ -203,7 +202,7 @@ class NetworkSession{
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?PublicKeyInterface $clientPubKey) : void{
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
}
|
||||
));
|
||||
@ -570,7 +569,7 @@ class NetworkSession{
|
||||
}, $reason);
|
||||
}
|
||||
|
||||
private function setAuthenticationStatus(bool $authenticated, bool $authRequired, ?string $error, ?PublicKeyInterface $clientPubKey) : void{
|
||||
private function setAuthenticationStatus(bool $authenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
if(!$this->connected){
|
||||
return;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -23,14 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\encryption;
|
||||
|
||||
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
|
||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
|
||||
use pocketmine\network\mcpe\JwtUtils;
|
||||
use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function gmp_init;
|
||||
use function gmp_strval;
|
||||
use function hex2bin;
|
||||
use function openssl_digest;
|
||||
use function openssl_error_string;
|
||||
use function openssl_pkey_derive;
|
||||
use function str_pad;
|
||||
|
||||
final class EncryptionUtils{
|
||||
@ -39,18 +40,30 @@ final class EncryptionUtils{
|
||||
//NOOP
|
||||
}
|
||||
|
||||
public static function generateSharedSecret(PrivateKeyInterface $localPriv, PublicKeyInterface $remotePub) : \GMP{
|
||||
return $localPriv->createExchange($remotePub)->calculateSharedKey();
|
||||
/**
|
||||
* @param resource $localPriv
|
||||
* @param resource $remotePub
|
||||
*/
|
||||
public static function generateSharedSecret($localPriv, $remotePub) : \GMP{
|
||||
$hexSecret = openssl_pkey_derive($remotePub, $localPriv, 48);
|
||||
if($hexSecret === false){
|
||||
throw new \InvalidArgumentException("Failed to derive shared secret: " . openssl_error_string());
|
||||
}
|
||||
return gmp_init(bin2hex($hexSecret), 16);
|
||||
}
|
||||
|
||||
public static function generateKey(\GMP $secret, string $salt) : string{
|
||||
return openssl_digest($salt . hex2bin(str_pad(gmp_strval($secret, 16), 96, "0", STR_PAD_LEFT)), 'sha256', true);
|
||||
}
|
||||
|
||||
public static function generateServerHandshakeJwt(PrivateKeyInterface $serverPriv, string $salt) : string{
|
||||
/**
|
||||
* @param resource $serverPriv
|
||||
*/
|
||||
public static function generateServerHandshakeJwt($serverPriv, string $salt) : string{
|
||||
$derPublicKey = JwtUtils::emitDerPublicKey($serverPriv);
|
||||
return JwtUtils::create(
|
||||
[
|
||||
"x5u" => base64_encode((new DerPublicKeySerializer())->serialize($serverPriv->getPublicKey())),
|
||||
"x5u" => base64_encode($derPublicKey),
|
||||
"alg" => "ES384"
|
||||
],
|
||||
[
|
||||
|
@ -23,50 +23,65 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\encryption;
|
||||
|
||||
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
|
||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||
use Mdanter\Ecc\EccFactory;
|
||||
use pocketmine\network\mcpe\JwtUtils;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
use function openssl_error_string;
|
||||
use function openssl_free_key;
|
||||
use function openssl_pkey_get_details;
|
||||
use function openssl_pkey_new;
|
||||
use function random_bytes;
|
||||
|
||||
class PrepareEncryptionTask extends AsyncTask{
|
||||
|
||||
private const TLS_KEY_ON_COMPLETION = "completion";
|
||||
|
||||
/** @var PrivateKeyInterface|null */
|
||||
/** @var resource|null */
|
||||
private static $SERVER_PRIVATE_KEY = null;
|
||||
|
||||
/** @var PrivateKeyInterface */
|
||||
/** @var string */
|
||||
private $serverPrivateKey;
|
||||
|
||||
/** @var string|null */
|
||||
private $aesKey = null;
|
||||
/** @var string|null */
|
||||
private $handshakeJwt = null;
|
||||
/** @var PublicKeyInterface */
|
||||
/** @var string */
|
||||
private $clientPub;
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(string $encryptionKey, string $handshakeJwt) : void $onCompletion
|
||||
*/
|
||||
public function __construct(PublicKeyInterface $clientPub, \Closure $onCompletion){
|
||||
public function __construct(string $clientPub, \Closure $onCompletion){
|
||||
if(self::$SERVER_PRIVATE_KEY === null){
|
||||
self::$SERVER_PRIVATE_KEY = EccFactory::getNistCurves()->generator384()->createPrivateKey();
|
||||
$serverPrivateKey = openssl_pkey_new(["ec" => ["curve_name" => "secp384r1"]]);
|
||||
if($serverPrivateKey === false){
|
||||
throw new \RuntimeException("openssl_pkey_new() failed: " . openssl_error_string());
|
||||
}
|
||||
self::$SERVER_PRIVATE_KEY = $serverPrivateKey;
|
||||
}
|
||||
|
||||
$this->serverPrivateKey = self::$SERVER_PRIVATE_KEY;
|
||||
$this->serverPrivateKey = igbinary_serialize(openssl_pkey_get_details(self::$SERVER_PRIVATE_KEY));
|
||||
$this->clientPub = $clientPub;
|
||||
$this->storeLocal(self::TLS_KEY_ON_COMPLETION, $onCompletion);
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
$serverPriv = $this->serverPrivateKey;
|
||||
$sharedSecret = EncryptionUtils::generateSharedSecret($serverPriv, $this->clientPub);
|
||||
/** @var mixed[] $serverPrivDetails */
|
||||
$serverPrivDetails = igbinary_unserialize($this->serverPrivateKey);
|
||||
$serverPriv = openssl_pkey_new($serverPrivDetails);
|
||||
if($serverPriv === false) throw new AssumptionFailedError("Failed to restore server signing key from details");
|
||||
$clientPub = JwtUtils::parseDerPublicKey($this->clientPub);
|
||||
$sharedSecret = EncryptionUtils::generateSharedSecret($serverPriv, $clientPub);
|
||||
|
||||
$salt = random_bytes(16);
|
||||
$this->aesKey = EncryptionUtils::generateKey($sharedSecret, $salt);
|
||||
$this->handshakeJwt = EncryptionUtils::generateServerHandshakeJwt($serverPriv, $salt);
|
||||
|
||||
openssl_free_key($serverPriv);
|
||||
openssl_free_key($clientPub);
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
|
||||
use pocketmine\entity\InvalidSkinException;
|
||||
use pocketmine\event\player\PlayerPreLoginEvent;
|
||||
use pocketmine\lang\KnownTranslationKeys;
|
||||
@ -63,13 +62,13 @@ class LoginPacketHandler extends PacketHandler{
|
||||
private $playerInfoConsumer;
|
||||
/**
|
||||
* @var \Closure
|
||||
* @phpstan-var \Closure(bool, bool, ?string, ?PublicKeyInterface) : void
|
||||
* @phpstan-var \Closure(bool, bool, ?string, ?string) : void
|
||||
*/
|
||||
private $authCallback;
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(PlayerInfo) : void $playerInfoConsumer
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, ?string $error, ?PublicKeyInterface $clientPubKey) : void $authCallback
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void $authCallback
|
||||
*/
|
||||
public function __construct(Server $server, NetworkSession $session, \Closure $playerInfoConsumer, \Closure $authCallback){
|
||||
$this->session = $session;
|
||||
@ -78,10 +77,6 @@ class LoginPacketHandler extends PacketHandler{
|
||||
$this->authCallback = $authCallback;
|
||||
}
|
||||
|
||||
private static function dummy() : void{
|
||||
echo PublicKeyInterface::class; //this prevents the import getting removed by tools that don't understand phpstan
|
||||
}
|
||||
|
||||
public function handleLogin(LoginPacket $packet) : bool{
|
||||
if(!$this->isCompatibleProtocol($packet->protocol)){
|
||||
$this->session->sendDataPacket(PlayStatusPacket::create($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
|
||||
|
@ -136,7 +136,7 @@ parameters:
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$clientPub of class pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask constructor expects Mdanter\\\\Ecc\\\\Crypto\\\\Key\\\\PublicKeyInterface, Mdanter\\\\Ecc\\\\Crypto\\\\Key\\\\PublicKeyInterface\\|null given\\.$#"
|
||||
message: "#^Parameter \\#1 \\$clientPub of class pocketmine\\\\network\\\\mcpe\\\\encryption\\\\PrepareEncryptionTask constructor expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
|
22
tests/phpstan/stubs/phpasn1.stub
Normal file
22
tests/phpstan/stubs/phpasn1.stub
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the PHPASN1 library.
|
||||
*
|
||||
* Copyright © Friedrich Große <friedrich.grosse@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace FG\ASN1\Universal;
|
||||
|
||||
class Integer
|
||||
{
|
||||
/**
|
||||
* @param int|string $value
|
||||
*/
|
||||
public function __construct($value){}
|
||||
|
||||
/** @return int|string */
|
||||
public function getContent(){}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user