diff --git a/CHANGELOG.md b/CHANGELOG.md index 025f0b18..d99a042b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `JMS\Payment\CoreBundle\Cryptography\MCryptEncryptionService` has been deprecated and will be removed in 2.0 (`mcrypt` has been deprecated in PHP 7.1 and is removed in PHP 7.2). Refer to http://jmspaymentcorebundle.readthedocs.io/en/latest/mcrypt.html for instructions on how to migrate away from `mcrypt`. ### Added -- Added support for custom crypto providers. +- Added support for custom encryption providers. - Added support for data encryption with [defuse/php-encryption](https://github.com/defuse/php-encryption). -- Added ability to configure which crypto provider should be used. Current available options are `mcrypt` (not recommended since it will be removed in PHP 7.2) and `defuse_php_encryption`. +- Added ability to configure which encryption provider should be used. Current available options are `mcrypt` (not recommended since it will be removed in PHP 7.2) and `defuse_php_encryption`. ### Removed - Removed support for PHP 5.3. If you're still using PHP 5.3, please consider upgrading since it reached End Of Life in August 2014. Otherwise, use `1.2.*`. diff --git a/Command/ReencryptDataCommand.php b/Command/ReencryptDataCommand.php new file mode 100644 index 00000000..2312dc1e --- /dev/null +++ b/Command/ReencryptDataCommand.php @@ -0,0 +1,139 @@ +setName('jms_payment_core:reencrypt-data') + ->setDescription('Re-encrypt encrypted database data') + ->addArgument( + 'src', + InputArgument::REQUIRED, + 'The cryptography provider with which data currently in the database was encrypted. + Possible values are mcrypt and defuse_php_encryption' + ) + ->addArgument( + 'src-secret', + InputArgument::REQUIRED, + 'The current encryption key' + ) + ->addArgument( + 'dest', + InputArgument::REQUIRED, + 'The new cryptography provider to use for encrypting data. + Possible values are mcrypt and defuse_php_encryption' + ) + ->addArgument( + 'dest-secret', + InputArgument::REQUIRED, + 'The new encryption key' + ) + ->addOption( + 'src-mcrypt-cipher', + null, + InputOption::VALUE_OPTIONAL, + 'The mcrypt cipher for the src provider', + 'rijndael-256' + ) + ->addOption( + 'src-mcrypt-mode', + null, + InputOption::VALUE_OPTIONAL, + 'The mcrypt mode for the src provider', + 'ctr' + ) + ->addOption( + 'dest-mcrypt-cipher', + null, + InputOption::VALUE_OPTIONAL, + 'The mcrypt cipher for the dest provider', + 'rijndael-256' + ) + ->addOption( + 'dest-mcrypt-mode', + null, + InputOption::VALUE_OPTIONAL, + 'The mcrypt mode for the dest provider', + 'ctr' + ) + ->addOption( + 'em', + null, + InputOption::VALUE_OPTIONAL, + 'The entity manager to use', + 'default' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $providers = $this->getProviders($input); + + $em = $this->getContainer()->get('doctrine')->getManager($input->getOption('em')); + + $query = $em->createQuery('SELECT pi from JMSPaymentCoreBundle:PaymentInstruction pi') + ->setFirstResult(0) + ->setMaxResults(128); + + $paginator = new Paginator($query, $fetchJoinCollection = false); + + foreach ($paginator as $pi) { + var_dump($pi->getExtendedData()); + } + } + + private function getProviders(InputInterface $input) + { + $supportedProviders = array( + 'mcrypt' => MCryptEncryptionService::class, + 'defuse_php_encryption' => DefusePhpEncryptionService::class, + ); + + foreach ([$input->getArgument('src'), $input->getArgument('dest')] as $provider) { + if (!array_key_exists($provider, $supportedProviders)) { + throw new \InvalidArgumentException("Unsupported cryptography provider: $provider"); + } + } + + $providers = array(); + + foreach (array('src', 'dest') as $providerType) { + foreach (($options = $input->getOptions()) as $key => $value) { + $options[str_replace("$providerType-", '', $key)] = $value; + } + + foreach ($supportedProviders as $name => $class) { + if ($name !== $input->getArgument($providerType)) { + continue; + } + + switch ($input->getArgument($providerType)) { + case 'mcrypt': + $providers[$providerType] = new MCryptEncryptionService( + $input->getArgument("$providerType-secret"), + $options['mcrypt-cipher'], + $options['mcrypt-mode'] + ); + break; + case 'defuse_php_encryption': + $providers[$providerType] = new DefusePhpEncryptionService($secret); + break; + } + } + } + + return $providers; + } +} diff --git a/Resources/doc/data_encryption.rst b/Resources/doc/data_encryption.rst new file mode 100644 index 00000000..c0fa067b --- /dev/null +++ b/Resources/doc/data_encryption.rst @@ -0,0 +1,7 @@ +Data Encryption +=============== + +- What is encrypted +- Usage (form) +- Crypto providers (including custom, Defuse vs mcrypt and BC) +- Migrating from mcrypt to defuse diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index b97b4ea6..d9acd5f8 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -33,6 +33,7 @@ License setup payment_form events + data_encryption plugins model backends diff --git a/Resources/doc/mcrypt.rst b/Resources/doc/mcrypt.rst new file mode 100644 index 00000000..e69de29b diff --git a/Resources/doc/setup.rst b/Resources/doc/setup.rst index 5ac499fb..ea046c33 100644 --- a/Resources/doc/setup.rst +++ b/Resources/doc/setup.rst @@ -25,15 +25,16 @@ And register the bundle in your ``AppKernel.php``: Configuration ------------- -The configuration is as simple as setting a random secret which will be used for encrypting data, in case this is requested. +The configuration is as simple as setting a random secret which will be used for encrypting data. For more information on how this bundle uses encryption see :doc:`data_encryption`. -You can generate the secret with the following command: +You can generate a secret with the following command: .. code-block :: bash - # Feel free to increase the length of the generated string by - # passing a larger number to openssl_random_pseudo_bytes - php -r 'echo bin2hex(openssl_random_pseudo_bytes(16))."\n";' + php -r ' + require "vendor/autoload.php"; + echo (\Defuse\Crypto\Key::createNewRandomKey())->saveToAsciiSafeString()."\n"; + ' And then use it in your configuration: @@ -44,15 +45,16 @@ And then use it in your configuration: # app/config/config.yml jms_payment_core: encryption: - secret: yoursecret + provider: defuse_php_encryption + secret: output_of_above_command -.. note :: +.. warning :: - If you change the secret, all data encrypted with the old secret will become unreadable. + If you change the ``secret`` or the ``crypto`` provider, all encrypted data will become unreadable. Create database tables ---------------------- -This bundle requires a few database tables to function. You can create these tables as follows. +This bundle requires a few database tables, which you can create as follows. If you're not using database migrations: @@ -67,14 +69,13 @@ Or, if you're using migrations: bin/console doctrine:migrations:diff bin/console doctrine:migrations:migrate -.. warning :: +.. note :: It's assumed you have entity auto mapping enabled, which is usually the case. If you don't, you need to either enable it: .. code-block :: yaml # app/config/config.yml - doctrine: orm: auto_mapping: true @@ -84,7 +85,6 @@ Or, if you're using migrations: .. code-block :: yaml # app/config/config.yml - doctrine: orm: mappings: diff --git a/Tests/Functional/Command/ReencryptDataCommandTest.php b/Tests/Functional/Command/ReencryptDataCommandTest.php new file mode 100644 index 00000000..5161129f --- /dev/null +++ b/Tests/Functional/Command/ReencryptDataCommandTest.php @@ -0,0 +1,128 @@ +add(new ReencryptDataCommand()); + + $this->command = $application->find('jms_payment_core:reencrypt-data'); + + parent::setUp(); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage foo + */ + public function testUnsupportedSourceProvider() + { + $this->execute(array( + 'src' => 'foo', + 'src-secret' => 'foo-secret', + 'dest' => 'defuse_php_encryption', + 'dest-secret' => 'bar-secret', + )); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage bar + */ + public function testUnsupportedDestProvider() + { + $this->execute(array( + 'src' => 'mcrypt', + 'src-secret' => 'foo-secret', + 'dest' => 'bar', + 'dest-secret' => 'bar-secret', + )); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The cipher "foo" is not supported. + */ + public function testMcryptSrcCipher() + { + $this->execute(array( + 'src' => 'mcrypt', + 'src-secret' => 'foo-secret', + '--src-mcrypt-cipher' => 'foo', + 'dest' => 'mcrypt', + 'dest-secret' => 'bar-secret', + )); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The cipher "bar" is not supported. + */ + public function testMcryptDestCipher() + { + $this->execute(array( + 'src' => 'mcrypt', + 'src-secret' => 'foo-secret', + 'dest' => 'mcrypt', + 'dest-secret' => 'bar-secret', + '--dest-mcrypt-cipher' => 'bar', + )); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The mode "foo" is not supported. + */ + public function testMcryptSrcMode() + { + $this->execute(array( + 'src' => 'mcrypt', + 'src-secret' => 'foo-secret', + '--src-mcrypt-mode' => 'foo', + 'dest' => 'mcrypt', + 'dest-secret' => 'bar-secret', + )); + } + + /** + * @runInSeparateProcess + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The mode "bar" is not supported. + */ + public function testMcryptDestMode() + { + $this->execute(array( + 'src' => 'mcrypt', + 'src-secret' => 'foo-secret', + 'dest' => 'mcrypt', + 'dest-secret' => 'bar-secret', + '--dest-mcrypt-mode' => 'bar', + )); + } + + private function execute(array $input) + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute(array_merge(array( + 'command' => $this->command->getName(), + ), $input)); + + return $commandTester->getDisplay(); + } +}