summaryrefslogtreecommitdiff
path: root/classes/Sessions.php
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@fakecake.org>2025-04-07 20:23:19 +0300
committerAndrew Dolgov <fox@fakecake.org>2025-04-07 20:28:35 +0300
commit58677fc791604bd891fb1ef4f4cc5e040ce8e39f (patch)
treeaf864af258bbda958b26ade6a444487f3bf7a1cf /classes/Sessions.php
parent026d68fc2d0f24e4f2d46c5743a22f42053caa67 (diff)
rename SODIUM_ENCRYPTION_KEY to SESSION_ENCRYPTION_KEY and move related stuff to Sessions class
Diffstat (limited to 'classes/Sessions.php')
-rw-r--r--classes/Sessions.php54
1 files changed, 49 insertions, 5 deletions
diff --git a/classes/Sessions.php b/classes/Sessions.php
index e8cba1765..d8f14eed0 100644
--- a/classes/Sessions.php
+++ b/classes/Sessions.php
@@ -8,6 +8,8 @@ class Sessions implements \SessionHandlerInterface {
private int $session_expire;
private string $session_name;
+ private const SODIUM_ALGO = 'xchacha20poly1305_ietf';
+
public function __construct() {
$this->session_expire = min(2147483647 - time() - 1, Config::get(Config::SESSION_COOKIE_LIFETIME));
$this->session_name = Config::get(Config::SESSION_NAME);
@@ -53,6 +55,48 @@ class Sessions implements \SessionHandlerInterface {
return true;
}
+ /** encrypts provided ciphertext using Sodium symmetric encryption key if available via Config::SESSION_ENCRYPTION_KEY
+ *
+ * @return array<string,mixed> encrypted data object containing algo, nonce, and encrypted data
+ *
+ */
+ private function encrypt_string(string $ciphertext) : array {
+ $key = Config::get(Config::SESSION_ENCRYPTION_KEY);
+ $nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
+
+ $payload = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($ciphertext, '', $nonce, hex2bin($key));
+
+ if ($payload) {
+ $encrypted_data = [
+ 'algo' => self::SODIUM_ALGO,
+ 'nonce' => $nonce,
+ 'payload' => $payload,
+ ];
+
+ return $encrypted_data;
+ }
+
+ throw new Exception("Config::encrypt_string() failed to encrypt ciphertext");
+ }
+
+ /** decrypts payload of encrypted object if Config::SESSION_ENCRYPTION_KEY is available and object is in correct format
+ *
+ * @param array<string,mixed> $encrypted_data
+ *
+ * @return string decrypted string payload
+ */
+ private function decrypt_string(array $encrypted_data) : string {
+ $key = Config::get(Config::SESSION_ENCRYPTION_KEY);
+
+ if ($encrypted_data['algo'] === self::SODIUM_ALGO) {
+ $payload = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($encrypted_data['payload'], '', $encrypted_data['nonce'], hex2bin($key));
+
+ return $payload;
+ }
+
+ throw new Exception('Config::decrypt_string() failed to decrypt passed encrypted data');
+ }
+
public function read(string $id): false|string {
$sth = Db::pdo()->prepare('SELECT data FROM ttrss_sessions WHERE id=?');
$sth->execute([$id]);
@@ -60,11 +104,11 @@ class Sessions implements \SessionHandlerInterface {
if ($row = $sth->fetch()) {
$data = base64_decode($row['data']);
- if (Config::get(Config::SODIUM_ENCRYPTION_KEY)) {
- $unserialized_data = unserialize($data);
+ if (Config::get(Config::SESSION_ENCRYPTION_KEY)) {
+ $unserialized_data = @unserialize($data); // avoid leaking plaintext session via error message
if ($unserialized_data !== false)
- return Config::decrypt_string($unserialized_data);
+ return $this->decrypt_string($unserialized_data);
}
// if Sodium key is missing or session data is not in serialized format, return as-is
@@ -80,8 +124,8 @@ class Sessions implements \SessionHandlerInterface {
public function write(string $id, string $data): bool {
- if (Config::get(Config::SODIUM_ENCRYPTION_KEY))
- $data = serialize(Config::encrypt_string($data));
+ if (Config::get(Config::SESSION_ENCRYPTION_KEY))
+ $data = serialize($this->encrypt_string($data));
$data = base64_encode($data);