More encrypt/decrypt improvements to revamp SSO data to be properly encrypted

This commit is contained in:
djmaze 2021-11-10 12:30:56 +01:00
parent cda88f438a
commit 9dae4cfa45
5 changed files with 64 additions and 48 deletions

View file

@ -216,7 +216,7 @@ trait UserAuth
if (empty($sSignMeToken)) {
return null;
}
$aResult = \SnappyMail\Crypt::DecryptCookie($sSignMeToken);
$aResult = \SnappyMail\Crypt::DecryptUrlSafe($sSignMeToken);
return \is_array($aResult) ? $aResult : null;
}
@ -224,7 +224,7 @@ trait UserAuth
{
Utils::SetCookie(
self::AUTH_SIGN_ME_TOKEN_KEY,
\SnappyMail\Crypt::EncryptCookie($aData),
\SnappyMail\Crypt::EncryptUrlSafe($aData),
\time() + 3600 * 24 * 30 // 30 days
);
}

View file

@ -28,7 +28,7 @@ class Api
return $bOne;
}
public static function Actions() : Actions
final public static function Actions() : Actions
{
static $oActions = null;
if (null === $oActions)
@ -161,18 +161,21 @@ class Api
return APP_VERSION;
}
public static function GetUserSsoHash(string $sEmail, string $sPassword, array $aAdditionalOptions = array(), bool $bUseTimeout = true) : ?string
public static function CreateUserSsoHash(string $sEmail, string $sPassword, array $aAdditionalOptions = array(), bool $bUseTimeout = true) : ?string
{
$sSsoHash = \MailSo\Base\Utils::Sha1Rand(\sha1($sPassword.$sEmail));
$data = \SnappyMail\Crypt::Encrypt(array(
'Email' => $sEmail,
'Password' => $sPassword,
'AdditionalOptions' => $aAdditionalOptions,
'Time' => $bUseTimeout ? \time() : 0
), $sSsoHash);
return static::Actions()->Cacher()->Set(
KeyPathHelper::SsoCacherKey($sSsoHash),
Utils::EncodeKeyValuesQ(array(
'Email' => $sEmail,
'Password' => $sPassword,
'AdditionalOptions' => $aAdditionalOptions,
'Time' => $bUseTimeout ? \time() : 0
))) ? $sSsoHash : null;
\json_encode(\array_map('base64_encode', $data))
) ? $sSsoHash : null;
}
public static function ClearUserSsoHash(string $sSsoHash) : bool

View file

@ -7,7 +7,7 @@ class KeyPathHelper
static public function PublicFile(string $sHash) : string
{
return '/Public/Files/'.sha1($sHash).'/Data/';
return '/Public/Files/'.\sha1($sHash).'/Data/';
}
static public function SsoCacherKey(string $sSsoHash) : string
@ -15,21 +15,11 @@ class KeyPathHelper
return '/Sso/Data/'.$sSsoHash.'/Login/';
}
static public function RsaCacherKey(string $sHash) : string
{
return '/Rsa/Data/'.$sHash.'/';
}
static public function RepositoryCacheFile(string $sRepo, string $sRepoFile) : string
{
return '/RepositoryCache/Repo/'.$sRepo.'/File/'.$sRepoFile;
}
static public function RepositoryCacheCore(string $sRepo) : string
{
return '/RepositoryCache/CoreRepo/'.$sRepo;
}
static public function ReadReceiptCache(string $sEmail, string $sFolderFullName, int $iUid) : string
{
return '/ReadReceipt/'.$sEmail.'/'.$sFolderFullName.'/'.$iUid;

View file

@ -652,6 +652,9 @@ class ServiceActions
return 'Pong';
}
/**
* Login with the \RainLoop\API::CreateUserSsoHash() generated hash
*/
public function ServiceSso() : string
{
$this->oHttp->ServerNoCache();
@ -668,7 +671,9 @@ class ServiceActions
$sSsoSubData = $this->Cacher()->Get(KeyPathHelper::SsoCacherKey($sSsoHash));
if (!empty($sSsoSubData))
{
$mData = Utils::DecodeKeyValuesQ($sSsoSubData);
$mData = \array_map('base64_decode', \json_decode($sSsoSubData, true));
$mData = \is_array($mData) ? \SnappyMail\Crypt::Decrypt($sSsoSubData, $sSsoHash) : null;
$this->Cacher()->Delete(KeyPathHelper::SsoCacherKey($sSsoHash));
if (\is_array($mData) && !empty($mData['Email']) && isset($mData['Password'], $mData['Time']) &&

View file

@ -1,12 +1,13 @@
<?php
/**
* This class encrypts any data into JSON format.
* Decrypted Objects are returned as Array.
*/
namespace SnappyMail;
abstract class Crypt
{
/**
* Or use 'aes-256-xts' ?
*/
protected static $cipher = '';
public static function listCiphers() : array
@ -36,39 +37,55 @@ abstract class Crypt
return false;
}
private static function Passphrase() : string
/**
* When $key is empty, it will use a fingerprint of the user agent.
*/
private static function Passphrase(?string $key) : string
{
return \sha1(\preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]) . APP_SALT, true);
return \sha1(
($key ?: \preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0])) . APP_SALT,
true
);
}
public static function DecryptCookie($sData)
public static function DecryptUrlSafe($sData, string $key = null) /* : mixed */
{
return \json_decode(
\zlib_decode(
\MailSo\Base\Crypt::Decrypt(
static::XxteaDecrypt(
\MailSo\Base\Utils::UrlSafeBase64Decode($sData),
static::Passphrase()
static::Passphrase($key)
)
),
true
);
}
public static function EncryptCookie($mData) : string
public static function EncryptUrlSafe($mData, string $key = null) : string
{
return \MailSo\Base\Utils::UrlSafeBase64Encode(
\MailSo\Base\Crypt::Encrypt(
static::XxteaEncrypt(
\zlib_encode(
\json_encode($mData),
ZLIB_ENCODING_RAW,
9
),
static::Passphrase()
static::Passphrase($key)
)
);
}
public static function Encrypt($data) : array
public static function Decrypt(array $data, string $key = null) /* : mixed */
{
if (3 === \count($data) && isset($data[0], $data[1], $data[2])) {
$fn = "\\SnappyMail\\Crypt::{$data[0]}Decrypt";
if (\method_exists($fn)) {
return $fn($data[2], $data[1], $key);
}
}
}
public static function Encrypt($data, string $key = null) : array
{
if (\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
$nonce = \random_bytes(24);
@ -84,7 +101,7 @@ abstract class Crypt
return ['xxtea', $salt, static::XxteaEncrypt($data, $salt)];
}
public static function SodiumDecrypt(string $data, string $nonce)
public static function SodiumDecrypt(string $data, string $nonce, string $key = null) /* : mixed */
{
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
return null;
@ -93,12 +110,11 @@ abstract class Crypt
$data,
APP_SALT,
$nonce,
static::Passphrase()
static::Passphrase($key)
));
}
public static function SodiumEncrypt($data, string $nonce) : ?string
public static function SodiumEncrypt($data, string $nonce, string $key = null) : ?string
{
if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
return null;
@ -107,11 +123,11 @@ abstract class Crypt
\json_encode($data),
APP_SALT,
$nonce,
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase())
\str_pad('', \SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, static::Passphrase($key))
);
}
public static function OpenSSLDecrypt(string $data, string $iv)
public static function OpenSSLDecrypt(string $data, string $iv, string $key = null) /* : mixed */
{
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_decrypt')) {
return null;
@ -119,13 +135,13 @@ abstract class Crypt
return \json_decode(\openssl_decrypt(
$data,
static::$cipher,
static::Passphrase(),
static::Passphrase($key),
OPENSSL_RAW_DATA,
$iv
), true);
}
public static function OpenSSLEncrypt($data, string $iv) : ?string
public static function OpenSSLEncrypt($data, string $iv, string $key = null) : ?string
{
if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_encrypt')) {
return null;
@ -133,31 +149,31 @@ abstract class Crypt
return \openssl_encrypt(
\json_encode($data),
static::$cipher,
static::Passphrase(),
static::Passphrase($key),
OPENSSL_RAW_DATA,
$iv
);
}
public static function XxteaDecrypt(string $data, string $salt)
public static function XxteaDecrypt(string $data, string $salt, string $key = null) /* : mixed */
{
if (!$data || !$salt) {
return null;
}
$key = $salt . static::Passphrase();
$key = $salt . static::Passphrase($key);
return \json_decode(\is_callable('xxtea_decrypt')
? \xxtea_decrypt($data, $key)
: \MailSo\Base\Xxtea::decrypt($data, $key)
, true);
}
public static function XxteaEncrypt($data, string $salt) : ?string
public static function XxteaEncrypt($data, string $salt, string $key = null) : ?string
{
if (!$data || !$salt) {
return null;
}
$data = \json_encode($data);
$key = $salt . static::Passphrase();
$key = $salt . static::Passphrase($key);
return \is_callable('xxtea_encrypt')
? \xxtea_encrypt($data, $key)
: \MailSo\Base\Xxtea::encrypt($data, $key);
@ -165,4 +181,6 @@ 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');