Made message signing in Cryptography use SHA512 as the message content for... #1
13 changed files with 857 additions and 134 deletions
1
.idea/sqldialects.xml
generated
1
.idea/sqldialects.xml
generated
|
@ -6,6 +6,7 @@
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/sessions.sql" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/sessions.sql" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/CaptchaManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/CaptchaManager.php" dialect="MariaDB" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/EncryptionRecordsManager.php" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/RegisteredPeerManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/RegisteredPeerManager.php" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/ResolvedServersManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/ResolvedServersManager.php" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/SessionManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/SessionManager.php" dialect="MariaDB" />
|
||||||
|
|
|
@ -1,152 +1,172 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Socialbox\Classes\CliCommands;
|
namespace Socialbox\Classes\CliCommands;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use PDOException;
|
use PDOException;
|
||||||
use Socialbox\Abstracts\CacheLayer;
|
use Socialbox\Abstracts\CacheLayer;
|
||||||
use Socialbox\Classes\Configuration;
|
use Socialbox\Classes\Configuration;
|
||||||
use Socialbox\Classes\Cryptography;
|
use Socialbox\Classes\Cryptography;
|
||||||
use Socialbox\Classes\Database;
|
use Socialbox\Classes\Database;
|
||||||
use Socialbox\Classes\Logger;
|
use Socialbox\Classes\Logger;
|
||||||
use Socialbox\Classes\Resources;
|
use Socialbox\Classes\Resources;
|
||||||
use Socialbox\Enums\DatabaseObjects;
|
use Socialbox\Enums\DatabaseObjects;
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
use Socialbox\Interfaces\CliCommandInterface;
|
use Socialbox\Exceptions\DatabaseOperationException;
|
||||||
|
use Socialbox\Interfaces\CliCommandInterface;
|
||||||
|
use Socialbox\Managers\EncryptionRecordsManager;
|
||||||
|
|
||||||
class InitializeCommand implements CliCommandInterface
|
class InitializeCommand implements CliCommandInterface
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public static function execute(array $args): int
|
|
||||||
{
|
{
|
||||||
if(Configuration::getInstanceConfiguration()->isEnabled() === false && !isset($args['force']))
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public static function execute(array $args): int
|
||||||
{
|
{
|
||||||
$required_configurations = [
|
if(Configuration::getInstanceConfiguration()->isEnabled() === false && !isset($args['force']))
|
||||||
'database.host', 'database.port', 'database.username', 'database.password', 'database.name',
|
|
||||||
'instance.enabled', 'instance.domain', 'registration.*'
|
|
||||||
];
|
|
||||||
|
|
||||||
Logger::getLogger()->error('Socialbox is disabled. Use --force to initialize the instance or set `instance.enabled` to True in the configuration');
|
|
||||||
Logger::getLogger()->info('The reason you are required to do this is to allow you to configure the instance before enabling it');
|
|
||||||
Logger::getLogger()->info('The following configurations are required to be set before enabling the instance:');
|
|
||||||
foreach($required_configurations as $config)
|
|
||||||
{
|
{
|
||||||
Logger::getLogger()->info(sprintf(' - %s', $config));
|
$required_configurations = [
|
||||||
}
|
'database.host', 'database.port', 'database.username', 'database.password', 'database.name',
|
||||||
|
'instance.enabled', 'instance.domain', 'registration.*'
|
||||||
|
];
|
||||||
|
|
||||||
Logger::getLogger()->info('instance.private_key & instance.public_key are automatically generated if not set');
|
Logger::getLogger()->error('Socialbox is disabled. Use --force to initialize the instance or set `instance.enabled` to True in the configuration');
|
||||||
Logger::getLogger()->info('instance.domain is required to be set to the domain name of the instance');
|
Logger::getLogger()->info('The reason you are required to do this is to allow you to configure the instance before enabling it');
|
||||||
Logger::getLogger()->info('instance.rpc_endpoint is required to be set to the publicly accessible http rpc endpoint of this server');
|
Logger::getLogger()->info('The following configurations are required to be set before enabling the instance:');
|
||||||
Logger::getLogger()->info('registration.* are required to be set to allow users to register to the instance');
|
foreach($required_configurations as $config)
|
||||||
Logger::getLogger()->info('You will be given a DNS TXT record to set for the public key after the initialization process');
|
|
||||||
Logger::getLogger()->info('The configuration file can be edited using ConfigLib:');
|
|
||||||
Logger::getLogger()->info(' configlib --conf socialbox -e nano');
|
|
||||||
Logger::getLogger()->info('Or manually at:');
|
|
||||||
Logger::getLogger()->info(sprintf(' %s', Configuration::getConfigurationLib()->getPath()));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Configuration::getInstanceConfiguration()->getDomain() === null)
|
|
||||||
{
|
|
||||||
Logger::getLogger()->error('instance.domain is required but was not set');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Configuration::getInstanceConfiguration()->getRpcEndpoint() === null)
|
|
||||||
{
|
|
||||||
Logger::getLogger()->error('instance.rpc_endpoint is required but was not set');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger::getLogger()->info('Initializing Socialbox...');
|
|
||||||
if(Configuration::getCacheConfiguration()->isEnabled())
|
|
||||||
{
|
|
||||||
Logger::getLogger()->verbose('Clearing cache layer...');
|
|
||||||
CacheLayer::getInstance()->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(DatabaseObjects::casesOrdered() as $object)
|
|
||||||
{
|
|
||||||
Logger::getLogger()->verbose("Initializing database object {$object->value}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Database::getConnection()->exec(file_get_contents(Resources::getDatabaseResource($object)));
|
|
||||||
}
|
|
||||||
catch (PDOException $e)
|
|
||||||
{
|
|
||||||
// Check if the error code is for "table already exists"
|
|
||||||
if ($e->getCode() === '42S01')
|
|
||||||
{
|
{
|
||||||
Logger::getLogger()->warning("Database object {$object->value} already exists, skipping...");
|
Logger::getLogger()->info(sprintf(' - %s', $config));
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
Logger::getLogger()->info('instance.private_key & instance.public_key are automatically generated if not set');
|
||||||
|
Logger::getLogger()->info('instance.domain is required to be set to the domain name of the instance');
|
||||||
|
Logger::getLogger()->info('instance.rpc_endpoint is required to be set to the publicly accessible http rpc endpoint of this server');
|
||||||
|
Logger::getLogger()->info('registration.* are required to be set to allow users to register to the instance');
|
||||||
|
Logger::getLogger()->info('You will be given a DNS TXT record to set for the public key after the initialization process');
|
||||||
|
Logger::getLogger()->info('The configuration file can be edited using ConfigLib:');
|
||||||
|
Logger::getLogger()->info(' configlib --conf socialbox -e nano');
|
||||||
|
Logger::getLogger()->info('Or manually at:');
|
||||||
|
Logger::getLogger()->info(sprintf(' %s', Configuration::getConfigurationLib()->getPath()));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Configuration::getInstanceConfiguration()->getDomain() === null)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('instance.domain is required but was not set');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Configuration::getInstanceConfiguration()->getRpcEndpoint() === null)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('instance.rpc_endpoint is required but was not set');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::getLogger()->info('Initializing Socialbox...');
|
||||||
|
if(Configuration::getCacheConfiguration()->isEnabled())
|
||||||
|
{
|
||||||
|
Logger::getLogger()->verbose('Clearing cache layer...');
|
||||||
|
CacheLayer::getInstance()->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(DatabaseObjects::casesOrdered() as $object)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->verbose("Initializing database object {$object->value}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Database::getConnection()->exec(file_get_contents(Resources::getDatabaseResource($object)));
|
||||||
|
}
|
||||||
|
catch (PDOException $e)
|
||||||
|
{
|
||||||
|
// Check if the error code is for "table already exists"
|
||||||
|
if ($e->getCode() === '42S01')
|
||||||
|
{
|
||||||
|
Logger::getLogger()->warning("Database object {$object->value} already exists, skipping...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error("Failed to initialize database object {$object->value}: {$e->getMessage()}", $e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error("Failed to initialize database object {$object->value}: {$e->getMessage()}", $e);
|
Logger::getLogger()->error("Failed to initialize database object {$object->value}: {$e->getMessage()}", $e);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception $e)
|
|
||||||
{
|
|
||||||
Logger::getLogger()->error("Failed to initialize database object {$object->value}: {$e->getMessage()}", $e);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
if(
|
||||||
!Configuration::getInstanceConfiguration()->getPublicKey() ||
|
!Configuration::getInstanceConfiguration()->getPublicKey() ||
|
||||||
!Configuration::getInstanceConfiguration()->getPrivateKey() ||
|
!Configuration::getInstanceConfiguration()->getPrivateKey() ||
|
||||||
!Configuration::getInstanceConfiguration()->getEncryptionKey()
|
!Configuration::getInstanceConfiguration()->getEncryptionKeys()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger::getLogger()->info('Generating new key pair...');
|
||||||
|
$keyPair = Cryptography::generateKeyPair();
|
||||||
|
$encryptionKeys = Cryptography::randomKeyS(230, 314, Configuration::getInstanceConfiguration()->getEncryptionKeysCount());
|
||||||
|
}
|
||||||
|
catch (CryptographyException $e)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('Failed to generate cryptography values', $e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::getLogger()->info('Updating configuration...');
|
||||||
|
Configuration::getConfigurationLib()->set('instance.private_key', $keyPair->getPrivateKey());
|
||||||
|
Configuration::getConfigurationLib()->set('instance.public_key', $keyPair->getPublicKey());
|
||||||
|
Configuration::getConfigurationLib()->set('instance.encryption_keys', $encryptionKeys);
|
||||||
|
Configuration::getConfigurationLib()->save(); // Save
|
||||||
|
Configuration::reload(); // Reload
|
||||||
|
|
||||||
|
Logger::getLogger()->info(sprintf('Set the DNS TXT record for the domain %s to the following value:', Configuration::getInstanceConfiguration()->getDomain()));
|
||||||
|
Logger::getLogger()->info(sprintf("v=socialbox;sb-rpc=%s;sb-key=%s;",
|
||||||
|
Configuration::getInstanceConfiguration()->getRpcEndpoint(), $keyPair->getPublicKey()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger::getLogger()->info('Generating new key pair...');
|
if(EncryptionRecordsManager::getRecordCount() < Configuration::getInstanceConfiguration()->getEncryptionRecordsCount())
|
||||||
$keyPair = Cryptography::generateKeyPair();
|
{
|
||||||
$encryptionKey = Cryptography::randomBytes(230, 314);
|
Logger::getLogger()->info('Generating encryption records...');
|
||||||
|
EncryptionRecordsManager::generateRecords(Configuration::getInstanceConfiguration()->getEncryptionRecordsCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (CryptographyException $e)
|
catch (CryptographyException $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error('Failed to generate cryptography values', $e);
|
Logger::getLogger()->error('Failed to generate encryption records due to a cryptography exception', $e);
|
||||||
return 1;
|
}
|
||||||
|
catch (DatabaseOperationException $e)
|
||||||
|
{
|
||||||
|
Logger::getLogger()->error('Failed to generate encryption records due to a database error', $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::getLogger()->info('Updating configuration...');
|
// TODO: Create a host peer here?
|
||||||
Configuration::getConfigurationLib()->set('instance.private_key', $keyPair->getPrivateKey());
|
Logger::getLogger()->info('Socialbox has been initialized successfully');
|
||||||
Configuration::getConfigurationLib()->set('instance.public_key', $keyPair->getPublicKey());
|
return 0;
|
||||||
Configuration::getConfigurationLib()->set('instance.encryption_key', $encryptionKey);
|
|
||||||
Configuration::getConfigurationLib()->save();
|
|
||||||
|
|
||||||
Logger::getLogger()->info(sprintf('Set the DNS TXT record for the domain %s to the following value:', Configuration::getInstanceConfiguration()->getDomain()));
|
|
||||||
Logger::getLogger()->info(sprintf("v=socialbox;sb-rpc=%s;sb-key=%s;",
|
|
||||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(), $keyPair->getPublicKey()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Create a host peer here?
|
/**
|
||||||
Logger::getLogger()->info('Socialbox has been initialized successfully');
|
* @inheritDoc
|
||||||
return 0;
|
*/
|
||||||
}
|
public static function getHelpMessage(): string
|
||||||
|
{
|
||||||
|
return "Initialize Command - Initializes Socialbox for first-runs\n" .
|
||||||
|
"Usage: socialbox init [arguments]\n\n" .
|
||||||
|
"Arguments:\n" .
|
||||||
|
" --force - Forces the initialization process to run even the instance is disabled\n";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public static function getHelpMessage(): string
|
public static function getShortHelpMessage(): string
|
||||||
{
|
{
|
||||||
return "Initialize Command - Initializes Socialbox for first-runs\n" .
|
return "Initializes Socialbox for first-runs";
|
||||||
"Usage: socialbox init [arguments]\n\n" .
|
}
|
||||||
"Arguments:\n" .
|
}
|
||||||
" --force - Forces the initialization process to run even the instance is disabled\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public static function getShortHelpMessage(): string
|
|
||||||
{
|
|
||||||
return "Initializes Socialbox for first-runs";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,9 +34,11 @@ class Configuration
|
||||||
$config->setDefault('instance.enabled', false); // False by default, requires the user to enable it.
|
$config->setDefault('instance.enabled', false); // False by default, requires the user to enable it.
|
||||||
$config->setDefault('instance.domain', null);
|
$config->setDefault('instance.domain', null);
|
||||||
$config->setDefault('instance.rpc_endpoint', null);
|
$config->setDefault('instance.rpc_endpoint', null);
|
||||||
|
$config->setDefault('instance.encryption_keys_count', 5);
|
||||||
|
$config->setDefault('instance.encryption_record_count', 5);
|
||||||
$config->setDefault('instance.private_key', null);
|
$config->setDefault('instance.private_key', null);
|
||||||
$config->setDefault('instance.public_key', null);
|
$config->setDefault('instance.public_key', null);
|
||||||
$config->setDefault('instance.encryption_key', null);
|
$config->setDefault('instance.encryption_keys', null);
|
||||||
|
|
||||||
// Security Configuration
|
// Security Configuration
|
||||||
$config->setDefault('security.display_internal_exceptions', false);
|
$config->setDefault('security.display_internal_exceptions', false);
|
||||||
|
@ -89,6 +91,25 @@ class Configuration
|
||||||
self::$registrationConfiguration = new RegistrationConfiguration(self::$configuration->getConfiguration()['registration']);
|
self::$registrationConfiguration = new RegistrationConfiguration(self::$configuration->getConfiguration()['registration']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all configuration instances by setting them to null and then
|
||||||
|
* reinitializes the configurations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function reload(): void
|
||||||
|
{
|
||||||
|
self::$configuration = null;
|
||||||
|
self::$instanceConfiguration = null;
|
||||||
|
self::$securityConfiguration = null;
|
||||||
|
self::$databaseConfiguration = null;
|
||||||
|
self::$loggingConfiguration = null;
|
||||||
|
self::$cacheConfiguration = null;
|
||||||
|
self::$registrationConfiguration = null;
|
||||||
|
|
||||||
|
self::initializeConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current configuration array. If the configuration is not initialized,
|
* Retrieves the current configuration array. If the configuration is not initialized,
|
||||||
* it triggers the initialization process.
|
* it triggers the initialization process.
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
private bool $enabled;
|
private bool $enabled;
|
||||||
private ?string $domain;
|
private ?string $domain;
|
||||||
private ?string $rpcEndpoint;
|
private ?string $rpcEndpoint;
|
||||||
|
private int $encryptionKeysCount;
|
||||||
|
private int $encryptionRecordsCount;
|
||||||
private ?string $privateKey;
|
private ?string $privateKey;
|
||||||
private ?string $publicKey;
|
private ?string $publicKey;
|
||||||
private ?string $encryptionKey;
|
private ?array $encryptionKeys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor that initializes object properties with the provided data.
|
* Constructor that initializes object properties with the provided data.
|
||||||
|
@ -22,9 +24,11 @@
|
||||||
$this->enabled = (bool)$data['enabled'];
|
$this->enabled = (bool)$data['enabled'];
|
||||||
$this->domain = $data['domain'];
|
$this->domain = $data['domain'];
|
||||||
$this->rpcEndpoint = $data['rpc_endpoint'];
|
$this->rpcEndpoint = $data['rpc_endpoint'];
|
||||||
|
$this->encryptionKeysCount = $data['encryption_keys_count'];
|
||||||
|
$this->encryptionRecordsCount = $data['encryption_records_count'];
|
||||||
$this->privateKey = $data['private_key'];
|
$this->privateKey = $data['private_key'];
|
||||||
$this->publicKey = $data['public_key'];
|
$this->publicKey = $data['public_key'];
|
||||||
$this->encryptionKey = $data['encryption_key'];
|
$this->encryptionKeys = $data['encryption_keys'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,6 +59,26 @@
|
||||||
return $this->rpcEndpoint;
|
return $this->rpcEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the number of encryption keys.
|
||||||
|
*
|
||||||
|
* @return int The number of encryption keys.
|
||||||
|
*/
|
||||||
|
public function getEncryptionKeysCount(): int
|
||||||
|
{
|
||||||
|
return $this->encryptionKeysCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the number of encryption records.
|
||||||
|
*
|
||||||
|
* @return int The number of encryption records.
|
||||||
|
*/
|
||||||
|
public function getEncryptionRecordsCount(): int
|
||||||
|
{
|
||||||
|
return $this->encryptionRecordsCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the private key.
|
* Retrieves the private key.
|
||||||
*
|
*
|
||||||
|
@ -76,12 +100,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the encryption key.
|
* Retrieves the encryption keys.
|
||||||
*
|
*
|
||||||
* @return string|null The encryption key.
|
* @return array|null The encryption keys.
|
||||||
*/
|
*/
|
||||||
public function getEncryptionKey(): ?string
|
public function getEncryptionKeys(): ?array
|
||||||
{
|
{
|
||||||
return $this->encryptionKey;
|
return $this->encryptionKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRandomEncryptionKey(): string
|
||||||
|
{
|
||||||
|
return $this->encryptionKeys[array_rand($this->encryptionKeys)];
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -276,7 +276,7 @@ class Cryptography
|
||||||
* @return string A hexadecimal string representing the random byte sequence.
|
* @return string A hexadecimal string representing the random byte sequence.
|
||||||
* @throws CryptographyException If the random byte generation fails.
|
* @throws CryptographyException If the random byte generation fails.
|
||||||
*/
|
*/
|
||||||
public static function randomBytes(int $minLength, int $maxLength): string
|
public static function randomKey(int $minLength, int $maxLength): string
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -287,4 +287,24 @@ class Cryptography
|
||||||
throw new CryptographyException('Failed to generate random bytes: ' . $e->getMessage());
|
throw new CryptographyException('Failed to generate random bytes: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of random keys, each with a length within the specified range.
|
||||||
|
*
|
||||||
|
* @param int $minLength The minimum length for each random key.
|
||||||
|
* @param int $maxLength The maximum length for each random key.
|
||||||
|
* @param int $amount The number of random keys to generate.
|
||||||
|
* @return array An array of randomly generated keys.
|
||||||
|
* @throws CryptographyException If the random key generation fails.
|
||||||
|
*/
|
||||||
|
public static function randomKeys(int $minLength, int $maxLength, int $amount): array
|
||||||
|
{
|
||||||
|
$keys = [];
|
||||||
|
for($i = 0; $i < $amount; $i++)
|
||||||
|
{
|
||||||
|
$keys[] = self::randomKey($minLength, $maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keys;
|
||||||
|
}
|
||||||
}
|
}
|
96
src/Socialbox/Classes/SecuredPassword.php
Normal file
96
src/Socialbox/Classes/SecuredPassword.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Random\RandomException;
|
||||||
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
|
use Socialbox\Objects\Database\EncryptionRecord;
|
||||||
|
use Socialbox\Objects\Database\SecurePasswordRecord;
|
||||||
|
|
||||||
|
class SecuredPassword
|
||||||
|
{
|
||||||
|
public const string ENCRYPTION_ALGORITHM = 'aes-256-gcm';
|
||||||
|
public const int ITERATIONS = 500000; // Increased iterations for PBKDF2
|
||||||
|
public const int KEY_LENGTH = 256; // Increased key length
|
||||||
|
public const int PEPPER_LENGTH = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a password using a derived key and other cryptographic elements
|
||||||
|
* to ensure secure storage.
|
||||||
|
*
|
||||||
|
* @param string $peerUuid The unique identifier of the peer associated with the password.
|
||||||
|
* @param string $password The plain text password to be secured.
|
||||||
|
* @param EncryptionRecord $record The encryption record containing information such as
|
||||||
|
* the key, salt, and pepper required for encryption.
|
||||||
|
* @return SecurePasswordRecord Returns an object containing the encrypted password
|
||||||
|
* along with associated cryptographic data such as IV and tag.
|
||||||
|
* @throws CryptographyException Throws an exception if password encryption or
|
||||||
|
* cryptographic element generation fails.
|
||||||
|
* @throws \DateMalformedStringException
|
||||||
|
*/
|
||||||
|
public static function securePassword(string $peerUuid, string $password, EncryptionRecord $record): SecurePasswordRecord
|
||||||
|
{
|
||||||
|
$decrypted = $record->decrypt();
|
||||||
|
$saltedPassword = $decrypted->getSalt() . $password;
|
||||||
|
$derivedKey = hash_pbkdf2('sha512', $saltedPassword, $decrypted->getPepper(), self::ITERATIONS, self::KEY_LENGTH / 8, true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$iv = random_bytes(openssl_cipher_iv_length(self::ENCRYPTION_ALGORITHM));
|
||||||
|
}
|
||||||
|
catch (RandomException $e)
|
||||||
|
{
|
||||||
|
throw new CryptographyException("Failed to generate IV for password encryption", $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = null;
|
||||||
|
$encryptedPassword = openssl_encrypt($derivedKey, self::ENCRYPTION_ALGORITHM, base64_decode($decrypted->getKey()), OPENSSL_RAW_DATA, $iv, $tag);
|
||||||
|
|
||||||
|
if ($encryptedPassword === false)
|
||||||
|
{
|
||||||
|
throw new CryptographyException("Password encryption failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SecurePasswordRecord([
|
||||||
|
'peer_uuid' => $peerUuid,
|
||||||
|
'iv' => base64_encode($iv),
|
||||||
|
'encrypted_password' => base64_encode($encryptedPassword),
|
||||||
|
'encrypted_tag' => base64_encode($tag),
|
||||||
|
'updated' => (new DateTime())->setTimestamp(time())
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the provided password against the secured data and encryption records.
|
||||||
|
*
|
||||||
|
* @param string $input The user-provided password to be verified.
|
||||||
|
* @param SecurePasswordRecord $secured An array containing encrypted data required for verification.
|
||||||
|
* @param EncryptionRecord[] $encryptionRecords An array of encryption records used to perform decryption and validation.
|
||||||
|
* @return bool Returns true if the password matches the secured data; otherwise, returns false.
|
||||||
|
* @throws CryptographyException
|
||||||
|
*/
|
||||||
|
public static function verifyPassword(string $input, SecurePasswordRecord $secured, array $encryptionRecords): bool
|
||||||
|
{
|
||||||
|
foreach ($encryptionRecords as $record)
|
||||||
|
{
|
||||||
|
$decrypted = $record->decrypt();
|
||||||
|
$saltedInput = $decrypted->getSalt() . $input;
|
||||||
|
$derivedKey = hash_pbkdf2('sha512', $saltedInput, $decrypted->getPepper(), self::ITERATIONS, self::KEY_LENGTH / 8, true);
|
||||||
|
|
||||||
|
// Validation by re-encrypting and comparing
|
||||||
|
$encryptedTag = base64_decode($secured->getEncryptedTag());
|
||||||
|
$reEncryptedPassword = openssl_encrypt($derivedKey,
|
||||||
|
self::ENCRYPTION_ALGORITHM, base64_decode($decrypted->getKey()), OPENSSL_RAW_DATA,
|
||||||
|
base64_decode($secured->getIv()), $encryptedTag
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($reEncryptedPassword !== false && hash_equals($reEncryptedPassword, base64_decode($secured->getEncryptedPassword())))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
73
src/Socialbox/Classes/StandardMethods/Identify.php
Normal file
73
src/Socialbox/Classes/StandardMethods/Identify.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Classes\StandardMethods;
|
||||||
|
|
||||||
|
use Socialbox\Abstracts\Method;
|
||||||
|
use Socialbox\Classes\Configuration;
|
||||||
|
use Socialbox\Classes\Validator;
|
||||||
|
use Socialbox\Enums\StandardError;
|
||||||
|
use Socialbox\Exceptions\DatabaseOperationException;
|
||||||
|
use Socialbox\Exceptions\StandardException;
|
||||||
|
use Socialbox\Interfaces\SerializableInterface;
|
||||||
|
use Socialbox\Managers\RegisteredPeerManager;
|
||||||
|
use Socialbox\Managers\SessionManager;
|
||||||
|
use Socialbox\Objects\ClientRequest;
|
||||||
|
use Socialbox\Objects\RpcRequest;
|
||||||
|
|
||||||
|
class Identify extends Method
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
|
||||||
|
{
|
||||||
|
// Check if the username parameter exists
|
||||||
|
if(!$rpcRequest->containsParameter('username'))
|
||||||
|
{
|
||||||
|
return $rpcRequest->produceError(StandardError::RPC_INVALID_ARGUMENTS, 'Missing parameter \'username\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the username is valid
|
||||||
|
if(!Validator::validateUsername($rpcRequest->getParameter('username')))
|
||||||
|
{
|
||||||
|
return $rpcRequest->produceError(StandardError::INVALID_USERNAME, StandardError::INVALID_USERNAME->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the request has a Session UUID
|
||||||
|
if($request->getSessionUuid() === null)
|
||||||
|
{
|
||||||
|
return $rpcRequest->produceError(StandardError::SESSION_REQUIRED);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the session and check if it's already authenticated
|
||||||
|
$session = SessionManager::getSession($request->getSessionUuid());
|
||||||
|
|
||||||
|
// If the session is already authenticated, return an error
|
||||||
|
if($session->getPeerUuid() !== null)
|
||||||
|
{
|
||||||
|
return $rpcRequest->produceError(StandardError::ALREADY_AUTHENTICATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the username does not exist, return an error
|
||||||
|
if(!RegisteredPeerManager::usernameExists($rpcRequest->getParameter('username')))
|
||||||
|
{
|
||||||
|
return $rpcRequest->produceError(StandardError::NOT_REGISTERED, StandardError::NOT_REGISTERED->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create session to be identified as the provided username
|
||||||
|
SessionManager::updatePeer($session->getUuid(), $rpcRequest->getParameter('username'));
|
||||||
|
|
||||||
|
// Set the required session flags
|
||||||
|
$initialFlags = [];
|
||||||
|
}
|
||||||
|
catch(DatabaseOperationException $e)
|
||||||
|
{
|
||||||
|
throw new StandardException("There was an unexpected error while trying to register", StandardError::INTERNAL_SERVER_ERROR, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true to indicate the operation was a success
|
||||||
|
return $rpcRequest->produceResponse(true);
|
||||||
|
}
|
||||||
|
}
|
205
src/Socialbox/Managers/EncryptionRecordsManager.php
Normal file
205
src/Socialbox/Managers/EncryptionRecordsManager.php
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Managers;
|
||||||
|
|
||||||
|
use PDOException;
|
||||||
|
use Random\RandomException;
|
||||||
|
use Socialbox\Classes\Configuration;
|
||||||
|
use Socialbox\Classes\Database;
|
||||||
|
use Socialbox\Classes\SecuredPassword;
|
||||||
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
|
use Socialbox\Exceptions\DatabaseOperationException;
|
||||||
|
use Socialbox\Objects\Database\EncryptionRecord;
|
||||||
|
|
||||||
|
class EncryptionRecordsManager
|
||||||
|
{
|
||||||
|
private const int KEY_LENGTH = 256; // Increased key length
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the total count of records in the encryption_records table.
|
||||||
|
*
|
||||||
|
* @return int The number of records in the encryption_records table.
|
||||||
|
* @throws DatabaseOperationException If a database operation error occurs while fetching the record count.
|
||||||
|
*/
|
||||||
|
public static function getRecordCount(): int
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stmt = Database::getConnection()->prepare('SELECT COUNT(*) FROM encryption_records');
|
||||||
|
$stmt->execute();
|
||||||
|
return $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
catch (PDOException $e)
|
||||||
|
{
|
||||||
|
throw new DatabaseOperationException('Failed to retrieve encryption record count', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new encryption record into the encryption_records table.
|
||||||
|
*
|
||||||
|
* @param EncryptionRecord $record The encryption record to insert, containing data, IV, and tag.
|
||||||
|
* @return void
|
||||||
|
* @throws DatabaseOperationException If the insertion into the database fails.
|
||||||
|
*/
|
||||||
|
private static function insertRecord(EncryptionRecord $record): void
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stmt = Database::getConnection()->prepare('INSERT INTO encryption_records (data, iv, tag) VALUES (?, ?, ?)');
|
||||||
|
|
||||||
|
$data = $record->getData();
|
||||||
|
$stmt->bindParam(1, $data);
|
||||||
|
|
||||||
|
$iv = $record->getIv();
|
||||||
|
$stmt->bindParam(2, $iv);
|
||||||
|
|
||||||
|
$tag = $record->getTag();
|
||||||
|
$stmt->bindParam(3, $tag);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
catch(PDOException $e)
|
||||||
|
{
|
||||||
|
|
||||||
|
throw new DatabaseOperationException('Failed to insert encryption record into the database', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a random encryption record from the database.
|
||||||
|
*
|
||||||
|
* @return EncryptionRecord An instance of EncryptionRecord containing the data of a randomly selected record.
|
||||||
|
* @throws DatabaseOperationException If an error occurs while attempting to retrieve the record from the database.
|
||||||
|
*/
|
||||||
|
public static function getRandomRecord(): EncryptionRecord
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stmt = Database::getConnection()->prepare('SELECT * FROM encryption_records ORDER BY RAND() LIMIT 1');
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
return new EncryptionRecord($data);
|
||||||
|
}
|
||||||
|
catch(PDOException $e)
|
||||||
|
{
|
||||||
|
throw new DatabaseOperationException('Failed to retrieve a random encryption record', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all encryption records from the database.
|
||||||
|
*
|
||||||
|
* @return EncryptionRecord[] An array of EncryptionRecord instances, each representing a record from the database.
|
||||||
|
* @throws DatabaseOperationException If an error occurs while attempting to retrieve the records from the database.
|
||||||
|
*/
|
||||||
|
public static function getAllRecords(): array
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stmt = Database::getConnection()->prepare('SELECT * FROM encryption_records');
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$records = [];
|
||||||
|
foreach ($data as $record)
|
||||||
|
{
|
||||||
|
$records[] = new EncryptionRecord($record);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $records;
|
||||||
|
}
|
||||||
|
catch(PDOException $e)
|
||||||
|
{
|
||||||
|
throw new DatabaseOperationException('Failed to retrieve all encryption records', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates encryption records and inserts them into the database until the specified total count is reached.
|
||||||
|
*
|
||||||
|
* @param int $count The total number of encryption records desired in the database.
|
||||||
|
* @return int The number of new records that were created and inserted.
|
||||||
|
* @throws CryptographyException
|
||||||
|
* @throws DatabaseOperationException
|
||||||
|
*/
|
||||||
|
public static function generateRecords(int $count): int
|
||||||
|
{
|
||||||
|
$currentCount = self::getRecordCount();
|
||||||
|
if($currentCount >= $count)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$created = 0;
|
||||||
|
for($i = 0; $i < $count - $currentCount; $i++)
|
||||||
|
{
|
||||||
|
self::insertRecord(self::generateEncryptionRecord());
|
||||||
|
$created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new encryption record containing a key, pepper, and salt.
|
||||||
|
*
|
||||||
|
* @return EncryptionRecord An instance of EncryptionRecord containing an encrypted structure
|
||||||
|
* with the generated key, pepper, and salt.
|
||||||
|
* @throws CryptographyException If random byte generation fails during the creation of the encryption record.
|
||||||
|
*/
|
||||||
|
private static function generateEncryptionRecord(): EncryptionRecord
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$key = random_bytes(self::KEY_LENGTH / 8);
|
||||||
|
$pepper = bin2hex(random_bytes(SecuredPassword::PEPPER_LENGTH / 2));
|
||||||
|
$salt = bin2hex(random_bytes(self::KEY_LENGTH / 16));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (RandomException $e)
|
||||||
|
{
|
||||||
|
throw new CryptographyException("Random bytes generation failed", $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::encrypt(['key' => base64_encode($key), 'pepper' => $pepper, 'salt' => $salt,]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the given vault item and returns an EncryptionRecord containing the encrypted data.
|
||||||
|
*
|
||||||
|
* @param array $vaultItem The associative array representing the vault item to be encrypted.
|
||||||
|
* @return EncryptionRecord An instance of EncryptionRecord containing the encrypted vault data, initialization vector (IV), and authentication tag.
|
||||||
|
* @throws CryptographyException If the initialization vector generation or vault encryption process fails.
|
||||||
|
*/
|
||||||
|
private static function encrypt(array $vaultItem): EncryptionRecord
|
||||||
|
{
|
||||||
|
$serializedVault = json_encode($vaultItem);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$iv = random_bytes(openssl_cipher_iv_length(SecuredPassword::ENCRYPTION_ALGORITHM));
|
||||||
|
}
|
||||||
|
catch (RandomException $e)
|
||||||
|
{
|
||||||
|
throw new CryptographyException("IV generation failed", $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
$tag = null;
|
||||||
|
|
||||||
|
$encryptedVault = openssl_encrypt($serializedVault, SecuredPassword::ENCRYPTION_ALGORITHM,
|
||||||
|
Configuration::getInstanceConfiguration()->getRandomEncryptionKey(), OPENSSL_RAW_DATA, $iv, $tag
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($encryptedVault === false)
|
||||||
|
{
|
||||||
|
throw new CryptographyException("Vault encryption failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EncryptionRecord([
|
||||||
|
'data' => base64_encode($encryptedVault),
|
||||||
|
'iv' => base64_encode($iv),
|
||||||
|
'tag' => base64_encode($tag),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
41
src/Socialbox/Objects/Database/DecryptedRecord.php
Normal file
41
src/Socialbox/Objects/Database/DecryptedRecord.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Objects\Database;
|
||||||
|
|
||||||
|
class DecryptedRecord
|
||||||
|
{
|
||||||
|
private string $key;
|
||||||
|
private string $pepper;
|
||||||
|
private string $salt;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->key = $data['key'];
|
||||||
|
$this->pepper = $data['pepper'];
|
||||||
|
$this->salt = $data['salt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKey(): string
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPepper(): string
|
||||||
|
{
|
||||||
|
return $this->pepper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSalt(): string
|
||||||
|
{
|
||||||
|
return $this->salt;
|
||||||
|
}
|
||||||
|
}
|
84
src/Socialbox/Objects/Database/EncryptionRecord.php
Normal file
84
src/Socialbox/Objects/Database/EncryptionRecord.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Objects\Database;
|
||||||
|
|
||||||
|
use Socialbox\Classes\Configuration;
|
||||||
|
use Socialbox\Classes\SecuredPassword;
|
||||||
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
|
use Socialbox\Managers\EncryptionRecordsManager;
|
||||||
|
|
||||||
|
class EncryptionRecord
|
||||||
|
{
|
||||||
|
private string $data;
|
||||||
|
private string $iv;
|
||||||
|
private string $tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor for the EncryptionRecord
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->data = $data['data'];
|
||||||
|
$this->iv = $data['iv'];
|
||||||
|
$this->tag = $data['tag'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the stored data.
|
||||||
|
*
|
||||||
|
* @return string The stored data.
|
||||||
|
*/
|
||||||
|
public function getData(): string
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the initialization vector (IV).
|
||||||
|
*
|
||||||
|
* @return string The initialization vector.
|
||||||
|
*/
|
||||||
|
public function getIv(): string
|
||||||
|
{
|
||||||
|
return $this->iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tag.
|
||||||
|
*
|
||||||
|
* @return string The tag.
|
||||||
|
*/
|
||||||
|
public function getTag(): string
|
||||||
|
{
|
||||||
|
return $this->tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the encrypted record using available encryption keys.
|
||||||
|
*
|
||||||
|
* Iterates through the configured encryption keys to attempt decryption of the data.
|
||||||
|
* If successful, returns a DecryptedRecord object with the decrypted data.
|
||||||
|
* Throws an exception if decryption fails with all available keys.
|
||||||
|
*
|
||||||
|
* @return DecryptedRecord The decrypted record containing the original data.
|
||||||
|
* @throws CryptographyException If decryption fails with all provided keys.
|
||||||
|
*/
|
||||||
|
public function decrypt(): DecryptedRecord
|
||||||
|
{
|
||||||
|
foreach(Configuration::getInstanceConfiguration()->getEncryptionKeys() as $encryptionKey)
|
||||||
|
{
|
||||||
|
$decryptedVault = openssl_decrypt(base64_decode($this->data), SecuredPassword::ENCRYPTION_ALGORITHM,
|
||||||
|
$encryptionKey, OPENSSL_RAW_DATA, base64_decode($this->iv), base64_decode($this->tag)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($decryptedVault !== false)
|
||||||
|
{
|
||||||
|
return new DecryptedRecord(json_decode($decryptedVault, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CryptographyException("Decryption failed");
|
||||||
|
}
|
||||||
|
}
|
84
src/Socialbox/Objects/Database/SecurePasswordRecord.php
Normal file
84
src/Socialbox/Objects/Database/SecurePasswordRecord.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Objects\Database;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class SecurePasswordRecord
|
||||||
|
{
|
||||||
|
private string $peerUuid;
|
||||||
|
private string $iv;
|
||||||
|
private string $encryptedPassword;
|
||||||
|
private string $encryptedTag;
|
||||||
|
private DateTime $updated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to initialize the object with provided data.
|
||||||
|
*
|
||||||
|
* @param array $data An associative array containing keys:
|
||||||
|
* - 'peer_uuid': The UUID of the peer.
|
||||||
|
* - 'iv': The initialization vector.
|
||||||
|
* - 'encrypted_password': The encrypted password.
|
||||||
|
* - 'encrypted_tag': The encrypted tag.
|
||||||
|
*
|
||||||
|
* @throws \DateMalformedStringException
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->peerUuid = $data['peer_uuid'];
|
||||||
|
$this->iv = $data['iv'];
|
||||||
|
$this->encryptedPassword = $data['encrypted_password'];
|
||||||
|
$this->encryptedTag = $data['encrypted_tag'];
|
||||||
|
$this->updated = new DateTime($data['updated']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the UUID of the peer.
|
||||||
|
*
|
||||||
|
* @return string The UUID of the peer.
|
||||||
|
*/
|
||||||
|
public function getPeerUuid(): string
|
||||||
|
{
|
||||||
|
return $this->peerUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the initialization vector (IV) value.
|
||||||
|
*
|
||||||
|
* @return string The initialization vector.
|
||||||
|
*/
|
||||||
|
public function getIv(): string
|
||||||
|
{
|
||||||
|
return $this->iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the encrypted password.
|
||||||
|
*
|
||||||
|
* @return string The encrypted password.
|
||||||
|
*/
|
||||||
|
public function getEncryptedPassword(): string
|
||||||
|
{
|
||||||
|
return $this->encryptedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the encrypted tag.
|
||||||
|
*
|
||||||
|
* @return string The encrypted tag.
|
||||||
|
*/
|
||||||
|
public function getEncryptedTag(): string
|
||||||
|
{
|
||||||
|
return $this->encryptedTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the updated timestamp.
|
||||||
|
*
|
||||||
|
* @return DateTime The updated timestamp.
|
||||||
|
*/
|
||||||
|
public function getUpdated(): DateTime
|
||||||
|
{
|
||||||
|
return $this->updated;
|
||||||
|
}
|
||||||
|
}
|
22
tests/Socialbox/Classes/SecuredPasswordTest.php
Normal file
22
tests/Socialbox/Classes/SecuredPasswordTest.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Socialbox\Managers\EncryptionRecordsManager;
|
||||||
|
|
||||||
|
class SecuredPasswordTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testVerifyPassword()
|
||||||
|
{
|
||||||
|
print("Getting random encryption record\n");
|
||||||
|
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
||||||
|
var_dump($encryptionRecord);
|
||||||
|
|
||||||
|
print("Securing password\n");
|
||||||
|
$securedPassword = SecuredPassword::securePassword('123-123-123', 'password!', $encryptionRecord);
|
||||||
|
|
||||||
|
print("Verifying password\n");
|
||||||
|
$this->assertTrue(SecuredPassword::verifyPassword('password!', $securedPassword, EncryptionRecordsManager::getAllRecords()));
|
||||||
|
}
|
||||||
|
}
|
24
tests/test.php
Normal file
24
tests/test.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Socialbox\Classes\SecuredPassword;
|
||||||
|
use Socialbox\Managers\EncryptionRecordsManager;
|
||||||
|
|
||||||
|
require 'ncc';
|
||||||
|
import('net.nosial.socialbox');
|
||||||
|
|
||||||
|
print("Getting random encryption record\n");
|
||||||
|
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
||||||
|
var_dump($encryptionRecord);
|
||||||
|
|
||||||
|
print("Securing password\n");
|
||||||
|
$securedPassword = SecuredPassword::securePassword('123-123-123', 'password!', $encryptionRecord);
|
||||||
|
|
||||||
|
print("Verifying password\n");
|
||||||
|
if(SecuredPassword::verifyPassword('password!', $securedPassword, EncryptionRecordsManager::getAllRecords()))
|
||||||
|
{
|
||||||
|
print("Password verified\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("Password not verified\n");
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue