Improved user authentication system by supporting OpenSSL aes-256-cbc-hmac-sha1 encryption

This commit is contained in:
djmaze 2021-11-10 00:57:09 +01:00
parent 99c72ad9e6
commit 8ca043b6e4
16 changed files with 468 additions and 450 deletions

View file

@ -18,30 +18,22 @@ namespace MailSo\Base;
class Crypt
{
public static function Encrypt(string $sString, string $sKey, string $sCipher = '') : string
public static function Encrypt(string $sString, string $sKey) : string
{
if (!\strlen($sString)) {
return '';
}
if ($sCipher && \is_callable('openssl_encrypt')) {
$iv = \str_pad('', \openssl_cipher_iv_length($sCipher), \sha1($sKey));
return \openssl_encrypt($sString, $sCipher, $sKey, OPENSSL_RAW_DATA, $iv);
}
if (\is_callable('xxtea_encrypt')) {
return \xxtea_encrypt($sString, $sKey);
}
return Xxtea::encrypt($sString, $sKey);
}
public static function Decrypt(string $sString, string $sKey, string $sCipher = '') : string
public static function Decrypt(string $sString, string $sKey) : string
{
if (!\strlen($sString)) {
return '';
}
if ($sCipher && \is_callable('openssl_encrypt')) {
$iv = \str_pad('', \openssl_cipher_iv_length($sCipher), \sha1($sKey));
return \openssl_decrypt($sString, $sCipher, $sKey, OPENSSL_RAW_DATA, $iv);
}
if (\is_callable('xxtea_decrypt')) {
return \xxtea_decrypt($sString, $sKey);
}

View file

@ -17,8 +17,20 @@ class Actions
use Actions\Themes;
const AUTH_MAILTO_TOKEN_KEY = 'smmailtoauth';
/**
* This 30 days cookie contains decrypt data,
* to decrypt a \RainLoop\Model\Account which is stored at
* /_data_/.../storage/DOMAIN/LOCAL/.sign_me/*
* Gets refreshed on each login
*/
const AUTH_SIGN_ME_TOKEN_KEY = 'smremember';
const AUTH_SPEC_TOKEN_KEY = 'smspecauth';
/**
* This session cookie contains a \RainLoop\Model\Account
*/
const AUTH_SPEC_TOKEN_KEY = 'smaccount';
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
@ -130,7 +142,6 @@ class Actions
$this->oAddressBookProvider = null;
$this->oSuggestionsProvider = null;
$this->sSpecAuthToken = '';
$this->bIsJson = false;
$oConfig = $this->Config();
@ -309,7 +320,6 @@ class Actions
if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) {
if (!$oAccount) {
$this->getAuthAccountHash();
$oAccount = $this->getAccountFromToken(false);
}
@ -363,7 +373,6 @@ class Actions
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) {
if (!$oAccount) {
$this->getAuthAccountHash();
$oAccount = $this->getAccountFromToken(false);
}
@ -871,7 +880,8 @@ class Actions
$aResult['IncLogin'] = $oAccount->IncLogin();
$aResult['OutLogin'] = $oAccount->OutLogin();
$aResult['AccountHash'] = $oAccount->Hash();
$aResult['AccountSignMe'] = $oAccount->SignMe();
// $aResult['AccountSignMe'] = $oAccount->SignMe();
$aResult['AccountSignMe'] = false;
$aResult['ContactsIsAllowed'] = $oAddressBookProvider->IsActive();
$aResult['ContactsSyncIsAllowed'] = (bool)$oConfig->Get('contacts', 'allow_sync', false);
$aResult['ContactsSyncInterval'] = (int)$oConfig->Get('contacts', 'sync_interval', 20);
@ -1066,17 +1076,6 @@ class Actions
}
}
public function AuthToken(Model\Account $oAccount): void
{
$this->SetAuthToken($oAccount);
$aAccounts = $this->GetAccounts($oAccount);
if (isset($aAccounts[$oAccount->Email()])) {
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
$this->SetAccounts($oAccount, $aAccounts);
}
}
/**
* @throws \RainLoop\Exceptions\ClientException
*/
@ -1202,7 +1201,7 @@ class Actions
{
$iResult = 0;
$oAccount = $this->GetAccountFromCustomToken($sHash, false);
$oAccount = $this->GetAccountFromCustomToken($sHash);
if ($oAccount) {
try {
$oMailClient = new \MailSo\Mail\MailClient();

View file

@ -73,9 +73,9 @@ trait Accounts
$oAccountToChange = null;
if ($oAccount->Email() === $sEmailToDelete && !empty($aAccounts[$sParentEmail])) {
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false, false);
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false);
if ($oAccountToChange) {
$this->AuthToken($oAccountToChange);
$this->SetAuthToken($oAccountToChange);
}
}

View file

@ -707,15 +707,12 @@ trait Messages
$this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt));
}
$bUsePhpMail = $oAccount->Domain()->OutUsePhpMail();
$oSmtpClient = new \MailSo\Smtp\SmtpClient();
$oSmtpClient->SetLogger($this->Logger());
$oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60));
$oAccount->OutConnectAndLoginHelper(
$this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail
);
$bUsePhpMail = false;
$oAccount->OutConnectAndLoginHelper($this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail);
if ($bUsePhpMail)
{

View file

@ -23,24 +23,16 @@ trait User
$sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', ''));
$sPassword = $this->GetActionParam('Password', '');
$sLanguage = $this->GetActionParam('Language', '');
$bSignMe = '1' === (string) $this->GetActionParam('SignMe', '0');
$bSignMe = !empty($this->GetActionParam('SignMe', 0));
$oAccount = null;
$this->Logger()->AddSecret($sPassword);
try
{
$oAccount = $this->LoginProcess($sEmail, $sPassword,
$bSignMe ? $this->generateSignMeToken($sEmail) : '');
$this->Plugins()->RunHook('login.success', array($oAccount));
}
catch (ClientException $oException)
{
throw $oException;
}
$oAccount = $this->LoginProcess($sEmail, $sPassword, $bSignMe);
$this->Plugins()->RunHook('login.success', array($oAccount));
$this->AuthToken($oAccount);
$this->SetAuthToken($oAccount);
if ($oAccount && \strlen($sLanguage))
{
@ -211,13 +203,9 @@ trait User
$oAccount = $this->getAccountFromToken(false);
if ($oAccount)
{
if ($oAccount->SignMe())
{
$this->ClearSignMeData($oAccount);
}
if (!$oAccount->IsAdditionalAccount())
{
$this->ClearSignMeData();
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
}
}
@ -515,11 +503,6 @@ trait User
$this->SettingsProvider()->Save($oAccount, $oSettings) : false);
}
private function generateSignMeToken(string $sEmail) : string
{
return \MailSo\Base\Utils::Sha1Rand(APP_SALT.$sEmail);
}
private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array
{
$aValues = $this->getDecodedRawKeyValue($sHash);

View file

@ -10,21 +10,20 @@ use \RainLoop\Exceptions\ClientException;
trait UserAuth
{
/*
const AUTH_SIGN_ME_TOKEN_KEY = 'smremember';
const AUTH_SPEC_TOKEN_KEY = 'smspecauth';
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
*/
/**
* @var string
*/
private $sSpecAuthToken;
private $sSpecAuthToken = null;
/**
* Or use 'aes-256-xts' ?
*/
private static $sCipher = 'aes-256-cbc-hmac-sha1';
/**
* @throws \RainLoop\Exceptions\ClientException
*/
public function LoginProcess(string &$sEmail, string &$sPassword, string $sSignMeToken = ''): Account
public function LoginProcess(string &$sEmail, string &$sPassword, bool $bSignMe = false): Account
{
$sInputEmail = $sEmail;
@ -117,7 +116,7 @@ trait UserAuth
$oAccount = null;
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
try {
$oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, $sClientCert, true);
$oAccount = Account::NewInstanceByLogin($this, $sEmail, $sLogin, $sPassword, $sClientCert, true);
if (!$oAccount) {
throw new ClientException(Notifications::AuthError);
@ -130,6 +129,7 @@ trait UserAuth
try {
$this->CheckMailConnection($oAccount, true);
$bSignMe && $this->SetSignMeToken($oAccount);
} catch (\Throwable $oException) {
$this->loginErrorDelay();
@ -142,107 +142,27 @@ trait UserAuth
/**
* @throws \RainLoop\Exceptions\ClientException
*/
public function GetAccountFromCustomToken(string $sToken, bool $bThrowExceptionOnFalse = true, bool $bValidateShortToken = true, bool $bQ = false): ?Account
public function GetAccountFromCustomToken(string $sToken, bool $bValidateShortToken = true, bool $bQ = false, bool $bThrowExceptionOnFalse = false): ?Account
{
$oResult = null;
if (!empty($sToken)) {
$aAccountHash = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation
8 <= \count($aAccountHash) && // length checking
!empty($aAccountHash[7]) && // does short token exist
(!$bValidateShortToken || Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed
(isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10]))
) {
$oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3],
empty($aAccountHash[5]) ? '' : $aAccountHash[5], empty($aAccountHash[11]) ? '' : $aAccountHash[11], $bThrowExceptionOnFalse);
if ($oAccount) {
// init proxy user/password
if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) {
$oAccount->SetProxyAuthUser($aAccountHash[8]);
$oAccount->SetProxyAuthPassword($aAccountHash[9]);
}
$this->Logger()->AddSecret($oAccount->Password());
$this->Logger()->AddSecret($oAccount->ProxyAuthPassword());
$oAccount->SetParentEmail($aAccountHash[6]);
$oResult = $oAccount;
}
} else if ($bThrowExceptionOnFalse) {
throw new ClientException(Notifications::AuthError);
}
$aToken = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
return Account::NewInstanceFromTokenArray(
$this,
$bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken),
$bThrowExceptionOnFalse
);
}
if ($bThrowExceptionOnFalse && !$oResult) {
throw new ClientException(Notifications::AuthError);
if ($bThrowExceptionOnFalse) {
throw new ClientException(\RainLoop\Notifications::AuthError);
}
return $oResult;
}
protected function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Account
{
$oAccount = null;
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
$oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
if ($oDomain) {
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
$oAccount = new Account($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, '', '', $sClientCert);
$this->Plugins()->RunHook('filter.account', array($oAccount));
if ($bThrowProvideException && !$oAccount) {
throw new ClientException(Notifications::AuthError);
}
} else if ($bThrowProvideException) {
throw new ClientException(Notifications::AccountNotAllowed);
}
} else if ($bThrowProvideException) {
throw new ClientException(Notifications::DomainNotAllowed);
}
}
return $oAccount;
}
public function GetSpecAuthTokenCookie(): string
{
return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
}
// rlspecauth / AuthAccountHash
public function getAuthAccountHash() : string
{
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
if (empty($sAuthAccountHash)) {
$oAccount = $this->GetAccountFromSignMeToken();
if ($oAccount) try
{
$this->CheckMailConnection($oAccount);
$this->AuthToken($oAccount);
$sAuthAccountHash = $this->GetSpecAuthToken();
}
catch (\Throwable $oException)
{
$oException = null;
$this->ClearSignMeData($oAccount);
}
}
$this->SetSpecAuthToken($sAuthAccountHash);
}
return $this->GetSpecAuthToken();
}
private function getLocalAuthToken(): string
{
$sToken = $this->GetSpecAuthToken();
return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : '';
return null;
}
public function GetShortLifeSpecAuthToken(int $iLife = 60): string
{
$aAccountHash = Utils::DecodeKeyValues($this->getLocalAuthToken());
$aAccountHash = Utils::DecodeKeyValues($this->sSpecAuthToken);
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0]) {
$aAccountHash[10] = \time() + $iLife;
return Utils::EncodeKeyValues($aAccountHash);
@ -256,126 +176,141 @@ trait UserAuth
*/
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
{
return $this->GetAccountFromCustomToken($this->getLocalAuthToken(), $bThrowExceptionOnFalse, true, true);
}
public function GetSpecAuthToken(): string
{
return $this->sSpecAuthToken;
}
public function SetSpecAuthToken(string $sSpecAuthToken): self
{
$this->sSpecAuthToken = $sSpecAuthToken;
return $this;
if (!\is_string($this->sSpecAuthToken)) {
$this->sSpecAuthToken = '';
if (isset($_COOKIE[self::AUTH_SPEC_LOGOUT_TOKEN_KEY])) {
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
// Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
} else {
$sAuthAccountHash = Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY);
if (empty($sAuthAccountHash)) {
$oAccount = $this->GetAccountFromSignMeToken();
if ($oAccount) {
$this->SetAuthToken($oAccount);
$sAuthAccountHash = $this->sSpecAuthToken;
}
}
$this->sSpecAuthToken = $sAuthAccountHash ?: '';
}
}
return $this->GetAccountFromCustomToken($this->sSpecAuthToken, true, true, $bThrowExceptionOnFalse);
}
public function SetAuthToken(Account $oAccount): void
{
$sSpecAuthToken = '_' . $oAccount->GetAuthTokenQ();
$sSpecAuthToken = $oAccount->GetAuthTokenQ();
$this->SetSpecAuthToken($sSpecAuthToken);
$this->sSpecAuthToken = $sSpecAuthToken;
Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken);
if ($oAccount->SignMe()) {
$this->SetSignMeToken($oAccount);
if (isset($aAccounts[$oAccount->Email()])) {
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
$this->SetAccounts($oAccount, $aAccounts);
}
}
private static function GetSignMeToken(): ?array
{
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, '');
return empty($sSignMeToken) ? null : Utils::DecodeKeyValuesQ($sSignMeToken);
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
if (empty($sSignMeToken)) {
return null;
}
$aResult = \SnappyMail\Crypt::DecryptCookie($sSignMeToken);
return \is_array($aResult) ? $aResult : null;
}
private static function SetSignMeTokenCookie(array $aData): void
{
Utils::SetCookie(
self::AUTH_SIGN_ME_TOKEN_KEY,
\SnappyMail\Crypt::EncryptCookie($aData),
\time() + 3600 * 24 * 30 // 30 days
);
}
private function SetSignMeToken(Account $oAccount): void
{
$this->ClearSignMeData($oAccount);
$this->ClearSignMeData();
$uuid = \SnappyMail\UUID::generate();
$salt = \sha1(\random_bytes(16));
Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY,
Utils::EncodeKeyValuesQ(array(
$data = \SnappyMail\Crypt::EncryptRaw($oAccount);
if ($data['xxtea']) {
static::SetSignMeTokenCookie(array(
'e' => $oAccount->Email(),
'u' => $uuid,
's' => $salt
)),
\time() + 3600 * 24 * 30); // 30 days
's' => \base64_encode($data[1])
));
} else {
static::SetSignMeTokenCookie(array(
'e' => $oAccount->Email(),
'u' => $uuid,
'i' => \base64_encode($data[1])
));
}
$this->StorageProvider()->Put($oAccount,
$this->StorageProvider()->Put(
$oAccount,
StorageType::SIGN_ME,
$uuid,
Utils::EncryptString($oAccount->GetAuthToken(), $salt)
$data[2]
);
}
public function GetAccountFromSignMeToken(): ?Account
{
$oAccount = null;
$aTokenData = static::GetSignMeToken();
if (!empty($aTokenData)) {
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && !empty($aTokenData['s']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
$sAuthToken = $this->StorageProvider()->Get($aTokenData['e'],
$oAccount = null;
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
$sAuthToken = $this->StorageProvider()->Get(
$aTokenData['e'],
StorageType::SIGN_ME,
$aTokenData['u']
);
if (empty($sAuthToken)) {
return null;
}
$sAuthToken = Utils::DecryptString($sAuthToken, $aTokenData['s']);
if (!empty($sAuthToken)) {
$oAccount = $this->GetAccountFromCustomToken($sAuthToken, false, false, true);
if (!empty($aTokenData['s'])) {
$aAccountHash = \SnappyMail\Crypt::XxteaDecrypt($sAuthToken, \base64_decode($aTokenData['s']));
} else if (!empty($aTokenData['i'])) {
$aAccountHash = \SnappyMail\Crypt::OpenSSLDecrypt($sAuthToken, \base64_decode($aTokenData['i']));
}
} else if (!empty($aTokenData['e']) && !empty($aTokenData['t'])) {
// This is old, see https://github.com/the-djmaze/snappymail/issues/126
$sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'],
StorageType::CONFIG,
'sign_me'
);
if (!empty($sTokenSettings)) {
$aSignMeData = Utils::DecodeKeyValuesQ($sTokenSettings);
if (!empty($aSignMeData['AuthToken']) &&
!empty($aSignMeData['SignMeToken']) &&
$aSignMeData['SignMeToken'] === $aTokenData['t']) {
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false, true);
if (!empty($aAccountHash) && \is_array($aAccountHash)) {
$oAccount = Account::NewInstanceFromTokenArray($this, $aAccountHash);
if ($oAccount) {
try
{
$this->CheckMailConnection($oAccount);
// Update lifetime
$this->SetSignMeToken($oAccount);
return $oAccount;
}
catch (\Throwable $oException)
{
}
}
}
}
if ($oAccount) {
// Update lifetime
$this->SetSignMeToken($oAccount);
}
} else {
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
}
return $oAccount;
$this->ClearSignMeData();
return null;
}
protected function ClearSignMeData(Account $oAccount) : void
protected function ClearSignMeData() : void
{
if ($oAccount) {
if (isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY])) {
$aTokenData = static::GetSignMeToken();
if (!empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
$this->StorageProvider()->Clear($oAccount, StorageType::SIGN_ME, $aTokenData['u']);
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
$this->StorageProvider()->Clear($aTokenData['e'], StorageType::SIGN_ME, $aTokenData['u']);
}
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
$this->StorageProvider()->Clear($oAccount, StorageType::CONFIG, 'sign_me');
}
}
public function GetSpecAuthLogoutTokenWithDeletion(): string
{
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, '');
if (\strlen($sResult)) {
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
}
return $sResult;
}
public function SetAuthLogoutToken(): void
{
\header('X-RainLoop-Action: Logout');

View file

@ -2,7 +2,10 @@
namespace RainLoop\Model;
class Account
use \RainLoop\Utils;
use \RainLoop\Exceptions\ClientException;
class Account implements \JsonSerializable
{
/**
* @var string
@ -22,23 +25,18 @@ class Account
/**
* @var string
*/
private $sProxyAuthUser;
private $sProxyAuthUser = '';
/**
* @var string
*/
private $sProxyAuthPassword;
private $sProxyAuthPassword = '';
/**
* @var string
*/
private $sClientCert;
/**
* @var string
*/
private $sSignMeToken;
/**
* @var \RainLoop\Model\Domain
*/
@ -47,21 +45,7 @@ class Account
/**
* @var string
*/
private $sParentEmail;
function __construct(string $sEmail, string $sLogin, string $sPassword, Domain $oDomain,
string $sSignMeToken = '', string $sProxyAuthUser = '', string $sProxyAuthPassword = '', string $sClientCert = '')
{
$this->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
$this->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
$this->sPassword = $sPassword;
$this->oDomain = $oDomain;
$this->sSignMeToken = $sSignMeToken;
$this->sProxyAuthUser = $sProxyAuthUser;
$this->sProxyAuthPassword = $sProxyAuthPassword;
$this->sClientCert = $sClientCert;
$this->sParentEmail = '';
}
private $sParentEmail = '';
public function Email() : string
{
@ -85,12 +69,12 @@ class Account
public function ParentEmailHelper() : string
{
return \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail;
return $this->sParentEmail ?: $this->sEmail;
}
public function IsAdditionalAccount() : string
public function IsAdditionalAccount() : bool
{
return \strlen($this->sParentEmail);
return !empty($this->sParentEmail);
}
public function IncLogin() : string
@ -135,16 +119,6 @@ class Account
return $this->sClientCert;
}
public function SignMe() : bool
{
return \strlen($this->sSignMeToken);
}
public function SignMeToken() : string
{
return $this->sSignMeToken;
}
public function Domain() : Domain
{
return $this->oDomain;
@ -152,8 +126,13 @@ class Account
public function Hash() : string
{
return \md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost().
APP_SALT.$this->DomainIncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT);
return \md5(\implode(APP_SALT, [
$this->sEmail,
$this->Domain()->IncHost(),
$this->Domain()->IncPort(),
$this->sPassword,
$this->sParentEmail
]));
}
public function SetPassword(string $sPassword) : void
@ -176,106 +155,108 @@ class Account
$this->sProxyAuthPassword = $sProxyAuthPassword;
}
public function DomainIncHost() : string
public function jsonSerialize()
{
return $this->Domain()->IncHost();
}
public function DomainIncPort() : int
{
return $this->Domain()->IncPort();
}
public function DomainIncSecure() : int
{
return $this->Domain()->IncSecure();
}
public function DomainOutHost() : string
{
return $this->Domain()->OutHost();
}
public function DomainOutPort() : int
{
return $this->Domain()->OutPort();
}
public function DomainOutSecure() : int
{
return $this->Domain()->OutSecure();
}
public function DomainOutAuth() : bool
{
return $this->Domain()->OutAuth();
}
public function DomainSieveHost() : string
{
return $this->Domain()->SieveHost();
}
public function DomainSievePort() : int
{
return $this->Domain()->SievePort();
}
public function DomainSieveSecure() : int
{
return $this->Domain()->SieveSecure();
return array(
'account', // 0
$this->sEmail, // 1
$this->sLogin, // 2
$this->sPassword, // 3
$this->sClientCert, // 4
$this->sParentEmail, // 5
$this->sProxyAuthUser, // 6
$this->sProxyAuthPassword // 7
);
}
public function GetAuthToken() : string
{
return \RainLoop\Utils::EncodeKeyValues(array(
'token', // 0
$this->sEmail, // 1
$this->sLogin, // 2
$this->sPassword, // 3
\RainLoop\Utils::Fingerprint(), // 4
$this->sSignMeToken, // 5
$this->sParentEmail, // 6
\RainLoop\Utils::GetShortToken(), // 7
$this->sProxyAuthUser, // 8
$this->sProxyAuthPassword, // 9
0, // 10 // lifetime
$this->sClientCert // 11
));
return Utils::EncodeKeyValues($this->jsonSerialize());
}
public function GetAuthTokenQ() : string
{
return \RainLoop\Utils::EncodeKeyValuesQ(array(
'token', // 0
$this->sEmail, // 1
$this->sLogin, // 2
$this->sPassword, // 3
\RainLoop\Utils::Fingerprint(), // 4
$this->sSignMeToken, // 5
$this->sParentEmail, // 6
\RainLoop\Utils::GetShortToken(), // 7
$this->sProxyAuthUser, // 8
$this->sProxyAuthPassword, // 9
0, // 10 // lifetime
$this->sClientCert // 11
));
return Utils::EncodeKeyValuesQ($this->jsonSerialize());
return \MailSo\Base\Utils::UrlSafeBase64Encode(
\MailSo\Base\Crypt::Encrypt(\json_encode($this), \sha1(APP_SALT))
);
}
public static function NewInstanceByLogin(\RainLoop\Actions $oActions, string $sEmail, string $sLogin, string $sPassword, string $sClientCert = '', bool $bThrowException = false): ?self
{
$oAccount = null;
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
if ($oDomain) {
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
$oAccount = new self;
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
$oAccount->sPassword = $sPassword;
$oAccount->oDomain = $oDomain;
$oAccount->sClientCert = $sClientCert;
$oActions->Plugins()->RunHook('filter.account', array($oAccount));
if ($bThrowException && !$oAccount) {
throw new ClientException(Notifications::AuthError);
}
} else if ($bThrowException) {
throw new ClientException(Notifications::AccountNotAllowed);
}
} else if ($bThrowException) {
throw new ClientException(Notifications::DomainNotAllowed);
}
}
return $oAccount;
}
public static function NewInstanceFromTokenArray(
\RainLoop\Actions $oActions,
array $aAccountHash,
bool $bThrowExceptionOnFalse = false): ?self
{
if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 8 === \count($aAccountHash)) {
$oAccount = static::NewInstanceByLogin(
$oActions,
$aAccountHash[1],
$aAccountHash[2],
$aAccountHash[3],
$aAccountHash[4] ?: '',
$bThrowExceptionOnFalse
);
if ($oAccount) {
// init proxy user/password
if (!empty($aAccountHash[6]) && !empty($aAccountHash[7])) {
$oAccount->SetProxyAuthUser($aAccountHash[6]);
$oAccount->SetProxyAuthPassword($aAccountHash[7]);
}
$oActions->Logger()->AddSecret($oAccount->Password());
$oActions->Logger()->AddSecret($oAccount->ProxyAuthPassword());
$oAccount->SetParentEmail($aAccountHash[5]);
return $oAccount;
}
}
return null;
}
public function IncConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool
{
$oImapClient = $oMailClient->ImapClient();
$aImapCredentials = array(
'UseConnect' => true,
'UseAuth' => true,
'Host' => $this->DomainIncHost(),
'Port' => $this->DomainIncPort(),
'Secure' => $this->DomainIncSecure(),
'Login' => $this->IncLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'ClientCert' => $this->ClientCert(),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
$aImapCredentials = \array_merge(
$this->Domain()->ImapSettings(),
array(
'Login' => $this->IncLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'ClientCert' => $this->ClientCert(),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
)
);
$oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, &$aImapCredentials));
@ -292,17 +273,15 @@ class Account
public function OutConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient, \RainLoop\Config\Application $oConfig, bool &$bUsePhpMail = false) : bool
{
$aSmtpCredentials = array(
'UseConnect' => !$bUsePhpMail,
'UseAuth' => $this->DomainOutAuth(),
'UsePhpMail' => $bUsePhpMail,
'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(),
'Host' => $this->DomainOutHost(),
'Port' => $this->DomainOutPort(),
'Secure' => $this->DomainOutSecure(),
'Login' => $this->OutLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
$aSmtpCredentials = \array_merge(
$this->Domain()->SmtpSettings(),
array(
'UseConnect' => !$bUsePhpMail,
'UsePhpMail' => $bUsePhpMail,
'Login' => $this->OutLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
)
);
$oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, &$aSmtpCredentials));
@ -321,16 +300,14 @@ class Account
public function SieveConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\ManageSieveClient $oSieveClient, \RainLoop\Config\Application $oConfig)
{
$aSieveCredentials = array(
'UseConnect' => true,
'UseAuth' => true,
'Host' => $this->DomainSieveHost(),
'Port' => $this->DomainSievePort(),
'Secure' => $this->DomainSieveSecure(),
'Login' => $this->IncLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true)
$aSieveCredentials = \array_merge(
$this->Domain()->SieveSettings(),
array(
'Login' => $this->IncLogin(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true)
)
);
$oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, &$aSieveCredentials));

View file

@ -396,27 +396,62 @@ class Domain implements \JsonSerializable
return true;
}
public function ImapSettings() : array
{
return array(
'UseConnect' => true,
'UseAuth' => true,
'Host' => $this->sIncHost,
'Port' => $this->iIncPort,
'Secure' => $this->iIncSecure,
);
}
public function SmtpSettings() : array
{
return array(
'UseConnect' => !$this->bOutUsePhpMail,
'UseAuth' => $this->bOutAuth,
'Host' => $this->sOutHost,
'Port' => $this->iOutPort,
'Secure' => $this->iOutSecure,
'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(),
'UsePhpMail' => $this->bOutUsePhpMail
);
}
public function SieveSettings() : array
{
return array(
'UseConnect' => true,
'UseAuth' => true,
'Host' => $this->sSieveHost,
'Port' => $this->iSievePort,
'Secure' => $this->iSieveSecure
);
}
public function ToSimpleJSON() : array
{
return array(
'Name' => $this->Name(),
'IncHost' => $this->IncHost(),
'IncPort' => $this->IncPort(),
'IncSecure' => $this->IncSecure(),
'IncShortLogin' => $this->IncShortLogin(),
'UseSieve' => $this->UseSieve(),
'SieveHost' => $this->SieveHost(),
'SievePort' => $this->SievePort(),
'SieveSecure' => $this->SieveSecure(),
'OutHost' => $this->OutHost(),
'OutPort' => $this->OutPort(),
'OutSecure' => $this->OutSecure(),
'OutShortLogin' => $this->OutShortLogin(),
'OutAuth' => $this->OutAuth(),
'OutSetSender' => $this->OutSetSender(),
'OutUsePhpMail' => $this->OutUsePhpMail(),
'WhiteList' => $this->WhiteList(),
'AliasName' => $this->AliasName()
'Name' => $this->sName,
'IncHost' => $this->sIncHost,
'IncPort' => $this->iIncPort,
'IncSecure' => $this->iIncSecure,
'IncShortLogin' => $this->bIncShortLogin,
'UseSieve' => $this->bUseSieve,
'SieveHost' => $this->sSieveHost,
'SievePort' => $this->iSievePort,
'SieveSecure' => $this->iSieveSecure,
'OutHost' => $this->sOutHost,
'OutPort' => $this->iOutPort,
'OutSecure' => $this->iOutSecure,
'OutShortLogin' => $this->bOutShortLogin,
'OutAuth' => $this->bOutAuth,
'OutSetSender' => $this->bOutSetSender,
'OutUsePhpMail' => $this->bOutUsePhpMail,
'WhiteList' => $this->sWhiteList,
'AliasName' => $this->sAliasName
);
}
@ -424,24 +459,24 @@ class Domain implements \JsonSerializable
{
return array(
// '@Object' => 'Object/Domain',
'Name' => \MailSo\Base\Utils::IdnToUtf8($this->Name()),
'IncHost' => \MailSo\Base\Utils::IdnToUtf8($this->IncHost()),
'IncPort' => $this->IncPort(),
'IncSecure' => $this->IncSecure(),
'IncShortLogin' => $this->IncShortLogin(),
'UseSieve' => $this->UseSieve(),
'SieveHost' => \MailSo\Base\Utils::IdnToUtf8($this->SieveHost()),
'SievePort' => $this->SievePort(),
'SieveSecure' => $this->SieveSecure(),
'OutHost' => \MailSo\Base\Utils::IdnToUtf8($this->OutHost()),
'OutPort' => $this->OutPort(),
'OutSecure' => $this->OutSecure(),
'OutShortLogin' => $this->OutShortLogin(),
'OutAuth' => $this->OutAuth(),
'OutSetSender' => $this->OutSetSender(),
'OutUsePhpMail' => $this->OutUsePhpMail(),
'WhiteList' => $this->WhiteList(),
'AliasName' => $this->AliasName()
'Name' => \MailSo\Base\Utils::IdnToUtf8($this->sName),
'IncHost' => \MailSo\Base\Utils::IdnToUtf8($this->sIncHost),
'IncPort' => $this->iIncPort,
'IncSecure' => $this->iIncSecure,
'IncShortLogin' => $this->bIncShortLogin,
'UseSieve' => $this->bUseSieve,
'SieveHost' => \MailSo\Base\Utils::IdnToUtf8($this->sSieveHost),
'SievePort' => $this->iSievePort,
'SieveSecure' => $this->iSieveSecure,
'OutHost' => \MailSo\Base\Utils::IdnToUtf8($this->sOutHost),
'OutPort' => $this->iOutPort,
'OutSecure' => $this->iOutSecure,
'OutShortLogin' => $this->bOutShortLogin,
'OutAuth' => $this->bOutAuth,
'OutSetSender' => $this->bOutSetSender,
'OutUsePhpMail' => $this->bOutUsePhpMail,
'WhiteList' => $this->sWhiteList,
'AliasName' => $this->sAliasName
);
}
}

View file

@ -29,54 +29,54 @@ class Storage extends \RainLoop\Providers\AbstractProvider
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
* @param mixed $sValue
*/
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool
{
if (!$this->verifyAccount($oAccount, $iStorageType))
if (!$this->verifyAccount($mAccount, $iStorageType))
{
return false;
}
return $this->oDriver->Put($oAccount, $iStorageType, $sKey, $sValue);
return $this->oDriver->Put($mAccount, $iStorageType, $sKey, $sValue);
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
* @param mixed $mDefault = false
*
* @return mixed
*/
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false)
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false)
{
if (!$this->verifyAccount($oAccount, $iStorageType))
if (!$this->verifyAccount($mAccount, $iStorageType))
{
return $mDefault;
}
return $this->oDriver->Get($oAccount, $iStorageType, $sKey, $mDefault);
return $this->oDriver->Get($mAccount, $iStorageType, $sKey, $mDefault);
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
*/
public function Clear($oAccount, int $iStorageType, string $sKey) : bool
public function Clear($mAccount, int $iStorageType, string $sKey) : bool
{
if (!$this->verifyAccount($oAccount, $iStorageType))
if (!$this->verifyAccount($mAccount, $iStorageType))
{
return false;
}
return $this->oDriver->Clear($oAccount, $iStorageType, $sKey);
return $this->oDriver->Clear($mAccount, $iStorageType, $sKey);
}
/**
* @param \RainLoop\Model\Account|string $oAccount
* @param \RainLoop\Model\Account|string $mAccount
*/
public function DeleteStorage($oAccount) : bool
public function DeleteStorage($mAccount) : bool
{
return $this->oDriver->DeleteStorage($oAccount);
return $this->oDriver->DeleteStorage($mAccount);
}
public function IsActive() : bool

View file

@ -29,24 +29,24 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
*/
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool
{
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey, true);
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey, true);
return $sFileName && false !== \file_put_contents($sFileName, $sValue);
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
* @param mixed $mDefault = false
*
* @return mixed
*/
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false)
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false)
{
$mValue = false;
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey);
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
if ($sFileName && \file_exists($sFileName)) {
$mValue = \file_get_contents($sFileName);
}
@ -55,12 +55,12 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
}
/**
* @param \RainLoop\Model\Account|string|null $oAccount
* @param \RainLoop\Model\Account|string|null $mAccount
*/
public function Clear($oAccount, int $iStorageType, string $sKey) : bool
public function Clear($mAccount, int $iStorageType, string $sKey) : bool
{
$mResult = true;
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey);
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
if ($sFileName && \file_exists($sFileName)) {
$mResult = \unlink($sFileName);
}
@ -69,11 +69,11 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
}
/**
* @param \RainLoop\Model\Account|string $oAccount
* @param \RainLoop\Model\Account|string $mAccount
*/
public function DeleteStorage($oAccount) : bool
public function DeleteStorage($mAccount) : bool
{
$sPath = $this->generateFileName($oAccount, StorageType::CONFIG, '', false, true);
$sPath = $this->generateFileName($mAccount, StorageType::CONFIG, '', false, true);
if ($sPath && \is_dir($sPath)) {
\MailSo\Base\Utils::RecRmDir($sPath);
}

View file

@ -5,22 +5,22 @@ namespace RainLoop\Providers\Storage;
interface IStorage
{
/**
* @param \RainLoop\Model\Account|null $oAccount
* @param \RainLoop\Model\Account|null $mAccount
*/
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool;
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool;
/**
* @param \RainLoop\Model\Account|null $oAccount
* @param \RainLoop\Model\Account|null $mAccount
* @param mixed $mDefault = false
*
* @return mixed
*/
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false);
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false);
/**
* @param \RainLoop\Model\Account|null $oAccount
* @param \RainLoop\Model\Account|null $mAccount
*/
public function Clear($oAccount, int $iStorageType, string $sKey) : bool;
public function Clear($mAccount, int $iStorageType, string $sKey) : bool;
public function IsLocal() : bool;
}

View file

@ -96,8 +96,6 @@ class Service
$bAdmin = true;
}
$bAdmin || $this->oActions->getAuthAccountHash();
if ($this->oHttp->IsPost())
{
$this->oHttp->ServerNoCache();

View file

@ -684,7 +684,7 @@ class ServiceActions
{
$oAccount = $this->oActions->LoginProcess($sEmail, $sPassword);
if ($oAccount instanceof Model\Account && $aAdditionalOptions)
if ($aAdditionalOptions)
{
$bNeedToSettings = false;
@ -711,7 +711,7 @@ class ServiceActions
}
}
$this->oActions->AuthToken($oAccount);
$this->oActions->SetAuthToken($oAccount);
$bLogout = !($oAccount instanceof Model\Account);
}
@ -746,7 +746,7 @@ class ServiceActions
try
{
$oAccount = $this->oActions->LoginProcess($sEmail, $sPassword);
$this->oActions->AuthToken($oAccount);
$this->oActions->SetAuthToken($oAccount);
$bLogout = !($oAccount instanceof Model\Account);
}
catch (\Throwable $oException)
@ -824,13 +824,13 @@ class ServiceActions
$aAccounts = $this->oActions->GetAccounts($oAccount);
if (isset($aAccounts[$sEmail]))
{
$oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false, false);
$oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false);
}
}
if ($oAccountToLogin)
{
$this->oActions->AuthToken($oAccountToLogin);
$this->oActions->SetAuthToken($oAccountToLogin);
}
}
}

View file

@ -16,15 +16,17 @@ class Utils
const
/**
* 30 days cookie
* Used by: ServiceProxyExternal, compileLogParams, GetCsrfToken
* To preven CSRF attacks on all requests.
*/
CONNECTION_TOKEN = 'rltoken',
CONNECTION_TOKEN = 'smtoken',
/**
* Session cookie
* Used by: GetAuthToken, GetAuthTokenQ, GetAccountFromCustomToken, EncryptStringQ, DecryptStringQ
*/
SHORT_TOKEN = 'rlsession';
SHORT_TOKEN = 'smsession';
public static function EncryptString(string $sString, string $sKey) : string
{
@ -96,14 +98,13 @@ class Utils
public static function Fingerprint() : string
{
return \md5($_SERVER['HTTP_USER_AGENT'] ?: 'RainLoopFingerprint');
return \sha1(\preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]));
}
public static function GetShortToken() : string
{
$sToken = static::GetCookie(self::SHORT_TOKEN, null);
if (!$sToken)
{
if (!$sToken) {
$sToken = \MailSo\Base\Utils::Sha1Rand(APP_SALT);
static::SetCookie(self::SHORT_TOKEN, $sToken, 0);
}

View file

@ -0,0 +1,100 @@
<?php
namespace SnappyMail;
abstract class Crypt
{
/**
* Or use 'aes-256-xts' ?
*/
const CIPHER = 'aes-256-cbc-hmac-sha1';
private static function Passphrase() : string
{
return \sha1(\preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]) . APP_SALT, true);
}
public static function DecryptCookie($sData)
{
return \json_decode(
\zlib_decode(
\MailSo\Base\Crypt::Decrypt(
\MailSo\Base\Utils::UrlSafeBase64Decode($sData),
static::Passphrase()
)
),
true
);
}
public static function EncryptCookie($mData) : string
{
return \MailSo\Base\Utils::UrlSafeBase64Encode(
\MailSo\Base\Crypt::Encrypt(
\zlib_encode(
\json_encode($mData),
ZLIB_ENCODING_RAW,
9
),
static::Passphrase()
)
);
}
public static function EncryptRaw($data) : array
{
if (\is_callable('openssl_encrypt')) {
$iv = \random_bytes(\openssl_cipher_iv_length(static::CIPHER));
$data = \openssl_encrypt(
\json_encode($data),
static::CIPHER,
static::Passphrase(),
OPENSSL_RAW_DATA,
$iv
);
return [static::CIPHER, $iv, $data];
}
$salt = \random_bytes(16);
return ['xxtea', $salt, static::XxteaEncrypt($data, $salt)];
}
public static function OpenSSLDecrypt(string $data, string $iv)
{
if (!$data || !$iv || !\is_callable('openssl_decrypt')) {
return null;
}
return \json_decode(\openssl_decrypt(
$data,
static::CIPHER,
static::Passphrase(),
OPENSSL_RAW_DATA,
$iv
), true);
}
public static function XxteaDecrypt(string $data, string $salt)
{
if (!$data || !$salt) {
return null;
}
$key = $salt . static::Passphrase();
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
{
if (!$data || !$salt) {
return null;
}
$data = \json_encode($data);
$key = $salt . static::Passphrase();
return \is_callable('xxtea_encrypt')
? \xxtea_encrypt($data, $key)
: \MailSo\Base\Xxtea::encrypt($data, $key);
}
}

View file

@ -17,6 +17,7 @@
'imagick' => extension_loaded('imagick'),
'intl' => function_exists('idn_to_ascii'),
'ldap' => extension_loaded('ldap'),
'OpenSSL' => extension_loaded('openssl'),
'mysql' => extension_loaded('pdo_mysql'),
'pgsql' => extension_loaded('pdo_pgsql'),
'sqlite' => extension_loaded('pdo_sqlite'),