diff --git a/src/Socialbox/Classes/Configuration.php b/src/Socialbox/Classes/Configuration.php index af43b69..411a8dc 100644 --- a/src/Socialbox/Classes/Configuration.php +++ b/src/Socialbox/Classes/Configuration.php @@ -166,6 +166,10 @@ // value that exceeds this limit, the server will use this limit instead. // recommendation: 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 $config->setDefault('policies.default_display_picture_privacy', 'PUBLIC'); diff --git a/src/Socialbox/Classes/Configuration/PoliciesConfiguration.php b/src/Socialbox/Classes/Configuration/PoliciesConfiguration.php index 329b92a..bd17e7f 100644 --- a/src/Socialbox/Classes/Configuration/PoliciesConfiguration.php +++ b/src/Socialbox/Classes/Configuration/PoliciesConfiguration.php @@ -12,6 +12,10 @@ private int $imageCaptchaExpires; private int $peerSyncInterval; private int $getContactsLimit; + private int $getEncryptionChannelRequestsLimit; + private int $getEncryptionChannelsLimit; + private int $getEncryptionChannelIncomingLimit; + private int $getEncryptionChannelOutgoingLimit; private PrivacyState $defaultDisplayPicturePrivacy; private PrivacyState $defaultFirstNamePrivacy; private PrivacyState $defaultMiddleNamePrivacy; @@ -43,6 +47,10 @@ $this->imageCaptchaExpires = $data['image_captcha_expires']; $this->peerSyncInterval = $data['peer_sync_interval']; $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->defaultFirstNamePrivacy = PrivacyState::tryFrom($data['default_first_name_privacy']) ?? PrivacyState::PRIVATE; $this->defaultMiddleNamePrivacy = PrivacyState::tryFrom($data['default_middle_name_privacy']) ?? PrivacyState::PRIVATE; @@ -110,6 +118,46 @@ 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 * diff --git a/src/Socialbox/Classes/RpcClient.php b/src/Socialbox/Classes/RpcClient.php index e1ad9e1..92d117c 100644 --- a/src/Socialbox/Classes/RpcClient.php +++ b/src/Socialbox/Classes/RpcClient.php @@ -10,6 +10,7 @@ use Socialbox\Exceptions\DatabaseOperationException; use Socialbox\Exceptions\ResolutionException; use Socialbox\Exceptions\RpcException; + use Socialbox\Objects\Client\EncryptionChannelSecret; use Socialbox\Objects\Client\ExportedSession; use Socialbox\Objects\Client\SignatureKeyPair; use Socialbox\Objects\KeyPair; @@ -39,6 +40,7 @@ private string $sessionUuid; private ?string $defaultSigningKey; private array $signingKeys; + private array $encryptionChannelSecrets; /** * Constructs a new instance with the specified peer address. @@ -78,6 +80,7 @@ $this->serverTransportEncryptionKey = $exportedSession->getServerTransportEncryptionKey(); $this->signingKeys = $exportedSession->getSigningKeys(); $this->defaultSigningKey = $exportedSession->getDefaultSigningKey(); + $this->encryptionChannelSecrets = $exportedSession->getEncryptionChannelSecrets(); // Still solve the server information $this->serverInformation = self::getServerInformation(); @@ -107,6 +110,7 @@ // Set the initial properties $this->signingKeys = []; + $this->encryptionChannelSecrets = []; $this->defaultSigningKey = null; $this->identifiedAs = $identifiedAs; $this->remoteServer = $server ?? $identifiedAs->getDomain(); @@ -771,6 +775,17 @@ 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. * @@ -797,6 +812,71 @@ $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. * @@ -821,7 +901,8 @@ 'client_transport_encryption_key' => $this->clientTransportEncryptionKey, 'server_transport_encryption_key' => $this->serverTransportEncryptionKey, '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) ]); } } \ No newline at end of file diff --git a/src/Socialbox/Classes/StandardMethods/Core/ResolvePeerSignature.php b/src/Socialbox/Classes/StandardMethods/Core/ResolveSignature.php similarity index 59% rename from src/Socialbox/Classes/StandardMethods/Core/ResolvePeerSignature.php rename to src/Socialbox/Classes/StandardMethods/Core/ResolveSignature.php index d9248f4..002d264 100644 --- a/src/Socialbox/Classes/StandardMethods/Core/ResolvePeerSignature.php +++ b/src/Socialbox/Classes/StandardMethods/Core/ResolveSignature.php @@ -5,6 +5,7 @@ use Exception; use InvalidArgumentException; use Socialbox\Abstracts\Method; + use Socialbox\Classes\Validator; use Socialbox\Enums\StandardError; use Socialbox\Exceptions\Standard\InvalidRpcArgumentException; use Socialbox\Exceptions\Standard\MissingRpcArgumentException; @@ -16,7 +17,7 @@ use Socialbox\Socialbox; use Symfony\Component\Uid\Uuid; - class ResolvePeerSignature extends Method + class ResolveSignature extends Method { /** @@ -30,30 +31,17 @@ 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 - { - $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())); + return $rpcRequest->produceResponse(Socialbox::resolvePeerSignature( + $rpcRequest->getParameter('peer'), $rpcRequest->getParameter('signature_uuid') + )); } } \ No newline at end of file diff --git a/src/Socialbox/Classes/StandardMethods/Core/VerifyPeerSignature.php b/src/Socialbox/Classes/StandardMethods/Core/VerifySignature.php similarity index 81% rename from src/Socialbox/Classes/StandardMethods/Core/VerifyPeerSignature.php rename to src/Socialbox/Classes/StandardMethods/Core/VerifySignature.php index d78c79b..f8e2633 100644 --- a/src/Socialbox/Classes/StandardMethods/Core/VerifyPeerSignature.php +++ b/src/Socialbox/Classes/StandardMethods/Core/VerifySignature.php @@ -14,7 +14,7 @@ use Socialbox\Objects\RpcRequest; use Socialbox\Socialbox; - class VerifyPeerSignature extends Method + class VerifySignature extends Method { /** @@ -37,11 +37,6 @@ throw new InvalidRpcArgumentException('signature_uuid', 'Invalid UUID V4'); } - if(!$rpcRequest->containsParameter('signature_public_key')) - { - throw new MissingRpcArgumentException('signature_public_key'); - } - if(!$rpcRequest->containsParameter('signature')) { throw new MissingRpcArgumentException('signature'); @@ -66,27 +61,25 @@ 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( signingPeer: $peerAddress, signatureUuid: $rpcRequest->getParameter('signature_uuid'), - signatureKey: $rpcRequest->getParameter('signature_public_key'), signature: $rpcRequest->getParameter('signature'), messageHash: $rpcRequest->getParameter('sha512'), - signatureTime: (int)$rpcRequest->getParameter('signature_time') + signatureTime: (int)$rpcRequest->getParameter('time') )->value); } return $rpcRequest->produceResponse(Socialbox::verifySignature( signingPeer: $peerAddress, signatureUuid: $rpcRequest->getParameter('signature_uuid'), - signatureKey: $rpcRequest->getParameter('signature_public_key'), signature: $rpcRequest->getParameter('signature'), messageHash: $rpcRequest->getParameter('sha512'), )->value); diff --git a/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php b/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php deleted file mode 100644 index df946c4..0000000 --- a/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php +++ /dev/null @@ -1,31 +0,0 @@ -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; - } - } \ No newline at end of file diff --git a/src/Socialbox/Enums/StandardMethods.php b/src/Socialbox/Enums/StandardMethods.php index 137c02a..b07adaf 100644 --- a/src/Socialbox/Enums/StandardMethods.php +++ b/src/Socialbox/Enums/StandardMethods.php @@ -16,8 +16,11 @@ use Socialbox\Classes\StandardMethods\Core\GetSessionState; use Socialbox\Classes\StandardMethods\Core\Ping; use Socialbox\Classes\StandardMethods\Core\ResolvePeer; - use Socialbox\Classes\StandardMethods\Core\ResolvePeerSignature; - use Socialbox\Classes\StandardMethods\Core\VerifyPeerSignature; + use Socialbox\Classes\StandardMethods\Core\ResolveSignature; + 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\AcceptPrivacyPolicy; use Socialbox\Classes\StandardMethods\ServerDocuments\AcceptTermsOfService; @@ -127,14 +130,6 @@ // MISC 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 case MESSAGES_GET_INBOX = 'messagesGetInbox'; case MESSAGES_GET_UNTRUSTED = 'messagesGetUntrusted'; @@ -189,8 +184,8 @@ self::GET_SESSION_STATE => GetSessionState::execute($request, $rpcRequest), self::PING => Ping::execute($request, $rpcRequest), self::RESOLVE_PEER => ResolvePeer::execute($request, $rpcRequest), - self::RESOLVE_PEER_SIGNATURE => ResolvePeerSignature::execute($request, $rpcRequest), - self::VERIFY_PEER_SIGNATURE => VerifyPeerSignature::execute($request, $rpcRequest), + self::RESOLVE_PEER_SIGNATURE => ResolveSignature::execute($request, $rpcRequest), + self::VERIFY_PEER_SIGNATURE => VerifySignature::execute($request, $rpcRequest), // Server Document Methods self::ACCEPT_PRIVACY_POLICY => AcceptPrivacyPolicy::execute($request, $rpcRequest), @@ -235,18 +230,13 @@ * Checks if the access method is allowed for the given client request. * * @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 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; - } - - throw new StandardRpcException(StandardError::METHOD_NOT_ALLOWED->getMessage(), StandardError::METHOD_NOT_ALLOWED); + return in_array($this, self::getAllowedMethods($clientRequest)); } /** diff --git a/src/Socialbox/Enums/Status/SignatureVerificationStatus.php b/src/Socialbox/Enums/Status/SignatureVerificationStatus.php index aa5d277..0ee7710 100644 --- a/src/Socialbox/Enums/Status/SignatureVerificationStatus.php +++ b/src/Socialbox/Enums/Status/SignatureVerificationStatus.php @@ -5,32 +5,32 @@ enum SignatureVerificationStatus : string { /** - * The provided signature does not match the expected signature. + * Returned if the signature is 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'; /** - * 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'; } diff --git a/src/Socialbox/Managers/EncryptionChannelManager.php b/src/Socialbox/Managers/EncryptionChannelManager.php index badcede..29e36c4 100644 --- a/src/Socialbox/Managers/EncryptionChannelManager.php +++ b/src/Socialbox/Managers/EncryptionChannelManager.php @@ -1,5 +1,6 @@ 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); $callingPeerAddress = $callingPeer->getAddress(); $stmt->bindParam(':calling_peer', $callingPeerAddress); $stmt->bindParam(':calling_signature_uuid', $signatureUuid); - $stmt->bindParam(':calling_signature_public_key', $signingPublicKey); $stmt->bindParam(':calling_encryption_public_key', $encryptionPublicKey); $receivingPeerAddress = $receivingPeer->getAddress(); $stmt->bindParam(':receiving_peer', $receivingPeerAddress); @@ -96,7 +90,6 @@ * @param int $page The page of channels to retrieve. * @return EncryptionChannelRecord[] The incoming channels for the peer. * @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 { @@ -137,7 +130,6 @@ * @param int $page The page of channels to retrieve. * @return EncryptionChannelRecord[] The incoming channels for the peer. * @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 { @@ -180,7 +172,6 @@ * @param int $page The page of channels to retrieve. * @return EncryptionChannelRecord[] The incoming channels for the peer. * @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 { @@ -221,7 +212,6 @@ * @param int $page The page of channels to retrieve. * @return EncryptionChannelRecord[] The outgoing channels for the specified peer. * @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 { @@ -281,23 +271,19 @@ * * @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 $signaturePublicKey The public key used for signing. * @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. * @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 { - $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; $stmt->bindParam(':state', $state); $stmt->bindParam(':receiving_signature_uuid', $signatureUuid); - $stmt->bindParam(':receiving_signature_public_key', $signaturePublicKey); $stmt->bindParam(':receiving_encryption_public_key', $encryptionPublicKey); - $stmt->bindParam(':transport_encryption_algorithm', $transportEncryptionAlgorithm); $stmt->bindParam(':transport_encryption_key', $encryptedTransportEncryptionKey); $stmt->bindParam(':uuid', $channelUuid); $stmt->execute(); @@ -314,7 +300,6 @@ * @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. * @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 { @@ -343,7 +328,7 @@ * * @param string $channelUuid The UUID of the channel to delete. * @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 { @@ -503,7 +488,6 @@ * @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. * @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 { @@ -531,23 +515,29 @@ /** * 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. */ - public static function importData(EncryptionChannelMessage|ChannelMessageRecord $message): void + public static function importData(ChannelMessageRecord $message): void { - $data = $message->toArray(); 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->bindParam(':uuid', $data['uuid']); - $stmt->bindParam(':channel_uuid', $data['channel_uuid']); - $stmt->bindParam(':recipient', $data['recipient']); - $stmt->bindParam(':message', $data['message']); - $stmt->bindParam(':signature', $data['signature']); - $stmt->bindParam(':received', $data['received']); - $stmt->bindParam(':timestamp', $data['timestamp']); + $uuid = $message->getUuid(); + $stmt->bindParam(':uuid', $uuid); + $channelUuid = $message->getChannelUuid(); + $stmt->bindParam(':channel_uuid', $channelUuid); + $recipient = $message->getRecipient()->value; + $stmt->bindParam(':recipient', $recipient); + $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(); } catch(PDOException $e) diff --git a/src/Socialbox/Managers/RegisteredPeerManager.php b/src/Socialbox/Managers/RegisteredPeerManager.php index 55e6192..91622fa 100644 --- a/src/Socialbox/Managers/RegisteredPeerManager.php +++ b/src/Socialbox/Managers/RegisteredPeerManager.php @@ -34,8 +34,8 @@ try { - $statement = Database::getConnection()->prepare('SELECT COUNT(*) FROM peers WHERE username=?'); - $statement->bindParam(1, $username); + $statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM peers WHERE username=:username AND server='host'"); + $statement->bindParam(':username', $username); $statement->execute(); $result = $statement->fetchColumn(); @@ -163,9 +163,9 @@ 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(); - $statement->bindParam(1, $username); + $statement->bindParam(':username', $username); $server = $address->getDomain(); // Convert to 'host' if the domain is the same as the server's host @@ -174,7 +174,7 @@ $server = 'host'; } - $statement->bindParam(2, $server); + $statement->bindParam(':server', $server); $statement->execute(); $result = $statement->fetch(PDO::FETCH_ASSOC); diff --git a/src/Socialbox/Objects/Client/EncryptionChannelSecret.php b/src/Socialbox/Objects/Client/EncryptionChannelSecret.php new file mode 100644 index 0000000..42f10b9 --- /dev/null +++ b/src/Socialbox/Objects/Client/EncryptionChannelSecret.php @@ -0,0 +1,129 @@ +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 + ]; + } + } \ No newline at end of file diff --git a/src/Socialbox/Objects/Client/ExportedSession.php b/src/Socialbox/Objects/Client/ExportedSession.php index db69534..8a18d36 100644 --- a/src/Socialbox/Objects/Client/ExportedSession.php +++ b/src/Socialbox/Objects/Client/ExportedSession.php @@ -29,6 +29,10 @@ * @var SignatureKeyPair[] */ private array $signingKeys; + /** + * @var EncryptionChannelSecret[] + */ + private array $encryptionChannelSecrets; /** * Constructor method to initialize class properties from the provided data array. @@ -62,6 +66,7 @@ $this->serverTransportEncryptionKey = $data['server_transport_encryption_key']; $this->defaultSigningKey = $data['default_signing_key'] ?? null; $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; } + /** + * 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 */ @@ -256,7 +315,8 @@ 'client_transport_encryption_key' => $this->clientTransportEncryptionKey, 'server_transport_encryption_key' => $this->serverTransportEncryptionKey, '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), ]; } diff --git a/src/Socialbox/Objects/ClientRequest.php b/src/Socialbox/Objects/ClientRequest.php index 157feba..6f35306 100644 --- a/src/Socialbox/Objects/ClientRequest.php +++ b/src/Socialbox/Objects/ClientRequest.php @@ -170,7 +170,6 @@ * * @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 StandardRpcException Thrown if the session UUID is invalid. */ public function getSession(): ?SessionRecord { @@ -187,7 +186,6 @@ * * @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 StandardRpcException Thrown if the session UUID is invalid. */ public function getPeer(): ?PeerDatabaseRecord { @@ -201,6 +199,36 @@ 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. * diff --git a/src/Socialbox/Objects/Database/ChannelMessageRecord.php b/src/Socialbox/Objects/Database/ChannelMessageRecord.php index 99c7c5a..33f5d24 100644 --- a/src/Socialbox/Objects/Database/ChannelMessageRecord.php +++ b/src/Socialbox/Objects/Database/ChannelMessageRecord.php @@ -30,7 +30,6 @@ * - 'signature' (string): The signature. * - 'received' (bool): Whether the message has been received. * - '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) { diff --git a/src/Socialbox/Objects/Database/EncryptionChannelRecord.php b/src/Socialbox/Objects/Database/EncryptionChannelRecord.php index 92a4246..49c44b9 100644 --- a/src/Socialbox/Objects/Database/EncryptionChannelRecord.php +++ b/src/Socialbox/Objects/Database/EncryptionChannelRecord.php @@ -17,7 +17,6 @@ private string $callingEncryptionPublicKey; private PeerAddress $receivingPeer; private ?string $receivingSignatureUuid; - private ?string $receivingSignaturePublicKey; private ?string $receivingEncryptionPublicKey; private string $transportEncryptionAlgorithm; private ?string $transportEncryptionKey; @@ -28,7 +27,6 @@ * Public Constructor for the encryption channel record * * @param array $data - * @throws \DateMalformedStringException */ public function __construct(array $data) { @@ -78,7 +76,6 @@ } $this->receivingSignatureUuid = $data['receiving_signature_uuid'] ?? null; - $this->receivingSignaturePublicKey = $data['receiving_signature_public_key'] ?? null; $this->receivingEncryptionPublicKey = $data['receiving_encryption_public_key'] ?? null; $this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm']; $this->transportEncryptionKey = $data['transport_encryption_key'] ?? null; @@ -169,16 +166,6 @@ 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 * @@ -249,7 +236,6 @@ 'calling_encryption_public_key' => $this->callingEncryptionPublicKey, 'receiving_peer' => $this->receivingPeer->getAddress(), 'receiving_signature_uuid' => $this->receivingSignatureUuid, - 'receiving_signature_public_key' => $this->receivingSignaturePublicKey, 'receiving_encryption_public_key' => $this->receivingEncryptionPublicKey, 'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm, 'transport_encryption_key' => $this->transportEncryptionKey, diff --git a/src/Socialbox/Objects/PeerAddress.php b/src/Socialbox/Objects/PeerAddress.php index 4372e11..27e7b22 100644 --- a/src/Socialbox/Objects/PeerAddress.php +++ b/src/Socialbox/Objects/PeerAddress.php @@ -83,7 +83,7 @@ return false; } - return $this->username === ReservedUsernames::HOST->value; + return $this->domain !== Configuration::getInstanceConfiguration()->getDomain(); } /** diff --git a/src/Socialbox/Objects/RpcRequest.php b/src/Socialbox/Objects/RpcRequest.php index a785d83..070645d 100644 --- a/src/Socialbox/Objects/RpcRequest.php +++ b/src/Socialbox/Objects/RpcRequest.php @@ -87,6 +87,26 @@ 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 * diff --git a/src/Socialbox/SocialClient.php b/src/Socialbox/SocialClient.php index fbce246..516ba87 100644 --- a/src/Socialbox/SocialClient.php +++ b/src/Socialbox/SocialClient.php @@ -76,53 +76,6 @@ 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 * the address book. @@ -459,74 +412,6 @@ )->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. * diff --git a/src/Socialbox/Socialbox.php b/src/Socialbox/Socialbox.php index c70bc17..ce667d3 100644 --- a/src/Socialbox/Socialbox.php +++ b/src/Socialbox/Socialbox.php @@ -200,7 +200,7 @@ // Prevent the peer from identifying as the host unless it's coming from an external domain if($clientRequest->getIdentifyAs()->getUsername() === ReservedUsernames::HOST->value) { - self::returnError(403, StandardError::FORBIDDEN, 'Unauthorized: Not allowed to identify as the host'); + self::returnError(403, StandardError::FORBIDDEN, 'Forbidden: Not allowed to identify as the host'); return; } } @@ -225,7 +225,7 @@ } 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; } } @@ -258,14 +258,6 @@ RegisteredPeerManager::enablePeer($peerUuid); $registeredPeer = RegisteredPeerManager::getPeer($peerUuid); } - else - { - // If the host is registered, but disabled, enable it - if(!$registeredPeer->isEnabled()) - { - RegisteredPeerManager::enablePeer($registeredPeer->getUuid()); - } - } } if($registeredPeer === null) @@ -355,7 +347,16 @@ return; } - $session = $clientRequest->getSession(); + try + { + $session = $clientRequest->getSession(); + } + catch (DatabaseOperationException $e) + { + self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to retrieve session', $e); + return; + } + if($session === null) { self::returnError(404, StandardError::SESSION_NOT_FOUND, 'Session not found'); @@ -525,36 +526,41 @@ { try { - $peer = $clientRequest->getPeer(); + $hostPeer = $clientRequest->getPeer(); } catch (DatabaseOperationException $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; } - 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 try { self::resolvePeer($clientRequest->getIdentifyAs()); } - catch (DatabaseOperationException $e) + catch (StandardRpcException $e) { - self::returnError(500, StandardError::INTERNAL_SERVER_ERROR, 'Failed to synchronize external peer', $e); - return; - } - catch (Exception $e) - { - throw new ResolutionException(sprintf('Failed to synchronize external peer %s: %s', $clientRequest->getIdentifyAs()->getAddress(), $e->getMessage()), $e->getCode(), $e); + throw new ResolutionException(sprintf('Failed to resolve peer %s: %s', $clientRequest->getIdentifyAs()->getAddress(), $e->getMessage()), $e->getCode(), $e); } } @@ -568,68 +574,76 @@ 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'])); $results = []; foreach($clientRequests as $rpcRequest) { $method = StandardMethods::tryFrom($rpcRequest->getMethod()); - - try - { - $method->checkAccess($clientRequest); - } - catch (StandardRpcException $e) - { - $response = $e->produceError($rpcRequest); - $results[] = $response->toArray(); - continue; - } - if($method === false) { Logger::getLogger()->warning('The requested method does not exist'); - $response = $rpcRequest->produceError(StandardError::RPC_METHOD_NOT_FOUND, 'The requested method does not exist'); - } - else - { - try - { - Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod())); - $response = $method->execute($clientRequest, $rpcRequest); - Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod())); - } - catch(StandardRpcException $e) - { - Logger::getLogger()->error('An error occurred while processing the RPC request', $e); - $response = $e->produceError($rpcRequest); - } - catch(Exception $e) - { - Logger::getLogger()->error('An internal error occurred while processing the RPC request', $e); - if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions()) - { - $response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, Utilities::throwableToString($e)); - } - else - { - $response = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR); - } - } + $results[] = $rpcRequest->produceError(StandardError::RPC_METHOD_NOT_FOUND, 'The requested method does not exist'); + continue; } - if($response !== null) + try { - Logger::getLogger()->debug(sprintf('Producing response for method %s', $rpcRequest->getMethod())); - $results[] = $response->toArray(); + 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 + { + Logger::getLogger()->debug(sprintf('Processing RPC request for method %s', $rpcRequest->getMethod())); + $results[] = $method->execute($clientRequest, $rpcRequest); + Logger::getLogger()->debug(sprintf('%s method executed successfully', $rpcRequest->getMethod())); + } + catch(StandardRpcException $e) + { + Logger::getLogger()->error('An error occurred while processing the RPC request', $e); + $results[] = $e->produceError($rpcRequest); + } + catch(Exception $e) + { + Logger::getLogger()->error('An internal error occurred while processing the RPC request', $e); + if(Configuration::getSecurityConfiguration()->isDisplayInternalExceptions()) + { + $results[] = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR, Utilities::throwableToString($e)); + } + else + { + $results[] = $rpcRequest->produceError(StandardError::INTERNAL_SERVER_ERROR); + } } } - $response = null; - + $results = array_map(fn($result) => $result->toArray(), $results); if(count($results) == 0) { - $response = null; + http_response_code(204); + return; } elseif(count($results) == 1) { @@ -640,12 +654,6 @@ $response = json_encode($results, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } - if($response === null) - { - http_response_code(204); - return; - } - $session = $clientRequest->getSession(); try