2024-08-31 16:36:22 -04:00
< ? php
2024-09-13 13:52:38 -04:00
namespace Socialbox ;
2024-09-24 15:01:46 -04:00
use Exception ;
2024-12-12 04:33:10 -05:00
use InvalidArgumentException ;
2024-09-27 14:21:35 -04:00
use Socialbox\Classes\Configuration ;
2024-12-12 04:33:10 -05:00
use Socialbox\Classes\Cryptography ;
2025-01-03 12:27:04 -05:00
use Socialbox\Classes\DnsHelper ;
2024-10-30 15:28:13 -04:00
use Socialbox\Classes\Logger ;
2024-12-24 19:18:06 -05:00
use Socialbox\Classes\ServerResolver ;
2024-09-27 14:21:35 -04:00
use Socialbox\Classes\Utilities ;
2024-12-12 04:33:10 -05:00
use Socialbox\Classes\Validator ;
2024-12-24 19:18:06 -05:00
use Socialbox\Enums\ReservedUsernames ;
2024-12-12 04:33:10 -05:00
use Socialbox\Enums\SessionState ;
2024-09-13 13:52:38 -04:00
use Socialbox\Enums\StandardError ;
2024-12-12 04:33:10 -05:00
use Socialbox\Enums\StandardHeaders ;
2024-09-13 13:52:38 -04:00
use Socialbox\Enums\StandardMethods ;
2024-12-12 04:33:10 -05:00
use Socialbox\Enums\Types\RequestType ;
2025-01-03 12:27:04 -05:00
use Socialbox\Exceptions\CryptographyException ;
2024-12-12 04:33:10 -05:00
use Socialbox\Exceptions\DatabaseOperationException ;
use Socialbox\Exceptions\RequestException ;
2024-09-24 15:01:46 -04:00
use Socialbox\Exceptions\StandardException ;
2024-12-12 04:33:10 -05:00
use Socialbox\Managers\RegisteredPeerManager ;
use Socialbox\Managers\SessionManager ;
use Socialbox\Objects\ClientRequest ;
use Socialbox\Objects\PeerAddress ;
2025-01-03 12:27:04 -05:00
use Socialbox\Objects\Standard\ServerInformation ;
use Throwable ;
2024-08-31 16:36:22 -04:00
2024-08-31 17:11:25 -04:00
class Socialbox
2024-08-31 16:36:22 -04:00
{
2024-10-31 12:20:16 -04:00
/**
2025-01-03 12:27:04 -05:00
* 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 .
2024-10-31 12:20:16 -04:00
*
* @ return void
*/
2024-12-12 04:33:10 -05:00
public static function handleRequest () : void
{
$requestHeaders = Utilities :: getRequestHeaders ();
if ( ! isset ( $requestHeaders [ StandardHeaders :: REQUEST_TYPE -> value ]))
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: REQUEST_TYPE -> value );
2024-12-12 04:33:10 -05:00
return ;
}
$clientRequest = new ClientRequest ( $requestHeaders , file_get_contents ( 'php://input' ) ? ? null );
2025-01-03 12:27:04 -05:00
// Handle the request type, only `init` and `dhe` are not encrypted using the session's encrypted key
2024-12-12 04:33:10 -05:00
// RPC Requests must be encrypted and signed by the client, vice versa for server responses.
2025-01-03 12:27:04 -05:00
switch ( $clientRequest -> getRequestType ())
2024-12-12 04:33:10 -05:00
{
2025-01-03 12:27:04 -05:00
case RequestType :: INFO :
self :: handleInformationRequest ();
break ;
2024-12-12 04:33:10 -05:00
case RequestType :: INITIATE_SESSION :
self :: handleInitiateSession ( $clientRequest );
break ;
case RequestType :: DHE_EXCHANGE :
self :: handleDheExchange ( $clientRequest );
break ;
case RequestType :: RPC :
self :: handleRpc ( $clientRequest );
break ;
default :
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Invalid Request-Type header' );
2024-12-12 04:33:10 -05:00
}
}
/**
2025-01-03 12:27:04 -05:00
* Handles an information request by setting the appropriate HTTP response code ,
* content type headers , and printing the server information in JSON format .
2024-12-12 04:33:10 -05:00
*
2025-01-03 12:27:04 -05:00
* @ 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 .
2024-12-24 19:18:06 -05:00
*
2025-01-03 12:27:04 -05:00
* @ param ClientRequest $clientRequest The client request containing headers to be validated .
2024-12-24 19:18:06 -05:00
* @ return bool Returns true if all required headers are valid , otherwise false .
2024-12-12 04:33:10 -05:00
*/
2024-12-24 19:18:06 -05:00
private static function validateInitHeaders ( ClientRequest $clientRequest ) : bool
2024-12-12 04:33:10 -05:00
{
2024-12-12 12:58:00 -05:00
if ( ! $clientRequest -> getClientName ())
2024-12-12 05:12:28 -05:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: CLIENT_NAME -> value );
2024-12-24 19:18:06 -05:00
return false ;
2024-12-12 05:12:28 -05:00
}
2024-12-12 12:58:00 -05:00
if ( ! $clientRequest -> getClientVersion ())
2024-12-12 05:12:28 -05:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: CLIENT_VERSION -> value );
return false ;
}
if ( ! $clientRequest -> headerExists ( StandardHeaders :: SIGNING_PUBLIC_KEY ))
{
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: SIGNING_PUBLIC_KEY -> value );
2024-12-24 19:18:06 -05:00
return false ;
2024-12-12 05:12:28 -05:00
}
2025-01-03 12:27:04 -05:00
if ( ! $clientRequest -> headerExists ( StandardHeaders :: ENCRYPTION_PUBLIC_KEY ))
2024-12-12 04:33:10 -05:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: ENCRYPTION_PUBLIC_KEY -> value );
2024-12-24 19:18:06 -05:00
return false ;
2024-12-12 04:33:10 -05:00
}
if ( ! $clientRequest -> headerExists ( StandardHeaders :: IDENTIFY_AS ))
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: IDENTIFY_AS -> value );
2024-12-24 19:18:06 -05:00
return false ;
2024-12-12 04:33:10 -05:00
}
if ( ! Validator :: validatePeerAddress ( $clientRequest -> getHeader ( StandardHeaders :: IDENTIFY_AS )))
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Invalid Identify-As header: ' . $clientRequest -> getHeader ( StandardHeaders :: IDENTIFY_AS ));
2024-12-24 19:18:06 -05:00
return false ;
}
return true ;
}
/**
2025-01-03 12:27:04 -05:00
* 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 .
2024-12-24 19:18:06 -05:00
*
2025-01-03 12:27:04 -05:00
* @ param ClientRequest $clientRequest The incoming client request containing all necessary headers
* and identification information required to initiate the session .
2024-12-24 19:18:06 -05:00
* @ return void
*/
private static function handleInitiateSession ( ClientRequest $clientRequest ) : void
{
2025-01-03 12:27:04 -05:00
// This is only called for the `init` request type
2024-12-24 19:18:06 -05:00
if ( ! self :: validateInitHeaders ( $clientRequest ))
{
2024-12-12 04:33:10 -05:00
return ;
}
2024-12-24 19:18:06 -05:00
// We always accept the client's public key at first
2025-01-03 12:27:04 -05:00
$clientPublicSigningKey = $clientRequest -> getHeader ( StandardHeaders :: SIGNING_PUBLIC_KEY );
$clientPublicEncryptionKey = $clientRequest -> getHeader ( StandardHeaders :: ENCRYPTION_PUBLIC_KEY );
2024-12-24 19:18:06 -05:00
2024-12-23 19:25:21 -05:00
// 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
2024-12-24 19:18:06 -05:00
if ( $clientRequest -> getIdentifyAs () -> getUsername () === ReservedUsernames :: HOST -> value )
2024-12-23 19:25:21 -05:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 403 , StandardError :: FORBIDDEN , 'Unauthorized: Not allowed to identify as the host' );
return ;
2024-12-23 19:25:21 -05:00
}
}
2024-12-24 19:18:06 -05:00
// If the peer is identifying as an external domain
2024-12-23 21:21:30 -05:00
else
{
2024-12-24 19:18:06 -05:00
// Only allow the host to identify as an external peer
if ( $clientRequest -> getIdentifyAs () -> getUsername () !== ReservedUsernames :: HOST -> value )
{
2025-01-03 12:27:04 -05:00
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' );
2024-12-24 19:18:06 -05:00
return ;
}
try
{
2025-01-03 12:27:04 -05:00
// We need to obtain the public key of the host, since we can't trust the client (Use database)
2024-12-24 19:18:06 -05:00
$resolvedServer = ServerResolver :: resolveDomain ( $clientRequest -> getIdentifyAs () -> getDomain ());
2025-01-03 12:27:04 -05:00
// Override the public signing key with the resolved server's public key
// Encryption key can be left as is.
$clientPublicSigningKey = $resolvedServer -> getPublicSigningKey ();
2024-12-24 19:18:06 -05:00
}
catch ( Exception $e )
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 502 , StandardError :: RESOLUTION_FAILED , 'Conflict: Failed to resolve the host domain: ' . $e -> getMessage (), $e );
2024-12-24 19:18:06 -05:00
return ;
}
2024-12-23 21:21:30 -05:00
}
2024-12-12 04:33:10 -05:00
try
{
2025-01-03 12:27:04 -05:00
// Check if we have a registered peer with the same address
2024-12-23 19:25:21 -05:00
$registeredPeer = RegisteredPeerManager :: getPeerByAddress ( $clientRequest -> getIdentifyAs ());
2024-12-12 04:33:10 -05:00
// If the peer is registered, check if it is enabled
if ( $registeredPeer !== null && ! $registeredPeer -> isEnabled ())
{
2025-01-03 12:27:04 -05:00
// 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' );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// Otherwise the peer isn't registered, so we need to register it
2024-12-12 04:33:10 -05:00
else
{
// Check if registration is enabled
if ( ! Configuration :: getRegistrationConfiguration () -> isRegistrationEnabled ())
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 401 , StandardError :: UNAUTHORIZED , 'Unauthorized: Registration is disabled' );
2024-12-12 04:33:10 -05:00
return ;
}
// Register the peer if it is not already registered
2024-12-23 19:25:21 -05:00
$peerUuid = RegisteredPeerManager :: createPeer ( PeerAddress :: fromAddress ( $clientRequest -> getHeader ( StandardHeaders :: IDENTIFY_AS )));
2024-12-12 04:33:10 -05:00
// Retrieve the peer object
$registeredPeer = RegisteredPeerManager :: getPeer ( $peerUuid );
}
2025-01-03 12:27:04 -05:00
// Generate server's encryption keys for this session
$serverEncryptionKey = Cryptography :: generateEncryptionKeyPair ();
// Create the session passing on the registered peer, client name, version, and public keys
$sessionUuid = SessionManager :: createSession ( $registeredPeer , $clientRequest -> getClientName (), $clientRequest -> getClientVersion (), $clientPublicSigningKey , $clientPublicEncryptionKey , $serverEncryptionKey );
// The server responds back with the session UUID & The server's public encryption key as the header
2024-12-12 04:33:10 -05:00
http_response_code ( 201 ); // Created
2025-01-03 12:27:04 -05:00
header ( 'Content-Type: text/plain' );
header ( StandardHeaders :: ENCRYPTION_PUBLIC_KEY -> value . ': ' . $serverEncryptionKey -> getPublicKey ());
2024-12-12 04:33:10 -05:00
print ( $sessionUuid ); // Return the session UUID
}
catch ( InvalidArgumentException $e )
{
2025-01-03 12:27:04 -05:00
// This is usually thrown due to an invalid input
self :: returnError ( 400 , StandardError :: BAD_REQUEST , $e -> getMessage (), $e );
2024-12-12 04:33:10 -05:00
}
catch ( Exception $e )
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 500 , StandardError :: INTERNAL_SERVER_ERROR , 'An internal error occurred while initiating the session' , $e );
2024-12-12 04:33:10 -05:00
}
}
/**
2025-01-03 12:27:04 -05:00
* 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 .
2024-12-12 04:33:10 -05:00
*
2025-01-03 12:27:04 -05:00
* @ param ClientRequest $clientRequest The request object containing headers , body , and session details
* required to perform the DHE exchange .
2024-12-12 04:33:10 -05:00
*
* @ return void
*/
private static function handleDheExchange ( ClientRequest $clientRequest ) : void
{
2025-01-03 12:27:04 -05:00
// Check if the session UUID is set in the headers, bad request if not
2024-12-12 04:33:10 -05:00
if ( ! $clientRequest -> headerExists ( StandardHeaders :: SESSION_UUID ))
{
2025-01-03 12:27:04 -05:00
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 ;
}
2024-12-12 04:33:10 -05:00
2025-01-03 12:27:04 -05:00
if ( empty ( $clientRequest -> getHeader ( StandardHeaders :: SIGNATURE )))
{
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Bad request: The signature is empty' );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// Check if the request body is empty, bad request if so
2024-12-12 04:33:10 -05:00
if ( empty ( $clientRequest -> getRequestBody ()))
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Bad request: The key exchange request body is empty' );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// Check if the session is awaiting a DHE exchange, forbidden if not
$session = $clientRequest -> getSession ();
if ( $session -> getState () !== SessionState :: AWAITING_DHE )
2024-12-12 04:33:10 -05:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 403 , StandardError :: FORBIDDEN , 'Bad request: The session is not awaiting a DHE exchange' );
return ;
}
2024-12-12 04:33:10 -05:00
2025-01-03 12:27:04 -05:00
// 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 );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// STAGE 1: CLIENT -> SERVER
2024-12-12 04:33:10 -05:00
try
{
2025-01-03 12:27:04 -05:00
// Attempt to decrypt the encrypted key passed on from the client using the shared secret
$clientTransportEncryptionKey = Cryptography :: decryptShared ( $clientRequest -> getRequestBody (), $sharedSecret );
2024-12-12 04:33:10 -05:00
}
2025-01-03 12:27:04 -05:00
catch ( CryptographyException $e )
2024-12-12 04:33:10 -05:00
{
2025-01-03 12:27:04 -05:00
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 ;
}
2024-12-12 04:33:10 -05:00
2025-01-03 12:27:04 -05:00
// 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 );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// Now update the session details with all the encryption keys and the state
2024-12-12 04:33:10 -05:00
try
{
2025-01-03 12:27:04 -05:00
SessionManager :: setEncryptionKeys ( $clientRequest -> getSessionUuid (), $sharedSecret , $clientTransportEncryptionKey , $serverTransportEncryptionKey );
SessionManager :: updateState ( $clientRequest -> getSessionUuid (), SessionState :: ACTIVE );
2024-12-12 04:33:10 -05:00
}
catch ( DatabaseOperationException $e )
{
Logger :: getLogger () -> error ( 'Failed to set the encryption key for the session' , $e );
2025-01-03 12:27:04 -05:00
self :: returnError ( 500 , StandardError :: INTERNAL_SERVER_ERROR , 'Failed to set the encryption key for the session' , $e );
2024-12-12 04:33:10 -05:00
return ;
}
2025-01-03 12:27:04 -05:00
// 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 );
2024-12-12 04:33:10 -05:00
}
/**
2025-01-03 12:27:04 -05:00
* 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 .
2024-12-12 04:33:10 -05:00
*
* @ return void
*/
private static function handleRpc ( ClientRequest $clientRequest ) : void
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
// 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
2024-12-12 05:12:28 -05:00
if ( ! $clientRequest -> headerExists ( StandardHeaders :: SESSION_UUID ))
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: SESSION_UUID -> value );
return ;
}
2024-12-12 05:12:28 -05:00
2025-01-03 12:27:04 -05:00
if ( ! $clientRequest -> headerExists ( StandardHeaders :: SIGNATURE ))
{
self :: returnError ( 400 , StandardError :: BAD_REQUEST , 'Missing required header: ' . StandardHeaders :: SIGNATURE -> value );
return ;
}
// Get the client session
$session = $clientRequest -> getSession ();
// Verify if the session is active
if ( $session -> getState () !== SessionState :: ACTIVE )
{
self :: returnError ( 403 , StandardError :: FORBIDDEN , 'Session is not active' );
return ;
}
try
{
// 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' );
2024-12-12 05:12:28 -05:00
return ;
}
2024-09-13 13:52:38 -04:00
try
{
2025-01-03 12:27:04 -05:00
$clientRequests = $clientRequest -> getRpcRequests ( $decryptedContent );
2024-09-13 13:52:38 -04:00
}
2024-12-12 04:33:10 -05:00
catch ( RequestException $e )
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( $e -> getCode (), $e -> getStandardError (), $e -> getMessage ());
2024-09-13 13:52:38 -04:00
return ;
}
2024-12-12 04:33:10 -05:00
Logger :: getLogger () -> verbose ( sprintf ( 'Received %d RPC request(s) from %s' , count ( $clientRequests ), $_SERVER [ 'REMOTE_ADDR' ]));
2024-10-30 15:28:13 -04:00
2024-09-13 13:52:38 -04:00
$results = [];
2024-12-12 04:33:10 -05:00
foreach ( $clientRequests as $rpcRequest )
2024-09-13 13:52:38 -04:00
{
$method = StandardMethods :: tryFrom ( $rpcRequest -> getMethod ());
2024-12-14 00:43:19 -05:00
try
{
$method -> checkAccess ( $clientRequest );
}
catch ( StandardException $e )
{
$response = $e -> produceError ( $rpcRequest );
$results [] = $response -> toArray ();
continue ;
}
2024-09-13 13:52:38 -04:00
if ( $method === false )
{
2024-10-30 15:28:13 -04:00
Logger :: getLogger () -> warning ( 'The requested method does not exist' );
2024-09-24 14:20:49 -04:00
$response = $rpcRequest -> produceError ( StandardError :: RPC_METHOD_NOT_FOUND , 'The requested method does not exist' );
2024-09-24 15:01:46 -04:00
}
2024-09-27 14:21:35 -04:00
else
2024-09-24 15:01:46 -04:00
{
2024-09-27 14:21:35 -04:00
try
{
2024-10-30 15:28:13 -04:00
Logger :: getLogger () -> debug ( sprintf ( 'Processing RPC request for method %s' , $rpcRequest -> getMethod ()));
2024-09-27 14:21:35 -04:00
$response = $method -> execute ( $clientRequest , $rpcRequest );
2024-10-31 15:49:42 -04:00
Logger :: getLogger () -> debug ( sprintf ( '%s method executed successfully' , $rpcRequest -> getMethod ()));
2024-09-27 14:21:35 -04:00
}
catch ( StandardException $e )
{
2024-10-30 15:28:13 -04:00
Logger :: getLogger () -> error ( 'An error occurred while processing the RPC request' , $e );
2024-09-27 14:21:35 -04:00
$response = $e -> produceError ( $rpcRequest );
}
catch ( Exception $e )
{
2024-10-30 15:28:13 -04:00
Logger :: getLogger () -> error ( 'An internal error occurred while processing the RPC request' , $e );
2024-12-12 04:33:10 -05:00
if ( Configuration :: getSecurityConfiguration () -> isDisplayInternalExceptions ())
2024-09-27 14:21:35 -04:00
{
$response = $rpcRequest -> produceError ( StandardError :: INTERNAL_SERVER_ERROR , Utilities :: throwableToString ( $e ));
}
else
{
$response = $rpcRequest -> produceError ( StandardError :: INTERNAL_SERVER_ERROR );
}
}
2024-09-24 15:01:46 -04:00
}
2024-09-13 13:52:38 -04:00
if ( $response !== null )
{
2024-10-30 15:28:13 -04:00
Logger :: getLogger () -> debug ( sprintf ( 'Producing response for method %s' , $rpcRequest -> getMethod ()));
2024-09-27 14:21:35 -04:00
$results [] = $response -> toArray ();
2024-09-13 13:52:38 -04:00
}
}
2024-12-12 04:33:10 -05:00
$response = null ;
2024-09-27 14:21:35 -04:00
if ( count ( $results ) == 0 )
{
2024-12-12 04:33:10 -05:00
$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 )
{
2024-09-27 14:21:35 -04:00
http_response_code ( 204 );
return ;
}
2025-01-03 12:27:04 -05:00
$session = $clientRequest -> getSession ();
2024-12-12 04:33:10 -05:00
try
{
2025-01-03 12:27:04 -05:00
$encryptedResponse = Cryptography :: encryptMessage (
message : $response ,
encryptionKey : $session -> getClientTransportEncryptionKey (),
algorithm : Configuration :: getCryptographyConfiguration () -> getTransportEncryptionAlgorithm ()
);
$signature = Cryptography :: signMessage (
message : $response ,
privateKey : Configuration :: getCryptographyConfiguration () -> getHostPrivateKey ()
);
2024-12-12 04:33:10 -05:00
}
catch ( Exceptions\CryptographyException $e )
2024-09-13 13:52:38 -04:00
{
2025-01-03 12:27:04 -05:00
self :: returnError ( 500 , StandardError :: INTERNAL_SERVER_ERROR , 'Failed to encrypt the server response' , $e );
2024-09-13 13:52:38 -04:00
return ;
}
2024-08-31 16:36:22 -04:00
2024-12-12 04:33:10 -05:00
http_response_code ( 200 );
header ( 'Content-Type: application/octet-stream' );
header ( StandardHeaders :: SIGNATURE -> value . ': ' . $signature );
print ( $encryptedResponse );
2024-09-13 13:52:38 -04:00
}
2025-01-03 12:27:04 -05:00
/**
* 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 ()
);
}
2024-08-31 16:36:22 -04:00
}