Improve OpenSSL cipher handling due to issues with OpenSSL 3.

More info at #641
This commit is contained in:
the-djmaze 2022-11-09 09:12:06 +01:00
parent 697250e361
commit 3992d9d5f7
2 changed files with 46 additions and 24 deletions

View file

@ -39,6 +39,14 @@ class Application extends \RainLoop\Config\AbstractConfig
$this->aReplaceEnv = null; $this->aReplaceEnv = null;
} }
$sCipher = $this->Get('security', 'encrypt_cipher', '');
if (!$sCipher || !\SnappyMail\Crypt::cipherSupported($sCipher)) {
$sCipher && \SnappyMail\Log::warning("OpenSSL does not support {$sCipher}");
$aCiphers = \SnappyMail\Crypt::listCiphers();
$this->Set('security', 'encrypt_cipher', $aCiphers[\array_rand($aCiphers)]);
$this->Save();
}
return $bResult; return $bResult;
} }
@ -125,12 +133,6 @@ class Application extends \RainLoop\Config\AbstractConfig
} }
$upload_max_filesize = $upload_max_filesize / 1024 / 1024; $upload_max_filesize = $upload_max_filesize / 1024 / 1024;
$sCipher = 'aes-256-cbc-hmac-sha1';
$aCiphers = \SnappyMail\Crypt::listCiphers();
if (!\in_array($sCipher, $aCiphers)) {
$sCipher = $aCiphers[\array_rand($aCiphers)];
}
return array( return array(
'webmail' => array( 'webmail' => array(
@ -191,7 +193,7 @@ class Application extends \RainLoop\Config\AbstractConfig
'admin_panel_key' => array('admin'), 'admin_panel_key' => array('admin'),
'content_security_policy' => array('', 'For example to allow all images use "img-src https:". More info at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives'), 'content_security_policy' => array('', 'For example to allow all images use "img-src https:". More info at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives'),
'csp_report' => array(false, 'Report CSP errors to PHP and/or SnappyMail Log'), 'csp_report' => array(false, 'Report CSP errors to PHP and/or SnappyMail Log'),
'encrypt_cipher' => array($sCipher, 'A valid cipher method from https://php.net/openssl_get_cipher_methods'), 'encrypt_cipher' => array('aes-256-cbc-hmac-sha1', 'A valid cipher method from https://php.net/openssl_get_cipher_methods'),
'cookie_samesite' => array('Strict', 'Strict, Lax or None') 'cookie_samesite' => array('Strict', 'Strict, Lax or None')
), ),

View file

@ -18,21 +18,23 @@ abstract class Crypt
$list = \array_diff($list, \array_map('strtoupper',$list)); $list = \array_diff($list, \array_map('strtoupper',$list));
$list = \array_filter($list, function($v){ $list = \array_filter($list, function($v){
// DES/ECB/bf/rc insecure, GCM/CCM not supported // DES/ECB/bf/rc insecure, GCM/CCM not supported
return !\preg_match('/(^(des|bf|rc))|-(ecb|gcm|ccm)/i', $v); return !\preg_match('/(^(des|bf|rc))|-(ecb|gcm|ccm|ocb)|wrap/i', $v);
}); });
\natcasesort($list); \natcasesort($list);
} }
return $list; return $list;
} }
public static function cipherSupported(string $cipher) : bool
{
return \in_array($cipher, static::listCiphers());
}
public static function setCipher(string $cipher) : bool public static function setCipher(string $cipher) : bool
{ {
if ($cipher) { if (static::cipherSupported($cipher)) {
$ciphers = static::listCiphers(); static::$cipher = $cipher;
if (\in_array($cipher, $ciphers)) { return true;
static::$cipher = $cipher;
return true;
}
} }
return false; return false;
} }
@ -96,12 +98,14 @@ abstract class Crypt
$nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES); $nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
return ['sodium', $nonce, static::SodiumEncrypt($data, $nonce, $key)]; return ['sodium', $nonce, static::SodiumEncrypt($data, $nonce, $key)];
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error($e->getMessage());
} }
try { try {
$iv = \random_bytes(\openssl_cipher_iv_length(static::$cipher)); $iv = \random_bytes(\openssl_cipher_iv_length(static::$cipher));
return ['openssl', $iv, static::OpenSSLEncrypt($data, $iv, $key)]; return ['openssl', $iv, static::OpenSSLEncrypt($data, $iv, $key)];
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error($e->getMessage());
} }
$salt = \random_bytes(16); $salt = \random_bytes(16);
@ -141,12 +145,16 @@ abstract class Crypt
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) { if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
throw new \Exception('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt not callable'); throw new \Exception('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt not callable');
} }
return \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( $result = \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
$data, $data,
APP_SALT, APP_SALT,
$nonce, $nonce,
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key)) \str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
); );
if (!$result) {
throw new \Exception('Sodium encryption failed');
}
return $result;
} }
public static function OpenSSLDecrypt(string $data, string $iv, string $key = null) /* : string|false */ public static function OpenSSLDecrypt(string $data, string $iv, string $key = null) /* : string|false */
@ -154,9 +162,12 @@ abstract class Crypt
if (!$data || !$iv) { if (!$data || !$iv) {
throw new \InvalidArgumentException('$data or $iv is empty string'); throw new \InvalidArgumentException('$data or $iv is empty string');
} }
if (!static::$cipher || !\is_callable('openssl_decrypt')) { if (!\is_callable('openssl_decrypt')) {
throw new \Exception('openssl_decrypt not callable'); throw new \Exception('openssl_decrypt not callable');
} }
if (!static::$cipher) {
throw new \Exception('openssl $cipher not set');
}
return \openssl_decrypt( return \openssl_decrypt(
$data, $data,
static::$cipher, static::$cipher,
@ -166,21 +177,28 @@ abstract class Crypt
); );
} }
public static function OpenSSLEncrypt(string $data, string $iv, string $key = null) : ?string public static function OpenSSLEncrypt(string $data, string $iv, string $key = null) : string
{ {
if (!$data || !$iv) { if (!$data || !$iv) {
throw new \InvalidArgumentException('$data or $iv is empty string'); throw new \InvalidArgumentException('$data or $iv is empty string');
} }
if (!static::$cipher || !\is_callable('openssl_encrypt')) { if (!\is_callable('openssl_encrypt')) {
throw new \Exception('openssl_encrypt not callable'); throw new \Exception('openssl_encrypt not callable');
} }
return \openssl_encrypt( if (!static::$cipher) {
throw new \Exception('openssl $cipher not set');
}
$result = \openssl_encrypt(
$data, $data,
static::$cipher, static::$cipher,
static::Passphrase($key), static::Passphrase($key),
OPENSSL_RAW_DATA, OPENSSL_RAW_DATA,
$iv $iv
); );
if (!$result) {
throw new \Exception('OpenSSL encryption with ' . static::$cipher . ' failed');
}
return $result;
} }
public static function XxteaDecrypt(string $data, string $salt, string $key = null) /* : mixed */ public static function XxteaDecrypt(string $data, string $salt, string $key = null) /* : mixed */
@ -194,15 +212,19 @@ abstract class Crypt
: \MailSo\Base\Xxtea::decrypt($data, $key); : \MailSo\Base\Xxtea::decrypt($data, $key);
} }
public static function XxteaEncrypt(string $data, string $salt, string $key = null) : ?string public static function XxteaEncrypt(string $data, string $salt, string $key = null) : string
{ {
if (!$data || !$salt) { if (!$data || !$salt) {
throw new \InvalidArgumentException('$data or $salt is empty string'); throw new \InvalidArgumentException('$data or $salt is empty string');
} }
$key = $salt . static::Passphrase($key); $key = $salt . static::Passphrase($key);
return \is_callable('xxtea_encrypt') $result = \is_callable('xxtea_encrypt')
? \xxtea_encrypt($data, $key) ? \xxtea_encrypt($data, $key)
: \MailSo\Base\Xxtea::encrypt($data, $key); : \MailSo\Base\Xxtea::encrypt($data, $key);
if (!$result) {
throw new \Exception('Xxtea encryption failed');
}
return $result;
} }
private static function jsonDecode(string $data) /*: mixed*/ private static function jsonDecode(string $data) /*: mixed*/
@ -212,6 +234,4 @@ abstract class Crypt
} }
\SnappyMail\Crypt::setCipher(\RainLoop\Api::Config()->Get('security', 'encrypt_cipher', 'aes-256-cbc-hmac-sha1')) \SnappyMail\Crypt::setCipher(\RainLoop\Api::Config()->Get('security', 'encrypt_cipher', 'aes-256-cbc-hmac-sha1'));
|| \SnappyMail\Crypt::setCipher('aes-256-cbc-hmac-sha1')
|| \SnappyMail\Crypt::setCipher('aes-256-xts');