Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(decypt): add EncryptionException to detect openssl_encrypt and openssl_decrypt failures #18

Merged
merged 1 commit into from Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion Controller/LogsAdminController.php
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
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.');
wjehanne marked this conversation as resolved.
Show resolved Hide resolved
}

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);
wjehanne marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Up @@ -13,6 +13,8 @@

namespace Ekino\DataProtectionBundle\Encryptor;

use Ekino\DataProtectionBundle\Exception\EncryptionException;

/**
* @author Rémi Marseille <remi.marseille@ekino.com>
*/
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
@@ -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 <william.jehanne@ekino.com>
*/
class EncryptionException extends \RuntimeException
{
}
8 changes: 8 additions & 0 deletions Resources/translations/EkinoDataProtectionBundle.en.xliff
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
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
@@ -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 <william.jehanne@ekino.com>
*/
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
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;
}