Made message signing in Cryptography use SHA512 as the message content for... #1

Closed
netkas wants to merge 421 commits from master into dev
20 changed files with 523 additions and 662 deletions
Showing only changes of commit 29a3d42538 - Show all commits

View file

@ -166,6 +166,10 @@
// value that exceeds this limit, the server will use this limit instead. // value that exceeds this limit, the server will use this limit instead.
// recommendation: 100 // recommendation: 100
$config->setDefault('policies.get_contacts_limit', 100); $config->setDefault('policies.get_contacts_limit', 100);
$config->setDefault('policies.get_encryption_channel_requests_limit', 100);
$config->setDefault('policies.get_encryption_channels_limit', 100);
$config->setDefault('policies.get_encryption_channel_incoming_limit', 100);
$config->setDefault('policies.get_encryption_channel_outgoing_limit', 100);
// Default privacy states for information fields associated with the peer // Default privacy states for information fields associated with the peer
$config->setDefault('policies.default_display_picture_privacy', 'PUBLIC'); $config->setDefault('policies.default_display_picture_privacy', 'PUBLIC');

View file

@ -12,6 +12,10 @@
private int $imageCaptchaExpires; private int $imageCaptchaExpires;
private int $peerSyncInterval; private int $peerSyncInterval;
private int $getContactsLimit; private int $getContactsLimit;
private int $getEncryptionChannelRequestsLimit;
private int $getEncryptionChannelsLimit;
private int $getEncryptionChannelIncomingLimit;
private int $getEncryptionChannelOutgoingLimit;
private PrivacyState $defaultDisplayPicturePrivacy; private PrivacyState $defaultDisplayPicturePrivacy;
private PrivacyState $defaultFirstNamePrivacy; private PrivacyState $defaultFirstNamePrivacy;
private PrivacyState $defaultMiddleNamePrivacy; private PrivacyState $defaultMiddleNamePrivacy;
@ -43,6 +47,10 @@
$this->imageCaptchaExpires = $data['image_captcha_expires']; $this->imageCaptchaExpires = $data['image_captcha_expires'];
$this->peerSyncInterval = $data['peer_sync_interval']; $this->peerSyncInterval = $data['peer_sync_interval'];
$this->getContactsLimit = $data['get_contacts_limit']; $this->getContactsLimit = $data['get_contacts_limit'];
$this->getEncryptionChannelRequestsLimit = $data['get_encryption_channel_requests_limit'];
$this->getEncryptionChannelsLimit = $data['get_encryption_channels_limit'];
$this->getEncryptionChannelIncomingLimit = $data['get_encryption_channel_incoming_limit'];
$this->getEncryptionChannelOutgoingLimit = $data['get_encryption_channel_outgoing_limit'];
$this->defaultDisplayPicturePrivacy = PrivacyState::tryFrom($data['default_display_picture_privacy']) ?? PrivacyState::PRIVATE; $this->defaultDisplayPicturePrivacy = PrivacyState::tryFrom($data['default_display_picture_privacy']) ?? PrivacyState::PRIVATE;
$this->defaultFirstNamePrivacy = PrivacyState::tryFrom($data['default_first_name_privacy']) ?? PrivacyState::PRIVATE; $this->defaultFirstNamePrivacy = PrivacyState::tryFrom($data['default_first_name_privacy']) ?? PrivacyState::PRIVATE;
$this->defaultMiddleNamePrivacy = PrivacyState::tryFrom($data['default_middle_name_privacy']) ?? PrivacyState::PRIVATE; $this->defaultMiddleNamePrivacy = PrivacyState::tryFrom($data['default_middle_name_privacy']) ?? PrivacyState::PRIVATE;
@ -110,6 +118,46 @@
return $this->getContactsLimit; return $this->getContactsLimit;
} }
/**
* Returns the maximum number of encryption channel requests that can be retrieved in a single request
*
* @return int
*/
public function getEncryptionChannelRequestsLimit(): int
{
return $this->getEncryptionChannelRequestsLimit;
}
/**
* Returns the maximum number of encryption channels that can be retrieved in a single request
*
* @return int
*/
public function getEncryptionChannelsLimit(): int
{
return $this->getEncryptionChannelsLimit;
}
/**
* Returns the maximum number of incoming encryption channels that can be retrieved in a single request
*
* @return int
*/
public function getEncryptionChannelIncomingLimit(): int
{
return $this->getEncryptionChannelIncomingLimit;
}
/**
* Returns the maximum number of outgoing encryption channels that can be retrieved in a single request
*
* @return int
*/
public function getEncryptionChannelOutgoingLimit(): int
{
return $this->getEncryptionChannelOutgoingLimit;
}
/** /**
* Returns the default privacy state for the display picture * Returns the default privacy state for the display picture
* *

View file

@ -10,6 +10,7 @@
use Socialbox\Exceptions\DatabaseOperationException; use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\ResolutionException; use Socialbox\Exceptions\ResolutionException;
use Socialbox\Exceptions\RpcException; use Socialbox\Exceptions\RpcException;
use Socialbox\Objects\Client\EncryptionChannelSecret;
use Socialbox\Objects\Client\ExportedSession; use Socialbox\Objects\Client\ExportedSession;
use Socialbox\Objects\Client\SignatureKeyPair; use Socialbox\Objects\Client\SignatureKeyPair;
use Socialbox\Objects\KeyPair; use Socialbox\Objects\KeyPair;
@ -39,6 +40,7 @@
private string $sessionUuid; private string $sessionUuid;
private ?string $defaultSigningKey; private ?string $defaultSigningKey;
private array $signingKeys; private array $signingKeys;
private array $encryptionChannelSecrets;
/** /**
* Constructs a new instance with the specified peer address. * Constructs a new instance with the specified peer address.
@ -78,6 +80,7 @@
$this->serverTransportEncryptionKey = $exportedSession->getServerTransportEncryptionKey(); $this->serverTransportEncryptionKey = $exportedSession->getServerTransportEncryptionKey();
$this->signingKeys = $exportedSession->getSigningKeys(); $this->signingKeys = $exportedSession->getSigningKeys();
$this->defaultSigningKey = $exportedSession->getDefaultSigningKey(); $this->defaultSigningKey = $exportedSession->getDefaultSigningKey();
$this->encryptionChannelSecrets = $exportedSession->getEncryptionChannelSecrets();
// Still solve the server information // Still solve the server information
$this->serverInformation = self::getServerInformation(); $this->serverInformation = self::getServerInformation();
@ -107,6 +110,7 @@
// Set the initial properties // Set the initial properties
$this->signingKeys = []; $this->signingKeys = [];
$this->encryptionChannelSecrets = [];
$this->defaultSigningKey = null; $this->defaultSigningKey = null;
$this->identifiedAs = $identifiedAs; $this->identifiedAs = $identifiedAs;
$this->remoteServer = $server ?? $identifiedAs->getDomain(); $this->remoteServer = $server ?? $identifiedAs->getDomain();
@ -771,6 +775,17 @@
return $this->signingKeys[$uuid] ?? null; return $this->signingKeys[$uuid] ?? null;
} }
/**
* Deletes a signing key from the current instance.
*
* @param string $uuid The UUID of the signing key to be deleted.
* @return void
*/
public function deleteSigningKey(string $uuid): void
{
unset($this->signingKeys[$uuid]);
}
/** /**
* Retrieves the default signing key associated with the current instance. * Retrieves the default signing key associated with the current instance.
* *
@ -797,6 +812,71 @@
$this->defaultSigningKey = $uuid; $this->defaultSigningKey = $uuid;
} }
/**
* Retrieves the encryption channel keys associated with the current instance.
*
* @return EncryptionChannelSecret[] The encryption channel keys.
*/
public function getEncryptionChannelSecrets(): array
{
return $this->encryptionChannelSecrets;
}
/**
* Adds a new encryption channel key to the current instance.
*
* @param EncryptionChannelSecret $key The encryption channel key to be added.
* @return void
*/
public function addEncryptionChannelSecret(EncryptionChannelSecret $key): void
{
$this->encryptionChannelSecrets[$key->getChannelUuid()] = $key;
}
/**
* Removes an encryption channel key from the current instance.
*
* @param string $uuid The UUID of the encryption channel key to be removed.
* @return void
*/
public function removeEncryptionChannelKey(string $uuid): void
{
unset($this->encryptionChannelSecrets[$uuid]);
}
/**
* Retrieves the encryption channel key associated with the specified UUID.
*
* @param string $uuid The UUID of the encryption channel key to be retrieved.
* @return EncryptionChannelSecret|null The encryption channel key associated with the UUID, or null if not found.
*/
public function getEncryptionChannelKey(string $uuid): ?EncryptionChannelSecret
{
return $this->encryptionChannelSecrets[$uuid] ?? null;
}
/**
* Checks if an encryption channel key exists with the specified UUID.
*
* @param string $uuid The UUID of the encryption channel key to check.
* @return bool True if the encryption channel key exists, false otherwise.
*/
public function encryptionChannelKeyExists(string $uuid): bool
{
return isset($this->encryptionChannelSecrets[$uuid]);
}
/**
* Deletes an encryption channel key from the current instance.
*
* @param string $uuid The UUID of the encryption channel key to be deleted.
* @return void
*/
public function deleteEncryptionChannelKey(string $uuid): void
{
unset($this->encryptionChannelSecrets[$uuid]);
}
/** /**
* Exports the current session details into an ExportedSession object. * Exports the current session details into an ExportedSession object.
* *
@ -821,7 +901,8 @@
'client_transport_encryption_key' => $this->clientTransportEncryptionKey, 'client_transport_encryption_key' => $this->clientTransportEncryptionKey,
'server_transport_encryption_key' => $this->serverTransportEncryptionKey, 'server_transport_encryption_key' => $this->serverTransportEncryptionKey,
'default_signing_key' => $this->defaultSigningKey, 'default_signing_key' => $this->defaultSigningKey,
'signing_keys' => array_map(fn(SignatureKeyPair $key) => $key->toArray(), $this->signingKeys) 'signing_keys' => array_map(fn(SignatureKeyPair $key) => $key->toArray(), $this->signingKeys),
'encryption_channel_secrets' => array_map(fn(EncryptionChannelSecret $key) => $key->toArray(), $this->encryptionChannelSecrets)
]); ]);
} }
} }

View file

@ -5,6 +5,7 @@
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use Socialbox\Abstracts\Method; use Socialbox\Abstracts\Method;
use Socialbox\Classes\Validator;
use Socialbox\Enums\StandardError; use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\Standard\InvalidRpcArgumentException; use Socialbox\Exceptions\Standard\InvalidRpcArgumentException;
use Socialbox\Exceptions\Standard\MissingRpcArgumentException; use Socialbox\Exceptions\Standard\MissingRpcArgumentException;
@ -16,7 +17,7 @@
use Socialbox\Socialbox; use Socialbox\Socialbox;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
class ResolvePeerSignature extends Method class ResolveSignature extends Method
{ {
/** /**
@ -30,30 +31,17 @@
throw new MissingRpcArgumentException('peer'); throw new MissingRpcArgumentException('peer');
} }
if(!$rpcRequest->containsParameter('uuid')) if(!$rpcRequest->containsParameter('signature_uuid'))
{ {
throw new MissingRpcArgumentException('uuid'); throw new MissingRpcArgumentException('signature_uuid');
}
elseif(!Validator::validateUuid($rpcRequest->getParameter('signature_uuid')))
{
throw new InvalidRpcArgumentException('signature_uuid', 'Invalid UUID V4');
} }
try return $rpcRequest->produceResponse(Socialbox::resolvePeerSignature(
{ $rpcRequest->getParameter('peer'), $rpcRequest->getParameter('signature_uuid')
$uuid = Uuid::fromString($rpcRequest->getParameter('uuid')); ));
}
catch(InvalidArgumentException $e)
{
throw new InvalidRpcArgumentException('uuid', $e);
}
// Parse the peer address
try
{
$peerAddress = PeerAddress::fromAddress($rpcRequest->getParameter('peer'));
}
catch(InvalidArgumentException $e)
{
throw new InvalidRpcArgumentException('peer', $e);
}
return $rpcRequest->produceResponse(Socialbox::resolvePeerSignature($peerAddress, $uuid->toRfc4122()));
} }
} }

View file

@ -14,7 +14,7 @@
use Socialbox\Objects\RpcRequest; use Socialbox\Objects\RpcRequest;
use Socialbox\Socialbox; use Socialbox\Socialbox;
class VerifyPeerSignature extends Method class VerifySignature extends Method
{ {
/** /**
@ -37,11 +37,6 @@
throw new InvalidRpcArgumentException('signature_uuid', 'Invalid UUID V4'); throw new InvalidRpcArgumentException('signature_uuid', 'Invalid UUID V4');
} }
if(!$rpcRequest->containsParameter('signature_public_key'))
{
throw new MissingRpcArgumentException('signature_public_key');
}
if(!$rpcRequest->containsParameter('signature')) if(!$rpcRequest->containsParameter('signature'))
{ {
throw new MissingRpcArgumentException('signature'); throw new MissingRpcArgumentException('signature');
@ -66,27 +61,25 @@
throw new InvalidRpcArgumentException('peer', $e); throw new InvalidRpcArgumentException('peer', $e);
} }
if($rpcRequest->containsParameter('signature_time')) if($rpcRequest->containsParameter('time'))
{ {
if(!is_numeric($rpcRequest->getParameter('signature_time'))) if(!is_numeric($rpcRequest->getParameter('time')))
{ {
throw new InvalidRpcArgumentException('signature_time', 'Invalid timestamp, must be a Unix Timestamp'); throw new InvalidRpcArgumentException('time', 'Invalid timestamp, must be a Unix Timestamp');
} }
return $rpcRequest->produceResponse(Socialbox::verifyTimedSignature( return $rpcRequest->produceResponse(Socialbox::verifyTimedSignature(
signingPeer: $peerAddress, signingPeer: $peerAddress,
signatureUuid: $rpcRequest->getParameter('signature_uuid'), signatureUuid: $rpcRequest->getParameter('signature_uuid'),
signatureKey: $rpcRequest->getParameter('signature_public_key'),
signature: $rpcRequest->getParameter('signature'), signature: $rpcRequest->getParameter('signature'),
messageHash: $rpcRequest->getParameter('sha512'), messageHash: $rpcRequest->getParameter('sha512'),
signatureTime: (int)$rpcRequest->getParameter('signature_time') signatureTime: (int)$rpcRequest->getParameter('time')
)->value); )->value);
} }
return $rpcRequest->produceResponse(Socialbox::verifySignature( return $rpcRequest->produceResponse(Socialbox::verifySignature(
signingPeer: $peerAddress, signingPeer: $peerAddress,
signatureUuid: $rpcRequest->getParameter('signature_uuid'), signatureUuid: $rpcRequest->getParameter('signature_uuid'),
signatureKey: $rpcRequest->getParameter('signature_public_key'),
signature: $rpcRequest->getParameter('signature'), signature: $rpcRequest->getParameter('signature'),
messageHash: $rpcRequest->getParameter('sha512'), messageHash: $rpcRequest->getParameter('sha512'),
)->value); )->value);

View file

@ -1,31 +0,0 @@
<?php
namespace Socialbox\Classes\StandardMethods\Encryption;
use Exception;
use InvalidArgumentException;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Cryptography;
use Socialbox\Classes\Validator;
use Socialbox\Enums\ReservedUsernames;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\Standard\InvalidRpcArgumentException;
use Socialbox\Exceptions\Standard\MissingRpcArgumentException;
use Socialbox\Exceptions\Standard\StandardRpcException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\EncryptionChannelManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\PeerAddress;
use Socialbox\Objects\RpcRequest;
class EncryptionAcceptChannel extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
}
}

View file

@ -1,317 +0,0 @@
<?php
namespace Socialbox\Classes\StandardMethods\Encryption;
use Exception;
use InvalidArgumentException;
use Socialbox\Abstracts\Method;
use Socialbox\Classes\Configuration;
use Socialbox\Classes\Cryptography;
use Socialbox\Classes\Validator;
use Socialbox\Enums\ReservedUsernames;
use Socialbox\Enums\SigningKeyState;
use Socialbox\Enums\StandardError;
use Socialbox\Exceptions\DatabaseOperationException;
use Socialbox\Exceptions\Standard\InvalidRpcArgumentException;
use Socialbox\Exceptions\Standard\MissingRpcArgumentException;
use Socialbox\Exceptions\Standard\StandardRpcException;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Managers\EncryptionChannelManager;
use Socialbox\Objects\ClientRequest;
use Socialbox\Objects\PeerAddress;
use Socialbox\Objects\RpcRequest;
use Socialbox\Objects\Standard\Signature;
use Socialbox\Socialbox;
class EncryptionCreateChannel extends Method
{
/**
* @inheritDoc
*/
public static function execute(ClientRequest $request, RpcRequest $rpcRequest): ?SerializableInterface
{
// Check the calling peer, if a server is making the request, it must be identified
// Otherwise, we assume the authenticated user is the calling peer
// But a server must provide a UUID. This is to prevent a user from creating a channel with a UUID
$callingPeer = self::getCallingPeer($request, $rpcRequest);
$callingPeerSignature = self::getCallingSignature($callingPeer, $rpcRequest);
$receivingPeer = self::getReceivingPeer($rpcRequest);
$receivingPeerSignature = self::getReceivingSignature($receivingPeer, $rpcRequest);
$channelUuid = self::getChannelUuid($request, $rpcRequest);
// Verify the calling encryption public key
if(!$rpcRequest->containsParameter('calling_encryption_public_key'))
{
throw new MissingRpcArgumentException('calling_encryption_public_key');
}
if(!Cryptography::validatePublicEncryptionKey($rpcRequest->getParameter('calling_encryption_public_key')))
{
throw new InvalidRpcArgumentException('calling_encryption_public_key', 'Invalid calling encryption public key');
}
// Transport Algorithm Validation
if(!$rpcRequest->containsParameter('transport_encryption_algorithm'))
{
throw new MissingRpcArgumentException('transport_encryption_algorithm');
}
if(!Cryptography::isSupportedAlgorithm($rpcRequest->getParameter('transport_encryption_algorithm')))
{
throw new InvalidRpcArgumentException('transport_encryption_algorithm', 'Unsupported Transport Encryption Algorithm');
}
// Create/Import the encryption channel
try
{
$channelUuid = EncryptionChannelManager::createChannel(
callingPeer: $callingPeer,
receivingPeer: $receivingPeer,
signatureUuid: $callingPeerSignature->getUuid(),
signingPublicKey: $callingPeerSignature->getPublicKey(),
encryptionPublicKey: $rpcRequest->getParameter('calling_encryption_public_key'),
transportEncryptionAlgorithm: $rpcRequest->getParameter('transport_encryption_algorithm'),
uuid: $channelUuid
);
}
catch (DatabaseOperationException $e)
{
throw new StandardRpcException('Failed to create the encryption channel', StandardError::INTERNAL_SERVER_ERROR, $e);
}
// If the receiving peer resides on an external server, then we need to tell the external server
// about the encryption channel so that the receiving peer can see it.
if($receivingPeer->getDomain() !== Configuration::getInstanceConfiguration()->getDomain())
{
$rpcClient = Socialbox::getExternalSession($receivingPeer->getDomain());
}
return $rpcRequest->produceResponse($channelUuid);
}
/**
* Returns the PeerAddress of the calling peer, if a server is making a request then the server must provide
* both the UUID of the encryption channel and the PeerAddress of the calling peer to prevent UUID conflicts
*
* Otherwise, the calling peer is assumed to be the authenticated user and no UUID is required
*
* @param ClientRequest $request The full client request
* @param RpcRequest $rpcRequest The focused RPC request
* @return PeerAddress The calling peer
* @throws StandardRpcException If the calling peer cannot be resolved
*/
private static function getCallingPeer(ClientRequest $request, RpcRequest $rpcRequest): PeerAddress
{
if($request->getIdentifyAs() !== null)
{
try
{
// Prevent UUID conflicts if the server is trying to use an UUID that already exists on this server
if (EncryptionChannelManager::channelExists($rpcRequest->getParameter('uuid')))
{
throw new StandardRpcException('UUID Conflict, a channel with this UUID already exists', StandardError::UUID_CONFLICT);
}
}
catch (DatabaseOperationException $e)
{
throw new StandardRpcException('Failed to resolve channel UUID', StandardError::INTERNAL_SERVER_ERROR, $e);
}
if($request->getIdentifyAs()->getUsername() == ReservedUsernames::HOST)
{
throw new StandardRpcException('The identifier cannot be a host', StandardError::BAD_REQUEST);
}
if($request->getIdentifyAs()->getDomain() !== Configuration::getInstanceConfiguration()->getDomain())
{
Socialbox::resolvePeer($request->getIdentifyAs());
}
return $request->getIdentifyAs();
}
try
{
return PeerAddress::fromAddress($request->getPeer()->getAddress());
}
catch(StandardRpcException $e)
{
throw $e;
}
catch(Exception $e)
{
throw new StandardRpcException('The calling peer cannot be resolved', StandardError::INTERNAL_SERVER_ERROR, $e);
}
}
/**
* Resolves and returns the calling peer's signing key, if the calling peer is coming from an external server
* then the signature returned is the resolved signature from the external server, otherwise the signature
* is locally resolved and returned
*
* @param PeerAddress $callingPeer The calling peer
* @param RpcRequest $rpcRequest The focused RPC request
* @return Signature The resolved signing key
* @throws InvalidRpcArgumentException If one or more RPC parameters are invalid
* @throws MissingRpcArgumentException If one or more RPC parameters are missing
* @throws StandardRpcException If the calling signature cannot be resolved
*/
private static function getCallingSignature(PeerAddress $callingPeer, RpcRequest $rpcRequest): Signature
{
// Caller signature verification
if(!$rpcRequest->containsParameter('calling_signature_uuid'))
{
throw new MissingRpcArgumentException('calling_signature_uuid');
}
if(!Validator::validateUuid($rpcRequest->getParameter('calling_signature_uuid')))
{
throw new InvalidRpcArgumentException('calling_signature_uuid', 'Invalid UUID V4');
}
if(!$rpcRequest->containsParameter('calling_signature_public_key'))
{
throw new MissingRpcArgumentException('calling_signature_public_key');
}
if(!Cryptography::validatePublicSigningKey($rpcRequest->getParameter('calling_signature_public_key')))
{
throw new InvalidRpcArgumentException('calling_signature_public_key', 'Invalid Public Key');
}
// Resolve the signature
$resolvedCallingSignature = Socialbox::resolvePeerSignature($callingPeer, $rpcRequest->getParameter('calling_signature_uuid'));
if($resolvedCallingSignature->getPublicKey() !== $rpcRequest->getParameter('calling_signature_public_key'))
{
throw new InvalidRpcArgumentException('calling_signature_public_key', 'Public signing key of the calling peer does not match the resolved signature');
}
if($resolvedCallingSignature->getState() === SigningKeyState::EXPIRED)
{
throw new StandardRpcException('The public signing key of the calling peer has expired', StandardError::EXPIRED);
}
$resolvedSignature = Socialbox::resolvePeerSignature($callingPeer, $rpcRequest->getParameter('calling_signature_uuid'));
if($resolvedSignature === null)
{
throw new StandardRpcException('The calling peer signature could not be resolved', StandardError::NOT_FOUND);
}
return $resolvedSignature;
}
/**
* Returns the PeerAddress of the receiving peer, if the receiving peer is from an external server then the
* receiving peer is resolved and returned, otherwise the receiving peer is locally resolved and returned
*
* @param RpcRequest $rpcRequest The focused RPC request
* @return PeerAddress The receiving peer
* @throws InvalidRpcArgumentException If one or more RPC parameters are invalid
* @throws MissingRpcArgumentException If one or more RPC parameters are missing
* @throws StandardRpcException If the receiving peer cannot be resolved
*/
private static function getReceivingPeer(RpcRequest $rpcRequest): PeerAddress
{
if(!$rpcRequest->containsParameter('receiving_peer'))
{
throw new MissingRpcArgumentException('receiving_peer');
}
try
{
$receivingPeer = PeerAddress::fromAddress($rpcRequest->getParameter('receiving_peer'));
}
catch(InvalidArgumentException $e)
{
throw new InvalidRpcArgumentException('receiving_peer', $e);
}
if($receivingPeer->getUsername() == ReservedUsernames::HOST)
{
throw new InvalidRpcArgumentException('receiving_peer', 'Hosts cannot receive channels');
}
// Resolve the receiving peer if it's from an external server
if($receivingPeer->getDomain() !== Configuration::getInstanceConfiguration()->getDomain())
{
Socialbox::resolvePeer($receivingPeer);
}
return $receivingPeer;
}
/**
* @param PeerAddress $receivingPeer
* @param RpcRequest $rpcRequest
* @return Signature
* @throws InvalidRpcArgumentException
* @throws MissingRpcArgumentException
* @throws StandardRpcException
*/
private static function getReceivingSignature(PeerAddress $receivingPeer, RpcRequest $rpcRequest): Signature
{
// Receiving signature verification
if(!$rpcRequest->containsParameter('receiving_signature_uuid'))
{
throw new MissingRpcArgumentException('receiving_signature_uuid');
}
if(!Validator::validateUuid($rpcRequest->getParameter('receiving_signature_uuid')))
{
throw new InvalidRpcArgumentException('receiving_signature_uuid', 'Invalid UUID V4');
}
if(!$rpcRequest->containsParameter('receiving_signature_public_key'))
{
throw new MissingRpcArgumentException('receiving_signature_public_key');
}
if(!Cryptography::validatePublicSigningKey($rpcRequest->getParameter('receiving_signature_public_key')))
{
throw new InvalidRpcArgumentException('receiving_signature_public_key', 'Invalid Public Key');
}
// Resolve the signature
$resolvedReceivingSignature = Socialbox::resolvePeerSignature($receivingPeer, $rpcRequest->getParameter('receiving_signature_uuid'));
if($resolvedReceivingSignature->getPublicKey() !== $rpcRequest->getParameter('receiving_signature_public_key'))
{
throw new InvalidRpcArgumentException('receiving_signature_public_key', 'Public signing key of the receiving peer does not match the resolved signature');
}
if($resolvedReceivingSignature->getState() === SigningKeyState::EXPIRED)
{
throw new StandardRpcException('The public signing key of the receiving peer has expired', StandardError::EXPIRED);
}
$resolvedSignature = Socialbox::resolvePeerSignature($receivingPeer, $rpcRequest->getParameter('receiving_signature_uuid'));
if($resolvedSignature === null)
{
throw new StandardRpcException('The receiving peer signature could not be resolved', StandardError::NOT_FOUND);
}
return $resolvedSignature;
}
/**
* @param ClientRequest $request
* @param RpcRequest $rpcRequest
* @return string|null
* @throws InvalidRpcArgumentException
* @throws MissingRpcArgumentException
*/
private static function getChannelUuid(ClientRequest $request, RpcRequest $rpcRequest): ?string
{
if($request->getIdentifyAs() !== null)
{
if(!$rpcRequest->containsParameter('uuid'))
{
throw new MissingRpcArgumentException('uuid');
}
if(!Validator::validateUuid($rpcRequest->getParameter('uuid')))
{
throw new InvalidRpcArgumentException('uuid', 'Invalid UUID V4');
}
if(EncryptionChannelManager::channelExists($rpcRequest->getParameter('uuid')))
{
throw new StandardRpcException('UUID Conflict, a channel with this UUID already exists', StandardError::UUID_CONFLICT);
}
return $rpcRequest->getParameter('uuid');
}
return null;
}
}

View file

@ -16,8 +16,11 @@
use Socialbox\Classes\StandardMethods\Core\GetSessionState; use Socialbox\Classes\StandardMethods\Core\GetSessionState;
use Socialbox\Classes\StandardMethods\Core\Ping; use Socialbox\Classes\StandardMethods\Core\Ping;
use Socialbox\Classes\StandardMethods\Core\ResolvePeer; use Socialbox\Classes\StandardMethods\Core\ResolvePeer;
use Socialbox\Classes\StandardMethods\Core\ResolvePeerSignature; use Socialbox\Classes\StandardMethods\Core\ResolveSignature;
use Socialbox\Classes\StandardMethods\Core\VerifyPeerSignature; use Socialbox\Classes\StandardMethods\Core\VerifySignature;
use Socialbox\Classes\StandardMethods\Encryption\EncryptionAcceptChannel;
use Socialbox\Classes\StandardMethods\Encryption\EncryptionCloseChannel;
use Socialbox\Classes\StandardMethods\Encryption\EncryptionCreateChannel;
use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptCommunityGuidelines; use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptCommunityGuidelines;
use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptPrivacyPolicy; use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptPrivacyPolicy;
use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptTermsOfService; use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptTermsOfService;
@ -127,14 +130,6 @@
// MISC // MISC
case GET_STATE = 'getState'; case GET_STATE = 'getState';
// End-to-End channels for communication purposes
case END_TO_END_CREATE_REQUEST = 'e2eCreateRequest';
case END_TO_END_GET_REQUESTS = 'e2eGetRequests';
case END_TO_END_ACCEPT_REQUEST = 'e2eAcceptRequest';
case END_TO_END_REJECT_REQUEST = 'e2eRejectRequest';
case END_TO_END_GET_CHANNELS = 'e2eGetChannels';
case END_TO_END_CLOSE_CHANNEL = 'e2eCloseChannel';
// Messaging methods // Messaging methods
case MESSAGES_GET_INBOX = 'messagesGetInbox'; case MESSAGES_GET_INBOX = 'messagesGetInbox';
case MESSAGES_GET_UNTRUSTED = 'messagesGetUntrusted'; case MESSAGES_GET_UNTRUSTED = 'messagesGetUntrusted';
@ -189,8 +184,8 @@
self::GET_SESSION_STATE => GetSessionState::execute($request, $rpcRequest), self::GET_SESSION_STATE => GetSessionState::execute($request, $rpcRequest),
self::PING => Ping::execute($request, $rpcRequest), self::PING => Ping::execute($request, $rpcRequest),
self::RESOLVE_PEER => ResolvePeer::execute($request, $rpcRequest), self::RESOLVE_PEER => ResolvePeer::execute($request, $rpcRequest),
self::RESOLVE_PEER_SIGNATURE => ResolvePeerSignature::execute($request, $rpcRequest), self::RESOLVE_PEER_SIGNATURE => ResolveSignature::execute($request, $rpcRequest),
self::VERIFY_PEER_SIGNATURE => VerifyPeerSignature::execute($request, $rpcRequest), self::VERIFY_PEER_SIGNATURE => VerifySignature::execute($request, $rpcRequest),
// Server Document Methods // Server Document Methods
self::ACCEPT_PRIVACY_POLICY => AcceptPrivacyPolicy::execute($request, $rpcRequest), self::ACCEPT_PRIVACY_POLICY => AcceptPrivacyPolicy::execute($request, $rpcRequest),
@ -235,18 +230,13 @@
* Checks if the access method is allowed for the given client request. * Checks if the access method is allowed for the given client request.
* *
* @param ClientRequest $clientRequest The client request instance to check access against. * @param ClientRequest $clientRequest The client request instance to check access against.
* @return void
* @throws DatabaseOperationException If an error occurs while checking the database for session information. * @throws DatabaseOperationException If an error occurs while checking the database for session information.
* @throws StandardRpcException If the method is not allowed for the given client request. * @throws StandardRpcException If the method is not allowed for the given client request.
* @return bool
*/ */
public function checkAccess(ClientRequest $clientRequest): void public function checkAccess(ClientRequest $clientRequest): bool
{ {
if(in_array($this, self::getAllowedMethods($clientRequest))) return in_array($this, self::getAllowedMethods($clientRequest));
{
return;
}
throw new StandardRpcException(StandardError::METHOD_NOT_ALLOWED->getMessage(), StandardError::METHOD_NOT_ALLOWED);
} }
/** /**

View file

@ -5,32 +5,32 @@
enum SignatureVerificationStatus : string enum SignatureVerificationStatus : string
{ {
/** /**
* The provided signature does not match the expected signature. * Returned if the signature is invalid
*/ */
case INVALID = 'INVALID'; case INVALID = 'INVALID';
/** /**
* The provided signature was valid, but the public key used to verify the signature was not the expected public key. * Returned if one or more of the parameters are invalid resulting in a failure to verify the signature
*/ */
case PUBLIC_KEY_MISMATCH = 'PUBLIC_KEY_MISMATCH'; case ERROR = 'ERROR';
/** /**
* The provided signature was valid, but the UUID used to verify the signature was not the expected UUID. * Returned if the signing key is not found
*/ */
case UUID_MISMATCH = 'UUID_MISMATCH'; case NOT_FOUND = 'NOT_FOUND';
/** /**
* The provided signature was valid, but the signing key has expired. * Returned if the signature has expired
*/ */
case EXPIRED = 'EXPIRED'; case EXPIRED = 'EXPIRED';
/** /**
* The provided signature was valid but unable to be verified against the peer's known public key. * Returned if there was an error while trying to resolve the signature locally or externally
*/ */
case UNVERIFIED = 'UNVERIFIED'; case RESOLUTION_ERROR = 'RESOLUTION_ERROR';
/** /**
* The provided signature was valid and verified locally and against the peer's known public key successfully. * Returned if the signature has been successfully verified
*/ */
case VERIFIED = 'VERIFIED'; case VERIFIED = 'VERIFIED';
} }

View file

@ -1,5 +1,6 @@
<?php <?php
namespace Socialbox\Managers; namespace Socialbox\Managers;
use InvalidArgumentException; use InvalidArgumentException;
@ -14,7 +15,6 @@
use Socialbox\Objects\Database\ChannelMessageRecord; use Socialbox\Objects\Database\ChannelMessageRecord;
use Socialbox\Objects\Database\EncryptionChannelRecord; use Socialbox\Objects\Database\EncryptionChannelRecord;
use Socialbox\Objects\PeerAddress; use Socialbox\Objects\PeerAddress;
use Socialbox\Objects\Standard\EncryptionChannelMessage;
class EncryptionChannelManager class EncryptionChannelManager
{ {
@ -24,14 +24,13 @@
* @param PeerAddress|string $callingPeer The peer that is creating the channel. * @param PeerAddress|string $callingPeer The peer that is creating the channel.
* @param PeerAddress|string $receivingPeer The peer that is receiving the channel. * @param PeerAddress|string $receivingPeer The peer that is receiving the channel.
* @param string $signatureUuid The UUID of the signature used to create the channel. * @param string $signatureUuid The UUID of the signature used to create the channel.
* @param string $signingPublicKey The public key used for signing.
* @param string $encryptionPublicKey The public key used for encryption. * @param string $encryptionPublicKey The public key used for encryption.
* @param string $transportEncryptionAlgorithm The algorithm used for transport encryption. * @param string $transportEncryptionAlgorithm The algorithm used for transport encryption.
* @return string The UUID of the created channel. * @return string The UUID of the created channel.
* @throws DatabaseOperationException If an error occurs while creating the channel. * @throws DatabaseOperationException If an error occurs while creating the channel.
*/ */
public static function createChannel(PeerAddress|string $callingPeer, PeerAddress|string $receivingPeer, public static function createChannel(PeerAddress|string $callingPeer, PeerAddress|string $receivingPeer,
string $signatureUuid, string $signingPublicKey, string $encryptionPublicKey, string $transportEncryptionAlgorithm, string $signatureUuid, string $encryptionPublicKey, string $transportEncryptionAlgorithm,
?string $uuid=null ?string $uuid=null
): string ): string
{ {
@ -45,10 +44,6 @@
$receivingPeer = PeerAddress::fromAddress($receivingPeer); $receivingPeer = PeerAddress::fromAddress($receivingPeer);
} }
if(!Cryptography::validatePublicSigningKey($signingPublicKey))
{
throw new InvalidArgumentException('Invalid signing public key provided');
}
if(!Cryptography::validatePublicEncryptionKey($encryptionPublicKey)) if(!Cryptography::validatePublicEncryptionKey($encryptionPublicKey))
{ {
@ -68,12 +63,11 @@
try try
{ {
$stmt = Database::getConnection()->prepare('INSERT INTO encryption_channels (uuid, calling_peer, calling_signature_uuid, calling_signature_public_key, calling_encryption_public_key, receiving_peer, transport_encryption_algorithm) VALUES (:uuid, :calling_peer, :calling_signature_uuid, :calling_signature_public_key, :calling_encryption_public_key, :receiving_peer, :transport_encryption_algorithm)'); $stmt = Database::getConnection()->prepare('INSERT INTO encryption_channels (uuid, calling_peer, calling_signature_uuid, calling_encryption_public_key, receiving_peer, transport_encryption_algorithm) VALUES (:uuid, :calling_peer, :calling_signature_uuid, :calling_encryption_public_key, :receiving_peer, :transport_encryption_algorithm)');
$stmt->bindParam(':uuid', $uuid); $stmt->bindParam(':uuid', $uuid);
$callingPeerAddress = $callingPeer->getAddress(); $callingPeerAddress = $callingPeer->getAddress();
$stmt->bindParam(':calling_peer', $callingPeerAddress); $stmt->bindParam(':calling_peer', $callingPeerAddress);
$stmt->bindParam(':calling_signature_uuid', $signatureUuid); $stmt->bindParam(':calling_signature_uuid', $signatureUuid);
$stmt->bindParam(':calling_signature_public_key', $signingPublicKey);
$stmt->bindParam(':calling_encryption_public_key', $encryptionPublicKey); $stmt->bindParam(':calling_encryption_public_key', $encryptionPublicKey);
$receivingPeerAddress = $receivingPeer->getAddress(); $receivingPeerAddress = $receivingPeer->getAddress();
$stmt->bindParam(':receiving_peer', $receivingPeerAddress); $stmt->bindParam(':receiving_peer', $receivingPeerAddress);
@ -96,7 +90,6 @@
* @param int $page The page of channels to retrieve. * @param int $page The page of channels to retrieve.
* @return EncryptionChannelRecord[] The incoming channels for the peer. * @return EncryptionChannelRecord[] The incoming channels for the peer.
* @throws DatabaseOperationException If an error occurs while retrieving the channels. * @throws DatabaseOperationException If an error occurs while retrieving the channels.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array public static function getChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array
{ {
@ -137,7 +130,6 @@
* @param int $page The page of channels to retrieve. * @param int $page The page of channels to retrieve.
* @return EncryptionChannelRecord[] The incoming channels for the peer. * @return EncryptionChannelRecord[] The incoming channels for the peer.
* @throws DatabaseOperationException If an error occurs while retrieving the channels. * @throws DatabaseOperationException If an error occurs while retrieving the channels.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getRequests(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array public static function getRequests(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array
{ {
@ -180,7 +172,6 @@
* @param int $page The page of channels to retrieve. * @param int $page The page of channels to retrieve.
* @return EncryptionChannelRecord[] The incoming channels for the peer. * @return EncryptionChannelRecord[] The incoming channels for the peer.
* @throws DatabaseOperationException If an error occurs while retrieving the channels. * @throws DatabaseOperationException If an error occurs while retrieving the channels.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getIncomingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array public static function getIncomingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array
{ {
@ -221,7 +212,6 @@
* @param int $page The page of channels to retrieve. * @param int $page The page of channels to retrieve.
* @return EncryptionChannelRecord[] The outgoing channels for the specified peer. * @return EncryptionChannelRecord[] The outgoing channels for the specified peer.
* @throws DatabaseOperationException If an error occurs while retrieving the channels. * @throws DatabaseOperationException If an error occurs while retrieving the channels.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getOutgoingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array public static function getOutgoingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array
{ {
@ -281,23 +271,19 @@
* *
* @param string $channelUuid The UUID of the channel to accept. * @param string $channelUuid The UUID of the channel to accept.
* @param string $signatureUuid The UUID of the signature used to create the channel. * @param string $signatureUuid The UUID of the signature used to create the channel.
* @param string $signaturePublicKey The public key used for signing.
* @param string $encryptionPublicKey The public key used for encryption. * @param string $encryptionPublicKey The public key used for encryption.
* @param string $transportEncryptionAlgorithm The algorithm used for transport encryption.
* @param string $encryptedTransportEncryptionKey The encrypted transport encryption key. * @param string $encryptedTransportEncryptionKey The encrypted transport encryption key.
* @throws DatabaseOperationException If an error occurs while accepting the channel. * @throws DatabaseOperationException If an error occurs while accepting the channel.
*/ */
public static function acceptChannel(string $channelUuid, string $signatureUuid, string $signaturePublicKey, string $encryptionPublicKey, string $transportEncryptionAlgorithm, string $encryptedTransportEncryptionKey): void public static function acceptChannel(string $channelUuid, string $signatureUuid, string $encryptionPublicKey, string $encryptedTransportEncryptionKey): void
{ {
try try
{ {
$stmt = Database::getConnection()->prepare('UPDATE encryption_channels SET state=:state, receiving_signature_uuid=:receiving_signature_uuid, receiving_signature_public_key=:receiving_signature_public_key, receiving_encryption_public_key=:receiving_encryption_public_key, transport_encryption_algorithm=:transport_encryption_algorithm, transport_encryption_key=:transport_encryption_key WHERE uuid=:uuid'); $stmt = Database::getConnection()->prepare('UPDATE encryption_channels SET state=:state, receiving_signature_uuid=:receiving_signature_uuid, receiving_encryption_public_key=:receiving_encryption_public_key, transport_encryption_algorithm=:transport_encryption_algorithm, transport_encryption_key=:transport_encryption_key WHERE uuid=:uuid');
$state = EncryptionChannelState::OPENED->value; $state = EncryptionChannelState::OPENED->value;
$stmt->bindParam(':state', $state); $stmt->bindParam(':state', $state);
$stmt->bindParam(':receiving_signature_uuid', $signatureUuid); $stmt->bindParam(':receiving_signature_uuid', $signatureUuid);
$stmt->bindParam(':receiving_signature_public_key', $signaturePublicKey);
$stmt->bindParam(':receiving_encryption_public_key', $encryptionPublicKey); $stmt->bindParam(':receiving_encryption_public_key', $encryptionPublicKey);
$stmt->bindParam(':transport_encryption_algorithm', $transportEncryptionAlgorithm);
$stmt->bindParam(':transport_encryption_key', $encryptedTransportEncryptionKey); $stmt->bindParam(':transport_encryption_key', $encryptedTransportEncryptionKey);
$stmt->bindParam(':uuid', $channelUuid); $stmt->bindParam(':uuid', $channelUuid);
$stmt->execute(); $stmt->execute();
@ -314,7 +300,6 @@
* @param string $channelUuid The UUID of the channel to retrieve. * @param string $channelUuid The UUID of the channel to retrieve.
* @return EncryptionChannelRecord|null The record of the encryption channel. Null if the channel does not exist. * @return EncryptionChannelRecord|null The record of the encryption channel. Null if the channel does not exist.
* @throws DatabaseOperationException If an error occurs while retrieving the channel. * @throws DatabaseOperationException If an error occurs while retrieving the channel.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getChannel(string $channelUuid): ?EncryptionChannelRecord public static function getChannel(string $channelUuid): ?EncryptionChannelRecord
{ {
@ -343,7 +328,7 @@
* *
* @param string $channelUuid The UUID of the channel to delete. * @param string $channelUuid The UUID of the channel to delete.
* @return void * @return void
*@throws DatabaseOperationException If an error occurs while deleting the channel. * @throws DatabaseOperationException If an error occurs while deleting the channel.
*/ */
public static function deleteChannel(string $channelUuid): void public static function deleteChannel(string $channelUuid): void
{ {
@ -503,7 +488,6 @@
* @param string $messageUuid The UUID of the message to retrieve. * @param string $messageUuid The UUID of the message to retrieve.
* @return ChannelMessageRecord|null The message with the specified UUID. Null if the message does not exist. * @return ChannelMessageRecord|null The message with the specified UUID. Null if the message does not exist.
* @throws DatabaseOperationException If an error occurs while retrieving the message. * @throws DatabaseOperationException If an error occurs while retrieving the message.
* @throws \DateMalformedStringException If the created date is not a valid date string.
*/ */
public static function getData(string $channelUuid, string $messageUuid): ?ChannelMessageRecord public static function getData(string $channelUuid, string $messageUuid): ?ChannelMessageRecord
{ {
@ -531,23 +515,29 @@
/** /**
* Imports the specified message data into the database. * Imports the specified message data into the database.
* *
* @param EncryptionChannelMessage|ChannelMessageRecord $message The message data to import. * @param ChannelMessageRecord $message The message data to import.
* @throws DatabaseOperationException If an error occurs while importing the message. * @throws DatabaseOperationException If an error occurs while importing the message.
*/ */
public static function importData(EncryptionChannelMessage|ChannelMessageRecord $message): void public static function importData(ChannelMessageRecord $message): void
{ {
$data = $message->toArray();
try try
{ {
$stmt = Database::getConnection()->prepare('INSERT INTO channel_com (uuid, channel_uuid, recipient, message, signature, received, timestamp) VALUES (:uuid, :channel_uuid, :recipient, :message, :signature, :received, :timestamp)'); $stmt = Database::getConnection()->prepare('INSERT INTO channel_com (uuid, channel_uuid, recipient, message, signature, received, timestamp) VALUES (:uuid, :channel_uuid, :recipient, :message, :signature, :received, :timestamp)');
$stmt->bindParam(':uuid', $data['uuid']); $uuid = $message->getUuid();
$stmt->bindParam(':channel_uuid', $data['channel_uuid']); $stmt->bindParam(':uuid', $uuid);
$stmt->bindParam(':recipient', $data['recipient']); $channelUuid = $message->getChannelUuid();
$stmt->bindParam(':message', $data['message']); $stmt->bindParam(':channel_uuid', $channelUuid);
$stmt->bindParam(':signature', $data['signature']); $recipient = $message->getRecipient()->value;
$stmt->bindParam(':received', $data['received']); $stmt->bindParam(':recipient', $recipient);
$stmt->bindParam(':timestamp', $data['timestamp']); $messageData = $message->getMessage();
$stmt->bindParam(':message', $messageData);
$signature = $message->getSignature();
$stmt->bindParam(':signature', $signature);
$received = $message->isReceived() ? 1 : 0;
$stmt->bindParam(':received', $received);
$timestamp = $message->getTimestamp();
$stmt->bindParam(':timestamp', $timestamp);
$stmt->execute(); $stmt->execute();
} }
catch(PDOException $e) catch(PDOException $e)

View file

@ -34,8 +34,8 @@
try try
{ {
$statement = Database::getConnection()->prepare('SELECT COUNT(*) FROM peers WHERE username=?'); $statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM peers WHERE username=:username AND server='host'");
$statement->bindParam(1, $username); $statement->bindParam(':username', $username);
$statement->execute(); $statement->execute();
$result = $statement->fetchColumn(); $result = $statement->fetchColumn();
@ -163,9 +163,9 @@
try try
{ {
$statement = Database::getConnection()->prepare('SELECT * FROM peers WHERE username=? AND server=?'); $statement = Database::getConnection()->prepare('SELECT * FROM peers WHERE username=:username AND server=:server');
$username = $address->getUsername(); $username = $address->getUsername();
$statement->bindParam(1, $username); $statement->bindParam(':username', $username);
$server = $address->getDomain(); $server = $address->getDomain();
// Convert to 'host' if the domain is the same as the server's host // Convert to 'host' if the domain is the same as the server's host
@ -174,7 +174,7 @@
$server = 'host'; $server = 'host';
} }
$statement->bindParam(2, $server); $statement->bindParam(':server', $server);
$statement->execute(); $statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC); $result = $statement->fetch(PDO::FETCH_ASSOC);

View file

@ -0,0 +1,129 @@
<?php
namespace Socialbox\Objects\Client;
use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\PeerAddress;
class EncryptionChannelSecret implements SerializableInterface
{
private string $channelUuid;
private PeerAddress $receiver;
private string $signatureUuid;
private string $publicEncryptionKey;
private string $privateEncryptionKey;
private string $transportEncryptionAlgorithm;
private ?string $transportEncryptionKey;
/**
* Public constructor
*
* @param array $data The data to create the object
*/
public function __construct(array $data)
{
$this->channelUuid = $data['uuid'];
$this->receiver = PeerAddress::fromAddress($data['receiver']);
$this->signatureUuid = $data['signature_uuid'];
$this->publicEncryptionKey = $data['public_encryption_key'];
$this->privateEncryptionKey = $data['private_encryption_key'];
$this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm'];
$this->transportEncryptionKey = $data['transport_encryption_key'] ?? null;
}
/**
* Returns the UUID of the key pair
*
* @return string The UUID of the key pair
*/
public function getChannelUuid(): string
{
return $this->channelUuid;
}
/**
* @return PeerAddress
*/
public function getReceiver(): PeerAddress
{
return $this->receiver;
}
/**
* Returns the UUID of the signature
*
* @return string The UUID of the signature
*/
public function getSignatureUuid(): string
{
return $this->signatureUuid;
}
/**
* Returns the public key of the key pair
*
* @return string The public key of the key pair
*/
public function getPublicEncryptionKey(): string
{
return $this->publicEncryptionKey;
}
/**
* Returns the private key of the key pair
*
* @return string The private key of the key pair
*/
public function getPrivateEncryptionKey(): string
{
return $this->privateEncryptionKey;
}
/**
* @return string
*/
public function getTransportEncryptionAlgorithm(): string
{
return $this->transportEncryptionAlgorithm;
}
/**
* @return string|null
*/
public function getTransportEncryptionKey(): ?string
{
return $this->transportEncryptionKey;
}
/**
* @param string|null $transportEncryptionKey
*/
public function setTransportEncryptionKey(?string $transportEncryptionKey): void
{
$this->transportEncryptionKey = $transportEncryptionKey;
}
/**
* @inheritDoc
*/
public static function fromArray(array $data): EncryptionChannelSecret
{
return new self($data);
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'uuid' => $this->channelUuid,
'receiver' => $this->receiver->getAddress(),
'signature_uuid' => $this->signatureUuid,
'public_key' => $this->publicEncryptionKey,
'private_key' => $this->privateEncryptionKey,
'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm,
'transport_encryption_key' => $this->transportEncryptionKey
];
}
}

View file

@ -29,6 +29,10 @@
* @var SignatureKeyPair[] * @var SignatureKeyPair[]
*/ */
private array $signingKeys; private array $signingKeys;
/**
* @var EncryptionChannelSecret[]
*/
private array $encryptionChannelSecrets;
/** /**
* Constructor method to initialize class properties from the provided data array. * Constructor method to initialize class properties from the provided data array.
@ -62,6 +66,7 @@
$this->serverTransportEncryptionKey = $data['server_transport_encryption_key']; $this->serverTransportEncryptionKey = $data['server_transport_encryption_key'];
$this->defaultSigningKey = $data['default_signing_key'] ?? null; $this->defaultSigningKey = $data['default_signing_key'] ?? null;
$this->signingKeys = array_map(fn($key) => SignatureKeyPair::fromArray($key), $data['signing_keys']); $this->signingKeys = array_map(fn($key) => SignatureKeyPair::fromArray($key), $data['signing_keys']);
$this->encryptionChannelSecrets = array_map(fn($key) => EncryptionChannelSecret::fromArray($key), $data['encryption_channel_secrets']);
} }
/** /**
@ -234,6 +239,60 @@
return $this->signingKeys; return $this->signingKeys;
} }
/**
* Retrieves the encrypted channel keys associated with the current instance.
*
* @return EncryptionChannelSecret[] The encrypted channel keys.
*/
public function getEncryptionChannelSecrets(): array
{
return $this->encryptionChannelSecrets;
}
/**
* Retrieves the signing key associated with the provided UUID.
*
* @param string $uuid The UUID of the signing key.
* @return SignatureKeyPair|null The signing key.
*/
public function getEncryptionChannelSecret(string $uuid): ?EncryptionChannelSecret
{
return $this->encryptionChannelSecrets[$uuid] ?? null;
}
/**
* Adds a new signing key to the current instance.
*
* @param EncryptionChannelSecret $key The signing key to add.
* @return void
*/
public function addEncryptionChannelSecret(EncryptionChannelSecret $key): void
{
$this->encryptionChannelSecrets[$key->getChannelUuid()] = $key;
}
/**
* Removes the signing key associated with the provided UUID.
*
* @param string $uuid The UUID of the signing key to remove.
* @return void
*/
public function removeEncryptionChannelSecret(string $uuid): void
{
unset($this->encryptionChannelSecrets[$uuid]);
}
/**
* Checks if a signing key exists for the provided UUID.
*
* @param string $uuid The UUID of the signing key.
* @return bool True if the signing key exists, false otherwise.
*/
public function encryptionChannelSecretExists(string $uuid): bool
{
return isset($this->encryptionChannelSecrets[$uuid]);
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -256,7 +315,8 @@
'client_transport_encryption_key' => $this->clientTransportEncryptionKey, 'client_transport_encryption_key' => $this->clientTransportEncryptionKey,
'server_transport_encryption_key' => $this->serverTransportEncryptionKey, 'server_transport_encryption_key' => $this->serverTransportEncryptionKey,
'default_signing_key' => $this->defaultSigningKey, 'default_signing_key' => $this->defaultSigningKey,
'signing_keys' => array_map(fn($key) => $key->toArray(), $this->signingKeys) 'signing_keys' => array_map(fn($key) => $key->toArray(), $this->signingKeys),
'encryption_channel_secrets' => array_map(fn($key) => $key->toArray(), $this->encryptionChannelSecrets),
]; ];
} }

View file

@ -170,7 +170,6 @@
* *
* @return SessionRecord|null Returns the associated SessionRecord if the session UUID exists, or null if no session UUID is set. * @return SessionRecord|null Returns the associated SessionRecord if the session UUID exists, or null if no session UUID is set.
* @throws DatabaseOperationException Thrown if an error occurs while retrieving the session. * @throws DatabaseOperationException Thrown if an error occurs while retrieving the session.
* @throws StandardRpcException Thrown if the session UUID is invalid.
*/ */
public function getSession(): ?SessionRecord public function getSession(): ?SessionRecord
{ {
@ -187,7 +186,6 @@
* *
* @return PeerDatabaseRecord|null Returns the associated RegisteredPeerRecord if available, or null if no session exists. * @return PeerDatabaseRecord|null Returns the associated RegisteredPeerRecord if available, or null if no session exists.
* @throws DatabaseOperationException Thrown if an error occurs while retrieving the peer. * @throws DatabaseOperationException Thrown if an error occurs while retrieving the peer.
* @throws StandardRpcException Thrown if the session UUID is invalid.
*/ */
public function getPeer(): ?PeerDatabaseRecord public function getPeer(): ?PeerDatabaseRecord
{ {
@ -201,6 +199,36 @@
return RegisteredPeerManager::getPeer($session->getPeerUuid()); return RegisteredPeerManager::getPeer($session->getPeerUuid());
} }
/**
* Returns the Peer Database Record of the identified peer of the request
*
* @return PeerDatabaseRecord|null The Peer Database Record of the identified peer or null if not set
* @throws DatabaseOperationException Thrown if an error occurs while retrieving the peer.
*/
public function getIdentifiedAsPeer(): ?PeerDatabaseRecord
{
$identifiedAs = $this->getIdentifyAs();
if($identifiedAs === null)
{
return null;
}
return RegisteredPeerManager::getPeerByAddress($identifiedAs);
}
/**
* Returns whether the request is external or not. As in, if the request is coming from server rather than
* a client.
*
* @return bool True if the request is external, false otherwise.
* @throws DatabaseOperationException Thrown if an error occurs while retrieving the peer.
* @throws StandardRpcException Thrown if the session UUID is invalid.
*/
public function isExternal(): bool
{
return $this->getPeer()->isExternal();
}
/** /**
* Retrieves the signature value. * Retrieves the signature value.
* *

View file

@ -30,7 +30,6 @@
* - 'signature' (string): The signature. * - 'signature' (string): The signature.
* - 'received' (bool): Whether the message has been received. * - 'received' (bool): Whether the message has been received.
* - 'timestamp' (int|string|\DateTime): The timestamp of the message. * - 'timestamp' (int|string|\DateTime): The timestamp of the message.
* @throws DateMalformedStringException If the timestamp is a string that cannot be parsed.
*/ */
public function __construct(array $data) public function __construct(array $data)
{ {

View file

@ -17,7 +17,6 @@
private string $callingEncryptionPublicKey; private string $callingEncryptionPublicKey;
private PeerAddress $receivingPeer; private PeerAddress $receivingPeer;
private ?string $receivingSignatureUuid; private ?string $receivingSignatureUuid;
private ?string $receivingSignaturePublicKey;
private ?string $receivingEncryptionPublicKey; private ?string $receivingEncryptionPublicKey;
private string $transportEncryptionAlgorithm; private string $transportEncryptionAlgorithm;
private ?string $transportEncryptionKey; private ?string $transportEncryptionKey;
@ -28,7 +27,6 @@
* Public Constructor for the encryption channel record * Public Constructor for the encryption channel record
* *
* @param array $data * @param array $data
* @throws \DateMalformedStringException
*/ */
public function __construct(array $data) public function __construct(array $data)
{ {
@ -78,7 +76,6 @@
} }
$this->receivingSignatureUuid = $data['receiving_signature_uuid'] ?? null; $this->receivingSignatureUuid = $data['receiving_signature_uuid'] ?? null;
$this->receivingSignaturePublicKey = $data['receiving_signature_public_key'] ?? null;
$this->receivingEncryptionPublicKey = $data['receiving_encryption_public_key'] ?? null; $this->receivingEncryptionPublicKey = $data['receiving_encryption_public_key'] ?? null;
$this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm']; $this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm'];
$this->transportEncryptionKey = $data['transport_encryption_key'] ?? null; $this->transportEncryptionKey = $data['transport_encryption_key'] ?? null;
@ -169,16 +166,6 @@
return $this->receivingSignatureUuid; return $this->receivingSignatureUuid;
} }
/**
* Returns the public key of the signing keypair that the receiver is using
*
* @return string|null
*/
public function getReceivingSignaturePublicKey(): ?string
{
return $this->receivingSignaturePublicKey;
}
/** /**
* Returns the public key of the encryption keypair that the receiver is using * Returns the public key of the encryption keypair that the receiver is using
* *
@ -249,7 +236,6 @@
'calling_encryption_public_key' => $this->callingEncryptionPublicKey, 'calling_encryption_public_key' => $this->callingEncryptionPublicKey,
'receiving_peer' => $this->receivingPeer->getAddress(), 'receiving_peer' => $this->receivingPeer->getAddress(),
'receiving_signature_uuid' => $this->receivingSignatureUuid, 'receiving_signature_uuid' => $this->receivingSignatureUuid,
'receiving_signature_public_key' => $this->receivingSignaturePublicKey,
'receiving_encryption_public_key' => $this->receivingEncryptionPublicKey, 'receiving_encryption_public_key' => $this->receivingEncryptionPublicKey,
'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm, 'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm,
'transport_encryption_key' => $this->transportEncryptionKey, 'transport_encryption_key' => $this->transportEncryptionKey,

View file

@ -83,7 +83,7 @@
return false; return false;
} }
return $this->username === ReservedUsernames::HOST->value; return $this->domain !== Configuration::getInstanceConfiguration()->getDomain();
} }
/** /**

View file

@ -87,6 +87,26 @@
return isset($this->parameters[$parameter]); return isset($this->parameters[$parameter]);
} }
/**
* Checks if the parameters exist within the RPC request
*
* @param array $parameters The parameters to check
* @param bool $nullAllowed True if the parameter value can be null, False otherwise.
* @return bool True if the parameters exist, False otherwise.
*/
public function containsParameters(array $parameters, bool $nullAllowed=false): bool
{
foreach($parameters as $parameter)
{
if(!$this->containsParameter($parameter, $nullAllowed))
{
return false;
}
}
return true;
}
/** /**
* Returns the parameter value from the RPC request * Returns the parameter value from the RPC request
* *

View file

@ -76,53 +76,6 @@
return $uuid; return $uuid;
} }
/**
* Creates a new encryption channel with the given peer, using the signature UUID to identify the calling peer.
* This method is a wrapper around the encryptionCreateChannel method, and is used to simplify the process of
* creating a new encryption channel. The signature UUID is used to identify the calling peer, and the public key
* of the signature is used as the encryption key.
*
* @param PeerAddress|string $receivingPeer
* @param string $signatureUuid
* @param string $transportEncryptionAlgorithm
* @return string
* @throws CryptographyException
* @throws RpcException
*/
public function newEncryptionChannel(PeerAddress|string $receivingPeer, string $signatureUuid, string $transportEncryptionAlgorithm): string
{
if(!$this->signingKeyExists($signatureUuid))
{
throw new InvalidArgumentException('The signature UUID does not exist in the client');
}
$signature = $this->getSigningKey($signatureUuid);
if($signature === null)
{
throw new InvalidArgumentException('The signature UUID does not exist in the client');
}
$encryptionKeypair = Cryptography::generateEncryptionKeyPair();
$channelUuid = $this->encryptionCreateChannel(
receivingPeer: $receivingPeer,
signatureUUid: $signature->getUuid(),
encryptionPublicKey: $encryptionKeypair->getPublicKey(),
transportEncryptionAlgorithm: $transportEncryptionAlgorithm
);
$this->addEncryptionChannelSecret(new EncryptionChannelSecret([
'uuid' => $channelUuid,
'receiver' => $receivingPeer->getAddress(),
'signature_uuid' => $signature->getUuid(),
'public_encryption_key' => $encryptionKeypair->getPublicKey(),
'private_encryption_key' => $encryptionKeypair->getPrivateKey(),
'transport_encryption_algorithm' => $transportEncryptionAlgorithm,
'transport_encryption_key' => null
]));
return $channelUuid;
}
/** /**
* Adds a new peer to the AddressBook, returns True upon success or False if the contact already exists in * Adds a new peer to the AddressBook, returns True upon success or False if the contact already exists in
* the address book. * the address book.
@ -459,74 +412,6 @@
)->getResponse()->getResult()) ?? SignatureVerificationStatus::INVALID; )->getResponse()->getResult()) ?? SignatureVerificationStatus::INVALID;
} }
public function encryptionAcceptChannel(string $channelUuid, string $signatureUuid, string $encryptionPublicKey, string $encryptedTransportEncryptionKey, null|PeerAddress|string $identifiedAs=null): true
{
if($identifiedAs instanceof PeerAddress)
{
$identifiedAs = $identifiedAs->getAddress();
}
return $this->sendRequest(
new RpcRequest(StandardMethods::ENCRYPTION_ACCEPT_CHANNEL, parameters: [
'channel_uuid' => $channelUuid,
'signature_uuid' => $signatureUuid,
'encryption_public_key' => $encryptionPublicKey,
'encrypted_transport_encryption_key' => $encryptedTransportEncryptionKey
]),
identifiedAs: $identifiedAs
)->getResponse()->getResult();
}
public function encryptionCloseChannel(string $channelUuid, null|PeerAddress|string $identifiedAs=null): true
{
if($identifiedAs instanceof PeerAddress)
{
$identifiedAs = $identifiedAs->getAddress();
}
return $this->sendRequest(
new RpcRequest(StandardMethods::ENCRYPTION_CLOSE_CHANNEL, parameters: [
'channel_uuid' => $channelUuid,
]),
identifiedAs: $identifiedAs
)->getResponse()->getResult();
}
/**
* Accepts an encryption channel request, returns True if the channel was accepted.
*
* @param PeerAddress|string $receivingPeer The address of the receiving peer that the channel is being requested to
* @param string $signatureUUid The UUID of the calling signature
* @param string $encryptionPublicKey The public key of the calling encryption key
* @param string $transportEncryptionAlgorithm The transport encryption algorithm to use
* @param string|null $identifyAs Optional. The address of the peer to identify as
* @param string|null $channelUuid Optional. If calling to an external server, the server must provide the other server the UUID to use
* @return string Returns True if the channel was accepted
* @throws RpcException Thrown if there was an error with the RPC request
*/
public function encryptionCreateChannel(
PeerAddress|string $receivingPeer, string $signatureUUid,
string $encryptionPublicKey, string $transportEncryptionAlgorithm='xchacha20',
?string $identifyAs=null, ?string $channelUuid=null
): string
{
if($receivingPeer instanceof PeerAddress)
{
$receivingPeer = $receivingPeer->getAddress();
}
return $this->sendRequest(
new RpcRequest(StandardMethods::ENCRYPTION_CREATE_CHANNEL, parameters: [
'receiving_peer' => $receivingPeer,
'signature_uuid' => $signatureUUid,
'encryption_public_key' => $encryptionPublicKey,
'transport_encryption_algorithm' => $transportEncryptionAlgorithm,
'channel_uuid' => $channelUuid
]),
identifiedAs: $identifyAs
)->getResponse()->getResult();
}
/** /**
* Accepts the community guidelines, returns True if the guidelines were accepted. * Accepts the community guidelines, returns True if the guidelines were accepted.
* *

View file

@ -200,7 +200,7 @@
// Prevent the peer from identifying as the host unless it's coming from an external domain // Prevent the peer from identifying as the host unless it's coming from an external domain
if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value) if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value)
{ {
self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as the host'); self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: Not allowed to identify as the host');
return; return;
} }
} }
@ -225,7 +225,7 @@
} }
catch (Exception $e) catch (Exception $e)
{ {
self::returnError(502, StandardError::RESOLUTION_FAILED, 'Conflict: Failed to resolve the host domain: ' . $e->getMessage(), $e); self::returnError(502, StandardError::RESOLUTION_FAILED, 'Conflict: Failed to resolve incoming host, ' . $e->getMessage(), $e);
return; return;
} }
} }
@ -258,14 +258,6 @@
RegisteredPeerManager::enablePeer($peerUuid); RegisteredPeerManager::enablePeer($peerUuid);
$registeredPeer = RegisteredPeerManager::getPeer($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) if($registeredPeer === null)
@ -355,7 +347,16 @@
return; return;
} }
try
{
$session = $clientRequest->getSession(); $session = $clientRequest->getSession();
}
catch (DatabaseOperationException $e)
{
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to retrieve session', $e);
return;
}
if($session === null) if($session === null)
{ {
self::returnError(404, StandardError::SESSION_NOT_FOUND, 'Session not found'); self::returnError(404, StandardError::SESSION_NOT_FOUND, 'Session not found');
@ -525,36 +526,41 @@
{ {
try try
{ {
$peer = $clientRequest->getPeer(); $hostPeer = $clientRequest->getPeer();
} }
catch (DatabaseOperationException $e) catch (DatabaseOperationException $e)
{ {
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to resolve host peer', $e); self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to resolve host peer', $e);
}
// First check if the client is identifying as the host
if($peer->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; return;
} }
if($clientRequest->getIdentifyAs()->getDomain() != $) // If for some reason the host peer was not found, this shouldn't happen.
if($hostPeer === null)
{
self::returnError(404, StandardError::INTERNAL_SERVER_ERROR, 'The host peer was not found in the system');
return;
}
// First check if the client is identifying as the host
elseif($hostPeer->getAddress() !== ReservedUsernames::HOST->value)
{
self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: External servers must identify as a host');
return;
}
// Secondly, check if the peer's server belongs to another server than the server is identified as
elseif($hostPeer->getServer() !== $clientRequest->getIdentifyAs()->getDomain())
{
self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: Not allowed to identify as a peer outside from the host server');
return;
}
// Synchronize the peer // Synchronize the peer
try try
{ {
self::resolvePeer($clientRequest->getIdentifyAs()); self::resolvePeer($clientRequest->getIdentifyAs());
} }
catch (DatabaseOperationException $e) catch (StandardRpcException $e)
{ {
self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to synchronize external peer', $e); throw new ResolutionException(sprintf('Failed to resolve peer %s: %s', $clientRequest->getIdentifyAs()->getAddress(), $e->getMessage()), $e->getCode(), $e);
return;
}
catch (Exception $e)
{
throw new ResolutionException(sprintf('Failed to synchronize external peer %s: %s', $clientRequest->getIdentifyAs()->getAddress(), $e->getMessage()), $e->getCode(), $e);
} }
} }
@ -568,68 +574,76 @@
return; return;
} }
if(count($clientRequests) === 0)
{
Logger::getLogger()->warning(sprintf('Received no RPC requests from %s', $_SERVER['REMOTE_ADDR']));
http_response_code(204);
return;
}
Logger::getLogger()->verbose(sprintf('Received %d RPC request(s) from %s', count($clientRequests), $_SERVER['REMOTE_ADDR'])); Logger::getLogger()->verbose(sprintf('Received %d RPC request(s) from %s', count($clientRequests), $_SERVER['REMOTE_ADDR']));
$results = []; $results = [];
foreach($clientRequests as $rpcRequest) foreach($clientRequests as $rpcRequest)
{ {
$method = StandardMethods::tryFrom($rpcRequest->getMethod()); $method = StandardMethods::tryFrom($rpcRequest->getMethod());
try
{
$method->checkAccess($clientRequest);
}
catch (StandardRpcException $e)
{
$response = $e->produceError($rpcRequest);
$results[] = $response->toArray();
continue;
}
if($method === false) if($method === false)
{ {
Logger::getLogger()->warning('The requested method does not exist'); Logger::getLogger()->warning('The requested method does not exist');
$response = $rpcRequest->produceError(StandardError::RPC_METHOD_NOT_FOUND, 'The requested method does not exist'); $results[] = $rpcRequest->produceError(StandardError::RPC_METHOD_NOT_FOUND, 'The requested method does not exist');
continue;
} }
else
try
{ {
if (!$method->checkAccess($clientRequest))
{
$results[] = $rpcRequest->produceError(StandardError::METHOD_NOT_ALLOWED, 'Insufficient access requirements to invoke the session');
continue;
}
}
catch (DatabaseOperationException $e)
{
Logger::getLogger()->error('Failed to check method access', $e);
$results[] = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, 'Failed to check method access due to an internal server error');
continue;
}
catch (StandardRpcException $e)
{
$results[] = $e->produceError($rpcRequest);
continue;
}
try try
{ {
Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod())); Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod()));
$response = $method->execute($clientRequest, $rpcRequest); $results[] = $method->execute($clientRequest, $rpcRequest);
Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod())); Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod()));
} }
catch(StandardRpcException $e) catch(StandardRpcException $e)
{ {
Logger::getLogger()->error('An error occurred while processing the RPC request', $e); Logger::getLogger()->error('An error occurred while processing the RPC request', $e);
$response = $e->produceError($rpcRequest); $results[] = $e->produceError($rpcRequest);
} }
catch(Exception $e) catch(Exception $e)
{ {
Logger::getLogger()->error('An internal error occurred while processing the RPC request', $e); Logger::getLogger()->error('An internal error occurred while processing the RPC request', $e);
if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions()) if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions())
{ {
$response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, Utilities::throwableToString($e)); $results[] = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, Utilities::throwableToString($e));
} }
else else
{ {
$response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR); $results[] = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR);
} }
} }
} }
if($response !== null) $results = array_map(fn($result) => $result->toArray(), $results);
{
Logger::getLogger()->debug(sprintf('Producing response for method %s', $rpcRequest->getMethod()));
$results[] = $response->toArray();
}
}
$response = null;
if(count($results) == 0) if(count($results) == 0)
{ {
$response = null; http_response_code(204);
return;
} }
elseif(count($results) == 1) elseif(count($results) == 1)
{ {
@ -640,12 +654,6 @@
$response = json_encode($results, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $response = json_encode($results, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} }
if($response === null)
{
http_response_code(204);
return;
}
$session = $clientRequest->getSession(); $session = $clientRequest->getSession();
try try