From c277aababc25cd1b42217a23b4a310df99c2fa73 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 23 Oct 2022 21:26:27 +0200 Subject: [PATCH] JWT introduce other algorithms --- core/Api/MailAPI.class.php | 2 +- core/Configuration/Settings.class.php | 96 +++++++++++++++---- core/Driver/SQL/MySQL.class.php | 2 +- core/Objects/Context.class.php | 2 +- core/Objects/DatabaseEntity/Session.class.php | 4 +- 5 files changed, 85 insertions(+), 21 deletions(-) diff --git a/core/Api/MailAPI.class.php b/core/Api/MailAPI.class.php index f2895f9..222f2f5 100644 --- a/core/Api/MailAPI.class.php +++ b/core/Api/MailAPI.class.php @@ -72,7 +72,7 @@ namespace Api\Mail { "subject" => "Test E-Mail", "body" => "Hey! If you receive this e-mail, your mail configuration seems to be working.", "gpgFingerprint" => $this->getParam("gpgFingerprint"), - "asnyc" => false + "async" => false )); $this->lastError = $req->getLastError(); diff --git a/core/Configuration/Settings.class.php b/core/Configuration/Settings.class.php index 1e9ee2a..8455ef7 100644 --- a/core/Configuration/Settings.class.php +++ b/core/Configuration/Settings.class.php @@ -6,6 +6,7 @@ namespace Configuration; +use Driver\Logger\Logger; use Driver\SQL\Query\Insert; use Objects\Context; @@ -17,7 +18,9 @@ class Settings { // settings private string $siteName; private string $baseUrl; - private string $jwtSecret; + private string $jwtPublicKey; + private string $jwtSecretKey; + private string $jwtAlgorithm; private bool $registrationAllowed; private bool $recaptchaEnabled; private bool $mailEnabled; @@ -27,9 +30,19 @@ class Settings { private string $mailFooter; private array $allowedExtensions; - public function getJwtKey(): \Firebase\JWT\Key { - // TODO: allow the use of other JWT algorithms (e.g. RS256) - return new \Firebase\JWT\Key($this->jwtSecret, "HS256"); + // + private Logger $logger; + + public function __construct() { + $this->logger = new Logger("Settings"); + } + + public function getJwtPublicKey(): \Firebase\JWT\Key { + return new \Firebase\JWT\Key($this->jwtPublicKey ?? $this->jwtSecretKey, $this->jwtAlgorithm); + } + + public function getJwtSecretKey(): \Firebase\JWT\Key { + return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm); } public function isInstalled(): bool { @@ -39,26 +52,74 @@ class Settings { public static function loadDefaults(): Settings { $hostname = $_SERVER["SERVER_NAME"] ?? "localhost"; $protocol = getProtocol(); - $jwt = generateRandomString(32); - $settings = new Settings(); + + // General $settings->siteName = "WebBase"; $settings->baseUrl = "$protocol://$hostname"; - $settings->jwtSecret = $jwt; + $settings->allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'htm', 'html']; $settings->installationComplete = false; $settings->registrationAllowed = false; + + // JWT + $settings->jwtSecretKey = null; + $settings->jwtPublicKey = null; + $settings->jwtAlgorithm = "HS256"; + + // Recaptcha + $settings->recaptchaEnabled = false; $settings->recaptchaPublicKey = ""; $settings->recaptchaPrivateKey = ""; - $settings->recaptchaEnabled = false; + + // Mail $settings->mailEnabled = false; $settings->mailSender = "webmaster@localhost"; $settings->mailFooter = ""; - $settings->allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'htm', 'html']; + return $settings; } + public function generateJwtKey(string $algorithm = null): bool { + $this->jwtAlgorithm = $algorithm ?? $this->jwtAlgorithm; + + // TODO: key encryption necessary? + if (in_array($this->jwtAlgorithm, ["HS256", "HS384", "HS512"])) { + $this->jwtSecretKey = generateRandomString(32); + $this->jwtPublicKey = null; + } else if (in_array($this->jwtAlgorithm, ["RS256", "RS384", "RS512"])) { + $bits = intval(substr($this->jwtAlgorithm, 2)); + $private_key = openssl_pkey_new(["private_key_bits" => $bits]); + $this->jwtPublicKey = openssl_pkey_get_details($private_key)['key']; + openssl_pkey_export($private_key, $this->jwtSecretKey); + } else if (in_array($this->jwtAlgorithm, ["ES256", "ES384"])) { + // $ec = new \Elliptic\EC('secp256k1'); ?? + $this->logger->error("JWT algorithm: '$this->jwtAlgorithm' is currently not supported."); + return false; + } else if ($this->jwtAlgorithm == "EdDSA") { + $keyPair = sodium_crypto_sign_keypair(); + $this->jwtSecretKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); + $this->jwtPublicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); + } else { + $this->logger->error("Invalid JWT algorithm: '$this->jwtAlgorithm', expected one of: " . + implode(",", array_keys(\Firebase\JWT\JWT::$supported_algs))); + return false; + } + + return true; + } + + public function saveJwtKey(Context $context) { + $req = new \Api\Settings\Set($context); + $req->execute(array("settings" => array( + "jwt_secret_key" => $this->jwtSecretKey, + "jwt_public_key" => $this->jwtSecretKey, + "jwt_algorithm" => $this->jwtAlgorithm, + ))); + } + public function loadFromDatabase(Context $context): bool { + $this->logger = new Logger("Settings", $context->getSQL()); $req = new \Api\Settings\Get($context); $success = $req->execute(); @@ -68,7 +129,9 @@ class Settings { $this->baseUrl = $result["base_url"] ?? $this->baseUrl; $this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed; $this->installationComplete = $result["installation_completed"] ?? $this->installationComplete; - $this->jwtSecret = $result["jwt_secret"] ?? $this->jwtSecret; + $this->jwtSecretKey = $result["jwt_secret_key"] ?? $this->jwtSecretKey; + $this->jwtPublicKey = $result["jwt_public_key"] ?? $this->jwtPublicKey; + $this->jwtAlgorithm = $result["jwt_algorithm"] ?? $this->jwtAlgorithm; $this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled; $this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey; $this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey; @@ -77,11 +140,10 @@ class Settings { $this->mailFooter = $result["mail_footer"] ?? $this->mailFooter; $this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions))); - if (!isset($result["jwt_secret"])) { - $req = new \Api\Settings\Set($context); - $req->execute(array("settings" => array( - "jwt_secret" => $this->jwtSecret - ))); + if (!isset($result["jwt_secret_key"])) { + if ($this->generateJwtKey()) { + $this->saveJwtKey($context); + } } } @@ -93,7 +155,9 @@ class Settings { ->addRow("base_url", $this->baseUrl, false, false) ->addRow("user_registration_enabled", $this->registrationAllowed ? "1" : "0", false, false) ->addRow("installation_completed", $this->installationComplete ? "1" : "0", true, true) - ->addRow("jwt_secret", $this->jwtSecret, true, true) + ->addRow("jwt_secret_key", $this->jwtSecretKey, true, false) + ->addRow("jwt_public_key", $this->jwtPublicKey, false, false) + ->addRow("jwt_algorithm", $this->jwtAlgorithm, false, false) ->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false) ->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false) ->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false) diff --git a/core/Driver/SQL/MySQL.class.php b/core/Driver/SQL/MySQL.class.php index fff0bb6..0c27c00 100644 --- a/core/Driver/SQL/MySQL.class.php +++ b/core/Driver/SQL/MySQL.class.php @@ -65,7 +65,7 @@ class MySQL extends SQL { return false; } - mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding', 'UTF-8')); + mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding', 'UTF8')); return true; } diff --git a/core/Objects/Context.class.php b/core/Objects/Context.class.php index 68a25cc..1178ec0 100644 --- a/core/Objects/Context.class.php +++ b/core/Objects/Context.class.php @@ -99,7 +99,7 @@ class Context { try { $token = $_COOKIE['session']; $settings = $this->configuration->getSettings(); - $decoded = (array)JWT::decode($token, $settings->getJwtKey()); + $decoded = (array)JWT::decode($token, $settings->getJwtSecretKey()); if (!is_null($decoded)) { $userId = ($decoded['userId'] ?? NULL); $sessionId = ($decoded['sessionId'] ?? NULL); diff --git a/core/Objects/DatabaseEntity/Session.class.php b/core/Objects/DatabaseEntity/Session.class.php index 400376e..2f16c32 100644 --- a/core/Objects/DatabaseEntity/Session.class.php +++ b/core/Objects/DatabaseEntity/Session.class.php @@ -74,8 +74,8 @@ class Session extends DatabaseEntity { $this->updateMetaData(); $settings = $this->context->getSettings(); $token = ['userId' => $this->user->getId(), 'sessionId' => $this->getId()]; - $jwtKey = $settings->getJwtKey(); - return JWT::encode($token, $jwtKey->getKeyMaterial(), $jwtKey->getAlgorithm()); + $jwtPublicKey = $settings->getJwtPublicKey(); + return JWT::encode($token, $jwtPublicKey->getKeyMaterial(), $jwtPublicKey->getAlgorithm()); } public function sendCookie(string $domain) {