socialbox-php/tests/Socialbox/Classes/CryptographyTest.php

434 lines
18 KiB
PHP
Raw Normal View History

<?php
namespace Socialbox\Classes;
use Exception;
use PHPUnit\Framework\TestCase;
use Socialbox\Exceptions\CryptographyException;
use Socialbox\Objects\KeyPair;
class CryptographyTest extends TestCase
{
/**
* Test that generateEncryptionKeyPair generates a KeyPair with valid keys.
*/
public function testGenerateEncryptionKeyPairProducesValidKeyPair(): void
{
$keyPair = Cryptography::generateEncryptionKeyPair();
$this->assertInstanceOf(KeyPair::class, $keyPair);
$this->assertNotEmpty($keyPair->getPublicKey());
$this->assertNotEmpty($keyPair->getPrivateKey());
}
/**
* Test that the generated public key starts with the defined encryption key type prefix.
*/
public function testGeneratedPublicKeyHasEncryptionPrefix(): void
{
$keyPair = Cryptography::generateEncryptionKeyPair();
$this->assertStringStartsWith('enc:', $keyPair->getPublicKey());
}
/**
* Test that the generated private key starts with the defined encryption key type prefix.
*/
public function testGeneratedPrivateKeyHasEncryptionPrefix(): void
{
$keyPair = Cryptography::generateEncryptionKeyPair();
$this->assertStringStartsWith('enc:', $keyPair->getPrivateKey());
}
/**
* Test that the generated keys are of different base64-encoded string values.
*/
public function testPublicAndPrivateKeysAreDifferent(): void
{
$keyPair = Cryptography::generateEncryptionKeyPair();
$this->assertNotEquals($keyPair->getPublicKey(), $keyPair->getPrivateKey());
}
/**
* Test that generateSigningKeyPair generates a KeyPair with valid keys.
*/
public function testGenerateSigningKeyPairProducesValidKeyPair(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$this->assertInstanceOf(KeyPair::class, $keyPair);
$this->assertNotEmpty($keyPair->getPublicKey());
$this->assertNotEmpty($keyPair->getPrivateKey());
}
/**
* Test that the generated public key starts with the defined signing key type prefix.
*/
public function testGeneratedPublicKeyHasSigningPrefix(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$this->assertStringStartsWith('sig:', $keyPair->getPublicKey());
}
/**
* Test that the generated private key starts with the defined signing key type prefix.
*/
public function testGeneratedPrivateKeyHasSigningPrefix(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$this->assertStringStartsWith('sig:', $keyPair->getPrivateKey());
}
/**
* Test that performDHE successfully calculates a shared secret with valid keys.
*/
public function testPerformDHESuccessfullyCalculatesSharedSecret(): void
{
$aliceKeyPair = Cryptography::generateEncryptionKeyPair();
$aliceSigningKeyPair = Cryptography::generateSigningKeyPair();
$bobKeyPair = Cryptography::generateEncryptionKeyPair();
$bobSigningKeyPair = Cryptography::generateSigningKeyPair();
// Alice performs DHE with Bob
$aliceSharedSecret = Cryptography::performDHE($bobKeyPair->getPublicKey(), $aliceKeyPair->getPrivateKey());
// Bob performs DHE with Alice
$bobSharedSecret = Cryptography::performDHE($aliceKeyPair->getPublicKey(), $bobKeyPair->getPrivateKey());
$this->assertEquals($aliceSharedSecret, $bobSharedSecret);
// Alice sends "Hello, Bob!" to Bob, signing the message and encrypting it with the shared secret
$message = "Hello, Bob!";
$aliceSignature = Cryptography::signMessage($message, $aliceSigningKeyPair->getPrivateKey());
$encryptedMessage = Cryptography::encryptShared($message, $aliceSharedSecret);
// Bob decrypts the message and verifies the signature
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $bobSharedSecret);
$isValid = Cryptography::verifyMessage($decryptedMessage, $aliceSignature, $aliceSigningKeyPair->getPublicKey());
$this->assertEquals($message, $decryptedMessage);
$this->assertTrue($isValid);
// Bob sends "Hello, Alice!" to Alice, signing the message and encrypting it with the shared secret
$message = "Hello, Alice!";
$bobSignature = Cryptography::signMessage($message, $bobSigningKeyPair->getPrivateKey());
$encryptedMessage = Cryptography::encryptShared($message, $bobSharedSecret);
// Alice decrypts the message and verifies the signature
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $aliceSharedSecret);
$isValid = Cryptography::verifyMessage($decryptedMessage, $bobSignature, $bobSigningKeyPair->getPublicKey());
$this->assertEquals($message, $decryptedMessage);
$this->assertTrue($isValid);
}
/**
* Test that performDHE throws an exception when an invalid public key is used.
*/
public function testPerformDHEThrowsExceptionForInvalidPublicKey(): void
{
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
$invalidPublicKey = 'invalid_key';
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage('Invalid key type. Expected enc:');
Cryptography::performDHE($invalidPublicKey, $encryptionKeyPair->getPrivateKey());
}
/**
* Test that performDHE throws an exception when an invalid private key is used.
*/
public function testPerformDHEThrowsExceptionForInvalidPrivateKey(): void
{
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
$invalidPrivateKey = 'invalid_key';
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage('Invalid key type. Expected enc:');
Cryptography::performDHE($encryptionKeyPair->getPublicKey(), $invalidPrivateKey);
}
/**
* Test that encrypt correctly encrypts a message with a valid shared secret.
*/
public function testEncryptSuccessfullyEncryptsMessage(): void
{
$sharedSecret = Cryptography::performDHE(
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
);
$message = "Test message";
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
$this->assertNotEmpty($encryptedMessage);
$this->assertNotEquals($message, $encryptedMessage);
}
/**
* Test that encrypt throws an exception when given an invalid shared secret.
*/
public function testEncryptThrowsExceptionForInvalidSharedSecret(): void
{
$invalidSharedSecret = "invalid_secret";
$message = "Test message";
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage("Encryption failed");
Cryptography::encryptShared($message, $invalidSharedSecret);
}
/**
* Test that the encrypted message is different from the original message.
*/
public function testEncryptProducesDifferentMessage(): void
{
$sharedSecret = Cryptography::performDHE(
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
);
$message = "Another test message";
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
$this->assertNotEquals($message, $encryptedMessage);
}
/**
* Test that decrypt successfully decrypts an encrypted message with a valid shared secret.
*/
public function testDecryptSuccessfullyDecryptsMessage(): void
{
$sharedSecret = Cryptography::performDHE(
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
);
$message = "Decryption test message";
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
$decryptedMessage = Cryptography::decryptShared($encryptedMessage, $sharedSecret);
$this->assertEquals($message, $decryptedMessage);
}
/**
* Test that decrypt throws an exception when given an invalid shared secret.
*/
public function testDecryptThrowsExceptionForInvalidSharedSecret(): void
{
$sharedSecret = Cryptography::performDHE(
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
);
$invalidSharedSecret = "invalid_shared_secret";
$message = "Decryption failure case";
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage("Decryption failed");
Cryptography::decryptShared($encryptedMessage, $invalidSharedSecret);
}
/**
* Test that decrypt throws an exception when the encrypted data is tampered with.
*/
public function testDecryptThrowsExceptionForTamperedEncryptedMessage(): void
{
$sharedSecret = Cryptography::performDHE(
Cryptography::generateEncryptionKeyPair()->getPublicKey(),
Cryptography::generateEncryptionKeyPair()->getPrivateKey()
);
$message = "Tampered message";
$encryptedMessage = Cryptography::encryptShared($message, $sharedSecret);
$tamperedMessage = $encryptedMessage . "tampered_data";
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage("Decryption failed");
Cryptography::decryptShared($tamperedMessage, $sharedSecret);
}
/**
* Test that sign successfully signs a message with a valid private key.
*/
public function testSignSuccessfullySignsMessage(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$message = "Message to sign";
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
$this->assertNotEmpty($signature);
}
/**
* Test that sign throws an exception when an invalid private key is used.
*/
public function testSignThrowsExceptionForInvalidPrivateKey(): void
{
$invalidPrivateKey = "invalid_key";
$message = "Message to sign";
$this->expectException(CryptographyException::class);
Cryptography::signMessage($message, $invalidPrivateKey);
}
/**
* Test that verify successfully validates a correct signature with a valid message and public key.
*/
public function testVerifySuccessfullyValidatesSignature(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$message = "Message to verify";
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
$isValid = Cryptography::verifyMessage($message, $signature, $keyPair->getPublicKey());
$this->assertTrue($isValid);
}
/**
* Test that verify fails for an invalid signature.
*/
public function testVerifyFailsForInvalidSignature(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$message = "Message to verify";
$signature = "invalid_signature";
$this->expectException(Exception::class);
Cryptography::verifyMessage($message, $signature, $keyPair->getPublicKey());
}
/**
* Test that verify throws an exception for an invalid public key.
*/
public function testVerifyThrowsExceptionForInvalidPublicKey(): void
{
$keyPair = Cryptography::generateSigningKeyPair();
$message = "Message to verify";
$signature = Cryptography::signMessage($message, $keyPair->getPrivateKey());
$invalidPublicKey = "invalid_public_key";
$this->expectException(CryptographyException::class);
Cryptography::verifyMessage($message, $signature, $invalidPublicKey);
}
/**
* Test that verify throws an exception for a public key with the wrong type prefix.
*/
public function testVerifyThrowsExceptionForInvalidKeyType(): void
{
$encryptionKeyPair = Cryptography::generateEncryptionKeyPair();
$message = "Message to verify";
$signature = "invalid_signature";
$this->expectException(CryptographyException::class);
$this->expectExceptionMessage("Invalid key type. Expected sig:");
Cryptography::verifyMessage($message, $signature, $encryptionKeyPair->getPublicKey());
}
/**
* Test that generateTransportKey creates a valid transport key for the default algorithm.
*/
public function testGenerateTransportKeyCreatesValidKeyForDefaultAlgo(): void
{
$transportKey = Cryptography::generateEncryptionKey('xchacha20');
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
$this->assertNotEmpty($transportKey);
$this->assertEquals(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, strlen($decodedKey));
}
/**
* Test that generateTransportKey creates valid keys for specific supported algorithms.
*/
public function testGenerateTransportKeyCreatesValidKeysForAlgorithms(): void
{
$algorithms = [
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
];
foreach ($algorithms as $algorithm => $expectedKeyLength) {
$transportKey = Cryptography::generateEncryptionKey($algorithm);
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
$this->assertNotEmpty($transportKey);
$this->assertEquals($expectedKeyLength, strlen($decodedKey));
}
}
/**
* Test that generateTransportKey throws an exception when given an invalid algorithm.
*/
public function testGenerateTransportKeyThrowsExceptionForInvalidAlgorithm(): void
{
$this->expectException(CryptographyException::class);
Cryptography::generateEncryptionKey("invalid_algorithm");
}
/**
* Test that generateTransportKey creates valid keys for other supported algorithms.
*/
public function testGenerateTransportKeyCreatesValidKeyForOtherSupportedAlgorithms(): void
{
$algorithms = [
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
];
foreach ($algorithms as $algorithm => $expectedKeyLength) {
$transportKey = Cryptography::generateEncryptionKey($algorithm);
$decodedKey = sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true);
$this->assertNotEmpty($transportKey);
$this->assertEquals($expectedKeyLength, strlen($decodedKey));
}
}
/**
* Test that generateTransportKey throws an exception for unsupported algorithms.
*/
public function testGenerateTransportKeyThrowsExceptionForUnsupportedAlgorithm(): void
{
$this->expectException(CryptographyException::class);
Cryptography::generateEncryptionKey('invalid_algo');
}
public function testEncryptTransportMessageSuccessfullyEncryptsMessage(): void
{
$algorithms = [
'xchacha20' => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
'chacha20' => SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES,
'aes256gcm' => SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
];
foreach ($algorithms as $algorithm => $keyLength) {
$transportKey = Cryptography::generateEncryptionKey($algorithm);
$this->assertNotEmpty($transportKey);
$this->assertEquals($keyLength, strlen(sodium_base642bin($transportKey, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, true)));
$message = "Test message";
$encryptedMessage = Cryptography::encryptMessage($message, $transportKey, $algorithm);
$decryptedMessage = Cryptography::decryptMessage($encryptedMessage, $transportKey, $algorithm);
$this->assertEquals($message, $decryptedMessage);
}
}
}