From 0c8f61f69a3cb3c66ed0b8dd8a033585281fd6c9 Mon Sep 17 00:00:00 2001 From: boite Date: Tue, 4 Jun 2019 10:46:40 +0100 Subject: [PATCH 1/6] Fix broken implementation of storage key encryption The storage key is now the hashed (and authenticated) original storage key so that lookups succeed. The storage key was encrypted npn-deterministicaly in the previous implementation which made lookups fail. Storage keys are guaranteed not to have been tampered with (say, an attacker replaces a stored value with another, authentic value, but that is not related to the original storage key). --- README.md | 9 +- .../AbstractEncryptedStorageAdapter.php | 30 +++++ src/Adapter/EncryptedStorageAdapter.php | 103 ++++++++++++------ ...ntextStorageKeyEncryptedStorageAdapter.php | 55 +++++++++- src/Command/GenerateKeyCommand.php | 25 ++++- src/Utils.php | 7 +- tests/Adapter/EncryptedStorageAdapterTest.php | 7 +- 7 files changed, 185 insertions(+), 51 deletions(-) create mode 100644 src/Adapter/AbstractEncryptedStorageAdapter.php diff --git a/README.md b/README.md index 2d94cde..02b46a1 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,8 @@ etc) into the one of the encryption adapters. Here's an example ```php $adapter = new \ObjectStorage\Adapter\EncryptedStorageAdapter( new \ObjectStorage\Adapter\PdoAdapter($pdo), - \ParagonIE\Halite\KeyFactory::loadEncryptionKey($pathToKeyfile) + \ParagonIE\Halite\KeyFactory::loadAuthenticationKey($pathToSigningKey), + \ParagonIE\Halite\KeyFactory::loadEncryptionKey($pathToEncryptionKey) ); // You can use $adapter as before and both the storage keys and objects will be // encrypted (use PlaintextKeyEncryptedStorageAdapter if you don't want the @@ -94,10 +95,12 @@ $adapter = new \ObjectStorage\Adapter\EncryptedStorageAdapter( The encryption routines are provided by [ParagonIE/Halite][] and libsodium. -Use the following command to generate an encryption key and save it to a file :- +Use the following commands to generate and save a signing key and an encryption +key as needed in the previous example:- ```sh -./bin/objectstorage genkey /path/to/a/file +./bin/objectstorage genkey --signing /path/to/a/file +./bin/objectstorage genkey /path/to/another/file ``` You can also use the included encrypt + decrypt commands. In the following diff --git a/src/Adapter/AbstractEncryptedStorageAdapter.php b/src/Adapter/AbstractEncryptedStorageAdapter.php new file mode 100644 index 0000000..928c174 --- /dev/null +++ b/src/Adapter/AbstractEncryptedStorageAdapter.php @@ -0,0 +1,30 @@ +storageAdapter = $storageAdapter; + } + + public function setEncryptionKey(EncryptionKey $encryptionKey) + { + $this->encryptionKey = $encryptionKey; + } +} diff --git a/src/Adapter/EncryptedStorageAdapter.php b/src/Adapter/EncryptedStorageAdapter.php index 82a513f..14c131c 100644 --- a/src/Adapter/EncryptedStorageAdapter.php +++ b/src/Adapter/EncryptedStorageAdapter.php @@ -5,6 +5,7 @@ use ParagonIE\Halite\Alerts\CannotPerformOperation; use ParagonIE\Halite\Halite; use ParagonIE\Halite\KeyFactory; +use ParagonIE\Halite\Symmetric\AuthenticationKey; use ParagonIE\Halite\Symmetric\Crypto; use ParagonIE\Halite\Symmetric\EncryptionKey; use ParagonIE\HiddenString\HiddenString; @@ -13,19 +14,16 @@ * Decorates a storage adapter to encrypt and decrypt object data and the keys * by which the data are stored. */ -class EncryptedStorageAdapter implements StorageAdapterInterface +class EncryptedStorageAdapter extends AbstractEncryptedStorageAdapter implements StorageAdapterInterface { - const CFG_ENCRYPTION_KEY = 'encryption_key'; - const CFG_ENCRYPTION_KEY_PATH = 'encryption_key_path'; - const CFG_STORAGE_ADAPTER = 'storage_adapter'; - + protected $authenticationKey; protected $encryptionKey; protected $storageAdapter; public static function build(array $config) { - if (!isset($config[self::CFG_ENCRYPT_STORAGE_ADAPTER]) - || !$config[self::CFG_ENCRYPT_STORAGE_ADAPTER] instanceof StorageAdapterInterface + if (!isset($config[self::CFG_STORAGE_ADAPTER]) + || !$config[self::CFG_STORAGE_ADAPTER] instanceof StorageAdapterInterface ) { throw new \InvalidArgumentException( 'The build configuration for this storage adapter is missing an instance of StorageAdapterInterface, keyed as "' @@ -34,6 +32,31 @@ public static function build(array $config) ); } + if (isset($config[self::CFG_AUTHENTICATION_KEY])) { + if (!$config[self::CFG_AUTHENTICATION_KEY] instanceof AuthenticationKey) { + throw new \InvalidArgumentException( + '"' . self::CFG_AUTHENTICATION_KEY . '" must be an instance of AuthenticationKey.' + ); + } + $authenticationKey = $config[self::CFG_AUTHENTICATION_KEY]; + } elseif (isset($config[self::CFG_AUTHENTICATION_KEY_PATH])) { + try { + $authenticationKey = KeyFactory::loadAuthenticationKey($config[self::CFG_AUTHENTICATION_KEY_PATH]); + } catch (CannotPerformOperation $e) { + throw new \InvalidArgumentException( + '"' . self::CFG_AUTHENTICATION_KEY_PATH . '" must be a readable file.' + ); + } + } else { + throw new \InvalidArgumentException( + 'The build configuration for this storage adapter is missing an authentication key ("' + . self::CFG_AUTHENTICATION_KEY + . '" or "' + . self::CFG_AUTHENTICATION_KEY_PATH + . '").' + ); + } + if (isset($config[self::CFG_ENCRYPTION_KEY])) { if (!$config[self::CFG_ENCRYPTION_KEY] instanceof EncryptionKey) { throw new \InvalidArgumentException( @@ -60,89 +83,97 @@ public static function build(array $config) } return new self( - $config[self::CFG_ENCRYPT_STORAGE_ADAPTER], + $config[self::CFG_STORAGE_ADAPTER], + $authenticationKey, $encryptionKey ); } public function __construct( StorageAdapterInterface $storageAdapter, + AuthenticationKey $authenticationKey, EncryptionKey $encryptionKey ) { $this->storageAdapter = $storageAdapter; + $this->authenticationKey = $authenticationKey; $this->encryptionKey = $encryptionKey; } - public function setAdapter(StorageAdapterInterface $storageAdapter) + public function setAuthenticationKey(AuthenticationKey $authenticationKey) { - $this->storageAdapter = $storageAdapter; - } - - public function setEncryptionKey(EncryptionKey $encryptionKey) - { - $this->encryptionKey = $encryptionKey; + $this->authenticationKey = $authenticationKey; } public function setData($key, $data) { try { - $encryptedStorageKey = Crypto::encrypt( - new HiddenString($key), - $this->encryptionKey, + $authenticStorageKey = Crypto::authenticate( + $key, + $this->authenticationKey, Halite::ENCODE_BASE64URLSAFE ); } catch (CannotPerformOperation $e) { - throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + throw new EncryptionFailureException('Failed to hash the storage key.', null, $e); } try { - $encryptedData = Crypto::encrypt( - new HiddenString($data), + $encryptedBlob = Crypto::encrypt( + new HiddenString($key . $data), $this->encryptionKey, Halite::ENCODE_BASE64URLSAFE ); } catch (CannotPerformOperation $e) { - throw new EncryptionFailureException('Failed to encrypt the object data.', null, $e); + throw new EncryptionFailureException('Failed to encrypt the storage key and object data.', null, $e); } - return $this->storageAdapter->setData($encryptedStorageKey, $encryptedData); + return $this->storageAdapter->setData($authenticStorageKey, $encryptedBlob); } public function getData($key) { try { - $encryptedStorageKey = Crypto::encrypt( - new HiddenString($key), - $this->encryptionKey, + $authenticStorageKey = Crypto::authenticate( + $key, + $this->authenticationKey, Halite::ENCODE_BASE64URLSAFE ); } catch (CannotPerformOperation $e) { - throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + throw new EncryptionFailureException('Failed to hash the storage key.', null, $e); } - $encryptedData = $this->storageAdapter->getData($encryptedStorageKey); + $encryptedBlob = $this->storageAdapter->getData($authenticStorageKey); try { - $plaintextData = (string) Crypto::decrypt($encryptedData, $this->encryptionKey); + $plaintextBlob = (string) Crypto::decrypt($encryptedBlob, $this->encryptionKey); } catch (CannotPerformOperation $e) { - throw new EncryptionFailureException('Failed to decrypt the object data.', null, $e); + throw new EncryptionFailureException('Failed to decrypt the storage key and object data.', null, $e); } - return $plaintextData; + $storageKeyLength = \strlen($key); + + if (false === \hash_equals($key, \substr($plaintextBlob, 0, $storageKeyLength))) { + // The $plaintextBlob was definitely encrypted with our encryption key, + // but the storage key is not the one in that blob. + throw new EncryptionFailureException( + 'The object data is not the expected one for the supplied storage key. The store has been corrupted or tampered with.' + ); + } + + return \substr($plaintextBlob, $storageKeyLength); } public function deleteData($key) { try { - $encryptedStorageKey = Crypto::encrypt( - new HiddenString($key), - $this->encryptionKey, + $authenticStorageKey = Crypto::authenticate( + $key, + $this->authenticationKey, Halite::ENCODE_BASE64URLSAFE ); } catch (CannotPerformOperation $e) { - throw new EncryptionFailureException('Failed to encrypt the storage key.', null, $e); + throw new EncryptionFailureException('Failed to hash the storage key.', null, $e); } - return $this->storageAdapter->deleteData($encryptedStorageKey); + return $this->storageAdapter->deleteData($authenticStorageKey); } } diff --git a/src/Adapter/PlaintextStorageKeyEncryptedStorageAdapter.php b/src/Adapter/PlaintextStorageKeyEncryptedStorageAdapter.php index 397c0a5..1fa9789 100644 --- a/src/Adapter/PlaintextStorageKeyEncryptedStorageAdapter.php +++ b/src/Adapter/PlaintextStorageKeyEncryptedStorageAdapter.php @@ -4,7 +4,9 @@ use ParagonIE\Halite\Alerts\CannotPerformOperation; use ParagonIE\Halite\Halite; +use ParagonIE\Halite\KeyFactory; use ParagonIE\Halite\Symmetric\Crypto; +use ParagonIE\Halite\Symmetric\EncryptionKey; use ParagonIE\HiddenString\HiddenString; /** @@ -12,8 +14,59 @@ * * Does not encrypt the keys by which data are stored. */ -class PlaintextStorageKeyEncryptedStorageAdapter extends EncryptedStorageAdapter +class PlaintextStorageKeyEncryptedStorageAdapter extends AbstractEncryptedStorageAdapter implements StorageAdapterInterface { + public static function build(array $config) + { + if (!isset($config[self::CFG_STORAGE_ADAPTER]) + || !$config[self::CFG_STORAGE_ADAPTER] instanceof StorageAdapterInterface + ) { + throw new \InvalidArgumentException( + 'The build configuration for this storage adapter is missing an instance of StorageAdapterInterface, keyed as "' + . self::CFG_STORAGE_ADAPTER + . '"."' + ); + } + + if (isset($config[self::CFG_ENCRYPTION_KEY])) { + if (!$config[self::CFG_ENCRYPTION_KEY] instanceof EncryptionKey) { + throw new \InvalidArgumentException( + '"' . self::CFG_ENCRYPTION_KEY . '" must be an instance of EncryptionKey.' + ); + } + $encryptionKey = $config[self::CFG_ENCRYPTION_KEY]; + } elseif (isset($config[self::CFG_ENCRYPTION_KEY_PATH])) { + try { + $encryptionKey = KeyFactory::loadEncryptionKey($config[self::CFG_ENCRYPTION_KEY_PATH]); + } catch (CannotPerformOperation $e) { + throw new \InvalidArgumentException( + '"' . self::CFG_ENCRYPTION_KEY_PATH . '" must be a readable file.' + ); + } + } else { + throw new \InvalidArgumentException( + 'The build configuration for this storage adapter is missing an encryption key ("' + . self::CFG_ENCRYPTION_KEY + . '" or "' + . self::CFG_ENCRYPTION_KEY_PATH + . '").' + ); + } + + return new self( + $config[self::CFG_STORAGE_ADAPTER], + $encryptionKey + ); + } + + public function __construct( + StorageAdapterInterface $storageAdapter, + EncryptionKey $encryptionKey + ) { + $this->storageAdapter = $storageAdapter; + $this->encryptionKey = $encryptionKey; + } + public function setData($key, $data) { try { diff --git a/src/Command/GenerateKeyCommand.php b/src/Command/GenerateKeyCommand.php index 0613811..662e041 100644 --- a/src/Command/GenerateKeyCommand.php +++ b/src/Command/GenerateKeyCommand.php @@ -4,25 +4,32 @@ use ParagonIE\Halite\KeyFactory; 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 Symfony\Component\Console\Input\InputArgument; class GenerateKeyCommand extends Command { const ARG_PATH = 'path'; + const OPT_SIGNING = 'signing'; protected function configure() { $this->setName('genkey') ->setDescription( - 'Generate a symmetric encryption key and write it to a file at the supplied path. This command will not overwrite an existing file.' + 'Generate a symmetric encryption or signing 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.' ) + ->addOption( + self::OPT_SIGNING, + 's', + InputOption::VALUE_NONE + ) ; } @@ -36,10 +43,22 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } + if (null !== $input->getOption(self::OPT_SIGNING)) { + if (true !== KeyFactory::save(KeyFactory::generateAuthenticationKey(), $path)) { + $output->writeln("I tried, but was unable to write the signing key to a file at \"{$path}\". I apologise!"); + + return 2; + } + + return 0; + } + 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!"); + $output->writeln("I tried, but was unable to write the encryption key to a file at \"{$path}\". I apologise!"); return 2; } + + return 0; } } diff --git a/src/Utils.php b/src/Utils.php index b5afcc8..f3c21c0 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -4,7 +4,6 @@ use InvalidArgumentException; use RuntimeException; -use ObjectStorage\Adapter\EncryptionAdapter; use ObjectStorage\Adapter\Bzip2Adapter; class Utils @@ -50,11 +49,7 @@ public static function getServiceFromConfig($config) $adapter = $adapterclassname::build($config[$adaptername]); if (isset($config['encryption'])) { - $key = (string) $config['encryption']['key']; - $iv = (string) $config['encryption']['iv']; - - // Wrap the real adapter into the encryption adapter - $adapter = new EncryptionAdapter($adapter, $key, $iv); + throw new RuntimeException('It is no longer possible to configure encrypted storage from objectstore.conf.'); } if (isset($config['bzip2'])) { diff --git a/tests/Adapter/EncryptedStorageAdapterTest.php b/tests/Adapter/EncryptedStorageAdapterTest.php index cbe5b87..ba06d19 100644 --- a/tests/Adapter/EncryptedStorageAdapterTest.php +++ b/tests/Adapter/EncryptedStorageAdapterTest.php @@ -12,12 +12,14 @@ class EncryptedStorageAdapterTest extends TestCase { + private $authenticationKey; private $encryptedStorageAdapter; private $encryptionKey; private $storageAdapter; protected function setUp(): void { + $this->authenticationKey = KeyFactory::generateAuthenticationKey(); $this->encryptionKey = KeyFactory::generateEncryptionKey(); $this->storageAdapter = $this->getMockBuilder(StorageAdapterInterface::class) @@ -26,6 +28,7 @@ protected function setUp(): void $this->encryptedStorageAdapter = new EncryptedStorageAdapter( $this->storageAdapter, + $this->authenticationKey, $this->encryptionKey ); } @@ -37,7 +40,7 @@ public function testSetDataDoesNotPassUnencryptedKeyOrDataToStorageAdapter() ->method('setData') ->with( new LogicalNot('some-key'), - new LogicalNot('some-data') + new LogicalNot('some-keysome-data') ) ; @@ -50,7 +53,7 @@ public function testGetDataDoesNotPassUnencryptedKeyOrDataToStorageAdapter() ->expects($this->once()) ->method('getData') ->with(new LogicalNot('some-key')) - ->willReturn(Crypto::encrypt(new HiddenString('some-data'), $this->encryptionKey)) + ->willReturn(Crypto::encrypt(new HiddenString('some-keysome-data'), $this->encryptionKey)) ; $this->encryptedStorageAdapter->getData('some-key'); From f959800be436258a51cc662c4145c591a4bcd5d4 Mon Sep 17 00:00:00 2001 From: boite Date: Mon, 3 Jun 2019 14:25:54 +0100 Subject: [PATCH 2/6] Cosmetic improvement to S3Adapter --- src/Adapter/S3Adapter.php | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Adapter/S3Adapter.php b/src/Adapter/S3Adapter.php index 78bc6de..525303b 100644 --- a/src/Adapter/S3Adapter.php +++ b/src/Adapter/S3Adapter.php @@ -2,14 +2,16 @@ namespace ObjectStorage\Adapter; -use InvalidArgumentException; use Aws\S3\S3Client; +use InvalidArgumentException; class S3Adapter implements BuildableAdapterInterface, StorageAdapterInterface { + const DEFAULT_ACL = 'public-read'; + private $s3client = null; private $bucketname = null; - private $defaultacl = 'public-read'; + private $defaultacl = self::DEFAULT_ACL; private $prefix = ''; public static function build(array $config) @@ -22,14 +24,14 @@ public static function build(array $config) ); } 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.' @@ -50,22 +52,22 @@ public static function build(array $config) return new self($client, trim($config['bucketname']), $prefix); } - public function __construct($s3client, $bucketname, $prefix = '') + public function __construct(S3Client $s3client, $bucketname, $prefix = '') { - $this->setS3Client($s3client); + $this->s3client = $s3client; $this->setBucketName($bucketname); - $this->setPrefix($prefix); + $this->prefix = $prefix; } - public function setS3Client($s3client) + public function setS3Client(S3Client $s3client) { $this->s3client = $s3client; } public function setBucketName($bucketname) { - if ('' == trim($bucketname)) { - throw new InvalidArgumentException('Invalid bucketname: ' . $bucketname); + if ('' === trim($bucketname)) { + throw new InvalidArgumentException('An empty bucketname is an invalid bucketname.'); } $this->bucketname = $bucketname; } @@ -77,11 +79,10 @@ public function setPrefix($prefix) public function setData($key, $data) { - $key = $this->prefix . $key; $this->s3client->putObject( [ 'Bucket' => $this->bucketname, - 'Key' => $key, + 'Key' => $this->prefix . $key, 'Body' => $data, 'ACL' => $this->defaultacl, ] @@ -90,11 +91,10 @@ public function setData($key, $data) public function getData($key) { - $key = $this->prefix . $key; $result = $this->s3client->getObject( [ 'Bucket' => $this->bucketname, - 'Key' => $key, + 'Key' => $this->prefix . $key, ] ); @@ -103,11 +103,10 @@ public function getData($key) public function deleteData($key) { - $key = $this->prefix . $key; $this->s3client->deleteObject( [ 'Bucket' => $this->bucketname, - 'Key' => $key, + 'Key' => $this->prefix . $key, ] ); } From d9cec374d2fef70461e4d67e768e5af946e914a0 Mon Sep 17 00:00:00 2001 From: boite Date: Mon, 3 Jun 2019 12:07:40 +0100 Subject: [PATCH 3/6] Fix S3 creds. config and add region and version --- src/Adapter/S3Adapter.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Adapter/S3Adapter.php b/src/Adapter/S3Adapter.php index 525303b..23188b0 100644 --- a/src/Adapter/S3Adapter.php +++ b/src/Adapter/S3Adapter.php @@ -8,6 +8,7 @@ class S3Adapter implements BuildableAdapterInterface, StorageAdapterInterface { const DEFAULT_ACL = 'public-read'; + const DEFAULT_API_VERSION = '2006-03-01'; private $s3client = null; private $bucketname = null; @@ -37,15 +38,29 @@ public static function build(array $config) 'Unable to build S3Adapter: missing "bucketname" from configuration.' ); } + if (!array_key_exists('region', $config) + || '' === trim($config['region']) + ) { + throw new InvalidArgumentException( + 'Unable to build S3Adapter: missing "region" from configuration.' + ); + } + if (!array_key_exists('version', $config)) { + $config['version'] = self::DEFAULT_API_VERSION; + } $prefix = ''; if (isset($config['prefix'])) { $prefix = trim($config['prefix']); } - $client = S3Client::factory( + $client = new S3Client( [ - 'key' => trim($config['access_key']), - 'secret' => trim($config['secret_key']), + 'credentials' => [ + 'key' => trim($config['access_key']), + 'secret' => trim($config['secret_key']), + ], + 'region' => trim($config['region']), + 'version' => trim($config['version']), ] ); From 4e5114406a6ce562a4805320ddf69edf32753fd0 Mon Sep 17 00:00:00 2001 From: boite Date: Mon, 3 Jun 2019 14:30:13 +0100 Subject: [PATCH 4/6] Change S3 default Canned ACL and expose config The default was "public-read" :o Now it is "private" and can be configured at build time by supplying a config with key "canned_acl_for_objects". --- src/Adapter/S3Adapter.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Adapter/S3Adapter.php b/src/Adapter/S3Adapter.php index 23188b0..9a09296 100644 --- a/src/Adapter/S3Adapter.php +++ b/src/Adapter/S3Adapter.php @@ -7,12 +7,12 @@ class S3Adapter implements BuildableAdapterInterface, StorageAdapterInterface { - const DEFAULT_ACL = 'public-read'; + const DEFAULT_ACL = 'private'; const DEFAULT_API_VERSION = '2006-03-01'; private $s3client = null; private $bucketname = null; - private $defaultacl = self::DEFAULT_ACL; + private $cannedAcl = self::DEFAULT_ACL; private $prefix = ''; public static function build(array $config) @@ -52,6 +52,10 @@ public static function build(array $config) if (isset($config['prefix'])) { $prefix = trim($config['prefix']); } + $cannedAcl = ''; + if (isset($config['canned_acl_for_objects'])) { + $cannedAcl = trim($config['canned_acl_for_objects']); + } $client = new S3Client( [ @@ -64,14 +68,17 @@ public static function build(array $config) ] ); - return new self($client, trim($config['bucketname']), $prefix); + return new self($client, trim($config['bucketname']), $prefix, $cannedAcl); } - public function __construct(S3Client $s3client, $bucketname, $prefix = '') + public function __construct(S3Client $s3client, $bucketname, $prefix = '', $cannedAcl = null) { $this->s3client = $s3client; $this->setBucketName($bucketname); $this->prefix = $prefix; + if (null !== $cannedAcl) { + $this->cannedAcl = $cannedAcl; + } } public function setS3Client(S3Client $s3client) @@ -92,6 +99,11 @@ public function setPrefix($prefix) $this->prefix = $prefix; } + public function setCannedAcl(string $cannedAcl) + { + $this->cannedAcl = $cannedAcl; + } + public function setData($key, $data) { $this->s3client->putObject( @@ -99,7 +111,7 @@ public function setData($key, $data) 'Bucket' => $this->bucketname, 'Key' => $this->prefix . $key, 'Body' => $data, - 'ACL' => $this->defaultacl, + 'ACL' => $this->cannedAcl, ] ); } From b6e12eb12a56b3c3848660f414348336b45590c4 Mon Sep 17 00:00:00 2001 From: boite Date: Tue, 4 Jun 2019 12:24:15 +0100 Subject: [PATCH 5/6] Add phpstan and enable autoloading for tests --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 708557e..2199e21 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "symfony/console": "^4", "aws/aws-sdk-php": "^3", "friendsofphp/php-cs-fixer": "^2.15", + "phpstan/phpstan": "^0.11.8", "phpunit/phpunit": "^8.1" }, "suggest": { @@ -29,5 +30,10 @@ "ObjectStorage\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "ObjectStorage\\Test\\": "tests/" + } + }, "license": "MIT" } From ed812875b5bfba363b47bbbdd35edcd8b3fd8005 Mon Sep 17 00:00:00 2001 From: boite Date: Thu, 6 Jun 2019 10:24:36 +0100 Subject: [PATCH 6/6] Pretty output for genkey command --- src/Command/GenerateKeyCommand.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Command/GenerateKeyCommand.php b/src/Command/GenerateKeyCommand.php index 662e041..1125345 100644 --- a/src/Command/GenerateKeyCommand.php +++ b/src/Command/GenerateKeyCommand.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; class GenerateKeyCommand extends Command { @@ -35,29 +36,33 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $path = $input->getArgument(self::ARG_PATH); if (\file_exists($path)) { - $output->writeln("I cannot create a key file at \"{$path}\" because a file exists there already. I stop!"); + $io->error("I cannot create a key file at \"{$path}\" because a file exists there already. I stop!"); return 1; } - if (null !== $input->getOption(self::OPT_SIGNING)) { + if ($input->getOption(self::OPT_SIGNING)) { if (true !== KeyFactory::save(KeyFactory::generateAuthenticationKey(), $path)) { - $output->writeln("I tried, but was unable to write the signing key to a file at \"{$path}\". I apologise!"); + $io->error("I tried, but was unable to write the signing key to a file at \"{$path}\". I apologise!"); return 2; } + $io->success("Signing key saved to \"{$path}\"."); return 0; } if (true !== KeyFactory::save(KeyFactory::generateEncryptionKey(), $path)) { - $output->writeln("I tried, but was unable to write the encryption key to a file at \"{$path}\". I apologise!"); + $io->error("I tried, but was unable to write the encryption key to a file at \"{$path}\". I apologise!"); return 2; } + $io->success("Encryption key saved to \"{$path}\"."); return 0; }