Skip to content

Commit

Permalink
Merge pull request #18 from williamjehanne/master
Browse files Browse the repository at this point in the history
feat(decypt): add EncryptionException to detect openssl_encrypt and openssl_decrypt failures
  • Loading branch information
Benoit Maziere authored Sep 3, 2019
2 parents e5cd05c + 1f1f06f commit 6867423
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 6 deletions.
9 changes: 8 additions & 1 deletion Controller/LogsAdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ekino\DataProtectionBundle\Controller;

use Ekino\DataProtectionBundle\Encryptor\EncryptorInterface;
use Ekino\DataProtectionBundle\Exception\EncryptionException;
use Ekino\DataProtectionBundle\Form\Type\LogType;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -56,7 +57,13 @@ public function decryptEncryptAction(Request $request): Response
if ($form->isSubmitted() && $form->isValid()) {
$log = $form->getData();
$content = $log->getContent();
$results = $log->isDecryptAction() ? $this->getDecryptedResults($content) : $this->getEncryptedResult($content);
try {
$results = $log->isDecryptAction() ? $this->getDecryptedResults($content) : $this->getEncryptedResult($content);
} catch (EncryptionException $e) {
$message = $log->isDecryptAction() ? 'admin.logs.decrypt.error' : 'admin.logs.encrypt.error';

$this->addFlash('error', $this->trans($message, [], 'EkinoDataProtectionBundle'));
}
}

return $this->renderWithExtraParams('@EkinoDataProtection/LogsAdmin/decrypt.html.twig', [
Expand Down
13 changes: 12 additions & 1 deletion Encryptor/Encryptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Ekino\DataProtectionBundle\Encryptor;

use Ekino\DataProtectionBundle\Exception\EncryptionException;

/**
* Encrypt data using the given cipher method.
*
Expand Down Expand Up @@ -52,6 +54,10 @@ public function encrypt(string $data): string
$iv = openssl_random_pseudo_bytes($ivSize);
$cipherText = openssl_encrypt($data, $this->method, $this->secret, OPENSSL_RAW_DATA, $iv);

if ($cipherText === false) {
throw new EncryptionException('Unexpected failure in openssl_encrypt.');
}

return base64_encode($iv.$cipherText);
}

Expand All @@ -64,7 +70,12 @@ public function decrypt(string $data): string
$ivSize = openssl_cipher_iv_length($this->method);
$iv = mb_substr($data, 0, $ivSize, '8bit');
$cipherText = mb_substr($data, $ivSize, null, '8bit');
$decrypt = openssl_decrypt($cipherText, $this->method, $this->secret, OPENSSL_RAW_DATA, $iv);

if ($decrypt === false) {
throw new EncryptionException('Unexpected failure in openssl_decrypt.');
}

return openssl_decrypt($cipherText, $this->method, $this->secret, OPENSSL_RAW_DATA, $iv);
return $decrypt;
}
}
6 changes: 6 additions & 0 deletions Encryptor/EncryptorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Ekino\DataProtectionBundle\Encryptor;

use Ekino\DataProtectionBundle\Exception\EncryptionException;

/**
* @author Rémi Marseille <[email protected]>
*/
Expand All @@ -21,13 +23,17 @@ interface EncryptorInterface
/**
* @param string $data
*
* @throws EncryptionException
*
* @return string
*/
public function encrypt(string $data): string;

/**
* @param string $data
*
* @throws EncryptionException
*
* @return string
*/
public function decrypt(string $data): string;
Expand Down
23 changes: 23 additions & 0 deletions Exception/EncryptionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ekino/data-protection-bundle project.
*
* (c) Ekino
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ekino\DataProtectionBundle\Exception;

/**
* Class EncryptionException.
*
* @author William JEHANNE <[email protected]>
*/
class EncryptionException extends \RuntimeException
{
}
8 changes: 8 additions & 0 deletions Resources/translations/EkinoDataProtectionBundle.en.xliff
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
<source>admin.logs.title</source>
<target>Logs</target>
</trans-unit>
<trans-unit id="admin.logs.decrypt.error">
<source>admin.logs.decrypt.error</source>
<target>An error occurred during the decryption, please check the content you submitted.</target>
</trans-unit>
<trans-unit id="admin.logs.encrypt.error">
<source>admin.logs.encrypt.error</source>
<target>An error occurred during the encryption, please check the content you submitted.</target>
</trans-unit>
</body>
</file>
</xliff>
8 changes: 8 additions & 0 deletions Resources/translations/EkinoDataProtectionBundle.fr.xliff
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
<source>admin.logs.title</source>
<target>Logs</target>
</trans-unit>
<trans-unit id="admin.logs.decrypt.error">
<source>admin.logs.decrypt.error</source>
<target>Une erreur s'est produite pendant le déchiffrement, veuillez vérifier le contenu que vous avez soumis.</target>
</trans-unit>
<trans-unit id="admin.logs.encrypt.error">
<source>admin.logs.encrypt.error</source>
<target>Une erreur s'est produite pendant le chiffrement, veuillez vérifier le contenu que vous avez soumis.</target>
</trans-unit>
</body>
</file>
</xliff>
115 changes: 115 additions & 0 deletions Tests/Controller/LogsAdminControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ekino/data-protection-bundle project.
*
* (c) Ekino
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ekino\DataProtectionBundle\Tests\Controller;

use Ekino\DataProtectionBundle\Controller\LogsAdminController;
use Ekino\DataProtectionBundle\Encryptor\EncryptorInterface;
use Ekino\DataProtectionBundle\Exception\EncryptionException;
use Ekino\DataProtectionBundle\Form\DataClass\Log;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\TranslatorInterface;

/**
* Class LogsAdminControllerTest.
*
* @author William JEHANNE <[email protected]>
*/
class LogsAdminControllerTest extends TestCase
{
/**
* @var LogsAdminController|MockObject
*/
private $controller;

/**
* @var EncryptorInterface|MockObject
*/
private $encryptor;

/**
* Initialize test LogsAdminControllerTest.
*/
protected function setUp(): void
{
$this->encryptor = $this->createMock(EncryptorInterface::class);
$this->controller = $this->getMockBuilder(LogsAdminController::class)
->setConstructorArgs([$this->encryptor])
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->setMethods(['addFlash', 'createForm', 'get', 'renderWithExtraParams'])
->getMock();
}

/**
* Test decryptEncryptAction method of LogsAdminController.
*/
public function testDecryptEncryptAction(): void
{
$form = $this->createMock(Form::class);
$log = $this->createMock(Log::class);
$log->expects($this->once())->method('getContent')->willReturn('foo');

$response = $this->createMock(Response::class);
$this->controller->expects($this->never())->method('addFlash');
$this->controller->expects($this->once())->method('renderWithExtraParams')->willReturn($response);

$form->expects($this->once())->method('handleRequest');
$form->expects($this->once())->method('isSubmitted')->willReturn(true);
$form->expects($this->once())->method('isValid')->willReturn(true);
$form->expects($this->once())->method('getData')->willReturn($log);

$this->controller->expects($this->once())->method('createForm')->willReturn($form);

$request = $this->createMock(Request::class);

$this->controller->decryptEncryptAction($request);
}

/**
* Test decryptEncryptAction method of LogsAdminController not ok.
*/
public function testDecryptEncryptActionNok(): void
{
$this->encryptor->expects($this->any())->method('encrypt')->willThrowException(new EncryptionException());

$form = $this->createMock(Form::class);
$log = $this->createMock(Log::class);
$log->expects($this->once())->method('getContent')->willReturn('foo');
$log->expects($this->any())->method('isDecryptAction')->willReturn(false);

$response = $this->createMock(Response::class);
$this->controller->expects($this->once())->method('addFlash')->with('error');
$this->controller->expects($this->once())->method('renderWithExtraParams')->willReturn($response);

$form->expects($this->once())->method('handleRequest');
$form->expects($this->once())->method('isSubmitted')->willReturn(true);
$form->expects($this->once())->method('isValid')->willReturn(true);
$form->expects($this->once())->method('getData')->willReturn($log);

$this->controller->expects($this->once())->method('createForm')->willReturn($form);

$request = $this->createMock(Request::class);

$translator = $this->createMock(TranslatorInterface::class);
$translator->expects($this->once())->method('trans')->willReturn('admin.logs.encrypt.error');
$this->controller->expects($this->once())->method('get')->with($this->equalTo('translator'))->willReturn($translator);

$this->controller->decryptEncryptAction($request);
}
}
60 changes: 56 additions & 4 deletions Tests/Encryptor/EncryptorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ekino\DataProtectionBundle\Tests\Encryptor;

use Ekino\DataProtectionBundle\Encryptor\Encryptor;
use Ekino\DataProtectionBundle\Exception\EncryptionException;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -24,15 +25,66 @@
*/
class EncryptorTest extends TestCase
{
/**
* @var string|bool $encryptData
*/
public static $encryptData = true;

/**
* @var string
*/
private $rawData = 'my raw data';

/**
* @var Encryptor
*/
private $encryptor;

/**
* Initialize test EncryptorTest.
*/
protected function setUp(): void
{
$this->encryptor = new Encryptor('aes-256-cbc', 'foo');
}

/**
* Test encrypt & decrypt.
*/
public function testEncryptAndDecrypt(): void
{
$rawData = 'my raw data';
$encryptor = new Encryptor('aes-256-cbc', 'foo');
$encryptedData = $encryptor->encrypt($rawData);
$encryptedData = $this->encryptor->encrypt($this->rawData);

$this->assertSame($this->rawData, $this->encryptor->decrypt($encryptedData));
}

/**
* Test encrypt not ok.
*/
public function testEncryptNok(): void
{
self::$encryptData = false;

$this->expectException(EncryptionException::class);
$this->expectExceptionMessage('Unexpected failure in openssl_encrypt.');

$this->assertSame($rawData, $encryptor->decrypt($encryptedData));
$encryptedData = $this->encryptor->encrypt($this->rawData);
}

/**
* Test decrypt not ok.
*/
public function testDecryptNok(): void
{
$this->expectException(EncryptionException::class);
$this->expectExceptionMessage('Unexpected failure in openssl_decrypt.');

$this->encryptor->decrypt('dummy-example-for-testing-purpose');
}
}

namespace Ekino\DataProtectionBundle\Encryptor;

function openssl_encrypt($data, $method, $key, $options, $iv) {
return \Ekino\DataProtectionBundle\Tests\Encryptor\EncryptorTest::$encryptData ? \openssl_encrypt($data, $method, $key, $options, $iv) : false;
}

0 comments on commit 6867423

Please sign in to comment.