Changed Encryption to use LibSodium instead of OpenSSL, refactored many things and overall improved the code quality and performance with magic.

This commit is contained in:
netkas 2025-01-03 12:27:04 -05:00
parent 46ad03a54d
commit 367399f0fd
44 changed files with 2971 additions and 2016 deletions

View file

@ -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),
]);
}
}

View file

@ -2,13 +2,15 @@
namespace Socialbox\Managers;
use DateTime;
use PDO;
use PDOException;
use Socialbox\Classes\Configuration;
use Socialbox\Classes\Cryptography;
use Socialbox\Classes\Database;
use Socialbox\Classes\SecuredPassword;
use Socialbox\Exceptions\CryptographyException;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Objects\Database\RegisteredPeerRecord;
use Socialbox\Objects\Database\SecurePasswordRecord;
class PasswordManager
{
@ -34,154 +36,139 @@
return $stmt->fetchColumn() > 0;
}
catch (\PDOException $e)
catch (PDOException $e)
{
throw new DatabaseOperationException('An error occurred while checking the password usage in the database', $e);
}
}
/**
* Sets a password for a given user or peer record by securely encrypting it
* and storing it in the authentication_passwords database table.
* Sets a secured password for the given peer UUID or registered peer record.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @param string $password The plaintext password to be securely stored.
* @throws CryptographyException If an error occurs while securing the password.
* @throws DatabaseOperationException If an error occurs while attempting to store the password in the database.
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
* @param string $hash The plaintext password to be securely stored.
* @return void
* @throws DatabaseOperationException If an error occurs while storing the password in the database.
* @throws CryptographyException If an error occurs during password encryption or hashing.
*/
public static function setPassword(string|RegisteredPeerRecord $peerUuid, string $password): void
public static function setPassword(string|RegisteredPeerRecord $peerUuid, string $hash): void
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
$securedPassword = SecuredPassword::securePassword($peerUuid, $password, $encryptionRecord);
// Throws an exception if the hash is invalid
Cryptography::validatePasswordHash($hash);
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
try
{
$stmt = Database::getConnection()->prepare("INSERT INTO authentication_passwords (peer_uuid, iv, encrypted_password, encrypted_tag) VALUES (:peer_uuid, :iv, :encrypted_password, :encrypted_tag)");
$stmt = Database::getConnection()->prepare("INSERT INTO authentication_passwords (peer_uuid, hash) VALUES (:peer_uuid, :hash)");
$stmt->bindParam(":peer_uuid", $peerUuid);
$iv = $securedPassword->getIv();
$stmt->bindParam(':iv', $iv);
$encryptedPassword = $securedPassword->getEncryptedPassword();
$stmt->bindParam(':encrypted_password', $encryptedPassword);
$encryptedTag = $securedPassword->getEncryptedTag();
$stmt->bindParam(':encrypted_tag', $encryptedTag);
$stmt->bindParam(':hash', $securedPassword);
$stmt->execute();
}
catch(\PDOException $e)
catch(PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to set password for user %s', $peerUuid), $e);
}
}
/**
* Updates the password for a given peer identified by their UUID or a RegisteredPeerRecord.
* Updates the secured password associated with the given peer UUID.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @param string $newPassword The new password to be set for the peer.
* @throws CryptographyException If an error occurs while securing the new password.
* @throws DatabaseOperationException If the update operation fails due to a database error.
* @throws \DateMalformedStringException If the updated timestamp cannot be formatted.
* @returns void
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
* @param string $hash The new password to be stored securely.
* @return void
* @throws DatabaseOperationException If an error occurs while updating the password in the database.
* @throws CryptographyException If an error occurs while encrypting the password or validating the hash.
*/
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $newPassword): void
public static function updatePassword(string|RegisteredPeerRecord $peerUuid, string $hash): void
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
Cryptography::validatePasswordHash($hash);
$encryptionRecord = EncryptionRecordsManager::getRandomRecord();
$securedPassword = SecuredPassword::securePassword($peerUuid, $newPassword, $encryptionRecord);
$encryptionKey = Configuration::getCryptographyConfiguration()->getRandomInternalEncryptionKey();
$securedPassword = Cryptography::encryptMessage($hash, $encryptionKey, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
try
{
$stmt = Database::getConnection()->prepare("UPDATE authentication_passwords SET iv=:iv, encrypted_password=:encrypted_password, encrypted_tag=:encrypted_tag, updated=:updated WHERE peer_uuid=:peer_uuid");
$stmt->bindParam(":peer_uuid", $peerUuid);
$iv = $securedPassword->getIv();
$stmt->bindParam(':iv', $iv);
$encryptedPassword = $securedPassword->getEncryptedPassword();
$stmt->bindParam(':encrypted_password', $encryptedPassword);
$encryptedTag = $securedPassword->getEncryptedTag();
$stmt->bindParam(':encrypted_tag', $encryptedTag);
$updated = $securedPassword->getUpdated()->format('Y-m-d H:i:s');
$stmt = Database::getConnection()->prepare("UPDATE authentication_passwords SET hash=:hash, updated=:updated WHERE peer_uuid=:peer_uuid");
$updated = (new DateTime())->setTimestamp(time());
$stmt->bindParam(':hash', $securedPassword);
$stmt->bindParam(':updated', $updated);
$stmt->bindParam(':peer_uuid', $peerUuid);
$stmt->execute();
}
catch(\PDOException $e)
catch(PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to update password for user %s', $peerUuid), $e);
}
}
/**
* Retrieves the password record associated with the given peer UUID.
* Verifies a given password against a stored password hash for a specific peer.
*
* @param string|RegisteredPeerRecord $peerUuid The UUID of the peer or an instance of RegisteredPeerRecord.
* @return SecurePasswordRecord|null Returns a SecurePasswordRecord if found, or null if no record is present.
* @throws DatabaseOperationException If a database operation error occurs during the retrieval process.
* @param string|RegisteredPeerRecord $peerUuid The unique identifier of the peer, or an instance of RegisteredPeerRecord.
* @param string $hash The password hash to verify.
* @return bool Returns true if the password matches the stored hash; false otherwise.
* @throws CryptographyException If the password hash is invalid or an error occurs during the cryptographic operation.
* @throws DatabaseOperationException If an error occurs during the database operation.
*/
private static function getPassword(string|RegisteredPeerRecord $peerUuid): ?SecurePasswordRecord
public static function verifyPassword(string|RegisteredPeerRecord $peerUuid, string $hash): bool
{
if($peerUuid instanceof RegisteredPeerRecord)
{
$peerUuid = $peerUuid->getUuid();
}
Cryptography::validatePasswordHash($hash);
try
{
$statement = Database::getConnection()->prepare("SELECT * FROM authentication_passwords WHERE peer_uuid=:peer_uuid LIMIT 1");
$statement->bindParam(':peer_uuid', $peerUuid);
$stmt = Database::getConnection()->prepare('SELECT hash FROM authentication_passwords WHERE peer_uuid=:uuid');
$stmt->bindParam(':uuid', $peerUuid);
$stmt->execute();
$statement->execute();
$data = $statement->fetch(PDO::FETCH_ASSOC);
if ($data === false)
$record = $stmt->fetch(PDO::FETCH_ASSOC);
if($record === false)
{
return null;
return false;
}
return SecurePasswordRecord::fromArray($data);
}
catch(\PDOException $e)
{
throw new DatabaseOperationException(sprintf('Failed to retrieve password record for user %s', $peerUuid), $e);
}
}
$encryptedHash = $record['hash'];
$decryptedHash = null;
foreach(Configuration::getCryptographyConfiguration()->getInternalEncryptionKeys() as $key)
{
try
{
$decryptedHash = Cryptography::decryptMessage($encryptedHash, $key, Configuration::getCryptographyConfiguration()->getEncryptionKeysAlgorithm());
}
catch(CryptographyException)
{
continue;
}
}
/**
* Verifies if the provided password matches the secured password associated with the given peer UUID.
*
* @param string|RegisteredPeerRecord $peerUuid The unique identifier or registered peer record of the user.
* @param string $password The password to be verified.
* @return bool Returns true if the password is verified successfully; otherwise, false.
* @throws DatabaseOperationException If an error occurs while retrieving the password record from the database.
* @throws CryptographyException If an error occurs while verifying the password.
*/
public static function verifyPassword(string|RegisteredPeerRecord $peerUuid, string $password): bool
{
$securedPassword = self::getPassword($peerUuid);
if($securedPassword === null)
{
return false;
}
if($decryptedHash === null)
{
throw new CryptographyException('Cannot decrypt hashed password');
}
$encryptionRecords = EncryptionRecordsManager::getAllRecords();
return SecuredPassword::verifyPassword($password, $securedPassword, $encryptionRecords);
return Cryptography::verifyPassword($hash, $decryptedHash);
}
catch(PDOException $e)
{
throw new DatabaseOperationException('An error occurred while verifying the password', $e);
}
}
}

View 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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -19,34 +19,27 @@
use Socialbox\Exceptions\StandardException;
use Socialbox\Objects\Database\RegisteredPeerRecord;
use Socialbox\Objects\Database\SessionRecord;
use Socialbox\Objects\KeyPair;
use Symfony\Component\Uid\Uuid;
class SessionManager
{
/**
* Creates a new session with the given public key.
*
* @param string $publicKey The public key to associate with the new session.
* @param RegisteredPeerRecord $peer
*
* @throws InvalidArgumentException If the public key is empty or invalid.
* @throws DatabaseOperationException If there is an error while creating the session in the database.
*/
public static function createSession(string $publicKey, RegisteredPeerRecord $peer, string $clientName, string $clientVersion): string
public static function createSession(RegisteredPeerRecord $peer, string $clientName, string $clientVersion, string $clientPublicSigningKey, string $clientPublicEncryptionKey, KeyPair $serverEncryptionKeyPair): string
{
if($publicKey === '')
if($clientPublicSigningKey === '' || Cryptography::validatePublicSigningKey($clientPublicSigningKey) === false)
{
throw new InvalidArgumentException('The public key cannot be empty');
throw new InvalidArgumentException('The public key is not a valid Ed25519 public key');
}
if(!Cryptography::validatePublicKey($publicKey))
if($clientPublicEncryptionKey === '' || Cryptography::validatePublicEncryptionKey($clientPublicEncryptionKey) === false)
{
throw new InvalidArgumentException('The given public key is invalid');
throw new InvalidArgumentException('The public key is not a valid X25519 public key');
}
$uuid = Uuid::v4()->toRfc4122();
$flags = [];
// TODO: Update this to support `host` peers
if($peer->isEnabled())
{
$flags[] = SessionFlags::AUTHENTICATION_REQUIRED;
@ -119,13 +112,18 @@
try
{
$statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, public_key, flags) VALUES (?, ?, ?, ?, ?, ?)");
$statement->bindParam(1, $uuid);
$statement->bindParam(2, $peerUuid);
$statement->bindParam(3, $clientName);
$statement->bindParam(4, $clientVersion);
$statement->bindParam(5, $publicKey);
$statement->bindParam(6, $implodedFlags);
$statement = Database::getConnection()->prepare("INSERT INTO sessions (uuid, peer_uuid, client_name, client_version, client_public_signing_key, client_public_encryption_key, server_public_encryption_key, server_private_encryption_key, flags) VALUES (:uuid, :peer_uuid, :client_name, :client_version, :client_public_signing_key, :client_public_encryption_key, :server_public_encryption_key, :server_private_encryption_key, :flags)");
$statement->bindParam(':uuid', $uuid);
$statement->bindParam(':peer_uuid', $peerUuid);
$statement->bindParam(':client_name', $clientName);
$statement->bindParam(':client_version', $clientVersion);
$statement->bindParam(':client_public_signing_key', $clientPublicSigningKey);
$statement->bindParam(':client_public_encryption_key', $clientPublicEncryptionKey);
$serverPublicEncryptionKey = $serverEncryptionKeyPair->getPublicKey();
$statement->bindParam(':server_public_encryption_key', $serverPublicEncryptionKey);
$serverPrivateEncryptionKey = $serverEncryptionKeyPair->getPrivateKey();
$statement->bindParam(':server_private_encryption_key', $serverPrivateEncryptionKey);
$statement->bindParam(':flags', $implodedFlags);
$statement->execute();
}
catch(PDOException $e)
@ -186,7 +184,6 @@
// Convert the timestamp fields to DateTime objects
$data['created'] = new DateTime($data['created']);
if(isset($data['last_request']) && $data['last_request'] !== null)
{
$data['last_request'] = new DateTime($data['last_request']);
@ -205,53 +202,6 @@
}
}
/**
* Update the authenticated peer associated with the given session UUID.
*
* @param string $uuid The UUID of the session to update.
* @param RegisteredPeerRecord|string $registeredPeerUuid
* @return void
* @throws DatabaseOperationException
*/
public static function updatePeer(string $uuid, RegisteredPeerRecord|string $registeredPeerUuid): void
{
if($registeredPeerUuid instanceof RegisteredPeerRecord)
{
$registeredPeerUuid = $registeredPeerUuid->getUuid();
}
Logger::getLogger()->verbose(sprintf("Assigning peer %s to session %s", $registeredPeerUuid, $uuid));
try
{
$statement = Database::getConnection()->prepare("UPDATE sessions SET peer_uuid=? WHERE uuid=?");
$statement->bindParam(1, $registeredPeerUuid);
$statement->bindParam(2, $uuid);
$statement->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException('Failed to update authenticated peer', $e);
}
}
public static function updateAuthentication(string $uuid, bool $authenticated): void
{
Logger::getLogger()->verbose(sprintf("Marking session %s as authenticated: %s", $uuid, $authenticated ? 'true' : 'false'));
try
{
$statement = Database::getConnection()->prepare("UPDATE sessions SET authenticated=? WHERE uuid=?");
$statement->bindParam(1, $authenticated);
$statement->bindParam(2, $uuid);
$statement->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException('Failed to update authenticated peer', $e);
}
}
/**
* Updates the last request timestamp for a given session by its UUID.
*
@ -305,24 +255,28 @@
}
/**
* Updates the encryption key for the specified session.
* Updates the encryption keys and session state for a specific session UUID in the database.
*
* @param string $uuid The unique identifier of the session for which the encryption key is to be set.
* @param string $encryptionKey The new encryption key to be assigned.
* @param string $uuid The unique identifier for the session to update.
* @param string $privateSharedSecret The private shared secret to secure communication.
* @param string $clientEncryptionKey The client's encryption key used for transport security.
* @param string $serverEncryptionKey The server's encryption key used for transport security.
* @return void
* @throws DatabaseOperationException If the database operation fails.
* @throws DatabaseOperationException If an error occurs during the database operation.
*/
public static function setEncryptionKey(string $uuid, string $encryptionKey): void
public static function setEncryptionKeys(string $uuid, string $privateSharedSecret, string $clientEncryptionKey, string $serverEncryptionKey): void
{
Logger::getLogger()->verbose(sprintf('Setting the encryption key for %s', $uuid));
try
{
$state_value = SessionState::ACTIVE->value;
$statement = Database::getConnection()->prepare('UPDATE sessions SET state=?, encryption_key=? WHERE uuid=?');
$statement->bindParam(1, $state_value);
$statement->bindParam(2, $encryptionKey);
$statement->bindParam(3, $uuid);
$statement = Database::getConnection()->prepare('UPDATE sessions SET state=:state, private_shared_secret=:private_shared_secret, client_transport_encryption_key=:client_transport_encryption_key, server_transport_encryption_key=:server_transport_encryption_key WHERE uuid=:uuid');
$statement->bindParam(':state', $state_value);
$statement->bindParam(':private_shared_secret', $privateSharedSecret);
$statement->bindParam(':client_transport_encryption_key', $clientEncryptionKey);
$statement->bindParam(':server_transport_encryption_key', $serverEncryptionKey);
$statement->bindParam(':uuid', $uuid);
$statement->execute();
}