Rememberme compatibility with php version 5.6+ (#4472)

This commit is contained in:
Sukhwinder Dhillon 2021-07-26 17:37:38 +02:00 committed by GitHub
parent 88f2c50f0b
commit 645c0770a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 110 additions and 43 deletions

View File

@ -45,13 +45,15 @@ class AuthenticationController extends Controller
if (RememberMe::hasCookie() && $this->hasDb()) {
$authenticated = false;
$iv = null;
try {
$rememberMeOld = RememberMe::fromCookie();
$iv = $rememberMeOld->getAesCrypt()->getIv();
$authenticated = $rememberMeOld->authenticate();
if ($authenticated) {
$rememberMe = $rememberMeOld->renew();
$this->getResponse()->setCookie($rememberMe->getCookie());
$rememberMe->persist($rememberMeOld->getAesCrypt()->getIv());
$rememberMe->persist($iv);
}
} catch (RuntimeException $e) {
Logger::error("Can't authenticate user via remember me cookie: %s", $e->getMessage());
@ -60,6 +62,7 @@ class AuthenticationController extends Controller
}
if (! $authenticated) {
(new RememberMe())->remove(bin2hex($iv));
$this->getResponse()->setCookie(RememberMe::forget());
}
}
@ -108,7 +111,7 @@ class AuthenticationController extends Controller
} else {
if (RememberMe::hasCookie() && $this->hasDb()) {
try {
(new RememberMe())->remove(RememberMe::fromCookie()->getAesCrypt()->getIV());
(new RememberMe())->remove(bin2hex(RememberMe::fromCookie()->getAesCrypt()->getIV()));
} catch (RuntimeException $e) {
// pass
}

View File

@ -74,7 +74,7 @@ class ManageUserDevicesController extends CompatController
public function deleteAction()
{
(new RememberMe())->removeSpecific($this->params->getRequired('fingerprint'));
(new RememberMe())->remove($this->params->getRequired('fingerprint'));
$this->redirectNow(
Url::fromPath('manage-user-devices/devices')

View File

@ -67,7 +67,7 @@ class MyDevicesController extends CompatController
public function deleteAction()
{
(new RememberMe())->removeSpecific($this->params->getRequired('fingerprint'));
(new RememberMe())->remove($this->params->getRequired('fingerprint'));
$this->redirectNow('my-devices');
}

View File

@ -0,0 +1,2 @@
ALTER TABLE `icingaweb_rememberme`
MODIFY random_iv varchar(32) NOT NULL;

View File

@ -45,7 +45,7 @@ CREATE TABLE `icingaweb_rememberme`(
id int(10) unsigned NOT NULL AUTO_INCREMENT,
username varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
passphrase varchar(256) NOT NULL,
random_iv varchar(24) NOT NULL,
random_iv varchar(32) NOT NULL,
http_user_agent text NOT NULL,
expires_at timestamp NULL DEFAULT NULL,
ctime timestamp NULL DEFAULT NULL,

View File

@ -0,0 +1,2 @@
ALTER TABLE ONLY "icingaweb_rememberme"
ALTER COLUMN random_iv type character varying(32);

View File

@ -105,7 +105,7 @@ CREATE TABLE "icingaweb_rememberme" (
"id" serial,
"username" character varying(254) NOT NULL,
"passphrase" character varying(256) NOT NULL,
"random_iv" character varying(24) NOT NULL,
"random_iv" character varying(32) NOT NULL,
"http_user_agent" text NOT NULL,
"expires_at" timestamp NULL DEFAULT NULL,
"ctime" timestamp NULL DEFAULT NULL,

View File

@ -51,15 +51,31 @@ class AesCrypt
private $tag;
/** @var string The cipher method */
private $method = 'aes-128-gcm';
private $method = 'AES-128-GCM';
const GCM_SUPPORT_VERSION = '7.1';
public function __construct($random_bytes_len = 128)
{
$len = openssl_cipher_iv_length($this->method);
$this->iv = random_bytes($len);
if (version_compare(PHP_VERSION, self::GCM_SUPPORT_VERSION, '<')) {
$this->method = 'AES-128-CBC';
}
$this->key = random_bytes($random_bytes_len);
}
/**
* Force set the method
*
* @return $this
*/
public function setMethod($method)
{
$this->method = $method;
return $this;
}
/**
* Set the key
*
@ -110,7 +126,8 @@ class AesCrypt
public function getIV()
{
if (empty($this->iv)) {
throw new RuntimeException('No iv set');
$len = openssl_cipher_iv_length($this->method);
$this->iv = random_bytes($len);
}
return $this->iv;
@ -162,8 +179,13 @@ class AesCrypt
*/
public function decrypt($data)
{
if ($this->method === 'AES-128-CBC') {
return $this->decryptCBC($data);
}
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV(), $this->getTag());
if (is_bool($decrypt) && $decrypt === false) {
if ($decrypt === false) {
throw new RuntimeException('Decryption failed');
}
@ -195,9 +217,13 @@ class AesCrypt
*/
public function encrypt($data)
{
if ($this->method === 'AES-128-CBC') {
return $this->encryptCBC($data);
}
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV(), $this->tag);
if (is_bool($encrypt) && $encrypt === false) {
if ($encrypt === false) {
throw new RuntimeException('Encryption failed');
}
@ -217,4 +243,37 @@ class AesCrypt
{
return base64_encode($this->encrypt($data));
}
private function decryptCBC($data)
{
if (strlen($this->getIV()) !== 16) {
throw new RuntimeException('Decryption failed');
}
$c = base64_decode($data);
$hmac = substr($c, 0, 32);
$data = substr($c, 32);
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV());
$calcHmac = hash_hmac('sha256', $data, $this->getKey(), true);
if ($decrypt === false || ! hash_equals($hmac, $calcHmac)) {
throw new RuntimeException('Decryption failed');
}
return $decrypt;
}
private function encryptCBC($data)
{
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV());
if ($encrypt === false) {
throw new RuntimeException('Encryption failed');
}
$hmac = hash_hmac('sha256', $encrypt, $this->getkey(), true);
return base64_encode($hmac . $encrypt);
}
}

View File

@ -72,7 +72,6 @@ class RememberMe
{
$data = explode('|', $_COOKIE[static::COOKIE]);
$iv = base64_decode(array_pop($data));
$tag = base64_decode(array_pop($data));
$select = (new Select())
->from(static::TABLE)
@ -91,8 +90,23 @@ class RememberMe
$rememberMe->aesCrypt = (new AesCrypt())
->setKey(hex2bin($rs->passphrase))
->setTag($tag)
->setIV($iv);
if (version_compare(PHP_VERSION, AesCrypt::GCM_SUPPORT_VERSION, '>=')) {
$tag = array_pop($data);
if (empty($data)) {
$rememberMe->aesCrypt = (new AesCrypt())
->setMethod('AES-128-CBC')
->setKey(hex2bin($rs->passphrase))
->setIV($iv);
$data[0] = $tag; // encryptedPass
} else {
$rememberMe->aesCrypt->setTag(base64_decode($tag));
}
}
$rememberMe->username = $rs->username;
$rememberMe->encryptedPassword = $data[0];
@ -140,14 +154,19 @@ class RememberMe
*/
public function getCookie()
{
$values = [
$this->encryptedPassword,
base64_encode($this->aesCrypt->getIV()),
];
if (version_compare(PHP_VERSION, AesCrypt::GCM_SUPPORT_VERSION, '>=')) {
array_splice($values, 1, 0, base64_encode($this->aesCrypt->getTag()));
}
return (new Cookie(static::COOKIE))
->setExpire($this->getExpiresAt())
->setHttpOnly(true)
->setValue(implode('|', [
$this->encryptedPassword,
base64_encode($this->aesCrypt->getTag()),
base64_encode($this->aesCrypt->getIV()),
]));
->setValue(implode('|', $values));
}
/**
@ -218,7 +237,7 @@ class RememberMe
public function persist($iv = null)
{
if ($iv) {
$this->remove($iv);
$this->remove(bin2hex($iv));
}
$this->getDb()->insert(static::TABLE, [
@ -244,7 +263,7 @@ class RememberMe
public function remove($iv)
{
$this->getDb()->delete(static::TABLE, [
'random_iv = ?' => bin2hex($iv)
'random_iv = ?' => $iv
]);
return $this;
@ -263,24 +282,6 @@ class RememberMe
);
}
/**
* Remove specific remember me information from the database
*
* @param string $username
*
* @param $iv
*
* @return $this
*/
public function removeSpecific($iv)
{
$this->getDb()->delete(static::TABLE, [
'random_iv = ?' => $iv
]);
return $this;
}
/**
* Get all users using rememberme cookie
*

View File

@ -5,7 +5,7 @@ namespace Icinga\Web;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Web\Url;
use ipl\Web\Url as iplWebUrl; //alias is needed for php5.6
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
@ -124,7 +124,7 @@ class RememberMeUserDevicesList extends BaseHtmlElement
$link = (new Link(
new Icon('trash'),
Url::fromPath($this->getUrl())
iplWebUrl::fromPath($this->getUrl())
->addParams(
[
'name' => $this->getUsername(),

View File

@ -5,7 +5,7 @@ namespace Icinga\Web;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Web\Url;
use ipl\Web\Url as iplWebUrl; //alias is needed for php5.6
use ipl\Web\Widget\Link;
/**
@ -91,7 +91,7 @@ class RememberMeUserList extends BaseHtmlElement
$element = Html::tag('tr');
$link = new Link(
$user->username,
Url::fromPath($this->getUrl())->addParams(['name' => $user->username]),
iplWebUrl::fromPath($this->getUrl())->addParams(['name' => $user->username]),
['title' => sprintf(t('Device list of %s'), $user->username)]
);