diff --git a/src/Socialbox/Classes/StandardMethods/AddressBook/AddressBookAddContact.php b/src/Socialbox/Classes/StandardMethods/AddressBook/AddressBookAddContact.php index 7af29fd..4e4f7e4 100644 --- a/src/Socialbox/Classes/StandardMethods/AddressBook/AddressBookAddContact.php +++ b/src/Socialbox/Classes/StandardMethods/AddressBook/AddressBookAddContact.php @@ -41,7 +41,7 @@ throw new InvalidRpcArgumentException('peer', $e); } - if($rpcRequest->containsParameter('relationship')) + if($rpcRequest->containsParameter('relationship') && $rpcRequest->getParameter('relationship') !== null) { $relationship = ContactRelationshipType::tryFrom(strtoupper($rpcRequest->getParameter('relationship'))); if($relationship === null) diff --git a/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php b/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php new file mode 100644 index 0000000..df946c4 --- /dev/null +++ b/src/Socialbox/Classes/StandardMethods/Encryption/EncryptionAcceptChannel.php @@ -0,0 +1,31 @@ +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 SigningKey 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): SigningKey + { + // 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 SigningKey + * @throws InvalidRpcArgumentException + * @throws MissingRpcArgumentException + * @throws StandardRpcException + */ + private static function getReceivingSignature(PeerAddress $receivingPeer, RpcRequest $rpcRequest): SigningKey + { + // 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/Classes/Validator.php b/src/Socialbox/Classes/Validator.php index 2e3165f..02ab8f5 100644 --- a/src/Socialbox/Classes/Validator.php +++ b/src/Socialbox/Classes/Validator.php @@ -68,4 +68,15 @@ { return checkdate($month, $day, $year); } + + /** + * Validates whether the given UUID is a valid UUID. + * + * @param string $uuid The UUID to validate. + * @return bool Returns true if the provided UUID is valid, otherwise false. + */ + public static function validateUuid(string $uuid): bool + { + return preg_match("/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/", $uuid) === 1; + } } \ No newline at end of file diff --git a/src/Socialbox/Managers/ContactManager.php b/src/Socialbox/Managers/ContactManager.php index 51b06ae..5462e75 100644 --- a/src/Socialbox/Managers/ContactManager.php +++ b/src/Socialbox/Managers/ContactManager.php @@ -11,7 +11,7 @@ use Socialbox\Objects\Database\ContactDatabaseRecord; use Socialbox\Objects\Database\ContactKnownKeyRecord; use Socialbox\Objects\PeerAddress; - use Socialbox\Objects\Standard\ContactRecord; + use Socialbox\Objects\Standard\Contact; use Socialbox\Objects\Standard\SigningKey; class ContactManager @@ -288,7 +288,7 @@ * @param string $peerUuid The unique identifier for the peer whose contacts are to be retrieved. * @param int $limit The maximum number of contacts to retrieve per page. Defaults to 100. * @param int $page The page number to retrieve. Defaults to 1. - * @return ContactRecord[] An array of ContactRecord instances representing the contacts for the given peer. + * @return Contact[] An array of ContactRecord instances representing the contacts for the given peer. * @throws DatabaseOperationException If the database query fails. */ public static function getStandardContacts(string $peerUuid, int $limit=100, int $page=1): array @@ -551,10 +551,10 @@ * * @param string $peerUuid The unique identifier of the peer. * @param string|PeerAddress $contactAddress The contact's address, either as a string or a PeerAddress instance. - * @return ContactRecord|null The standard contact record if found, or null if no matching contact exists. + * @return Contact|null The standard contact record if found, or null if no matching contact exists. * @throws DatabaseOperationException If the database query fails. */ - public static function getStandardContact(string $peerUuid, string|PeerAddress $contactAddress): ?ContactRecord + public static function getStandardContact(string $peerUuid, string|PeerAddress $contactAddress): ?Contact { $contact = self::getContact($peerUuid, $contactAddress); if($contact === null) @@ -562,7 +562,7 @@ return null; } - return new ContactRecord([ + return new Contact([ 'address' => $contact->getContactPeerAddress(), 'relationship' => $contact->getRelationship(), 'known_keys' => self::contactGetSigningKeys($contact), diff --git a/src/Socialbox/Objects/Database/ContactDatabaseRecord.php b/src/Socialbox/Objects/Database/ContactDatabaseRecord.php index 47551a0..d10914b 100644 --- a/src/Socialbox/Objects/Database/ContactDatabaseRecord.php +++ b/src/Socialbox/Objects/Database/ContactDatabaseRecord.php @@ -7,7 +7,7 @@ use InvalidArgumentException; use Socialbox\Enums\Types\ContactRelationshipType; use Socialbox\Interfaces\SerializableInterface; - use Socialbox\Objects\Standard\ContactRecord; + use Socialbox\Objects\Standard\Contact; class ContactDatabaseRecord implements SerializableInterface { @@ -126,11 +126,11 @@ /** * Converts the object to a standard contact record. * - * @return ContactRecord The standard contact record. + * @return Contact The standard contact record. */ - public function toStandard(): ContactRecord + public function toStandard(): Contact { - return new ContactRecord([ + return new Contact([ 'address' => $this->contactPeerAddress, 'relationship' => $this->relationship, 'added_timestamp' => $this->created->getTimestamp() diff --git a/src/Socialbox/Objects/Standard/ContactRecord.php b/src/Socialbox/Objects/Standard/Contact.php similarity index 96% rename from src/Socialbox/Objects/Standard/ContactRecord.php rename to src/Socialbox/Objects/Standard/Contact.php index 8b4cabd..4d56f5a 100644 --- a/src/Socialbox/Objects/Standard/ContactRecord.php +++ b/src/Socialbox/Objects/Standard/Contact.php @@ -7,7 +7,7 @@ use Socialbox\Interfaces\SerializableInterface; use Socialbox\Objects\PeerAddress; - class ContactRecord implements SerializableInterface + class Contact implements SerializableInterface { private PeerAddress $address; private ContactRelationshipType $relationship; @@ -102,7 +102,7 @@ /** * @inheritDoc */ - public static function fromArray(array $data): ContactRecord + public static function fromArray(array $data): Contact { return new self($data); }