From 1769eeb8315ddd7f13b0aec9a2e1616be8026276 Mon Sep 17 00:00:00 2001 From: "eapl.mx" Date: Tue, 24 Dec 2024 14:31:38 -0600 Subject: [PATCH] feat(session): implement persistent cookies with hmac (replaced AES encryption) --- libs/persistent_session.php | 120 +++++++++++++++++++++--------------- libs/session.php | 4 +- views/test_login.php | 1 + 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/libs/persistent_session.php b/libs/persistent_session.php index 45d45fd..03e3b3d 100644 --- a/libs/persistent_session.php +++ b/libs/persistent_session.php @@ -9,12 +9,13 @@ if (!empty($missing_keys)) { die('Missing required keys in config.ini: ' . implode(', ', $missing_keys)); } -# To make it more secure, something like JWT could be used instead - const COOKIE_NAME = 'timeline_login'; const ENCRYPTION_METHOD = 'aes-256-cbc'; const EXPIRATION_DAYS = 30; +const HASH_LENGTH = 128; +const HASH_ALGORITHM = 'sha512'; + session_start([ 'name' => 'timeline_session', 'use_strict_mode' => true, @@ -25,50 +26,80 @@ session_start([ 'cookie_samesite' => 'Strict', # Not compatible with PHP < 7.3 ]); -function hasValidSession(): bool|string { +function hasValidSession(): bool { # If short lived session is valid - if (isset($_SESSION['valid_session'])) { + if (isset($_SESSION['session_expiration'])) { return true; } + # TODO: Check if the session has expired + # Add more protection to prevent session fixation + # https://owasp.org/www-community/attacks/Session_fixation + # Otherwise, check the persistent cookie return isSavedCookieValid(); } -function encrypt(string $data, string $key, string $method): string { - $ivSize = openssl_cipher_iv_length($method); - $iv = openssl_random_pseudo_bytes($ivSize); - $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); - $encrypted = strtoupper(implode(unpack('H*', $encrypted))); - - return $encrypted; -} - -function decrypt(string $data, string $key, string $method): string | bool { - $data = pack('H*', $data); - $ivSize = openssl_cipher_iv_length($method); - $iv = openssl_random_pseudo_bytes($ivSize); - $decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); - - var_dump($decrypted); - - if ($decrypted === false) { +function getCookieData() { + if (!isset($_COOKIE[COOKIE_NAME])) { + #echo "Cookie " . COOKIE_NAME . " not found"; return false; } - return trim($decrypted); -} + $raw = base64_decode($_COOKIE[COOKIE_NAME]); + #var_dump($raw); -function saveLoginSuccess() { - $_SESSION['valid_session'] = true; + # Cookie should be at least the size of the hash length. + # If it's not, we can just bail out + if (strlen($raw) < HASH_LENGTH) { + #echo "Didn't get minimum length"; + return false; + } $config = parse_ini_file('private/config.ini'); - # Set a cookie to remember the user - $cookieExpiry = EXPIRATION_DAYS * 24 * 60 * 60 + time(); - $encodedCookieValue = generateCookieValue(strval($cookieExpiry), $config['secret_key']); + # The cookie data contains the actual data w/ the hash concatonated to the end, + # since the hash is a fixed length, we can extract the last hash_length chars + # to get the hash. + $hash = substr($raw, strlen($raw) - HASH_LENGTH, HASH_LENGTH); + $data = substr($raw, 0, - (HASH_LENGTH)); - setcookie(COOKIE_NAME, $encodedCookieValue, [ + # Calculate what the hash should be, based on the data. If the data has not been + # tampered with, $hash and $hash_calculated will be the same + $hash_calculated = hash_hmac(HASH_ALGORITHM, $data, $config['secret_key']); + + # If we calculate a different hash, we can't trust the data. + if ($hash_calculated !== $hash) { + #echo "Different HASH"; + return False; + } + + # Is it expired ? + if (intval($data) < time()) { + #echo "Cookie expired"; + return False; + } + + return $data; +} + +function makePersistentCookie() { + $config = parse_ini_file('private/config.ini'); + + $cookieExpiry = EXPIRATION_DAYS * 24 * 60 * 60 + time(); # X days + #$cookieExpiry = 10 + time(); # Debug value - 5 minutes + + # Calculate a hash for the data and append it to the end of the data string + $cookieValue = strval($cookieExpiry); + + $hash = hash_hmac(HASH_ALGORITHM, $cookieValue, $config['secret_key']); + $cookieValue .= $hash; + $cookieValue = base64_encode($cookieValue); + + # Also create the short-timed session + $_SESSION['session_expiration'] = $cookieExpiry; + + return setcookie(COOKIE_NAME, $cookieValue, [ 'expires' => $cookieExpiry, 'secure' => $config['secure_cookies'], 'httponly' => true, @@ -76,35 +107,22 @@ function saveLoginSuccess() { ]); } -function generateCookieValue($value, $secretKey) { - $key = bin2hex($secretKey); - - $encrypted = encrypt($value, $key, ENCRYPTION_METHOD); - return $encrypted; +function saveLogin() { + makePersistentCookie(); } function isSavedCookieValid() { - if (!isset($_COOKIE[COOKIE_NAME])) { - return false; - } - - $config = parse_ini_file('private/config.ini'); - - $encoded_cookie_value = $_COOKIE[COOKIE_NAME]; - $key = bin2hex($config['secret_key']); - - $cookieVal = decrypt($encoded_cookie_value, $key, ENCRYPTION_METHOD); - - if ($cookieVal === false) { + $cookieExpiry = getCookieData(); + + if ($cookieExpiry === false) { deletePersistentCookie(); return false; } - # TODO: Check that the cookie is not expired + # Refresh session + $_SESSION['session_expiration'] = intval($cookieExpiry); - saveLoginSuccess(); # Extend expiracy for previous cookie - - return true; # If it was decoded correctly, it's a valid session + return true; } function deletePersistentCookie() { diff --git a/libs/session.php b/libs/session.php index 10a688b..c21f512 100644 --- a/libs/session.php +++ b/libs/session.php @@ -14,14 +14,14 @@ if (isset($_POST['submit_pass']) && $_POST['pass']) { if ($passwordInForm == $passwordInConfig) { $_SESSION['password'] = $passwordInForm; - saveLoginSuccess(); + saveLogin(); } elseif ($isCodeValid = verifyTOTP( $config['totp_secret'], $passwordInForm, intval($config['totp_digits']) )) { $_SESSION['password'] = 'valid_totp'; - saveLoginSuccess(); + saveLogin(); } else { $error = 'Incorrect Password'; } diff --git a/views/test_login.php b/views/test_login.php index d750909..03d4be8 100644 --- a/views/test_login.php +++ b/views/test_login.php @@ -4,6 +4,7 @@ require_once "libs/persistent_session.php"; if (!hasValidSession()) { + echo "Not a valid session - Go to /login"; #header("Location: /login"); exit; }