1175 lines
No EOL
53 KiB
PHP
1175 lines
No EOL
53 KiB
PHP
<?php
|
|
|
|
namespace Socialbox;
|
|
|
|
use Exception;
|
|
use InvalidArgumentException;
|
|
use Socialbox\Classes\Configuration;
|
|
use Socialbox\Classes\Cryptography;
|
|
use Socialbox\Classes\DnsHelper;
|
|
use Socialbox\Classes\Logger;
|
|
use Socialbox\Classes\ServerResolver;
|
|
use Socialbox\Classes\Utilities;
|
|
use Socialbox\Classes\Validator;
|
|
use Socialbox\Enums\Flags\PeerFlags;
|
|
use Socialbox\Enums\PrivacyState;
|
|
use Socialbox\Enums\ReservedUsernames;
|
|
use Socialbox\Enums\SessionState;
|
|
use Socialbox\Enums\StandardError;
|
|
use Socialbox\Enums\StandardHeaders;
|
|
use Socialbox\Enums\StandardMethods;
|
|
use Socialbox\Enums\Status\SignatureVerificationStatus;
|
|
use Socialbox\Enums\Types\ContactRelationshipType;
|
|
use Socialbox\Enums\Types\InformationFieldName;
|
|
use Socialbox\Enums\Types\RequestType;
|
|
use Socialbox\Exceptions\CryptographyException;
|
|
use Socialbox\Exceptions\DatabaseOperationException;
|
|
use Socialbox\Exceptions\RequestException;
|
|
use Socialbox\Exceptions\ResolutionException;
|
|
use Socialbox\Exceptions\RpcException;
|
|
use Socialbox\Exceptions\Standard\StandardRpcException;
|
|
use Socialbox\Managers\ContactManager;
|
|
use Socialbox\Managers\ExternalSessionManager;
|
|
use Socialbox\Managers\PeerInformationManager;
|
|
use Socialbox\Managers\RegisteredPeerManager;
|
|
use Socialbox\Managers\SessionManager;
|
|
use Socialbox\Managers\SigningKeysManager;
|
|
use Socialbox\Objects\ClientRequest;
|
|
use Socialbox\Objects\PeerAddress;
|
|
use Socialbox\Objects\Standard\InformationField;
|
|
use Socialbox\Objects\Standard\Peer;
|
|
use Socialbox\Objects\Standard\ServerInformation;
|
|
use Socialbox\Objects\Standard\SigningKey;
|
|
use Throwable;
|
|
|
|
class Socialbox
|
|
{
|
|
/**
|
|
* Handles incoming client requests by parsing request headers, determining the request type,
|
|
* and routing the request to the appropriate handler method. Implements error handling for
|
|
* missing or invalid request types.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function handleRequest(): void
|
|
{
|
|
$requestHeaders = Utilities::getRequestHeaders();
|
|
if(!isset($requestHeaders[StandardHeaders::REQUEST_TYPE->value]))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::REQUEST_TYPE->value);
|
|
return;
|
|
}
|
|
|
|
$clientRequest = new ClientRequest($requestHeaders, file_get_contents('php://input') ?? null);
|
|
|
|
// Handle the request type, only `init` and `dhe` are not encrypted using the session's encrypted key
|
|
// RPC Requests must be encrypted and signed by the client, vice versa for server responses.
|
|
try
|
|
{
|
|
switch($clientRequest->getRequestType())
|
|
{
|
|
case RequestType::PING:
|
|
self::handlePingRequest();
|
|
break;
|
|
|
|
case RequestType::INFO:
|
|
self::handleInformationRequest();
|
|
break;
|
|
|
|
case RequestType::INITIATE_SESSION:
|
|
self::handleInitiateSession($clientRequest);
|
|
break;
|
|
|
|
case RequestType::DHE_EXCHANGE:
|
|
self::handleDheExchange($clientRequest);
|
|
break;
|
|
|
|
case RequestType::RPC:
|
|
self::handleRpc($clientRequest);
|
|
break;
|
|
|
|
default:
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Request-Type header');
|
|
}
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'An internal error occurred while processing the request', $e);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles an incoming ping request by sending a successful HTTP response.
|
|
*
|
|
* @return void
|
|
*/
|
|
private static function handlePingRequest(): void
|
|
{
|
|
http_response_code(200);
|
|
header('Content-Type: text/plain');
|
|
print('OK');
|
|
}
|
|
|
|
/**
|
|
* Handles an information request by setting the appropriate HTTP response code,
|
|
* content type headers, and printing the server information in JSON format.
|
|
*
|
|
* @return void
|
|
*/
|
|
private static function handleInformationRequest(): void
|
|
{
|
|
http_response_code(200);
|
|
header('Content-Type: application/json');
|
|
Logger::getLogger()->info(json_encode(self::getServerInformation()->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
print(json_encode(self::getServerInformation()->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
/**
|
|
* Validates the initial headers of a client request to ensure all required headers exist
|
|
* and contain valid values. If any validation fails, an error response is returned.
|
|
*
|
|
* @param ClientRequest $clientRequest The client request containing headers to be validated.
|
|
* @return bool Returns true if all required headers are valid, otherwise false.
|
|
*/
|
|
private static function validateInitHeaders(ClientRequest $clientRequest): bool
|
|
{
|
|
if(!$clientRequest->getClientName())
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_NAME->value);
|
|
return false;
|
|
}
|
|
|
|
if(!$clientRequest->getClientVersion())
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::CLIENT_VERSION->value);
|
|
return false;
|
|
}
|
|
|
|
if(!$clientRequest->headerExists(StandardHeaders::ENCRYPTION_PUBLIC_KEY))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::ENCRYPTION_PUBLIC_KEY->value);
|
|
return false;
|
|
}
|
|
|
|
if(!$clientRequest->headerExists(StandardHeaders::IDENTIFY_AS))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::IDENTIFY_AS->value);
|
|
return false;
|
|
}
|
|
|
|
if(!Validator::validatePeerAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Invalid Identify-As header: ' . $clientRequest->getHeader(StandardHeaders::IDENTIFY_AS));
|
|
return false;
|
|
}
|
|
|
|
if(!$clientRequest->getIdentifyAs()->isExternal() && !$clientRequest->headerExists(StandardHeaders::SIGNING_PUBLIC_KEY))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNING_PUBLIC_KEY->value);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handles the initiation of a session for a client request. This involves validating headers,
|
|
* verifying peer identities, resolving domains, registering peers if necessary, and finally
|
|
* creating a session while providing the required session UUID as a response.
|
|
*
|
|
* @param ClientRequest $clientRequest The incoming client request containing all necessary headers
|
|
* and identification information required to initiate the session.
|
|
* @return void
|
|
*/
|
|
private static function handleInitiateSession(ClientRequest $clientRequest): void
|
|
{
|
|
// This is only called for the `init` request type
|
|
if(!self::validateInitHeaders($clientRequest))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We always accept the client's public key at first
|
|
$clientPublicSigningKey = $clientRequest->getHeader(StandardHeaders::SIGNING_PUBLIC_KEY);
|
|
$clientPublicEncryptionKey = $clientRequest->getHeader(StandardHeaders::ENCRYPTION_PUBLIC_KEY);
|
|
|
|
// If the peer is identifying as the same domain
|
|
if($clientRequest->getIdentifyAs()->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
|
{
|
|
// Prevent the peer from identifying as the host unless it's coming from an external domain
|
|
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
|
{
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as the host');
|
|
return;
|
|
}
|
|
}
|
|
// If the peer is identifying as an external domain
|
|
else
|
|
{
|
|
// Only allow the host to identify as a host
|
|
if($clientRequest->getIdentifyAs()->getUsername() !== ReservedUsernames::HOST->value)
|
|
{
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: Any external peer must identify as the host, only the host can preform actions on behalf of it\'s peers');
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// We need to obtain the public key of the host, since we can't trust the client (Use database)
|
|
$resolvedServer = ServerResolver::resolveDomain($clientRequest->getIdentifyAs()->getDomain());
|
|
|
|
// Override the public signing key with the resolved server's public key
|
|
// Encryption key can be left as is.
|
|
$clientPublicSigningKey = $resolvedServer->getPublicSigningKey();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
self::returnError(502, StandardError::RESOLUTION_FAILED, 'Conflict: Failed to resolve the host domain: ' . $e->getMessage(), $e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
// Check if we have a registered peer with the same address
|
|
$registeredPeer = RegisteredPeerManager::getPeerByAddress($clientRequest->getIdentifyAs());
|
|
|
|
// If the peer is registered, check if it is enabled
|
|
if($registeredPeer !== null && !$registeredPeer->isEnabled())
|
|
{
|
|
// Refuse to create a session if the peer is disabled/banned, this usually happens when
|
|
// a peer gets banned or more commonly when a client attempts to register as this peer but
|
|
// destroyed the session before it was created.
|
|
// This is to prevent multiple sessions from being created for the same peer, this is cleaned up
|
|
// with a cron job using `socialbox clean-sessions`
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: The requested peer is disabled/banned');
|
|
return;
|
|
}
|
|
|
|
// If-clause for handling the host peer, host peers are always enabled unless the fist clause is true
|
|
// in which case the host was blocked by this server.
|
|
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
|
|
{
|
|
// If the host is not registered, register it
|
|
if($registeredPeer === null)
|
|
{
|
|
$peerUuid = RegisteredPeerManager::createPeer(PeerAddress::fromAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)));
|
|
RegisteredPeerManager::enablePeer($peerUuid);
|
|
$registeredPeer = RegisteredPeerManager::getPeer($peerUuid);
|
|
}
|
|
else
|
|
{
|
|
// If the host is registered, but disabled, enable it
|
|
if(!$registeredPeer->isEnabled())
|
|
{
|
|
RegisteredPeerManager::enablePeer($registeredPeer->getUuid());
|
|
}
|
|
}
|
|
}
|
|
|
|
if($registeredPeer === null)
|
|
{
|
|
// Check if registration is enabled
|
|
if(!Configuration::getRegistrationConfiguration()->isRegistrationEnabled())
|
|
{
|
|
self::returnError(401, StandardError::UNAUTHORIZED, 'Unauthorized: Registration is disabled');
|
|
return;
|
|
}
|
|
|
|
// Register the peer if it is not already registered
|
|
$peerUuid = RegisteredPeerManager::createPeer(PeerAddress::fromAddress($clientRequest->getHeader(StandardHeaders::IDENTIFY_AS)));
|
|
// Retrieve the peer object
|
|
$registeredPeer = RegisteredPeerManager::getPeer($peerUuid);
|
|
}
|
|
|
|
// Generate server's encryption keys for this session
|
|
$serverEncryptionKeyPair = Cryptography::generateEncryptionKeyPair();
|
|
|
|
// Create the session passing on the registered peer, client name, version, and public keys
|
|
$sessionUuid = SessionManager::createSession(
|
|
peer: $registeredPeer,
|
|
clientName: $clientRequest->getClientName(),
|
|
clientVersion: $clientRequest->getClientVersion(),
|
|
clientPublicSigningKey: $clientPublicSigningKey,
|
|
clientPublicEncryptionKey: $clientPublicEncryptionKey,
|
|
serverEncryptionKeyPair: $serverEncryptionKeyPair
|
|
);
|
|
}
|
|
catch(InvalidArgumentException $e)
|
|
{
|
|
// This is usually thrown due to an invalid input
|
|
self::returnError(400, StandardError::BAD_REQUEST, $e->getMessage(), $e);
|
|
return;
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'An internal error occurred while initiating the session', $e);
|
|
return;
|
|
}
|
|
|
|
// The server responds back with the session UUID & The server's public encryption key as the header
|
|
http_response_code(201); // Created
|
|
header('Content-Type: text/plain');
|
|
header(StandardHeaders::ENCRYPTION_PUBLIC_KEY->value . ': ' . $serverEncryptionKeyPair->getPublicKey());
|
|
print($sessionUuid); // Return the session UUID
|
|
}
|
|
|
|
/**
|
|
* Handles the Diffie-Hellman Ephemeral (DHE) key exchange process between the client and server,
|
|
* ensuring secure transport encryption key negotiation. The method validates request headers,
|
|
* session state, and cryptographic operations, and updates the session with the resulting keys
|
|
* and state upon successful negotiation.
|
|
*
|
|
* @param ClientRequest $clientRequest The request object containing headers, body, and session details
|
|
* required to perform the DHE exchange.
|
|
*
|
|
* @return void
|
|
* @throws CryptographyException
|
|
*/
|
|
private static function handleDheExchange(ClientRequest $clientRequest): void
|
|
{
|
|
// Check if the session UUID is set in the headers, bad request if not
|
|
if(!$clientRequest->headerExists(StandardHeaders::SESSION_UUID))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
|
return;
|
|
}
|
|
|
|
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()))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Bad request: The key exchange request body is empty');
|
|
return;
|
|
}
|
|
|
|
$session = $clientRequest->getSession();
|
|
if($session === null)
|
|
{
|
|
self::returnError(404, StandardError::SESSION_NOT_FOUND, 'Session not found');
|
|
return;
|
|
}
|
|
|
|
// Check if the session is awaiting a DHE exchange, forbidden if not
|
|
if($session->getState() !== SessionState::AWAITING_DHE)
|
|
{
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Bad request: The session is not awaiting a DHE exchange');
|
|
return;
|
|
}
|
|
|
|
|
|
// DHE STAGE: CLIENT -> SERVER
|
|
// Server & Client: Begin the DHE exchange using the exchanged public keys.
|
|
// On the client's side, same method but with the server's public key & client's private key
|
|
try
|
|
{
|
|
$sharedSecret = Cryptography::performDHE($session->getClientPublicEncryptionKey(), $session->getServerPrivateEncryptionKey());
|
|
}
|
|
catch (CryptographyException $e)
|
|
{
|
|
Logger::getLogger()->error('Failed to perform DHE exchange', $e);
|
|
self::returnError(422, StandardError::CRYPTOGRAPHIC_ERROR, 'DHE exchange failed', $e);
|
|
return;
|
|
}
|
|
|
|
// STAGE 1: CLIENT -> SERVER
|
|
try
|
|
{
|
|
// Attempt to decrypt the encrypted key passed on from the client using the shared secret
|
|
$clientTransportEncryptionKey = Cryptography::decryptShared($clientRequest->getRequestBody(), $sharedSecret);
|
|
}
|
|
catch (CryptographyException $e)
|
|
{
|
|
self::returnError(400, StandardError::CRYPTOGRAPHIC_ERROR, 'Failed to decrypt the key', $e);
|
|
return;
|
|
}
|
|
|
|
// Get the signature from the client and validate it against the decrypted key
|
|
$clientSignature = $clientRequest->getHeader(StandardHeaders::SIGNATURE);
|
|
if(!Cryptography::verifyMessage($clientTransportEncryptionKey, $clientSignature, $session->getClientPublicSigningKey()))
|
|
{
|
|
self::returnError(401, StandardError::UNAUTHORIZED, 'Invalid signature');
|
|
return;
|
|
}
|
|
|
|
// Validate the encryption key given by the client
|
|
if(!Cryptography::validateEncryptionKey($clientTransportEncryptionKey, Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'The transport encryption key is invalid and does not meet the server\'s requirements');
|
|
return;
|
|
}
|
|
|
|
// Receive stage complete, now we move on to the server's response
|
|
|
|
// STAGE 2: SERVER -> CLIENT
|
|
try
|
|
{
|
|
// Generate the server's transport encryption key (our side)
|
|
$serverTransportEncryptionKey = Cryptography::generateEncryptionKey(Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm());
|
|
|
|
// Sign the shared secret using the server's private key
|
|
$signature = Cryptography::signMessage($serverTransportEncryptionKey, Configuration::getCryptographyConfiguration()->getHostPrivateKey());
|
|
// Encrypt the server's transport key using the shared secret
|
|
$encryptedServerTransportKey = Cryptography::encryptShared($serverTransportEncryptionKey, $sharedSecret);
|
|
}
|
|
catch (CryptographyException $e)
|
|
{
|
|
Logger::getLogger()->error('Failed to generate the server\'s transport encryption key', $e);
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'There was an error while trying to process the DHE exchange', $e);
|
|
return;
|
|
}
|
|
|
|
// Now update the session details with all the encryption keys and the state
|
|
try
|
|
{
|
|
SessionManager::setEncryptionKeys($clientRequest->getSessionUuid(), $sharedSecret, $clientTransportEncryptionKey, $serverTransportEncryptionKey);
|
|
SessionManager::updateState($clientRequest->getSessionUuid(), SessionState::ACTIVE);
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
Logger::getLogger()->error('Failed to set the encryption key for the session', $e);
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to set the encryption key for the session', $e);
|
|
return;
|
|
}
|
|
|
|
// Return the encrypted transport key for the server back to the client.
|
|
http_response_code(200);
|
|
header('Content-Type: application/octet-stream');
|
|
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
|
print($encryptedServerTransportKey);
|
|
}
|
|
|
|
/**
|
|
* Handles a Remote Procedure Call (RPC) request, ensuring proper decryption,
|
|
* signature verification, and response encryption, while processing one or more
|
|
* RPC methods as specified in the request.
|
|
*
|
|
* @param ClientRequest $clientRequest The RPC client request containing headers, body, and session information.
|
|
*
|
|
* @return void
|
|
* @throws CryptographyException
|
|
* @throws DatabaseOperationException
|
|
* @throws ResolutionException
|
|
*/
|
|
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))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SESSION_UUID->value);
|
|
return;
|
|
}
|
|
|
|
if(!$clientRequest->headerExists(StandardHeaders::SIGNATURE))
|
|
{
|
|
self::returnError(400, StandardError::BAD_REQUEST, 'Missing required header: ' . StandardHeaders::SIGNATURE->value);
|
|
return;
|
|
}
|
|
|
|
// Get the client session
|
|
$session = $clientRequest->getSession();
|
|
|
|
// Verify if the session is active
|
|
if($session->getState() !== SessionState::ACTIVE)
|
|
{
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Session is not active (' . $session->getState()->value . ')');
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
SessionManager::updateLastRequest($session->getUuid());
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
Logger::getLogger()->error('Failed to update the last request time for the session', $e);
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to update the session', $e);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
// If the client has provided an identification header, further validation and resolution is required
|
|
if($clientRequest->getIdentifyAs() !== null)
|
|
{
|
|
// First check if the client is identifying as the host
|
|
if($clientRequest->getPeer()->getAddress() !== ReservedUsernames::HOST->value)
|
|
{
|
|
// TODO: Maybe allow user client to change identification but within an RPC method rather than the headers
|
|
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as a different peer');
|
|
return;
|
|
}
|
|
|
|
// Synchronize the peer
|
|
try
|
|
{
|
|
self::resolvePeer($clientRequest->getIdentifyAs());
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to synchronize external peer', $e);
|
|
return;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new ResolutionException(sprintf('Failed to synchronize external peer %s: %s', $clientRequest->getIdentifyAs()->getAddress(), $e->getMessage()), $e->getCode(), $e);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
$clientRequests = $clientRequest->getRpcRequests($decryptedContent);
|
|
}
|
|
catch (RequestException $e)
|
|
{
|
|
self::returnError($e->getCode(), $e->getStandardError(), $e->getMessage());
|
|
return;
|
|
}
|
|
|
|
Logger::getLogger()->verbose(sprintf('Received %d RPC request(s) from %s', count($clientRequests), $_SERVER['REMOTE_ADDR']));
|
|
|
|
$results = [];
|
|
foreach($clientRequests as $rpcRequest)
|
|
{
|
|
$method = StandardMethods::tryFrom($rpcRequest->getMethod());
|
|
|
|
try
|
|
{
|
|
$method->checkAccess($clientRequest);
|
|
}
|
|
catch (StandardRpcException $e)
|
|
{
|
|
$response = $e->produceError($rpcRequest);
|
|
$results[] = $response->toArray();
|
|
continue;
|
|
}
|
|
|
|
if($method === false)
|
|
{
|
|
Logger::getLogger()->warning('The requested method does not exist');
|
|
$response = $rpcRequest->produceError(StandardError::RPC_METHOD_NOT_FOUND, 'The requested method does not exist');
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod()));
|
|
$response = $method->execute($clientRequest, $rpcRequest);
|
|
Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod()));
|
|
}
|
|
catch(StandardRpcException $e)
|
|
{
|
|
Logger::getLogger()->error('An error occurred while processing the RPC request', $e);
|
|
$response = $e->produceError($rpcRequest);
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
Logger::getLogger()->error('An internal error occurred while processing the RPC request', $e);
|
|
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
|
|
{
|
|
$response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, Utilities::throwableToString($e));
|
|
}
|
|
else
|
|
{
|
|
$response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
if($response !== null)
|
|
{
|
|
Logger::getLogger()->debug(sprintf('Producing response for method %s', $rpcRequest->getMethod()));
|
|
$results[] = $response->toArray();
|
|
}
|
|
}
|
|
|
|
$response = null;
|
|
|
|
if(count($results) == 0)
|
|
{
|
|
$response = null;
|
|
}
|
|
elseif(count($results) == 1)
|
|
{
|
|
$response = json_encode($results[0], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
}
|
|
else
|
|
{
|
|
$response = json_encode($results, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
}
|
|
|
|
if($response === null)
|
|
{
|
|
http_response_code(204);
|
|
return;
|
|
}
|
|
|
|
$session = $clientRequest->getSession();
|
|
|
|
try
|
|
{
|
|
$encryptedResponse = Cryptography::encryptMessage(
|
|
message: $response,
|
|
encryptionKey: $session->getClientTransportEncryptionKey(),
|
|
algorithm: Configuration::getCryptographyConfiguration()->getTransportEncryptionAlgorithm()
|
|
);
|
|
|
|
$signature = Cryptography::signMessage(
|
|
message: $response,
|
|
privateKey: Configuration::getCryptographyConfiguration()->getHostPrivateKey()
|
|
);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to encrypt the server response', $e);
|
|
return;
|
|
}
|
|
|
|
http_response_code(200);
|
|
header('Content-Type: application/octet-stream');
|
|
header(StandardHeaders::SIGNATURE->value . ': ' . $signature);
|
|
print($encryptedResponse);
|
|
}
|
|
|
|
/**
|
|
* Sends an error response by setting the HTTP response code, headers, and printing an error message.
|
|
* Optionally includes exception details in the response if enabled in the configuration.
|
|
* Logs the error message and any associated exception.
|
|
*
|
|
* @param int $responseCode The HTTP response code to send.
|
|
* @param StandardError $standardError The standard error containing error details.
|
|
* @param string|null $message An optional error message to display. Defaults to the message from the StandardError instance.
|
|
* @param Throwable|null $e An optional throwable to include in logs and the response, if enabled.
|
|
*
|
|
* @return void
|
|
*/
|
|
private static function returnError(int $responseCode, StandardError $standardError, ?string $message=null, ?Throwable $e=null): void
|
|
{
|
|
if($message === null)
|
|
{
|
|
$message = $standardError->getMessage();
|
|
}
|
|
|
|
http_response_code($responseCode);
|
|
header('Content-Type: text/plain');
|
|
header(StandardHeaders::ERROR_CODE->value . ': ' . $standardError->value);
|
|
print($message);
|
|
|
|
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions() && $e !== null)
|
|
{
|
|
print(PHP_EOL . PHP_EOL . Utilities::throwableToString($e));
|
|
}
|
|
|
|
if($e !== null)
|
|
{
|
|
Logger::getLogger()->error($message, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves an external session associated with the given domain.
|
|
*
|
|
* If a session already exists for the specified domain, it retrieves and uses the existing session.
|
|
* Otherwise, it establishes a new connection, creates a session, and stores it for later use.
|
|
*
|
|
* @param string $domain The domain for which the external session is to be retrieved.
|
|
* @return SocialClient The RPC client initialized with the external session for the given domain.
|
|
* @throws CryptographyException If there was an error in the cryptography
|
|
* @throws DatabaseOperationException If there was an error while processing the session against the database
|
|
* @throws ResolutionException If the connection to the remote server fails.
|
|
* @throws RpcException If there is an RPC exception while connecting to the remote server
|
|
*/
|
|
public static function getExternalSession(string $domain): SocialClient
|
|
{
|
|
if(ExternalSessionManager::sessionExists($domain))
|
|
{
|
|
return new SocialClient(self::getServerAddress(), $domain, ExternalSessionManager::getSession($domain));
|
|
}
|
|
|
|
try
|
|
{
|
|
$client = new SocialClient(self::getServerAddress(), $domain);
|
|
$client->authenticate();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new ResolutionException(sprintf('Failed to connect to remote server %s: %s', $domain, $e->getMessage()), $e->getCode(), $e);
|
|
}
|
|
|
|
ExternalSessionManager::addSession($client->exportSession());
|
|
return $client;
|
|
}
|
|
|
|
/**
|
|
* Retrieves external server information for the specified domain.
|
|
*
|
|
* @param string $domain The domain from which the server information is to be retrieved.
|
|
* @return ServerInformation The server information retrieved from the external session.
|
|
* @throws ResolutionException If unable to retrieve server information for the given domain.
|
|
*/
|
|
public static function getExternalServerInformation(string $domain): ServerInformation
|
|
{
|
|
try
|
|
{
|
|
return self::getExternalSession($domain)->getServerInformation();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new ResolutionException(sprintf('Failed to retrieve server information from %s: %s', $domain, $e->getMessage()), $e->getCode(), $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies the signature of a message using the public key of the signing peer both locally and externally.
|
|
* If the peer is registered locally, the signature is verified using the local public key.
|
|
* If the peer is external, the signature is verified by resolving the peer's public key from the external server.
|
|
* The signature is verified against the resolved public key, and the status of the verification is returned.
|
|
*
|
|
* @param PeerAddress|string $signingPeer The peer address or string identifier of the signing peer
|
|
* @param string $signatureUuid The UUID of the signature key to be resolved
|
|
* @param string $signatureKey The public key of the signature that was used to sign the message
|
|
* @param string $signature The signature to be verified
|
|
* @param string $messageHash The SHA-512 hash of the message that was signed
|
|
* @param int $signatureTime The time at which the message was signed
|
|
* @return SignatureVerificationStatus The status of the signature verification
|
|
*/
|
|
public static function verifyPeerSignature(PeerAddress|string $signingPeer, string $signatureUuid, string $signatureKey, string $signature, string $messageHash, int $signatureTime): SignatureVerificationStatus
|
|
{
|
|
try
|
|
{
|
|
if (!Cryptography::verifyTimedMessage($messageHash, $signature, $signatureKey, $signatureTime, false))
|
|
{
|
|
return SignatureVerificationStatus::INVALID;
|
|
}
|
|
}
|
|
catch (CryptographyException)
|
|
{
|
|
return SignatureVerificationStatus::INVALID;
|
|
}
|
|
|
|
// Resolve the peer signature key
|
|
try
|
|
{
|
|
$signingKey = self::resolvePeerSignature($signingPeer, $signatureUuid);
|
|
}
|
|
catch(StandardRpcException)
|
|
{
|
|
return SignatureVerificationStatus::UNVERIFIED;
|
|
}
|
|
|
|
if($signingKey === null)
|
|
{
|
|
return SignatureVerificationStatus::UNVERIFIED;
|
|
}
|
|
|
|
if($signingKey->getPublicKey() !== $signatureKey)
|
|
{
|
|
return SignatureVerificationStatus::PUBLIC_KEY_MISMATCH;
|
|
}
|
|
|
|
if($signingKey->getUuid() !== $signatureUuid)
|
|
{
|
|
return SignatureVerificationStatus::UUID_MISMATCH;
|
|
}
|
|
|
|
if(time() > $signingKey->getExpires())
|
|
{
|
|
return SignatureVerificationStatus::EXPIRED;
|
|
}
|
|
|
|
// Verify the signature with the resolved key
|
|
try
|
|
{
|
|
if (!Cryptography::verifyTimedMessage($messageHash, $signature, $signingKey->getPublicKey(), $signatureTime, false))
|
|
{
|
|
return SignatureVerificationStatus::INVALID;
|
|
}
|
|
}
|
|
catch (CryptographyException)
|
|
{
|
|
return SignatureVerificationStatus::INVALID;
|
|
}
|
|
|
|
return SignatureVerificationStatus::VERIFIED;
|
|
}
|
|
|
|
/**
|
|
* Resolves a peer signature key based on the given peer address or string identifier.
|
|
*
|
|
* @param PeerAddress|string $peerAddress The peer address or string identifier to be resolved.
|
|
* @param string $signatureUuid The UUID of the signature key to be resolved.
|
|
* @return SigningKey|null The resolved signing key for the peer. Null if not found
|
|
* @throws StandardRpcException If there was an error while resolving the peer signature key.
|
|
*/
|
|
public static function resolvePeerSignature(PeerAddress|string $peerAddress, string $signatureUuid): ?SigningKey
|
|
{
|
|
// Convert string peer address to object PeerAddress
|
|
if(is_string($peerAddress))
|
|
{
|
|
try
|
|
{
|
|
$peerAddress = PeerAddress::fromAddress($peerAddress);
|
|
}
|
|
catch(InvalidArgumentException $e)
|
|
{
|
|
throw new StandardRpcException($e->getMessage(), StandardError::RPC_INVALID_ARGUMENTS, $e);
|
|
}
|
|
}
|
|
|
|
// Prevent resolutions against any host
|
|
if($peerAddress->getUsername() == ReservedUsernames::HOST)
|
|
{
|
|
throw new StandardRpcException('Cannot resolve signature for a host peer', StandardError::FORBIDDEN);
|
|
}
|
|
|
|
// If the peer is registered within this server
|
|
if($peerAddress->getDomain() === Configuration::getInstanceConfiguration()->getDomain())
|
|
{
|
|
|
|
try
|
|
{
|
|
$peer = RegisteredPeerManager::getPeerByAddress($peerAddress);
|
|
if($peer === null || !$peer?->isEnabled())
|
|
{
|
|
// Fail if the peer is not found or enabled
|
|
return null;
|
|
}
|
|
|
|
$signingKey = SigningKeysManager::getSigningKey($peer->getUuid(), $signatureUuid);
|
|
if($signingKey === null)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
throw new StandardRpcException('There was an error while trying to resolve the signature key for the peer locally', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
return $signingKey->toStandard();
|
|
}
|
|
|
|
// The requested peer is coming from an external server
|
|
try
|
|
{
|
|
$client = self::getExternalSession($peerAddress->getDomain());
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
throw new StandardRpcException(sprintf('There was an error while trying to communicate with %s', $peerAddress->getDomain()), StandardError::RESOLUTION_FAILED, $e);
|
|
}
|
|
|
|
try
|
|
{
|
|
return $client->resolvePeerSignature($peerAddress, $signatureUuid);
|
|
}
|
|
catch(RpcException $e)
|
|
{
|
|
// Reflect the server error to the client
|
|
throw new StandardRpcException($e->getMessage(), StandardError::tryFrom((int)$e->getCode()) ?? StandardError::UNKNOWN, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves an external peer based on the given peer address or string identifier.
|
|
*
|
|
* @param PeerAddress|string $peerAddress The external peer address or string identifier to be resolved.
|
|
* @param PeerAddress|string|null $identifiedAs Optional. The peer address or string identifier by which the caller is identified
|
|
* @return Peer The resolved external peer after synchronization.
|
|
* @throws StandardRpcException Thrown if there was an error with the resolution process
|
|
*/
|
|
public static function resolvePeer(PeerAddress|string $peerAddress, null|PeerAddress|string $identifiedAs=null): Peer
|
|
{
|
|
if($peerAddress->getDomain() !== Configuration::getInstanceConfiguration()->getDomain())
|
|
{
|
|
return self::resolveExternalPeer($peerAddress, $identifiedAs);
|
|
}
|
|
|
|
if($peerAddress->getUsername() === ReservedUsernames::HOST->value)
|
|
{
|
|
return new Peer([
|
|
'address' => sprintf('%s@%s', ReservedUsernames::HOST->value, Configuration::getInstanceConfiguration()->getDomain()),
|
|
'information_fields' => [
|
|
new InformationField([
|
|
'name' => InformationFieldName::DISPLAY_NAME,
|
|
'value' => Configuration::getInstanceConfiguration()->getName()
|
|
])
|
|
],
|
|
'flags' => [],
|
|
// TODO: Should use existed-since field
|
|
'registered' => 0
|
|
]);
|
|
}
|
|
|
|
return self::resolveLocalPeer($peerAddress, $identifiedAs);
|
|
}
|
|
|
|
/**
|
|
* Resolves a peer based on the given peer address or string identifier.
|
|
*
|
|
* @param PeerAddress|string $peerAddress The peer address or string identifier to be resolved.
|
|
* @param PeerAddress|string|null $identifiedAs Optional. The peer address or string identifier by which the caller is identified
|
|
* @return Peer The resolved peer after synchronization.
|
|
* @throws StandardRpcException Thrown if there was an error with the resolution process
|
|
*/
|
|
private static function resolveExternalPeer(PeerAddress|string $peerAddress, null|PeerAddress|string $identifiedAs=null): Peer
|
|
{
|
|
if(is_string($peerAddress))
|
|
{
|
|
$peerAddress = PeerAddress::fromAddress($peerAddress);
|
|
}
|
|
|
|
if(is_string($identifiedAs))
|
|
{
|
|
$identifiedAs = PeerAddress::fromAddress($identifiedAs);
|
|
}
|
|
|
|
// Resolve the peer from the local database if it exists
|
|
try
|
|
{
|
|
$existingPeer = RegisteredPeerManager::getPeerByAddress($peerAddress);
|
|
}
|
|
catch(DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve the peer due to an internal server error', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
if($existingPeer === null)
|
|
{
|
|
// if the peer doesn't exist, resolve it externally and synchronize it
|
|
try
|
|
{
|
|
$peer = self::getExternalSession($peerAddress->getDomain())->resolvePeer($peerAddress, $identifiedAs);
|
|
}
|
|
catch(RpcException $e)
|
|
{
|
|
throw StandardRpcException::fromRpcException($e);
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve the peer: ' . $e->getMessage(), StandardError::RESOLUTION_FAILED, $e);
|
|
}
|
|
|
|
// Do not synchronize if this is a personal request, there may be information fields that
|
|
// the peer does not want to share with the server
|
|
if($identifiedAs !== null)
|
|
{
|
|
try
|
|
{
|
|
RegisteredPeerManager::synchronizeExternalPeer($peer);
|
|
}
|
|
catch(DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to synchronize the external peer due to an internal server error', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
|
|
return $peer;
|
|
}
|
|
|
|
// if we're not identifying as a personal peer and If the peer exists, but it's outdated, synchronize it
|
|
if($identifiedAs === null && $existingPeer->getUpdated()->getTimestamp() < time() - Configuration::getPoliciesConfiguration()->getPeerSyncInterval())
|
|
{
|
|
try
|
|
{
|
|
$peer = self::getExternalSession($peerAddress->getDomain())->resolvePeer($peerAddress, $identifiedAs);
|
|
}
|
|
catch(RpcException $e)
|
|
{
|
|
throw StandardRpcException::fromRpcException($e);
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve the peer: ' . $e->getMessage(), StandardError::RESOLUTION_FAILED, $e);
|
|
}
|
|
|
|
try
|
|
{
|
|
RegisteredPeerManager::synchronizeExternalPeer($peer);
|
|
}
|
|
catch(DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to synchronize the external peer due to an internal server error', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
return $peer;
|
|
}
|
|
|
|
// If the peer exists and is up to date, return it from our local database instead. (Quicker)
|
|
try
|
|
{
|
|
$informationFields = PeerInformationManager::getFields($existingPeer);
|
|
}
|
|
catch(DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to obtain local information fields about an external peer locally due to an internal server error', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
return new Peer([
|
|
'address' => $existingPeer->getAddress(),
|
|
'information_fields' => $informationFields,
|
|
'flags' => $existingPeer->getFlags(),
|
|
'registered' => $existingPeer->getCreated()->getTimestamp()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Resolves a peer locally based on the given peer address or string identifier.
|
|
*
|
|
* @param PeerAddress|string $peerAddress The peer address or string identifier to be resolved.
|
|
* @param PeerAddress|string|null $identifiedAs Optional. The peer address or string identifier by which the caller is identified
|
|
* @return Peer The resolved peer after synchronization.
|
|
* @throws StandardRpcException Thrown if there was an error with the resolution process
|
|
*/
|
|
private static function resolveLocalPeer(PeerAddress|string $peerAddress, null|PeerAddress|string $identifiedAs=null): Peer
|
|
{
|
|
if(is_string($peerAddress))
|
|
{
|
|
$peerAddress = PeerAddress::fromAddress($peerAddress);
|
|
}
|
|
|
|
if(is_string($identifiedAs))
|
|
{
|
|
$identifiedAs = PeerAddress::fromAddress($identifiedAs);
|
|
}
|
|
|
|
// Resolve the peer
|
|
try
|
|
{
|
|
$peer = RegisteredPeerManager::getPeerByAddress($peerAddress);
|
|
|
|
if($peer === null)
|
|
{
|
|
throw new StandardRpcException('The requested peer was not found', StandardError::PEER_NOT_FOUND);
|
|
}
|
|
}
|
|
catch(DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve the peer: ' . $e->getMessage(), StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Get the initial peer information fields, public always
|
|
$peerInformationFields = PeerInformationManager::getFilteredFields($peer, [PrivacyState::PUBLIC]);
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve peer information: ' . $e->getMessage(), StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
// If there's an identifier, we can resolve more information fields if the target peer has added the caller
|
|
// as a contact or if the caller is a trusted contact
|
|
if($identifiedAs !== null)
|
|
{
|
|
try
|
|
{
|
|
$peerContact = ContactManager::getContact($peer->getUuid(), $identifiedAs);
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve peer because there was an error while trying to retrieve contact information for the peer', StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
|
|
// If it is a contact, what sort of contact? retrieve depending on the contact type
|
|
if($peerContact !== null)
|
|
{
|
|
try
|
|
{
|
|
if($peerContact->getRelationship() === ContactRelationshipType::MUTUAL)
|
|
{
|
|
// Retrieve the mutual information fields
|
|
array_merge($peerInformationFields, PeerInformationManager::getFilteredFields($peer, [PrivacyState::CONTACTS]));
|
|
}
|
|
elseif($peerContact->getRelationship() === ContactRelationshipType::TRUSTED)
|
|
{
|
|
// Retrieve the mutual and trusted information fields
|
|
array_merge($peerInformationFields, PeerInformationManager::getFilteredFields($peer, [PrivacyState::CONTACTS, PrivacyState::TRUSTED]));
|
|
}
|
|
}
|
|
catch (DatabaseOperationException $e)
|
|
{
|
|
throw new StandardRpcException('Failed to resolve peer information: ' . $e->getMessage(), StandardError::INTERNAL_SERVER_ERROR, $e);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new Peer([
|
|
'address' => $peer->getAddress(),
|
|
'information_fields' => $peerInformationFields,
|
|
'flags' => PeerFlags::toString($peer->getFlags()),
|
|
'registered' => $peer->getCreated()->getTimestamp()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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 server address.
|
|
*
|
|
* @return PeerAddress The constructed server address containing the host and domain information.
|
|
*/
|
|
public static function getServerAddress(): PeerAddress
|
|
{
|
|
return new PeerAddress(ReservedUsernames::HOST->value, Configuration::getInstanceConfiguration()->getDomain());
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
);
|
|
}
|
|
} |