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

Closed
netkas wants to merge 421 commits from master into dev
7 changed files with 371 additions and 12 deletions
Showing only changes of commit 05f6661a75 - Show all commits

View file

@ -41,7 +41,7 @@
throw new InvalidRpcArgumentException('peer', $e); throw new InvalidRpcArgumentException('peer', $e);
} }
if($rpcRequest->containsParameter('relationship')) if($rpcRequest->containsParameter('relationship') && $rpcRequest->getParameter('relationship') !== null)
{ {
$relationship = ContactRelationshipType::tryFrom(strtoupper($rpcRequest->getParameter('relationship'))); $relationship = ContactRelationshipType::tryFrom(strtoupper($rpcRequest->getParameter('relationship')));
if($relationship === null) if($relationship === null)

View file

@ -0,0 +1,31 @@
<?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

@ -0,0 +1,317 @@
<?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\SigningKey;
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 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;
}
}

View file

@ -68,4 +68,15 @@
{ {
return checkdate($month, $day, $year); 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;
}
} }

View file

@ -11,7 +11,7 @@
use Socialbox\Objects\Database\ContactDatabaseRecord; use Socialbox\Objects\Database\ContactDatabaseRecord;
use Socialbox\Objects\Database\ContactKnownKeyRecord; use Socialbox\Objects\Database\ContactKnownKeyRecord;
use Socialbox\Objects\PeerAddress; use Socialbox\Objects\PeerAddress;
use Socialbox\Objects\Standard\ContactRecord; use Socialbox\Objects\Standard\Contact;
use Socialbox\Objects\Standard\SigningKey; use Socialbox\Objects\Standard\SigningKey;
class ContactManager class ContactManager
@ -288,7 +288,7 @@
* @param string $peerUuid The unique identifier for the peer whose contacts are to be retrieved. * @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 $limit The maximum number of contacts to retrieve per page. Defaults to 100.
* @param int $page The page number to retrieve. Defaults to 1. * @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. * @throws DatabaseOperationException If the database query fails.
*/ */
public static function getStandardContacts(string $peerUuid, int $limit=100, int $page=1): array 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 $peerUuid The unique identifier of the peer.
* @param string|PeerAddress $contactAddress The contact's address, either as a string or a PeerAddress instance. * @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. * @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); $contact = self::getContact($peerUuid, $contactAddress);
if($contact === null) if($contact === null)
@ -562,7 +562,7 @@
return null; return null;
} }
return new ContactRecord([ return new Contact([
'address' => $contact->getContactPeerAddress(), 'address' => $contact->getContactPeerAddress(),
'relationship' => $contact->getRelationship(), 'relationship' => $contact->getRelationship(),
'known_keys' => self::contactGetSigningKeys($contact), 'known_keys' => self::contactGetSigningKeys($contact),

View file

@ -7,7 +7,7 @@
use InvalidArgumentException; use InvalidArgumentException;
use Socialbox\Enums\Types\ContactRelationshipType; use Socialbox\Enums\Types\ContactRelationshipType;
use Socialbox\Interfaces\SerializableInterface; use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\Standard\ContactRecord; use Socialbox\Objects\Standard\Contact;
class ContactDatabaseRecord implements SerializableInterface class ContactDatabaseRecord implements SerializableInterface
{ {
@ -126,11 +126,11 @@
/** /**
* Converts the object to a standard contact record. * 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, 'address' => $this->contactPeerAddress,
'relationship' => $this->relationship, 'relationship' => $this->relationship,
'added_timestamp' => $this->created->getTimestamp() 'added_timestamp' => $this->created->getTimestamp()

View file

@ -7,7 +7,7 @@
use Socialbox\Interfaces\SerializableInterface; use Socialbox\Interfaces\SerializableInterface;
use Socialbox\Objects\PeerAddress; use Socialbox\Objects\PeerAddress;
class ContactRecord implements SerializableInterface class Contact implements SerializableInterface
{ {
private PeerAddress $address; private PeerAddress $address;
private ContactRelationshipType $relationship; private ContactRelationshipType $relationship;
@ -102,7 +102,7 @@
/** /**
* @inheritDoc * @inheritDoc
*/ */
public static function fromArray(array $data): ContactRecord public static function fromArray(array $data): Contact
{ {
return new self($data); return new self($data);
} }