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">
|
<module type="WEB_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<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$/.idea/dataSources" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
|
|
4
.idea/sqldialects.xml
generated
4
.idea/sqldialects.xml
generated
|
@ -2,14 +2,14 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="SqlDialectMappings">
|
<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/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/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/sessions.sql" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/CaptchaManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/CaptchaManager.php" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/EncryptionRecordsManager.php" dialect="MariaDB" />
|
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/PasswordManager.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/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/SessionManager.php" dialect="MariaDB" />
|
||||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/VariableManager.php" dialect="MariaDB" />
|
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/VariableManager.php" dialect="MariaDB" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"ext-redis": "*",
|
"ext-redis": "*",
|
||||||
"ext-memcached": "*",
|
"ext-memcached": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-gd": "*"
|
"ext-gd": "*",
|
||||||
|
"ext-sodium": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,46 +1,45 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Socialbox\Classes\CliCommands;
|
namespace Socialbox\Classes\CliCommands;
|
||||||
|
|
||||||
use Socialbox\Classes\Configuration;
|
use Socialbox\Classes\Configuration;
|
||||||
use Socialbox\Classes\Logger;
|
use Socialbox\Classes\Logger;
|
||||||
use Socialbox\Interfaces\CliCommandInterface;
|
use Socialbox\Interfaces\CliCommandInterface;
|
||||||
|
|
||||||
class DnsRecordCommand implements CliCommandInterface
|
class DnsRecordCommand implements CliCommandInterface
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public static function execute(array $args): int
|
|
||||||
{
|
{
|
||||||
$txt_record = sprintf('v=socialbox;sb-rpc=%s;sb-key=%s',
|
/**
|
||||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(),
|
* @inheritDoc
|
||||||
Configuration::getInstanceConfiguration()->getPublicKey()
|
*/
|
||||||
);
|
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('Please set the following DNS TXT record for the domain:');
|
||||||
Logger::getLogger()->info(sprintf(' %s', $txt_record));
|
Logger::getLogger()->info(sprintf(' %s', $txt_record));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public static function getHelpMessage(): string
|
public static function getHelpMessage(): string
|
||||||
{
|
{
|
||||||
return <<<HELP
|
return <<<HELP
|
||||||
Usage: socialbox dns-record
|
Usage: socialbox dns-record
|
||||||
|
|
||||||
Displays the DNS TXT record that should be set for the domain.
|
Displays the DNS TXT record that should be set for the domain.
|
||||||
HELP;
|
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;
|
namespace Socialbox\Classes\CliCommands;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use LogLib\Log;
|
|
||||||
use PDOException;
|
use PDOException;
|
||||||
use Socialbox\Abstracts\CacheLayer;
|
use Socialbox\Abstracts\CacheLayer;
|
||||||
use Socialbox\Classes\Configuration;
|
use Socialbox\Classes\Configuration;
|
||||||
|
@ -13,9 +12,8 @@
|
||||||
use Socialbox\Classes\Resources;
|
use Socialbox\Classes\Resources;
|
||||||
use Socialbox\Enums\DatabaseObjects;
|
use Socialbox\Enums\DatabaseObjects;
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
use Socialbox\Exceptions\DatabaseOperationException;
|
|
||||||
use Socialbox\Interfaces\CliCommandInterface;
|
use Socialbox\Interfaces\CliCommandInterface;
|
||||||
use Socialbox\Managers\EncryptionRecordsManager;
|
use Socialbox\Socialbox;
|
||||||
|
|
||||||
class InitializeCommand implements CliCommandInterface
|
class InitializeCommand implements CliCommandInterface
|
||||||
{
|
{
|
||||||
|
@ -208,6 +206,7 @@
|
||||||
Logger::getLogger()->info('cache.database defaulting to 0');
|
Logger::getLogger()->info('cache.database defaulting to 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger::getLogger()->info('Updating configuration...');
|
||||||
Configuration::getConfigurationLib()->save(); // Save
|
Configuration::getConfigurationLib()->save(); // Save
|
||||||
Configuration::reload(); // Reload
|
Configuration::reload(); // Reload
|
||||||
}
|
}
|
||||||
|
@ -261,16 +260,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
!Configuration::getInstanceConfiguration()->getPublicKey() ||
|
!Configuration::getCryptographyConfiguration()->getHostPublicKey() ||
|
||||||
!Configuration::getInstanceConfiguration()->getPrivateKey() ||
|
!Configuration::getCryptographyConfiguration()->getHostPrivateKey() ||
|
||||||
!Configuration::getInstanceConfiguration()->getEncryptionKeys()
|
!Configuration::getCryptographyConfiguration()->getHostPublicKey()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
$expires = time() + 31536000;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger::getLogger()->info('Generating new key pair...');
|
Logger::getLogger()->info('Generating new key pair (expires ' . date('Y-m-d H:i:s', $expires) . ')...');
|
||||||
$keyPair = Cryptography::generateKeyPair();
|
$signingKeyPair = Cryptography::generateSigningKeyPair();
|
||||||
$encryptionKeys = Cryptography::randomKeyS(230, 314, Configuration::getInstanceConfiguration()->getEncryptionKeysCount());
|
|
||||||
}
|
}
|
||||||
catch (CryptographyException $e)
|
catch (CryptographyException $e)
|
||||||
{
|
{
|
||||||
|
@ -278,40 +278,35 @@
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::getLogger()->info('Updating configuration...');
|
Configuration::getConfigurationLib()->set('cryptography.host_keypair_expires', $expires);
|
||||||
Configuration::getConfigurationLib()->set('instance.private_key', $keyPair->getPrivateKey());
|
Configuration::getConfigurationLib()->set('cryptography.host_private_key', $signingKeyPair->getPrivateKey());
|
||||||
Configuration::getConfigurationLib()->set('instance.public_key', $keyPair->getPublicKey());
|
Configuration::getConfigurationLib()->set('cryptography.host_public_key', $signingKeyPair->getPublicKey());
|
||||||
Configuration::getConfigurationLib()->set('instance.encryption_keys', $encryptionKeys);
|
|
||||||
Configuration::getConfigurationLib()->save(); // Save
|
|
||||||
Configuration::reload(); // Reload
|
|
||||||
|
|
||||||
Logger::getLogger()->info(sprintf('Set the DNS TXT record for the domain %s to the following value:', Configuration::getInstanceConfiguration()->getDomain()));
|
|
||||||
Logger::getLogger()->info(sprintf("v=socialbox;sb-rpc=%s;sb-key=%s;",
|
|
||||||
Configuration::getInstanceConfiguration()->getRpcEndpoint(), $keyPair->getPublicKey()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
// 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...');
|
$encryptionKeys[] = Cryptography::generateEncryptionKey(Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||||
EncryptionRecordsManager::generateRecords(Configuration::getInstanceConfiguration()->getEncryptionRecordsCount());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (CryptographyException $e)
|
Configuration::getConfigurationLib()->set('cryptography.internal_encryption_keys', $encryptionKeys);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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('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')
|
if(getenv('SB_MODE') === 'automated')
|
||||||
{
|
{
|
||||||
Configuration::getConfigurationLib()->set('instance.enabled', true);
|
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;
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
use Socialbox\Classes\ClientCommands\StorageConfiguration;
|
|
||||||
use Socialbox\Classes\Configuration\CacheConfiguration;
|
use Socialbox\Classes\Configuration\CacheConfiguration;
|
||||||
|
use Socialbox\Classes\Configuration\CryptographyConfiguration;
|
||||||
use Socialbox\Classes\Configuration\DatabaseConfiguration;
|
use Socialbox\Classes\Configuration\DatabaseConfiguration;
|
||||||
use Socialbox\Classes\Configuration\InstanceConfiguration;
|
use Socialbox\Classes\Configuration\InstanceConfiguration;
|
||||||
use Socialbox\Classes\Configuration\LoggingConfiguration;
|
use Socialbox\Classes\Configuration\LoggingConfiguration;
|
||||||
use Socialbox\Classes\Configuration\RegistrationConfiguration;
|
use Socialbox\Classes\Configuration\RegistrationConfiguration;
|
||||||
use Socialbox\Classes\Configuration\SecurityConfiguration;
|
use Socialbox\Classes\Configuration\SecurityConfiguration;
|
||||||
|
use Socialbox\Classes\Configuration\StorageConfiguration;
|
||||||
|
|
||||||
class Configuration
|
class Configuration
|
||||||
{
|
{
|
||||||
private static ?\ConfigLib\Configuration $configuration = null;
|
private static ?\ConfigLib\Configuration $configuration = null;
|
||||||
private static ?InstanceConfiguration $instanceConfiguration = null;
|
private static ?InstanceConfiguration $instanceConfiguration = null;
|
||||||
private static ?SecurityConfiguration $securityConfiguration = null;
|
private static ?SecurityConfiguration $securityConfiguration = null;
|
||||||
|
private static ?CryptographyConfiguration $cryptographyConfiguration = null;
|
||||||
private static ?DatabaseConfiguration $databaseConfiguration = null;
|
private static ?DatabaseConfiguration $databaseConfiguration = null;
|
||||||
private static ?LoggingConfiguration $loggingConfiguration = null;
|
private static ?LoggingConfiguration $loggingConfiguration = null;
|
||||||
private static ?CacheConfiguration $cacheConfiguration = null;
|
private static ?CacheConfiguration $cacheConfiguration = null;
|
||||||
|
@ -33,19 +35,47 @@
|
||||||
|
|
||||||
// Instance configuration
|
// Instance configuration
|
||||||
$config->setDefault('instance.enabled', false); // False by default, requires the user to enable it.
|
$config->setDefault('instance.enabled', false); // False by default, requires the user to enable it.
|
||||||
|
$config->setDefault('instance.name', "Socialbox Server");
|
||||||
$config->setDefault('instance.domain', null);
|
$config->setDefault('instance.domain', null);
|
||||||
$config->setDefault('instance.rpc_endpoint', null);
|
$config->setDefault('instance.rpc_endpoint', null);
|
||||||
$config->setDefault('instance.encryption_keys_count', 5);
|
|
||||||
$config->setDefault('instance.encryption_records_count', 5);
|
|
||||||
$config->setDefault('instance.private_key', null);
|
|
||||||
$config->setDefault('instance.public_key', null);
|
|
||||||
$config->setDefault('instance.encryption_keys', null);
|
|
||||||
|
|
||||||
// Security Configuration
|
// Security Configuration
|
||||||
$config->setDefault('security.display_internal_exceptions', false);
|
$config->setDefault('security.display_internal_exceptions', false);
|
||||||
$config->setDefault('security.resolved_servers_ttl', 600);
|
$config->setDefault('security.resolved_servers_ttl', 600);
|
||||||
$config->setDefault('security.captcha_ttl', 200);
|
$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
|
// Database configuration
|
||||||
$config->setDefault('database.host', '127.0.0.1');
|
$config->setDefault('database.host', '127.0.0.1');
|
||||||
$config->setDefault('database.port', 3306);
|
$config->setDefault('database.port', 3306);
|
||||||
|
@ -98,6 +128,7 @@
|
||||||
self::$configuration = $config;
|
self::$configuration = $config;
|
||||||
self::$instanceConfiguration = new InstanceConfiguration(self::$configuration->getConfiguration()['instance']);
|
self::$instanceConfiguration = new InstanceConfiguration(self::$configuration->getConfiguration()['instance']);
|
||||||
self::$securityConfiguration = new SecurityConfiguration(self::$configuration->getConfiguration()['security']);
|
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::$databaseConfiguration = new DatabaseConfiguration(self::$configuration->getConfiguration()['database']);
|
||||||
self::$loggingConfiguration = new LoggingConfiguration(self::$configuration->getConfiguration()['logging']);
|
self::$loggingConfiguration = new LoggingConfiguration(self::$configuration->getConfiguration()['logging']);
|
||||||
self::$cacheConfiguration = new CacheConfiguration(self::$configuration->getConfiguration()['cache']);
|
self::$cacheConfiguration = new CacheConfiguration(self::$configuration->getConfiguration()['cache']);
|
||||||
|
@ -140,6 +171,14 @@
|
||||||
return self::$configuration->getConfiguration();
|
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
|
public static function getConfigurationLib(): \ConfigLib\Configuration
|
||||||
{
|
{
|
||||||
if(self::$configuration === null)
|
if(self::$configuration === null)
|
||||||
|
@ -180,6 +219,24 @@
|
||||||
return self::$securityConfiguration;
|
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.
|
* 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
|
class InstanceConfiguration
|
||||||
{
|
{
|
||||||
private bool $enabled;
|
private bool $enabled;
|
||||||
|
private string $name;
|
||||||
private ?string $domain;
|
private ?string $domain;
|
||||||
private ?string $rpcEndpoint;
|
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.
|
* Constructor that initializes object properties with the provided data.
|
||||||
|
@ -22,13 +18,9 @@
|
||||||
public function __construct(array $data)
|
public function __construct(array $data)
|
||||||
{
|
{
|
||||||
$this->enabled = (bool)$data['enabled'];
|
$this->enabled = (bool)$data['enabled'];
|
||||||
|
$this->name = $data['name'];
|
||||||
$this->domain = $data['domain'];
|
$this->domain = $data['domain'];
|
||||||
$this->rpcEndpoint = $data['rpc_endpoint'];
|
$this->rpcEndpoint = $data['rpc_endpoint'];
|
||||||
$this->encryptionKeysCount = $data['encryption_keys_count'];
|
|
||||||
$this->encryptionRecordsCount = $data['encryption_records_count'];
|
|
||||||
$this->privateKey = $data['private_key'];
|
|
||||||
$this->publicKey = $data['public_key'];
|
|
||||||
$this->encryptionKeys = $data['encryption_keys'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,6 +33,11 @@
|
||||||
return $this->enabled;
|
return $this->enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the domain.
|
* Retrieves the domain.
|
||||||
*
|
*
|
||||||
|
@ -58,62 +55,4 @@
|
||||||
{
|
{
|
||||||
return $this->rpcEndpoint;
|
return $this->rpcEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the number of encryption keys.
|
|
||||||
*
|
|
||||||
* @return int The number of encryption keys.
|
|
||||||
*/
|
|
||||||
public function getEncryptionKeysCount(): int
|
|
||||||
{
|
|
||||||
return $this->encryptionKeysCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the number of encryption records.
|
|
||||||
*
|
|
||||||
* @return int The number of encryption records.
|
|
||||||
*/
|
|
||||||
public function getEncryptionRecordsCount(): int
|
|
||||||
{
|
|
||||||
return $this->encryptionRecordsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the private key.
|
|
||||||
*
|
|
||||||
* @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
|
<?php
|
||||||
|
|
||||||
namespace Socialbox\Classes\ClientCommands;
|
namespace Socialbox\Classes\Configuration;
|
||||||
|
|
||||||
class StorageConfiguration
|
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
|
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',
|
primary key comment 'The primary unique index of the peer uuid',
|
||||||
iv mediumtext not null comment 'The Initial Vector of the password record',
|
hash mediumtext not null comment 'The encrypted hash of the password',
|
||||||
encrypted_password mediumtext not null comment 'The encrypted password data',
|
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
|
||||||
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',
|
|
||||||
constraint authentication_passwords_peer_uuid_uindex
|
constraint authentication_passwords_peer_uuid_uindex
|
||||||
unique (peer_uuid) comment 'The primary unique index of the peer uuid',
|
unique (peer_uuid) comment 'The primary unique index of the peer uuid',
|
||||||
constraint authentication_passwords_registered_peers_uuid_fk
|
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
|
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,
|
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',
|
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_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',
|
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',
|
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',
|
client_public_signing_key text not null comment 'The client''s public signing key used for signing decrypted messages',
|
||||||
state enum ('AWAITING_DHE', 'ACTIVE', 'CLOSED', 'EXPIRED') default 'AWAITING_DHE' not null comment 'The status of the session',
|
client_public_encryption_key text not null comment 'The Public Key of the client''s encryption key',
|
||||||
encryption_key text null comment 'The key used for encryption that is obtained through a DHE',
|
server_public_encryption_key text not null comment 'The server''s public encryption key for this session',
|
||||||
flags text null comment 'The current flags that is set to the session',
|
server_private_encryption_key text not null comment 'The server''s private encryption key for this session',
|
||||||
created timestamp default current_timestamp() not null comment 'The Timestamp for when the session was last created',
|
private_shared_secret text null comment 'The shared secret encryption key between the Client & Server',
|
||||||
last_request timestamp null comment 'The Timestamp for when the last request was made using this session',
|
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
|
constraint sessions_uuid_uindex
|
||||||
unique (uuid) comment 'The Unique Primary index for the session UUID',
|
unique (uuid) comment 'The Unique Primary index for the session UUID',
|
||||||
constraint sessions_registered_peers_uuid_fk
|
constraint sessions_registered_peers_uuid_fk
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
|
|
||||||
namespace Socialbox\Classes;
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
use Socialbox\Enums\Options\ClientOptions;
|
use Socialbox\Enums\StandardError;
|
||||||
use Socialbox\Enums\StandardHeaders;
|
use Socialbox\Enums\StandardHeaders;
|
||||||
use Socialbox\Enums\Types\RequestType;
|
use Socialbox\Enums\Types\RequestType;
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
use Socialbox\Exceptions\DatabaseOperationException;
|
|
||||||
use Socialbox\Exceptions\ResolutionException;
|
use Socialbox\Exceptions\ResolutionException;
|
||||||
use Socialbox\Exceptions\RpcException;
|
use Socialbox\Exceptions\RpcException;
|
||||||
use Socialbox\Objects\ExportedSession;
|
use Socialbox\Objects\ExportedSession;
|
||||||
|
@ -14,6 +13,7 @@
|
||||||
use Socialbox\Objects\PeerAddress;
|
use Socialbox\Objects\PeerAddress;
|
||||||
use Socialbox\Objects\RpcRequest;
|
use Socialbox\Objects\RpcRequest;
|
||||||
use Socialbox\Objects\RpcResult;
|
use Socialbox\Objects\RpcResult;
|
||||||
|
use Socialbox\Objects\Standard\ServerInformation;
|
||||||
|
|
||||||
class RpcClient
|
class RpcClient
|
||||||
{
|
{
|
||||||
|
@ -22,9 +22,14 @@
|
||||||
|
|
||||||
private bool $bypassSignatureVerification;
|
private bool $bypassSignatureVerification;
|
||||||
private PeerAddress $peerAddress;
|
private PeerAddress $peerAddress;
|
||||||
private KeyPair $keyPair;
|
private string $serverPublicSigningKey;
|
||||||
private string $encryptionKey;
|
private string $serverPublicEncryptionKey;
|
||||||
private string $serverPublicKey;
|
private KeyPair $clientSigningKeyPair;
|
||||||
|
private KeyPair $clientEncryptionKeyPair;
|
||||||
|
private string $privateSharedSecret;
|
||||||
|
private string $clientTransportEncryptionKey;
|
||||||
|
private string $serverTransportEncryptionKey;
|
||||||
|
private ServerInformation $serverInformation;
|
||||||
private string $rpcEndpoint;
|
private string $rpcEndpoint;
|
||||||
private string $sessionUuid;
|
private string $sessionUuid;
|
||||||
|
|
||||||
|
@ -42,14 +47,41 @@
|
||||||
$this->bypassSignatureVerification = false;
|
$this->bypassSignatureVerification = false;
|
||||||
|
|
||||||
// If an exported session is provided, no need to re-connect.
|
// If an exported session is provided, no need to re-connect.
|
||||||
|
// Just use the session details provided.
|
||||||
if($exportedSession !== null)
|
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->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->rpcEndpoint = $exportedSession->getRpcEndpoint();
|
||||||
$this->sessionUuid = $exportedSession->getSessionUuid();
|
$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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,51 +94,61 @@
|
||||||
// Set the initial properties
|
// Set the initial properties
|
||||||
$this->peerAddress = $peerAddress;
|
$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
|
// Resolve the domain and get the server's Public Key & RPC Endpoint
|
||||||
try
|
$resolvedServer = ServerResolver::resolveDomain($this->peerAddress->getDomain(), false);
|
||||||
{
|
|
||||||
$resolvedServer = ServerResolver::resolveDomain($this->peerAddress->getDomain(), false);
|
|
||||||
}
|
|
||||||
catch (DatabaseOperationException $e)
|
|
||||||
{
|
|
||||||
throw new ResolutionException('Failed to resolve domain: ' . $e->getMessage(), 0, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->serverPublicKey = $resolvedServer->getPublicKey();
|
// Import the RPC Endpoint & the server's public key.
|
||||||
$this->rpcEndpoint = $resolvedServer->getEndpoint();
|
$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');
|
throw new ResolutionException('Failed to resolve domain: No public key found for the server');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to create an encrypted session with the server
|
// Resolve basic server information
|
||||||
$this->sessionUuid = $this->createSession();
|
$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();
|
$this->sendDheExchange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new session by sending an HTTP GET request to the RPC endpoint.
|
* Initiates a new session with the server and retrieves the session UUID.
|
||||||
* The request includes specific headers required for session initiation.
|
|
||||||
*
|
*
|
||||||
* @return string Returns the session UUID received from the server.
|
* @return string The session UUID provided by the server upon successful session creation.
|
||||||
* @throws RpcException If the server response is invalid, the session creation fails, or no session UUID is returned.
|
* @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();
|
$ch = curl_init();
|
||||||
|
|
||||||
|
@ -116,28 +158,45 @@
|
||||||
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
StandardHeaders::CLIENT_NAME->value . ': ' . self::CLIENT_NAME,
|
||||||
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
StandardHeaders::CLIENT_VERSION->value . ': ' . self::CLIENT_VERSION,
|
||||||
StandardHeaders::IDENTIFY_AS->value . ': ' . $this->peerAddress->getAddress(),
|
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
|
// 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
|
// Otherwise, the server will obtain the public key itself from DNS records rather than trusting the client
|
||||||
if(!$this->peerAddress->isHost())
|
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_URL, $this->rpcEndpoint);
|
||||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 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);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
// If the response is false, the request failed
|
||||||
if($response === false)
|
if($response === false)
|
||||||
{
|
{
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
throw new RpcException(sprintf('Failed to create the session at %s, no response received', $this->rpcEndpoint));
|
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);
|
$responseCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||||
if($responseCode !== 201)
|
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));
|
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))
|
if(empty($response))
|
||||||
{
|
{
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
throw new RpcException(sprintf('Failed to create the session at %s, server did not return a session UUID', $this->rpcEndpoint));
|
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);
|
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
|
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
|
// 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
|
// Upon success the server should return 204 without a body
|
||||||
try
|
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)
|
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();
|
$ch = curl_init();
|
||||||
|
@ -186,6 +286,7 @@
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::DHE_EXCHANGE->value,
|
StandardHeaders::REQUEST_TYPE->value . ': ' . RequestType::DHE_EXCHANGE->value,
|
||||||
StandardHeaders::SESSION_UUID->value . ': ' . $this->sessionUuid,
|
StandardHeaders::SESSION_UUID->value . ': ' . $this->sessionUuid,
|
||||||
|
StandardHeaders::SIGNATURE->value . ': ' . $signature
|
||||||
]);
|
]);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $encryptedKey);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $encryptedKey);
|
||||||
|
|
||||||
|
@ -194,17 +295,28 @@
|
||||||
if($response === false)
|
if($response === false)
|
||||||
{
|
{
|
||||||
curl_close($ch);
|
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);
|
$responseCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||||
if($responseCode !== 204)
|
if($responseCode !== 200)
|
||||||
{
|
{
|
||||||
curl_close($ch);
|
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
|
try
|
||||||
{
|
{
|
||||||
$encryptedData = Cryptography::encryptTransport($jsonData, $this->encryptionKey);
|
$encryptedData = Cryptography::encryptMessage(
|
||||||
$signature = Cryptography::signContent($jsonData, $this->keyPair->getPrivateKey(), true);
|
message: $jsonData,
|
||||||
|
encryptionKey: $this->serverTransportEncryptionKey,
|
||||||
|
algorithm: $this->serverInformation->getTransportEncryptionAlgorithm()
|
||||||
|
);
|
||||||
|
|
||||||
|
$signature = Cryptography::signMessage(
|
||||||
|
message: $jsonData,
|
||||||
|
privateKey: $this->clientSigningKeyPair->getPrivateKey(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (CryptographyException $e)
|
catch (CryptographyException $e)
|
||||||
{
|
{
|
||||||
|
@ -289,7 +409,11 @@
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$decryptedResponse = Cryptography::decryptTransport($responseString, $this->encryptionKey);
|
$decryptedResponse = Cryptography::decryptMessage(
|
||||||
|
encryptedMessage: $responseString,
|
||||||
|
encryptionKey: $this->clientTransportEncryptionKey,
|
||||||
|
algorithm: $this->serverInformation->getTransportEncryptionAlgorithm()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (CryptographyException $e)
|
catch (CryptographyException $e)
|
||||||
{
|
{
|
||||||
|
@ -298,7 +422,7 @@
|
||||||
|
|
||||||
if (!$this->bypassSignatureVerification)
|
if (!$this->bypassSignatureVerification)
|
||||||
{
|
{
|
||||||
$signature = $headers['signature'][0] ?? null;
|
$signature = $headers[strtolower(StandardHeaders::SIGNATURE->value)][0] ?? null;
|
||||||
if ($signature === null)
|
if ($signature === null)
|
||||||
{
|
{
|
||||||
throw new RpcException('The server did not provide a signature for the response');
|
throw new RpcException('The server did not provide a signature for the response');
|
||||||
|
@ -306,7 +430,11 @@
|
||||||
|
|
||||||
try
|
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');
|
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.
|
* Sends an RPC request and retrieves the corresponding RPC response.
|
||||||
*
|
*
|
||||||
|
@ -395,12 +576,19 @@
|
||||||
{
|
{
|
||||||
return new ExportedSession([
|
return new ExportedSession([
|
||||||
'peer_address' => $this->peerAddress->getAddress(),
|
'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,
|
'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;
|
namespace Socialbox\Classes;
|
||||||
|
|
||||||
use Socialbox\Exceptions\DatabaseOperationException;
|
use InvalidArgumentException;
|
||||||
use Socialbox\Exceptions\ResolutionException;
|
use Socialbox\Exceptions\ResolutionException;
|
||||||
use Socialbox\Managers\ResolvedServersManager;
|
use Socialbox\Managers\ResolvedDnsRecordsManager;
|
||||||
use Socialbox\Objects\ResolvedServer;
|
use Socialbox\Objects\DnsRecord;
|
||||||
|
|
||||||
class ServerResolver
|
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.
|
* @param string $domain The domain name to resolve.
|
||||||
* @return ResolvedServer An instance of ResolvedServer containing the endpoint and public key.
|
* @param bool $useDatabase Whether to check the database for cached resolution data; defaults to true.
|
||||||
* @throws ResolutionException If the DNS TXT records cannot be resolved or if required information is missing.
|
* @return DnsRecord The parsed DNS record for the given domain.
|
||||||
* @throws DatabaseOperationException
|
* @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
|
// Check the database if enabled
|
||||||
if($useDatabase)
|
if ($useDatabase)
|
||||||
{
|
{
|
||||||
$resolvedServer = ResolvedServersManager::getResolvedServer($domain);
|
$resolvedServer = ResolvedDnsRecordsManager::getDnsRecord($domain);
|
||||||
if($resolvedServer !== null)
|
if ($resolvedServer !== null)
|
||||||
{
|
{
|
||||||
return $resolvedServer->toResolvedServer();
|
return $resolvedServer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,23 +37,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$fullRecord = self::concatenateTxtRecords($txtRecords);
|
$fullRecord = self::concatenateTxtRecords($txtRecords);
|
||||||
if (preg_match(self::PATTERN, $fullRecord, $matches))
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
$endpoint = trim($matches[1]);
|
// Parse the TXT record using DnsHelper
|
||||||
$publicKey = trim(str_replace(' ', '', $matches[2]));
|
$record = DnsHelper::parseTxt($fullRecord);
|
||||||
if (empty($endpoint))
|
|
||||||
|
// 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))
|
|
||||||
{
|
return $record;
|
||||||
throw new ResolutionException(sprintf("Failed to resolve public key for %s", $domain));
|
|
||||||
}
|
|
||||||
return new ResolvedServer($endpoint, $publicKey);
|
|
||||||
}
|
}
|
||||||
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.
|
* @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.
|
* @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)
|
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);
|
$sanitizedImage = Utilities::resizeImage(Utilities::sanitizeJpeg($decodedImage), 126, 126);
|
||||||
}
|
}
|
||||||
catch(Exception $e)
|
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
|
try
|
||||||
|
|
|
@ -122,22 +122,37 @@
|
||||||
return $headers;
|
return $headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function throwableToString(Throwable $e, int $level=0): string
|
||||||
* 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
|
|
||||||
{
|
{
|
||||||
return sprintf(
|
// Indentation for nested exceptions
|
||||||
"%s: %s in %s:%d\nStack trace:\n%s",
|
$indentation = str_repeat(' ', $level);
|
||||||
get_class($e),
|
|
||||||
$e->getMessage(),
|
// Basic information about the Throwable
|
||||||
$e->getFile(),
|
$type = get_class($e);
|
||||||
$e->getLine(),
|
$message = $e->getMessage() ?: 'No message';
|
||||||
$e->getTraceAsString()
|
$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
|
enum DatabaseObjects : string
|
||||||
{
|
{
|
||||||
case VARIABLES = 'variables.sql';
|
case VARIABLES = 'variables.sql';
|
||||||
case ENCRYPTION_RECORDS = 'encryption_records.sql';
|
case RESOLVED_DNS_RECORDS = 'resolved_dns_records.sql';
|
||||||
case RESOLVED_SERVERS = 'resolved_servers.sql';
|
|
||||||
|
|
||||||
case REGISTERED_PEERS = 'registered_peers.sql';
|
case REGISTERED_PEERS = 'registered_peers.sql';
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@
|
||||||
{
|
{
|
||||||
return match ($this)
|
return match ($this)
|
||||||
{
|
{
|
||||||
self::VARIABLES, self::ENCRYPTION_RECORDS, self::RESOLVED_SERVERS => 0,
|
self::VARIABLES, self::RESOLVED_DNS_RECORDS => 0,
|
||||||
self::REGISTERED_PEERS => 1,
|
self::REGISTERED_PEERS => 1,
|
||||||
self::AUTHENTICATION_PASSWORDS, self::CAPTCHA_IMAGES, self::SESSIONS, self::EXTERNAL_SESSIONS => 2,
|
self::AUTHENTICATION_PASSWORDS, self::CAPTCHA_IMAGES, self::SESSIONS, self::EXTERNAL_SESSIONS => 2,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,16 +7,21 @@ enum StandardError : int
|
||||||
// Fallback Codes
|
// Fallback Codes
|
||||||
case UNKNOWN = -1;
|
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
|
// RPC Errors
|
||||||
case RPC_METHOD_NOT_FOUND = -1000;
|
case RPC_METHOD_NOT_FOUND = -1000;
|
||||||
case RPC_INVALID_ARGUMENTS = -1001;
|
case RPC_INVALID_ARGUMENTS = -1001;
|
||||||
|
CASE RPC_BAD_REQUEST = -1002;
|
||||||
// Server Errors
|
|
||||||
case INTERNAL_SERVER_ERROR = -2000;
|
|
||||||
case SERVER_UNAVAILABLE = -2001;
|
|
||||||
|
|
||||||
// Client Errors
|
// Client Errors
|
||||||
case BAD_REQUEST = -3000;
|
|
||||||
case METHOD_NOT_ALLOWED = -3001;
|
case METHOD_NOT_ALLOWED = -3001;
|
||||||
|
|
||||||
// Authentication/Cryptography Errors
|
// Authentication/Cryptography Errors
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
enum StandardHeaders : string
|
enum StandardHeaders : string
|
||||||
{
|
{
|
||||||
case REQUEST_TYPE = 'Request-Type';
|
case REQUEST_TYPE = 'Request-Type';
|
||||||
|
case ERROR_CODE = 'Error-Code';
|
||||||
case IDENTIFY_AS = 'Identify-As';
|
case IDENTIFY_AS = 'Identify-As';
|
||||||
case CLIENT_NAME = 'Client-Name';
|
case CLIENT_NAME = 'Client-Name';
|
||||||
case CLIENT_VERSION = 'Client-Version';
|
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 SESSION_UUID = 'Session-UUID';
|
||||||
case SIGNATURE = 'Signature';
|
case SIGNATURE = 'Signature';
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
|
|
||||||
enum RequestType : string
|
enum RequestType : string
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Represents the action of getting server information (Non-RPC Request)
|
||||||
|
*/
|
||||||
|
case INFO = 'info';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the action of initiating a session.
|
* 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;
|
namespace Socialbox\Managers;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
use Socialbox\Classes\Configuration;
|
||||||
|
use Socialbox\Classes\Cryptography;
|
||||||
use Socialbox\Classes\Database;
|
use Socialbox\Classes\Database;
|
||||||
use Socialbox\Classes\SecuredPassword;
|
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
use Socialbox\Exceptions\DatabaseOperationException;
|
use Socialbox\Exceptions\DatabaseOperationException;
|
||||||
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
||||||
use Socialbox\Objects\Database\SecurePasswordRecord;
|
|
||||||
|
|
||||||
class PasswordManager
|
class PasswordManager
|
||||||
{
|
{
|
||||||
|
@ -34,154 +36,139 @@
|
||||||
|
|
||||||
return $stmt->fetchColumn() > 0;
|
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);
|
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
|
* Sets a secured password for the given peer UUID or registered peer record.
|
||||||
* and storing it in the authentication_passwords database table.
|
|
||||||
*
|
*
|
||||||
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
|
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
|
||||||
* @param string $password The plaintext password to be securely stored.
|
* @param string $hash 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.
|
|
||||||
* @return void
|
* @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)
|
if($peerUuid instanceof RegisteredPeerRecord)
|
||||||
{
|
{
|
||||||
$peerUuid = $peerUuid->getUuid();
|
$peerUuid = $peerUuid->getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
// Throws an exception if the hash is invalid
|
||||||
$securedPassword = SecuredPassword::securePassword($peerUuid, $password, $encryptionRecord);
|
Cryptography::validatePasswordHash($hash);
|
||||||
|
|
||||||
|
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
|
||||||
|
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||||
|
|
||||||
try
|
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);
|
$stmt->bindParam(":peer_uuid", $peerUuid);
|
||||||
|
$stmt->bindParam(':hash', $securedPassword);
|
||||||
$iv = $securedPassword->getIv();
|
|
||||||
$stmt->bindParam(':iv', $iv);
|
|
||||||
|
|
||||||
$encryptedPassword = $securedPassword->getEncryptedPassword();
|
|
||||||
$stmt->bindParam(':encrypted_password', $encryptedPassword);
|
|
||||||
|
|
||||||
$encryptedTag = $securedPassword->getEncryptedTag();
|
|
||||||
$stmt->bindParam(':encrypted_tag', $encryptedTag);
|
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
catch(\PDOException $e)
|
catch(PDOException $e)
|
||||||
{
|
{
|
||||||
throw new DatabaseOperationException(sprintf('Failed to set password for user %s', $peerUuid), $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|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
|
||||||
* @param string $newPassword The new password to be set for the peer.
|
* @param string $hash The new password to be stored securely.
|
||||||
* @throws CryptographyException If an error occurs while securing the new password.
|
* @return void
|
||||||
* @throws DatabaseOperationException If the update operation fails due to a database error.
|
* @throws DatabaseOperationException If an error occurs while updating the password in the database.
|
||||||
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
|
* @throws CryptographyException If an error occurs while encrypting the password or validating the hash.
|
||||||
* @returns void
|
|
||||||
*/
|
*/
|
||||||
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $newPassword): void
|
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $hash): void
|
||||||
{
|
{
|
||||||
if($peerUuid instanceof RegisteredPeerRecord)
|
if($peerUuid instanceof RegisteredPeerRecord)
|
||||||
{
|
{
|
||||||
$peerUuid = $peerUuid->getUuid();
|
$peerUuid = $peerUuid->getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cryptography::validatePasswordHash($hash);
|
||||||
|
|
||||||
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
|
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
|
||||||
$securedPassword = SecuredPassword::securePassword($peerUuid, $newPassword, $encryptionRecord);
|
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||||
|
|
||||||
try
|
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 = Database::getConnection()->prepare("UPDATE authentication_passwords SET hash=:hash, updated=:updated WHERE peer_uuid=:peer_uuid");
|
||||||
$stmt->bindParam(":peer_uuid", $peerUuid);
|
$updated = (new DateTime())->setTimestamp(time());
|
||||||
|
$stmt->bindParam(':hash', $securedPassword);
|
||||||
$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->bindParam(':updated', $updated);
|
$stmt->bindParam(':updated', $updated);
|
||||||
|
$stmt->bindParam(':peer_uuid', $peerUuid);
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
catch(\PDOException $e)
|
catch(PDOException $e)
|
||||||
{
|
{
|
||||||
throw new DatabaseOperationException(sprintf('Failed to update password for user %s', $peerUuid), $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.
|
* @param string|RegisteredPeerRecord $peerUuid The unique identifier of the peer, or an instance of RegisteredPeerRecord.
|
||||||
* @return SecurePasswordRecord|null Returns a SecurePasswordRecord if found, or null if no record is present.
|
* @param string $hash The password hash to verify.
|
||||||
* @throws DatabaseOperationException If a database operation error occurs during the retrieval process.
|
* @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)
|
if($peerUuid instanceof RegisteredPeerRecord)
|
||||||
{
|
{
|
||||||
$peerUuid = $peerUuid->getUuid();
|
$peerUuid = $peerUuid->getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cryptography::validatePasswordHash($hash);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$statement = Database::getConnection()->prepare("SELECT * FROM authentication_passwords WHERE peer_uuid=:peer_uuid LIMIT 1");
|
$stmt = Database::getConnection()->prepare('SELECT hash FROM authentication_passwords WHERE peer_uuid=:uuid');
|
||||||
$statement->bindParam(':peer_uuid', $peerUuid);
|
$stmt->bindParam(':uuid', $peerUuid);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
$statement->execute();
|
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$data = $statement->fetch(PDO::FETCH_ASSOC);
|
if($record === false)
|
||||||
|
|
||||||
if ($data === false)
|
|
||||||
{
|
{
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SecurePasswordRecord::fromArray($data);
|
$encryptedHash = $record['hash'];
|
||||||
}
|
$decryptedHash = null;
|
||||||
catch(\PDOException $e)
|
foreach(Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys() as $key)
|
||||||
{
|
{
|
||||||
throw new DatabaseOperationException(sprintf('Failed to retrieve password record for user %s', $peerUuid), $e);
|
try
|
||||||
}
|
{
|
||||||
}
|
$decryptedHash = Cryptography::decryptMessage($encryptedHash, $key, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
|
||||||
|
}
|
||||||
|
catch(CryptographyException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if($decryptedHash === null)
|
||||||
* Verifies if the provided password matches the secured password associated with the given peer UUID.
|
{
|
||||||
*
|
throw new CryptographyException('Cannot decrypt hashed password');
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$encryptionRecords = EncryptionRecordsManager::getAllRecords();
|
return Cryptography::verifyPassword($hash, $decryptedHash);
|
||||||
return SecuredPassword::verifyPassword($password, $securedPassword, $encryptionRecords);
|
}
|
||||||
|
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\Exceptions\StandardException;
|
||||||
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
use Socialbox\Objects\Database\RegisteredPeerRecord;
|
||||||
use Socialbox\Objects\Database\SessionRecord;
|
use Socialbox\Objects\Database\SessionRecord;
|
||||||
|
use Socialbox\Objects\KeyPair;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
class SessionManager
|
class SessionManager
|
||||||
{
|
{
|
||||||
/**
|
public static function createSession(RegisteredPeerRecord $peer, string $clientName, string $clientVersion, string $clientPublicSigningKey, string $clientPublicEncryptionKey, KeyPair $serverEncryptionKeyPair): string
|
||||||
* 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
|
|
||||||
{
|
{
|
||||||
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();
|
$uuid = Uuid::v4()->toRfc4122();
|
||||||
$flags = [];
|
$flags = [];
|
||||||
|
|
||||||
|
// TODO: Update this to support `host` peers
|
||||||
if($peer->isEnabled())
|
if($peer->isEnabled())
|
||||||
{
|
{
|
||||||
$flags[] = SessionFlags::AUTHENTICATION_REQUIRED;
|
$flags[] = SessionFlags::AUTHENTICATION_REQUIRED;
|
||||||
|
@ -119,13 +112,18 @@
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, public_key, flags) VALUES (?, ?, ?, ?, ?, ?)");
|
$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(1, $uuid);
|
$statement->bindParam(':uuid', $uuid);
|
||||||
$statement->bindParam(2, $peerUuid);
|
$statement->bindParam(':peer_uuid', $peerUuid);
|
||||||
$statement->bindParam(3, $clientName);
|
$statement->bindParam(':client_name', $clientName);
|
||||||
$statement->bindParam(4, $clientVersion);
|
$statement->bindParam(':client_version', $clientVersion);
|
||||||
$statement->bindParam(5, $publicKey);
|
$statement->bindParam(':client_public_signing_key', $clientPublicSigningKey);
|
||||||
$statement->bindParam(6, $implodedFlags);
|
$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();
|
$statement->execute();
|
||||||
}
|
}
|
||||||
catch(PDOException $e)
|
catch(PDOException $e)
|
||||||
|
@ -186,7 +184,6 @@
|
||||||
|
|
||||||
// Convert the timestamp fields to DateTime objects
|
// Convert the timestamp fields to DateTime objects
|
||||||
$data['created'] = new DateTime($data['created']);
|
$data['created'] = new DateTime($data['created']);
|
||||||
|
|
||||||
if(isset($data['last_request']) && $data['last_request'] !== null)
|
if(isset($data['last_request']) && $data['last_request'] !== null)
|
||||||
{
|
{
|
||||||
$data['last_request'] = new DateTime($data['last_request']);
|
$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.
|
* 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 $uuid The unique identifier for the session to update.
|
||||||
* @param string $encryptionKey The new encryption key to be assigned.
|
* @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
|
* @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));
|
Logger::getLogger()->verbose(sprintf('Setting the encryption key for %s', $uuid));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$state_value = SessionState::ACTIVE->value;
|
$state_value = SessionState::ACTIVE->value;
|
||||||
$statement = Database::getConnection()->prepare('UPDATE sessions SET state=?, encryption_key=? WHERE 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(1, $state_value);
|
$statement->bindParam(':state', $state_value);
|
||||||
$statement->bindParam(2, $encryptionKey);
|
$statement->bindParam(':private_shared_secret', $privateSharedSecret);
|
||||||
$statement->bindParam(3, $uuid);
|
$statement->bindParam(':client_transport_encryption_key', $clientEncryptionKey);
|
||||||
|
$statement->bindParam(':server_transport_encryption_key', $serverEncryptionKey);
|
||||||
|
$statement->bindParam(':uuid', $uuid);
|
||||||
|
|
||||||
$statement->execute();
|
$statement->execute();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
namespace Socialbox\Objects;
|
namespace Socialbox\Objects;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use Socialbox\Classes\Cryptography;
|
use Socialbox\Classes\Cryptography;
|
||||||
use Socialbox\Classes\Utilities;
|
|
||||||
use Socialbox\Enums\SessionState;
|
|
||||||
use Socialbox\Enums\StandardHeaders;
|
use Socialbox\Enums\StandardHeaders;
|
||||||
use Socialbox\Enums\Types\RequestType;
|
use Socialbox\Enums\Types\RequestType;
|
||||||
use Socialbox\Exceptions\CryptographyException;
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
|
@ -18,7 +15,7 @@
|
||||||
class ClientRequest
|
class ClientRequest
|
||||||
{
|
{
|
||||||
private array $headers;
|
private array $headers;
|
||||||
private RequestType $requestType;
|
private ?RequestType $requestType;
|
||||||
private ?string $requestBody;
|
private ?string $requestBody;
|
||||||
|
|
||||||
private ?string $clientName;
|
private ?string $clientName;
|
||||||
|
@ -27,6 +24,14 @@
|
||||||
private ?string $sessionUuid;
|
private ?string $sessionUuid;
|
||||||
private ?string $signature;
|
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)
|
public function __construct(array $headers, ?string $requestBody)
|
||||||
{
|
{
|
||||||
$this->headers = $headers;
|
$this->headers = $headers;
|
||||||
|
@ -34,17 +39,28 @@
|
||||||
|
|
||||||
$this->clientName = $headers[StandardHeaders::CLIENT_NAME->value] ?? null;
|
$this->clientName = $headers[StandardHeaders::CLIENT_NAME->value] ?? null;
|
||||||
$this->clientVersion = $headers[StandardHeaders::CLIENT_VERSION->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->identifyAs = $headers[StandardHeaders::IDENTIFY_AS->value] ?? null;
|
||||||
$this->sessionUuid = $headers[StandardHeaders::SESSION_UUID->value] ?? null;
|
$this->sessionUuid = $headers[StandardHeaders::SESSION_UUID->value] ?? null;
|
||||||
$this->signature = $headers[StandardHeaders::SIGNATURE->value] ?? null;
|
$this->signature = $headers[StandardHeaders::SIGNATURE->value] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the headers.
|
||||||
|
*
|
||||||
|
* @return array Returns an array of headers.
|
||||||
|
*/
|
||||||
public function getHeaders(): array
|
public function getHeaders(): array
|
||||||
{
|
{
|
||||||
return $this->headers;
|
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
|
public function headerExists(StandardHeaders|string $header): bool
|
||||||
{
|
{
|
||||||
if(is_string($header))
|
if(is_string($header))
|
||||||
|
@ -55,6 +71,12 @@
|
||||||
return isset($this->headers[$header->value]);
|
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
|
public function getHeader(StandardHeaders|string $header): ?string
|
||||||
{
|
{
|
||||||
if(!$this->headerExists($header))
|
if(!$this->headerExists($header))
|
||||||
|
@ -70,26 +92,51 @@
|
||||||
return $this->headers[$header->value];
|
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
|
public function getRequestBody(): ?string
|
||||||
{
|
{
|
||||||
return $this->requestBody;
|
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
|
public function getClientName(): ?string
|
||||||
{
|
{
|
||||||
return $this->clientName;
|
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
|
public function getClientVersion(): ?string
|
||||||
{
|
{
|
||||||
return $this->clientVersion;
|
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;
|
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
|
public function getIdentifyAs(): ?PeerAddress
|
||||||
{
|
{
|
||||||
if($this->identifyAs === null)
|
if($this->identifyAs === null)
|
||||||
|
@ -100,11 +147,21 @@
|
||||||
return PeerAddress::fromAddress($this->identifyAs);
|
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
|
public function getSessionUuid(): ?string
|
||||||
{
|
{
|
||||||
return $this->sessionUuid;
|
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
|
public function getSession(): ?SessionRecord
|
||||||
{
|
{
|
||||||
if($this->sessionUuid === null)
|
if($this->sessionUuid === null)
|
||||||
|
@ -115,6 +172,11 @@
|
||||||
return SessionManager::getSession($this->sessionUuid);
|
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
|
public function getPeer(): ?RegisteredPeerRecord
|
||||||
{
|
{
|
||||||
$session = $this->getSession();
|
$session = $this->getSession();
|
||||||
|
@ -127,11 +189,22 @@
|
||||||
return RegisteredPeerManager::getPeer($session->getPeerUuid());
|
return RegisteredPeerManager::getPeer($session->getPeerUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the signature value.
|
||||||
|
*
|
||||||
|
* @return string|null The signature value or null if not set
|
||||||
|
*/
|
||||||
public function getSignature(): ?string
|
public function getSignature(): ?string
|
||||||
{
|
{
|
||||||
return $this->signature;
|
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
|
private function verifySignature(string $decryptedContent): bool
|
||||||
{
|
{
|
||||||
if($this->getSignature() == null || $this->getSessionUuid() == null)
|
if($this->getSignature() == null || $this->getSessionUuid() == null)
|
||||||
|
@ -141,7 +214,11 @@
|
||||||
|
|
||||||
try
|
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)
|
catch(CryptographyException)
|
||||||
{
|
{
|
||||||
|
@ -156,52 +233,12 @@
|
||||||
* @return RpcRequest[] The parsed RpcRequest objects
|
* @return RpcRequest[] The parsed RpcRequest objects
|
||||||
* @throws RequestException Thrown if the request is invalid
|
* @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);
|
throw new RequestException('Malformed JSON', 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the body only contains a method, we assume it's a single request
|
// 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
|
<?php
|
||||||
|
|
||||||
namespace Socialbox\Objects\Database;
|
namespace Socialbox\Objects\Database;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Socialbox\Interfaces\SerializableInterface;
|
use Socialbox\Interfaces\SerializableInterface;
|
||||||
use Socialbox\Objects\ResolvedServer;
|
use Socialbox\Objects\DnsRecord;
|
||||||
|
|
||||||
class ResolvedServerRecord implements SerializableInterface
|
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)
|
|
||||||
{
|
{
|
||||||
$this->domain = (string)$data['domain'];
|
private string $domain;
|
||||||
$this->endpoint = (string)$data['endpoint'];
|
private string $endpoint;
|
||||||
$this->publicKey = (string)$data['public_key'];
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the public key.
|
||||||
|
*
|
||||||
|
* @return string The public key as a string.
|
||||||
|
*/
|
||||||
|
public function getPublicKey(): string
|
||||||
|
{
|
||||||
|
return $this->publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the expiration timestamp.
|
||||||
|
*
|
||||||
|
* @return DateTime The DateTime object representing the expiration time.
|
||||||
|
*/
|
||||||
|
public function getExpires(): DateTime
|
||||||
|
{
|
||||||
|
return $this->expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
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')
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return string The domain value.
|
|
||||||
*/
|
|
||||||
public function getDomain(): string
|
|
||||||
{
|
|
||||||
return $this->domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return string The endpoint value.
|
|
||||||
*/
|
|
||||||
public function getEndpoint(): string
|
|
||||||
{
|
|
||||||
return $this->endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the record to a ResolvedServer object.
|
|
||||||
*
|
|
||||||
* @return ResolvedServer The ResolvedServer object.
|
|
||||||
*/
|
|
||||||
public function toResolvedServer(): ResolvedServer
|
|
||||||
{
|
|
||||||
return new ResolvedServer($this->endpoint, $this->publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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')
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 $clientName;
|
||||||
private string $clientVersion;
|
private string $clientVersion;
|
||||||
private bool $authenticated;
|
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 SessionState $state;
|
||||||
private ?string $encryptionKey;
|
|
||||||
/**
|
/**
|
||||||
* @var SessionFlags[]
|
* @var SessionFlags[]
|
||||||
*/
|
*/
|
||||||
|
@ -42,10 +46,14 @@
|
||||||
$this->clientName = $data['client_name'];
|
$this->clientName = $data['client_name'];
|
||||||
$this->clientVersion = $data['client_version'];
|
$this->clientVersion = $data['client_version'];
|
||||||
$this->authenticated = $data['authenticated'] ?? false;
|
$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->created = $data['created'];
|
||||||
$this->lastRequest = $data['last_request'];
|
$this->lastRequest = $data['last_request'];
|
||||||
$this->encryptionKey = $data['encryption_key'] ?? null;
|
|
||||||
$this->flags = SessionFlags::fromString($data['flags']);
|
$this->flags = SessionFlags::fromString($data['flags']);
|
||||||
|
|
||||||
if(SessionState::tryFrom($data['state']) == null)
|
if(SessionState::tryFrom($data['state']) == null)
|
||||||
|
@ -99,9 +107,55 @@
|
||||||
*
|
*
|
||||||
* @return string Returns the public key as a string.
|
* @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;
|
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.
|
* Retrieves the creation date and time of the object.
|
||||||
*
|
*
|
||||||
|
@ -194,6 +238,11 @@
|
||||||
return $this->clientVersion;
|
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
|
public function toStandardSessionState(): \Socialbox\Objects\Standard\SessionState
|
||||||
{
|
{
|
||||||
return new \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.
|
* @inheritDoc
|
||||||
*
|
|
||||||
* @param array $data An associative array of data used to initialize the object properties.
|
|
||||||
* @return object Returns a newly created object instance.
|
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): object
|
public static function fromArray(array $data): object
|
||||||
{
|
{
|
||||||
|
@ -218,10 +264,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the object's properties to an associative array.
|
* @inheritDoc
|
||||||
*
|
|
||||||
* @return array An associative array representing the object's data, including keys 'uuid', 'peer_uuid',
|
|
||||||
* 'authenticated', 'public_key', 'state', 'flags', 'created', and 'last_request'.
|
|
||||||
*/
|
*/
|
||||||
public function toArray(): array
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
|
@ -229,7 +272,12 @@
|
||||||
'uuid' => $this->uuid,
|
'uuid' => $this->uuid,
|
||||||
'peer_uuid' => $this->peerUuid,
|
'peer_uuid' => $this->peerUuid,
|
||||||
'authenticated' => $this->authenticated,
|
'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,
|
'state' => $this->state->value,
|
||||||
'flags' => SessionFlags::toString($this->flags),
|
'flags' => SessionFlags::toString($this->flags),
|
||||||
'created' => $this->created,
|
'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;
|
namespace Socialbox\Objects;
|
||||||
|
|
||||||
|
use Socialbox\Interfaces\SerializableInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an exported session containing cryptographic keys, identifiers, and endpoints.
|
* Represents an exported session containing cryptographic keys, identifiers, and endpoints.
|
||||||
*/
|
*/
|
||||||
class ExportedSession
|
class ExportedSession implements SerializableInterface
|
||||||
{
|
{
|
||||||
private string $peerAddress;
|
private string $peerAddress;
|
||||||
private string $privateKey;
|
|
||||||
private string $publicKey;
|
|
||||||
private string $encryptionKey;
|
|
||||||
private string $serverPublicKey;
|
|
||||||
private string $rpcEndpoint;
|
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.
|
* @param array $data Associative array containing the required properties such as:
|
||||||
* Expected keys:
|
* 'peer_address', 'rpc_endpoint', 'session_uuid',
|
||||||
* - 'peer_address': The address of the peer.
|
* 'server_public_signing_key', 'server_public_encryption_key',
|
||||||
* - 'private_key': The private key for secure communication.
|
* 'client_public_signing_key', 'client_private_signing_key',
|
||||||
* - 'public_key': The public key for secure communication.
|
* 'client_public_encryption_key', 'client_private_encryption_key',
|
||||||
* - 'encryption_key': The encryption key used for communication.
|
* 'private_shared_secret', 'client_transport_encryption_key',
|
||||||
* - 'server_public_key': The server's public key.
|
* 'server_transport_encryption_key'.
|
||||||
* - 'rpc_endpoint': The RPC endpoint for network communication.
|
|
||||||
* - 'session_uuid': The unique identifier for the session.
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(array $data)
|
public function __construct(array $data)
|
||||||
{
|
{
|
||||||
$this->peerAddress = $data['peer_address'];
|
$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->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
|
public function getPeerAddress(): string
|
||||||
{
|
{
|
||||||
|
@ -52,47 +66,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the private key.
|
* Retrieves the RPC endpoint.
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*
|
*
|
||||||
* @return string 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.
|
* @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
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'peer_address' => $this->peerAddress,
|
'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,
|
'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.
|
* @inheritDoc
|
||||||
*
|
|
||||||
* @param array $data The input data used to construct the ExportedSession instance.
|
|
||||||
* @return ExportedSession The new ExportedSession instance created from the given data.
|
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): ExportedSession
|
public static function fromArray(array $data): ExportedSession
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,25 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Socialbox\Objects;
|
namespace Socialbox\Objects;
|
||||||
|
|
||||||
class ResolvedServer
|
use Socialbox\Objects\Standard\ServerInformation;
|
||||||
{
|
|
||||||
private string $endpoint;
|
|
||||||
private string $publicKey;
|
|
||||||
|
|
||||||
public function __construct(string $endpoint, string $publicKey)
|
class ResolvedServer
|
||||||
{
|
{
|
||||||
$this->endpoint = $endpoint;
|
private DnsRecord $dnsRecord;
|
||||||
$this->publicKey = $publicKey;
|
private ServerInformation $serverInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEndpoint(): string
|
|
||||||
{
|
|
||||||
return $this->endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPublicKey(): string
|
|
||||||
{
|
|
||||||
return $this->publicKey;
|
|
||||||
}
|
|
||||||
}
|
|
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 InvalidArgumentException;
|
||||||
use Socialbox\Classes\Configuration;
|
use Socialbox\Classes\Configuration;
|
||||||
use Socialbox\Classes\Cryptography;
|
use Socialbox\Classes\Cryptography;
|
||||||
|
use Socialbox\Classes\DnsHelper;
|
||||||
use Socialbox\Classes\Logger;
|
use Socialbox\Classes\Logger;
|
||||||
use Socialbox\Classes\ServerResolver;
|
use Socialbox\Classes\ServerResolver;
|
||||||
use Socialbox\Classes\Utilities;
|
use Socialbox\Classes\Utilities;
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
use Socialbox\Enums\StandardHeaders;
|
use Socialbox\Enums\StandardHeaders;
|
||||||
use Socialbox\Enums\StandardMethods;
|
use Socialbox\Enums\StandardMethods;
|
||||||
use Socialbox\Enums\Types\RequestType;
|
use Socialbox\Enums\Types\RequestType;
|
||||||
|
use Socialbox\Exceptions\CryptographyException;
|
||||||
use Socialbox\Exceptions\DatabaseOperationException;
|
use Socialbox\Exceptions\DatabaseOperationException;
|
||||||
use Socialbox\Exceptions\RequestException;
|
use Socialbox\Exceptions\RequestException;
|
||||||
use Socialbox\Exceptions\StandardException;
|
use Socialbox\Exceptions\StandardException;
|
||||||
|
@ -23,34 +25,37 @@
|
||||||
use Socialbox\Managers\SessionManager;
|
use Socialbox\Managers\SessionManager;
|
||||||
use Socialbox\Objects\ClientRequest;
|
use Socialbox\Objects\ClientRequest;
|
||||||
use Socialbox\Objects\PeerAddress;
|
use Socialbox\Objects\PeerAddress;
|
||||||
|
use Socialbox\Objects\Standard\ServerInformation;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class Socialbox
|
class Socialbox
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handles incoming client requests by validating required headers and processing
|
* Handles incoming client requests by parsing request headers, determining the request type,
|
||||||
* the request based on its type. The method ensures proper handling of
|
* and routing the request to the appropriate handler method. Implements error handling for
|
||||||
* specific request types like RPC, session initiation, and DHE exchange,
|
* missing or invalid request types.
|
||||||
* while returning an appropriate HTTP response for invalid or missing data.
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function handleRequest(): void
|
public static function handleRequest(): void
|
||||||
{
|
{
|
||||||
$requestHeaders = Utilities::getRequestHeaders();
|
$requestHeaders = Utilities::getRequestHeaders();
|
||||||
|
|
||||||
if(!isset($requestHeaders[StandardHeaders::REQUEST_TYPE->value]))
|
if(!isset($requestHeaders[StandardHeaders::REQUEST_TYPE->value]))
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::REQUEST_TYPE->value);
|
||||||
print('Missing required header: ' . StandardHeaders::REQUEST_TYPE->value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$clientRequest = new ClientRequest($requestHeaders, file_get_contents('php://input') ?? null);
|
$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.
|
// 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:
|
case RequestType::INITIATE_SESSION:
|
||||||
self::handleInitiateSession($clientRequest);
|
self::handleInitiateSession($clientRequest);
|
||||||
break;
|
break;
|
||||||
|
@ -64,58 +69,66 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Request-Type header');
|
||||||
print('Invalid Request-Type header');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the headers in an initialization request to ensure that all
|
* Handles an information request by setting the appropriate HTTP response code,
|
||||||
* required information is present and properly formatted. This includes
|
* content type headers, and printing the server information in JSON format.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @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.
|
* @return bool Returns true if all required headers are valid, otherwise false.
|
||||||
*/
|
*/
|
||||||
private static function validateInitHeaders(ClientRequest $clientRequest): bool
|
private static function validateInitHeaders(ClientRequest $clientRequest): bool
|
||||||
{
|
{
|
||||||
if(!$clientRequest->getClientName())
|
if(!$clientRequest->getClientName())
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
||||||
print('Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$clientRequest->getClientVersion())
|
if(!$clientRequest->getClientVersion())
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
||||||
print('Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$clientRequest->headerExists(StandardHeaders::PUBLIC_KEY))
|
if(!$clientRequest->headerExists(StandardHeaders::SIGNING_PUBLIC_KEY))
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNING_PUBLIC_KEY->value);
|
||||||
print('Missing required header: ' . StandardHeaders::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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
||||||
print('Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
||||||
{
|
{
|
||||||
http_response_code(400);
|
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
||||||
print('Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,24 +136,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a client request to initiate a session. Validates required headers,
|
* Handles the initiation of a session for a client request. This involves validating headers,
|
||||||
* ensures the peer is authorized and enabled, and creates a new session UUID
|
* verifying peer identities, resolving domains, registering peers if necessary, and finally
|
||||||
* if all checks pass. Handles edge cases like missing headers, invalid inputs,
|
* creating a session while providing the required session UUID as a response.
|
||||||
* or unauthorized peers.
|
|
||||||
*
|
*
|
||||||
* @param ClientRequest $clientRequest The request from the client containing
|
* @param ClientRequest $clientRequest The incoming client request containing all necessary headers
|
||||||
* the required headers and information.
|
* and identification information required to initiate the session.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
||||||
{
|
{
|
||||||
|
// This is only called for the `init` request type
|
||||||
if(!self::validateInitHeaders($clientRequest))
|
if(!self::validateInitHeaders($clientRequest))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always accept the client's public key at first
|
// 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 the peer is identifying as the same domain
|
||||||
if($clientRequest->getIdentifyAs()->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
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
|
// Prevent the peer from identifying as the host unless it's coming from an external domain
|
||||||
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
||||||
{
|
{
|
||||||
http_response_code(403);
|
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as the host');
|
||||||
print('Unauthorized: The requested peer is not allowed to identify as the host');
|
return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the peer is identifying as an external domain
|
// If the peer is identifying as an external domain
|
||||||
|
@ -159,64 +172,49 @@
|
||||||
// Only allow the host to identify as an external peer
|
// Only allow the host to identify as an external peer
|
||||||
if($clientRequest->getIdentifyAs()->getUsername() !== ReservedUsernames::HOST->value)
|
if($clientRequest->getIdentifyAs()->getUsername() !== ReservedUsernames::HOST->value)
|
||||||
{
|
{
|
||||||
http_response_code(403);
|
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');
|
||||||
print('Unauthorized: The requested peer is not allowed to identify as an external peer');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
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());
|
$resolvedServer = ServerResolver::resolveDomain($clientRequest->getIdentifyAs()->getDomain());
|
||||||
|
|
||||||
// Override the public key with the resolved server's public key
|
// Override the public signing key with the resolved server's public key
|
||||||
$publicKey = $resolvedServer->getPublicKey();
|
// Encryption key can be left as is.
|
||||||
}
|
$clientPublicSigningKey = $resolvedServer->getPublicSigningKey();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error('An internal error occurred while resolving the host domain', $e);
|
self::returnError(502, StandardError::RESOLUTION_FAILED, 'Conflict: Failed to resolve the host domain: ' . $e->getMessage(), $e);
|
||||||
http_response_code(500);
|
|
||||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
|
||||||
{
|
|
||||||
print(Utilities::throwableToString($e));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print('An internal error occurred');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Check if we have a registered peer with the same address
|
||||||
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
||||||
|
|
||||||
// If the peer is registered, check if it is enabled
|
// If the peer is registered, check if it is enabled
|
||||||
if($registeredPeer !== null && !$registeredPeer->isEnabled())
|
if($registeredPeer !== null && !$registeredPeer->isEnabled())
|
||||||
{
|
{
|
||||||
// Refuse to create a session if the peer is disabled/banned
|
// Refuse to create a session if the peer is disabled/banned, this usually happens when
|
||||||
// This also prevents multiple sessions from being created for the same peer
|
// a peer gets banned or more commonly when a client attempts to register as this peer but
|
||||||
// A cron job should be used to clean up disabled peers
|
// destroyed the session before it was created.
|
||||||
http_response_code(403);
|
// This is to prevent multiple sessions from being created for the same peer, this is cleaned up
|
||||||
print('Unauthorized: The requested peer is disabled/banned');
|
// with a cron job using `socialbox clean-sessions`
|
||||||
|
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: The requested peer is disabled/banned');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Otherwise the peer isn't registered, so we need to register it
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check if registration is enabled
|
// Check if registration is enabled
|
||||||
if(!Configuration::getRegistrationConfiguration()->isRegistrationEnabled())
|
if(!Configuration::getRegistrationConfiguration()->isRegistrationEnabled())
|
||||||
{
|
{
|
||||||
http_response_code(403);
|
self::returnError(401, StandardError::UNAUTHORIZED, 'Unauthorized: Registration is disabled');
|
||||||
print('Unauthorized: Registration is disabled');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,141 +224,220 @@
|
||||||
$registeredPeer = RegisteredPeerManager::getPeer($peerUuid);
|
$registeredPeer = RegisteredPeerManager::getPeer($peerUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the session UUID
|
// Generate server's encryption keys for this session
|
||||||
$sessionUuid = SessionManager::createSession($publicKey, $registeredPeer, $clientRequest->getClientName(), $clientRequest->getClientVersion());
|
$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
|
http_response_code(201); // Created
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
header(StandardHeaders::ENCRYPTION_PUBLIC_KEY->value . ': ' . $serverEncryptionKey->getPublicKey());
|
||||||
print($sessionUuid); // Return the session UUID
|
print($sessionUuid); // Return the session UUID
|
||||||
}
|
}
|
||||||
catch(InvalidArgumentException $e)
|
catch(InvalidArgumentException $e)
|
||||||
{
|
{
|
||||||
http_response_code(412); // Precondition failed
|
// This is usually thrown due to an invalid input
|
||||||
print($e->getMessage()); // Why the request failed
|
self::returnError(400, StandardError::BAD_REQUEST, $e->getMessage(), $e);
|
||||||
}
|
}
|
||||||
catch(Exception $e)
|
catch(Exception $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error('An internal error occurred while initiating the session', $e);
|
self::returnError(500, StandardError::INTERNAL_SERVER_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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the Diffie-Hellman key exchange by decrypting the encrypted key passed on from the client using
|
* Handles the Diffie-Hellman Ephemeral (DHE) key exchange process between the client and server,
|
||||||
* the server's private key and setting the encryption key to the session.
|
* 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
|
* @param ClientRequest $clientRequest The request object containing headers, body, and session details
|
||||||
* 400: Bad request
|
* required to perform the DHE exchange.
|
||||||
* 500: Internal server error
|
|
||||||
* 204: Success, no content.
|
|
||||||
*
|
*
|
||||||
* @param ClientRequest $clientRequest
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private static function handleDheExchange(ClientRequest $clientRequest): 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))
|
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);
|
||||||
|
|
||||||
http_response_code(412);
|
|
||||||
print('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
|
||||||
return;
|
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()))
|
if(empty($clientRequest->getRequestBody()))
|
||||||
{
|
{
|
||||||
Logger::getLogger()->verbose('Bad request: The key exchange request body is empty');
|
self::returnError(400, StandardError::BAD_REQUEST, 'Bad request: The key exchange request body is empty');
|
||||||
|
|
||||||
http_response_code(400);
|
|
||||||
print('Bad request: The key exchange request body is empty');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the session is awaiting a DHE exchange
|
// Check if the session is awaiting a DHE exchange, forbidden if not
|
||||||
if($clientRequest->getSession()->getState() !== SessionState::AWAITING_DHE)
|
$session = $clientRequest->getSession();
|
||||||
|
if($session->getState() !== SessionState::AWAITING_DHE)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->verbose('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');
|
||||||
|
|
||||||
http_response_code(400);
|
|
||||||
print('Bad request: The session is not awaiting a DHE exchange');
|
|
||||||
return;
|
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
|
try
|
||||||
{
|
{
|
||||||
// Attempt to decrypt the encrypted key passed on from the client
|
$sharedSecret = Cryptography::performDHE($session->getClientPublicEncryptionKey(), $session->getServerPrivateEncryptionKey());
|
||||||
$encryptionKey = Cryptography::decryptContent($clientRequest->getRequestBody(), Configuration::getInstanceConfiguration()->getPrivateKey());
|
|
||||||
}
|
}
|
||||||
catch (Exceptions\CryptographyException $e)
|
catch (CryptographyException $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error(sprintf('Bad Request: Failed to decrypt the key for session %s', $clientRequest->getSessionUuid()), $e);
|
Logger::getLogger()->error('Failed to perform DHE exchange', $e);
|
||||||
|
self::returnError(422, StandardError::CRYPTOGRAPHIC_ERROR, 'DHE exchange failed', $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());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STAGE 1: CLIENT -> SERVER
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Finally set the encryption key to the session
|
// Attempt to decrypt the encrypted key passed on from the client using the shared secret
|
||||||
SessionManager::setEncryptionKey($clientRequest->getSessionUuid(), $encryptionKey);
|
$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)
|
catch (DatabaseOperationException $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error('Failed to set the encryption key for the session', $e);
|
Logger::getLogger()->error('Failed to set the encryption key for the session', $e);
|
||||||
http_response_code(500);
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to set the encryption key for the session', $e);
|
||||||
|
|
||||||
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
|
||||||
{
|
|
||||||
print(Utilities::throwableToString($e));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print('Internal Server Error: Failed to set the encryption key for the session');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::getLogger()->info(sprintf('DHE exchange completed for session %s', $clientRequest->getSessionUuid()));
|
// Return the encrypted transport key for the server back to the client.
|
||||||
http_response_code(204); // Success, no content
|
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,
|
* Handles a Remote Procedure Call (RPC) request, ensuring proper decryption,
|
||||||
* and returns the appropriate response(s) or error(s).
|
* 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
|
* @return void
|
||||||
*/
|
*/
|
||||||
private static function handleRpc(ClientRequest $clientRequest): 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))
|
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);
|
if(!$clientRequest->headerExists(StandardHeaders::SIGNATURE))
|
||||||
print('Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
{
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
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)
|
catch (RequestException $e)
|
||||||
{
|
{
|
||||||
http_response_code($e->getCode());
|
self::returnError($e->getCode(), $e->getStandardError(), $e->getMessage());
|
||||||
print($e->getMessage());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,16 +519,24 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$session = $clientRequest->getSession();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$encryptedResponse = Cryptography::encryptTransport($response, $clientRequest->getSession()->getEncryptionKey());
|
$encryptedResponse = Cryptography::encryptMessage(
|
||||||
$signature = Cryptography::signContent($response, Configuration::getInstanceConfiguration()->getPrivateKey(), true);
|
message: $response,
|
||||||
|
encryptionKey: $session->getClientTransportEncryptionKey(),
|
||||||
|
algorithm: Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()
|
||||||
|
);
|
||||||
|
|
||||||
|
$signature = Cryptography::signMessage(
|
||||||
|
message: $response,
|
||||||
|
privateKey: Configuration::getCryptographyConfiguration()->getHostPrivateKey()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (Exceptions\CryptographyException $e)
|
catch (Exceptions\CryptographyException $e)
|
||||||
{
|
{
|
||||||
Logger::getLogger()->error('Failed to encrypt the response', $e);
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to encrypt the server response', $e);
|
||||||
http_response_code(500);
|
|
||||||
print('Internal Server Error: Failed to encrypt the response');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,4 +545,69 @@
|
||||||
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
||||||
print($encryptedResponse);
|
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