Some checks are pending
CI / release (push) Waiting to run
CI / debug (push) Waiting to run
CI / release_executable (push) Waiting to run
CI / debug_executable (push) Waiting to run
CI / check-phpunit (push) Waiting to run
CI / check-phpdoc (push) Waiting to run
CI / generate-phpdoc (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / release-documentation (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
434 lines
No EOL
18 KiB
PHP
434 lines
No EOL
18 KiB
PHP
<?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);
|
|
}
|
|
}
|
|
} |