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

View file

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

View file

@ -7,7 +7,7 @@ class KeyPathHelper
static public function PublicFile(string $sHash) : string 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 static public function SsoCacherKey(string $sSsoHash) : string
@ -15,21 +15,11 @@ class KeyPathHelper
return '/Sso/Data/'.$sSsoHash.'/Login/'; 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 static public function RepositoryCacheFile(string $sRepo, string $sRepoFile) : string
{ {
return '/RepositoryCache/Repo/'.$sRepo.'/File/'.$sRepoFile; 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 static public function ReadReceiptCache(string $sEmail, string $sFolderFullName, int $iUid) : string
{ {
return '/ReadReceipt/'.$sEmail.'/'.$sFolderFullName.'/'.$iUid; return '/ReadReceipt/'.$sEmail.'/'.$sFolderFullName.'/'.$iUid;

View file

@ -652,6 +652,9 @@ class ServiceActions
return 'Pong'; return 'Pong';
} }
/**
* Login with the \RainLoop\API::CreateUserSsoHash() generated hash
*/
public function ServiceSso() : string public function ServiceSso() : string
{ {
$this->oHttp->ServerNoCache(); $this->oHttp->ServerNoCache();
@ -668,7 +671,9 @@ class ServiceActions
$sSsoSubData = $this->Cacher()->Get(KeyPathHelper::SsoCacherKey($sSsoHash)); $sSsoSubData = $this->Cacher()->Get(KeyPathHelper::SsoCacherKey($sSsoHash));
if (!empty($sSsoSubData)) 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)); $this->Cacher()->Delete(KeyPathHelper::SsoCacherKey($sSsoHash));
if (\is_array($mData) && !empty($mData['Email']) && isset($mData['Password'], $mData['Time']) && if (\is_array($mData) && !empty($mData['Email']) && isset($mData['Password'], $mData['Time']) &&

View file

@ -1,12 +1,13 @@
<?php <?php
/**
* This class encrypts any data into JSON format.
* Decrypted Objects are returned as Array.
*/
namespace SnappyMail; namespace SnappyMail;
abstract class Crypt abstract class Crypt
{ {
/**
* Or use 'aes-256-xts' ?
*/
protected static $cipher = ''; protected static $cipher = '';
public static function listCiphers() : array public static function listCiphers() : array
@ -36,39 +37,55 @@ abstract class Crypt
return false; 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( return \json_decode(
\zlib_decode( \zlib_decode(
\MailSo\Base\Crypt::Decrypt( static::XxteaDecrypt(
\MailSo\Base\Utils::UrlSafeBase64Decode($sData), \MailSo\Base\Utils::UrlSafeBase64Decode($sData),
static::Passphrase() static::Passphrase($key)
) )
), ),
true true
); );
} }
public static function EncryptCookie($mData) : string public static function EncryptUrlSafe($mData, string $key = null) : string
{ {
return \MailSo\Base\Utils::UrlSafeBase64Encode( return \MailSo\Base\Utils::UrlSafeBase64Encode(
\MailSo\Base\Crypt::Encrypt( static::XxteaEncrypt(
\zlib_encode( \zlib_encode(
\json_encode($mData), \json_encode($mData),
ZLIB_ENCODING_RAW, ZLIB_ENCODING_RAW,
9 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')) { if (\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
$nonce = \random_bytes(24); $nonce = \random_bytes(24);
@ -84,7 +101,7 @@ abstract class Crypt
return ['xxtea', $salt, static::XxteaEncrypt($data, $salt)]; 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')) { if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
return null; return null;
@ -93,12 +110,11 @@ abstract class Crypt
$data, $data,
APP_SALT, APP_SALT,
$nonce, $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')) { if (!\is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
return null; return null;
@ -107,11 +123,11 @@ abstract class Crypt
\json_encode($data), \json_encode($data),
APP_SALT, APP_SALT,
$nonce, $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')) { if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_decrypt')) {
return null; return null;
@ -119,13 +135,13 @@ abstract class Crypt
return \json_decode(\openssl_decrypt( return \json_decode(\openssl_decrypt(
$data, $data,
static::$cipher, static::$cipher,
static::Passphrase(), static::Passphrase($key),
OPENSSL_RAW_DATA, OPENSSL_RAW_DATA,
$iv $iv
), true); ), 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')) { if (!$data || !$iv || !static::$cipher || !\is_callable('openssl_encrypt')) {
return null; return null;
@ -133,31 +149,31 @@ abstract class Crypt
return \openssl_encrypt( return \openssl_encrypt(
\json_encode($data), \json_encode($data),
static::$cipher, static::$cipher,
static::Passphrase(), static::Passphrase($key),
OPENSSL_RAW_DATA, OPENSSL_RAW_DATA,
$iv $iv
); );
} }
public static function XxteaDecrypt(string $data, string $salt) public static function XxteaDecrypt(string $data, string $salt, string $key = null) /* : mixed */
{ {
if (!$data || !$salt) { if (!$data || !$salt) {
return null; return null;
} }
$key = $salt . static::Passphrase(); $key = $salt . static::Passphrase($key);
return \json_decode(\is_callable('xxtea_decrypt') return \json_decode(\is_callable('xxtea_decrypt')
? \xxtea_decrypt($data, $key) ? \xxtea_decrypt($data, $key)
: \MailSo\Base\Xxtea::decrypt($data, $key) : \MailSo\Base\Xxtea::decrypt($data, $key)
, true); , true);
} }
public static function XxteaEncrypt($data, string $salt) : ?string public static function XxteaEncrypt($data, string $salt, string $key = null) : ?string
{ {
if (!$data || !$salt) { if (!$data || !$salt) {
return null; return null;
} }
$data = \json_encode($data); $data = \json_encode($data);
$key = $salt . static::Passphrase(); $key = $salt . static::Passphrase($key);
return \is_callable('xxtea_encrypt') return \is_callable('xxtea_encrypt')
? \xxtea_encrypt($data, $key) ? \xxtea_encrypt($data, $key)
: \MailSo\Base\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');