diff --git a/src/Cipher/AdvancedEncryptionStandardCipher.php b/src/Cipher/AdvancedEncryptionStandardCipher.php index 80db242..0bf45cc 100644 --- a/src/Cipher/AdvancedEncryptionStandardCipher.php +++ b/src/Cipher/AdvancedEncryptionStandardCipher.php @@ -7,6 +7,8 @@ use IlicMiljan\SecureProps\Cipher\Exception\FailedEncryptingValue; use IlicMiljan\SecureProps\Cipher\Exception\FailedGeneratingInitializationVector; use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength; +use IlicMiljan\SecureProps\Encoder\Base64Encoder; +use IlicMiljan\SecureProps\Encoder\Encoder; use SensitiveParameter; class AdvancedEncryptionStandardCipher implements Cipher @@ -15,11 +17,20 @@ class AdvancedEncryptionStandardCipher implements Cipher private const TAG_LENGTH = 16; private const KEY_LENGTH = 32; + private Encoder $encoder; + public function __construct( #[SensitiveParameter] - private string $key + private string $key, + ?Encoder $encoder = null ) { $this->validateKey($key); + + if ($encoder === null) { + $this->encoder = new Base64Encoder(); + } else { + $this->encoder = $encoder; + } } /** @@ -35,15 +46,16 @@ public function encrypt(#[SensitiveParameter] string $string): string throw new FailedEncryptingValue(); } - return base64_encode($iv . $encryptedString . $tag); + return $this->encoder->encode($iv . $encryptedString . $tag); } /** * @inheritDoc + * */ public function decrypt(#[SensitiveParameter] string $string): string { - $data = base64_decode($string); + $data = $this->encoder->decode($string); $ivLength = $this->calculateInitializationVectorLength(self::CIPHER); diff --git a/src/Cipher/AsymmetricEncryptionCipher.php b/src/Cipher/AsymmetricEncryptionCipher.php index ec1e55c..8165f23 100644 --- a/src/Cipher/AsymmetricEncryptionCipher.php +++ b/src/Cipher/AsymmetricEncryptionCipher.php @@ -4,14 +4,24 @@ use IlicMiljan\SecureProps\Cipher\Exception\FailedDecryptingValue; use IlicMiljan\SecureProps\Cipher\Exception\FailedEncryptingValue; +use IlicMiljan\SecureProps\Encoder\Base64Encoder; +use IlicMiljan\SecureProps\Encoder\Encoder; use SensitiveParameter; class AsymmetricEncryptionCipher implements Cipher { + private Encoder $encoder; + public function __construct( private string $publicKey, - private string $privateKey + private string $privateKey, + ?Encoder $encoder = null ) { + if ($encoder === null) { + $this->encoder = new Base64Encoder(); + } else { + $this->encoder = $encoder; + } } /** @@ -23,7 +33,7 @@ public function encrypt(#[SensitiveParameter] string $string): string throw new FailedEncryptingValue(); } - return base64_encode($encrypted); + return $this->encoder->encode($encrypted); } /** @@ -31,7 +41,7 @@ public function encrypt(#[SensitiveParameter] string $string): string */ public function decrypt(#[SensitiveParameter] string $string): string { - $data = base64_decode($string); + $data = $this->encoder->decode($string); if (!openssl_private_decrypt($data, $decrypted, $this->privateKey)) { throw new FailedDecryptingValue(); diff --git a/src/Encoder/Base64Encoder.php b/src/Encoder/Base64Encoder.php new file mode 100644 index 0000000..3d71285 --- /dev/null +++ b/src/Encoder/Base64Encoder.php @@ -0,0 +1,24 @@ += 80200) { + $this->markTestSkipped( + 'All tests in this file have been skipped because they are do not apply to PHP 8.2 or higher.' + ); + } + } + + public function testCanBeCreated(): void + { + $encrypted = new SensitiveParameter(); + + $this->assertInstanceOf(SensitiveParameter::class, $encrypted); + } +} diff --git a/tests/Cipher/AdvancedEncryptionStandardCipherTest.php b/tests/Cipher/AdvancedEncryptionStandardCipherTest.php index 8e4d09d..863b902 100644 --- a/tests/Cipher/AdvancedEncryptionStandardCipherTest.php +++ b/tests/Cipher/AdvancedEncryptionStandardCipherTest.php @@ -3,16 +3,31 @@ namespace IlicMiljan\SecureProps\Tests\Cipher; use IlicMiljan\SecureProps\Cipher\AdvancedEncryptionStandardCipher; +use IlicMiljan\SecureProps\Cipher\Exception\CipherException; use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength; +use IlicMiljan\SecureProps\Encoder\Encoder; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class AdvancedEncryptionStandardCipherTest extends TestCase { + /** @var Encoder&MockObject */ + private $encoder; private AdvancedEncryptionStandardCipher $cipher; + private AdvancedEncryptionStandardCipher $cipherWithCustomEncoder; protected function setUp(): void { - $this->cipher = new AdvancedEncryptionStandardCipher(openssl_random_pseudo_bytes(32)); + $this->encoder = $this->createMock(Encoder::class); + + $this->cipher = new AdvancedEncryptionStandardCipher( + openssl_random_pseudo_bytes(32), + ); + + $this->cipherWithCustomEncoder = new AdvancedEncryptionStandardCipher( + openssl_random_pseudo_bytes(32), + $this->encoder + ); } public function testConstructWithInvalidKeyLengthThrowsException(): void @@ -22,6 +37,9 @@ public function testConstructWithInvalidKeyLengthThrowsException(): void new AdvancedEncryptionStandardCipher(openssl_random_pseudo_bytes(16)); } + /** + * @throws CipherException + */ public function testEncryptAndDecryptSuccessfully(): void { $encryptedString = $this->cipher->encrypt('plainText'); @@ -29,4 +47,23 @@ public function testEncryptAndDecryptSuccessfully(): void $this->assertEquals('plainText', $decryptedString); } + + /** + * @throws CipherException + */ + public function testEncryptAndDecryptWithCustomEncoder(): void + { + $this->encoder->method('encode')->willReturnCallback(function ($data) { + return $data; + }); + + $this->encoder->method('decode')->willReturnCallback(function ($data) { + return $data; + }); + + $encryptedText = $this->cipherWithCustomEncoder->encrypt('plainText'); + $decryptedText = $this->cipherWithCustomEncoder->decrypt($encryptedText); + + $this->assertEquals('plainText', $decryptedText); + } } diff --git a/tests/Cipher/AsymmetricEncryptionCipherTest.php b/tests/Cipher/AsymmetricEncryptionCipherTest.php index 356ad23..52f191b 100644 --- a/tests/Cipher/AsymmetricEncryptionCipherTest.php +++ b/tests/Cipher/AsymmetricEncryptionCipherTest.php @@ -3,15 +3,23 @@ namespace IlicMiljan\SecureProps\Tests\Cipher; use IlicMiljan\SecureProps\Cipher\AsymmetricEncryptionCipher; +use IlicMiljan\SecureProps\Cipher\Exception\CipherException; +use IlicMiljan\SecureProps\Encoder\Encoder; use OpenSSLAsymmetricKey; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class AsymmetricEncryptionCipherTest extends TestCase { + /** @var Encoder&MockObject */ + private $encoder; private AsymmetricEncryptionCipher $cipher; + private AsymmetricEncryptionCipher $cipherWithCustomEncoder; protected function setUp(): void { + $this->encoder = $this->createMock(Encoder::class); + /** @var OpenSSLAsymmetricKey $asymmetricKey */ $asymmetricKey = openssl_pkey_new(); /** @var string[] $asymmetricKeyDetails */ @@ -20,8 +28,17 @@ protected function setUp(): void openssl_pkey_export($asymmetricKey, $privateKey); $this->cipher = new AsymmetricEncryptionCipher($asymmetricKeyDetails['key'], $privateKey); + + $this->cipherWithCustomEncoder = new AsymmetricEncryptionCipher( + $asymmetricKeyDetails['key'], + $privateKey, + $this->encoder + ); } + /** + * @throws CipherException + */ public function testEncryptAndDecryptSuccessfully(): void { $encryptedString = $this->cipher->encrypt('plainText'); @@ -29,4 +46,23 @@ public function testEncryptAndDecryptSuccessfully(): void $this->assertEquals('plainText', $decryptedString); } + + /** + * @throws CipherException + */ + public function testEncryptAndDecryptWithCustomEncoder(): void + { + $this->encoder->method('encode')->willReturnCallback(function ($data) { + return $data; + }); + + $this->encoder->method('decode')->willReturnCallback(function ($data) { + return $data; + }); + + $encryptedText = $this->cipherWithCustomEncoder->encrypt('plainText'); + $decryptedText = $this->cipherWithCustomEncoder->decrypt($encryptedText); + + $this->assertEquals('plainText', $decryptedText); + } } diff --git a/tests/Encoder/Base64EncoderTest.php b/tests/Encoder/Base64EncoderTest.php new file mode 100644 index 0000000..0a3c5f9 --- /dev/null +++ b/tests/Encoder/Base64EncoderTest.php @@ -0,0 +1,52 @@ +encoder = new Base64Encoder(); + } + + public function testEncode(): void + { + $string = "Hello, World!"; + $expectedEncodedString = base64_encode($string); + + $this->assertEquals( + $expectedEncodedString, + $this->encoder->encode($string), + "The encoded string does not match the expected output." + ); + } + + public function testDecode(): void + { + $encodedString = "SGVsbG8sIFdvcmxkIQ=="; + $expectedDecodedString = base64_decode($encodedString); + + $this->assertEquals( + $expectedDecodedString, + $this->encoder->decode($encodedString) + ); + } + + public function testEncodeDecode(): void + { + $originalString = "Test encode and decode!"; + + $encodedString = $this->encoder->encode($originalString); + $decodedString = $this->encoder->decode($encodedString); + + $this->assertEquals( + $originalString, + $decodedString + ); + } +} diff --git a/tests/Encoder/NullEncoderTest.php b/tests/Encoder/NullEncoderTest.php new file mode 100644 index 0000000..a02bf52 --- /dev/null +++ b/tests/Encoder/NullEncoderTest.php @@ -0,0 +1,50 @@ +encoder = new NullEncoder(); + } + + public function testEncode(): void + { + $string = "This is a test string."; + + $this->assertEquals( + $string, + $this->encoder->encode($string) + ); + } + + public function testDecode(): void + { + $string = "Decoding test string."; + + $this->assertEquals( + $string, + $this->encoder->decode($string) + ); + } + + public function testEncodeDecode(): void + { + $originalString = "Encode then decode this!"; + + $encodedString = $this->encoder->encode($originalString); + $decodedString = $this->encoder->decode($encodedString); + + /** @noinspection PhpConditionAlreadyCheckedInspection */ + $this->assertEquals( + $originalString, + $decodedString, + ); + } +} diff --git a/tests/Exception/SingleEncryptedAttributeExpectedTest.php b/tests/Exception/SingleEncryptedAttributeExpectedTest.php new file mode 100644 index 0000000..6970932 --- /dev/null +++ b/tests/Exception/SingleEncryptedAttributeExpectedTest.php @@ -0,0 +1,48 @@ +count = 1; + } + + public function testCanBeCreated(): void + { + $exception = new SingleEncryptedAttributeExpected($this->count); + + $this->assertInstanceOf(SingleEncryptedAttributeExpected::class, $exception); + } + + public function testReturnsCount(): void + { + $exception = new SingleEncryptedAttributeExpected($this->count); + + $this->assertEquals($this->count, $exception->getCount()); + } + + public function testPreviousExceptionIsStored(): void + { + $previous = new RuntimeException('Previous exception'); + $exception = new SingleEncryptedAttributeExpected($this->count, $previous); + + $this->assertSame($previous, $exception->getPrevious()); + } + + public function testImplementsEncryptionServiceExceptionInterface(): void + { + $exception = new SingleEncryptedAttributeExpected($this->count); + + $this->assertInstanceOf(EncryptionServiceException::class, $exception); + } +}