From b34471246eb3ea13b5a5b48e1f632fc260073ea5 Mon Sep 17 00:00:00 2001 From: boite Date: Tue, 28 May 2019 14:54:08 +0100 Subject: [PATCH 1/6] Update to modern dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 2947016..56894e8 100644 --- a/composer.json +++ b/composer.json @@ -12,11 +12,11 @@ } ], "require": { - "php": ">=5.3.0" + "php": "^7.2" }, "require-dev": { - "symfony/console": "~2.4", - "aws/aws-sdk-php": "~2.7" + "symfony/console": "^4", + "aws/aws-sdk-php": "^3" }, "suggest": { "linkorb/bergen-client-php": "To use the Bergen Adapter." From 635a16695c704afcf88df6738aee213422652eed Mon Sep 17 00:00:00 2001 From: boite Date: Tue, 28 May 2019 14:54:22 +0100 Subject: [PATCH 2/6] Add BuildableAdapterInterface to replace StorageAdapterInterface::build which was temporarily disabled in commit af4fa8329b4bc32173fcb188966b4cdd823a2219. --- src/Adapter/BergenAdapter.php | 2 +- src/Adapter/BuildableAdapterInterface.php | 15 +++++++++++++++ src/Adapter/FileAdapter.php | 2 +- src/Adapter/GridFsAdapter.php | 2 +- src/Adapter/PdoAdapter.php | 2 +- src/Adapter/S3Adapter.php | 2 +- src/Adapter/StorageAdapterInterface.php | 7 ------- 7 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 src/Adapter/BuildableAdapterInterface.php diff --git a/src/Adapter/BergenAdapter.php b/src/Adapter/BergenAdapter.php index 94f4d30..1cf800b 100644 --- a/src/Adapter/BergenAdapter.php +++ b/src/Adapter/BergenAdapter.php @@ -8,7 +8,7 @@ use Bergen\Client\V1\V1StorageClient; use GuzzleHttp\RequestOptions; -class BergenAdapter implements StorageAdapterInterface +class BergenAdapter implements BuildableAdapterInterface, StorageAdapterInterface { /** * @var \Bergen\Client\V1\V1StorageClient diff --git a/src/Adapter/BuildableAdapterInterface.php b/src/Adapter/BuildableAdapterInterface.php new file mode 100644 index 0000000..e7b63eb --- /dev/null +++ b/src/Adapter/BuildableAdapterInterface.php @@ -0,0 +1,15 @@ + Date: Thu, 30 May 2019 17:23:09 +0100 Subject: [PATCH 3/6] Add tooling --- .gitignore | 2 ++ .php_cs.dist | 17 +++++++++++++++++ composer.json | 4 +++- phpunit.xml.dist | 27 +++++++++++++++++++++++++++ tests/bootstrap.php | 3 +++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .php_cs.dist create mode 100644 phpunit.xml.dist create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore index 1b59987..b2ea09c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .DS_Store objectstorage.conf +.php_cs.cache +.phpunit.result.cache ### Eclipse ### *.pydevproject diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..1f88099 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,17 @@ +in(__DIR__.'/src') +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR1' => true, + '@PSR2' => true, + '@Symfony' => true, + 'phpdoc_align' => false, + 'concat_space' => ['spacing' => 'one'], + 'array_syntax' => ['syntax' => 'short'], + ]) + ->setFinder($finder) +; diff --git a/composer.json b/composer.json index 56894e8..37d7812 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,9 @@ }, "require-dev": { "symfony/console": "^4", - "aws/aws-sdk-php": "^3" + "aws/aws-sdk-php": "^3", + "friendsofphp/php-cs-fixer": "^2.15", + "phpunit/phpunit": "^8.1" }, "suggest": { "linkorb/bergen-client-php": "To use the Bergen Adapter." diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..fb876ee --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + + + + + + + + + tests + + + + + + src + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..ebf2d4f --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ + Date: Thu, 30 May 2019 17:29:10 +0100 Subject: [PATCH 4/6] Fix per coding standards --- src/Adapter/BergenAdapter.php | 1 + src/Adapter/Bzip2Adapter.php | 5 ++- src/Adapter/FileAdapter.php | 20 +++++------ src/Adapter/GridFsAdapter.php | 13 ++++--- src/Adapter/MetaDataAdapterInterface.php | 2 ++ src/Adapter/PdoAdapter.php | 44 ++++++++++++------------ src/Adapter/S3Adapter.php | 40 ++++++++++----------- src/Adapter/StorageAdapterInterface.php | 2 ++ src/Command/DecryptCommand.php | 36 ++++++++++--------- src/Command/DeleteCommand.php | 3 +- src/Command/DownloadCommand.php | 4 +-- src/Command/EncryptCommand.php | 19 +++++----- src/Command/GenerateKeyCommand.php | 20 +++++------ src/Command/ListCommand.php | 12 +++---- src/Command/UploadCommand.php | 4 +-- src/Key.php | 1 + src/Service.php | 2 +- src/Utils.php | 24 ++++++------- 18 files changed, 124 insertions(+), 128 deletions(-) diff --git a/src/Adapter/BergenAdapter.php b/src/Adapter/BergenAdapter.php index 1cf800b..f17c60d 100644 --- a/src/Adapter/BergenAdapter.php +++ b/src/Adapter/BergenAdapter.php @@ -60,6 +60,7 @@ public function getData($key) } catch (UnexpectedResponseError $e) { throw new AdapterException('Unable to get data.', null, $e); } + return (string) $response->getBody(); } diff --git a/src/Adapter/Bzip2Adapter.php b/src/Adapter/Bzip2Adapter.php index e636b67..e640b0d 100644 --- a/src/Adapter/Bzip2Adapter.php +++ b/src/Adapter/Bzip2Adapter.php @@ -2,9 +2,6 @@ namespace ObjectStorage\Adapter; -use RuntimeException; -use InvalidArgumentException; - class Bzip2Adapter implements StorageAdapterInterface { private $child; @@ -19,6 +16,7 @@ public function __construct(StorageAdapterInterface $child, $level) public function setData($key, $data) { $data = bzcompress($data, $this->level); + return $this->child->setData($key, $data); } @@ -26,6 +24,7 @@ public function getData($key) { $data = $this->child->getData($key); $data = bzdecompress($data); + return $data; } diff --git a/src/Adapter/FileAdapter.php b/src/Adapter/FileAdapter.php index 6aaccdb..5546dac 100644 --- a/src/Adapter/FileAdapter.php +++ b/src/Adapter/FileAdapter.php @@ -29,7 +29,7 @@ public function __construct($path) public function setPath($path) { if (!file_exists($path)) { - throw new RuntimeException("Path does not exist: " . $path); + throw new RuntimeException('Path does not exist: ' . $path); } $this->path = $path; } @@ -38,26 +38,26 @@ public function setData($key, $data) { $pathinfo = $this->key2PathInfo($key); $this->ensureDirectory($this->path . $pathinfo['dirname']); - file_put_contents($this->path . $pathinfo['dirname'] . "/" . $pathinfo['filename'], $data); + file_put_contents($this->path . $pathinfo['dirname'] . '/' . $pathinfo['filename'], $data); } public function getData($key) { $pathinfo = $this->key2PathInfo($key); - if (!file_exists($this->path . $pathinfo['dirname'] . "/" . $pathinfo['filename'])) { - throw new RuntimeException("Key not found: " . $key); + if (!file_exists($this->path . $pathinfo['dirname'] . '/' . $pathinfo['filename'])) { + throw new RuntimeException('Key not found: ' . $key); } - $data = file_get_contents($this->path . $pathinfo['dirname'] . "/" . $pathinfo['filename']); + $data = file_get_contents($this->path . $pathinfo['dirname'] . '/' . $pathinfo['filename']); + return $data; } public function deleteData($key) { - $pathinfo = $this->key2PathInfo($key); - $filename = $this->path . $pathinfo['dirname'] . "/" . $pathinfo['filename']; + $filename = $this->path . $pathinfo['dirname'] . '/' . $pathinfo['filename']; if (!file_exists($filename)) { - throw new InvalidArgumentException("Key does not exist: " . $key); + throw new InvalidArgumentException('Key does not exist: ' . $key); } unlink($filename); @@ -66,7 +66,8 @@ public function deleteData($key) private function key2PathInfo($key) { $info = pathinfo($key); - return array("dirname" => $info['dirname'], "filename" => $info['basename']); + + return ['dirname' => $info['dirname'], 'filename' => $info['basename']]; } private function ensureDirectory($path) @@ -75,5 +76,4 @@ private function ensureDirectory($path) mkdir($path, 0777, true); } } - } diff --git a/src/Adapter/GridFsAdapter.php b/src/Adapter/GridFsAdapter.php index d58c93c..43a677a 100644 --- a/src/Adapter/GridFsAdapter.php +++ b/src/Adapter/GridFsAdapter.php @@ -3,7 +3,6 @@ namespace ObjectStorage\Adapter; use InvalidArgumentException; - use MongoClient; use MongoGridFS; @@ -19,7 +18,7 @@ public static function build(array $config) ); } if (!array_key_exists('dbname', $config) - || trim($config['dbname']) === '' + || '' === trim($config['dbname']) ) { throw new InvalidArgumentException( 'Unable to build GridFsAdapter: missing "dbname" from configuration.' @@ -42,26 +41,26 @@ public function __construct(MongoGridFS $gridfs) { $this->setGridFs($gridfs); } - + public function setGridFs($gridfs) { $this->gridfs = $gridfs; } - + public function setData($key, $data) { - $this->gridfs->storeBytes($data, array("filename" => $key, "_id" => $key)); + $this->gridfs->storeBytes($data, ['filename' => $key, '_id' => $key]); } public function getData($key) { $file = $this->gridfs->get($key); + return $file->getBytes(); } - + public function deleteData($key) { $this->gridfs->delete($key); } - } diff --git a/src/Adapter/MetaDataAdapterInterface.php b/src/Adapter/MetaDataAdapterInterface.php index c8f5051..69d88ff 100644 --- a/src/Adapter/MetaDataAdapterInterface.php +++ b/src/Adapter/MetaDataAdapterInterface.php @@ -5,6 +5,8 @@ interface MetaDataAdapterInterface { public function listKeys($key); + public function setKey($key, $data); + public function deleteKey($key); } diff --git a/src/Adapter/PdoAdapter.php b/src/Adapter/PdoAdapter.php index 5b7c199..703eb69 100644 --- a/src/Adapter/PdoAdapter.php +++ b/src/Adapter/PdoAdapter.php @@ -3,7 +3,6 @@ namespace ObjectStorage\Adapter; use PDO; -use ObjectStorage\Key; use InvalidArgumentException; class PdoAdapter implements BuildableAdapterInterface, StorageAdapterInterface @@ -14,14 +13,14 @@ class PdoAdapter implements BuildableAdapterInterface, StorageAdapterInterface public static function build(array $config) { if (!array_key_exists('dsn', $config) - || trim($config['dsn']) === '' + || '' === trim($config['dsn']) ) { throw new InvalidArgumentException( 'Unable to build PdoAdapter: missing "dsn" from configuration.' ); } if (!array_key_exists('tablename', $config) - || trim($config['tablename']) == '' + || '' == trim($config['tablename']) ) { throw new InvalidArgumentException( 'Unable to build PdoAdapter: missing "tablename" from configuration.' @@ -47,59 +46,60 @@ public function __construct(PDO $pdo, $tablename = 'objectstorage') $this->setPdo($pdo); $this->setTablename($tablename); } - + public function setPdo($pdo) { $this->pdo = $pdo; } - + public function setTablename($tablename) { if (!ctype_alnum($tablename)) { - throw new InvalidArgumentException("Only alphanumeric tablenames allowed"); + throw new InvalidArgumentException('Only alphanumeric tablenames allowed'); } $this->tablename = $tablename; } - + public function setData($key, $data) { $statement = $this->pdo->prepare( - "INSERT INTO " . $this->tablename . " + 'INSERT INTO ' . $this->tablename . ' (`objectkey`, `objectdata`) VALUES (:key, :data) ON DUPLICATE KEY UPDATE - `objectdata` = :data" + `objectdata` = :data' ); - $statement->bindParam(":data", $data, PDO::PARAM_STR); - $statement->bindParam(":key", $key, PDO::PARAM_STR); + $statement->bindParam(':data', $data, PDO::PARAM_STR); + $statement->bindParam(':key', $key, PDO::PARAM_STR); $statement->execute(); } public function getData($key) { $statement = $this->pdo->prepare( - "SELECT objectdata FROM " . $this->tablename . " - WHERE objectkey = :key" + 'SELECT objectdata FROM ' . $this->tablename . ' + WHERE objectkey = :key' ); - - $statement->bindParam(":key", $key, PDO::PARAM_STR); + + $statement->bindParam(':key', $key, PDO::PARAM_STR); $statement->execute(); $res = $statement->fetchAll(); - foreach($res as $r) { - return (string)$r['objectdata']; + foreach ($res as $r) { + return (string) $r['objectdata']; } + return null; } - + public function deleteData($key) { $statement = $this->pdo->prepare( - "DELETE FROM " . $this->tablename . " - WHERE objectkey = :key" + 'DELETE FROM ' . $this->tablename . ' + WHERE objectkey = :key' ); - - $statement->bindParam(":key", $key, PDO::PARAM_STR); + + $statement->bindParam(':key', $key, PDO::PARAM_STR); $statement->execute(); } } diff --git a/src/Adapter/S3Adapter.php b/src/Adapter/S3Adapter.php index 9e88c20..78bc6de 100644 --- a/src/Adapter/S3Adapter.php +++ b/src/Adapter/S3Adapter.php @@ -3,7 +3,6 @@ namespace ObjectStorage\Adapter; use InvalidArgumentException; - use Aws\S3\S3Client; class S3Adapter implements BuildableAdapterInterface, StorageAdapterInterface @@ -16,21 +15,21 @@ class S3Adapter implements BuildableAdapterInterface, StorageAdapterInterface public static function build(array $config) { if (!array_key_exists('access_key', $config) - || trim($config['access_key']) === '' + || '' === trim($config['access_key']) ) { throw new InvalidArgumentException( 'Unable to build S3Adapter: missing "access_key" from configuration.' ); } if (!array_key_exists('secret_key', $config) - || trim($config['secret_key']) == '' + || '' == trim($config['secret_key']) ) { throw new InvalidArgumentException( 'Unable to build S3Adapter: missing "secret_key" from configuration.' ); } if (!array_key_exists('bucketname', $config) - || trim($config['bucketname']) == '' + || '' == trim($config['bucketname']) ) { throw new InvalidArgumentException( 'Unable to build S3Adapter: missing "bucketname" from configuration.' @@ -57,7 +56,7 @@ public function __construct($s3client, $bucketname, $prefix = '') $this->setBucketName($bucketname); $this->setPrefix($prefix); } - + public function setS3Client($s3client) { $this->s3client = $s3client; @@ -65,27 +64,27 @@ public function setS3Client($s3client) public function setBucketName($bucketname) { - if (trim($bucketname)=='') { - throw new InvalidArgumentException("Invalid bucketname: " . $bucketname); + if ('' == trim($bucketname)) { + throw new InvalidArgumentException('Invalid bucketname: ' . $bucketname); } $this->bucketname = $bucketname; } - + public function setPrefix($prefix) { $this->prefix = $prefix; } - + public function setData($key, $data) { $key = $this->prefix . $key; $this->s3client->putObject( - array( + [ 'Bucket' => $this->bucketname, 'Key' => $key, 'Body' => $data, - 'ACL' => $this->defaultacl - ) + 'ACL' => $this->defaultacl, + ] ); } @@ -93,22 +92,23 @@ public function getData($key) { $key = $this->prefix . $key; $result = $this->s3client->getObject( - array( - 'Bucket' => $this->bucketname, + [ + 'Bucket' => $this->bucketname, 'Key' => $key, - ) + ] ); - return (string)$result['Body']; + + return (string) $result['Body']; } - + public function deleteData($key) { $key = $this->prefix . $key; $this->s3client->deleteObject( - array( - 'Bucket' => $this->bucketname, + [ + 'Bucket' => $this->bucketname, 'Key' => $key, - ) + ] ); } } diff --git a/src/Adapter/StorageAdapterInterface.php b/src/Adapter/StorageAdapterInterface.php index d04626a..044bd35 100644 --- a/src/Adapter/StorageAdapterInterface.php +++ b/src/Adapter/StorageAdapterInterface.php @@ -5,6 +5,8 @@ interface StorageAdapterInterface { public function getData($key); + public function setData($key, $data); + public function deleteData($key); } diff --git a/src/Command/DecryptCommand.php b/src/Command/DecryptCommand.php index ed5df0f..c1ed1c8 100644 --- a/src/Command/DecryptCommand.php +++ b/src/Command/DecryptCommand.php @@ -5,7 +5,6 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use RuntimeException; @@ -24,39 +23,42 @@ protected function configure() ) ; } - - private function strtohex($x) + + private function strtohex($x) { - $s=''; + $s = ''; foreach (str_split($x) as $c) { - $s.=sprintf("%02X", ord($c)); + $s .= sprintf('%02X', ord($c)); } - return($s); + + return $s; } - - private function hextostr($hex){ - $string=''; - for ($i=0; $i < strlen($hex)-1; $i+=2) { - $string .= chr(hexdec($hex[$i].$hex[$i+1])); + + private function hextostr($hex) + { + $string = ''; + for ($i = 0; $i < strlen($hex) - 1; $i += 2) { + $string .= chr(hexdec($hex[$i] . $hex[$i + 1])); } + return $string; } - + protected function execute(InputInterface $input, OutputInterface $output) { $key = getenv('OBJECTSTORAGE_ENCRYPTION_KEY'); $iv = getenv('OBJECTSTORAGE_ENCRYPTION_IV'); if (!$key || !$iv) { - throw new RuntimeException("Could not obtain encryption key + iv from environment"); + throw new RuntimeException('Could not obtain encryption key + iv from environment'); } - + $filename = $input->getArgument('filename'); - + $key = $this->hextostr($key); $iv = $this->hextostr($iv); - - $data =file_get_contents($filename); + + $data = file_get_contents($filename); $res = openssl_decrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); echo $res; } diff --git a/src/Command/DeleteCommand.php b/src/Command/DeleteCommand.php index 305dc3c..aabea0c 100644 --- a/src/Command/DeleteCommand.php +++ b/src/Command/DeleteCommand.php @@ -11,7 +11,6 @@ class DeleteCommand extends Command { - protected function configure() { $this->setName('objectstorage:delete') @@ -41,6 +40,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Deleting key '" . $key . "'"); $service->delete($key); - $output->writeln("Done"); + $output->writeln('Done'); } } diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php index 7e08131..1ea01c9 100644 --- a/src/Command/DownloadCommand.php +++ b/src/Command/DownloadCommand.php @@ -11,7 +11,6 @@ class DownloadCommand extends Command { - protected function configure() { $this->setName('objectstorage:download') @@ -34,7 +33,6 @@ protected function configure() InputArgument::REQUIRED, 'Local filename to write to' ); - } protected function execute(InputInterface $input, OutputInterface $output) @@ -48,6 +46,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Downloading key '" . $key . "' to file '" . $filename . "'"); $service->download($key, $filename); - $output->writeln("Done"); + $output->writeln('Done'); } } diff --git a/src/Command/EncryptCommand.php b/src/Command/EncryptCommand.php index 95658e2..6fe5483 100644 --- a/src/Command/EncryptCommand.php +++ b/src/Command/EncryptCommand.php @@ -5,7 +5,6 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use RuntimeException; @@ -24,7 +23,7 @@ protected function configure() ) ; } - + /* private function strtohex($x) { @@ -35,28 +34,28 @@ private function strtohex($x) return($s); } */ - + private function hextostr($hex) { - $string=''; - for ($i=0; $i < strlen($hex)-1; $i+=2) { - $string .= chr(hexdec($hex[$i].$hex[$i+1])); + $string = ''; + for ($i = 0; $i < strlen($hex) - 1; $i += 2) { + $string .= chr(hexdec($hex[$i] . $hex[$i + 1])); } + return $string; } - + protected function execute(InputInterface $input, OutputInterface $output) { $key = getenv('OBJECTSTORAGE_ENCRYPTION_KEY'); $iv = getenv('OBJECTSTORAGE_ENCRYPTION_IV'); if (!$key || !$iv) { - throw new RuntimeException("Could not obtain encryption key + iv from environment"); + throw new RuntimeException('Could not obtain encryption key + iv from environment'); } - + $key = $this->hextostr($key); $iv = $this->hextostr($iv); - $filename = $input->getArgument('filename'); diff --git a/src/Command/GenerateKeyCommand.php b/src/Command/GenerateKeyCommand.php index 0fa7291..9abe985 100644 --- a/src/Command/GenerateKeyCommand.php +++ b/src/Command/GenerateKeyCommand.php @@ -3,11 +3,8 @@ namespace ObjectStorage\Command; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use RuntimeException; class GenerateKeyCommand extends Command { @@ -19,22 +16,23 @@ protected function configure() ) ; } - + private function strtohex($x) { - $s=''; + $s = ''; foreach (str_split($x) as $c) { - $s.=sprintf("%02X", ord($c)); + $s .= sprintf('%02X', ord($c)); } - return($s); + + return $s; } - + protected function execute(InputInterface $input, OutputInterface $output) { $key = openssl_random_pseudo_bytes(32); $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); - - $output->writeln("KEY: " . $this->strtohex($key)); - $output->writeln("IV: " . $this->strtohex($iv)); + + $output->writeln('KEY: ' . $this->strtohex($key)); + $output->writeln('IV: ' . $this->strtohex($iv)); } } diff --git a/src/Command/ListCommand.php b/src/Command/ListCommand.php index 352b476..e67716c 100644 --- a/src/Command/ListCommand.php +++ b/src/Command/ListCommand.php @@ -29,7 +29,7 @@ protected function configure() 'The prefix to scan for' ); } - + protected function execute(InputInterface $input, OutputInterface $output) { $configfilename = $input->getOption('config'); @@ -41,15 +41,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Listing keys /w prefix '" . $prefix . "'\n"); $keys = $service->listKeys($prefix); - foreach($keys as $key) - { + foreach ($keys as $key) { $output->writeln("* Key: '" . $key->getKey() . "'"); $metadata = $key->getMetaData(); - foreach($metadata as $k => $v) { - $output->writeln(" - '$k' = " . (string)$v); - + foreach ($metadata as $k => $v) { + $output->writeln(" - '$k' = " . (string) $v); } } - $output->writeln("Done"); + $output->writeln('Done'); } } diff --git a/src/Command/UploadCommand.php b/src/Command/UploadCommand.php index 8be7ee9..c6d86cc 100644 --- a/src/Command/UploadCommand.php +++ b/src/Command/UploadCommand.php @@ -11,7 +11,6 @@ class UploadCommand extends Command { - protected function configure() { $this->setName('objectstorage:upload') @@ -34,7 +33,6 @@ protected function configure() InputArgument::REQUIRED, 'Local filename to upload' ); - } protected function execute(InputInterface $input, OutputInterface $output) @@ -48,6 +46,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Uploading '" . $filename . "' as key '" . $key . "'"); $service->upload($key, $filename); - $output->writeln("Done"); + $output->writeln('Done'); } } diff --git a/src/Key.php b/src/Key.php index 62bf2fb..14e6f62 100644 --- a/src/Key.php +++ b/src/Key.php @@ -21,6 +21,7 @@ public function setMetaData($data) { $this->metadata = $data; } + public function getMetaData() { return $this->metadata; diff --git a/src/Service.php b/src/Service.php index 41f4fdd..222b6fd 100644 --- a/src/Service.php +++ b/src/Service.php @@ -42,7 +42,7 @@ public function delete($key) public function upload($key, $filename) { if (!file_exists($filename)) { - throw new RuntimeException("File not found: " . $filename); + throw new RuntimeException('File not found: ' . $filename); } $data = file_get_contents($filename); diff --git a/src/Utils.php b/src/Utils.php index 050aac4..b5afcc8 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -4,10 +4,8 @@ use InvalidArgumentException; use RuntimeException; - use ObjectStorage\Adapter\EncryptionAdapter; use ObjectStorage\Adapter\Bzip2Adapter; -use ObjectStorage\Service; class Utils { @@ -21,26 +19,27 @@ public static function loadConfig($filename = null) } elseif (file_exists('/etc/objectstorage.conf')) { $filename = '/etc/objectstorage.conf'; } else { - throw new RuntimeException("No configfile detected"); + throw new RuntimeException('No configfile detected'); } } if (!file_exists($filename)) { - throw new RuntimeException("Config file not found"); + throw new RuntimeException('Config file not found'); } $config = parse_ini_file($filename, true); if (!isset($config['general']['adapter'])) { - throw new RuntimeException("Config file not valid, please check objectstore.conf.dist for an example"); + throw new RuntimeException('Config file not valid, please check objectstore.conf.dist for an example'); } + return $config; } public static function getServiceFromConfig($config) { - $adaptername = (string)$config['general']['adapter']; + $adaptername = (string) $config['general']['adapter']; $adapterclassname = 'ObjectStorage\\Adapter\\' . ucfirst($adaptername) . 'Adapter'; if (!class_exists($adapterclassname)) { - throw new RuntimeException("Adapter class not found or supported: " . $adapterclassname); + throw new RuntimeException('Adapter class not found or supported: ' . $adapterclassname); } if (!array_key_exists($adaptername, $config)) { throw new InvalidArgumentException( @@ -51,21 +50,22 @@ public static function getServiceFromConfig($config) $adapter = $adapterclassname::build($config[$adaptername]); if (isset($config['encryption'])) { - $key = (string)$config['encryption']['key']; - $iv = (string)$config['encryption']['iv']; - + $key = (string) $config['encryption']['key']; + $iv = (string) $config['encryption']['iv']; + // Wrap the real adapter into the encryption adapter $adapter = new EncryptionAdapter($adapter, $key, $iv); } if (isset($config['bzip2'])) { - $level = (string)$config['bzip2']['level']; + $level = (string) $config['bzip2']['level']; // Wrap the real adapter into the bzip2 compression adapter $adapter = new Bzip2Adapter($adapter, $level); } - + $service = new Service($adapter); + return $service; } } From b36d34a277487cc536a4452d3eed2fd92f406334 Mon Sep 17 00:00:00 2001 From: boite Date: Fri, 31 May 2019 11:47:39 +0100 Subject: [PATCH 5/6] Speed-up console commands by lazy loading them --- bin/objectstorage | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/bin/objectstorage b/bin/objectstorage index 74f60ee..d5a19fe 100755 --- a/bin/objectstorage +++ b/bin/objectstorage @@ -1,17 +1,27 @@ #!/usr/bin/env php setCatchExceptions(true); -$application->add(new \ObjectStorage\Command\UploadCommand()); -$application->add(new \ObjectStorage\Command\DownloadCommand()); -$application->add(new \ObjectStorage\Command\ListCommand()); -$application->add(new \ObjectStorage\Command\DeleteCommand()); -$application->add(new \ObjectStorage\Command\GenerateKeyCommand()); -$application->add(new \ObjectStorage\Command\EncryptCommand()); -$application->add(new \ObjectStorage\Command\DecryptCommand()); +$application->setCommandLoader(new FactoryCommandLoader([ + 'objectstorage:upload' => function () { return new UploadCommand(); }, + 'objectstorage:download' => function () { return new DownloadCommand(); }, + 'objectstorage:list' => function () { return new ListCommand(); }, + 'objectstorage:delete' => function () { return new DeleteCommand(); }, + 'objectstorage:generatekey' => function () { return new GenerateKeyCommand(); }, + 'objectstorage:encrypt' => function () { return new EncryptCommand(); }, + 'objectstorage:decrypt' => function () { return new DecryptCommand(); }, +])); $application->run(); From 995b567e1b0f14c2ca371cedc2439e54f90f450d Mon Sep 17 00:00:00 2001 From: boite Date: Thu, 30 May 2019 17:32:05 +0100 Subject: [PATCH 6/6] Reimplement encrypted storage using modern standards via paragonie/halite and libsodium. --- README.md | 65 ++++---- bin/objectstorage | 8 +- composer.json | 3 +- src/Adapter/EncryptedStorageAdapter.php | 148 ++++++++++++++++++ src/Adapter/EncryptionAdapter.php | 56 ------- src/Adapter/EncryptionFailureException.php | 7 + ...ntextStorageKeyEncryptedStorageAdapter.php | 49 ++++++ src/Command/DecryptCommand.php | 95 +++++++---- src/Command/EncryptCommand.php | 94 +++++++---- src/Command/GenerateKeyCommand.php | 37 +++-- tests/Adapter/EncryptedStorageAdapterTest.php | 69 ++++++++ ...tStorageKeyEncryptedStorageAdapterTest.php | 69 ++++++++ 12 files changed, 531 insertions(+), 169 deletions(-) create mode 100644 src/Adapter/EncryptedStorageAdapter.php delete mode 100644 src/Adapter/EncryptionAdapter.php create mode 100644 src/Adapter/EncryptionFailureException.php create mode 100644 src/Adapter/PlaintextStorageKeyEncryptedStorageAdapter.php create mode 100644 tests/Adapter/EncryptedStorageAdapterTest.php create mode 100644 tests/Adapter/PlaintextStorageKeyEncryptedStorageAdapterTest.php diff --git a/README.md b/README.md index 4ae1b58..2d94cde 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# ObjectStorage 2.0 library +# ObjectStorage 3.0 library ObjectStorage library for your cloud-based applications. -*NOTE: version 1.0, previously only available as dev-master, is still available by updating your composer.json to require version ~1.0* - ## Object Storage vs a normal file system Object-based storage solves large scale storage problems for cloud-based applications. @@ -78,42 +76,38 @@ $service->delete('my-message'); ### Encryption -The library includes an EncryptionAdapter that will allow you to transparently encrypt/decrypt +The library includes adapters to allow you to transparently encrypt/decrypt your data before it's passed to the storage backend. -This is done by wrapping the original storage adapter (s3, file, pdo, gridfs, etc) into -the EncryptionAdapter. Here's an example +This is done by wrapping the original storage adapter (s3, file, pdo, gridfs, +etc) into the one of the encryption adapters. Here's an example ```php -$adapter = new ObjectStorage\Adapter\PdoAdapter($pdo); -$adapter = new ObjectStorage\Adapter\EncryptionAdapter($adapter, $key, $iv); -// You can use $adapter as before, but all data will be encrypted +$adapter = new \ObjectStorage\Adapter\EncryptedStorageAdapter( + new \ObjectStorage\Adapter\PdoAdapter($pdo), + \ParagonIE\Halite\KeyFactory::loadEncryptionKey($pathToKeyfile) +); +// You can use $adapter as before and both the storage keys and objects will be +// encrypted (use PlaintextKeyEncryptedStorageAdapter if you don't want the +// storage keys to be encrypted). ``` -The key and iv are hex encoded strings. To generate these, use the following command: - -./bin/objectstorage objectstorage:generatekey +The encryption routines are provided by [ParagonIE/Halite][] and libsodium. -This will output something like the following: - - KEY: C2FE680A5613469189621C9E46B52C15C9C80E50370E7950D6FD2D027C4FAEF0 - IV: E5F3E442F3CE0ECC931B7E866A5F3121 - -Save these 2 values somewhere safely. +Use the following command to generate an encryption key and save it to a file :- -The encryption is similar to using the following commands: - - openssl enc -aes-256-cbc -K C2FE680A5613469189621C9E46B52C15C9C80E50370E7950D6FD2D027C4FAEF0 -iv E5F3E442F3CE0ECC931B7E866A5F3121 < original.txt > encrypted.aes +```sh +./bin/objectstorage genkey /path/to/a/file +``` - openssl enc -d -aes-256-cbc -K C2FE680A5613469189621C9E46B52C15C9C80E50370E7950D6FD2D027C4FAEF0 -iv E5F3E442F3CE0ECC931B7E866A5F3121 < encrypted.aes - -You can also use the included encrypt + decrypt commands: +You can also use the included encrypt + decrypt commands. In the following +example we encrypt `example.pdf` with the encryption key in `key.asc` and then +decrypt it again, using the same key and writing it to a new `example-new.pdf`: - export OBJECTSTORAGE_ENCRYPTION_KEY=C2FE680A5613469189621C9E46B52C15C9C80E50370E7950D6FD2D027C4FAEF0 - export OBJECTSTORAGE_ENCRYPTION_IV=E5F3E442F3CE0ECC931B7E866A5F3121 - - bin/objectstorage objectstorage:encrypt example.pdf > example.pdf.encrypted - bin/objectstorage objectstorage:decrypt example.pdf.encrypted > example_new.pdf +```sh +bin/objectstorage encrypt key.asc example.pdf example.pdf.encrypted +bin/objectstorage decrypt key.asc example.pdf.encrypted example-new.pdf +``` ## Console tool @@ -172,11 +166,20 @@ Then, add `linkorb/objectstorage` to your project's `composer.json`: ```json { "require": { - "linkorb/objectstorage": "~2.0" + "linkorb/objectstorage": "^3.0" } } ``` +## Older versions of this library + +Version 1.0, previously only available as dev-master, is still available by +updating your composer.json to require version "~1.0". + +The `php5` branch will still work with PHP <= 5.6, but it will not have the +latest features and, particularly, should not be used if you need encrypted +storage. + ## Contributing Ready to build and improve on this repo? Excellent! @@ -195,3 +198,5 @@ Btw, we're hiring! ## License Please check LICENSE.md for full license information + +[ParagonIE/Halite]: "Halite - Simple PHP Cryptography Library" diff --git a/bin/objectstorage b/bin/objectstorage index d5a19fe..4b59771 100755 --- a/bin/objectstorage +++ b/bin/objectstorage @@ -13,15 +13,15 @@ use ObjectStorage\Command\UploadCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\CommandLoader\FactoryCommandLoader; -$application = new Application('ObjectStorage CLI utility', '1.0.0'); +$application = new Application('ObjectStorage CLI utility', 'v3'); $application->setCatchExceptions(true); $application->setCommandLoader(new FactoryCommandLoader([ 'objectstorage:upload' => function () { return new UploadCommand(); }, 'objectstorage:download' => function () { return new DownloadCommand(); }, 'objectstorage:list' => function () { return new ListCommand(); }, 'objectstorage:delete' => function () { return new DeleteCommand(); }, - 'objectstorage:generatekey' => function () { return new GenerateKeyCommand(); }, - 'objectstorage:encrypt' => function () { return new EncryptCommand(); }, - 'objectstorage:decrypt' => function () { return new DecryptCommand(); }, + 'genkey' => function () { return new GenerateKeyCommand(); }, + 'encrypt' => function () { return new EncryptCommand(); }, + 'decrypt' => function () { return new DecryptCommand(); }, ])); $application->run(); diff --git a/composer.json b/composer.json index 37d7812..708557e 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ } ], "require": { - "php": "^7.2" + "php": "^7.2", + "paragonie/halite": "^4" }, "require-dev": { "symfony/console": "^4", diff --git a/src/Adapter/EncryptedStorageAdapter.php b/src/Adapter/EncryptedStorageAdapter.php new file mode 100644 index 0000000..82a513f --- /dev/null +++ b/src/Adapter/EncryptedStorageAdapter.php @@ -0,0 +1,148 @@ +storageAdapter = $storageAdapter; + $this->encryptionKey = $encryptionKey; + } + + public function setAdapter(StorageAdapterInterface $storageAdapter) + { + $this->storageAdapter = $storageAdapter; + } + + public function setEncryptionKey(EncryptionKey $encryptionKey) + { + $this->encryptionKey = $encryptionKey; + } + + public function setData($key, $data) + { + try { + $encryptedStorageKey = Crypto::encrypt( + new HiddenString($key), + $this->encryptionKey, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + } + + try { + $encryptedData = Crypto::encrypt( + new HiddenString($data), + $this->encryptionKey, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to encrypt the object data.', null, $e); + } + + return $this->storageAdapter->setData($encryptedStorageKey, $encryptedData); + } + + public function getData($key) + { + try { + $encryptedStorageKey = Crypto::encrypt( + new HiddenString($key), + $this->encryptionKey, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + } + + $encryptedData = $this->storageAdapter->getData($encryptedStorageKey); + + try { + $plaintextData = (string) Crypto::decrypt($encryptedData, $this->encryptionKey); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to decrypt the object data.', null, $e); + } + + return $plaintextData; + } + + public function deleteData($key) + { + try { + $encryptedStorageKey = Crypto::encrypt( + new HiddenString($key), + $this->encryptionKey, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + } + + return $this->storageAdapter->deleteData($encryptedStorageKey); + } +} diff --git a/src/Adapter/EncryptionAdapter.php b/src/Adapter/EncryptionAdapter.php deleted file mode 100644 index ab24fc7..0000000 --- a/src/Adapter/EncryptionAdapter.php +++ /dev/null @@ -1,56 +0,0 @@ -child = $child; - $this->encryption_key = $this->hextostr($encryption_key); - $this->encryption_iv = $this->hextostr($encryption_iv); - } - - public function setData($key, $data) - { - $data = openssl_encrypt($data, 'aes-256-cbc', $this->encryption_key, OPENSSL_RAW_DATA, $this->encryption_iv); - return $this->child->setData($key, $data); - } - - public function getData($key) - { - $data = $this->child->getData($key); - $data = openssl_decrypt($data, 'aes-256-cbc', $this->encryption_key, OPENSSL_RAW_DATA, $this->encryption_iv); - return $data; - } - - public function deleteData($key) - { - return $this->child->deleteData($key); - } - - - private function strtohex($x) - { - $s=''; - foreach (str_split($x) as $c) { - $s.=sprintf("%02X", ord($c)); - } - return($s); - } - - private function hextostr($hex){ - $string=''; - for ($i=0; $i < strlen($hex)-1; $i+=2) { - $string .= chr(hexdec($hex[$i].$hex[$i+1])); - } - return $string; - } -} diff --git a/src/Adapter/EncryptionFailureException.php b/src/Adapter/EncryptionFailureException.php new file mode 100644 index 0000000..16d9e9e --- /dev/null +++ b/src/Adapter/EncryptionFailureException.php @@ -0,0 +1,7 @@ +encryptionKey, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to encrypt the object data.', null, $e); + } + + return $this->storageAdapter->setData($key, $encryptedData); + } + + public function getData($key) + { + $encryptedData = $this->storageAdapter->getData($key); + + try { + $plaintextData = (string) Crypto::decrypt($encryptedData, $this->encryptionKey); + } catch (CannotPerformOperation $e) { + throw new EncryptionFailureException('Failed to decrypt the object data.', null, $e); + } + + return $plaintextData; + } + + public function deleteData($key) + { + return $this->storageAdapter->deleteData($key); + } +} diff --git a/src/Command/DecryptCommand.php b/src/Command/DecryptCommand.php index c1ed1c8..17f3289 100644 --- a/src/Command/DecryptCommand.php +++ b/src/Command/DecryptCommand.php @@ -2,64 +2,95 @@ namespace ObjectStorage\Command; +use ParagonIE\Halite\Alerts\CannotPerformOperation; +use ParagonIE\Halite\Halite; +use ParagonIE\Halite\KeyFactory; +use ParagonIE\Halite\Symmetric\Crypto; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; -use RuntimeException; class DecryptCommand extends Command { protected function configure() { - $this->setName('objectstorage:decrypt') + $this->setName('decrypt') ->setDescription( 'Decrypt a file' ) - ->addArgument( - 'filename', - InputArgument::REQUIRED, - 'The file to decrypt' - ) + ->addArgument( + 'keyfile', + InputArgument::REQUIRED, + 'The path to a file containing an encryption key, such as one generated with the "objectstorage genkey" console command.' + ) + ->addArgument( + 'infile', + InputArgument::REQUIRED, + 'The file to decrypt' + ) + ->addArgument( + 'outfile', + InputArgument::REQUIRED, + 'The path to which to write the decrypted file' + ) ; } - private function strtohex($x) + protected function execute(InputInterface $input, OutputInterface $output) { - $s = ''; - foreach (str_split($x) as $c) { - $s .= sprintf('%02X', ord($c)); + $errorOutput = ($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $encryptedFile = $input->getArgument('infile'); + $plaintextFile = $input->getArgument('outfile'); + + try { + $key = KeyFactory::loadEncryptionKey($input->getArgument('keyfile')); + } catch (CannotPerformOperation $e) { + $errorOutput->writeln($e->getMessage()); + + return 1; } + if (\file_exists($plaintextFile)) { + $errorOutput->writeln("The file at \"{$plaintextFile}\" already exists and will not be overwritten."); - return $s; - } + return 2; + } + if (!\file_exists($encryptedFile) || !\is_readable($encryptedFile)) { + $errorOutput->writeln("The file at \"{$encryptedFile}\" cannot be opened for reading."); - private function hextostr($hex) - { - $string = ''; - for ($i = 0; $i < strlen($hex) - 1; $i += 2) { - $string .= chr(hexdec($hex[$i] . $hex[$i + 1])); + return 3; } - return $string; - } + $ciphertext = \file_get_contents($encryptedFile); - protected function execute(InputInterface $input, OutputInterface $output) - { - $key = getenv('OBJECTSTORAGE_ENCRYPTION_KEY'); - $iv = getenv('OBJECTSTORAGE_ENCRYPTION_IV'); + if (false === $ciphertext) { + $errorOutput->writeln("The file at \"{$encryptedFile}\" cannot be opened for reading."); - if (!$key || !$iv) { - throw new RuntimeException('Could not obtain encryption key + iv from environment'); + return 3; } - $filename = $input->getArgument('filename'); + try { + $plaintext = (string) Crypto::decrypt( + $ciphertext, + $key, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + $errorOutput->writeln($e->getMessage()); - $key = $this->hextostr($key); - $iv = $this->hextostr($iv); + return 4; + } + + $isWritten = \file_put_contents($plaintextFile, $plaintext); + + if (false === $isWritten) { + $errorOutput->writeln("The decrypted data could not be written to the file at \"{$plaintextFile}\"."); + + return 5; + } - $data = file_get_contents($filename); - $res = openssl_decrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); - echo $res; + $output->writeln('Successs!'); } } diff --git a/src/Command/EncryptCommand.php b/src/Command/EncryptCommand.php index 6fe5483..faa740c 100644 --- a/src/Command/EncryptCommand.php +++ b/src/Command/EncryptCommand.php @@ -2,65 +2,97 @@ namespace ObjectStorage\Command; +use ParagonIE\Halite\Alerts\CannotPerformOperation; +use ParagonIE\Halite\Halite; +use ParagonIE\Halite\KeyFactory; +use ParagonIE\Halite\Symmetric\Crypto; +use ParagonIE\HiddenString\HiddenString; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; -use RuntimeException; class EncryptCommand extends Command { protected function configure() { - $this->setName('objectstorage:encrypt') + $this + ->setName('encrypt') ->setDescription( 'Encrypt a file' ) ->addArgument( - 'filename', + 'keyfile', InputArgument::REQUIRED, - 'The file to decrypt' + 'The path to a file containing an encryption key, such as one generated with the "objectstorage genkey" console command.' + ) + ->addArgument( + 'infile', + InputArgument::REQUIRED, + 'The file to encrypt' + ) + ->addArgument( + 'outfile', + InputArgument::REQUIRED, + 'The path to which to write the encrypted file' ) ; } - /* - private function strtohex($x) + protected function execute(InputInterface $input, OutputInterface $output) { - $s=''; - foreach (str_split($x) as $c) { - $s.=sprintf("%02X", ord($c)); + $errorOutput = ($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $plaintextFile = $input->getArgument('infile'); + $encryptedFile = $input->getArgument('outfile'); + + try { + $key = KeyFactory::loadEncryptionKey($input->getArgument('keyfile')); + } catch (CannotPerformOperation $e) { + $errorOutput->writeln($e->getMessage()); + + return 1; } - return($s); - } - */ + if (\file_exists($encryptedFile)) { + $errorOutput->writeln("The file at \"{$encryptedFile}\" already exists and will not be overwritten."); - private function hextostr($hex) - { - $string = ''; - for ($i = 0; $i < strlen($hex) - 1; $i += 2) { - $string .= chr(hexdec($hex[$i] . $hex[$i + 1])); + return 2; } + if (!\file_exists($plaintextFile) || !\is_readable($plaintextFile)) { + $errorOutput->writeln("The file at \"{$plaintextFile}\" cannot be opened for reading."); - return $string; - } + return 3; + } - protected function execute(InputInterface $input, OutputInterface $output) - { - $key = getenv('OBJECTSTORAGE_ENCRYPTION_KEY'); - $iv = getenv('OBJECTSTORAGE_ENCRYPTION_IV'); + $plaintext = \file_get_contents($plaintextFile); + + if (false === $plaintext) { + $errorOutput->writeln("The file at \"{$plaintextFile}\" cannot be opened for reading."); - if (!$key || !$iv) { - throw new RuntimeException('Could not obtain encryption key + iv from environment'); + return 3; } - $key = $this->hextostr($key); - $iv = $this->hextostr($iv); + try { + $encryptedData = Crypto::encrypt( + new HiddenString($plaintext), + $key, + Halite::ENCODE_BASE64URLSAFE + ); + } catch (CannotPerformOperation $e) { + $errorOutput->writeln($e->getMessage()); - $filename = $input->getArgument('filename'); + return 4; + } + + $isWritten = \file_put_contents($encryptedFile, $encryptedData); + + if (false === $isWritten) { + $errorOutput->writeln("The encrypted data could not be written to the file at \"{$plaintextFile}\"."); + + return 5; + } - $data = file_get_contents($filename); - $res = openssl_encrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); - echo $res; + $output->writeln('Successs!'); } } diff --git a/src/Command/GenerateKeyCommand.php b/src/Command/GenerateKeyCommand.php index 9abe985..0613811 100644 --- a/src/Command/GenerateKeyCommand.php +++ b/src/Command/GenerateKeyCommand.php @@ -2,37 +2,44 @@ namespace ObjectStorage\Command; +use ParagonIE\Halite\KeyFactory; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; class GenerateKeyCommand extends Command { + const ARG_PATH = 'path'; + protected function configure() { - $this->setName('objectstorage:generatekey') + $this->setName('genkey') ->setDescription( - 'Generate encryption key and iv' + 'Generate a symmetric encryption key and write it to a file at the supplied path. This command will not overwrite an existing file.' + ) + ->addArgument( + self::ARG_PATH, + InputArgument::REQUIRED, + 'The path to which to save the key file.' ) ; } - private function strtohex($x) + protected function execute(InputInterface $input, OutputInterface $output) { - $s = ''; - foreach (str_split($x) as $c) { - $s .= sprintf('%02X', ord($c)); - } + $path = $input->getArgument(self::ARG_PATH); - return $s; - } + if (\file_exists($path)) { + $output->writeln("I cannot create a key file at \"{$path}\" because a file exists there already. I stop!"); - protected function execute(InputInterface $input, OutputInterface $output) - { - $key = openssl_random_pseudo_bytes(32); - $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); + return 1; + } - $output->writeln('KEY: ' . $this->strtohex($key)); - $output->writeln('IV: ' . $this->strtohex($iv)); + if (true !== KeyFactory::save(KeyFactory::generateEncryptionKey(), $path)) { + $output->writeln("I tried, but was unable to write the key to a file at \"{$path}\". I apologise!"); + + return 2; + } } } diff --git a/tests/Adapter/EncryptedStorageAdapterTest.php b/tests/Adapter/EncryptedStorageAdapterTest.php new file mode 100644 index 0000000..cbe5b87 --- /dev/null +++ b/tests/Adapter/EncryptedStorageAdapterTest.php @@ -0,0 +1,69 @@ +encryptionKey = KeyFactory::generateEncryptionKey(); + + $this->storageAdapter = $this->getMockBuilder(StorageAdapterInterface::class) + ->getMockForAbstractClass() + ; + + $this->encryptedStorageAdapter = new EncryptedStorageAdapter( + $this->storageAdapter, + $this->encryptionKey + ); + } + + public function testSetDataDoesNotPassUnencryptedKeyOrDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('setData') + ->with( + new LogicalNot('some-key'), + new LogicalNot('some-data') + ) + ; + + $this->encryptedStorageAdapter->setData('some-key', 'some-data'); + } + + public function testGetDataDoesNotPassUnencryptedKeyOrDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('getData') + ->with(new LogicalNot('some-key')) + ->willReturn(Crypto::encrypt(new HiddenString('some-data'), $this->encryptionKey)) + ; + + $this->encryptedStorageAdapter->getData('some-key'); + } + + public function testDeleteDataDoesNotPassUnencryptedKeyOrDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('deleteData') + ->with(new LogicalNot('some-key')) + ; + + $this->encryptedStorageAdapter->deleteData('some-key'); + } +} diff --git a/tests/Adapter/PlaintextStorageKeyEncryptedStorageAdapterTest.php b/tests/Adapter/PlaintextStorageKeyEncryptedStorageAdapterTest.php new file mode 100644 index 0000000..9a7546d --- /dev/null +++ b/tests/Adapter/PlaintextStorageKeyEncryptedStorageAdapterTest.php @@ -0,0 +1,69 @@ +encryptionKey = KeyFactory::generateEncryptionKey(); + + $this->storageAdapter = $this->getMockBuilder(StorageAdapterInterface::class) + ->getMockForAbstractClass() + ; + + $this->encryptedStorageAdapter = new PlaintextStorageKeyEncryptedStorageAdapter( + $this->storageAdapter, + $this->encryptionKey + ); + } + + public function testSetDataDoesNotPassUnencryptedDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('setData') + ->with( + $this->identicalTo('some-key'), + new LogicalNot('some-data') + ) + ; + + $this->encryptedStorageAdapter->setData('some-key', 'some-data'); + } + + public function testGetDataDoesNotPassUnencryptedDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('getData') + ->with($this->identicalTo('some-key')) + ->willReturn(Crypto::encrypt(new HiddenString('some-data'), $this->encryptionKey)) + ; + + $this->encryptedStorageAdapter->getData('some-key'); + } + + public function testDeleteDataDoesNotPassUnencryptedDataToStorageAdapter() + { + $this->storageAdapter + ->expects($this->once()) + ->method('deleteData') + ->with($this->identicalTo('some-key')) + ; + + $this->encryptedStorageAdapter->deleteData('some-key'); + } +}