Made message signing in Cryptography use SHA512 as the message content for... #1
44 changed files with 2971 additions and 2016 deletions
2
.idea/socialbox-php.iml
generated
2
.idea/socialbox-php.iml
generated
|
@ -2,7 +2,7 @@
|
|||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea/dataSources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
|
|
4
.idea/sqldialects.xml
generated
4
.idea/sqldialects.xml
generated
|
@ -2,14 +2,14 @@
|
|||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/captcha_images.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/external_sessions.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/registered_peers.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/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/PasswordManager.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/ResolvedDnsRecordsManager.php" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/SessionManager.php" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/VariableManager.php" dialect="MariaDB" />
|
||||
</component>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"ext-redis": "*",
|
||||
"ext-memcached": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*"
|
||||
"ext-gd": "*",
|
||||
"ext-sodium": "*"
|
||||
}
|
||||
}
|
|
@ -1,46 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes\CliCommands;
|
||||
namespace Socialbox\Classes\CliCommands;
|
||||
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Logger;
|
||||
use Socialbox\Interfaces\CliCommandInterface;
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Logger;
|
||||
use Socialbox\Interfaces\CliCommandInterface;
|
||||
|
||||
class DnsRecordCommand implements CliCommandInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function execute(array $args): int
|
||||
class DnsRecordCommand implements CliCommandInterface
|
||||
{
|
||||
$txt_record = sprintf('v=socialbox;sb-rpc=%s;sb-key=%s',
|
||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(),
|
||||
Configuration::getInstanceConfiguration()->getPublicKey()
|
||||
);
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function execute(array $args): int
|
||||
{
|
||||
$txt_record = sprintf('v=socialbox;sb-rpc=%s;sb-key=%s',
|
||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(),
|
||||
Configuration::getCryptographyConfiguration()->getHostPublicKey()
|
||||
);
|
||||
|
||||
Logger::getLogger()->info('Please set the following DNS TXT record for the domain:');
|
||||
Logger::getLogger()->info(sprintf(' %s', $txt_record));
|
||||
return 0;
|
||||
}
|
||||
Logger::getLogger()->info('Please set the following DNS TXT record for the domain:');
|
||||
Logger::getLogger()->info(sprintf(' %s', $txt_record));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getHelpMessage(): string
|
||||
{
|
||||
return <<<HELP
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getHelpMessage(): string
|
||||
{
|
||||
return <<<HELP
|
||||
Usage: socialbox dns-record
|
||||
|
||||
Displays the DNS TXT record that should be set for the domain.
|
||||
HELP;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getShortHelpMessage(): string
|
||||
{
|
||||
return 'Displays the DNS TXT record that should be set for the domain.';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getShortHelpMessage(): string
|
||||
{
|
||||
return 'Displays the DNS TXT record that should be set for the domain.';
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
namespace Socialbox\Classes\CliCommands;
|
||||
|
||||
use Exception;
|
||||
use LogLib\Log;
|
||||
use PDOException;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
|
@ -13,9 +12,8 @@
|
|||
use Socialbox\Classes\Resources;
|
||||
use Socialbox\Enums\DatabaseObjects;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Interfaces\CliCommandInterface;
|
||||
use Socialbox\Managers\EncryptionRecordsManager;
|
||||
use Socialbox\Socialbox;
|
||||
|
||||
class InitializeCommand implements CliCommandInterface
|
||||
{
|
||||
|
@ -208,6 +206,7 @@
|
|||
Logger::getLogger()->info('cache.database defaulting to 0');
|
||||
}
|
||||
|
||||
Logger::getLogger()->info('Updating configuration...');
|
||||
Configuration::getConfigurationLib()->save(); // Save
|
||||
Configuration::reload(); // Reload
|
||||
}
|
||||
|
@ -261,16 +260,17 @@
|
|||
}
|
||||
|
||||
if(
|
||||
!Configuration::getInstanceConfiguration()->getPublicKey() ||
|
||||
!Configuration::getInstanceConfiguration()->getPrivateKey() ||
|
||||
!Configuration::getInstanceConfiguration()->getEncryptionKeys()
|
||||
!Configuration::getCryptographyConfiguration()->getHostPublicKey() ||
|
||||
!Configuration::getCryptographyConfiguration()->getHostPrivateKey() ||
|
||||
!Configuration::getCryptographyConfiguration()->getHostPublicKey()
|
||||
)
|
||||
{
|
||||
$expires = time() + 31536000;
|
||||
|
||||
try
|
||||
{
|
||||
Logger::getLogger()->info('Generating new key pair...');
|
||||
$keyPair = Cryptography::generateKeyPair();
|
||||
$encryptionKeys = Cryptography::randomKeyS(230, 314, Configuration::getInstanceConfiguration()->getEncryptionKeysCount());
|
||||
Logger::getLogger()->info('Generating new key pair (expires ' . date('Y-m-d H:i:s', $expires) . ')...');
|
||||
$signingKeyPair = Cryptography::generateSigningKeyPair();
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
|
@ -278,40 +278,35 @@
|
|||
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()
|
||||
));
|
||||
Configuration::getConfigurationLib()->set('cryptography.host_keypair_expires', $expires);
|
||||
Configuration::getConfigurationLib()->set('cryptography.host_private_key', $signingKeyPair->getPrivateKey());
|
||||
Configuration::getConfigurationLib()->set('cryptography.host_public_key', $signingKeyPair->getPublicKey());
|
||||
}
|
||||
|
||||
try
|
||||
// If Internal Encryption keys are null or has less keys than configured, populate the configuration
|
||||
// property with encryption keys.
|
||||
if(
|
||||
Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys() === null ||
|
||||
count(Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys()) < Configuration::getCryptographyConfiguration()->getEncryptionKeysCount())
|
||||
{
|
||||
if(EncryptionRecordsManager::getRecordCount() < Configuration::getInstanceConfiguration()->getEncryptionRecordsCount())
|
||||
Logger::getLogger()->info('Generating internal encryption keys...');
|
||||
$encryptionKeys = Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys() ?? [];
|
||||
while(count($encryptionKeys) < Configuration::getCryptographyConfiguration()->getEncryptionKeysCount())
|
||||
{
|
||||
Logger::getLogger()->info('Generating encryption records...');
|
||||
EncryptionRecordsManager::generateRecords(Configuration::getInstanceConfiguration()->getEncryptionRecordsCount());
|
||||
$encryptionKeys[] = Cryptography::generateEncryptionKey(Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||
}
|
||||
}
|
||||
catch (CryptographyException $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);
|
||||
return 1;
|
||||
|
||||
Configuration::getConfigurationLib()->set('cryptography.internal_encryption_keys', $encryptionKeys);
|
||||
}
|
||||
|
||||
// TODO: Create a host peer here?
|
||||
Logger::getLogger()->info('Updating configuration...');
|
||||
Configuration::getConfigurationLib()->save();;
|
||||
Configuration::reload();
|
||||
|
||||
Logger::getLogger()->info('Socialbox has been initialized successfully');
|
||||
Logger::getLogger()->info(sprintf('Set the DNS TXT record for the domain %s to the following value:', Configuration::getInstanceConfiguration()->getDomain()));
|
||||
Logger::getLogger()->info(Socialbox::getDnsRecord());
|
||||
|
||||
if(getenv('SB_MODE') === 'automated')
|
||||
{
|
||||
Configuration::getConfigurationLib()->set('instance.enabled', true);
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes\ClientCommands;
|
||||
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\Logger;
|
||||
use Socialbox\Classes\Utilities;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialbox\Exceptions\RpcException;
|
||||
use Socialbox\Interfaces\CliCommandInterface;
|
||||
use Socialbox\Objects\ClientSession;
|
||||
use Socialbox\SocialClient;
|
||||
|
||||
class ConnectCommand implements CliCommandInterface
|
||||
{
|
||||
public static function execute(array $args): int
|
||||
{
|
||||
if(!isset($args['name']))
|
||||
{
|
||||
Logger::getLogger()->error('The name argument is required, this is the name of the session');
|
||||
}
|
||||
|
||||
$workingDirectory = getcwd();
|
||||
|
||||
if(isset($args['directory']))
|
||||
{
|
||||
if(!is_dir($args['directory']))
|
||||
{
|
||||
Logger::getLogger()->error('The directory provided does not exist');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$workingDirectory = $args['directory'];
|
||||
}
|
||||
|
||||
$sessionFile = $workingDirectory . DIRECTORY_SEPARATOR . Utilities::sanitizeFileName($args['name']) . '.json';
|
||||
|
||||
if(!file_exists($sessionFile))
|
||||
{
|
||||
return self::createSession($args, $sessionFile);
|
||||
}
|
||||
|
||||
Logger::getLogger()->info(sprintf('Session file already exists at %s', $sessionFile));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static function createSession(array $args, string $sessionFile): int
|
||||
{
|
||||
if(!isset($args['domain']))
|
||||
{
|
||||
Logger::getLogger()->error('The domain argument is required, this is the domain of the socialbox instance');
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$client = new SocialClient($args['domain']);
|
||||
}
|
||||
catch (DatabaseOperationException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to create the client session', $e);
|
||||
return 1;
|
||||
}
|
||||
catch (ResolutionException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to resolve the domain', $e);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$keyPair = Cryptography::generateKeyPair();
|
||||
$session = $client->createSession($keyPair);
|
||||
}
|
||||
catch (CryptographyException | RpcException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to create the session', $e);
|
||||
return 1;
|
||||
}
|
||||
|
||||
$sessionData = new ClientSession([
|
||||
'domain' => $args['domain'],
|
||||
'session_uuid' => $session,
|
||||
'public_key' => $keyPair->getPublicKey(),
|
||||
'private_key' => $keyPair->getPrivateKey()
|
||||
]);
|
||||
|
||||
$sessionData->save($sessionFile);
|
||||
Logger::getLogger()->info(sprintf('Session created and saved to %s', $sessionFile));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function getHelpMessage(): string
|
||||
{
|
||||
return <<<HELP
|
||||
Usage: socialbox connect --name <name> --domain <domain> [--directory <directory>]
|
||||
|
||||
Creates a new session with the specified name and domain. The session will be saved to the current working directory by default, or to the specified directory if provided.
|
||||
|
||||
Options:
|
||||
--name The name of the session to create.
|
||||
--domain The domain of the socialbox instance.
|
||||
--directory The directory where the session file should be saved.
|
||||
|
||||
Example:
|
||||
socialbox connect --name mysession --domain socialbox.example.com
|
||||
HELP;
|
||||
|
||||
}
|
||||
|
||||
public static function getShortHelpMessage(): string
|
||||
{
|
||||
return 'Connect Command - Creates a new session with the specified name and domain';
|
||||
}
|
||||
}
|
|
@ -2,19 +2,21 @@
|
|||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Socialbox\Classes\ClientCommands\StorageConfiguration;
|
||||
use Socialbox\Classes\Configuration\CacheConfiguration;
|
||||
use Socialbox\Classes\Configuration\CryptographyConfiguration;
|
||||
use Socialbox\Classes\Configuration\DatabaseConfiguration;
|
||||
use Socialbox\Classes\Configuration\InstanceConfiguration;
|
||||
use Socialbox\Classes\Configuration\LoggingConfiguration;
|
||||
use Socialbox\Classes\Configuration\RegistrationConfiguration;
|
||||
use Socialbox\Classes\Configuration\SecurityConfiguration;
|
||||
use Socialbox\Classes\Configuration\StorageConfiguration;
|
||||
|
||||
class Configuration
|
||||
{
|
||||
private static ?\ConfigLib\Configuration $configuration = null;
|
||||
private static ?InstanceConfiguration $instanceConfiguration = null;
|
||||
private static ?SecurityConfiguration $securityConfiguration = null;
|
||||
private static ?CryptographyConfiguration $cryptographyConfiguration = null;
|
||||
private static ?DatabaseConfiguration $databaseConfiguration = null;
|
||||
private static ?LoggingConfiguration $loggingConfiguration = null;
|
||||
private static ?CacheConfiguration $cacheConfiguration = null;
|
||||
|
@ -33,19 +35,47 @@
|
|||
|
||||
// Instance configuration
|
||||
$config->setDefault('instance.enabled', false); // False by default, requires the user to enable it.
|
||||
$config->setDefault('instance.name', "Socialbox Server");
|
||||
$config->setDefault('instance.domain', null);
|
||||
$config->setDefault('instance.rpc_endpoint', null);
|
||||
$config->setDefault('instance.encryption_keys_count', 5);
|
||||
$config->setDefault('instance.encryption_records_count', 5);
|
||||
$config->setDefault('instance.private_key', null);
|
||||
$config->setDefault('instance.public_key', null);
|
||||
$config->setDefault('instance.encryption_keys', null);
|
||||
|
||||
// Security Configuration
|
||||
$config->setDefault('security.display_internal_exceptions', false);
|
||||
$config->setDefault('security.resolved_servers_ttl', 600);
|
||||
$config->setDefault('security.captcha_ttl', 200);
|
||||
|
||||
// Cryptography Configuration
|
||||
// The Unix Timestamp for when the host's keypair should expire
|
||||
// Setting this value to 0 means the keypair never expires
|
||||
// Setting this value to null will automatically set the current unix timestamp + 1 year as the value
|
||||
// This means at initialization, the key is automatically set to expire in a year.
|
||||
$config->setDefault('cryptography.host_keypair_expires', null);
|
||||
// The host's public/private keypair in base64 encoding, when null; the initialization process
|
||||
// will automatically generate a new keypair
|
||||
$config->setDefault('cryptography.host_public_key', null);
|
||||
$config->setDefault('cryptography.host_private_key', null);
|
||||
|
||||
// The internal encryption keys used for encrypting data in the database when needed.
|
||||
// When null, the initialization process will automatically generate a set of keys
|
||||
// based on the `encryption_keys_count` and `encryption_keys_algorithm` configuration.
|
||||
// This is an array of base64 encoded keys.
|
||||
$config->setDefault('cryptography.internal_encryption_keys', null);
|
||||
|
||||
// The number of encryption keys to generate and set to `instance.encryption_keys` this will be used
|
||||
// to randomly encrypt/decrypt sensitive data in the database, this includes hashes.
|
||||
// The higher the number the higher performance impact it will have on the server
|
||||
$config->setDefault('cryptography.encryption_keys_count', 10);
|
||||
// The host's encryption algorithm, this will be used to generate a set of encryption keys
|
||||
// This is for internal encryption, these keys are never shared outside this configuration.
|
||||
// Recommendation: Higher security over performance
|
||||
$config->setDefault('cryptography.encryption_keys_algorithm', 'xchacha20');
|
||||
|
||||
// The encryption algorithm to use for encrypted message transport between the client aand the server
|
||||
// This is the encryption the server tells the client to use and the client must support it.
|
||||
// Recommendation: Good balance between security and performance
|
||||
// For universal support & performance, use aes256gcm for best performance or for best security use xchacha20
|
||||
$config->setDefault('cryptography.transport_encryption_algorithm', 'chacha20');
|
||||
|
||||
// Database configuration
|
||||
$config->setDefault('database.host', '127.0.0.1');
|
||||
$config->setDefault('database.port', 3306);
|
||||
|
@ -98,6 +128,7 @@
|
|||
self::$configuration = $config;
|
||||
self::$instanceConfiguration = new InstanceConfiguration(self::$configuration->getConfiguration()['instance']);
|
||||
self::$securityConfiguration = new SecurityConfiguration(self::$configuration->getConfiguration()['security']);
|
||||
self::$cryptographyConfiguration = new CryptographyConfiguration(self::$configuration->getConfiguration()['cryptography']);
|
||||
self::$databaseConfiguration = new DatabaseConfiguration(self::$configuration->getConfiguration()['database']);
|
||||
self::$loggingConfiguration = new LoggingConfiguration(self::$configuration->getConfiguration()['logging']);
|
||||
self::$cacheConfiguration = new CacheConfiguration(self::$configuration->getConfiguration()['cache']);
|
||||
|
@ -140,6 +171,14 @@
|
|||
return self::$configuration->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration library instance.
|
||||
*
|
||||
* This method returns the current Configuration instance from the ConfigLib namespace.
|
||||
* If the configuration has not been initialized yet, it initializes it first.
|
||||
*
|
||||
* @return \ConfigLib\Configuration The configuration library instance.
|
||||
*/
|
||||
public static function getConfigurationLib(): \ConfigLib\Configuration
|
||||
{
|
||||
if(self::$configuration === null)
|
||||
|
@ -180,6 +219,24 @@
|
|||
return self::$securityConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cryptography configuration.
|
||||
*
|
||||
* This method returns the current CryptographyConfiguration instance.
|
||||
* If the configuration has not been initialized yet, it initializes it first.
|
||||
*
|
||||
* @return CryptographyConfiguration|null The cryptography configuration instance or null if not available.
|
||||
*/
|
||||
public static function getCryptographyConfiguration(): ?CryptographyConfiguration
|
||||
{
|
||||
if(self::$cryptographyConfiguration === null)
|
||||
{
|
||||
self::initializeConfiguration();
|
||||
}
|
||||
|
||||
return self::$cryptographyConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current database configuration.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes\Configuration;
|
||||
|
||||
class CryptographyConfiguration
|
||||
{
|
||||
private ?int $hostKeyPairExpires;
|
||||
private ?string $hostPublicKey;
|
||||
private ?string $hostPrivateKey;
|
||||
private ?array $internalEncryptionKeys;
|
||||
private int $encryptionKeysCount;
|
||||
private string $encryptionKeysAlgorithm;
|
||||
private string $transportEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* Constructor to initialize encryption and transport keys from provided data.
|
||||
*
|
||||
* @param array $data An associative array containing key-value pairs for encryption keys, algorithms, and expiration settings.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->hostKeyPairExpires = $data['host_keypair_expires'] ?? null;
|
||||
$this->hostPublicKey = $data['host_public_key'] ?? null;
|
||||
$this->hostPrivateKey = $data['host_private_key'] ?? null;
|
||||
$this->internalEncryptionKeys = $data['internal_encryption_keys'] ?? null;
|
||||
$this->encryptionKeysCount = $data['encryption_keys_count'];
|
||||
$this->encryptionKeysAlgorithm = $data['encryption_keys_algorithm'];
|
||||
$this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expiration timestamp of the host key pair.
|
||||
*
|
||||
* @return int|null The expiration timestamp of the host key pair, or null if not set.
|
||||
*/
|
||||
public function getHostKeyPairExpires(): ?int
|
||||
{
|
||||
return $this->hostKeyPairExpires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the host's public key.
|
||||
*
|
||||
* @return string|null The host's public key, or null if not set.
|
||||
*/
|
||||
public function getHostPublicKey(): ?string
|
||||
{
|
||||
return $this->hostPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the private key associated with the host.
|
||||
*
|
||||
* @return string|null The host's private key, or null if not set.
|
||||
*/
|
||||
public function getHostPrivateKey(): ?string
|
||||
{
|
||||
return $this->hostPrivateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the internal encryption keys.
|
||||
*
|
||||
* @return array|null Returns an array of internal encryption keys if set, or null if no keys are available.
|
||||
*/
|
||||
public function getInternalEncryptionKeys(): ?array
|
||||
{
|
||||
return $this->internalEncryptionKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a random internal encryption key from the available set of encryption keys.
|
||||
*
|
||||
* @return string|null Returns a randomly selected encryption key as a string, or null if no keys are available.
|
||||
*/
|
||||
public function getRandomInternalEncryptionKey(): ?string
|
||||
{
|
||||
return $this->internalEncryptionKeys[array_rand($this->internalEncryptionKeys)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the total count of encryption keys.
|
||||
*
|
||||
* @return int The number of encryption keys.
|
||||
*/
|
||||
public function getEncryptionKeysCount(): int
|
||||
{
|
||||
return $this->encryptionKeysCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the algorithm used for the encryption keys.
|
||||
*
|
||||
* @return string The encryption keys algorithm.
|
||||
*/
|
||||
public function getEncryptionKeysAlgorithm(): string
|
||||
{
|
||||
return $this->encryptionKeysAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transport encryption algorithm being used.
|
||||
*
|
||||
* @return string The transport encryption algorithm.
|
||||
*/
|
||||
public function getTransportEncryptionAlgorithm(): string
|
||||
{
|
||||
return $this->transportEncryptionAlgorithm;
|
||||
}
|
||||
}
|
|
@ -5,13 +5,9 @@
|
|||
class InstanceConfiguration
|
||||
{
|
||||
private bool $enabled;
|
||||
private string $name;
|
||||
private ?string $domain;
|
||||
private ?string $rpcEndpoint;
|
||||
private int $encryptionKeysCount;
|
||||
private int $encryptionRecordsCount;
|
||||
private ?string $privateKey;
|
||||
private ?string $publicKey;
|
||||
private ?array $encryptionKeys;
|
||||
|
||||
/**
|
||||
* Constructor that initializes object properties with the provided data.
|
||||
|
@ -22,13 +18,9 @@
|
|||
public function __construct(array $data)
|
||||
{
|
||||
$this->enabled = (bool)$data['enabled'];
|
||||
$this->name = $data['name'];
|
||||
$this->domain = $data['domain'];
|
||||
$this->rpcEndpoint = $data['rpc_endpoint'];
|
||||
$this->encryptionKeysCount = $data['encryption_keys_count'];
|
||||
$this->encryptionRecordsCount = $data['encryption_records_count'];
|
||||
$this->privateKey = $data['private_key'];
|
||||
$this->publicKey = $data['public_key'];
|
||||
$this->encryptionKeys = $data['encryption_keys'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,6 +33,11 @@
|
|||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the domain.
|
||||
*
|
||||
|
@ -58,62 +55,4 @@
|
|||
{
|
||||
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.
|
||||
*
|
||||
* @return string|null The private key.
|
||||
*/
|
||||
public function getPrivateKey(): ?string
|
||||
{
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public key.
|
||||
*
|
||||
* @return string|null The public key.
|
||||
*/
|
||||
public function getPublicKey(): ?string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the encryption keys.
|
||||
*
|
||||
* @return array|null The encryption keys.
|
||||
*/
|
||||
public function getEncryptionKeys(): ?array
|
||||
{
|
||||
return $this->encryptionKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRandomEncryptionKey(): string
|
||||
{
|
||||
return $this->encryptionKeys[array_rand($this->encryptionKeys)];
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes\ClientCommands;
|
||||
namespace Socialbox\Classes\Configuration;
|
||||
|
||||
class StorageConfiguration
|
||||
{
|
File diff suppressed because it is too large
Load diff
41
src/Socialbox/Classes/DnsHelper.php
Normal file
41
src/Socialbox/Classes/DnsHelper.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Socialbox\Objects\DnsRecord;
|
||||
|
||||
class DnsHelper
|
||||
{
|
||||
/**
|
||||
* Generates a TXT formatted string containing the provided RPC endpoint, public key, and expiration time.
|
||||
*
|
||||
* @param string $rpcEndpoint The RPC endpoint to include in the TXT string.
|
||||
* @param string $publicKey The public key to include in the TXT string.
|
||||
* @param int $expirationTime The expiration time in seconds to include in the TXT string.
|
||||
*
|
||||
* @return string A formatted TXT string containing the input data.
|
||||
*/
|
||||
public static function generateTxt(string $rpcEndpoint, string $publicKey, int $expirationTime): string
|
||||
{
|
||||
return sprintf('v=socialbox;sb-rpc=%s;sb-key=%s;sb-exp=%d', $rpcEndpoint, $publicKey, $expirationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a TXT record string and extracts its components into a DnsRecord object.
|
||||
*
|
||||
* @param string $txtRecord The TXT record string to be parsed.
|
||||
* @return DnsRecord The extracted DnsRecord object containing the RPC endpoint, public key, and expiration time.
|
||||
* @throws InvalidArgumentException If the TXT record format is invalid.
|
||||
*/
|
||||
public static function parseTxt(string $txtRecord): DnsRecord
|
||||
{
|
||||
$pattern = '/v=socialbox;sb-rpc=(?P<rpcEndpoint>https?:\/\/[^;]+);sb-key=(?P<publicSigningKey>[^;]+);sb-exp=(?P<expirationTime>\d+)/';
|
||||
if (preg_match($pattern, $txtRecord, $matches))
|
||||
{
|
||||
return new DnsRecord($matches['rpcEndpoint'], $matches['publicSigningKey'], (int)$matches['expirationTime']);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid TXT record format.');
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
create table authentication_passwords
|
||||
(
|
||||
peer_uuid varchar(36) not null comment 'The Universal Unique Identifier for the peer that is associated with this password record'
|
||||
peer_uuid varchar(36) not null comment 'The Universal Unique Identifier for the peer that is associated with this password record'
|
||||
primary key comment 'The primary unique index of the peer uuid',
|
||||
iv mediumtext not null comment 'The Initial Vector of the password record',
|
||||
encrypted_password mediumtext not null comment 'The encrypted password data',
|
||||
encrypted_tag mediumtext not null comment 'The encrypted tag of the password record',
|
||||
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
|
||||
hash mediumtext not null comment 'The encrypted hash of the password',
|
||||
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
|
||||
constraint authentication_passwords_peer_uuid_uindex
|
||||
unique (peer_uuid) comment 'The primary unique index of the peer uuid',
|
||||
constraint authentication_passwords_registered_peers_uuid_fk
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
create table encryption_records
|
||||
(
|
||||
data mediumtext not null comment 'The data column',
|
||||
iv mediumtext not null comment 'The initialization vector column',
|
||||
tag mediumtext not null comment 'The authentication tag used to verify if the data was tampered'
|
||||
)
|
||||
comment 'Table for housing encryption records for the server';
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
create table resolved_dns_records
|
||||
(
|
||||
domain varchar(512) not null comment 'The domain name'
|
||||
primary key comment 'Unique Index for the server domain',
|
||||
rpc_endpoint text not null comment 'The endpoint of the RPC server',
|
||||
public_key text not null comment 'The Public Key of the server',
|
||||
expires bigint not null comment 'The Unix Timestamp for when the server''s keypair expires',
|
||||
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
|
||||
constraint resolved_dns_records_domain_uindex
|
||||
unique (domain) comment 'Unique Index for the server domain',
|
||||
constraint resolved_dns_records_pk
|
||||
unique (domain) comment 'Unique Index for the server domain'
|
||||
)
|
||||
comment 'A table for housing DNS resolutions';
|
||||
|
||||
create index resolved_dns_records_updated_index
|
||||
on resolved_dns_records (updated)
|
||||
comment 'The index for the updated column';
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
create table resolved_servers
|
||||
(
|
||||
domain varchar(512) not null comment 'The domain name'
|
||||
primary key comment 'Unique Index for the server domain',
|
||||
endpoint text not null comment 'The endpoint of the RPC server',
|
||||
public_key text not null comment 'The Public Key of the server',
|
||||
updated timestamp default current_timestamp() not null comment 'The TImestamp for when this record was last updated',
|
||||
constraint resolved_servers_domain_uindex
|
||||
unique (domain) comment 'Unique Index for the server domain'
|
||||
)
|
||||
comment 'A table for housing DNS resolutions';
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
create table sessions
|
||||
(
|
||||
uuid varchar(36) default uuid() not null comment 'The Unique Primary index for the session UUID'
|
||||
uuid varchar(36) default uuid() not null comment 'The Unique Primary index for the session UUID'
|
||||
primary key,
|
||||
peer_uuid varchar(36) not null comment 'The peer the session is identified as, null if the session isn''t identified as a peer',
|
||||
client_name varchar(256) not null comment 'The name of the client that is using this session',
|
||||
client_version varchar(16) not null comment 'The version of the client',
|
||||
authenticated tinyint(1) default 0 not null comment 'Indicates if the session is currently authenticated as the peer',
|
||||
public_key text not null comment 'The client''s public key provided when creating the session',
|
||||
state enum ('AWAITING_DHE', 'ACTIVE', 'CLOSED', 'EXPIRED') default 'AWAITING_DHE' not null comment 'The status of the session',
|
||||
encryption_key text null comment 'The key used for encryption that is obtained through a DHE',
|
||||
flags text null comment 'The current flags that is set to the session',
|
||||
created timestamp default current_timestamp() not null comment 'The Timestamp for when the session was last created',
|
||||
last_request timestamp null comment 'The Timestamp for when the last request was made using this session',
|
||||
peer_uuid varchar(36) not null comment 'The peer the session is identified as, null if the session isn''t identified as a peer',
|
||||
client_name varchar(256) not null comment 'The name of the client that is using this session',
|
||||
client_version varchar(16) not null comment 'The version of the client',
|
||||
authenticated tinyint(1) default 0 not null comment 'Indicates if the session is currently authenticated as the peer',
|
||||
client_public_signing_key text not null comment 'The client''s public signing key used for signing decrypted messages',
|
||||
client_public_encryption_key text not null comment 'The Public Key of the client''s encryption key',
|
||||
server_public_encryption_key text not null comment 'The server''s public encryption key for this session',
|
||||
server_private_encryption_key text not null comment 'The server''s private encryption key for this session',
|
||||
private_shared_secret text null comment 'The shared secret encryption key between the Client & Server',
|
||||
client_transport_encryption_key text null comment 'The encryption key for sending messages to the client',
|
||||
server_transport_encryption_key text null comment 'The encryption key for sending messages to the server',
|
||||
state enum ('AWAITING_DHE', 'ACTIVE', 'CLOSED', 'EXPIRED') default 'AWAITING_DHE' not null comment 'The status of the session',
|
||||
flags text null comment 'The current flags that is set to the session',
|
||||
created timestamp default current_timestamp() not null comment 'The Timestamp for when the session was last created',
|
||||
last_request timestamp null comment 'The Timestamp for when the last request was made using this session',
|
||||
constraint sessions_uuid_uindex
|
||||
unique (uuid) comment 'The Unique Primary index for the session UUID',
|
||||
constraint sessions_registered_peers_uuid_fk
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Socialbox\Enums\Options\ClientOptions;
|
||||
use Socialbox\Enums\StandardError;
|
||||
use Socialbox\Enums\StandardHeaders;
|
||||
use Socialbox\Enums\Types\RequestType;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialbox\Exceptions\RpcException;
|
||||
use Socialbox\Objects\ExportedSession;
|
||||
|
@ -14,6 +13,7 @@
|
|||
use Socialbox\Objects\PeerAddress;
|
||||
use Socialbox\Objects\RpcRequest;
|
||||
use Socialbox\Objects\RpcResult;
|
||||
use Socialbox\Objects\Standard\ServerInformation;
|
||||
|
||||
class RpcClient
|
||||
{
|
||||
|
@ -22,9 +22,14 @@
|
|||
|
||||
private bool $bypassSignatureVerification;
|
||||
private PeerAddress $peerAddress;
|
||||
private KeyPair $keyPair;
|
||||
private string $encryptionKey;
|
||||
private string $serverPublicKey;
|
||||
private string $serverPublicSigningKey;
|
||||
private string $serverPublicEncryptionKey;
|
||||
private KeyPair $clientSigningKeyPair;
|
||||
private KeyPair $clientEncryptionKeyPair;
|
||||
private string $privateSharedSecret;
|
||||
private string $clientTransportEncryptionKey;
|
||||
private string $serverTransportEncryptionKey;
|
||||
private ServerInformation $serverInformation;
|
||||
private string $rpcEndpoint;
|
||||
private string $sessionUuid;
|
||||
|
||||
|
@ -42,14 +47,41 @@
|
|||
$this->bypassSignatureVerification = false;
|
||||
|
||||
// If an exported session is provided, no need to re-connect.
|
||||
// Just use the session details provided.
|
||||
if($exportedSession !== null)
|
||||
{
|
||||
// Check if the server keypair has expired from the exported session
|
||||
if(time() > $exportedSession->getServerKeypairExpires())
|
||||
{
|
||||
throw new RpcException('The server keypair has expired, a new session must be created');
|
||||
}
|
||||
|
||||
$this->peerAddress = PeerAddress::fromAddress($exportedSession->getPeerAddress());
|
||||
$this->keyPair = new KeyPair($exportedSession->getPublicKey(), $exportedSession->getPrivateKey());
|
||||
$this->encryptionKey = $exportedSession->getEncryptionKey();
|
||||
$this->serverPublicKey = $exportedSession->getServerPublicKey();
|
||||
$this->rpcEndpoint = $exportedSession->getRpcEndpoint();
|
||||
$this->sessionUuid = $exportedSession->getSessionUuid();
|
||||
$this->serverPublicSigningKey = $exportedSession->getServerPublicSigningKey();
|
||||
$this->serverPublicEncryptionKey = $exportedSession->getServerPublicEncryptionKey();
|
||||
$this->clientSigningKeyPair = new KeyPair($exportedSession->getClientPublicSigningKey(), $exportedSession->getClientPrivateSigningKey());
|
||||
$this->clientEncryptionKeyPair = new KeyPair($exportedSession->getClientPublicEncryptionKey(), $exportedSession->getClientPrivateEncryptionKey());
|
||||
$this->privateSharedSecret = $exportedSession->getPrivateSharedSecret();
|
||||
$this->clientTransportEncryptionKey = $exportedSession->getClientTransportEncryptionKey();
|
||||
$this->serverTransportEncryptionKey = $exportedSession->getServerTransportEncryptionKey();
|
||||
|
||||
// Still solve the server information
|
||||
$this->serverInformation = self::getServerInformation();
|
||||
|
||||
// Check if the active keypair has expired
|
||||
if(time() > $this->serverInformation->getServerKeypairExpires())
|
||||
{
|
||||
throw new RpcException('The server keypair has expired but the server has not provided a new keypair, contact the server administrator');
|
||||
}
|
||||
|
||||
// Check if the transport encryption algorithm has changed
|
||||
if($this->serverInformation->getTransportEncryptionAlgorithm() !== $exportedSession->getTransportEncryptionAlgorithm())
|
||||
{
|
||||
throw new RpcException('The server has changed its transport encryption algorithm, a new session must be created');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -62,51 +94,61 @@
|
|||
// Set the initial properties
|
||||
$this->peerAddress = $peerAddress;
|
||||
|
||||
// If the username is `host` and the domain is the same as this server's domain, we use our keypair
|
||||
// Essentially this is a special case for the server to contact another server
|
||||
if($this->peerAddress->isHost())
|
||||
{
|
||||
$this->keyPair = new KeyPair(Configuration::getInstanceConfiguration()->getPublicKey(), Configuration::getInstanceConfiguration()->getPrivateKey());
|
||||
}
|
||||
// Otherwise we generate a random keypair
|
||||
else
|
||||
{
|
||||
$this->keyPair = Cryptography::generateKeyPair();
|
||||
}
|
||||
|
||||
$this->encryptionKey = Cryptography::generateEncryptionKey();
|
||||
|
||||
// Resolve the domain and get the server's Public Key & RPC Endpoint
|
||||
try
|
||||
{
|
||||
$resolvedServer = ServerResolver::resolveDomain($this->peerAddress->getDomain(), false);
|
||||
}
|
||||
catch (DatabaseOperationException $e)
|
||||
{
|
||||
throw new ResolutionException('Failed to resolve domain: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
$resolvedServer = ServerResolver::resolveDomain($this->peerAddress->getDomain(), false);
|
||||
|
||||
$this->serverPublicKey = $resolvedServer->getPublicKey();
|
||||
$this->rpcEndpoint = $resolvedServer->getEndpoint();
|
||||
// Import the RPC Endpoint & the server's public key.
|
||||
$this->serverPublicSigningKey = $resolvedServer->getPublicSigningKey();
|
||||
$this->rpcEndpoint = $resolvedServer->getRpcEndpoint();
|
||||
|
||||
if(empty($this->serverPublicKey))
|
||||
if(empty($this->serverPublicSigningKey))
|
||||
{
|
||||
throw new ResolutionException('Failed to resolve domain: No public key found for the server');
|
||||
}
|
||||
|
||||
// Attempt to create an encrypted session with the server
|
||||
$this->sessionUuid = $this->createSession();
|
||||
// Resolve basic server information
|
||||
$this->serverInformation = self::getServerInformation();
|
||||
|
||||
// Check if the server keypair has expired
|
||||
if(time() > $this->serverInformation->getServerKeypairExpires())
|
||||
{
|
||||
throw new RpcException('The server keypair has expired but the server has not provided a new keypair, contact the server administrator');
|
||||
}
|
||||
|
||||
// If the username is `host` and the domain is the same as this server's domain, we use our signing keypair
|
||||
// Essentially this is a special case for the server to contact another server
|
||||
if($this->peerAddress->isHost())
|
||||
{
|
||||
$this->clientSigningKeyPair = new KeyPair(Configuration::getCryptographyConfiguration()->getHostPublicKey(), Configuration::getCryptographyConfiguration()->getHostPrivateKey());
|
||||
}
|
||||
// Otherwise we generate a random signing keypair
|
||||
else
|
||||
{
|
||||
$this->clientSigningKeyPair = Cryptography::generateSigningKeyPair();
|
||||
}
|
||||
|
||||
// Always use a random encryption keypair
|
||||
$this->clientEncryptionKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
// Create a session with the server, with the method we obtain the Session UUID
|
||||
// And the server's public encryption key.
|
||||
$this->createSession();
|
||||
|
||||
// Generate a transport encryption key on our end using the server's preferred algorithm
|
||||
$this->clientTransportEncryptionKey = Cryptography::generateEncryptionKey($this->serverInformation->getTransportEncryptionAlgorithm());
|
||||
|
||||
// Preform the DHE so that transport encryption keys can be exchanged
|
||||
$this->sendDheExchange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session by sending an HTTP GET request to the RPC endpoint.
|
||||
* The request includes specific headers required for session initiation.
|
||||
* Initiates a new session with the server and retrieves the session UUID.
|
||||
*
|
||||
* @return string Returns the session UUID received from the server.
|
||||
* @throws RpcException If the server response is invalid, the session creation fails, or no session UUID is returned.
|
||||
* @return string The session UUID provided by the server upon successful session creation.
|
||||
* @throws RpcException If the session cannot be created, if the server does not provide a valid response,
|
||||
* or critical headers like encryption public key are missing in the server's response.
|
||||
*/
|
||||
private function createSession(): string
|
||||
private function createSession(): void
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
|
@ -116,28 +158,45 @@
|
|||
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
||||
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
||||
StandardHeaders::IDENTIFY_AS->value . ': ' . $this->peerAddress->getAddress(),
|
||||
// Always provide our generated encrypted public key
|
||||
StandardHeaders::ENCRYPTION_PUBLIC_KEY->value . ': ' . $this->clientEncryptionKeyPair->getPublicKey()
|
||||
];
|
||||
|
||||
// If we're not connecting as the host, we need to provide our public key
|
||||
// Otherwise, the server will obtain the public key itself from DNS records rather than trusting the client
|
||||
if(!$this->peerAddress->isHost())
|
||||
{
|
||||
$headers[] = StandardHeaders::PUBLIC_KEY->value . ': ' . $this->keyPair->getPublicKey();
|
||||
$headers[] = StandardHeaders::SIGNING_PUBLIC_KEY->value . ': ' . $this->clientSigningKeyPair->getPublicKey();
|
||||
}
|
||||
|
||||
$responseHeaders = [];
|
||||
curl_setopt($ch, CURLOPT_URL, $this->rpcEndpoint);
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
// Capture the response headers to get the encryption public key
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders)
|
||||
{
|
||||
$len = strlen($header);
|
||||
$header = explode(':', $header, 2);
|
||||
if (count($header) < 2) // ignore invalid headers
|
||||
{
|
||||
return $len;
|
||||
}
|
||||
|
||||
$responseHeaders[strtolower(trim($header[0]))][] = trim($header[1]);
|
||||
return $len;
|
||||
});
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// If the response is false, the request failed
|
||||
if($response === false)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException(sprintf('Failed to create the session at %s, no response received', $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
// If the response code is not 201, the request failed
|
||||
$responseCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
if($responseCode !== 201)
|
||||
{
|
||||
|
@ -151,14 +210,44 @@
|
|||
throw new RpcException(sprintf('Failed to create the session at %s, server responded with ' . $responseCode . ': ' . $response, $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
// If the response is empty, the server did not provide a session UUID
|
||||
if(empty($response))
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException(sprintf('Failed to create the session at %s, server did not return a session UUID', $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
// Get the Encryption Public Key from the server's response headers
|
||||
$serverPublicEncryptionKey = $responseHeaders[strtolower(StandardHeaders::ENCRYPTION_PUBLIC_KEY->value)][0] ?? null;
|
||||
|
||||
// null check
|
||||
if($serverPublicEncryptionKey === null)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException('Failed to create session at %s, the server did not return a public encryption key');
|
||||
}
|
||||
|
||||
// Validate the server's encryption public key
|
||||
if(!Cryptography::validatePublicEncryptionKey($serverPublicEncryptionKey))
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException('The server did not provide a valid encryption public key');
|
||||
}
|
||||
|
||||
// If the server did not provide an encryption public key, the response is invalid
|
||||
// We can't preform the DHE without the server's encryption key.
|
||||
if ($serverPublicEncryptionKey === null)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException('The server did not provide a signature for the response');
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
return $response;
|
||||
|
||||
// Set the server's encryption key
|
||||
$this->serverPublicEncryptionKey = $serverPublicEncryptionKey;
|
||||
// Set the session UUID
|
||||
$this->sessionUuid = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,15 +257,26 @@
|
|||
*/
|
||||
private function sendDheExchange(): void
|
||||
{
|
||||
// First preform the DHE
|
||||
try
|
||||
{
|
||||
$this->privateSharedSecret = Cryptography::performDHE($this->serverPublicEncryptionKey, $this->clientEncryptionKeyPair->getPrivateKey());
|
||||
}
|
||||
catch(CryptographyException $e)
|
||||
{
|
||||
throw new RpcException('Failed to preform DHE: ' . $e->getMessage(), StandardError::CRYPTOGRAPHIC_ERROR->value, $e);
|
||||
}
|
||||
|
||||
// Request body should contain the encrypted key, the client's public key, and the session UUID
|
||||
// Upon success the server should return 204 without a body
|
||||
try
|
||||
{
|
||||
$encryptedKey = Cryptography::encryptContent($this->encryptionKey, $this->serverPublicKey);
|
||||
$encryptedKey = Cryptography::encryptShared($this->clientTransportEncryptionKey, $this->privateSharedSecret);
|
||||
$signature = Cryptography::signMessage($this->clientTransportEncryptionKey, $this->clientSigningKeyPair->getPrivateKey());
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
throw new RpcException('Failed to encrypt DHE exchange data', 0, $e);
|
||||
throw new RpcException('Failed to encrypt DHE exchange data', StandardError::CRYPTOGRAPHIC_ERROR->value, $e);
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
|
@ -186,6 +286,7 @@
|
|||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::DHE_EXCHANGE->value,
|
||||
StandardHeaders::SESSION_UUID->value . ': ' . $this->sessionUuid,
|
||||
StandardHeaders::SIGNATURE->value . ': ' . $signature
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $encryptedKey);
|
||||
|
||||
|
@ -194,17 +295,28 @@
|
|||
if($response === false)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException('Failed to send DHE exchange, no response received');
|
||||
throw new RpcException('Failed to send DHE exchange, no response received', StandardError::CRYPTOGRAPHIC_ERROR->value);
|
||||
}
|
||||
|
||||
$responseCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
if($responseCode !== 204)
|
||||
if($responseCode !== 200)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException('Failed to send DHE exchange, server responded with ' . $responseCode . ': ' . $response);
|
||||
throw new RpcException('Failed to send DHE exchange, server responded with ' . $responseCode . ': ' . $response, StandardError::CRYPTOGRAPHIC_ERROR->value);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
try
|
||||
{
|
||||
$this->serverTransportEncryptionKey = Cryptography::decryptShared($response, $this->privateSharedSecret);
|
||||
}
|
||||
catch(CryptographyException $e)
|
||||
{
|
||||
throw new RpcException('Failed to decrypt DHE exchange data', 0, $e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,8 +330,16 @@
|
|||
{
|
||||
try
|
||||
{
|
||||
$encryptedData = Cryptography::encryptTransport($jsonData, $this->encryptionKey);
|
||||
$signature = Cryptography::signContent($jsonData, $this->keyPair->getPrivateKey(), true);
|
||||
$encryptedData = Cryptography::encryptMessage(
|
||||
message: $jsonData,
|
||||
encryptionKey: $this->serverTransportEncryptionKey,
|
||||
algorithm: $this->serverInformation->getTransportEncryptionAlgorithm()
|
||||
);
|
||||
|
||||
$signature = Cryptography::signMessage(
|
||||
message: $jsonData,
|
||||
privateKey: $this->clientSigningKeyPair->getPrivateKey(),
|
||||
);
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
|
@ -289,7 +409,11 @@
|
|||
|
||||
try
|
||||
{
|
||||
$decryptedResponse = Cryptography::decryptTransport($responseString, $this->encryptionKey);
|
||||
$decryptedResponse = Cryptography::decryptMessage(
|
||||
encryptedMessage: $responseString,
|
||||
encryptionKey: $this->clientTransportEncryptionKey,
|
||||
algorithm: $this->serverInformation->getTransportEncryptionAlgorithm()
|
||||
);
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
|
@ -298,7 +422,7 @@
|
|||
|
||||
if (!$this->bypassSignatureVerification)
|
||||
{
|
||||
$signature = $headers['signature'][0] ?? null;
|
||||
$signature = $headers[strtolower(StandardHeaders::SIGNATURE->value)][0] ?? null;
|
||||
if ($signature === null)
|
||||
{
|
||||
throw new RpcException('The server did not provide a signature for the response');
|
||||
|
@ -306,7 +430,11 @@
|
|||
|
||||
try
|
||||
{
|
||||
if (!Cryptography::verifyContent($decryptedResponse, $signature, $this->serverPublicKey, true))
|
||||
if(!Cryptography::verifyMessage(
|
||||
message: $decryptedResponse,
|
||||
signature: $signature,
|
||||
publicKey: $this->serverPublicSigningKey,
|
||||
))
|
||||
{
|
||||
throw new RpcException('Failed to verify the response signature');
|
||||
}
|
||||
|
@ -333,6 +461,59 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves server information by making an RPC request.
|
||||
*
|
||||
* @return ServerInformation The parsed server information received in the response.
|
||||
* @throws RpcException If the request fails, no response is received, or the server returns an error status code or invalid data.
|
||||
*/
|
||||
public function getServerInformation(): ServerInformation
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
// Basic session details
|
||||
$headers = [
|
||||
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::INFO->value,
|
||||
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
||||
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
||||
];
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $this->rpcEndpoint);
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if($response === false)
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException(sprintf('Failed to get server information at %s, no response received', $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
$responseCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
if($responseCode !== 200)
|
||||
{
|
||||
curl_close($ch);
|
||||
|
||||
if(empty($response))
|
||||
{
|
||||
throw new RpcException(sprintf('Failed to get server information at %s, server responded with ' . $responseCode, $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
throw new RpcException(sprintf('Failed to get server information at %s, server responded with ' . $responseCode . ': ' . $response, $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
if(empty($response))
|
||||
{
|
||||
curl_close($ch);
|
||||
throw new RpcException(sprintf('Failed to get server information at %s, server returned an empty response', $this->rpcEndpoint));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
return ServerInformation::fromArray(json_decode($response, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an RPC request and retrieves the corresponding RPC response.
|
||||
*
|
||||
|
@ -395,12 +576,19 @@
|
|||
{
|
||||
return new ExportedSession([
|
||||
'peer_address' => $this->peerAddress->getAddress(),
|
||||
'private_key' => $this->keyPair->getPrivateKey(),
|
||||
'public_key' => $this->keyPair->getPublicKey(),
|
||||
'encryption_key' => $this->encryptionKey,
|
||||
'server_public_key' => $this->serverPublicKey,
|
||||
'rpc_endpoint' => $this->rpcEndpoint,
|
||||
'session_uuid' => $this->sessionUuid
|
||||
'session_uuid' => $this->sessionUuid,
|
||||
'transport_encryption_algorithm' => $this->serverInformation->getTransportEncryptionAlgorithm(),
|
||||
'server_keypair_expires' => $this->serverInformation->getServerKeypairExpires(),
|
||||
'server_public_signing_key' => $this->serverPublicSigningKey,
|
||||
'server_public_encryption_key' => $this->serverPublicEncryptionKey,
|
||||
'client_public_signing_key' => $this->clientSigningKeyPair->getPublicKey(),
|
||||
'client_private_signing_key' => $this->clientSigningKeyPair->getPrivateKey(),
|
||||
'client_public_encryption_key' => $this->clientEncryptionKeyPair->getPublicKey(),
|
||||
'client_private_encryption_key' => $this->clientEncryptionKeyPair->getPrivateKey(),
|
||||
'private_shared_secret' => $this->privateSharedSecret,
|
||||
'client_transport_encryption_key' => $this->clientTransportEncryptionKey,
|
||||
'server_transport_encryption_key' => $this->serverTransportEncryptionKey
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -2,32 +2,31 @@
|
|||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use InvalidArgumentException;
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialbox\Managers\ResolvedServersManager;
|
||||
use Socialbox\Objects\ResolvedServer;
|
||||
use Socialbox\Managers\ResolvedDnsRecordsManager;
|
||||
use Socialbox\Objects\DnsRecord;
|
||||
|
||||
class ServerResolver
|
||||
{
|
||||
private const string PATTERN = '/v=socialbox;sb-rpc=(https?:\/\/[^;]+);sb-key=([^;]+)/';
|
||||
|
||||
/**
|
||||
* Resolves a given domain to fetch the RPC endpoint and public key from its DNS TXT records.
|
||||
* Resolves a domain by retrieving and parsing its DNS TXT records.
|
||||
* Optionally checks a database for cached resolution data before performing a DNS query.
|
||||
*
|
||||
* @param string $domain The domain to be resolved.
|
||||
* @return ResolvedServer An instance of ResolvedServer containing the endpoint and public key.
|
||||
* @throws ResolutionException If the DNS TXT records cannot be resolved or if required information is missing.
|
||||
* @throws DatabaseOperationException
|
||||
* @param string $domain The domain name to resolve.
|
||||
* @param bool $useDatabase Whether to check the database for cached resolution data; defaults to true.
|
||||
* @return DnsRecord The parsed DNS record for the given domain.
|
||||
* @throws ResolutionException If the DNS TXT records cannot be retrieved or parsed.
|
||||
*/
|
||||
public static function resolveDomain(string $domain, bool $useDatabase=true): ResolvedServer
|
||||
public static function resolveDomain(string $domain, bool $useDatabase=true): DnsRecord
|
||||
{
|
||||
// First query the database to check if the domain is already resolved
|
||||
if($useDatabase)
|
||||
// Check the database if enabled
|
||||
if ($useDatabase)
|
||||
{
|
||||
$resolvedServer = ResolvedServersManager::getResolvedServer($domain);
|
||||
if($resolvedServer !== null)
|
||||
$resolvedServer = ResolvedDnsRecordsManager::getDnsRecord($domain);
|
||||
if ($resolvedServer !== null)
|
||||
{
|
||||
return $resolvedServer->toResolvedServer();
|
||||
return $resolvedServer;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,23 +37,23 @@
|
|||
}
|
||||
|
||||
$fullRecord = self::concatenateTxtRecords($txtRecords);
|
||||
if (preg_match(self::PATTERN, $fullRecord, $matches))
|
||||
|
||||
try
|
||||
{
|
||||
$endpoint = trim($matches[1]);
|
||||
$publicKey = trim(str_replace(' ', '', $matches[2]));
|
||||
if (empty($endpoint))
|
||||
// Parse the TXT record using DnsHelper
|
||||
$record = DnsHelper::parseTxt($fullRecord);
|
||||
|
||||
// Cache the resolved server record in the database
|
||||
if($useDatabase)
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to resolve RPC endpoint for %s", $domain));
|
||||
ResolvedDnsRecordsManager::addResolvedServer($domain, $record);
|
||||
}
|
||||
if (empty($publicKey))
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to resolve public key for %s", $domain));
|
||||
}
|
||||
return new ResolvedServer($endpoint, $publicKey);
|
||||
|
||||
return $record;
|
||||
}
|
||||
else
|
||||
catch (InvalidArgumentException $e)
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to find valid SocialBox record for %s", $domain));
|
||||
throw new ResolutionException(sprintf("Failed to find valid SocialBox record for %s: %s", $domain, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,9 +63,9 @@
|
|||
* @param string $domain The domain name to fetch TXT records for.
|
||||
* @return array|false An array of DNS TXT records on success, or false on failure.
|
||||
*/
|
||||
private static function dnsGetTxtRecords(string $domain)
|
||||
private static function dnsGetTxtRecords(string $domain): array|false
|
||||
{
|
||||
return dns_get_record($domain, DNS_TXT);
|
||||
return @dns_get_record($domain, DNS_TXT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,14 +38,14 @@
|
|||
|
||||
if($decodedImage === false)
|
||||
{
|
||||
return $rpcRequest->produceError(StandardError::BAD_REQUEST, "Failed to decode JPEG image base64 data");
|
||||
return $rpcRequest->produceError(StandardError::RPC_BAD_REQUEST, "Failed to decode JPEG image base64 data");
|
||||
}
|
||||
|
||||
$sanitizedImage = Utilities::resizeImage(Utilities::sanitizeJpeg($decodedImage), 126, 126);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new StandardException('Failed to process JPEG image: ' . $e->getMessage(), StandardError::BAD_REQUEST, $e);
|
||||
throw new StandardException('Failed to process JPEG image: ' . $e->getMessage(), StandardError::RPC_BAD_REQUEST, $e);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
|
@ -122,22 +122,37 @@
|
|||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Throwable object into a formatted string.
|
||||
*
|
||||
* @param Throwable $e The throwable to be converted into a string.
|
||||
* @return string The formatted string representation of the throwable, including the exception class, message, file, line, and stack trace.
|
||||
*/
|
||||
public static function throwableToString(Throwable $e): string
|
||||
public static function throwableToString(Throwable $e, int $level=0): string
|
||||
{
|
||||
return sprintf(
|
||||
"%s: %s in %s:%d\nStack trace:\n%s",
|
||||
get_class($e),
|
||||
$e->getMessage(),
|
||||
$e->getFile(),
|
||||
$e->getLine(),
|
||||
$e->getTraceAsString()
|
||||
// Indentation for nested exceptions
|
||||
$indentation = str_repeat(' ', $level);
|
||||
|
||||
// Basic information about the Throwable
|
||||
$type = get_class($e);
|
||||
$message = $e->getMessage() ?: 'No message';
|
||||
$file = $e->getFile() ?: 'Unknown file';
|
||||
$line = $e->getLine() ?? 'Unknown line';
|
||||
|
||||
// Compose the base string representation of this Throwable
|
||||
$result = sprintf("%s%s: %s\n%s in %s on line %s\n",
|
||||
$indentation, $type, $message, $indentation, $file, $line
|
||||
);
|
||||
|
||||
// Append stack trace if available
|
||||
$stackTrace = $e->getTraceAsString();
|
||||
if (!empty($stackTrace))
|
||||
{
|
||||
$result .= $indentation . " Stack trace:\n" . $indentation . " " . str_replace("\n", "\n" . $indentation . " ", $stackTrace) . "\n";
|
||||
}
|
||||
|
||||
// Recursively append the cause if it exists
|
||||
$previous = $e->getPrevious();
|
||||
if ($previous)
|
||||
{
|
||||
$result .= $indentation . "Caused by:\n" . self::throwableToString($previous, $level + 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
enum DatabaseObjects : string
|
||||
{
|
||||
case VARIABLES = 'variables.sql';
|
||||
case ENCRYPTION_RECORDS = 'encryption_records.sql';
|
||||
case RESOLVED_SERVERS = 'resolved_servers.sql';
|
||||
case RESOLVED_DNS_RECORDS = 'resolved_dns_records.sql';
|
||||
|
||||
case REGISTERED_PEERS = 'registered_peers.sql';
|
||||
|
||||
|
@ -24,7 +23,7 @@
|
|||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::VARIABLES, self::ENCRYPTION_RECORDS, self::RESOLVED_SERVERS => 0,
|
||||
self::VARIABLES, self::RESOLVED_DNS_RECORDS => 0,
|
||||
self::REGISTERED_PEERS => 1,
|
||||
self::AUTHENTICATION_PASSWORDS, self::CAPTCHA_IMAGES, self::SESSIONS, self::EXTERNAL_SESSIONS => 2,
|
||||
};
|
||||
|
|
|
@ -7,16 +7,21 @@ enum StandardError : int
|
|||
// Fallback Codes
|
||||
case UNKNOWN = -1;
|
||||
|
||||
// Server/Request Errors
|
||||
case INTERNAL_SERVER_ERROR = -100;
|
||||
case SERVER_UNAVAILABLE = -101;
|
||||
case BAD_REQUEST = -102;
|
||||
case FORBIDDEN = -103;
|
||||
case UNAUTHORIZED = -104;
|
||||
case RESOLUTION_FAILED = -105;
|
||||
case CRYPTOGRAPHIC_ERROR = -106;
|
||||
|
||||
// RPC Errors
|
||||
case RPC_METHOD_NOT_FOUND = -1000;
|
||||
case RPC_INVALID_ARGUMENTS = -1001;
|
||||
|
||||
// Server Errors
|
||||
case INTERNAL_SERVER_ERROR = -2000;
|
||||
case SERVER_UNAVAILABLE = -2001;
|
||||
CASE RPC_BAD_REQUEST = -1002;
|
||||
|
||||
// Client Errors
|
||||
case BAD_REQUEST = -3000;
|
||||
case METHOD_NOT_ALLOWED = -3001;
|
||||
|
||||
// Authentication/Cryptography Errors
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
enum StandardHeaders : string
|
||||
{
|
||||
case REQUEST_TYPE = 'Request-Type';
|
||||
case ERROR_CODE = 'Error-Code';
|
||||
case IDENTIFY_AS = 'Identify-As';
|
||||
case CLIENT_NAME = 'Client-Name';
|
||||
case CLIENT_VERSION = 'Client-Version';
|
||||
case PUBLIC_KEY = 'Public-Key';
|
||||
case SIGNING_PUBLIC_KEY = 'Signing-Public-Key';
|
||||
case ENCRYPTION_PUBLIC_KEY = 'Encryption-Public-Key';
|
||||
|
||||
case SESSION_UUID = 'Session-UUID';
|
||||
case SIGNATURE = 'Signature';
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
enum RequestType : string
|
||||
{
|
||||
/**
|
||||
* Represents the action of getting server information (Non-RPC Request)
|
||||
*/
|
||||
case INFO = 'info';
|
||||
|
||||
/**
|
||||
* Represents the action of initiating a session.
|
||||
*/
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
<?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),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
namespace Socialbox\Managers;
|
||||
|
||||
use DateTime;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\Database;
|
||||
use Socialbox\Classes\SecuredPassword;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
||||
use Socialbox\Objects\Database\SecurePasswordRecord;
|
||||
|
||||
class PasswordManager
|
||||
{
|
||||
|
@ -34,154 +36,139 @@
|
|||
|
||||
return $stmt->fetchColumn() > 0;
|
||||
}
|
||||
catch (\PDOException $e)
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('An error occurred while checking the password usage in the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a password for a given user or peer record by securely encrypting it
|
||||
* and storing it in the authentication_passwords database table.
|
||||
* Sets a secured password for the given peer UUID or registered peer record.
|
||||
*
|
||||
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
|
||||
* @param string $password The plaintext password to be securely stored.
|
||||
* @throws CryptographyException If an error occurs while securing the password.
|
||||
* @throws DatabaseOperationException If an error occurs while attempting to store the password in the database.
|
||||
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
|
||||
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
|
||||
* @param string $hash The plaintext password to be securely stored.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If an error occurs while storing the password in the database.
|
||||
* @throws CryptographyException If an error occurs during password encryption or hashing.
|
||||
*/
|
||||
public static function setPassword(string|RegisteredPeerRecord $peerUuid, string $password): void
|
||||
public static function setPassword(string|RegisteredPeerRecord $peerUuid, string $hash): void
|
||||
{
|
||||
if($peerUuid instanceof RegisteredPeerRecord)
|
||||
{
|
||||
$peerUuid = $peerUuid->getUuid();
|
||||
}
|
||||
|
||||
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
||||
$securedPassword = SecuredPassword::securePassword($peerUuid, $password, $encryptionRecord);
|
||||
|
||||
// Throws an exception if the hash is invalid
|
||||
Cryptography::validatePasswordHash($hash);
|
||||
|
||||
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
|
||||
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||
|
||||
try
|
||||
{
|
||||
$stmt = Database::getConnection()->prepare("INSERT INTO authentication_passwords (peer_uuid, iv, encrypted_password, encrypted_tag) VALUES (:peer_uuid, :iv, :encrypted_password, :encrypted_tag)");
|
||||
$stmt = Database::getConnection()->prepare("INSERT INTO authentication_passwords (peer_uuid, hash) VALUES (:peer_uuid, :hash)");
|
||||
$stmt->bindParam(":peer_uuid", $peerUuid);
|
||||
|
||||
$iv = $securedPassword->getIv();
|
||||
$stmt->bindParam(':iv', $iv);
|
||||
|
||||
$encryptedPassword = $securedPassword->getEncryptedPassword();
|
||||
$stmt->bindParam(':encrypted_password', $encryptedPassword);
|
||||
|
||||
$encryptedTag = $securedPassword->getEncryptedTag();
|
||||
$stmt->bindParam(':encrypted_tag', $encryptedTag);
|
||||
$stmt->bindParam(':hash', $securedPassword);
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
catch(\PDOException $e)
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to set password for user %s', $peerUuid), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the password for a given peer identified by their UUID or a RegisteredPeerRecord.
|
||||
* Updates the secured password associated with the given peer UUID.
|
||||
*
|
||||
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
|
||||
* @param string $newPassword The new password to be set for the peer.
|
||||
* @throws CryptographyException If an error occurs while securing the new password.
|
||||
* @throws DatabaseOperationException If the update operation fails due to a database error.
|
||||
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
|
||||
* @returns void
|
||||
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
|
||||
* @param string $hash The new password to be stored securely.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If an error occurs while updating the password in the database.
|
||||
* @throws CryptographyException If an error occurs while encrypting the password or validating the hash.
|
||||
*/
|
||||
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $newPassword): void
|
||||
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $hash): void
|
||||
{
|
||||
if($peerUuid instanceof RegisteredPeerRecord)
|
||||
{
|
||||
$peerUuid = $peerUuid->getUuid();
|
||||
}
|
||||
|
||||
Cryptography::validatePasswordHash($hash);
|
||||
|
||||
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
||||
$securedPassword = SecuredPassword::securePassword($peerUuid, $newPassword, $encryptionRecord);
|
||||
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
|
||||
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||
|
||||
try
|
||||
{
|
||||
$stmt = Database::getConnection()->prepare("UPDATE authentication_passwords SET iv=:iv, encrypted_password=:encrypted_password, encrypted_tag=:encrypted_tag, updated=:updated WHERE peer_uuid=:peer_uuid");
|
||||
$stmt->bindParam(":peer_uuid", $peerUuid);
|
||||
|
||||
$iv = $securedPassword->getIv();
|
||||
$stmt->bindParam(':iv', $iv);
|
||||
|
||||
$encryptedPassword = $securedPassword->getEncryptedPassword();
|
||||
$stmt->bindParam(':encrypted_password', $encryptedPassword);
|
||||
|
||||
$encryptedTag = $securedPassword->getEncryptedTag();
|
||||
$stmt->bindParam(':encrypted_tag', $encryptedTag);
|
||||
|
||||
$updated = $securedPassword->getUpdated()->format('Y-m-d H:i:s');
|
||||
$stmt = Database::getConnection()->prepare("UPDATE authentication_passwords SET hash=:hash, updated=:updated WHERE peer_uuid=:peer_uuid");
|
||||
$updated = (new DateTime())->setTimestamp(time());
|
||||
$stmt->bindParam(':hash', $securedPassword);
|
||||
$stmt->bindParam(':updated', $updated);
|
||||
$stmt->bindParam(':peer_uuid', $peerUuid);
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
catch(\PDOException $e)
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to update password for user %s', $peerUuid), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the password record associated with the given peer UUID.
|
||||
* Verifies a given password against a stored password hash for a specific peer.
|
||||
*
|
||||
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
|
||||
* @return SecurePasswordRecord|null Returns a SecurePasswordRecord if found, or null if no record is present.
|
||||
* @throws DatabaseOperationException If a database operation error occurs during the retrieval process.
|
||||
* @param string|RegisteredPeerRecord $peerUuid The unique identifier of the peer, or an instance of RegisteredPeerRecord.
|
||||
* @param string $hash The password hash to verify.
|
||||
* @return bool Returns true if the password matches the stored hash; false otherwise.
|
||||
* @throws CryptographyException If the password hash is invalid or an error occurs during the cryptographic operation.
|
||||
* @throws DatabaseOperationException If an error occurs during the database operation.
|
||||
*/
|
||||
private static function getPassword(string|RegisteredPeerRecord $peerUuid): ?SecurePasswordRecord
|
||||
public static function verifyPassword(string|RegisteredPeerRecord $peerUuid, string $hash): bool
|
||||
{
|
||||
if($peerUuid instanceof RegisteredPeerRecord)
|
||||
{
|
||||
$peerUuid = $peerUuid->getUuid();
|
||||
}
|
||||
|
||||
Cryptography::validatePasswordHash($hash);
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT * FROM authentication_passwords WHERE peer_uuid=:peer_uuid LIMIT 1");
|
||||
$statement->bindParam(':peer_uuid', $peerUuid);
|
||||
$stmt = Database::getConnection()->prepare('SELECT hash FROM authentication_passwords WHERE peer_uuid=:uuid');
|
||||
$stmt->bindParam(':uuid', $peerUuid);
|
||||
$stmt->execute();
|
||||
|
||||
$statement->execute();
|
||||
$data = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($data === false)
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if($record === false)
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return SecurePasswordRecord::fromArray($data);
|
||||
}
|
||||
catch(\PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to retrieve password record for user %s', $peerUuid), $e);
|
||||
}
|
||||
}
|
||||
$encryptedHash = $record['hash'];
|
||||
$decryptedHash = null;
|
||||
foreach(Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys() as $key)
|
||||
{
|
||||
try
|
||||
{
|
||||
$decryptedHash = Cryptography::decryptMessage($encryptedHash, $key, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||
}
|
||||
catch(CryptographyException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the provided password matches the secured password associated with the given peer UUID.
|
||||
*
|
||||
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
|
||||
* @param string $password The password to be verified.
|
||||
* @return bool Returns true if the password is verified successfully; otherwise, false.
|
||||
* @throws DatabaseOperationException If an error occurs while retrieving the password record from the database.
|
||||
* @throws CryptographyException If an error occurs while verifying the password.
|
||||
*/
|
||||
public static function verifyPassword(string|RegisteredPeerRecord $peerUuid, string $password): bool
|
||||
{
|
||||
$securedPassword = self::getPassword($peerUuid);
|
||||
if($securedPassword === null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if($decryptedHash === null)
|
||||
{
|
||||
throw new CryptographyException('Cannot decrypt hashed password');
|
||||
}
|
||||
|
||||
$encryptionRecords = EncryptionRecordsManager::getAllRecords();
|
||||
return SecuredPassword::verifyPassword($password, $securedPassword, $encryptionRecords);
|
||||
return Cryptography::verifyPassword($hash, $decryptedHash);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('An error occurred while verifying the password', $e);
|
||||
}
|
||||
}
|
||||
}
|
184
src/Socialbox/Managers/ResolvedDnsRecordsManager.php
Normal file
184
src/Socialbox/Managers/ResolvedDnsRecordsManager.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Managers;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use PDOException;
|
||||
use Socialbox\Classes\Database;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Objects\DnsRecord;
|
||||
|
||||
class ResolvedDnsRecordsManager
|
||||
{
|
||||
/**
|
||||
* Checks whether a resolved server record exists in the database for the provided domain.
|
||||
*
|
||||
* @param string $domain The domain name to check for existence in the resolved records.
|
||||
* @return bool True if the resolved server record exists, otherwise false.
|
||||
* @throws DatabaseOperationException If the process encounters a database error.
|
||||
*/
|
||||
public static function resolvedServerExists(string $domain): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM resolved_dns_records WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
return $statement->fetchColumn() > 0;
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to check if a resolved server exists in the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a resolved server record from the database for the provided domain.
|
||||
*
|
||||
* @param string $domain The domain name of the resolved server to be deleted.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the deletion process encounters a database error.
|
||||
*/
|
||||
public static function deleteResolvedServer(string $domain): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("DELETE FROM resolved_dns_records WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to delete a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last updated timestamp of a resolved server from the database for a given domain.
|
||||
*
|
||||
* This method queries the database to fetch the timestamp indicating when the resolved server
|
||||
* associated with the specified domain was last updated.
|
||||
*
|
||||
* @param string $domain The domain name for which the last updated timestamp is to be retrieved.
|
||||
* @return DateTime The DateTime object representing the last updated timestamp of the resolved server.
|
||||
*
|
||||
* @throws DatabaseOperationException If the operation to retrieve the updated timestamp from the
|
||||
* database fails.
|
||||
*/
|
||||
public static function getResolvedServerUpdated(string $domain): DateTime
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT updated FROM resolved_dns_records WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
$result = $statement->fetchColumn();
|
||||
return new DateTime($result);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to get the updated date of a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a DNS record for the specified domain from the database.
|
||||
*
|
||||
* This method fetches the DNS record details, such as the RPC endpoint, public key,
|
||||
* and expiration details, associated with the provided domain. If no record is found,
|
||||
* it returns null.
|
||||
*
|
||||
* @param string $domain The domain name for which the DNS record is to be retrieved.
|
||||
* @return DnsRecord|null The DNS record object if found, or null if no record exists for the given domain.
|
||||
*
|
||||
* @throws DatabaseOperationException If the operation to retrieve the DNS record from
|
||||
* the database fails.
|
||||
*/
|
||||
public static function getDnsRecord(string $domain): ?DnsRecord
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT * FROM resolved_dns_records WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
$result = $statement->fetch();
|
||||
|
||||
if($result === false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return DnsRecord::fromArray([
|
||||
'rpc_endpoint' => $result['rpc_endpoint'],
|
||||
'public_key' => $result['public_key'],
|
||||
'expires' => $result['expires']
|
||||
]);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to get a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a resolved server in the database based on the provided domain and DNS record.
|
||||
*
|
||||
* If a resolved server for the given domain already exists in the database, the server's details
|
||||
* will be updated. Otherwise, a new record will be inserted into the database.
|
||||
*
|
||||
* @param string $domain The domain name associated with the resolved server.
|
||||
* @param DnsRecord $dnsRecord An object containing DNS record details such as the RPC endpoint,
|
||||
* public key, and expiration details.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the operation to add or update the resolved server in
|
||||
* the database fails.
|
||||
*/
|
||||
public static function addResolvedServer(string $domain, DnsRecord $dnsRecord): void
|
||||
{
|
||||
$endpoint = $dnsRecord->getRpcEndpoint();
|
||||
$publicKey = $dnsRecord->getPublicSigningKey();
|
||||
|
||||
if(self::resolvedServerExists($domain))
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("UPDATE resolved_dns_records SET rpc_endpoint=?, public_key=?, expires=?, updated=? WHERE domain=?");
|
||||
$statement->bindParam(1, $endpoint);
|
||||
$statement->bindParam(2, $publicKey);
|
||||
$expires = (new DateTime())->setTimestamp($dnsRecord->getExpires());
|
||||
$statement->bindParam(3, $expires);
|
||||
$updated = new DateTime();
|
||||
$statement->bindParam(4, $updated);
|
||||
$statement->bindParam(5, $domain);
|
||||
$statement->execute();
|
||||
|
||||
if($statement->rowCount() === 0)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to update a resolved server in the database');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("INSERT INTO resolved_dns_records (domain, rpc_endpoint, public_key, expires, updated) VALUES (?, ?, ?, ?, ?)");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->bindParam(2, $endpoint);
|
||||
$statement->bindParam(3, $publicKey);
|
||||
$expires = (new DateTime())->setTimestamp($dnsRecord->getExpires());
|
||||
$statement->bindParam(4, $expires);
|
||||
$updated = new DateTime();
|
||||
$statement->bindParam(5, $updated);
|
||||
$statement->execute();
|
||||
|
||||
if($statement->rowCount() === 0)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to add a resolved server to the database');
|
||||
}
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to add a resolved server to the database', $e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Managers;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use PDOException;
|
||||
use Socialbox\Classes\Database;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Objects\Database\ResolvedServerRecord;
|
||||
use Socialbox\Objects\ResolvedServer;
|
||||
|
||||
class ResolvedServersManager
|
||||
{
|
||||
/**
|
||||
* Checks if a resolved server exists in the database for the given domain.
|
||||
*
|
||||
* @param string $domain The domain to check in the resolved_servers table.
|
||||
* @return bool True if the server exists in the database, otherwise false.
|
||||
* @throws DatabaseOperationException If there is an error during the database operation.
|
||||
*/
|
||||
public static function resolvedServerExists(string $domain): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM resolved_servers WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
return $statement->fetchColumn() > 0;
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to check if a resolved server exists in the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a resolved server from the database.
|
||||
*
|
||||
* @param string $domain The domain name of the server to be deleted.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the deletion operation fails.
|
||||
*/
|
||||
public static function deleteResolvedServer(string $domain): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("DELETE FROM resolved_servers WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to delete a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last updated date of a resolved server based on its domain.
|
||||
*
|
||||
* @param string $domain The domain of the resolved server.
|
||||
* @return DateTime The last updated date and time of the resolved server.
|
||||
*/
|
||||
public static function getResolvedServerUpdated(string $domain): DateTime
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT updated FROM resolved_servers WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
$result = $statement->fetchColumn();
|
||||
return new DateTime($result);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to get the updated date of a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resolved server record from the database for a given domain.
|
||||
*
|
||||
* @param string $domain The domain name for which to retrieve the resolved server record.
|
||||
* @return ResolvedServerRecord|null The resolved server record associated with the given domain.
|
||||
* @throws DatabaseOperationException If there is an error retrieving the resolved server record from the database.
|
||||
* @throws \DateMalformedStringException If the date string is malformed.
|
||||
*/
|
||||
public static function getResolvedServer(string $domain): ?ResolvedServerRecord
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT * FROM resolved_servers WHERE domain=?");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->execute();
|
||||
$result = $statement->fetch();
|
||||
|
||||
if($result === false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResolvedServerRecord::fromArray($result);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to get a resolved server from the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a resolved server in the database.
|
||||
*
|
||||
* @param string $domain The domain name of the resolved server.
|
||||
* @param ResolvedServer $resolvedServer The resolved server object containing endpoint and public key.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If a database operation fails.
|
||||
*/
|
||||
public static function addResolvedServer(string $domain, ResolvedServer $resolvedServer): void
|
||||
{
|
||||
$endpoint = $resolvedServer->getEndpoint();
|
||||
$publicKey = $resolvedServer->getPublicKey();
|
||||
|
||||
if(self::resolvedServerExists($domain))
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("UPDATE resolved_servers SET endpoint=?, public_key=?, updated=NOW() WHERE domain=?");
|
||||
$statement->bindParam(1, $endpoint);
|
||||
$statement->bindParam(2, $publicKey);
|
||||
$statement->bindParam(3, $domain);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to update a resolved server in the database', $e);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("INSERT INTO resolved_servers (domain, endpoint, public_key) VALUES (?, ?, ?)");
|
||||
$statement->bindParam(1, $domain);
|
||||
$statement->bindParam(2, $endpoint);
|
||||
$statement->bindParam(3, $publicKey);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to add a resolved server to the database', $e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,34 +19,27 @@
|
|||
use Socialbox\Exceptions\StandardException;
|
||||
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
||||
use Socialbox\Objects\Database\SessionRecord;
|
||||
use Socialbox\Objects\KeyPair;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class SessionManager
|
||||
{
|
||||
/**
|
||||
* Creates a new session with the given public key.
|
||||
*
|
||||
* @param string $publicKey The public key to associate with the new session.
|
||||
* @param RegisteredPeerRecord $peer
|
||||
*
|
||||
* @throws InvalidArgumentException If the public key is empty or invalid.
|
||||
* @throws DatabaseOperationException If there is an error while creating the session in the database.
|
||||
*/
|
||||
public static function createSession(string $publicKey, RegisteredPeerRecord $peer, string $clientName, string $clientVersion): string
|
||||
public static function createSession(RegisteredPeerRecord $peer, string $clientName, string $clientVersion, string $clientPublicSigningKey, string $clientPublicEncryptionKey, KeyPair $serverEncryptionKeyPair): string
|
||||
{
|
||||
if($publicKey === '')
|
||||
if($clientPublicSigningKey === '' || Cryptography::validatePublicSigningKey($clientPublicSigningKey) === false)
|
||||
{
|
||||
throw new InvalidArgumentException('The public key cannot be empty');
|
||||
throw new InvalidArgumentException('The public key is not a valid Ed25519 public key');
|
||||
}
|
||||
|
||||
if(!Cryptography::validatePublicKey($publicKey))
|
||||
if($clientPublicEncryptionKey === '' || Cryptography::validatePublicEncryptionKey($clientPublicEncryptionKey) === false)
|
||||
{
|
||||
throw new InvalidArgumentException('The given public key is invalid');
|
||||
throw new InvalidArgumentException('The public key is not a valid X25519 public key');
|
||||
}
|
||||
|
||||
$uuid = Uuid::v4()->toRfc4122();
|
||||
$flags = [];
|
||||
|
||||
// TODO: Update this to support `host` peers
|
||||
if($peer->isEnabled())
|
||||
{
|
||||
$flags[] = SessionFlags::AUTHENTICATION_REQUIRED;
|
||||
|
@ -119,13 +112,18 @@
|
|||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, public_key, flags) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$statement->bindParam(1, $uuid);
|
||||
$statement->bindParam(2, $peerUuid);
|
||||
$statement->bindParam(3, $clientName);
|
||||
$statement->bindParam(4, $clientVersion);
|
||||
$statement->bindParam(5, $publicKey);
|
||||
$statement->bindParam(6, $implodedFlags);
|
||||
$statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, client_public_signing_key, client_public_encryption_key, server_public_encryption_key, server_private_encryption_key, flags) VALUES (:uuid, :peer_uuid, :client_name, :client_version, :client_public_signing_key, :client_public_encryption_key, :server_public_encryption_key, :server_private_encryption_key, :flags)");
|
||||
$statement->bindParam(':uuid', $uuid);
|
||||
$statement->bindParam(':peer_uuid', $peerUuid);
|
||||
$statement->bindParam(':client_name', $clientName);
|
||||
$statement->bindParam(':client_version', $clientVersion);
|
||||
$statement->bindParam(':client_public_signing_key', $clientPublicSigningKey);
|
||||
$statement->bindParam(':client_public_encryption_key', $clientPublicEncryptionKey);
|
||||
$serverPublicEncryptionKey = $serverEncryptionKeyPair->getPublicKey();
|
||||
$statement->bindParam(':server_public_encryption_key', $serverPublicEncryptionKey);
|
||||
$serverPrivateEncryptionKey = $serverEncryptionKeyPair->getPrivateKey();
|
||||
$statement->bindParam(':server_private_encryption_key', $serverPrivateEncryptionKey);
|
||||
$statement->bindParam(':flags', $implodedFlags);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
|
@ -186,7 +184,6 @@
|
|||
|
||||
// Convert the timestamp fields to DateTime objects
|
||||
$data['created'] = new DateTime($data['created']);
|
||||
|
||||
if(isset($data['last_request']) && $data['last_request'] !== null)
|
||||
{
|
||||
$data['last_request'] = new DateTime($data['last_request']);
|
||||
|
@ -205,53 +202,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the authenticated peer associated with the given session UUID.
|
||||
*
|
||||
* @param string $uuid The UUID of the session to update.
|
||||
* @param RegisteredPeerRecord|string $registeredPeerUuid
|
||||
* @return void
|
||||
* @throws DatabaseOperationException
|
||||
*/
|
||||
public static function updatePeer(string $uuid, RegisteredPeerRecord|string $registeredPeerUuid): void
|
||||
{
|
||||
if($registeredPeerUuid instanceof RegisteredPeerRecord)
|
||||
{
|
||||
$registeredPeerUuid = $registeredPeerUuid->getUuid();
|
||||
}
|
||||
|
||||
Logger::getLogger()->verbose(sprintf("Assigning peer %s to session %s", $registeredPeerUuid, $uuid));
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("UPDATE sessions SET peer_uuid=? WHERE uuid=?");
|
||||
$statement->bindParam(1, $registeredPeerUuid);
|
||||
$statement->bindParam(2, $uuid);
|
||||
$statement->execute();
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to update authenticated peer', $e);
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateAuthentication(string $uuid, bool $authenticated): void
|
||||
{
|
||||
Logger::getLogger()->verbose(sprintf("Marking session %s as authenticated: %s", $uuid, $authenticated ? 'true' : 'false'));
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("UPDATE sessions SET authenticated=? WHERE uuid=?");
|
||||
$statement->bindParam(1, $authenticated);
|
||||
$statement->bindParam(2, $uuid);
|
||||
$statement->execute();
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException('Failed to update authenticated peer', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last request timestamp for a given session by its UUID.
|
||||
*
|
||||
|
@ -305,24 +255,28 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the encryption key for the specified session.
|
||||
* Updates the encryption keys and session state for a specific session UUID in the database.
|
||||
*
|
||||
* @param string $uuid The unique identifier of the session for which the encryption key is to be set.
|
||||
* @param string $encryptionKey The new encryption key to be assigned.
|
||||
* @param string $uuid The unique identifier for the session to update.
|
||||
* @param string $privateSharedSecret The private shared secret to secure communication.
|
||||
* @param string $clientEncryptionKey The client's encryption key used for transport security.
|
||||
* @param string $serverEncryptionKey The server's encryption key used for transport security.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the database operation fails.
|
||||
* @throws DatabaseOperationException If an error occurs during the database operation.
|
||||
*/
|
||||
public static function setEncryptionKey(string $uuid, string $encryptionKey): void
|
||||
public static function setEncryptionKeys(string $uuid, string $privateSharedSecret, string $clientEncryptionKey, string $serverEncryptionKey): void
|
||||
{
|
||||
Logger::getLogger()->verbose(sprintf('Setting the encryption key for %s', $uuid));
|
||||
|
||||
try
|
||||
{
|
||||
$state_value = SessionState::ACTIVE->value;
|
||||
$statement = Database::getConnection()->prepare('UPDATE sessions SET state=?, encryption_key=? WHERE uuid=?');
|
||||
$statement->bindParam(1, $state_value);
|
||||
$statement->bindParam(2, $encryptionKey);
|
||||
$statement->bindParam(3, $uuid);
|
||||
$statement = Database::getConnection()->prepare('UPDATE sessions SET state=:state, private_shared_secret=:private_shared_secret, client_transport_encryption_key=:client_transport_encryption_key, server_transport_encryption_key=:server_transport_encryption_key WHERE uuid=:uuid');
|
||||
$statement->bindParam(':state', $state_value);
|
||||
$statement->bindParam(':private_shared_secret', $privateSharedSecret);
|
||||
$statement->bindParam(':client_transport_encryption_key', $clientEncryptionKey);
|
||||
$statement->bindParam(':server_transport_encryption_key', $serverEncryptionKey);
|
||||
$statement->bindParam(':uuid', $uuid);
|
||||
|
||||
$statement->execute();
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\Utilities;
|
||||
use Socialbox\Enums\SessionState;
|
||||
use Socialbox\Enums\StandardHeaders;
|
||||
use Socialbox\Enums\Types\RequestType;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
|
@ -18,7 +15,7 @@
|
|||
class ClientRequest
|
||||
{
|
||||
private array $headers;
|
||||
private RequestType $requestType;
|
||||
private ?RequestType $requestType;
|
||||
private ?string $requestBody;
|
||||
|
||||
private ?string $clientName;
|
||||
|
@ -27,6 +24,14 @@
|
|||
private ?string $sessionUuid;
|
||||
private ?string $signature;
|
||||
|
||||
/**
|
||||
* Initializes the instance with the provided request headers and optional request body.
|
||||
*
|
||||
* @param array $headers An associative array of request headers used to set properties such as client name, version, and others.
|
||||
* @param string|null $requestBody The optional body of the request, or null if not provided.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $headers, ?string $requestBody)
|
||||
{
|
||||
$this->headers = $headers;
|
||||
|
@ -34,17 +39,28 @@
|
|||
|
||||
$this->clientName = $headers[StandardHeaders::CLIENT_NAME->value] ?? null;
|
||||
$this->clientVersion = $headers[StandardHeaders::CLIENT_VERSION->value] ?? null;
|
||||
$this->requestType = RequestType::from($headers[StandardHeaders::REQUEST_TYPE->value]);
|
||||
$this->requestType = RequestType::tryFrom($headers[StandardHeaders::REQUEST_TYPE->value]);
|
||||
$this->identifyAs = $headers[StandardHeaders::IDENTIFY_AS->value] ?? null;
|
||||
$this->sessionUuid = $headers[StandardHeaders::SESSION_UUID->value] ?? null;
|
||||
$this->signature = $headers[StandardHeaders::SIGNATURE->value] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the headers.
|
||||
*
|
||||
* @return array Returns an array of headers.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified header exists in the collection of headers.
|
||||
*
|
||||
* @param StandardHeaders|string $header The header to check, either as a StandardHeaders enum or a string.
|
||||
* @return bool Returns true if the header exists, otherwise false.
|
||||
*/
|
||||
public function headerExists(StandardHeaders|string $header): bool
|
||||
{
|
||||
if(is_string($header))
|
||||
|
@ -55,6 +71,12 @@
|
|||
return isset($this->headers[$header->value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specified header.
|
||||
*
|
||||
* @param StandardHeaders|string $header The header to retrieve, provided as either a StandardHeaders enum or a string key.
|
||||
* @return string|null Returns the header value if it exists, or null if the header does not exist.
|
||||
*/
|
||||
public function getHeader(StandardHeaders|string $header): ?string
|
||||
{
|
||||
if(!$this->headerExists($header))
|
||||
|
@ -70,26 +92,51 @@
|
|||
return $this->headers[$header->value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the request body.
|
||||
*
|
||||
* @return string|null Returns the request body as a string if available, or null if not set.
|
||||
*/
|
||||
public function getRequestBody(): ?string
|
||||
{
|
||||
return $this->requestBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the client.
|
||||
*
|
||||
* @return string|null Returns the client's name if set, or null if not available.
|
||||
*/
|
||||
public function getClientName(): ?string
|
||||
{
|
||||
return $this->clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client version.
|
||||
*
|
||||
* @return string|null Returns the client version if available, or null if not set.
|
||||
*/
|
||||
public function getClientVersion(): ?string
|
||||
{
|
||||
return $this->clientVersion;
|
||||
}
|
||||
|
||||
public function getRequestType(): RequestType
|
||||
/**
|
||||
* Retrieves the request type associated with the current instance.
|
||||
*
|
||||
* @return RequestType|null Returns the associated RequestType if available, or null if not set.
|
||||
*/
|
||||
public function getRequestType(): ?RequestType
|
||||
{
|
||||
return $this->requestType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the peer address the instance identifies as.
|
||||
*
|
||||
* @return PeerAddress|null Returns a PeerAddress instance if the identification address is set, or null otherwise.
|
||||
*/
|
||||
public function getIdentifyAs(): ?PeerAddress
|
||||
{
|
||||
if($this->identifyAs === null)
|
||||
|
@ -100,11 +147,21 @@
|
|||
return PeerAddress::fromAddress($this->identifyAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the UUID of the current session.
|
||||
*
|
||||
* @return string|null Returns the session UUID if available, or null if it is not set.
|
||||
*/
|
||||
public function getSessionUuid(): ?string
|
||||
{
|
||||
return $this->sessionUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current session associated with the session UUID.
|
||||
*
|
||||
* @return SessionRecord|null Returns the associated SessionRecord if the session UUID exists, or null if no session UUID is set.
|
||||
*/
|
||||
public function getSession(): ?SessionRecord
|
||||
{
|
||||
if($this->sessionUuid === null)
|
||||
|
@ -115,6 +172,11 @@
|
|||
return SessionManager::getSession($this->sessionUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the associated peer for the current session.
|
||||
*
|
||||
* @return RegisteredPeerRecord|null Returns the associated RegisteredPeerRecord if available, or null if no session exists.
|
||||
*/
|
||||
public function getPeer(): ?RegisteredPeerRecord
|
||||
{
|
||||
$session = $this->getSession();
|
||||
|
@ -127,11 +189,22 @@
|
|||
return RegisteredPeerManager::getPeer($session->getPeerUuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the signature value.
|
||||
*
|
||||
* @return string|null The signature value or null if not set
|
||||
*/
|
||||
public function getSignature(): ?string
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature of the provided decrypted content.
|
||||
*
|
||||
* @param string $decryptedContent The decrypted content to verify the signature against.
|
||||
* @return bool True if the signature is valid, false otherwise.
|
||||
*/
|
||||
private function verifySignature(string $decryptedContent): bool
|
||||
{
|
||||
if($this->getSignature() == null || $this->getSessionUuid() == null)
|
||||
|
@ -141,7 +214,11 @@
|
|||
|
||||
try
|
||||
{
|
||||
return Cryptography::verifyContent($decryptedContent, $this->getSignature(), $this->getSession()->getPublicKey(), true);
|
||||
return Cryptography::verifyMessage(
|
||||
message: $decryptedContent,
|
||||
signature: $this->getSignature(),
|
||||
publicKey: $this->getSession()->getClientPublicSigningKey()
|
||||
);
|
||||
}
|
||||
catch(CryptographyException)
|
||||
{
|
||||
|
@ -156,52 +233,12 @@
|
|||
* @return RpcRequest[] The parsed RpcRequest objects
|
||||
* @throws RequestException Thrown if the request is invalid
|
||||
*/
|
||||
public function getRpcRequests(): array
|
||||
public function getRpcRequests(string $json): array
|
||||
{
|
||||
if($this->getSessionUuid() === null)
|
||||
$body = json_decode($json, true);
|
||||
if($body === false)
|
||||
{
|
||||
throw new RequestException("Session UUID required", 400);
|
||||
}
|
||||
|
||||
// Get the existing session
|
||||
$session = $this->getSession();
|
||||
|
||||
// If we're awaiting a DHE, encryption is not possible at this point
|
||||
if($session->getState() === SessionState::AWAITING_DHE)
|
||||
{
|
||||
throw new RequestException("DHE exchange required", 400);
|
||||
}
|
||||
|
||||
// If the session is not active, we can't serve these requests
|
||||
if($session->getState() !== SessionState::ACTIVE)
|
||||
{
|
||||
throw new RequestException("Session is not active", 400);
|
||||
}
|
||||
|
||||
// Attempt to decrypt the content and verify the signature of the request
|
||||
try
|
||||
{
|
||||
$decrypted = Cryptography::decryptTransport($this->requestBody, $session->getEncryptionKey());
|
||||
|
||||
if(!$this->verifySignature($decrypted))
|
||||
{
|
||||
throw new RequestException("Invalid request signature", 401);
|
||||
}
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
throw new RequestException("Failed to decrypt request body", 400, $e);
|
||||
}
|
||||
|
||||
// At this stage, all checks has passed; we can try parsing the RPC request
|
||||
try
|
||||
{
|
||||
// Decode the request body
|
||||
$body = Utilities::jsonDecode($decrypted);
|
||||
}
|
||||
catch(InvalidArgumentException $e)
|
||||
{
|
||||
throw new RequestException("Invalid JSON in request body: " . $e->getMessage(), 400, $e);
|
||||
throw new RequestException('Malformed JSON', 400);
|
||||
}
|
||||
|
||||
// If the body only contains a method, we assume it's a single request
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects\Database;
|
||||
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\SecuredPassword;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -1,110 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects\Database;
|
||||
namespace Socialbox\Objects\Database;
|
||||
|
||||
use DateTime;
|
||||
use Socialbox\Interfaces\SerializableInterface;
|
||||
use Socialbox\Objects\ResolvedServer;
|
||||
use DateTime;
|
||||
use Socialbox\Interfaces\SerializableInterface;
|
||||
use Socialbox\Objects\DnsRecord;
|
||||
|
||||
class ResolvedServerRecord implements SerializableInterface
|
||||
{
|
||||
private string $domain;
|
||||
private string $endpoint;
|
||||
private string $publicKey;
|
||||
private DateTime $updated;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class.
|
||||
*
|
||||
* @param array $data An associative array containing the domain, endpoint, public_key, and updated values.
|
||||
* @throws \DateMalformedStringException
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
class ResolvedServerRecord implements SerializableInterface
|
||||
{
|
||||
$this->domain = (string)$data['domain'];
|
||||
$this->endpoint = (string)$data['endpoint'];
|
||||
$this->publicKey = (string)$data['public_key'];
|
||||
private string $domain;
|
||||
private string $endpoint;
|
||||
private string $publicKey;
|
||||
private DateTime $expires;
|
||||
private DateTime $updated;
|
||||
|
||||
if(is_null($data['updated']))
|
||||
/**
|
||||
* Constructs a new instance of the class.
|
||||
*
|
||||
* @param array $data An associative array containing the domain, endpoint, public_key, and updated values.
|
||||
* @throws \DateMalformedStringException
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->updated = new DateTime();
|
||||
$this->domain = (string)$data['domain'];
|
||||
$this->endpoint = (string)$data['endpoint'];
|
||||
$this->publicKey = (string)$data['public_key'];
|
||||
|
||||
if(is_null($data['expires']))
|
||||
{
|
||||
$this->expires = new DateTime();
|
||||
}
|
||||
elseif (is_int($data['expires']))
|
||||
{
|
||||
$this->expires = (new DateTime())->setTimestamp($data['expires']);
|
||||
}
|
||||
elseif (is_string($data['expires']))
|
||||
{
|
||||
$this->expires = new DateTime($data['expires']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->expires = $data['expires'];
|
||||
}
|
||||
|
||||
if(is_null($data['updated']))
|
||||
{
|
||||
$this->updated = new DateTime();
|
||||
}
|
||||
elseif (is_int($data['updated']))
|
||||
{
|
||||
$this->updated = (new DateTime())->setTimestamp($data['updated']);
|
||||
}
|
||||
elseif (is_string($data['updated']))
|
||||
{
|
||||
$this->updated = new DateTime($data['updated']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->updated = $data['updated'];
|
||||
}
|
||||
}
|
||||
elseif (is_string($data['updated']))
|
||||
|
||||
/**
|
||||
* Retrieves the domain value.
|
||||
*
|
||||
* @return string The domain as a string.
|
||||
*/
|
||||
public function getDomain(): string
|
||||
{
|
||||
$this->updated = new DateTime($data['updated']);
|
||||
return $this->domain;
|
||||
}
|
||||
else
|
||||
|
||||
/**
|
||||
* Retrieves the configured endpoint.
|
||||
*
|
||||
* @return string The endpoint as a string.
|
||||
*/
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
$this->updated = $data['updated'];
|
||||
return $this->endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string The domain value.
|
||||
*/
|
||||
public function getDomain(): string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
/**
|
||||
* Retrieves the public key.
|
||||
*
|
||||
* @return string The public key as a string.
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string The endpoint value.
|
||||
*/
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
/**
|
||||
* Retrieves the expiration timestamp.
|
||||
*
|
||||
* @return DateTime The DateTime object representing the expiration time.
|
||||
*/
|
||||
public function getExpires(): DateTime
|
||||
{
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string The public key.
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
/**
|
||||
* Retrieves the timestamp of the last update.
|
||||
*
|
||||
* @return DateTime The DateTime object representing the last update time.
|
||||
*/
|
||||
public function getUpdated(): DateTime
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the timestamp of the last update.
|
||||
*
|
||||
* @return DateTime The DateTime object representing the last update time.
|
||||
*/
|
||||
public function getUpdated(): DateTime
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
/**
|
||||
* Fetches the DNS record based on the provided endpoint, public key, and expiration time.
|
||||
*
|
||||
* @return DnsRecord An instance of the DnsRecord containing the endpoint, public key, and expiration timestamp.
|
||||
*/
|
||||
public function getDnsRecord(): DnsRecord
|
||||
{
|
||||
return new DnsRecord($this->endpoint, $this->publicKey, $this->expires->getTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the record to a ResolvedServer object.
|
||||
*
|
||||
* @return ResolvedServer The ResolvedServer object.
|
||||
*/
|
||||
public function toResolvedServer(): ResolvedServer
|
||||
{
|
||||
return new ResolvedServer($this->endpoint, $this->publicKey);
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function fromArray(array $data): object
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \DateMalformedStringException
|
||||
*/
|
||||
public static function fromArray(array $data): object
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'domain' => $this->domain,
|
||||
'endpoint' => $this->endpoint,
|
||||
'public_key' => $this->publicKey,
|
||||
'updated' => $this->updated->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'domain' => $this->domain,
|
||||
'endpoint' => $this->endpoint,
|
||||
'public_key' => $this->publicKey,
|
||||
'updated' => $this->updated->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<?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'];
|
||||
|
||||
if($data['updated'] instanceof DateTime)
|
||||
{
|
||||
$this->updated = $data['updated'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'peer_uuid' => $this->peerUuid,
|
||||
'iv' => $this->iv,
|
||||
'encrypted_password' => $this->encryptedPassword,
|
||||
'encrypted_tag' => $this->encryptedTag,
|
||||
'updated' => $this->updated->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): SecurePasswordRecord
|
||||
{
|
||||
return new SecurePasswordRecord($data);
|
||||
}
|
||||
}
|
|
@ -15,9 +15,13 @@
|
|||
private string $clientName;
|
||||
private string $clientVersion;
|
||||
private bool $authenticated;
|
||||
private string $publicKey;
|
||||
private string $clientPublicSigningKey;
|
||||
public string $clientPublicEncryptionKey;
|
||||
private string $serverPublicEncryptionKey;
|
||||
private string $serverPrivateEncryptionKey;
|
||||
private ?string $clientTransportEncryptionKey;
|
||||
private ?string $serverTransportEncryptionKey;
|
||||
private SessionState $state;
|
||||
private ?string $encryptionKey;
|
||||
/**
|
||||
* @var SessionFlags[]
|
||||
*/
|
||||
|
@ -42,10 +46,14 @@
|
|||
$this->clientName = $data['client_name'];
|
||||
$this->clientVersion = $data['client_version'];
|
||||
$this->authenticated = $data['authenticated'] ?? false;
|
||||
$this->publicKey = $data['public_key'];
|
||||
$this->clientPublicSigningKey = $data['client_public_signing_key'];
|
||||
$this->clientPublicEncryptionKey = $data['client_public_encryption_key'];
|
||||
$this->serverPublicEncryptionKey = $data['server_public_encryption_key'];
|
||||
$this->serverPrivateEncryptionKey = $data['server_private_encryption_key'];
|
||||
$this->clientTransportEncryptionKey = $data['client_transport_encryption_key'] ?? null;
|
||||
$this->serverTransportEncryptionKey = $data['server_transport_encryption_key'] ?? null;
|
||||
$this->created = $data['created'];
|
||||
$this->lastRequest = $data['last_request'];
|
||||
$this->encryptionKey = $data['encryption_key'] ?? null;
|
||||
$this->flags = SessionFlags::fromString($data['flags']);
|
||||
|
||||
if(SessionState::tryFrom($data['state']) == null)
|
||||
|
@ -99,9 +107,55 @@
|
|||
*
|
||||
* @return string Returns the public key as a string.
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
public function getClientPublicSigningKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
return $this->clientPublicSigningKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the encryption key associated with the instance.
|
||||
*
|
||||
* @return string|null Returns the encryption key as a string, or null if not set.
|
||||
*/
|
||||
public function getClientPublicEncryptionKey(): ?string
|
||||
{
|
||||
return $this->clientPublicEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServerPublicEncryptionKey(): string
|
||||
{
|
||||
return $this->serverPublicEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServerPrivateEncryptionKey(): string
|
||||
{
|
||||
return $this->serverPrivateEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client encryption key associated with the instance.
|
||||
*
|
||||
* @return string|null Returns the client encryption key as a string, or null if not set.
|
||||
*/
|
||||
public function getClientTransportEncryptionKey(): ?string
|
||||
{
|
||||
return $this->clientTransportEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server encryption key associated with the instance.
|
||||
*
|
||||
* @return string|null Returns the server encryption key as a string, or null if not set.
|
||||
*/
|
||||
public function getServerTransportEncryptionKey(): ?string
|
||||
{
|
||||
return $this->serverTransportEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,16 +168,6 @@
|
|||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the encryption key associated with the instance.
|
||||
*
|
||||
* @return string|null Returns the encryption key as a string.
|
||||
*/
|
||||
public function getEncryptionKey(): ?string
|
||||
{
|
||||
return $this->encryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the creation date and time of the object.
|
||||
*
|
||||
|
@ -194,6 +238,11 @@
|
|||
return $this->clientVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current session state into a standard session state object.
|
||||
*
|
||||
* @return \Socialbox\Objects\Standard\SessionState The standardized session state object.
|
||||
*/
|
||||
public function toStandardSessionState(): \Socialbox\Objects\Standard\SessionState
|
||||
{
|
||||
return new \Socialbox\Objects\Standard\SessionState([
|
||||
|
@ -207,10 +256,7 @@
|
|||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class using the provided array data.
|
||||
*
|
||||
* @param array $data An associative array of data used to initialize the object properties.
|
||||
* @return object Returns a newly created object instance.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function fromArray(array $data): object
|
||||
{
|
||||
|
@ -218,10 +264,7 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts the object's properties to an associative array.
|
||||
*
|
||||
* @return array An associative array representing the object's data, including keys 'uuid', 'peer_uuid',
|
||||
* 'authenticated', 'public_key', 'state', 'flags', 'created', and 'last_request'.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
@ -229,7 +272,12 @@
|
|||
'uuid' => $this->uuid,
|
||||
'peer_uuid' => $this->peerUuid,
|
||||
'authenticated' => $this->authenticated,
|
||||
'public_key' => $this->publicKey,
|
||||
'client_public_signing_key' => $this->clientPublicSigningKey,
|
||||
'client_public_encryption_key' => $this->clientPublicEncryptionKey,
|
||||
'server_public_encryption_key' => $this->serverPublicEncryptionKey,
|
||||
'server_private_encryption_key' => $this->serverPrivateEncryptionKey,
|
||||
'client_transport_encryption_key' => $this->clientTransportEncryptionKey,
|
||||
'server_transport_encryption_key' => $this->serverTransportEncryptionKey,
|
||||
'state' => $this->state->value,
|
||||
'flags' => SessionFlags::toString($this->flags),
|
||||
'created' => $this->created,
|
||||
|
|
67
src/Socialbox/Objects/DnsRecord.php
Normal file
67
src/Socialbox/Objects/DnsRecord.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
class DnsRecord
|
||||
{
|
||||
private string $rpcEndpoint;
|
||||
private string $publicSigningKey;
|
||||
private int $expires;
|
||||
|
||||
/**
|
||||
* Constructor for initializing the class with required parameters.
|
||||
*
|
||||
* @param string $rpcEndpoint The RPC endpoint.
|
||||
* @param string $publicSigningKey The public signing key.
|
||||
* @param int $expires The expiration time in seconds.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $rpcEndpoint, string $publicSigningKey, int $expires)
|
||||
{
|
||||
$this->rpcEndpoint = $rpcEndpoint;
|
||||
$this->publicSigningKey = $publicSigningKey;
|
||||
$this->expires = $expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the RPC endpoint.
|
||||
*
|
||||
* @return string The RPC endpoint.
|
||||
*/
|
||||
public function getRpcEndpoint(): string
|
||||
{
|
||||
return $this->rpcEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public signing key.
|
||||
*
|
||||
* @return string Returns the public signing key as a string.
|
||||
*/
|
||||
public function getPublicSigningKey(): string
|
||||
{
|
||||
return $this->publicSigningKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expiration time.
|
||||
*
|
||||
* @return int The expiration timestamp as an integer.
|
||||
*/
|
||||
public function getExpires(): int
|
||||
{
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of DnsRecord from the provided array of data.
|
||||
*
|
||||
* @param array $data An associative array containing the keys 'rpc_endpoint', 'public_key', and 'expires'
|
||||
* required to instantiate a DnsRecord object.
|
||||
* @return DnsRecord Returns a new DnsRecord instance populated with the data from the array.
|
||||
*/
|
||||
public static function fromArray(array $data): DnsRecord
|
||||
{
|
||||
return new DnsRecord($data['rpc_endpoint'], $data['public_key'], $data['expires']);
|
||||
}
|
||||
}
|
|
@ -2,49 +2,63 @@
|
|||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
use Socialbox\Interfaces\SerializableInterface;
|
||||
|
||||
/**
|
||||
* Represents an exported session containing cryptographic keys, identifiers, and endpoints.
|
||||
*/
|
||||
class ExportedSession
|
||||
class ExportedSession implements SerializableInterface
|
||||
{
|
||||
private string $peerAddress;
|
||||
private string $privateKey;
|
||||
private string $publicKey;
|
||||
private string $encryptionKey;
|
||||
private string $serverPublicKey;
|
||||
private string $rpcEndpoint;
|
||||
private string $sessionUuid;
|
||||
private string $sessionUUID;
|
||||
private string $transportEncryptionAlgorithm;
|
||||
private int $serverKeypairExpires;
|
||||
private string $serverPublicSigningKey;
|
||||
private string $serverPublicEncryptionKey;
|
||||
private string $clientPublicSigningKey;
|
||||
private string $clientPrivateSigningKey;
|
||||
private string $clientPublicEncryptionKey;
|
||||
private string $clientPrivateEncryptionKey;
|
||||
private string $privateSharedSecret;
|
||||
private string $clientTransportEncryptionKey;
|
||||
private string $serverTransportEncryptionKey;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the class with the provided data.
|
||||
* Constructor method to initialize class properties from the provided data array.
|
||||
*
|
||||
* @param array $data An associative array containing the configuration data.
|
||||
* Expected keys:
|
||||
* - 'peer_address': The address of the peer.
|
||||
* - 'private_key': The private key for secure communication.
|
||||
* - 'public_key': The public key for secure communication.
|
||||
* - 'encryption_key': The encryption key used for communication.
|
||||
* - 'server_public_key': The server's public key.
|
||||
* - 'rpc_endpoint': The RPC endpoint for network communication.
|
||||
* - 'session_uuid': The unique identifier for the session.
|
||||
* @param array $data Associative array containing the required properties such as:
|
||||
* 'peer_address', 'rpc_endpoint', 'session_uuid',
|
||||
* 'server_public_signing_key', 'server_public_encryption_key',
|
||||
* 'client_public_signing_key', 'client_private_signing_key',
|
||||
* 'client_public_encryption_key', 'client_private_encryption_key',
|
||||
* 'private_shared_secret', 'client_transport_encryption_key',
|
||||
* 'server_transport_encryption_key'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->peerAddress = $data['peer_address'];
|
||||
$this->privateKey = $data['private_key'];
|
||||
$this->publicKey = $data['public_key'];
|
||||
$this->encryptionKey = $data['encryption_key'];
|
||||
$this->serverPublicKey = $data['server_public_key'];
|
||||
$this->rpcEndpoint = $data['rpc_endpoint'];
|
||||
$this->sessionUuid = $data['session_uuid'];
|
||||
$this->sessionUUID = $data['session_uuid'];
|
||||
$this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm'];
|
||||
$this->serverKeypairExpires = $data['server_keypair_expires'];
|
||||
$this->serverPublicSigningKey = $data['server_public_signing_key'];
|
||||
$this->serverPublicEncryptionKey = $data['server_public_encryption_key'];
|
||||
$this->clientPublicSigningKey = $data['client_public_signing_key'];
|
||||
$this->clientPrivateSigningKey = $data['client_private_signing_key'];
|
||||
$this->clientPublicEncryptionKey = $data['client_public_encryption_key'];
|
||||
$this->clientPrivateEncryptionKey = $data['client_private_encryption_key'];
|
||||
$this->privateSharedSecret = $data['private_shared_secret'];
|
||||
$this->clientTransportEncryptionKey = $data['client_transport_encryption_key'];
|
||||
$this->serverTransportEncryptionKey = $data['server_transport_encryption_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the address of the peer.
|
||||
* Retrieves the peer address associated with the current instance.
|
||||
*
|
||||
* @return string The peer's address as a string.
|
||||
* @return string The peer address.
|
||||
*/
|
||||
public function getPeerAddress(): string
|
||||
{
|
||||
|
@ -52,47 +66,7 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves the private key.
|
||||
*
|
||||
* @return string The private key.
|
||||
*/
|
||||
public function getPrivateKey(): string
|
||||
{
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public key.
|
||||
*
|
||||
* @return string The public key.
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the encryption key.
|
||||
*
|
||||
* @return string The encryption key.
|
||||
*/
|
||||
public function getEncryptionKey(): string
|
||||
{
|
||||
return $this->encryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public key of the server.
|
||||
*
|
||||
* @return string The server's public key.
|
||||
*/
|
||||
public function getServerPublicKey(): string
|
||||
{
|
||||
return $this->serverPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the RPC endpoint URL.
|
||||
* Retrieves the RPC endpoint.
|
||||
*
|
||||
* @return string The RPC endpoint.
|
||||
*/
|
||||
|
@ -102,38 +76,150 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves the unique identifier for the current session.
|
||||
* Retrieves the session UUID associated with the current instance.
|
||||
*
|
||||
* @return string The session UUID.
|
||||
*/
|
||||
public function getSessionUuid(): string
|
||||
public function getSessionUUID(): string
|
||||
{
|
||||
return $this->sessionUuid;
|
||||
return $this->sessionUUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current instance into an array representation.
|
||||
* Retrieves the transport encryption algorithm being used.
|
||||
*
|
||||
* @return array An associative array containing the instance properties and their respective values.
|
||||
* @return string The transport encryption algorithm.
|
||||
*/
|
||||
public function getTransportEncryptionAlgorithm(): string
|
||||
{
|
||||
return $this->transportEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expiration time of the server key pair.
|
||||
*
|
||||
* @return int The expiration timestamp of the server key pair.
|
||||
*/
|
||||
public function getServerKeypairExpires(): int
|
||||
{
|
||||
return $this->serverKeypairExpires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public signing key of the server.
|
||||
*
|
||||
* @return string The server's public signing key.
|
||||
*/
|
||||
public function getServerPublicSigningKey(): string
|
||||
{
|
||||
return $this->serverPublicSigningKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server's public encryption key.
|
||||
*
|
||||
* @return string The server's public encryption key.
|
||||
*/
|
||||
public function getServerPublicEncryptionKey(): string
|
||||
{
|
||||
return $this->serverPublicEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client's public signing key.
|
||||
*
|
||||
* @return string The client's public signing key.
|
||||
*/
|
||||
public function getClientPublicSigningKey(): string
|
||||
{
|
||||
return $this->clientPublicSigningKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client's private signing key.
|
||||
*
|
||||
* @return string The client's private signing key.
|
||||
*/
|
||||
public function getClientPrivateSigningKey(): string
|
||||
{
|
||||
return $this->clientPrivateSigningKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the public encryption key of the client.
|
||||
*
|
||||
* @return string The client's public encryption key.
|
||||
*/
|
||||
public function getClientPublicEncryptionKey(): string
|
||||
{
|
||||
return $this->clientPublicEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client's private encryption key.
|
||||
*
|
||||
* @return string The client's private encryption key.
|
||||
*/
|
||||
public function getClientPrivateEncryptionKey(): string
|
||||
{
|
||||
return $this->clientPrivateEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the private shared secret associated with the current instance.
|
||||
*
|
||||
* @return string The private shared secret.
|
||||
*/
|
||||
public function getPrivateSharedSecret(): string
|
||||
{
|
||||
return $this->privateSharedSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client transport encryption key.
|
||||
*
|
||||
* @return string The client transport encryption key.
|
||||
*/
|
||||
public function getClientTransportEncryptionKey(): string
|
||||
{
|
||||
return $this->clientTransportEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server transport encryption key associated with the current instance.
|
||||
*
|
||||
* @return string The server transport encryption key.
|
||||
*/
|
||||
public function getServerTransportEncryptionKey(): string
|
||||
{
|
||||
return $this->serverTransportEncryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'peer_address' => $this->peerAddress,
|
||||
'private_key' => $this->privateKey,
|
||||
'public_key' => $this->publicKey,
|
||||
'encryption_key' => $this->encryptionKey,
|
||||
'server_public_key' => $this->serverPublicKey,
|
||||
'rpc_endpoint' => $this->rpcEndpoint,
|
||||
'session_uuid' => $this->sessionUuid
|
||||
'session_uuid' => $this->sessionUUID,
|
||||
'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm,
|
||||
'server_keypair_expires' => $this->serverKeypairExpires,
|
||||
'server_public_signing_key' => $this->serverPublicSigningKey,
|
||||
'server_public_encryption_key' => $this->serverPublicEncryptionKey,
|
||||
'client_public_signing_key' => $this->clientPublicSigningKey,
|
||||
'client_private_signing_key' => $this->clientPrivateSigningKey,
|
||||
'client_public_encryption_key' => $this->clientPublicEncryptionKey,
|
||||
'client_private_encryption_key' => $this->clientPrivateEncryptionKey,
|
||||
'private_shared_secret' => $this->privateSharedSecret,
|
||||
'client_transport_encryption_key' => $this->clientTransportEncryptionKey,
|
||||
'server_transport_encryption_key' => $this->serverTransportEncryptionKey,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of ExportedSession from the provided array.
|
||||
*
|
||||
* @param array $data The input data used to construct the ExportedSession instance.
|
||||
* @return ExportedSession The new ExportedSession instance created from the given data.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function fromArray(array $data): ExportedSession
|
||||
{
|
||||
|
|
|
@ -1,25 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects;
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
class ResolvedServer
|
||||
{
|
||||
private string $endpoint;
|
||||
private string $publicKey;
|
||||
use Socialbox\Objects\Standard\ServerInformation;
|
||||
|
||||
public function __construct(string $endpoint, string $publicKey)
|
||||
class ResolvedServer
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
}
|
||||
private DnsRecord $dnsRecord;
|
||||
private ServerInformation $serverInformation;
|
||||
}
|
25
src/Socialbox/Objects/ResolvedServerOld.php
Normal file
25
src/Socialbox/Objects/ResolvedServerOld.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
class ResolvedServer
|
||||
{
|
||||
private string $endpoint;
|
||||
private string $publicKey;
|
||||
|
||||
public function __construct(string $endpoint, string $publicKey)
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
}
|
75
src/Socialbox/Objects/Standard/ServerInformation.php
Normal file
75
src/Socialbox/Objects/Standard/ServerInformation.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects\Standard;
|
||||
|
||||
use Socialbox\Interfaces\SerializableInterface;
|
||||
|
||||
class ServerInformation implements SerializableInterface
|
||||
{
|
||||
private string $serverName;
|
||||
private int $serverKeypairExpires;
|
||||
private string $transportEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* Constructor method to initialize the object with provided data.
|
||||
*
|
||||
* @param array $data The array containing initialization parameters, including 'server_name', 'server_keypair_expires', and 'transport_encryption_algorithm'.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->serverName = $data['server_name'];
|
||||
$this->serverKeypairExpires = $data['server_keypair_expires'];
|
||||
$this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the server.
|
||||
*
|
||||
* @return string The server name.
|
||||
*/
|
||||
public function getServerName(): string
|
||||
{
|
||||
return $this->serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expiration time of the server key pair.
|
||||
*
|
||||
* @return int The expiration timestamp of the server key pair.
|
||||
*/
|
||||
public function getServerKeypairExpires(): int
|
||||
{
|
||||
return $this->serverKeypairExpires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transport encryption algorithm being used.
|
||||
*
|
||||
* @return string The transport encryption algorithm.
|
||||
*/
|
||||
public function getTransportEncryptionAlgorithm(): string
|
||||
{
|
||||
return $this->transportEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function fromArray(array $data): ServerInformation
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'server_name' => $this->serverName,
|
||||
'server_keypair_expires' => $this->serverKeypairExpires,
|
||||
'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
use InvalidArgumentException;
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\DnsHelper;
|
||||
use Socialbox\Classes\Logger;
|
||||
use Socialbox\Classes\ServerResolver;
|
||||
use Socialbox\Classes\Utilities;
|
||||
|
@ -16,6 +17,7 @@
|
|||
use Socialbox\Enums\StandardHeaders;
|
||||
use Socialbox\Enums\StandardMethods;
|
||||
use Socialbox\Enums\Types\RequestType;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\RequestException;
|
||||
use Socialbox\Exceptions\StandardException;
|
||||
|
@ -23,34 +25,37 @@
|
|||
use Socialbox\Managers\SessionManager;
|
||||
use Socialbox\Objects\ClientRequest;
|
||||
use Socialbox\Objects\PeerAddress;
|
||||
use Socialbox\Objects\Standard\ServerInformation;
|
||||
use Throwable;
|
||||
|
||||
class Socialbox
|
||||
{
|
||||
/**
|
||||
* Handles incoming client requests by validating required headers and processing
|
||||
* the request based on its type. The method ensures proper handling of
|
||||
* specific request types like RPC, session initiation, and DHE exchange,
|
||||
* while returning an appropriate HTTP response for invalid or missing data.
|
||||
* Handles incoming client requests by parsing request headers, determining the request type,
|
||||
* and routing the request to the appropriate handler method. Implements error handling for
|
||||
* missing or invalid request types.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function handleRequest(): void
|
||||
{
|
||||
$requestHeaders = Utilities::getRequestHeaders();
|
||||
|
||||
if(!isset($requestHeaders[StandardHeaders::REQUEST_TYPE->value]))
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Missing required header: ' . StandardHeaders::REQUEST_TYPE->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::REQUEST_TYPE->value);
|
||||
return;
|
||||
}
|
||||
|
||||
$clientRequest = new ClientRequest($requestHeaders, file_get_contents('php://input') ?? null);
|
||||
|
||||
// Handle the request type, only `init` and `dhe` are not encrypted using the session's encrypted key
|
||||
// Handle the request type, only `init` and `dhe` are not encrypted using the session's encrypted key
|
||||
// RPC Requests must be encrypted and signed by the client, vice versa for server responses.
|
||||
switch(RequestType::tryFrom($clientRequest->getHeader(StandardHeaders::REQUEST_TYPE)))
|
||||
switch($clientRequest->getRequestType())
|
||||
{
|
||||
case RequestType::INFO:
|
||||
self::handleInformationRequest();
|
||||
break;
|
||||
|
||||
case RequestType::INITIATE_SESSION:
|
||||
self::handleInitiateSession($clientRequest);
|
||||
break;
|
||||
|
@ -64,58 +69,66 @@
|
|||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
print('Invalid Request-Type header');
|
||||
break;
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Request-Type header');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the headers in an initialization request to ensure that all
|
||||
* required information is present and properly formatted. This includes
|
||||
* checking for headers such as Client Name, Client Version, Public Key,
|
||||
* and Identify-As, as well as validating the Identify-As header value.
|
||||
* If any validation fails, a corresponding HTTP response code and message
|
||||
* are returned.
|
||||
* Handles an information request by setting the appropriate HTTP response code,
|
||||
* content type headers, and printing the server information in JSON format.
|
||||
*
|
||||
* @param ClientRequest $clientRequest The client request containing headers to validate.
|
||||
* @return void
|
||||
*/
|
||||
private static function handleInformationRequest(): void
|
||||
{
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
Logger::getLogger()->info(json_encode(self::getServerInformation()->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
print(json_encode(self::getServerInformation()->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the initial headers of a client request to ensure all required headers exist
|
||||
* and contain valid values. If any validation fails, an error response is returned.
|
||||
*
|
||||
* @param ClientRequest $clientRequest The client request containing headers to be validated.
|
||||
* @return bool Returns true if all required headers are valid, otherwise false.
|
||||
*/
|
||||
private static function validateInitHeaders(ClientRequest $clientRequest): bool
|
||||
{
|
||||
if(!$clientRequest->getClientName())
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$clientRequest->getClientVersion())
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$clientRequest->headerExists(StandardHeaders::PUBLIC_KEY))
|
||||
if(!$clientRequest->headerExists(StandardHeaders::SIGNING_PUBLIC_KEY))
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Missing required header: ' . StandardHeaders::PUBLIC_KEY->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNING_PUBLIC_KEY->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$clientRequest->headerExists(StandardHeaders::ENCRYPTION_PUBLIC_KEY))
|
||||
{
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::ENCRYPTION_PUBLIC_KEY->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
||||
{
|
||||
http_response_code(400);
|
||||
print('Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -123,24 +136,25 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Processes a client request to initiate a session. Validates required headers,
|
||||
* ensures the peer is authorized and enabled, and creates a new session UUID
|
||||
* if all checks pass. Handles edge cases like missing headers, invalid inputs,
|
||||
* or unauthorized peers.
|
||||
* Handles the initiation of a session for a client request. This involves validating headers,
|
||||
* verifying peer identities, resolving domains, registering peers if necessary, and finally
|
||||
* creating a session while providing the required session UUID as a response.
|
||||
*
|
||||
* @param ClientRequest $clientRequest The request from the client containing
|
||||
* the required headers and information.
|
||||
* @param ClientRequest $clientRequest The incoming client request containing all necessary headers
|
||||
* and identification information required to initiate the session.
|
||||
* @return void
|
||||
*/
|
||||
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
||||
{
|
||||
// This is only called for the `init` request type
|
||||
if(!self::validateInitHeaders($clientRequest))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We always accept the client's public key at first
|
||||
$publicKey = $clientRequest->getHeader(StandardHeaders::PUBLIC_KEY);
|
||||
$clientPublicSigningKey = $clientRequest->getHeader(StandardHeaders::SIGNING_PUBLIC_KEY);
|
||||
$clientPublicEncryptionKey = $clientRequest->getHeader(StandardHeaders::ENCRYPTION_PUBLIC_KEY);
|
||||
|
||||
// If the peer is identifying as the same domain
|
||||
if($clientRequest->getIdentifyAs()->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
||||
|
@ -148,9 +162,8 @@
|
|||
// Prevent the peer from identifying as the host unless it's coming from an external domain
|
||||
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
||||
{
|
||||
http_response_code(403);
|
||||
print('Unauthorized: The requested peer is not allowed to identify as the host');
|
||||
return;
|
||||
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as the host');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If the peer is identifying as an external domain
|
||||
|
@ -159,64 +172,49 @@
|
|||
// Only allow the host to identify as an external peer
|
||||
if($clientRequest->getIdentifyAs()->getUsername() !== ReservedUsernames::HOST->value)
|
||||
{
|
||||
http_response_code(403);
|
||||
print('Unauthorized: The requested peer is not allowed to identify as an external peer');
|
||||
self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: Any external peer must identify as the host, only the host can preform actions on behalf of it\'s peers');
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// We need to obtain the public key of the host, since we can't trust the client
|
||||
// We need to obtain the public key of the host, since we can't trust the client (Use database)
|
||||
$resolvedServer = ServerResolver::resolveDomain($clientRequest->getIdentifyAs()->getDomain());
|
||||
|
||||
// Override the public key with the resolved server's public key
|
||||
$publicKey = $resolvedServer->getPublicKey();
|
||||
}
|
||||
catch (Exceptions\ResolutionException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to resolve the host domain', $e);
|
||||
http_response_code(409);
|
||||
print('Conflict: Failed to resolve the host domain: ' . $e->getMessage());
|
||||
return;
|
||||
// Override the public signing key with the resolved server's public key
|
||||
// Encryption key can be left as is.
|
||||
$clientPublicSigningKey = $resolvedServer->getPublicSigningKey();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
Logger::getLogger()->error('An internal error occurred while resolving the host domain', $e);
|
||||
http_response_code(500);
|
||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
||||
{
|
||||
print(Utilities::throwableToString($e));
|
||||
}
|
||||
else
|
||||
{
|
||||
print('An internal error occurred');
|
||||
}
|
||||
|
||||
self::returnError(502, StandardError::RESOLUTION_FAILED, 'Conflict: Failed to resolve the host domain: ' . $e->getMessage(), $e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check if we have a registered peer with the same address
|
||||
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
||||
|
||||
// If the peer is registered, check if it is enabled
|
||||
if($registeredPeer !== null && !$registeredPeer->isEnabled())
|
||||
{
|
||||
// Refuse to create a session if the peer is disabled/banned
|
||||
// This also prevents multiple sessions from being created for the same peer
|
||||
// A cron job should be used to clean up disabled peers
|
||||
http_response_code(403);
|
||||
print('Unauthorized: The requested peer is disabled/banned');
|
||||
// Refuse to create a session if the peer is disabled/banned, this usually happens when
|
||||
// a peer gets banned or more commonly when a client attempts to register as this peer but
|
||||
// destroyed the session before it was created.
|
||||
// This is to prevent multiple sessions from being created for the same peer, this is cleaned up
|
||||
// with a cron job using `socialbox clean-sessions`
|
||||
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: The requested peer is disabled/banned');
|
||||
return;
|
||||
}
|
||||
// Otherwise the peer isn't registered, so we need to register it
|
||||
else
|
||||
{
|
||||
// Check if registration is enabled
|
||||
if(!Configuration::getRegistrationConfiguration()->isRegistrationEnabled())
|
||||
{
|
||||
http_response_code(403);
|
||||
print('Unauthorized: Registration is disabled');
|
||||
self::returnError(401, StandardError::UNAUTHORIZED, 'Unauthorized: Registration is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -226,141 +224,220 @@
|
|||
$registeredPeer = RegisteredPeerManager::getPeer($peerUuid);
|
||||
}
|
||||
|
||||
// Create the session UUID
|
||||
$sessionUuid = SessionManager::createSession($publicKey, $registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion());
|
||||
// Generate server's encryption keys for this session
|
||||
$serverEncryptionKey = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
// Create the session passing on the registered peer, client name, version, and public keys
|
||||
$sessionUuid = SessionManager::createSession($registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion(), $clientPublicSigningKey, $clientPublicEncryptionKey, $serverEncryptionKey);
|
||||
|
||||
// The server responds back with the session UUID & The server's public encryption key as the header
|
||||
http_response_code(201); // Created
|
||||
header('Content-Type: text/plain');
|
||||
header(StandardHeaders::ENCRYPTION_PUBLIC_KEY->value . ': ' . $serverEncryptionKey->getPublicKey());
|
||||
print($sessionUuid); // Return the session UUID
|
||||
}
|
||||
catch(InvalidArgumentException $e)
|
||||
{
|
||||
http_response_code(412); // Precondition failed
|
||||
print($e->getMessage()); // Why the request failed
|
||||
// This is usually thrown due to an invalid input
|
||||
self::returnError(400, StandardError::BAD_REQUEST, $e->getMessage(), $e);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Logger::getLogger()->error('An internal error occurred while initiating the session', $e);
|
||||
http_response_code(500); // Internal server error
|
||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
||||
{
|
||||
print(Utilities::throwableToString($e));
|
||||
}
|
||||
else
|
||||
{
|
||||
print('An internal error occurred');
|
||||
}
|
||||
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'An internal error occurred while initiating the session', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the Diffie-Hellman key exchange by decrypting the encrypted key passed on from the client using
|
||||
* the server's private key and setting the encryption key to the session.
|
||||
* Handles the Diffie-Hellman Ephemeral (DHE) key exchange process between the client and server,
|
||||
* ensuring secure transport encryption key negotiation. The method validates request headers,
|
||||
* session state, and cryptographic operations, and updates the session with the resulting keys
|
||||
* and state upon successful negotiation.
|
||||
*
|
||||
* 412: Headers malformed
|
||||
* 400: Bad request
|
||||
* 500: Internal server error
|
||||
* 204: Success, no content.
|
||||
* @param ClientRequest $clientRequest The request object containing headers, body, and session details
|
||||
* required to perform the DHE exchange.
|
||||
*
|
||||
* @param ClientRequest $clientRequest
|
||||
* @return void
|
||||
*/
|
||||
private static function handleDheExchange(ClientRequest $clientRequest): void
|
||||
{
|
||||
// Check if the session UUID is set in the headers
|
||||
// Check if the session UUID is set in the headers, bad request if not
|
||||
if(!$clientRequest->headerExists(StandardHeaders::SESSION_UUID))
|
||||
{
|
||||
Logger::getLogger()->verbose('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
|
||||
http_response_code(412);
|
||||
print('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the request body is empty
|
||||
if(!$clientRequest->headerExists(StandardHeaders::SIGNATURE))
|
||||
{
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNATURE->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(empty($clientRequest->getHeader(StandardHeaders::SIGNATURE)))
|
||||
{
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Bad request: The signature is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the request body is empty, bad request if so
|
||||
if(empty($clientRequest->getRequestBody()))
|
||||
{
|
||||
Logger::getLogger()->verbose('Bad request: The key exchange request body is empty');
|
||||
|
||||
http_response_code(400);
|
||||
print('Bad request: The key exchange request body is empty');
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Bad request: The key exchange request body is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the session is awaiting a DHE exchange
|
||||
if($clientRequest->getSession()->getState() !== SessionState::AWAITING_DHE)
|
||||
// Check if the session is awaiting a DHE exchange, forbidden if not
|
||||
$session = $clientRequest->getSession();
|
||||
if($session->getState() !== SessionState::AWAITING_DHE)
|
||||
{
|
||||
Logger::getLogger()->verbose('Bad request: The session is not awaiting a DHE exchange');
|
||||
|
||||
http_response_code(400);
|
||||
print('Bad request: The session is not awaiting a DHE exchange');
|
||||
self::returnError(403, StandardError::FORBIDDEN, 'Bad request: The session is not awaiting a DHE exchange');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// DHE STAGE: CLIENT -> SERVER
|
||||
// Server & Client: Begin the DHE exchange using the exchanged public keys.
|
||||
// On the client's side, same method but with the server's public key & client's private key
|
||||
try
|
||||
{
|
||||
// Attempt to decrypt the encrypted key passed on from the client
|
||||
$encryptionKey = Cryptography::decryptContent($clientRequest->getRequestBody(), Configuration::getInstanceConfiguration()->getPrivateKey());
|
||||
$sharedSecret = Cryptography::performDHE($session->getClientPublicEncryptionKey(), $session->getServerPrivateEncryptionKey());
|
||||
}
|
||||
catch (Exceptions\CryptographyException $e)
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
Logger::getLogger()->error(sprintf('Bad Request: Failed to decrypt the key for session %s', $clientRequest->getSessionUuid()), $e);
|
||||
|
||||
http_response_code(400);
|
||||
print('Bad Request: Cryptography error, make sure you have encrypted the key using the server\'s public key; ' . $e->getMessage());
|
||||
Logger::getLogger()->error('Failed to perform DHE exchange', $e);
|
||||
self::returnError(422, StandardError::CRYPTOGRAPHIC_ERROR, 'DHE exchange failed', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// STAGE 1: CLIENT -> SERVER
|
||||
try
|
||||
{
|
||||
// Finally set the encryption key to the session
|
||||
SessionManager::setEncryptionKey($clientRequest->getSessionUuid(), $encryptionKey);
|
||||
// Attempt to decrypt the encrypted key passed on from the client using the shared secret
|
||||
$clientTransportEncryptionKey = Cryptography::decryptShared($clientRequest->getRequestBody(), $sharedSecret);
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
self::returnError(400, StandardError::CRYPTOGRAPHIC_ERROR, 'Failed to decrypt the key', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the signature from the client and validate it against the decrypted key
|
||||
$clientSignature = $clientRequest->getHeader(StandardHeaders::SIGNATURE);
|
||||
if(!Cryptography::verifyMessage($clientTransportEncryptionKey, $clientSignature, $session->getClientPublicSigningKey()))
|
||||
{
|
||||
self::returnError(401, StandardError::UNAUTHORIZED, 'Invalid signature');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the encryption key given by the client
|
||||
if(!Cryptography::validateEncryptionKey($clientTransportEncryptionKey, Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()))
|
||||
{
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'The transport encryption key is invalid and does not meet the server\'s requirements');
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive stage complete, now we move on to the server's response
|
||||
|
||||
// STAGE 2: SERVER -> CLIENT
|
||||
try
|
||||
{
|
||||
// Generate the server's transport encryption key (our side)
|
||||
$serverTransportEncryptionKey = Cryptography::generateEncryptionKey(Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm());
|
||||
|
||||
// Sign the shared secret using the server's private key
|
||||
$signature = Cryptography::signMessage($serverTransportEncryptionKey, Configuration::getCryptographyConfiguration()->getHostPrivateKey());
|
||||
// Encrypt the server's transport key using the shared secret
|
||||
$encryptedServerTransportKey = Cryptography::encryptShared($serverTransportEncryptionKey, $sharedSecret);
|
||||
}
|
||||
catch (CryptographyException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to generate the server\'s transport encryption key', $e);
|
||||
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'There was an error while trying to process the DHE exchange', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now update the session details with all the encryption keys and the state
|
||||
try
|
||||
{
|
||||
SessionManager::setEncryptionKeys($clientRequest->getSessionUuid(), $sharedSecret, $clientTransportEncryptionKey, $serverTransportEncryptionKey);
|
||||
SessionManager::updateState($clientRequest->getSessionUuid(), SessionState::ACTIVE);
|
||||
}
|
||||
catch (DatabaseOperationException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to set the encryption key for the session', $e);
|
||||
http_response_code(500);
|
||||
|
||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
||||
{
|
||||
print(Utilities::throwableToString($e));
|
||||
}
|
||||
else
|
||||
{
|
||||
print('Internal Server Error: Failed to set the encryption key for the session');
|
||||
}
|
||||
|
||||
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to set the encryption key for the session', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::getLogger()->info(sprintf('DHE exchange completed for session %s', $clientRequest->getSessionUuid()));
|
||||
http_response_code(204); // Success, no content
|
||||
// Return the encrypted transport key for the server back to the client.
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/octet-stream');
|
||||
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
||||
print($encryptedServerTransportKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming RPC requests from a client, processes each request,
|
||||
* and returns the appropriate response(s) or error(s).
|
||||
* Handles a Remote Procedure Call (RPC) request, ensuring proper decryption,
|
||||
* signature verification, and response encryption, while processing one or more
|
||||
* RPC methods as specified in the request.
|
||||
*
|
||||
* @param ClientRequest $clientRequest The RPC client request containing headers, body, and session information.
|
||||
*
|
||||
* @param ClientRequest $clientRequest The client's request containing one or multiple RPC calls.
|
||||
* @return void
|
||||
*/
|
||||
private static function handleRpc(ClientRequest $clientRequest): void
|
||||
{
|
||||
// Client: Encrypt the request body using the server's encryption key & sign it using the client's private key
|
||||
// Server: Decrypt the request body using the servers's encryption key & verify the signature using the client's public key
|
||||
// Server: Encrypt the response using the client's encryption key & sign it using the server's private key
|
||||
|
||||
if(!$clientRequest->headerExists(StandardHeaders::SESSION_UUID))
|
||||
{
|
||||
Logger::getLogger()->verbose('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
return;
|
||||
}
|
||||
|
||||
http_response_code(412);
|
||||
print('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
||||
if(!$clientRequest->headerExists(StandardHeaders::SIGNATURE))
|
||||
{
|
||||
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNATURE->value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the client session
|
||||
$session = $clientRequest->getSession();
|
||||
|
||||
// Verify if the session is active
|
||||
if($session->getState() !== SessionState::ACTIVE)
|
||||
{
|
||||
self::returnError(403, StandardError::FORBIDDEN, 'Session is not active');
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$clientRequests = $clientRequest->getRpcRequests();
|
||||
// Attempt to decrypt the request body using the server's encryption key
|
||||
$decryptedContent = Cryptography::decryptMessage($clientRequest->getRequestBody(), $session->getServerTransportEncryptionKey(), Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm());
|
||||
}
|
||||
catch(CryptographyException $e)
|
||||
{
|
||||
self::returnError(400, StandardError::CRYPTOGRAPHIC_ERROR, 'Failed to decrypt request', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to verify the decrypted content using the client's public signing key
|
||||
if(!Cryptography::verifyMessage($decryptedContent, $clientRequest->getSignature(), $session->getClientPublicSigningKey()))
|
||||
{
|
||||
self::returnError(400, StandardError::CRYPTOGRAPHIC_ERROR, 'Signature verification failed');
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$clientRequests = $clientRequest->getRpcRequests($decryptedContent);
|
||||
}
|
||||
catch (RequestException $e)
|
||||
{
|
||||
http_response_code($e->getCode());
|
||||
print($e->getMessage());
|
||||
self::returnError($e->getCode(), $e->getStandardError(), $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -442,16 +519,24 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$session = $clientRequest->getSession();
|
||||
|
||||
try
|
||||
{
|
||||
$encryptedResponse = Cryptography::encryptTransport($response, $clientRequest->getSession()->getEncryptionKey());
|
||||
$signature = Cryptography::signContent($response, Configuration::getInstanceConfiguration()->getPrivateKey(), true);
|
||||
$encryptedResponse = Cryptography::encryptMessage(
|
||||
message: $response,
|
||||
encryptionKey: $session->getClientTransportEncryptionKey(),
|
||||
algorithm: Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()
|
||||
);
|
||||
|
||||
$signature = Cryptography::signMessage(
|
||||
message: $response,
|
||||
privateKey: Configuration::getCryptographyConfiguration()->getHostPrivateKey()
|
||||
);
|
||||
}
|
||||
catch (Exceptions\CryptographyException $e)
|
||||
{
|
||||
Logger::getLogger()->error('Failed to encrypt the response', $e);
|
||||
http_response_code(500);
|
||||
print('Internal Server Error: Failed to encrypt the response');
|
||||
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to encrypt the server response', $e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -460,4 +545,69 @@
|
|||
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
||||
print($encryptedResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an error response by setting the HTTP response code, headers, and printing an error message.
|
||||
* Optionally includes exception details in the response if enabled in the configuration.
|
||||
* Logs the error message and any associated exception.
|
||||
*
|
||||
* @param int $responseCode The HTTP response code to send.
|
||||
* @param StandardError $standardError The standard error containing error details.
|
||||
* @param string|null $message An optional error message to display. Defaults to the message from the StandardError instance.
|
||||
* @param Throwable|null $e An optional throwable to include in logs and the response, if enabled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function returnError(int $responseCode, StandardError $standardError, ?string $message=null, ?Throwable $e=null): void
|
||||
{
|
||||
if($message === null)
|
||||
{
|
||||
$message = $standardError->getMessage();
|
||||
}
|
||||
|
||||
http_response_code($responseCode);
|
||||
header('Content-Type: text/plain');
|
||||
header(StandardHeaders::ERROR_CODE->value . ': ' . $standardError->value);
|
||||
print($message);
|
||||
|
||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions() && $e !== null)
|
||||
{
|
||||
print(PHP_EOL . PHP_EOL . Utilities::throwableToString($e));
|
||||
}
|
||||
|
||||
if($e !== null)
|
||||
{
|
||||
Logger::getLogger()->error($message, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server information by assembling data from the configuration settings.
|
||||
*
|
||||
* @return ServerInformation An instance of ServerInformation containing details such as server name, hashing algorithm,
|
||||
* transport AES mode, and AES key length.
|
||||
*/
|
||||
public static function getServerInformation(): ServerInformation
|
||||
{
|
||||
return ServerInformation::fromArray([
|
||||
'server_name' => Configuration::getInstanceConfiguration()->getName(),
|
||||
'server_keypair_expires' => Configuration::getCryptographyConfiguration()->getHostKeyPairExpires(),
|
||||
'transport_encryption_algorithm' => Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the DNS record by generating a TXT record using the RPC endpoint,
|
||||
* host public key, and host key pair expiration from the configuration.
|
||||
*
|
||||
* @return string The generated DNS TXT record.
|
||||
*/
|
||||
public static function getDnsRecord(): string
|
||||
{
|
||||
return DnsHelper::generateTxt(
|
||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(),
|
||||
Configuration::getCryptographyConfiguration()->getHostPublicKey(),
|
||||
Configuration::getCryptographyConfiguration()->getHostKeyPairExpires()
|
||||
);
|
||||
}
|
||||
}
|
439
tests/Socialbox/Classes/CryptographyTest.php
Normal file
439
tests/Socialbox/Classes/CryptographyTest.php
Normal file
|
@ -0,0 +1,439 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Objects\KeyPair;
|
||||
|
||||
class CryptographyTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test that generateEncryptionKeyPair generates a KeyPair with valid keys.
|
||||
*/
|
||||
public function testGenerateEncryptionKeyPairProducesValidKeyPair(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
$this->assertInstanceOf(KeyPair::class, $keyPair);
|
||||
$this->assertNotEmpty($keyPair->getPublicKey());
|
||||
$this->assertNotEmpty($keyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated public key starts with the defined encryption key type prefix.
|
||||
*/
|
||||
public function testGeneratedPublicKeyHasEncryptionPrefix(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
$this->assertStringStartsWith('enc:', $keyPair->getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated private key starts with the defined encryption key type prefix.
|
||||
*/
|
||||
public function testGeneratedPrivateKeyHasEncryptionPrefix(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
$this->assertStringStartsWith('enc:', $keyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated keys are of different base64-encoded string values.
|
||||
*/
|
||||
public function testPublicAndPrivateKeysAreDifferent(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateEncryptionKeyPair();
|
||||
|
||||
$this->assertNotEquals($keyPair->getPublicKey(), $keyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that generateSigningKeyPair generates a KeyPair with valid keys.
|
||||
*/
|
||||
public function testGenerateSigningKeyPairProducesValidKeyPair(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
|
||||
$this->assertInstanceOf(KeyPair::class, $keyPair);
|
||||
$this->assertNotEmpty($keyPair->getPublicKey());
|
||||
$this->assertNotEmpty($keyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated public key starts with the defined signing key type prefix.
|
||||
*/
|
||||
public function testGeneratedPublicKeyHasSigningPrefix(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
|
||||
$this->assertStringStartsWith('sig:', $keyPair->getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated private key starts with the defined signing key type prefix.
|
||||
*/
|
||||
public function testGeneratedPrivateKeyHasSigningPrefix(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
|
||||
$this->assertStringStartsWith('sig:', $keyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that performDHE successfully calculates a shared secret with valid keys.
|
||||
*/
|
||||
public function testPerformDHESuccessfullyCalculatesSharedSecret(): void
|
||||
{
|
||||
$aliceKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
$aliceSigningKeyPair = Cryptography::generateSigningKeyPair();
|
||||
$bobKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
$bobSigningKeyPair = Cryptography::generateSigningKeyPair();
|
||||
|
||||
// Alice performs DHE with Bob
|
||||
$aliceSharedSecret = Cryptography::performDHE($bobKeyPair->getPublicKey(), $aliceKeyPair->getPrivateKey());
|
||||
// Bob performs DHE with Alice
|
||||
$bobSharedSecret = Cryptography::performDHE($aliceKeyPair->getPublicKey(), $bobKeyPair->getPrivateKey());
|
||||
$this->assertEquals($aliceSharedSecret, $bobSharedSecret);
|
||||
|
||||
// Alice sends "Hello, Bob!" to Bob, signing the message and encrypting it with the shared secret
|
||||
$message = "Hello, Bob!";
|
||||
$aliceSignature = Cryptography::signMessage($message, $aliceSigningKeyPair->getPrivateKey());
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $aliceSharedSecret);
|
||||
|
||||
// Bob decrypts the message and verifies the signature
|
||||
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $bobSharedSecret);
|
||||
$isValid = Cryptography::verifyMessage($decryptedMessage, $aliceSignature, $aliceSigningKeyPair->getPublicKey());
|
||||
$this->assertEquals($message, $decryptedMessage);
|
||||
$this->assertTrue($isValid);
|
||||
|
||||
// Bob sends "Hello, Alice!" to Alice, signing the message and encrypting it with the shared secret
|
||||
$message = "Hello, Alice!";
|
||||
$bobSignature = Cryptography::signMessage($message, $bobSigningKeyPair->getPrivateKey());
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $bobSharedSecret);
|
||||
|
||||
// Alice decrypts the message and verifies the signature
|
||||
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $aliceSharedSecret);
|
||||
$isValid = Cryptography::verifyMessage($decryptedMessage, $bobSignature, $bobSigningKeyPair->getPublicKey());
|
||||
$this->assertEquals($message, $decryptedMessage);
|
||||
$this->assertTrue($isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that performDHE throws an exception when an invalid public key is used.
|
||||
*/
|
||||
public function testPerformDHEThrowsExceptionForInvalidPublicKey(): void
|
||||
{
|
||||
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
$invalidPublicKey = 'invalid_key';
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage('Invalid key type. Expected enc:');
|
||||
|
||||
Cryptography::performDHE($invalidPublicKey, $encryptionKeyPair->getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that performDHE throws an exception when an invalid private key is used.
|
||||
*/
|
||||
public function testPerformDHEThrowsExceptionForInvalidPrivateKey(): void
|
||||
{
|
||||
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
$invalidPrivateKey = 'invalid_key';
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage('Invalid key type. Expected enc:');
|
||||
|
||||
Cryptography::performDHE($encryptionKeyPair->getPublicKey(), $invalidPrivateKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that encrypt correctly encrypts a message with a valid shared secret.
|
||||
*/
|
||||
public function testEncryptSuccessfullyEncryptsMessage(): void
|
||||
{
|
||||
$sharedSecret = Cryptography::performDHE(
|
||||
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
|
||||
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
|
||||
);
|
||||
$message = "Test message";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
|
||||
|
||||
$this->assertNotEmpty($encryptedMessage);
|
||||
$this->assertNotEquals($message, $encryptedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that encrypt throws an exception when given an invalid shared secret.
|
||||
*/
|
||||
public function testEncryptThrowsExceptionForInvalidSharedSecret(): void
|
||||
{
|
||||
$invalidSharedSecret = "invalid_secret";
|
||||
$message = "Test message";
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Encryption failed");
|
||||
|
||||
Cryptography::encryptShared($message, $invalidSharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the encrypted message is different from the original message.
|
||||
*/
|
||||
public function testEncryptProducesDifferentMessage(): void
|
||||
{
|
||||
$sharedSecret = Cryptography::performDHE(
|
||||
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
|
||||
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
|
||||
);
|
||||
$message = "Another test message";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
|
||||
|
||||
$this->assertNotEquals($message, $encryptedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that decrypt successfully decrypts an encrypted message with a valid shared secret.
|
||||
*/
|
||||
public function testDecryptSuccessfullyDecryptsMessage(): void
|
||||
{
|
||||
$sharedSecret = Cryptography::performDHE(
|
||||
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
|
||||
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
|
||||
);
|
||||
$message = "Decryption test message";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
|
||||
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $sharedSecret);
|
||||
|
||||
$this->assertEquals($message, $decryptedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that decrypt throws an exception when given an invalid shared secret.
|
||||
*/
|
||||
public function testDecryptThrowsExceptionForInvalidSharedSecret(): void
|
||||
{
|
||||
$sharedSecret = Cryptography::performDHE(
|
||||
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
|
||||
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
|
||||
);
|
||||
$invalidSharedSecret = "invalid_shared_secret";
|
||||
$message = "Decryption failure case";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Decryption failed");
|
||||
|
||||
Cryptography::decryptShared($encryptedMessage, $invalidSharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that decrypt throws an exception when the encrypted data is tampered with.
|
||||
*/
|
||||
public function testDecryptThrowsExceptionForTamperedEncryptedMessage(): void
|
||||
{
|
||||
$sharedSecret = Cryptography::performDHE(
|
||||
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
|
||||
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
|
||||
);
|
||||
$message = "Tampered message";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
|
||||
$tamperedMessage = $encryptedMessage . "tampered_data";
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Decryption failed");
|
||||
|
||||
Cryptography::decryptShared($tamperedMessage, $sharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that sign successfully signs a message with a valid private key.
|
||||
*/
|
||||
public function testSignSuccessfullySignsMessage(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
$message = "Message to sign";
|
||||
|
||||
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
|
||||
|
||||
$this->assertNotEmpty($signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that sign throws an exception when an invalid private key is used.
|
||||
*/
|
||||
public function testSignThrowsExceptionForInvalidPrivateKey(): void
|
||||
{
|
||||
$invalidPrivateKey = "invalid_key";
|
||||
$message = "Message to sign";
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Failed to sign message");
|
||||
|
||||
Cryptography::signMessage($message, $invalidPrivateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verify successfully validates a correct signature with a valid message and public key.
|
||||
*/
|
||||
public function testVerifySuccessfullyValidatesSignature(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
$message = "Message to verify";
|
||||
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
|
||||
|
||||
$isValid = Cryptography::verifyMessage($message, $signature, $keyPair->getPublicKey());
|
||||
|
||||
$this->assertTrue($isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verify fails for an invalid signature.
|
||||
*/
|
||||
public function testVerifyFailsForInvalidSignature(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
$message = "Message to verify";
|
||||
$signature = "invalid_signature";
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
|
||||
Cryptography::verifyMessage($message, $signature, $keyPair->getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verify throws an exception for an invalid public key.
|
||||
*/
|
||||
public function testVerifyThrowsExceptionForInvalidPublicKey(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateSigningKeyPair();
|
||||
$message = "Message to verify";
|
||||
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
|
||||
$invalidPublicKey = "invalid_public_key";
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Failed to verify signature");
|
||||
|
||||
Cryptography::verifyMessage($message, $signature, $invalidPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verify throws an exception for a public key with the wrong type prefix.
|
||||
*/
|
||||
public function testVerifyThrowsExceptionForInvalidKeyType(): void
|
||||
{
|
||||
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
|
||||
$message = "Message to verify";
|
||||
$signature = "invalid_signature";
|
||||
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Invalid key type. Expected sig:");
|
||||
|
||||
Cryptography::verifyMessage($message, $signature, $encryptionKeyPair->getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generateTransportKey creates a valid transport key for the default algorithm.
|
||||
*/
|
||||
public function testGenerateTransportKeyCreatesValidKeyForDefaultAlgo(): void
|
||||
{
|
||||
$transportKey = Cryptography::generateEncryptionKey();
|
||||
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
|
||||
|
||||
$this->assertNotEmpty($transportKey);
|
||||
$this->assertEquals(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, strlen($decodedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generateTransportKey creates valid keys for specific supported algorithms.
|
||||
*/
|
||||
public function testGenerateTransportKeyCreatesValidKeysForAlgorithms(): void
|
||||
{
|
||||
$algorithms = [
|
||||
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
|
||||
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
|
||||
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
|
||||
];
|
||||
|
||||
foreach ($algorithms as $algorithm => $expectedKeyLength) {
|
||||
$transportKey = Cryptography::generateEncryptionKey($algorithm);
|
||||
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
|
||||
|
||||
$this->assertNotEmpty($transportKey);
|
||||
$this->assertEquals($expectedKeyLength, strlen($decodedKey));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generateTransportKey throws an exception when given an invalid algorithm.
|
||||
*/
|
||||
public function testGenerateTransportKeyThrowsExceptionForInvalidAlgorithm(): void
|
||||
{
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Unsupported algorithm");
|
||||
|
||||
Cryptography::generateEncryptionKey("invalid_algorithm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generateTransportKey creates valid keys for other supported algorithms.
|
||||
*/
|
||||
public function testGenerateTransportKeyCreatesValidKeyForOtherSupportedAlgorithms(): void
|
||||
{
|
||||
$algorithms = [
|
||||
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
|
||||
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
|
||||
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
|
||||
];
|
||||
|
||||
foreach ($algorithms as $algorithm => $expectedKeyLength) {
|
||||
$transportKey = Cryptography::generateEncryptionKey($algorithm);
|
||||
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
|
||||
|
||||
$this->assertNotEmpty($transportKey);
|
||||
$this->assertEquals($expectedKeyLength, strlen($decodedKey));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that generateTransportKey throws an exception for unsupported algorithms.
|
||||
*/
|
||||
public function testGenerateTransportKeyThrowsExceptionForUnsupportedAlgorithm(): void
|
||||
{
|
||||
$this->expectException(CryptographyException::class);
|
||||
$this->expectExceptionMessage("Unsupported algorithm");
|
||||
|
||||
Cryptography::generateEncryptionKey('invalid_algo');
|
||||
}
|
||||
|
||||
public function testEncryptTransportMessageSuccessfullyEncryptsMessage(): void
|
||||
{
|
||||
$algorithms = [
|
||||
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
|
||||
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
|
||||
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
|
||||
];
|
||||
|
||||
foreach ($algorithms as $algorithm => $keyLength) {
|
||||
$transportKey = Cryptography::generateEncryptionKey($algorithm);
|
||||
$this->assertNotEmpty($transportKey);
|
||||
$this->assertEquals($keyLength, strlen(sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true)));
|
||||
$message = "Test message";
|
||||
|
||||
$encryptedMessage = Cryptography::encryptMessage($message, $transportKey);
|
||||
$decryptedMessage = Cryptography::decryptMessage($encryptedMessage, $transportKey);
|
||||
|
||||
$this->assertEquals($message, $decryptedMessage);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue