From 2a31bc96e36eb0ab8bde79a8f1c36097500996d1 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 24 Sep 2025 14:07:56 -0700 Subject: [PATCH 01/10] hard fork php-sdk locally --- .gitmodules | 3 + .../amazon/encryption/s3/RoundTripTests.java | 16 +- test-server/php-sdk | 1 + test-server/php-v2-server/composer.json | 4 +- .../Crypto/AbstractCryptoClient.php | 121 +++++ .../Crypto/AbstractCryptoClientV2.php | 119 +++++ .../Crypto/AesDecryptingStream.php | 147 ++++++ .../Crypto/AesEncryptingStream.php | 152 ++++++ .../Crypto/AesGcmDecryptingStream.php | 104 ++++ .../Crypto/AesGcmEncryptingStream.php | 119 +++++ .../Crypto/AesStreamInterface.php | 30 ++ .../Crypto/AesStreamInterfaceV2.php | 31 ++ .../local-php-sdk/Crypto/Cipher/Cbc.php | 88 ++++ .../Crypto/Cipher/CipherBuilderTrait.php | 72 +++ .../Crypto/Cipher/CipherMethod.php | 59 +++ .../local-php-sdk/Crypto/DecryptionTrait.php | 181 +++++++ .../Crypto/DecryptionTraitV2.php | 254 ++++++++++ .../local-php-sdk/Crypto/EncryptionTrait.php | 194 ++++++++ .../Crypto/EncryptionTraitV2.php | 198 ++++++++ .../Crypto/KmsMaterialsProvider.php | 123 +++++ .../Crypto/KmsMaterialsProviderV2.php | 101 ++++ .../Crypto/MaterialsProvider.php | 105 ++++ .../Crypto/MaterialsProviderInterface.php | 61 +++ .../Crypto/MaterialsProviderInterfaceV2.php | 53 +++ .../Crypto/MaterialsProviderV2.php | 66 +++ .../local-php-sdk/Crypto/MetadataEnvelope.php | 61 +++ .../Crypto/MetadataStrategyInterface.php | 30 ++ .../local-php-sdk/S3EC/CryptoParamsTrait.php | 75 +++ .../S3EC/CryptoParamsTraitV2.php | 19 + .../S3EC/HeadersMetadataStrategy.php | 52 ++ .../S3EC/InstructionFileMetadataStrategy.php | 90 ++++ .../local-php-sdk/S3EC/S3EncryptionClient.php | 339 +++++++++++++ .../S3EC/S3EncryptionClientV2.php | 447 ++++++++++++++++++ .../S3EC/S3EncryptionMultipartUploader.php | 169 +++++++ .../S3EC/S3EncryptionMultipartUploaderV2.php | 176 +++++++ .../local-php-sdk/S3EC/UserAgentTrait.php | 32 ++ test-server/php-v2-server/src/index.php | 4 +- 37 files changed, 3885 insertions(+), 11 deletions(-) create mode 160000 test-server/php-sdk create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClientV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesDecryptingStream.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterfaceV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/Cbc.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherMethod.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTrait.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterfaceV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php create mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTrait.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/HeadersMetadataStrategy.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php create mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php diff --git a/.gitmodules b/.gitmodules index b2b112a0..a5aee21e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "test-server/ruby-v3-server/local-ruby-sdk"] path = test-server/ruby-v3-server/local-ruby-sdk url = git@github.com:aws/aws-sdk-ruby-staging.git +[submodule "test-server/php-sdk"] + path = test-server/php-sdk + url = git@github.com:aws/aws-sdk-php.git diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index cd67f2e0..8db96f4b 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -79,15 +79,15 @@ public class RoundTripTests { static { final Map servers = new LinkedHashMap<>(); servers.put(JAVA_V3, new LanguageServerTarget(JAVA_V3, "8080")); - servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); - servers.put(GO_V3, new LanguageServerTarget(GO_V3, "8082")); - servers.put(NET_V2, new LanguageServerTarget(NET_V2, "8083")); - servers.put(NET_V3, new LanguageServerTarget(NET_V3, "8084")); - servers.put(CPP_V2, new LanguageServerTarget(CPP_V2, "8085")); + // servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); + // servers.put(GO_V3, new LanguageServerTarget(GO_V3, "8082")); + // servers.put(NET_V2, new LanguageServerTarget(NET_V2, "8083")); + // servers.put(NET_V3, new LanguageServerTarget(NET_V3, "8084")); + // servers.put(CPP_V2, new LanguageServerTarget(CPP_V2, "8085")); servers.put(PHP_V2, new LanguageServerTarget(PHP_V2, "8087")); - servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); - servers.put(RUBY_V2, new LanguageServerTarget(RUBY_V2, "8086")); - servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); + // servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); + // servers.put(RUBY_V2, new LanguageServerTarget(RUBY_V2, "8086")); + // servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); serverMap = filterServers(servers); } diff --git a/test-server/php-sdk b/test-server/php-sdk new file mode 160000 index 00000000..d78bd3b2 --- /dev/null +++ b/test-server/php-sdk @@ -0,0 +1 @@ +Subproject commit d78bd3b221890aac679ec3b6cb5abcb01fd42699 diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index e9c399ac..a6d517a9 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -10,7 +10,9 @@ }, "autoload": { "psr-4": { - "S3EC\\PhpV2Server\\": "src/" + "S3EC\\PhpV2Server\\": "src/", + "Local\\Aws\\S3\\Crypto\\": "local-php-sdk/S3EC/", + "Local\\Aws\\Crypto\\": "local-php-sdk/Crypto/" } }, "scripts": { diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php b/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php new file mode 100644 index 00000000..02b0211e --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php @@ -0,0 +1,121 @@ +stream = $cipherText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + + public function getSize(): ?int + { + $plainTextSize = $this->stream->getSize(); + + if ($this->cipherMethod->requiresPadding()) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. The + // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and + // strlen($cipherText) - 1 + return null; + } + + return $plainTextSize; + } + + public function isWritable(): bool + { + return false; + } + + public function read($length): string + { + if ($length > strlen($this->buffer)) { + $this->buffer .= $this->decryptBlock( + (int) ( + self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) + ) + ); + } + + $data = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $data ? $data : ''; + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($offset === 0 && $whence === SEEK_SET) { + $this->buffer = ''; + $this->cipherMethod->seek(0, SEEK_SET); + $this->stream->seek(0, SEEK_SET); + } else { + throw new LogicException('AES encryption streams only support being' + . ' rewound, not arbitrary seeking.'); + } + } + + private function decryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + + $cipherText = ''; + do { + $cipherText .= $this->stream->read((int) ($length - strlen($cipherText))); + } while (strlen($cipherText) < $length && !$this->stream->eof()); + + $options = OPENSSL_RAW_DATA; + if ( + !$this->stream->eof() + && $this->stream->getSize() !== $this->stream->tell() + ) { + $options |= OPENSSL_ZERO_PADDING; + } + + $plaintext = openssl_decrypt( + $cipherText, + $this->cipherMethod->getOpenSslName(), + $this->key, + $options, + $this->cipherMethod->getCurrentIv() + ); + + $this->cipherMethod->update($cipherText); + + return $plaintext; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php new file mode 100644 index 00000000..b024c563 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php @@ -0,0 +1,152 @@ +stream = $plainText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + + public function getSize(): ?int + { + $plainTextSize = $this->stream->getSize(); + + if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. + $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; + return $plainTextSize + $padding; + } + + return $plainTextSize; + } + + public function isWritable(): bool + { + return false; + } + + public function read($length): string + { + if ($length > strlen($this->buffer)) { + $this->buffer .= $this->encryptBlock( + (int) + self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) + ); + } + + $data = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $data ? $data : ''; + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_CUR) { + $offset = $this->tell() + $offset; + $whence = SEEK_SET; + } + + if ($whence === SEEK_SET) { + $this->buffer = ''; + $wholeBlockOffset + = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; + $this->stream->seek($wholeBlockOffset); + $this->cipherMethod->seek($wholeBlockOffset); + $this->read($offset - $wholeBlockOffset); + } else { + throw new LogicException('Unrecognized whence.'); + } + } + + private function encryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + + $plainText = ''; + do { + $plainText .= $this->stream->read((int) ($length - strlen($plainText))); + } while (strlen($plainText) < $length && !$this->stream->eof()); + + $options = OPENSSL_RAW_DATA; + if ( + !$this->stream->eof() + || $this->stream->getSize() !== $this->stream->tell() + ) { + $options |= OPENSSL_ZERO_PADDING; + } + + $cipherText = openssl_encrypt( + $plainText, + $this->cipherMethod->getOpenSslName(), + $this->key, + $options, + $this->cipherMethod->getCurrentIv() + ); + + $this->cipherMethod->update($cipherText); + + return $cipherText; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php new file mode 100644 index 00000000..aa87198a --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php @@ -0,0 +1,104 @@ +cipherText = $cipherText; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->tag = $tag; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + + public function getAesName() + { + return 'AES/GCM/NoPadding'; + } + + public function getCurrentIv() + { + return $this->initializationVector; + } + + public function createStream() + { + + $result = \openssl_decrypt( + (string) $this->cipherText, + $this->getOpenSslName(), + $this->key, + OPENSSL_RAW_DATA, + $this->initializationVector, + $this->tag, + $this->aad + ); + if ($result === false) { + throw new CryptoException('The requested object could not be ' + . 'decrypted due to an invalid authentication tag.'); + } + return Psr7\Utils::streamFor($result); + + } + + public function isWritable(): bool + { + return false; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php new file mode 100644 index 00000000..45a3e82b --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php @@ -0,0 +1,119 @@ +plaintext = $plaintext; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + + /** + * Same as static method and retained for backwards compatibility + * + * @return string + */ + public function getAesName() + { + return self::getStaticAesName(); + } + + public function getCurrentIv() + { + return $this->initializationVector; + } + + public function createStream() + { + return Psr7\Utils::streamFor(\openssl_encrypt( + (string) $this->plaintext, + $this->getOpenSslName(), + $this->key, + OPENSSL_RAW_DATA, + $this->initializationVector, + $this->tag, + $this->aad, + $this->tagLength + )); + } + + /** + * @return string + */ + public function getTag() + { + return $this->tag; + } + + public function isWritable(): bool + { + return false; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php new file mode 100644 index 00000000..5227790a --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php @@ -0,0 +1,30 @@ +baseIv = $this->iv = $iv; + $this->keySize = $keySize; + + if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { + throw new InvalidArgumentException('Invalid initialization vector'); + } + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-cbc"; + } + + public function getAesName() + { + return 'AES/CBC/PKCS5Padding'; + } + + public function getCurrentIv() + { + return $this->iv; + } + + public function requiresPadding() + { + return true; + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($offset === 0 && $whence === SEEK_SET) { + $this->iv = $this->baseIv; + } else { + throw new LogicException('CBC initialization only support being' + . ' rewound, not arbitrary seeking.'); + } + } + + public function update($cipherTextBlock) + { + $this->iv = substr($cipherTextBlock, self::BLOCK_SIZE * -1); + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php b/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php new file mode 100644 index 00000000..bb8e6772 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php @@ -0,0 +1,72 @@ +decryptCek( + base64_decode( + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] + ), + json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ) + ); + $cipherOptions['KeySize'] = strlen($cek) * 8; + $cipherOptions['Cipher'] = $this->getCipherFromAesName( + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] + ); + + $decryptionStream = $this->getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ); + unset($cek); + + return $decryptionStream; + } + + private function getTagFromCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return (string) new LimitStream( + $cipherText, + $tagLength, + $cipherTextSize - $tagLength + ); + } + + private function getStrippedCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return new LimitStream( + $cipherText, + $cipherTextSize - $tagLength, + 0 + ); + } + + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ) { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ); + + return new AesGcmDecryptingStream( + $this->getStrippedCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ), + $cek, + $cipherOptions['Iv'], + $cipherOptions['Tag'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'] ?: null, + $cipherOptions['KeySize'] + ); + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + return new AesDecryptingStream( + $cipherTextStream, + $cek, + $cipherMethod + ); + } + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php new file mode 100644 index 00000000..e8b583ea --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php @@ -0,0 +1,254 @@ +decryptCek( + base64_decode( + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] + ), + json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ), + $options + ); + $options['@CipherOptions']['KeySize'] = strlen($cek) * 8; + $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName( + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] + ); + + $this->validateOptionsAndEnvelope($options, $envelope); + + $decryptionStream = $this->getDecryptingStream( + $cipherText, + $cek, + $options['@CipherOptions'] + ); + unset($cek); + + return $decryptionStream; + } + + private function getTagFromCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return (string) new LimitStream( + $cipherText, + $tagLength, + $cipherTextSize - $tagLength + ); + } + + private function getStrippedCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return new LimitStream( + $cipherText, + $cipherTextSize - $tagLength, + 0 + ); + } + + private function validateOptionsAndEnvelope($options, $envelope) + { + $allowedCiphers = AbstractCryptoClientV2::$supportedCiphers; + $allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps; + if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') { + $allowedCiphers = array_unique(array_merge( + $allowedCiphers, + AbstractCryptoClient::$supportedCiphers + )); + $allowedKeywraps = array_unique(array_merge( + $allowedKeywraps, + AbstractCryptoClient::$supportedKeyWraps + )); + } + + $v1SchemaException = new CryptoException("The requested object is encrypted" + . " with V1 encryption schemas that have been disabled by" + . " client configuration @SecurityProfile=V2. Retry with" + . " V2_AND_LEGACY enabled or reencrypt the object."); + + if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) { + if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" + . " the cipher '{$options['@CipherOptions']['Cipher']}', which is not" + . " supported for decryption with the selected security profile." + . " This profile allows decryption with: " + . implode(", ", $allowedCiphers)); + } + if ( + !in_array( + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], + $allowedKeywraps + ) + ) { + if ( + in_array( + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], + AbstractCryptoClient::$supportedKeyWraps + ) + ) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" + . " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," + . " which is not supported for decryption with the current security" + . " profile."); + } + + $matdesc = json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ); + if ( + isset($matdesc['aws:x-amz-cek-alg']) + && $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== + $matdesc['aws:x-amz-cek-alg'] + ) { + throw new CryptoException("There is a mismatch in specified content" + . " encryption algrithm between the materials description value" + . " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}" + . " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}."); + } + } + + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ) { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ); + + return new AesGcmDecryptingStream( + $this->getStrippedCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ), + $cek, + $cipherOptions['Iv'], + $cipherOptions['Tag'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'] ?: null, + $cipherOptions['KeySize'] + ); + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + return new AesDecryptingStream( + $cipherTextStream, + $cek, + $cipherMethod + ); + } + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php new file mode 100644 index 00000000..38c346a7 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php @@ -0,0 +1,194 @@ + true, + 'KeySize' => true, + 'Aad' => true, + ]; + + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + abstract protected function buildCipherMethod($cipherName, $iv, $keySize); + + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * @param MaterialsProvider $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return AesStreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $cipherOptions + * is not valid. + * + * @internal + */ + public function encrypt( + Stream $plaintext, + array $cipherOptions, + MaterialsProvider $provider, + MetadataEnvelope $envelope + ) { + $materialsDescription = $provider->getMaterialsDescription(); + + $cipherOptions = array_intersect_key( + $cipherOptions, + self::$allowedOptions + ); + + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' + . ' specified in the "cipher_options".'); + } + + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' + . ' supported by the SDK.'); + } + + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' + . ' an integer.'); + } + + if ( + !MaterialsProvider::isSupportedKeySize( + $cipherOptions['KeySize'] + ) + ) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' + . ' is not supported by AES (128, 192, or 256).'); + } + + $cipherOptions['Iv'] = $provider->generateIv( + $this->getCipherOpenSslName( + $cipherOptions['Cipher'], + $cipherOptions['KeySize'] + ) + ); + + $cek = $provider->generateCek($cipherOptions['KeySize']); + + list($encryptingStream, $aesName) = $this->getEncryptingStream( + $plaintext, + $cek, + $cipherOptions + ); + + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = + $provider->encryptCek( + $cek, + $materialsDescription + ); + unset($cek); + + $envelope[MetadataEnvelope::IV_HEADER] = + base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = + $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = + strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = + json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = + strlen($cipherOptions['Tag']) * 8; + } + + return $encryptingStream; + } + + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream( + Stream $plaintext, + $cek, + &$cipherOptions + ) { + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['TagLength'] = 16; + + $cipherTextStream = new AesGcmEncryptingStream( + $plaintext, + $cek, + $cipherOptions['Iv'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'], + $cipherOptions['KeySize'] + ); + + if (!empty($cipherOptions['Aad'])) { + trigger_error("'Aad' has been supplied for content encryption" + . " with " . $cipherTextStream->getAesName() . ". The" + . " PHP SDK encryption client can decrypt an object" + . " encrypted in this way, but other AWS SDKs may not be" + . " able to.", E_USER_WARNING); + } + + $appendStream = new AppendStream([ + $cipherTextStream->createStream() + ]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return [$appendStream, $cipherTextStream->getAesName()]; + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + $cipherTextStream = new AesEncryptingStream( + $plaintext, + $cek, + $cipherMethod + ); + return [$cipherTextStream, $cipherTextStream->getAesName()]; + } + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php new file mode 100644 index 00000000..77c9e802 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php @@ -0,0 +1,198 @@ + true, + 'KeySize' => true, + 'Aad' => true, + ]; + + private static $encryptClasses = [ + 'gcm' => AesGcmEncryptingStream::class + ]; + + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + abstract protected function buildCipherMethod($cipherName, $iv, $keySize); + + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $options Options for use in encryption, including cipher + * options, and encryption context. + * @param MaterialsProviderV2 $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return StreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] + * is not valid. + *s + * @internal + */ + public function encrypt( + Stream $plaintext, + array $options, + MaterialsProviderV2 $provider, + MetadataEnvelope $envelope + ) { + $options = array_change_key_case($options); + $cipherOptions = array_intersect_key( + $options['@cipheroptions'], + self::$allowedOptions + ); + + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' + . ' specified in @CipherOptions["Cipher"].'); + } + + $cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']); + + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' + . ' supported by the SDK.'); + } + + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' + . ' an integer.'); + } + + if ( + !MaterialsProviderV2::isSupportedKeySize( + $cipherOptions['KeySize'] + ) + ) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' + . ' is not supported by AES (128 or 256).'); + } + + $cipherOptions['Iv'] = $provider->generateIv( + $this->getCipherOpenSslName( + $cipherOptions['Cipher'], + $cipherOptions['KeySize'] + ) + ); + + $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; + $aesName = $encryptClass::getStaticAesName(); + $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; + + $keys = $provider->generateCek( + $cipherOptions['KeySize'], + $materialsDescription, + $options + ); + + // Some providers modify materials description based on options + if (isset($keys['UpdatedContext'])) { + $materialsDescription = $keys['UpdatedContext']; + } + + $encryptingStream = $this->getEncryptingStream( + $plaintext, + $keys['Plaintext'], + $cipherOptions + ); + + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; + unset($keys); + + $envelope[MetadataEnvelope::IV_HEADER] = + base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = + $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = + strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = + json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = + strlen($cipherOptions['Tag']) * 8; + } + + return $encryptingStream; + } + + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream( + Stream $plaintext, + $cek, + &$cipherOptions + ) { + switch ($cipherOptions['Cipher']) { + // Only 'gcm' is supported for encryption currently + case 'gcm': + $cipherOptions['TagLength'] = 16; + $encryptClass = self::$encryptClasses['gcm']; + $cipherTextStream = new $encryptClass( + $plaintext, + $cek, + $cipherOptions['Iv'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'], + $cipherOptions['KeySize'] + ); + + if (!empty($cipherOptions['Aad'])) { + trigger_error("'Aad' has been supplied for content encryption" + . " with " . $cipherTextStream->getAesName() . ". The" + . " PHP SDK encryption client can decrypt an object" + . " encrypted in this way, but other AWS SDKs may not be" + . " able to.", E_USER_WARNING); + } + + $appendStream = new AppendStream([ + $cipherTextStream->createStream() + ]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return $appendStream; + } + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php new file mode 100644 index 00000000..bca019bb --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php @@ -0,0 +1,123 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + + public function fromDecryptionEnvelope(MetadataEnvelope $envelope) + { + if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) { + throw new \RuntimeException('Not able to detect the materials description.'); + } + + $materialsDescription = json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ); + + if ( + empty($materialsDescription['kms_cmk_id']) + && empty($materialsDescription['aws:x-amz-cek-alg']) + ) { + throw new \RuntimeException('Not able to detect kms_cmk_id (legacy' + . ' implementation) or aws:x-amz-cek-alg (current implementation)' + . ' from kms materials description.'); + } + + return new self( + $this->kmsClient, + isset($materialsDescription['kms_cmk_id']) + ? $materialsDescription['kms_cmk_id'] + : null + ); + } + + /** + * The KMS key id for use in matching this Provider to its keys, + * consistently with other SDKs as 'kms_cmk_id'. + * + * @return array + */ + public function getMaterialsDescription() + { + return ['kms_cmk_id' => $this->kmsKeyId]; + } + + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + + /** + * Takes a content encryption key (CEK) and description to return an encrypted + * key by using KMS' Encrypt API. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function encryptCek($unencryptedCek, $materialDescription) + { + $encryptedDataKey = $this->kmsClient->encrypt([ + 'Plaintext' => $unencryptedCek, + 'KeyId' => $this->kmsKeyId, + 'EncryptionContext' => $materialDescription + ]); + return base64_encode($encryptedDataKey['CiphertextBlob']); + } + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key by using KMS' Decrypt API. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function decryptCek($encryptedCek, $materialDescription) + { + $result = $this->kmsClient->decrypt([ + 'CiphertextBlob' => $encryptedCek, + 'EncryptionContext' => $materialDescription + ]); + + return $result['Plaintext']; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php new file mode 100644 index 00000000..d0d923e1 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php @@ -0,0 +1,101 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + + /** + * @inheritDoc + */ + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + + /** + * @inheritDoc + */ + public function decryptCek($encryptedCek, $materialDescription, $options) + { + $params = [ + 'CiphertextBlob' => $encryptedCek, + 'EncryptionContext' => $materialDescription + ]; + if (empty($options['@KmsAllowDecryptWithAnyCmk'])) { + if (empty($this->kmsKeyId)) { + throw new CryptoException('KMS CMK ID was not specified and the' + . ' operation is not opted-in to attempting to use any valid' + . ' CMK it discovers. Please specify a CMK ID, or explicitly' + . ' enable attempts to use any valid KMS CMK with the' + . ' @KmsAllowDecryptWithAnyCmk option.'); + } + $params['KeyId'] = $this->kmsKeyId; + } + + $result = $this->kmsClient->decrypt($params); + return $result['Plaintext']; + } + + /** + * @inheritDoc + */ + public function generateCek($keySize, $context, $options) + { + if (empty($this->kmsKeyId)) { + throw new CryptoException('A KMS key id is required for encryption' + . ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been' + . ' instantiated with a KMS key id.'); + } + $options = array_change_key_case($options); + if ( + !isset($options['@kmsencryptioncontext']) + || !is_array($options['@kmsencryptioncontext']) + ) { + throw new CryptoException("'@KmsEncryptionContext' is a" + . " required argument when using KmsMaterialsProviderV2, and" + . " must be an associative array (or empty array)."); + } + if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) { + throw new CryptoException("Conflict in reserved @KmsEncryptionContext" + . " key aws:x-amz-cek-alg. This value is reserved for the S3" + . " Encryption Client and cannot be set by the user."); + } + $context = array_merge($options['@kmsencryptioncontext'], $context); + $result = $this->kmsClient->generateDataKey([ + 'KeyId' => $this->kmsKeyId, + 'KeySpec' => "AES_{$keySize}", + 'EncryptionContext' => $context + ]); + return [ + 'Plaintext' => $result['Plaintext'], + 'Ciphertext' => base64_encode($result['CiphertextBlob']), + 'UpdatedContext' => $context + ]; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php new file mode 100644 index 00000000..afbcaf13 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php @@ -0,0 +1,105 @@ + true, + 192 => true, + 256 => true, + ]; + + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + + /** + * Performs further initialization of the MaterialsProvider based on the + * data inside the MetadataEnvelope. + * + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be read from. + * + * @return MaterialsProvider + * + * @throws \RuntimeException Thrown when there is an empty or improperly + * formed materials description in the envelope. + * + * @internal + */ + abstract public function fromDecryptionEnvelope(MetadataEnvelope $envelope); + + /** + * Returns the material description for this Provider so it can be verified + * by encryption mechanisms. + * + * @return string + */ + abstract public function getMaterialsDescription(); + + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + abstract public function getWrapAlgorithmName(); + + /** + * Takes a content encryption key (CEK) and description to return an + * encrypted key according to the Provider's specifications. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + abstract public function encryptCek($unencryptedCek, $materialDescription); + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + abstract public function decryptCek($encryptedCek, $materialDescription); + + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * + * @return string + */ + public function generateCek($keySize) + { + return openssl_random_pseudo_bytes($keySize / 8); + } + + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return openssl_random_pseudo_bytes( + openssl_cipher_iv_length($openSslName) + ); + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php new file mode 100644 index 00000000..71093d54 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php @@ -0,0 +1,61 @@ + true, + 256 => true, + ]; + + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + abstract public function getWrapAlgorithmName(); + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * decrypting the CEK. + * @param string $options Options for use in decrypting the CEK. + * + * @return string + */ + abstract public function decryptCek($encryptedCek, $materialDescription, $options); + + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * @param array $context Context map needed for key encryption + * @param array $options Additional options to be used in CEK generation + * + * @return array + */ + abstract public function generateCek($keySize, $context, $options); + + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return openssl_random_pseudo_bytes( + openssl_cipher_iv_length($openSslName) + ); + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php new file mode 100644 index 00000000..3206c0d8 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php @@ -0,0 +1,61 @@ +getConstants()) as $constant) { + self::$constants[$constant] = true; + } + } + + return array_keys(self::$constants); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($name, $value) + { + $constants = self::getConstantValues(); + if (is_null($name) || !in_array($name, $constants)) { + throw new InvalidArgumentException('MetadataEnvelope fields must' + . ' must match a predefined offset; use the header constants.'); + } + + $this->data[$name] = $value; + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->data; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php new file mode 100644 index 00000000..370ef585 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php @@ -0,0 +1,30 @@ +instructionFileSuffix; + } + + protected function determineGetObjectStrategy( + $result, + $instructionFileSuffix + ) { + if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) { + return new HeadersMetadataStrategy(); + } + + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + } + + protected function getMetadataStrategy(array $args, $instructionFileSuffix) + { + if (!empty($args['@MetadataStrategy'])) { + if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) { + return $args['@MetadataStrategy']; + } + + if (is_string($args['@MetadataStrategy'])) { + switch ($args['@MetadataStrategy']) { + case HeadersMetadataStrategy::class: + return new HeadersMetadataStrategy(); + case InstructionFileMetadataStrategy::class: + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + default: + throw new \InvalidArgumentException('Could not match the' + . ' specified string in "MetadataStrategy" to a' + . ' predefined strategy.'); + } + } else { + throw new \InvalidArgumentException('The metadata strategy that' + . ' was passed to "MetadataStrategy" was unrecognized.'); + } + } elseif ($instructionFileSuffix) { + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + } + + return null; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php new file mode 100644 index 00000000..592f68bc --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php @@ -0,0 +1,19 @@ + $value) { + $args['Metadata'][$header] = $value; + } + + return $args; + } + + /** + * Generates a MetadataEnvelope according to the metadata headers from the + * GetObject result. + * + * @param array $args Arguments from Command and Result that contains + * S3 Object information, relevant headers, and command + * configuration. + * + * @return MetadataEnvelope + */ + public function load(array $args) + { + $envelope = new MetadataEnvelope(); + $constantValues = MetadataEnvelope::getConstantValues(); + + foreach ($constantValues as $constant) { + if (!empty($args['Metadata'][$constant])) { + $envelope[$constant] = $args['Metadata'][$constant]; + } + } + + return $envelope; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php b/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php new file mode 100644 index 00000000..25384bb3 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php @@ -0,0 +1,90 @@ +suffix = empty($suffix) + ? self::DEFAULT_FILE_SUFFIX + : $suffix; + $this->client = $client; + } + + /** + * Places the information in the MetadataEnvelope to a location on S3. + * + * @param MetadataEnvelope $envelope Encryption data to save according to + * the strategy. + * @param array $args Starting arguments for PutObject, used for saving + * extra the instruction file. + * + * @return array Updated arguments for PutObject. + */ + public function save(MetadataEnvelope $envelope, array $args) + { + $this->client->putObject([ + 'Bucket' => $args['Bucket'], + 'Key' => $args['Key'] . $this->suffix, + 'Body' => json_encode($envelope) + ]); + + return $args; + } + + /** + * Uses the strategy's client to retrieve the instruction file from S3 and generates + * a MetadataEnvelope from its contents. + * + * @param array $args Arguments from Command and Result that contains + * S3 Object information, relevant headers, and command + * configuration. + * + * @return MetadataEnvelope + */ + public function load(array $args) + { + $result = $this->client->getObject([ + 'Bucket' => $args['Bucket'], + 'Key' => $args['Key'] . $this->suffix + ]); + + $metadataHeaders = json_decode($result['Body'], true); + $envelope = new MetadataEnvelope(); + $constantValues = MetadataEnvelope::getConstantValues(); + + foreach ($constantValues as $constant) { + if (!empty($metadataHeaders[$constant])) { + $envelope[$constant] = $metadataHeaders[$constant]; + } + } + + return $envelope; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php new file mode 100644 index 00000000..1ddd4862 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php @@ -0,0 +1,339 @@ +client = $client; + $this->instructionFileSuffix = $instructionFileSuffix; + MetricsBuilder::appendMetricsCaptureMiddleware( + $this->client->getHandlerList(), + MetricsBuilder::S3_CRYPTO_V1N + ); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + /** + * Encrypts the data in the 'Body' field of $args and promises to upload it + * to the specified location on S3. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. Only the + * Cipher option is required. Accepts the following: + * - Cipher: (string) cbc|gcm + * See also: AbstractCryptoClient::$supportedCiphers. Note that + * cbc is deprecated and gcm should be used when possible. + * - KeySize: (int) 128|192|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. Note if you pass in Aad for gcm encryption, the + * PHP SDK will be able to decrypt the resulting object, but other + * AWS SDKs may not be able to do so. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if using an InstructionFileMetadataHandler. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $envelope = new MetadataEnvelope(); + + return Promise\Create::promiseFor($this->encrypt( + Psr7\Utils::streamFor($args['Body']), + $args['@CipherOptions'] ?: [], + $provider, + $envelope + ))->then( + function ($encryptedBodyStream) use ($args) { + $hash = new PhpHash('sha256'); + $hashingEncryptedBodyStream = new HashingStream( + $encryptedBodyStream, + $hash, + self::getContentShaDecorator($args) + ); + return [$hashingEncryptedBodyStream, $args]; + } + )->then( + function ($putObjectContents) use ($strategy, $envelope) { + list($bodyStream, $args) = $putObjectContents; + if ($strategy === null) { + $strategy = self::getDefaultStrategy(); + } + + $updatedArgs = $strategy->save($envelope, $args); + $updatedArgs['Body'] = $bodyStream; + return $updatedArgs; + } + )->then( + function ($args) { + unset($args['@CipherOptions']); + return $this->client->putObjectAsync($args); + } + ); + } + + private static function getContentShaDecorator(&$args) + { + return function ($hash) use (&$args) { + $args['ContentSHA256'] = bin2hex($hash); + }; + } + + /** + * Encrypts the data in the 'Body' field of $args and uploads it to the + * specified location on S3. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Cipher: (string) cbc|gcm + * See also: AbstractCryptoClient::$supportedCiphers. Note that + * cbc is deprecated and gcm should be used when possible. + * - KeySize: (int) 128|192|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. Note if you pass in Aad for gcm encryption, the + * PHP SDK will be able to decrypt the resulting object, but other + * AWS SDKs may not be able to do so. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if an using an InstructionFileMetadataHandler was + * determined. + * + * @return \Aws\Result PutObject call result with the details of uploading + * the encrypted file. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObject(array $args) + { + return $this->putObjectAsync($args)->wait(); + } + + /** + * Promises to retrieve an object from S3 and decrypt the data in the + * 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading + * MetadataEnvelope information. Defaults to determining based on object + * response headers. Can either be a class implementing MetadataStrategy, + * a class name of a predefined strategy, or empty/null to default. + * - @InstructionFileSuffix: (string) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler is being used. + * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when required arguments are not + * passed or are passed incorrectly. + */ + public function getObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $saveAs = null; + if (!empty($args['SaveAs'])) { + $saveAs = $args['SaveAs']; + } + + $promise = $this->client->getObjectAsync($args) + ->then( + function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { + if ($strategy === null) { + $strategy = $this->determineGetObjectStrategy( + $result, + $instructionFileSuffix + ); + } + + $envelope = $strategy->load($args + [ + 'Metadata' => $result['Metadata'] + ]); + + $provider = $provider->fromDecryptionEnvelope($envelope); + + $result['Body'] = $this->decrypt( + $result['Body'], + $provider, + $envelope, + isset($args['@CipherOptions']) + ? $args['@CipherOptions'] + : [] + ); + return $result; + } + )->then( + function ($result) use ($saveAs) { + if (!empty($saveAs)) { + file_put_contents( + $saveAs, + (string) $result['Body'], + LOCK_EX + ); + } + return $result; + } + ); + + return $promise; + } + + /** + * Retrieves an object from S3 and decrypts the data in the 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * - @InstructionFileSuffix: (string|null) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler was detected. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * + * @return \Aws\Result GetObject call result with the 'Body' field + * wrapped in a decryption stream with its metadata + * information. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function getObject(array $args) + { + return $this->getObjectAsync($args)->wait(); + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php new file mode 100644 index 00000000..39a98dcf --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php @@ -0,0 +1,447 @@ + + * use Local\Aws\Crypto\KmsMaterialsProviderV2; + * use Aws\S3\Crypto\S3EncryptionClientV2; + * use Aws\S3\S3Client; + * + * $encryptionClient = new S3EncryptionClientV2( + * new S3Client([ + * 'region' => 'us-west-2', + * 'version' => 'latest' + * ]) + * ); + * $materialsProvider = new KmsMaterialsProviderV2( + * new KmsClient([ + * 'profile' => 'default', + * 'region' => 'us-east-1', + * 'version' => 'latest', + * ], + * 'your-kms-key-id' + * ); + * + * $encryptionClient->putObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * '@KmsEncryptionContext' => ['foo' => 'bar'], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * 'Body' => 'your-encrypted-data', + * ]); + * + * + * Example read call (using objects from previous example): + * + * + * $encryptionClient->getObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * ]); + * + */ +class S3EncryptionClientV2 extends AbstractCryptoClientV2 +{ + use CipherBuilderTrait; + use CryptoParamsTraitV2; + use DecryptionTraitV2; + use EncryptionTraitV2; + use UserAgentTrait; + + const CRYPTO_VERSION = '2.1'; + + private $client; + private $instructionFileSuffix; + private $legacyWarningCount; + + /** + * @param S3Client $client The S3Client to be used for true uploading and + * retrieving objects from S3 when using the + * encryption client. + * @param string|null $instructionFileSuffix Suffix for a client wide + * default when using instruction + * files for metadata storage. + */ + public function __construct( + S3Client $client, + $instructionFileSuffix = null + ) { + $this->client = $client; + $this->instructionFileSuffix = $instructionFileSuffix; + $this->legacyWarningCount = 0; + MetricsBuilder::appendMetricsCaptureMiddleware( + $this->client->getHandlerList(), + MetricsBuilder::S3_CRYPTO_V2 + ); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + /** + * Encrypts the data in the 'Body' field of $args and promises to upload it + * to the specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. Only the + * Cipher option is required. Accepts the following: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if using an InstructionFileMetadataHandler. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $envelope = new MetadataEnvelope(); + + return Promise\Create::promiseFor($this->encrypt( + Psr7\Utils::streamFor($args['Body']), + $args, + $provider, + $envelope + ))->then( + function ($encryptedBodyStream) use ($args) { + $hash = new PhpHash('sha256'); + $hashingEncryptedBodyStream = new HashingStream( + $encryptedBodyStream, + $hash, + self::getContentShaDecorator($args) + ); + return [$hashingEncryptedBodyStream, $args]; + } + )->then( + function ($putObjectContents) use ($strategy, $envelope) { + list($bodyStream, $args) = $putObjectContents; + if ($strategy === null) { + $strategy = self::getDefaultStrategy(); + } + + $updatedArgs = $strategy->save($envelope, $args); + $updatedArgs['Body'] = $bodyStream; + return $updatedArgs; + } + )->then( + function ($args) { + unset($args['@CipherOptions']); + return $this->client->putObjectAsync($args); + } + ); + } + + private static function getContentShaDecorator(&$args) + { + return function ($hash) use (&$args) { + $args['ContentSHA256'] = bin2hex($hash); + }; + } + + /** + * Encrypts the data in the 'Body' field of $args and uploads it to the + * specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if an using an InstructionFileMetadataHandler was + * determined. + * + * @return \Aws\Result PutObject call result with the details of uploading + * the encrypted file. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObject(array $args) + { + return $this->putObjectAsync($args)->wait(); + } + + /** + * Promises to retrieve an object from S3 and decrypt the data in the + * 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading + * MetadataEnvelope information. Defaults to determining based on object + * response headers. Can either be a class implementing MetadataStrategy, + * a class name of a predefined strategy, or empty/null to default. + * - @InstructionFileSuffix: (string) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler is being used. + * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when required arguments are not + * passed or are passed incorrectly. + */ + public function getObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + if ( + !isset($args['@SecurityProfile']) + || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) + ) { + throw new CryptoException("@SecurityProfile is required and must be" + . " set to 'V2' or 'V2_AND_LEGACY'"); + } + + // Only throw this legacy warning once per client + if ( + in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) + && $this->legacyWarningCount < 1 + ) { + $this->legacyWarningCount++; + trigger_error( + "This S3 Encryption Client operation is configured to" + . " read encrypted data with legacy encryption modes. If you" + . " don't have objects encrypted with these legacy modes," + . " you should disable support for them to enhance security. ", + E_USER_WARNING + ); + } + + $saveAs = null; + if (!empty($args['SaveAs'])) { + $saveAs = $args['SaveAs']; + } + + $promise = $this->client->getObjectAsync($args) + ->then( + function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { + if ($strategy === null) { + $strategy = $this->determineGetObjectStrategy( + $result, + $instructionFileSuffix + ); + } + + $envelope = $strategy->load($args + [ + 'Metadata' => $result['Metadata'] + ]); + + $result['Body'] = $this->decrypt( + $result['Body'], + $provider, + $envelope, + $args + ); + return $result; + } + )->then( + function ($result) use ($saveAs) { + if (!empty($saveAs)) { + file_put_contents( + $saveAs, + (string) $result['Body'], + LOCK_EX + ); + } + return $result; + } + ); + + return $promise; + } + + /** + * Retrieves an object from S3 and decrypts the data in the 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * - @InstructionFileSuffix: (string|null) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler was detected. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return \Aws\Result GetObject call result with the 'Body' field + * wrapped in a decryption stream with its metadata + * information. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function getObject(array $args) + { + return $this->getObjectAsync($args)->wait(); + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php new file mode 100644 index 00000000..4fdb7a77 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php @@ -0,0 +1,169 @@ +appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); + $this->client = $client; + $config['params'] = []; + if (!empty($config['bucket'])) { + $config['params']['Bucket'] = $config['bucket']; + } + if (!empty($config['key'])) { + $config['params']['Key'] = $config['key']; + } + + $this->provider = $this->getMaterialsProvider($config); + unset($config['@MaterialsProvider']); + + $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); + unset($config['@InstructionFileSuffix']); + $this->strategy = $this->getMetadataStrategy( + $config, + $this->instructionFileSuffix + ); + if ($this->strategy === null) { + $this->strategy = self::getDefaultStrategy(); + } + unset($config['@MetadataStrategy']); + + $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); + + parent::__construct($client, $source, $config); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + private function getEncryptingDataPreparer() + { + return function () { + // Defer encryption work until promise is executed + $envelope = new MetadataEnvelope(); + + list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( + $this->source, + $this->config['@cipheroptions'] ?: [], + $this->provider, + $envelope + ))->then( + function ($bodyStream) use ($envelope) { + $params = $this->strategy->save( + $envelope, + $this->config['params'] + ); + return [$bodyStream, $params]; + } + )->wait(); + + $this->source->rewind(); + $this->config['params'] = $params; + }; + } +} \ No newline at end of file diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php new file mode 100644 index 00000000..813305ef --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php @@ -0,0 +1,176 @@ +appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); + $this->client = $client; + $config['params'] = []; + if (!empty($config['bucket'])) { + $config['params']['Bucket'] = $config['bucket']; + } + if (!empty($config['key'])) { + $config['params']['Key'] = $config['key']; + } + + $this->provider = $this->getMaterialsProvider($config); + unset($config['@MaterialsProvider']); + + $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); + unset($config['@InstructionFileSuffix']); + $this->strategy = $this->getMetadataStrategy( + $config, + $this->instructionFileSuffix + ); + if ($this->strategy === null) { + $this->strategy = self::getDefaultStrategy(); + } + unset($config['@MetadataStrategy']); + + $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); + + parent::__construct($client, $source, $config); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + private function getEncryptingDataPreparer() + { + return function () { + // Defer encryption work until promise is executed + $envelope = new MetadataEnvelope(); + + list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( + $this->source, + $this->config ?: [], + $this->provider, + $envelope + ))->then( + function ($bodyStream) use ($envelope) { + $params = $this->strategy->save( + $envelope, + $this->config['params'] + ); + return [$bodyStream, $params]; + } + )->wait(); + + $this->source->rewind(); + $this->config['params'] = $params; + }; + } +} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php b/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php new file mode 100644 index 00000000..7f95ca06 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php @@ -0,0 +1,32 @@ +getHandlerList(); + $list->appendBuild(Middleware::mapRequest( + function (RequestInterface $req) use ($agentString) { + if ( + !empty($req->getHeader('User-Agent')) + && !empty($req->getHeader('User-Agent')[0]) + ) { + $userAgent = $req->getHeader('User-Agent')[0]; + if (strpos($userAgent, $agentString) === false) { + $userAgent .= " {$agentString}"; + }; + } else { + $userAgent = $agentString; + } + + $req = $req->withHeader('User-Agent', $userAgent); + return $req; + } + )); + } +} diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index cc5dee29..59acda97 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -5,8 +5,8 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Aws\S3\Crypto\S3EncryptionClientV2; -use Aws\Crypto\KmsMaterialsProviderV2; +use Local\Aws\S3\Crypto\S3EncryptionClientV2; +use Local\Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; From 4fd76f2b45da7e8cf0a8b74751cd1deb3dd8f1f1 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 24 Sep 2025 14:08:58 -0700 Subject: [PATCH 02/10] remove aws sdk php submodule --- .gitmodules | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index a5aee21e..ac305547 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,4 @@ url = git@github.com:aws/aws-sdk-ruby-staging.git [submodule "test-server/ruby-v3-server/local-ruby-sdk"] path = test-server/ruby-v3-server/local-ruby-sdk - url = git@github.com:aws/aws-sdk-ruby-staging.git -[submodule "test-server/php-sdk"] - path = test-server/php-sdk - url = git@github.com:aws/aws-sdk-php.git + url = git@github.com:aws/aws-sdk-ruby-staging.git \ No newline at end of file From a5870cb7ffa00b29b298ee0113b4c43ffeccbfdc Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 24 Sep 2025 14:21:19 -0700 Subject: [PATCH 03/10] use local aws php sdk for php test-servers --- .../amazon/encryption/s3/RoundTripTests.java | 2 +- test-server/php-sdk | 1 - test-server/php-v3-server/composer.json | 4 +- .../Crypto/AbstractCryptoClient.php | 121 +++++ .../Crypto/AbstractCryptoClientV2.php | 119 +++++ .../Crypto/AesDecryptingStream.php | 147 ++++++ .../Crypto/AesEncryptingStream.php | 152 ++++++ .../Crypto/AesGcmDecryptingStream.php | 104 ++++ .../Crypto/AesGcmEncryptingStream.php | 119 +++++ .../Crypto/AesStreamInterface.php | 30 ++ .../Crypto/AesStreamInterfaceV2.php | 31 ++ .../local-php-sdk/Crypto/Cipher/Cbc.php | 88 ++++ .../Crypto/Cipher/CipherBuilderTrait.php | 72 +++ .../Crypto/Cipher/CipherMethod.php | 59 +++ .../local-php-sdk/Crypto/DecryptionTrait.php | 181 +++++++ .../Crypto/DecryptionTraitV2.php | 254 ++++++++++ .../local-php-sdk/Crypto/EncryptionTrait.php | 194 ++++++++ .../Crypto/EncryptionTraitV2.php | 198 ++++++++ .../Crypto/KmsMaterialsProvider.php | 123 +++++ .../Crypto/KmsMaterialsProviderV2.php | 101 ++++ .../Crypto/MaterialsProvider.php | 105 ++++ .../Crypto/MaterialsProviderInterface.php | 61 +++ .../Crypto/MaterialsProviderInterfaceV2.php | 53 +++ .../Crypto/MaterialsProviderV2.php | 66 +++ .../local-php-sdk/Crypto/MetadataEnvelope.php | 61 +++ .../Crypto/MetadataStrategyInterface.php | 30 ++ .../local-php-sdk/S3EC/CryptoParamsTrait.php | 75 +++ .../S3EC/CryptoParamsTraitV2.php | 19 + .../S3EC/HeadersMetadataStrategy.php | 52 ++ .../S3EC/InstructionFileMetadataStrategy.php | 90 ++++ .../local-php-sdk/S3EC/S3EncryptionClient.php | 339 +++++++++++++ .../S3EC/S3EncryptionClientV2.php | 447 ++++++++++++++++++ .../S3EC/S3EncryptionMultipartUploader.php | 169 +++++++ .../S3EC/S3EncryptionMultipartUploaderV2.php | 176 +++++++ .../local-php-sdk/S3EC/UserAgentTrait.php | 32 ++ test-server/php-v3-server/src/index.php | 4 +- 36 files changed, 3874 insertions(+), 5 deletions(-) delete mode 160000 test-server/php-sdk create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClientV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesDecryptingStream.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterfaceV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/Cbc.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherMethod.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTrait.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterfaceV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php create mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTrait.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/HeadersMetadataStrategy.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 8db96f4b..301a45f2 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -85,7 +85,7 @@ public class RoundTripTests { // servers.put(NET_V3, new LanguageServerTarget(NET_V3, "8084")); // servers.put(CPP_V2, new LanguageServerTarget(CPP_V2, "8085")); servers.put(PHP_V2, new LanguageServerTarget(PHP_V2, "8087")); - // servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); + servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); // servers.put(RUBY_V2, new LanguageServerTarget(RUBY_V2, "8086")); // servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); diff --git a/test-server/php-sdk b/test-server/php-sdk deleted file mode 160000 index d78bd3b2..00000000 --- a/test-server/php-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d78bd3b221890aac679ec3b6cb5abcb01fd42699 diff --git a/test-server/php-v3-server/composer.json b/test-server/php-v3-server/composer.json index 7ed1daf3..eb824537 100644 --- a/test-server/php-v3-server/composer.json +++ b/test-server/php-v3-server/composer.json @@ -10,7 +10,9 @@ }, "autoload": { "psr-4": { - "S3EC\\PhpV2Server\\": "src/" + "S3EC\\PhpV2Server\\": "src/", + "Local\\Aws\\S3\\Crypto\\": "local-php-sdk/S3EC/", + "Local\\Aws\\Crypto\\": "local-php-sdk/Crypto/" } }, "scripts": { diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php b/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php new file mode 100644 index 00000000..02b0211e --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php @@ -0,0 +1,121 @@ +stream = $cipherText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + + public function getSize(): ?int + { + $plainTextSize = $this->stream->getSize(); + + if ($this->cipherMethod->requiresPadding()) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. The + // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and + // strlen($cipherText) - 1 + return null; + } + + return $plainTextSize; + } + + public function isWritable(): bool + { + return false; + } + + public function read($length): string + { + if ($length > strlen($this->buffer)) { + $this->buffer .= $this->decryptBlock( + (int) ( + self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) + ) + ); + } + + $data = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $data ? $data : ''; + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($offset === 0 && $whence === SEEK_SET) { + $this->buffer = ''; + $this->cipherMethod->seek(0, SEEK_SET); + $this->stream->seek(0, SEEK_SET); + } else { + throw new LogicException('AES encryption streams only support being' + . ' rewound, not arbitrary seeking.'); + } + } + + private function decryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + + $cipherText = ''; + do { + $cipherText .= $this->stream->read((int) ($length - strlen($cipherText))); + } while (strlen($cipherText) < $length && !$this->stream->eof()); + + $options = OPENSSL_RAW_DATA; + if ( + !$this->stream->eof() + && $this->stream->getSize() !== $this->stream->tell() + ) { + $options |= OPENSSL_ZERO_PADDING; + } + + $plaintext = openssl_decrypt( + $cipherText, + $this->cipherMethod->getOpenSslName(), + $this->key, + $options, + $this->cipherMethod->getCurrentIv() + ); + + $this->cipherMethod->update($cipherText); + + return $plaintext; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php new file mode 100644 index 00000000..b024c563 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php @@ -0,0 +1,152 @@ +stream = $plainText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + + public function getSize(): ?int + { + $plainTextSize = $this->stream->getSize(); + + if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. + $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; + return $plainTextSize + $padding; + } + + return $plainTextSize; + } + + public function isWritable(): bool + { + return false; + } + + public function read($length): string + { + if ($length > strlen($this->buffer)) { + $this->buffer .= $this->encryptBlock( + (int) + self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) + ); + } + + $data = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $data ? $data : ''; + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_CUR) { + $offset = $this->tell() + $offset; + $whence = SEEK_SET; + } + + if ($whence === SEEK_SET) { + $this->buffer = ''; + $wholeBlockOffset + = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; + $this->stream->seek($wholeBlockOffset); + $this->cipherMethod->seek($wholeBlockOffset); + $this->read($offset - $wholeBlockOffset); + } else { + throw new LogicException('Unrecognized whence.'); + } + } + + private function encryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + + $plainText = ''; + do { + $plainText .= $this->stream->read((int) ($length - strlen($plainText))); + } while (strlen($plainText) < $length && !$this->stream->eof()); + + $options = OPENSSL_RAW_DATA; + if ( + !$this->stream->eof() + || $this->stream->getSize() !== $this->stream->tell() + ) { + $options |= OPENSSL_ZERO_PADDING; + } + + $cipherText = openssl_encrypt( + $plainText, + $this->cipherMethod->getOpenSslName(), + $this->key, + $options, + $this->cipherMethod->getCurrentIv() + ); + + $this->cipherMethod->update($cipherText); + + return $cipherText; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php new file mode 100644 index 00000000..aa87198a --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php @@ -0,0 +1,104 @@ +cipherText = $cipherText; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->tag = $tag; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + + public function getAesName() + { + return 'AES/GCM/NoPadding'; + } + + public function getCurrentIv() + { + return $this->initializationVector; + } + + public function createStream() + { + + $result = \openssl_decrypt( + (string) $this->cipherText, + $this->getOpenSslName(), + $this->key, + OPENSSL_RAW_DATA, + $this->initializationVector, + $this->tag, + $this->aad + ); + if ($result === false) { + throw new CryptoException('The requested object could not be ' + . 'decrypted due to an invalid authentication tag.'); + } + return Psr7\Utils::streamFor($result); + + } + + public function isWritable(): bool + { + return false; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php new file mode 100644 index 00000000..45a3e82b --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php @@ -0,0 +1,119 @@ +plaintext = $plaintext; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + + /** + * Same as static method and retained for backwards compatibility + * + * @return string + */ + public function getAesName() + { + return self::getStaticAesName(); + } + + public function getCurrentIv() + { + return $this->initializationVector; + } + + public function createStream() + { + return Psr7\Utils::streamFor(\openssl_encrypt( + (string) $this->plaintext, + $this->getOpenSslName(), + $this->key, + OPENSSL_RAW_DATA, + $this->initializationVector, + $this->tag, + $this->aad, + $this->tagLength + )); + } + + /** + * @return string + */ + public function getTag() + { + return $this->tag; + } + + public function isWritable(): bool + { + return false; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php new file mode 100644 index 00000000..5227790a --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php @@ -0,0 +1,30 @@ +baseIv = $this->iv = $iv; + $this->keySize = $keySize; + + if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { + throw new InvalidArgumentException('Invalid initialization vector'); + } + } + + public function getOpenSslName() + { + return "aes-{$this->keySize}-cbc"; + } + + public function getAesName() + { + return 'AES/CBC/PKCS5Padding'; + } + + public function getCurrentIv() + { + return $this->iv; + } + + public function requiresPadding() + { + return true; + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($offset === 0 && $whence === SEEK_SET) { + $this->iv = $this->baseIv; + } else { + throw new LogicException('CBC initialization only support being' + . ' rewound, not arbitrary seeking.'); + } + } + + public function update($cipherTextBlock) + { + $this->iv = substr($cipherTextBlock, self::BLOCK_SIZE * -1); + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php b/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php new file mode 100644 index 00000000..bb8e6772 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php @@ -0,0 +1,72 @@ +decryptCek( + base64_decode( + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] + ), + json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ) + ); + $cipherOptions['KeySize'] = strlen($cek) * 8; + $cipherOptions['Cipher'] = $this->getCipherFromAesName( + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] + ); + + $decryptionStream = $this->getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ); + unset($cek); + + return $decryptionStream; + } + + private function getTagFromCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return (string) new LimitStream( + $cipherText, + $tagLength, + $cipherTextSize - $tagLength + ); + } + + private function getStrippedCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return new LimitStream( + $cipherText, + $cipherTextSize - $tagLength, + 0 + ); + } + + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ) { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ); + + return new AesGcmDecryptingStream( + $this->getStrippedCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ), + $cek, + $cipherOptions['Iv'], + $cipherOptions['Tag'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'] ?: null, + $cipherOptions['KeySize'] + ); + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + return new AesDecryptingStream( + $cipherTextStream, + $cek, + $cipherMethod + ); + } + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php new file mode 100644 index 00000000..e8b583ea --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php @@ -0,0 +1,254 @@ +decryptCek( + base64_decode( + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] + ), + json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ), + $options + ); + $options['@CipherOptions']['KeySize'] = strlen($cek) * 8; + $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName( + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] + ); + + $this->validateOptionsAndEnvelope($options, $envelope); + + $decryptionStream = $this->getDecryptingStream( + $cipherText, + $cek, + $options['@CipherOptions'] + ); + unset($cek); + + return $decryptionStream; + } + + private function getTagFromCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return (string) new LimitStream( + $cipherText, + $tagLength, + $cipherTextSize - $tagLength + ); + } + + private function getStrippedCiphertextStream( + StreamInterface $cipherText, + $tagLength + ) { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' + . ' size.'); + } + return new LimitStream( + $cipherText, + $cipherTextSize - $tagLength, + 0 + ); + } + + private function validateOptionsAndEnvelope($options, $envelope) + { + $allowedCiphers = AbstractCryptoClientV2::$supportedCiphers; + $allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps; + if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') { + $allowedCiphers = array_unique(array_merge( + $allowedCiphers, + AbstractCryptoClient::$supportedCiphers + )); + $allowedKeywraps = array_unique(array_merge( + $allowedKeywraps, + AbstractCryptoClient::$supportedKeyWraps + )); + } + + $v1SchemaException = new CryptoException("The requested object is encrypted" + . " with V1 encryption schemas that have been disabled by" + . " client configuration @SecurityProfile=V2. Retry with" + . " V2_AND_LEGACY enabled or reencrypt the object."); + + if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) { + if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" + . " the cipher '{$options['@CipherOptions']['Cipher']}', which is not" + . " supported for decryption with the selected security profile." + . " This profile allows decryption with: " + . implode(", ", $allowedCiphers)); + } + if ( + !in_array( + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], + $allowedKeywraps + ) + ) { + if ( + in_array( + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], + AbstractCryptoClient::$supportedKeyWraps + ) + ) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" + . " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," + . " which is not supported for decryption with the current security" + . " profile."); + } + + $matdesc = json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ); + if ( + isset($matdesc['aws:x-amz-cek-alg']) + && $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== + $matdesc['aws:x-amz-cek-alg'] + ) { + throw new CryptoException("There is a mismatch in specified content" + . " encryption algrithm between the materials description value" + . " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}" + . " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}."); + } + } + + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream( + $cipherText, + $cek, + $cipherOptions + ) { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ); + + return new AesGcmDecryptingStream( + $this->getStrippedCiphertextStream( + $cipherTextStream, + $cipherOptions['TagLength'] + ), + $cek, + $cipherOptions['Iv'], + $cipherOptions['Tag'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'] ?: null, + $cipherOptions['KeySize'] + ); + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + return new AesDecryptingStream( + $cipherTextStream, + $cek, + $cipherMethod + ); + } + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php new file mode 100644 index 00000000..38c346a7 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php @@ -0,0 +1,194 @@ + true, + 'KeySize' => true, + 'Aad' => true, + ]; + + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + abstract protected function buildCipherMethod($cipherName, $iv, $keySize); + + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * @param MaterialsProvider $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return AesStreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $cipherOptions + * is not valid. + * + * @internal + */ + public function encrypt( + Stream $plaintext, + array $cipherOptions, + MaterialsProvider $provider, + MetadataEnvelope $envelope + ) { + $materialsDescription = $provider->getMaterialsDescription(); + + $cipherOptions = array_intersect_key( + $cipherOptions, + self::$allowedOptions + ); + + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' + . ' specified in the "cipher_options".'); + } + + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' + . ' supported by the SDK.'); + } + + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' + . ' an integer.'); + } + + if ( + !MaterialsProvider::isSupportedKeySize( + $cipherOptions['KeySize'] + ) + ) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' + . ' is not supported by AES (128, 192, or 256).'); + } + + $cipherOptions['Iv'] = $provider->generateIv( + $this->getCipherOpenSslName( + $cipherOptions['Cipher'], + $cipherOptions['KeySize'] + ) + ); + + $cek = $provider->generateCek($cipherOptions['KeySize']); + + list($encryptingStream, $aesName) = $this->getEncryptingStream( + $plaintext, + $cek, + $cipherOptions + ); + + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = + $provider->encryptCek( + $cek, + $materialsDescription + ); + unset($cek); + + $envelope[MetadataEnvelope::IV_HEADER] = + base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = + $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = + strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = + json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = + strlen($cipherOptions['Tag']) * 8; + } + + return $encryptingStream; + } + + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream( + Stream $plaintext, + $cek, + &$cipherOptions + ) { + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['TagLength'] = 16; + + $cipherTextStream = new AesGcmEncryptingStream( + $plaintext, + $cek, + $cipherOptions['Iv'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'], + $cipherOptions['KeySize'] + ); + + if (!empty($cipherOptions['Aad'])) { + trigger_error("'Aad' has been supplied for content encryption" + . " with " . $cipherTextStream->getAesName() . ". The" + . " PHP SDK encryption client can decrypt an object" + . " encrypted in this way, but other AWS SDKs may not be" + . " able to.", E_USER_WARNING); + } + + $appendStream = new AppendStream([ + $cipherTextStream->createStream() + ]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return [$appendStream, $cipherTextStream->getAesName()]; + default: + $cipherMethod = $this->buildCipherMethod( + $cipherOptions['Cipher'], + $cipherOptions['Iv'], + $cipherOptions['KeySize'] + ); + $cipherTextStream = new AesEncryptingStream( + $plaintext, + $cek, + $cipherMethod + ); + return [$cipherTextStream, $cipherTextStream->getAesName()]; + } + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php new file mode 100644 index 00000000..77c9e802 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php @@ -0,0 +1,198 @@ + true, + 'KeySize' => true, + 'Aad' => true, + ]; + + private static $encryptClasses = [ + 'gcm' => AesGcmEncryptingStream::class + ]; + + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + abstract protected function buildCipherMethod($cipherName, $iv, $keySize); + + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $options Options for use in encryption, including cipher + * options, and encryption context. + * @param MaterialsProviderV2 $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return StreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] + * is not valid. + *s + * @internal + */ + public function encrypt( + Stream $plaintext, + array $options, + MaterialsProviderV2 $provider, + MetadataEnvelope $envelope + ) { + $options = array_change_key_case($options); + $cipherOptions = array_intersect_key( + $options['@cipheroptions'], + self::$allowedOptions + ); + + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' + . ' specified in @CipherOptions["Cipher"].'); + } + + $cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']); + + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' + . ' supported by the SDK.'); + } + + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' + . ' an integer.'); + } + + if ( + !MaterialsProviderV2::isSupportedKeySize( + $cipherOptions['KeySize'] + ) + ) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' + . ' is not supported by AES (128 or 256).'); + } + + $cipherOptions['Iv'] = $provider->generateIv( + $this->getCipherOpenSslName( + $cipherOptions['Cipher'], + $cipherOptions['KeySize'] + ) + ); + + $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; + $aesName = $encryptClass::getStaticAesName(); + $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; + + $keys = $provider->generateCek( + $cipherOptions['KeySize'], + $materialsDescription, + $options + ); + + // Some providers modify materials description based on options + if (isset($keys['UpdatedContext'])) { + $materialsDescription = $keys['UpdatedContext']; + } + + $encryptingStream = $this->getEncryptingStream( + $plaintext, + $keys['Plaintext'], + $cipherOptions + ); + + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; + unset($keys); + + $envelope[MetadataEnvelope::IV_HEADER] = + base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = + $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = + strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = + json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = + strlen($cipherOptions['Tag']) * 8; + } + + return $encryptingStream; + } + + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream( + Stream $plaintext, + $cek, + &$cipherOptions + ) { + switch ($cipherOptions['Cipher']) { + // Only 'gcm' is supported for encryption currently + case 'gcm': + $cipherOptions['TagLength'] = 16; + $encryptClass = self::$encryptClasses['gcm']; + $cipherTextStream = new $encryptClass( + $plaintext, + $cek, + $cipherOptions['Iv'], + $cipherOptions['Aad'] = isset($cipherOptions['Aad']) + ? $cipherOptions['Aad'] + : '', + $cipherOptions['TagLength'], + $cipherOptions['KeySize'] + ); + + if (!empty($cipherOptions['Aad'])) { + trigger_error("'Aad' has been supplied for content encryption" + . " with " . $cipherTextStream->getAesName() . ". The" + . " PHP SDK encryption client can decrypt an object" + . " encrypted in this way, but other AWS SDKs may not be" + . " able to.", E_USER_WARNING); + } + + $appendStream = new AppendStream([ + $cipherTextStream->createStream() + ]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return $appendStream; + } + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php new file mode 100644 index 00000000..bca019bb --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php @@ -0,0 +1,123 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + + public function fromDecryptionEnvelope(MetadataEnvelope $envelope) + { + if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) { + throw new \RuntimeException('Not able to detect the materials description.'); + } + + $materialsDescription = json_decode( + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], + true + ); + + if ( + empty($materialsDescription['kms_cmk_id']) + && empty($materialsDescription['aws:x-amz-cek-alg']) + ) { + throw new \RuntimeException('Not able to detect kms_cmk_id (legacy' + . ' implementation) or aws:x-amz-cek-alg (current implementation)' + . ' from kms materials description.'); + } + + return new self( + $this->kmsClient, + isset($materialsDescription['kms_cmk_id']) + ? $materialsDescription['kms_cmk_id'] + : null + ); + } + + /** + * The KMS key id for use in matching this Provider to its keys, + * consistently with other SDKs as 'kms_cmk_id'. + * + * @return array + */ + public function getMaterialsDescription() + { + return ['kms_cmk_id' => $this->kmsKeyId]; + } + + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + + /** + * Takes a content encryption key (CEK) and description to return an encrypted + * key by using KMS' Encrypt API. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function encryptCek($unencryptedCek, $materialDescription) + { + $encryptedDataKey = $this->kmsClient->encrypt([ + 'Plaintext' => $unencryptedCek, + 'KeyId' => $this->kmsKeyId, + 'EncryptionContext' => $materialDescription + ]); + return base64_encode($encryptedDataKey['CiphertextBlob']); + } + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key by using KMS' Decrypt API. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function decryptCek($encryptedCek, $materialDescription) + { + $result = $this->kmsClient->decrypt([ + 'CiphertextBlob' => $encryptedCek, + 'EncryptionContext' => $materialDescription + ]); + + return $result['Plaintext']; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php new file mode 100644 index 00000000..d0d923e1 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php @@ -0,0 +1,101 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + + /** + * @inheritDoc + */ + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + + /** + * @inheritDoc + */ + public function decryptCek($encryptedCek, $materialDescription, $options) + { + $params = [ + 'CiphertextBlob' => $encryptedCek, + 'EncryptionContext' => $materialDescription + ]; + if (empty($options['@KmsAllowDecryptWithAnyCmk'])) { + if (empty($this->kmsKeyId)) { + throw new CryptoException('KMS CMK ID was not specified and the' + . ' operation is not opted-in to attempting to use any valid' + . ' CMK it discovers. Please specify a CMK ID, or explicitly' + . ' enable attempts to use any valid KMS CMK with the' + . ' @KmsAllowDecryptWithAnyCmk option.'); + } + $params['KeyId'] = $this->kmsKeyId; + } + + $result = $this->kmsClient->decrypt($params); + return $result['Plaintext']; + } + + /** + * @inheritDoc + */ + public function generateCek($keySize, $context, $options) + { + if (empty($this->kmsKeyId)) { + throw new CryptoException('A KMS key id is required for encryption' + . ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been' + . ' instantiated with a KMS key id.'); + } + $options = array_change_key_case($options); + if ( + !isset($options['@kmsencryptioncontext']) + || !is_array($options['@kmsencryptioncontext']) + ) { + throw new CryptoException("'@KmsEncryptionContext' is a" + . " required argument when using KmsMaterialsProviderV2, and" + . " must be an associative array (or empty array)."); + } + if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) { + throw new CryptoException("Conflict in reserved @KmsEncryptionContext" + . " key aws:x-amz-cek-alg. This value is reserved for the S3" + . " Encryption Client and cannot be set by the user."); + } + $context = array_merge($options['@kmsencryptioncontext'], $context); + $result = $this->kmsClient->generateDataKey([ + 'KeyId' => $this->kmsKeyId, + 'KeySpec' => "AES_{$keySize}", + 'EncryptionContext' => $context + ]); + return [ + 'Plaintext' => $result['Plaintext'], + 'Ciphertext' => base64_encode($result['CiphertextBlob']), + 'UpdatedContext' => $context + ]; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php new file mode 100644 index 00000000..afbcaf13 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php @@ -0,0 +1,105 @@ + true, + 192 => true, + 256 => true, + ]; + + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + + /** + * Performs further initialization of the MaterialsProvider based on the + * data inside the MetadataEnvelope. + * + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be read from. + * + * @return MaterialsProvider + * + * @throws \RuntimeException Thrown when there is an empty or improperly + * formed materials description in the envelope. + * + * @internal + */ + abstract public function fromDecryptionEnvelope(MetadataEnvelope $envelope); + + /** + * Returns the material description for this Provider so it can be verified + * by encryption mechanisms. + * + * @return string + */ + abstract public function getMaterialsDescription(); + + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + abstract public function getWrapAlgorithmName(); + + /** + * Takes a content encryption key (CEK) and description to return an + * encrypted key according to the Provider's specifications. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + abstract public function encryptCek($unencryptedCek, $materialDescription); + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + abstract public function decryptCek($encryptedCek, $materialDescription); + + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * + * @return string + */ + public function generateCek($keySize) + { + return openssl_random_pseudo_bytes($keySize / 8); + } + + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return openssl_random_pseudo_bytes( + openssl_cipher_iv_length($openSslName) + ); + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php new file mode 100644 index 00000000..71093d54 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php @@ -0,0 +1,61 @@ + true, + 256 => true, + ]; + + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + abstract public function getWrapAlgorithmName(); + + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * decrypting the CEK. + * @param string $options Options for use in decrypting the CEK. + * + * @return string + */ + abstract public function decryptCek($encryptedCek, $materialDescription, $options); + + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * @param array $context Context map needed for key encryption + * @param array $options Additional options to be used in CEK generation + * + * @return array + */ + abstract public function generateCek($keySize, $context, $options); + + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return openssl_random_pseudo_bytes( + openssl_cipher_iv_length($openSslName) + ); + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php new file mode 100644 index 00000000..3206c0d8 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php @@ -0,0 +1,61 @@ +getConstants()) as $constant) { + self::$constants[$constant] = true; + } + } + + return array_keys(self::$constants); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($name, $value) + { + $constants = self::getConstantValues(); + if (is_null($name) || !in_array($name, $constants)) { + throw new InvalidArgumentException('MetadataEnvelope fields must' + . ' must match a predefined offset; use the header constants.'); + } + + $this->data[$name] = $value; + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->data; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php new file mode 100644 index 00000000..370ef585 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php @@ -0,0 +1,30 @@ +instructionFileSuffix; + } + + protected function determineGetObjectStrategy( + $result, + $instructionFileSuffix + ) { + if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) { + return new HeadersMetadataStrategy(); + } + + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + } + + protected function getMetadataStrategy(array $args, $instructionFileSuffix) + { + if (!empty($args['@MetadataStrategy'])) { + if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) { + return $args['@MetadataStrategy']; + } + + if (is_string($args['@MetadataStrategy'])) { + switch ($args['@MetadataStrategy']) { + case HeadersMetadataStrategy::class: + return new HeadersMetadataStrategy(); + case InstructionFileMetadataStrategy::class: + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + default: + throw new \InvalidArgumentException('Could not match the' + . ' specified string in "MetadataStrategy" to a' + . ' predefined strategy.'); + } + } else { + throw new \InvalidArgumentException('The metadata strategy that' + . ' was passed to "MetadataStrategy" was unrecognized.'); + } + } elseif ($instructionFileSuffix) { + return new InstructionFileMetadataStrategy( + $this->client, + $instructionFileSuffix + ); + } + + return null; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php new file mode 100644 index 00000000..592f68bc --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php @@ -0,0 +1,19 @@ + $value) { + $args['Metadata'][$header] = $value; + } + + return $args; + } + + /** + * Generates a MetadataEnvelope according to the metadata headers from the + * GetObject result. + * + * @param array $args Arguments from Command and Result that contains + * S3 Object information, relevant headers, and command + * configuration. + * + * @return MetadataEnvelope + */ + public function load(array $args) + { + $envelope = new MetadataEnvelope(); + $constantValues = MetadataEnvelope::getConstantValues(); + + foreach ($constantValues as $constant) { + if (!empty($args['Metadata'][$constant])) { + $envelope[$constant] = $args['Metadata'][$constant]; + } + } + + return $envelope; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php b/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php new file mode 100644 index 00000000..25384bb3 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php @@ -0,0 +1,90 @@ +suffix = empty($suffix) + ? self::DEFAULT_FILE_SUFFIX + : $suffix; + $this->client = $client; + } + + /** + * Places the information in the MetadataEnvelope to a location on S3. + * + * @param MetadataEnvelope $envelope Encryption data to save according to + * the strategy. + * @param array $args Starting arguments for PutObject, used for saving + * extra the instruction file. + * + * @return array Updated arguments for PutObject. + */ + public function save(MetadataEnvelope $envelope, array $args) + { + $this->client->putObject([ + 'Bucket' => $args['Bucket'], + 'Key' => $args['Key'] . $this->suffix, + 'Body' => json_encode($envelope) + ]); + + return $args; + } + + /** + * Uses the strategy's client to retrieve the instruction file from S3 and generates + * a MetadataEnvelope from its contents. + * + * @param array $args Arguments from Command and Result that contains + * S3 Object information, relevant headers, and command + * configuration. + * + * @return MetadataEnvelope + */ + public function load(array $args) + { + $result = $this->client->getObject([ + 'Bucket' => $args['Bucket'], + 'Key' => $args['Key'] . $this->suffix + ]); + + $metadataHeaders = json_decode($result['Body'], true); + $envelope = new MetadataEnvelope(); + $constantValues = MetadataEnvelope::getConstantValues(); + + foreach ($constantValues as $constant) { + if (!empty($metadataHeaders[$constant])) { + $envelope[$constant] = $metadataHeaders[$constant]; + } + } + + return $envelope; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php new file mode 100644 index 00000000..1ddd4862 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php @@ -0,0 +1,339 @@ +client = $client; + $this->instructionFileSuffix = $instructionFileSuffix; + MetricsBuilder::appendMetricsCaptureMiddleware( + $this->client->getHandlerList(), + MetricsBuilder::S3_CRYPTO_V1N + ); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + /** + * Encrypts the data in the 'Body' field of $args and promises to upload it + * to the specified location on S3. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. Only the + * Cipher option is required. Accepts the following: + * - Cipher: (string) cbc|gcm + * See also: AbstractCryptoClient::$supportedCiphers. Note that + * cbc is deprecated and gcm should be used when possible. + * - KeySize: (int) 128|192|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. Note if you pass in Aad for gcm encryption, the + * PHP SDK will be able to decrypt the resulting object, but other + * AWS SDKs may not be able to do so. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if using an InstructionFileMetadataHandler. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $envelope = new MetadataEnvelope(); + + return Promise\Create::promiseFor($this->encrypt( + Psr7\Utils::streamFor($args['Body']), + $args['@CipherOptions'] ?: [], + $provider, + $envelope + ))->then( + function ($encryptedBodyStream) use ($args) { + $hash = new PhpHash('sha256'); + $hashingEncryptedBodyStream = new HashingStream( + $encryptedBodyStream, + $hash, + self::getContentShaDecorator($args) + ); + return [$hashingEncryptedBodyStream, $args]; + } + )->then( + function ($putObjectContents) use ($strategy, $envelope) { + list($bodyStream, $args) = $putObjectContents; + if ($strategy === null) { + $strategy = self::getDefaultStrategy(); + } + + $updatedArgs = $strategy->save($envelope, $args); + $updatedArgs['Body'] = $bodyStream; + return $updatedArgs; + } + )->then( + function ($args) { + unset($args['@CipherOptions']); + return $this->client->putObjectAsync($args); + } + ); + } + + private static function getContentShaDecorator(&$args) + { + return function ($hash) use (&$args) { + $args['ContentSHA256'] = bin2hex($hash); + }; + } + + /** + * Encrypts the data in the 'Body' field of $args and uploads it to the + * specified location on S3. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Cipher: (string) cbc|gcm + * See also: AbstractCryptoClient::$supportedCiphers. Note that + * cbc is deprecated and gcm should be used when possible. + * - KeySize: (int) 128|192|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. Note if you pass in Aad for gcm encryption, the + * PHP SDK will be able to decrypt the resulting object, but other + * AWS SDKs may not be able to do so. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if an using an InstructionFileMetadataHandler was + * determined. + * + * @return \Aws\Result PutObject call result with the details of uploading + * the encrypted file. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObject(array $args) + { + return $this->putObjectAsync($args)->wait(); + } + + /** + * Promises to retrieve an object from S3 and decrypt the data in the + * 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading + * MetadataEnvelope information. Defaults to determining based on object + * response headers. Can either be a class implementing MetadataStrategy, + * a class name of a predefined strategy, or empty/null to default. + * - @InstructionFileSuffix: (string) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler is being used. + * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when required arguments are not + * passed or are passed incorrectly. + */ + public function getObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $saveAs = null; + if (!empty($args['SaveAs'])) { + $saveAs = $args['SaveAs']; + } + + $promise = $this->client->getObjectAsync($args) + ->then( + function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { + if ($strategy === null) { + $strategy = $this->determineGetObjectStrategy( + $result, + $instructionFileSuffix + ); + } + + $envelope = $strategy->load($args + [ + 'Metadata' => $result['Metadata'] + ]); + + $provider = $provider->fromDecryptionEnvelope($envelope); + + $result['Body'] = $this->decrypt( + $result['Body'], + $provider, + $envelope, + isset($args['@CipherOptions']) + ? $args['@CipherOptions'] + : [] + ); + return $result; + } + )->then( + function ($result) use ($saveAs) { + if (!empty($saveAs)) { + file_put_contents( + $saveAs, + (string) $result['Body'], + LOCK_EX + ); + } + return $result; + } + ); + + return $promise; + } + + /** + * Retrieves an object from S3 and decrypts the data in the 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * - @InstructionFileSuffix: (string|null) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler was detected. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * + * @return \Aws\Result GetObject call result with the 'Body' field + * wrapped in a decryption stream with its metadata + * information. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function getObject(array $args) + { + return $this->getObjectAsync($args)->wait(); + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php new file mode 100644 index 00000000..39a98dcf --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php @@ -0,0 +1,447 @@ + + * use Local\Aws\Crypto\KmsMaterialsProviderV2; + * use Aws\S3\Crypto\S3EncryptionClientV2; + * use Aws\S3\S3Client; + * + * $encryptionClient = new S3EncryptionClientV2( + * new S3Client([ + * 'region' => 'us-west-2', + * 'version' => 'latest' + * ]) + * ); + * $materialsProvider = new KmsMaterialsProviderV2( + * new KmsClient([ + * 'profile' => 'default', + * 'region' => 'us-east-1', + * 'version' => 'latest', + * ], + * 'your-kms-key-id' + * ); + * + * $encryptionClient->putObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * '@KmsEncryptionContext' => ['foo' => 'bar'], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * 'Body' => 'your-encrypted-data', + * ]); + * + * + * Example read call (using objects from previous example): + * + * + * $encryptionClient->getObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * ]); + * + */ +class S3EncryptionClientV2 extends AbstractCryptoClientV2 +{ + use CipherBuilderTrait; + use CryptoParamsTraitV2; + use DecryptionTraitV2; + use EncryptionTraitV2; + use UserAgentTrait; + + const CRYPTO_VERSION = '2.1'; + + private $client; + private $instructionFileSuffix; + private $legacyWarningCount; + + /** + * @param S3Client $client The S3Client to be used for true uploading and + * retrieving objects from S3 when using the + * encryption client. + * @param string|null $instructionFileSuffix Suffix for a client wide + * default when using instruction + * files for metadata storage. + */ + public function __construct( + S3Client $client, + $instructionFileSuffix = null + ) { + $this->client = $client; + $this->instructionFileSuffix = $instructionFileSuffix; + $this->legacyWarningCount = 0; + MetricsBuilder::appendMetricsCaptureMiddleware( + $this->client->getHandlerList(), + MetricsBuilder::S3_CRYPTO_V2 + ); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + /** + * Encrypts the data in the 'Body' field of $args and promises to upload it + * to the specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. Only the + * Cipher option is required. Accepts the following: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if using an InstructionFileMetadataHandler. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $envelope = new MetadataEnvelope(); + + return Promise\Create::promiseFor($this->encrypt( + Psr7\Utils::streamFor($args['Body']), + $args, + $provider, + $envelope + ))->then( + function ($encryptedBodyStream) use ($args) { + $hash = new PhpHash('sha256'); + $hashingEncryptedBodyStream = new HashingStream( + $encryptedBodyStream, + $hash, + self::getContentShaDecorator($args) + ); + return [$hashingEncryptedBodyStream, $args]; + } + )->then( + function ($putObjectContents) use ($strategy, $envelope) { + list($bodyStream, $args) = $putObjectContents; + if ($strategy === null) { + $strategy = self::getDefaultStrategy(); + } + + $updatedArgs = $strategy->save($envelope, $args); + $updatedArgs['Body'] = $bodyStream; + return $updatedArgs; + } + )->then( + function ($args) { + unset($args['@CipherOptions']); + return $this->client->putObjectAsync($args); + } + ); + } + + private static function getContentShaDecorator(&$args) + { + return function ($hash) use (&$args) { + $args['ContentSHA256'] = bin2hex($hash); + }; + } + + /** + * Encrypts the data in the 'Body' field of $args and uploads it to the + * specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if an using an InstructionFileMetadataHandler was + * determined. + * + * @return \Aws\Result PutObject call result with the details of uploading + * the encrypted file. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObject(array $args) + { + return $this->putObjectAsync($args)->wait(); + } + + /** + * Promises to retrieve an object from S3 and decrypt the data in the + * 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading + * MetadataEnvelope information. Defaults to determining based on object + * response headers. Can either be a class implementing MetadataStrategy, + * a class name of a predefined strategy, or empty/null to default. + * - @InstructionFileSuffix: (string) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler is being used. + * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when required arguments are not + * passed or are passed incorrectly. + */ + public function getObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + if ( + !isset($args['@SecurityProfile']) + || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) + ) { + throw new CryptoException("@SecurityProfile is required and must be" + . " set to 'V2' or 'V2_AND_LEGACY'"); + } + + // Only throw this legacy warning once per client + if ( + in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) + && $this->legacyWarningCount < 1 + ) { + $this->legacyWarningCount++; + trigger_error( + "This S3 Encryption Client operation is configured to" + . " read encrypted data with legacy encryption modes. If you" + . " don't have objects encrypted with these legacy modes," + . " you should disable support for them to enhance security. ", + E_USER_WARNING + ); + } + + $saveAs = null; + if (!empty($args['SaveAs'])) { + $saveAs = $args['SaveAs']; + } + + $promise = $this->client->getObjectAsync($args) + ->then( + function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { + if ($strategy === null) { + $strategy = $this->determineGetObjectStrategy( + $result, + $instructionFileSuffix + ); + } + + $envelope = $strategy->load($args + [ + 'Metadata' => $result['Metadata'] + ]); + + $result['Body'] = $this->decrypt( + $result['Body'], + $provider, + $envelope, + $args + ); + return $result; + } + )->then( + function ($result) use ($saveAs) { + if (!empty($saveAs)) { + file_put_contents( + $saveAs, + (string) $result['Body'], + LOCK_EX + ); + } + return $result; + } + ); + + return $promise; + } + + /** + * Retrieves an object from S3 and decrypts the data in the 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * - @InstructionFileSuffix: (string|null) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler was detected. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return \Aws\Result GetObject call result with the 'Body' field + * wrapped in a decryption stream with its metadata + * information. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function getObject(array $args) + { + return $this->getObjectAsync($args)->wait(); + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php new file mode 100644 index 00000000..4fdb7a77 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php @@ -0,0 +1,169 @@ +appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); + $this->client = $client; + $config['params'] = []; + if (!empty($config['bucket'])) { + $config['params']['Bucket'] = $config['bucket']; + } + if (!empty($config['key'])) { + $config['params']['Key'] = $config['key']; + } + + $this->provider = $this->getMaterialsProvider($config); + unset($config['@MaterialsProvider']); + + $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); + unset($config['@InstructionFileSuffix']); + $this->strategy = $this->getMetadataStrategy( + $config, + $this->instructionFileSuffix + ); + if ($this->strategy === null) { + $this->strategy = self::getDefaultStrategy(); + } + unset($config['@MetadataStrategy']); + + $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); + + parent::__construct($client, $source, $config); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + private function getEncryptingDataPreparer() + { + return function () { + // Defer encryption work until promise is executed + $envelope = new MetadataEnvelope(); + + list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( + $this->source, + $this->config['@cipheroptions'] ?: [], + $this->provider, + $envelope + ))->then( + function ($bodyStream) use ($envelope) { + $params = $this->strategy->save( + $envelope, + $this->config['params'] + ); + return [$bodyStream, $params]; + } + )->wait(); + + $this->source->rewind(); + $this->config['params'] = $params; + }; + } +} \ No newline at end of file diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php new file mode 100644 index 00000000..813305ef --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php @@ -0,0 +1,176 @@ +appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); + $this->client = $client; + $config['params'] = []; + if (!empty($config['bucket'])) { + $config['params']['Bucket'] = $config['bucket']; + } + if (!empty($config['key'])) { + $config['params']['Key'] = $config['key']; + } + + $this->provider = $this->getMaterialsProvider($config); + unset($config['@MaterialsProvider']); + + $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); + unset($config['@InstructionFileSuffix']); + $this->strategy = $this->getMetadataStrategy( + $config, + $this->instructionFileSuffix + ); + if ($this->strategy === null) { + $this->strategy = self::getDefaultStrategy(); + } + unset($config['@MetadataStrategy']); + + $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); + + parent::__construct($client, $source, $config); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + private function getEncryptingDataPreparer() + { + return function () { + // Defer encryption work until promise is executed + $envelope = new MetadataEnvelope(); + + list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( + $this->source, + $this->config ?: [], + $this->provider, + $envelope + ))->then( + function ($bodyStream) use ($envelope) { + $params = $this->strategy->save( + $envelope, + $this->config['params'] + ); + return [$bodyStream, $params]; + } + )->wait(); + + $this->source->rewind(); + $this->config['params'] = $params; + }; + } +} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php b/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php new file mode 100644 index 00000000..7f95ca06 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php @@ -0,0 +1,32 @@ +getHandlerList(); + $list->appendBuild(Middleware::mapRequest( + function (RequestInterface $req) use ($agentString) { + if ( + !empty($req->getHeader('User-Agent')) + && !empty($req->getHeader('User-Agent')[0]) + ) { + $userAgent = $req->getHeader('User-Agent')[0]; + if (strpos($userAgent, $agentString) === false) { + $userAgent .= " {$agentString}"; + }; + } else { + $userAgent = $agentString; + } + + $req = $req->withHeader('User-Agent', $userAgent); + return $req; + } + )); + } +} diff --git a/test-server/php-v3-server/src/index.php b/test-server/php-v3-server/src/index.php index cc5dee29..59acda97 100644 --- a/test-server/php-v3-server/src/index.php +++ b/test-server/php-v3-server/src/index.php @@ -5,8 +5,8 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Aws\S3\Crypto\S3EncryptionClientV2; -use Aws\Crypto\KmsMaterialsProviderV2; +use Local\Aws\S3\Crypto\S3EncryptionClientV2; +use Local\Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; From afcc44b5e980e2579c6bab4812ffc1d0102ed217 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 24 Sep 2025 14:59:42 -0700 Subject: [PATCH 04/10] add a fake v3 client --- .../local-php-sdk/Crypto/MetadataEnvelope.php | 4 + .../S3EC/S3EncryptionClientV3.php | 447 ++++++++++++++++++ test-server/php-v3-server/src/index.php | 6 +- 3 files changed, 454 insertions(+), 3 deletions(-) create mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php index 3206c0d8..1e780d9e 100644 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php @@ -17,12 +17,16 @@ class MetadataEnvelope implements ArrayAccess, IteratorAggregate, JsonSerializab use HasDataTrait; const CONTENT_KEY_V2_HEADER = 'x-amz-key-v2'; + const ENCRYPTED_DATA_KEY_V3 = 'x-amz-3'; const IV_HEADER = 'x-amz-iv'; const MATERIALS_DESCRIPTION_HEADER = 'x-amz-matdesc'; + const MAT_DESC_V3 = 'x-amx-m'; const KEY_WRAP_ALGORITHM_HEADER = 'x-amz-wrap-alg'; + const ENCRYPTED_DATA_KEY_ALGORITHM_V3 = 'x-amz-w'; const CONTENT_CRYPTO_SCHEME_HEADER = 'x-amz-cek-alg'; const CRYPTO_TAG_LENGTH_HEADER = 'x-amz-tag-len'; const UNENCRYPTED_CONTENT_LENGTH_HEADER = 'x-amz-unencrypted-content-length'; + const ENCRYPTION_CONTEXT_V3 = 'x-amz-t'; private static $constants = []; diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php new file mode 100644 index 00000000..50719a72 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php @@ -0,0 +1,447 @@ + + * use Local\Aws\Crypto\KmsMaterialsProviderV2; + * use Aws\S3\Crypto\S3EncryptionClientV2; + * use Aws\S3\S3Client; + * + * $encryptionClient = new S3EncryptionClientV2( + * new S3Client([ + * 'region' => 'us-west-2', + * 'version' => 'latest' + * ]) + * ); + * $materialsProvider = new KmsMaterialsProviderV2( + * new KmsClient([ + * 'profile' => 'default', + * 'region' => 'us-east-1', + * 'version' => 'latest', + * ], + * 'your-kms-key-id' + * ); + * + * $encryptionClient->putObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * '@KmsEncryptionContext' => ['foo' => 'bar'], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * 'Body' => 'your-encrypted-data', + * ]); + * + * + * Example read call (using objects from previous example): + * + * + * $encryptionClient->getObject([ + * '@MaterialsProvider' => $materialsProvider, + * '@CipherOptions' => [ + * 'Cipher' => 'gcm', + * 'KeySize' => 256, + * ], + * 'Bucket' => 'your-bucket', + * 'Key' => 'your-key', + * ]); + * + */ +class S3EncryptionClientV3 extends AbstractCryptoClientV2 +{ + use CipherBuilderTrait; + use CryptoParamsTraitV2; + use DecryptionTraitV2; + use EncryptionTraitV2; + use UserAgentTrait; + + const CRYPTO_VERSION = '3.0'; + + private $client; + private $instructionFileSuffix; + private $legacyWarningCount; + + /** + * @param S3Client $client The S3Client to be used for true uploading and + * retrieving objects from S3 when using the + * encryption client. + * @param string|null $instructionFileSuffix Suffix for a client wide + * default when using instruction + * files for metadata storage. + */ + public function __construct( + S3Client $client, + $instructionFileSuffix = null + ) { + $this->client = $client; + $this->instructionFileSuffix = $instructionFileSuffix; + $this->legacyWarningCount = 0; + MetricsBuilder::appendMetricsCaptureMiddleware( + $this->client->getHandlerList(), + MetricsBuilder::S3_CRYPTO_V2 + ); + } + + private static function getDefaultStrategy() + { + return new HeadersMetadataStrategy(); + } + + /** + * Encrypts the data in the 'Body' field of $args and promises to upload it + * to the specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. Only the + * Cipher option is required. Accepts the following: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if using an InstructionFileMetadataHandler. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + $envelope = new MetadataEnvelope(); + + return Promise\Create::promiseFor($this->encrypt( + Psr7\Utils::streamFor($args['Body']), + $args, + $provider, + $envelope + ))->then( + function ($encryptedBodyStream) use ($args) { + $hash = new PhpHash('sha256'); + $hashingEncryptedBodyStream = new HashingStream( + $encryptedBodyStream, + $hash, + self::getContentShaDecorator($args) + ); + return [$hashingEncryptedBodyStream, $args]; + } + )->then( + function ($putObjectContents) use ($strategy, $envelope) { + list($bodyStream, $args) = $putObjectContents; + if ($strategy === null) { + $strategy = self::getDefaultStrategy(); + } + + $updatedArgs = $strategy->save($envelope, $args); + $updatedArgs['Body'] = $bodyStream; + return $updatedArgs; + } + )->then( + function ($args) { + unset($args['@CipherOptions']); + return $this->client->putObjectAsync($args); + } + ); + } + + private static function getContentShaDecorator(&$args) + { + return function ($hash) use (&$args) { + $args['ContentSHA256'] = bin2hex($hash); + }; + } + + /** + * Encrypts the data in the 'Body' field of $args and uploads it to the + * specified location on S3. + * + * Note that for PHP versions of < 7.1, this operation uses an AES-GCM + * polyfill for encryption since there is no native PHP support. The + * performance for large inputs will be a lot slower than for PHP 7.1+, so + * upgrading older PHP version environments may be necessary to use this + * effectively. + * + * @param array $args Arguments for encrypting an object and uploading it + * to S3 via PutObject. + * + * The required configuration arguments are as follows: + * + * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek + * encrypting/decrypting for encryption metadata. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Cipher: (string) gcm + * See also: AbstractCryptoClientV2::$supportedCiphers + * - KeySize: (int) 128|256 + * See also: MaterialsProvider::$supportedKeySizes + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. Note if you pass in + * Aad, the PHP SDK will be able to decrypt the resulting object, + * but other AWS SDKs may not be able to do so. + * - @KmsEncryptionContext: (array) Only required if using + * KmsMaterialsProviderV2. An associative array of key-value + * pairs to be added to the encryption context for KMS key encryption. An + * empty array may be passed if no additional context is desired. + * + * The optional configuration arguments are as follows: + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing + * MetadataEnvelope information. Defaults to using a + * HeadersMetadataStrategy. Can either be a class implementing + * MetadataStrategy, a class name of a predefined strategy, or empty/null + * to default. + * - @InstructionFileSuffix: (string|null) Suffix used when writing to an + * instruction file if an using an InstructionFileMetadataHandler was + * determined. + * + * @return \Aws\Result PutObject call result with the details of uploading + * the encrypted file. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function putObject(array $args) + { + return $this->putObjectAsync($args)->wait(); + } + + /** + * Promises to retrieve an object from S3 and decrypt the data in the + * 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * + * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading + * MetadataEnvelope information. Defaults to determining based on object + * response headers. Can either be a class implementing MetadataStrategy, + * a class name of a predefined strategy, or empty/null to default. + * - @InstructionFileSuffix: (string) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler is being used. + * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return PromiseInterface + * + * @throws \InvalidArgumentException Thrown when required arguments are not + * passed or are passed incorrectly. + */ + public function getObjectAsync(array $args) + { + $provider = $this->getMaterialsProvider($args); + unset($args['@MaterialsProvider']); + + $instructionFileSuffix = $this->getInstructionFileSuffix($args); + unset($args['@InstructionFileSuffix']); + + $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); + unset($args['@MetadataStrategy']); + + if ( + !isset($args['@SecurityProfile']) + || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) + ) { + throw new CryptoException("@SecurityProfile is required and must be" + . " set to 'V2' or 'V2_AND_LEGACY'"); + } + + // Only throw this legacy warning once per client + if ( + in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) + && $this->legacyWarningCount < 1 + ) { + $this->legacyWarningCount++; + trigger_error( + "This S3 Encryption Client operation is configured to" + . " read encrypted data with legacy encryption modes. If you" + . " don't have objects encrypted with these legacy modes," + . " you should disable support for them to enhance security. ", + E_USER_WARNING + ); + } + + $saveAs = null; + if (!empty($args['SaveAs'])) { + $saveAs = $args['SaveAs']; + } + + $promise = $this->client->getObjectAsync($args) + ->then( + function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { + if ($strategy === null) { + $strategy = $this->determineGetObjectStrategy( + $result, + $instructionFileSuffix + ); + } + + $envelope = $strategy->load($args + [ + 'Metadata' => $result['Metadata'] + ]); + + $result['Body'] = $this->decrypt( + $result['Body'], + $provider, + $envelope, + $args + ); + return $result; + } + )->then( + function ($result) use ($saveAs) { + if (!empty($saveAs)) { + file_put_contents( + $saveAs, + (string) $result['Body'], + LOCK_EX + ); + } + return $result; + } + ); + + return $promise; + } + + /** + * Retrieves an object from S3 and decrypts the data in the 'Body' field. + * + * @param array $args Arguments for retrieving an object from S3 via + * GetObject and decrypting it. + * + * The required configuration argument is as follows: + * + * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek + * encrypting/decrypting for decryption metadata. May have data loaded + * from the MetadataEnvelope upon decryption. + * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. + * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 + * content encryption and key wrap schemas are able to be decrypted. + * - 'V2_AND_LEGACY' indicates that objects encrypted with both + * S3EncryptionClientV2 and older legacy encryption clients are able + * to be decrypted. + * + * The optional configuration arguments are as follows: + * + * - SaveAs: (string) The path to a file on disk to save the decrypted + * object data. This will be handled by file_put_contents instead of the + * Guzzle sink. + * - @InstructionFileSuffix: (string|null) Suffix used when looking for an + * instruction file if an InstructionFileMetadataHandler was detected. + * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher + * is required. Accepts the following options: + * - Aad: (string) Additional authentication data. This option is + * passed directly to OpenSSL when using gcm. It is ignored when + * using cbc. + * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with + * KMS materials for any KMS key ID, instead of needing the KMS key ID to + * be specified and provided to the decrypt operation. Ignored for non-KMS + * materials providers. Defaults to false. + * + * @return \Aws\Result GetObject call result with the 'Body' field + * wrapped in a decryption stream with its metadata + * information. + * + * @throws \InvalidArgumentException Thrown when arguments above are not + * passed or are passed incorrectly. + */ + public function getObject(array $args) + { + return $this->getObjectAsync($args)->wait(); + } +} diff --git a/test-server/php-v3-server/src/index.php b/test-server/php-v3-server/src/index.php index 59acda97..a60e5a3f 100644 --- a/test-server/php-v3-server/src/index.php +++ b/test-server/php-v3-server/src/index.php @@ -5,7 +5,7 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Local\Aws\S3\Crypto\S3EncryptionClientV2; +use Local\Aws\S3\Crypto\S3EncryptionClientV3; use Local\Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; @@ -157,7 +157,7 @@ function getCachedClient($clientId) // Recreate the AWS clients from stored configuration $s3Client = new S3Client($config['s3Config']); - $encryptionClient = new S3EncryptionClientV2($s3Client); + $encryptionClient = new S3EncryptionClientV3($s3Client); $kmsClient = new KmsClient($config['kmsConfig']); $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $config['kmsKeyId']); @@ -183,7 +183,7 @@ function createDefaultClientTuple(): array ] ] ]); - $encryptionClient = new S3EncryptionClientV2($s3Client); + $encryptionClient = new S3EncryptionClientV3($s3Client); $kmsClient = new KmsClient([ 'region' => 'us-west-2', From 3659587351ea4352ec72ab84727d7464597d43e9 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 10:08:54 -0700 Subject: [PATCH 05/10] more work: --- .../local-php-sdk/Crypto/MetadataEnvelope.php | 3 +++ .../S3EC/InstructionFileMetadataStrategy.php | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php index 1e780d9e..045fb16d 100644 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php +++ b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php @@ -24,9 +24,12 @@ class MetadataEnvelope implements ArrayAccess, IteratorAggregate, JsonSerializab const KEY_WRAP_ALGORITHM_HEADER = 'x-amz-wrap-alg'; const ENCRYPTED_DATA_KEY_ALGORITHM_V3 = 'x-amz-w'; const CONTENT_CRYPTO_SCHEME_HEADER = 'x-amz-cek-alg'; + const CONTENT_CIPHER_V3 = 'x-amz-c'; const CRYPTO_TAG_LENGTH_HEADER = 'x-amz-tag-len'; const UNENCRYPTED_CONTENT_LENGTH_HEADER = 'x-amz-unencrypted-content-length'; const ENCRYPTION_CONTEXT_V3 = 'x-amz-t'; + const KEY_COMMITMENT_V3 = 'x-amz-d'; + const MESSAGE_ID_V3 = 'x-amz-i'; private static $constants = []; diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php b/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php index 25384bb3..517a296f 100644 --- a/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php +++ b/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php @@ -85,6 +85,24 @@ public function load(array $args) } } + // check if we are reading a V3 object + // if it is a V3 object some data is stored in the object metadata and some + // as in the instruction file + //= aws-encryption-sdk-specification/s3-encryption/data-format/content-metadata.md#v3 + //# In the V3 format, the mapkeys "x-amz-c", "x-amz-d", and "x-amz-i" + //# MUST be stored exclusively in the Object Metadata + if (!empty($envelope[MetadataEnvelope::ENCRYPTED_DATA_KEY_V3])) { + // this data is stored in the original object's metadata + // V3 added x-amz-c, x-amz-d, x-amz-i, x-amz-3, x-amz-w, x-amz-m, x-amz-t + // x-amz-c, x-amz-d, x-amz-i are strictly stored on the object metadata + // the rest are stored in the instruction file + $contentCipherV3 = $args['Metadata'][MetadataEnvelope::CONTENT_CIPHER_V3]; + $keyCommitmentV3 = $args['Metadata'][MetadataEnvelope::KEY_COMMITMENT_V3]; + $messageIdV3 = $args['Metadata'][MetadataEnvelope::MESSAGE_ID_V3]; + + + } + return $envelope; } } From f4e97325cb3c14fd0d355905e307e0d9fab55ecf Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 10:38:01 -0700 Subject: [PATCH 06/10] submodule to the private aws php sdk --- .gitmodules | 10 +- test-server/php-v2-server/local-php-sdk | 1 + .../Crypto/AbstractCryptoClient.php | 121 ----- .../Crypto/AbstractCryptoClientV2.php | 119 ----- .../Crypto/AesDecryptingStream.php | 147 ------ .../Crypto/AesEncryptingStream.php | 152 ------ .../Crypto/AesGcmDecryptingStream.php | 104 ---- .../Crypto/AesGcmEncryptingStream.php | 119 ----- .../Crypto/AesStreamInterface.php | 30 -- .../Crypto/AesStreamInterfaceV2.php | 31 -- .../local-php-sdk/Crypto/Cipher/Cbc.php | 88 ---- .../Crypto/Cipher/CipherBuilderTrait.php | 72 --- .../Crypto/Cipher/CipherMethod.php | 59 --- .../local-php-sdk/Crypto/DecryptionTrait.php | 181 ------- .../Crypto/DecryptionTraitV2.php | 254 ---------- .../local-php-sdk/Crypto/EncryptionTrait.php | 194 -------- .../Crypto/EncryptionTraitV2.php | 198 -------- .../Crypto/KmsMaterialsProvider.php | 123 ----- .../Crypto/KmsMaterialsProviderV2.php | 101 ---- .../Crypto/MaterialsProvider.php | 105 ---- .../Crypto/MaterialsProviderInterface.php | 61 --- .../Crypto/MaterialsProviderInterfaceV2.php | 53 --- .../Crypto/MaterialsProviderV2.php | 66 --- .../local-php-sdk/Crypto/MetadataEnvelope.php | 61 --- .../Crypto/MetadataStrategyInterface.php | 30 -- .../local-php-sdk/S3EC/CryptoParamsTrait.php | 75 --- .../S3EC/CryptoParamsTraitV2.php | 19 - .../S3EC/HeadersMetadataStrategy.php | 52 -- .../S3EC/InstructionFileMetadataStrategy.php | 90 ---- .../local-php-sdk/S3EC/S3EncryptionClient.php | 339 ------------- .../S3EC/S3EncryptionClientV2.php | 447 ------------------ .../S3EC/S3EncryptionMultipartUploader.php | 169 ------- .../S3EC/S3EncryptionMultipartUploaderV2.php | 176 ------- .../local-php-sdk/S3EC/UserAgentTrait.php | 32 -- test-server/php-v3-server/local-php-sdk | 1 + .../Crypto/AbstractCryptoClient.php | 121 ----- .../Crypto/AbstractCryptoClientV2.php | 119 ----- .../Crypto/AesDecryptingStream.php | 147 ------ .../Crypto/AesEncryptingStream.php | 152 ------ .../Crypto/AesGcmDecryptingStream.php | 104 ---- .../Crypto/AesGcmEncryptingStream.php | 119 ----- .../Crypto/AesStreamInterface.php | 30 -- .../Crypto/AesStreamInterfaceV2.php | 31 -- .../local-php-sdk/Crypto/Cipher/Cbc.php | 88 ---- .../Crypto/Cipher/CipherBuilderTrait.php | 72 --- .../Crypto/Cipher/CipherMethod.php | 59 --- .../local-php-sdk/Crypto/DecryptionTrait.php | 181 ------- .../Crypto/DecryptionTraitV2.php | 254 ---------- .../local-php-sdk/Crypto/EncryptionTrait.php | 194 -------- .../Crypto/EncryptionTraitV2.php | 198 -------- .../Crypto/KmsMaterialsProvider.php | 123 ----- .../Crypto/KmsMaterialsProviderV2.php | 101 ---- .../Crypto/MaterialsProvider.php | 105 ---- .../Crypto/MaterialsProviderInterface.php | 61 --- .../Crypto/MaterialsProviderInterfaceV2.php | 53 --- .../Crypto/MaterialsProviderV2.php | 66 --- .../local-php-sdk/Crypto/MetadataEnvelope.php | 68 --- .../Crypto/MetadataStrategyInterface.php | 30 -- .../local-php-sdk/S3EC/CryptoParamsTrait.php | 75 --- .../S3EC/CryptoParamsTraitV2.php | 19 - .../S3EC/HeadersMetadataStrategy.php | 52 -- .../S3EC/InstructionFileMetadataStrategy.php | 108 ----- .../local-php-sdk/S3EC/S3EncryptionClient.php | 339 ------------- .../S3EC/S3EncryptionClientV2.php | 447 ------------------ .../S3EC/S3EncryptionClientV3.php | 447 ------------------ .../S3EC/S3EncryptionMultipartUploader.php | 169 ------- .../S3EC/S3EncryptionMultipartUploaderV2.php | 176 ------- .../local-php-sdk/S3EC/UserAgentTrait.php | 32 -- 68 files changed, 11 insertions(+), 8209 deletions(-) create mode 160000 test-server/php-v2-server/local-php-sdk delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClientV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesDecryptingStream.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterfaceV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/Cbc.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherMethod.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTrait.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterfaceV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php delete mode 100644 test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTrait.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/HeadersMetadataStrategy.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php delete mode 100644 test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php create mode 160000 test-server/php-v3-server/local-php-sdk delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClientV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesDecryptingStream.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterfaceV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/Cbc.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherMethod.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTrait.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterfaceV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php delete mode 100644 test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTrait.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/HeadersMetadataStrategy.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php delete mode 100644 test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php diff --git a/.gitmodules b/.gitmodules index ac305547..ce2abc73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,12 @@ url = git@github.com:aws/aws-sdk-ruby-staging.git [submodule "test-server/ruby-v3-server/local-ruby-sdk"] path = test-server/ruby-v3-server/local-ruby-sdk - url = git@github.com:aws/aws-sdk-ruby-staging.git \ No newline at end of file + url = git@github.com:aws/aws-sdk-ruby-staging.git +[submodule "test-server/php-v2-server/local-php-sdk"] + path = test-server/php-v2-server/local-php-sdk + url = git@github.com:aws/private-aws-sdk-php-staging.git + branch = s3ec/transitional +[submodule "test-server/php-v3-server/local-php-sdk"] + path = test-server/php-v3-server/local-php-sdk + url = git@github.com:aws/private-aws-sdk-php-staging.git + branch = s3ec/improved diff --git a/test-server/php-v2-server/local-php-sdk b/test-server/php-v2-server/local-php-sdk new file mode 160000 index 00000000..d78bd3b2 --- /dev/null +++ b/test-server/php-v2-server/local-php-sdk @@ -0,0 +1 @@ +Subproject commit d78bd3b221890aac679ec3b6cb5abcb01fd42699 diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php b/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php deleted file mode 100644 index 02b0211e..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/AbstractCryptoClient.php +++ /dev/null @@ -1,121 +0,0 @@ -stream = $cipherText; - $this->key = $key; - $this->cipherMethod = clone $cipherMethod; - } - - public function getOpenSslName() - { - return $this->cipherMethod->getOpenSslName(); - } - - public function getAesName() - { - return $this->cipherMethod->getAesName(); - } - - public function getCurrentIv() - { - return $this->cipherMethod->getCurrentIv(); - } - - public function getSize(): ?int - { - $plainTextSize = $this->stream->getSize(); - - if ($this->cipherMethod->requiresPadding()) { - // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be - // added to the plaintext to make it an even number of blocks. The - // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and - // strlen($cipherText) - 1 - return null; - } - - return $plainTextSize; - } - - public function isWritable(): bool - { - return false; - } - - public function read($length): string - { - if ($length > strlen($this->buffer)) { - $this->buffer .= $this->decryptBlock( - (int) ( - self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) - ) - ); - } - - $data = substr($this->buffer, 0, $length); - $this->buffer = substr($this->buffer, $length); - - return $data ? $data : ''; - } - - public function seek($offset, $whence = SEEK_SET): void - { - if ($offset === 0 && $whence === SEEK_SET) { - $this->buffer = ''; - $this->cipherMethod->seek(0, SEEK_SET); - $this->stream->seek(0, SEEK_SET); - } else { - throw new LogicException('AES encryption streams only support being' - . ' rewound, not arbitrary seeking.'); - } - } - - private function decryptBlock($length) - { - if ($this->stream->eof()) { - return ''; - } - - $cipherText = ''; - do { - $cipherText .= $this->stream->read((int) ($length - strlen($cipherText))); - } while (strlen($cipherText) < $length && !$this->stream->eof()); - - $options = OPENSSL_RAW_DATA; - if ( - !$this->stream->eof() - && $this->stream->getSize() !== $this->stream->tell() - ) { - $options |= OPENSSL_ZERO_PADDING; - } - - $plaintext = openssl_decrypt( - $cipherText, - $this->cipherMethod->getOpenSslName(), - $this->key, - $options, - $this->cipherMethod->getCurrentIv() - ); - - $this->cipherMethod->update($cipherText); - - return $plaintext; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php deleted file mode 100644 index b024c563..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/AesEncryptingStream.php +++ /dev/null @@ -1,152 +0,0 @@ -stream = $plainText; - $this->key = $key; - $this->cipherMethod = clone $cipherMethod; - } - - public function getOpenSslName() - { - return $this->cipherMethod->getOpenSslName(); - } - - public function getAesName() - { - return $this->cipherMethod->getAesName(); - } - - public function getCurrentIv() - { - return $this->cipherMethod->getCurrentIv(); - } - - public function getSize(): ?int - { - $plainTextSize = $this->stream->getSize(); - - if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { - // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be - // added to the plaintext to make it an even number of blocks. - $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; - return $plainTextSize + $padding; - } - - return $plainTextSize; - } - - public function isWritable(): bool - { - return false; - } - - public function read($length): string - { - if ($length > strlen($this->buffer)) { - $this->buffer .= $this->encryptBlock( - (int) - self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) - ); - } - - $data = substr($this->buffer, 0, $length); - $this->buffer = substr($this->buffer, $length); - - return $data ? $data : ''; - } - - public function seek($offset, $whence = SEEK_SET): void - { - if ($whence === SEEK_CUR) { - $offset = $this->tell() + $offset; - $whence = SEEK_SET; - } - - if ($whence === SEEK_SET) { - $this->buffer = ''; - $wholeBlockOffset - = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; - $this->stream->seek($wholeBlockOffset); - $this->cipherMethod->seek($wholeBlockOffset); - $this->read($offset - $wholeBlockOffset); - } else { - throw new LogicException('Unrecognized whence.'); - } - } - - private function encryptBlock($length) - { - if ($this->stream->eof()) { - return ''; - } - - $plainText = ''; - do { - $plainText .= $this->stream->read((int) ($length - strlen($plainText))); - } while (strlen($plainText) < $length && !$this->stream->eof()); - - $options = OPENSSL_RAW_DATA; - if ( - !$this->stream->eof() - || $this->stream->getSize() !== $this->stream->tell() - ) { - $options |= OPENSSL_ZERO_PADDING; - } - - $cipherText = openssl_encrypt( - $plainText, - $this->cipherMethod->getOpenSslName(), - $this->key, - $options, - $this->cipherMethod->getCurrentIv() - ); - - $this->cipherMethod->update($cipherText); - - return $cipherText; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php deleted file mode 100644 index aa87198a..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php +++ /dev/null @@ -1,104 +0,0 @@ -cipherText = $cipherText; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->tag = $tag; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-gcm"; - } - - public function getAesName() - { - return 'AES/GCM/NoPadding'; - } - - public function getCurrentIv() - { - return $this->initializationVector; - } - - public function createStream() - { - - $result = \openssl_decrypt( - (string) $this->cipherText, - $this->getOpenSslName(), - $this->key, - OPENSSL_RAW_DATA, - $this->initializationVector, - $this->tag, - $this->aad - ); - if ($result === false) { - throw new CryptoException('The requested object could not be ' - . 'decrypted due to an invalid authentication tag.'); - } - return Psr7\Utils::streamFor($result); - - } - - public function isWritable(): bool - { - return false; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php deleted file mode 100644 index 45a3e82b..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php +++ /dev/null @@ -1,119 +0,0 @@ -plaintext = $plaintext; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-gcm"; - } - - /** - * Same as static method and retained for backwards compatibility - * - * @return string - */ - public function getAesName() - { - return self::getStaticAesName(); - } - - public function getCurrentIv() - { - return $this->initializationVector; - } - - public function createStream() - { - return Psr7\Utils::streamFor(\openssl_encrypt( - (string) $this->plaintext, - $this->getOpenSslName(), - $this->key, - OPENSSL_RAW_DATA, - $this->initializationVector, - $this->tag, - $this->aad, - $this->tagLength - )); - } - - /** - * @return string - */ - public function getTag() - { - return $this->tag; - } - - public function isWritable(): bool - { - return false; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php deleted file mode 100644 index 5227790a..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/AesStreamInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -baseIv = $this->iv = $iv; - $this->keySize = $keySize; - - if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { - throw new InvalidArgumentException('Invalid initialization vector'); - } - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-cbc"; - } - - public function getAesName() - { - return 'AES/CBC/PKCS5Padding'; - } - - public function getCurrentIv() - { - return $this->iv; - } - - public function requiresPadding() - { - return true; - } - - public function seek($offset, $whence = SEEK_SET) - { - if ($offset === 0 && $whence === SEEK_SET) { - $this->iv = $this->baseIv; - } else { - throw new LogicException('CBC initialization only support being' - . ' rewound, not arbitrary seeking.'); - } - } - - public function update($cipherTextBlock) - { - $this->iv = substr($cipherTextBlock, self::BLOCK_SIZE * -1); - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php b/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php deleted file mode 100644 index bb8e6772..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php +++ /dev/null @@ -1,72 +0,0 @@ -decryptCek( - base64_decode( - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] - ), - json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ) - ); - $cipherOptions['KeySize'] = strlen($cek) * 8; - $cipherOptions['Cipher'] = $this->getCipherFromAesName( - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] - ); - - $decryptionStream = $this->getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ); - unset($cek); - - return $decryptionStream; - } - - private function getTagFromCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return (string) new LimitStream( - $cipherText, - $tagLength, - $cipherTextSize - $tagLength - ); - } - - private function getStrippedCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return new LimitStream( - $cipherText, - $cipherTextSize - $tagLength, - 0 - ); - } - - /** - * Generates a stream that wraps the cipher text with the proper cipher and - * uses the content encryption key (CEK) to decrypt the data when read. - * - * @param string $cipherText Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return AesStreamInterface - * - * @internal - */ - protected function getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ) { - $cipherTextStream = Psr7\Utils::streamFor($cipherText); - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ); - - return new AesGcmDecryptingStream( - $this->getStrippedCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ), - $cek, - $cipherOptions['Iv'], - $cipherOptions['Tag'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'] ?: null, - $cipherOptions['KeySize'] - ); - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - return new AesDecryptingStream( - $cipherTextStream, - $cek, - $cipherMethod - ); - } - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php deleted file mode 100644 index e8b583ea..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/DecryptionTraitV2.php +++ /dev/null @@ -1,254 +0,0 @@ -decryptCek( - base64_decode( - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] - ), - json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ), - $options - ); - $options['@CipherOptions']['KeySize'] = strlen($cek) * 8; - $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName( - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] - ); - - $this->validateOptionsAndEnvelope($options, $envelope); - - $decryptionStream = $this->getDecryptingStream( - $cipherText, - $cek, - $options['@CipherOptions'] - ); - unset($cek); - - return $decryptionStream; - } - - private function getTagFromCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return (string) new LimitStream( - $cipherText, - $tagLength, - $cipherTextSize - $tagLength - ); - } - - private function getStrippedCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return new LimitStream( - $cipherText, - $cipherTextSize - $tagLength, - 0 - ); - } - - private function validateOptionsAndEnvelope($options, $envelope) - { - $allowedCiphers = AbstractCryptoClientV2::$supportedCiphers; - $allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps; - if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') { - $allowedCiphers = array_unique(array_merge( - $allowedCiphers, - AbstractCryptoClient::$supportedCiphers - )); - $allowedKeywraps = array_unique(array_merge( - $allowedKeywraps, - AbstractCryptoClient::$supportedKeyWraps - )); - } - - $v1SchemaException = new CryptoException("The requested object is encrypted" - . " with V1 encryption schemas that have been disabled by" - . " client configuration @SecurityProfile=V2. Retry with" - . " V2_AND_LEGACY enabled or reencrypt the object."); - - if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) { - if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) { - throw $v1SchemaException; - } - throw new CryptoException("The requested object is encrypted with" - . " the cipher '{$options['@CipherOptions']['Cipher']}', which is not" - . " supported for decryption with the selected security profile." - . " This profile allows decryption with: " - . implode(", ", $allowedCiphers)); - } - if ( - !in_array( - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], - $allowedKeywraps - ) - ) { - if ( - in_array( - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], - AbstractCryptoClient::$supportedKeyWraps - ) - ) { - throw $v1SchemaException; - } - throw new CryptoException("The requested object is encrypted with" - . " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," - . " which is not supported for decryption with the current security" - . " profile."); - } - - $matdesc = json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ); - if ( - isset($matdesc['aws:x-amz-cek-alg']) - && $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== - $matdesc['aws:x-amz-cek-alg'] - ) { - throw new CryptoException("There is a mismatch in specified content" - . " encryption algrithm between the materials description value" - . " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}" - . " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}."); - } - } - - /** - * Generates a stream that wraps the cipher text with the proper cipher and - * uses the content encryption key (CEK) to decrypt the data when read. - * - * @param string $cipherText Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return AesStreamInterface - * - * @internal - */ - protected function getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ) { - $cipherTextStream = Psr7\Utils::streamFor($cipherText); - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ); - - return new AesGcmDecryptingStream( - $this->getStrippedCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ), - $cek, - $cipherOptions['Iv'], - $cipherOptions['Tag'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'] ?: null, - $cipherOptions['KeySize'] - ); - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - return new AesDecryptingStream( - $cipherTextStream, - $cek, - $cipherMethod - ); - } - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php deleted file mode 100644 index 38c346a7..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTrait.php +++ /dev/null @@ -1,194 +0,0 @@ - true, - 'KeySize' => true, - 'Aad' => true, - ]; - - /** - * Dependency to generate a CipherMethod from a set of inputs for loading - * in to an AesEncryptingStream. - * - * @param string $cipherName Name of the cipher to generate for encrypting. - * @param string $iv Base Initialization Vector for the cipher. - * @param int $keySize Size of the encryption key, in bits, that will be - * used. - * - * @return Cipher\CipherMethod - * - * @internal - */ - abstract protected function buildCipherMethod($cipherName, $iv, $keySize); - - /** - * Builds an AesStreamInterface and populates encryption metadata into the - * supplied envelope. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * @param MaterialsProvider $provider A provider to supply and encrypt - * materials used in encryption. - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be added to. - * - * @return AesStreamInterface - * - * @throws \InvalidArgumentException Thrown when a value in $cipherOptions - * is not valid. - * - * @internal - */ - public function encrypt( - Stream $plaintext, - array $cipherOptions, - MaterialsProvider $provider, - MetadataEnvelope $envelope - ) { - $materialsDescription = $provider->getMaterialsDescription(); - - $cipherOptions = array_intersect_key( - $cipherOptions, - self::$allowedOptions - ); - - if (empty($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('An encryption cipher must be' - . ' specified in the "cipher_options".'); - } - - if (!self::isSupportedCipher($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('The cipher requested is not' - . ' supported by the SDK.'); - } - - if (empty($cipherOptions['KeySize'])) { - $cipherOptions['KeySize'] = 256; - } - if (!is_int($cipherOptions['KeySize'])) { - throw new \InvalidArgumentException('The cipher "KeySize" must be' - . ' an integer.'); - } - - if ( - !MaterialsProvider::isSupportedKeySize( - $cipherOptions['KeySize'] - ) - ) { - throw new \InvalidArgumentException('The cipher "KeySize" requested' - . ' is not supported by AES (128, 192, or 256).'); - } - - $cipherOptions['Iv'] = $provider->generateIv( - $this->getCipherOpenSslName( - $cipherOptions['Cipher'], - $cipherOptions['KeySize'] - ) - ); - - $cek = $provider->generateCek($cipherOptions['KeySize']); - - list($encryptingStream, $aesName) = $this->getEncryptingStream( - $plaintext, - $cek, - $cipherOptions - ); - - // Populate envelope data - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = - $provider->encryptCek( - $cek, - $materialsDescription - ); - unset($cek); - - $envelope[MetadataEnvelope::IV_HEADER] = - base64_encode($cipherOptions['Iv']); - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = - $provider->getWrapAlgorithmName(); - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; - $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = - strlen($plaintext); - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = - json_encode($materialsDescription); - if (!empty($cipherOptions['Tag'])) { - $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = - strlen($cipherOptions['Tag']) * 8; - } - - return $encryptingStream; - } - - /** - * Generates a stream that wraps the plaintext with the proper cipher and - * uses the content encryption key (CEK) to encrypt the data when read. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return array returns an array with two elements as follows: [string, AesStreamInterface] - * - * @internal - */ - protected function getEncryptingStream( - Stream $plaintext, - $cek, - &$cipherOptions - ) { - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['TagLength'] = 16; - - $cipherTextStream = new AesGcmEncryptingStream( - $plaintext, - $cek, - $cipherOptions['Iv'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'], - $cipherOptions['KeySize'] - ); - - if (!empty($cipherOptions['Aad'])) { - trigger_error("'Aad' has been supplied for content encryption" - . " with " . $cipherTextStream->getAesName() . ". The" - . " PHP SDK encryption client can decrypt an object" - . " encrypted in this way, but other AWS SDKs may not be" - . " able to.", E_USER_WARNING); - } - - $appendStream = new AppendStream([ - $cipherTextStream->createStream() - ]); - $cipherOptions['Tag'] = $cipherTextStream->getTag(); - $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); - return [$appendStream, $cipherTextStream->getAesName()]; - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - $cipherTextStream = new AesEncryptingStream( - $plaintext, - $cek, - $cipherMethod - ); - return [$cipherTextStream, $cipherTextStream->getAesName()]; - } - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php deleted file mode 100644 index 77c9e802..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/EncryptionTraitV2.php +++ /dev/null @@ -1,198 +0,0 @@ - true, - 'KeySize' => true, - 'Aad' => true, - ]; - - private static $encryptClasses = [ - 'gcm' => AesGcmEncryptingStream::class - ]; - - /** - * Dependency to generate a CipherMethod from a set of inputs for loading - * in to an AesEncryptingStream. - * - * @param string $cipherName Name of the cipher to generate for encrypting. - * @param string $iv Base Initialization Vector for the cipher. - * @param int $keySize Size of the encryption key, in bits, that will be - * used. - * - * @return Cipher\CipherMethod - * - * @internal - */ - abstract protected function buildCipherMethod($cipherName, $iv, $keySize); - - /** - * Builds an AesStreamInterface and populates encryption metadata into the - * supplied envelope. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param array $options Options for use in encryption, including cipher - * options, and encryption context. - * @param MaterialsProviderV2 $provider A provider to supply and encrypt - * materials used in encryption. - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be added to. - * - * @return StreamInterface - * - * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] - * is not valid. - *s - * @internal - */ - public function encrypt( - Stream $plaintext, - array $options, - MaterialsProviderV2 $provider, - MetadataEnvelope $envelope - ) { - $options = array_change_key_case($options); - $cipherOptions = array_intersect_key( - $options['@cipheroptions'], - self::$allowedOptions - ); - - if (empty($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('An encryption cipher must be' - . ' specified in @CipherOptions["Cipher"].'); - } - - $cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']); - - if (!self::isSupportedCipher($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('The cipher requested is not' - . ' supported by the SDK.'); - } - - if (empty($cipherOptions['KeySize'])) { - $cipherOptions['KeySize'] = 256; - } - if (!is_int($cipherOptions['KeySize'])) { - throw new \InvalidArgumentException('The cipher "KeySize" must be' - . ' an integer.'); - } - - if ( - !MaterialsProviderV2::isSupportedKeySize( - $cipherOptions['KeySize'] - ) - ) { - throw new \InvalidArgumentException('The cipher "KeySize" requested' - . ' is not supported by AES (128 or 256).'); - } - - $cipherOptions['Iv'] = $provider->generateIv( - $this->getCipherOpenSslName( - $cipherOptions['Cipher'], - $cipherOptions['KeySize'] - ) - ); - - $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; - $aesName = $encryptClass::getStaticAesName(); - $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; - - $keys = $provider->generateCek( - $cipherOptions['KeySize'], - $materialsDescription, - $options - ); - - // Some providers modify materials description based on options - if (isset($keys['UpdatedContext'])) { - $materialsDescription = $keys['UpdatedContext']; - } - - $encryptingStream = $this->getEncryptingStream( - $plaintext, - $keys['Plaintext'], - $cipherOptions - ); - - // Populate envelope data - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; - unset($keys); - - $envelope[MetadataEnvelope::IV_HEADER] = - base64_encode($cipherOptions['Iv']); - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = - $provider->getWrapAlgorithmName(); - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; - $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = - strlen($plaintext); - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = - json_encode($materialsDescription); - if (!empty($cipherOptions['Tag'])) { - $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = - strlen($cipherOptions['Tag']) * 8; - } - - return $encryptingStream; - } - - /** - * Generates a stream that wraps the plaintext with the proper cipher and - * uses the content encryption key (CEK) to encrypt the data when read. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return array returns an array with two elements as follows: [string, AesStreamInterface] - * - * @internal - */ - protected function getEncryptingStream( - Stream $plaintext, - $cek, - &$cipherOptions - ) { - switch ($cipherOptions['Cipher']) { - // Only 'gcm' is supported for encryption currently - case 'gcm': - $cipherOptions['TagLength'] = 16; - $encryptClass = self::$encryptClasses['gcm']; - $cipherTextStream = new $encryptClass( - $plaintext, - $cek, - $cipherOptions['Iv'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'], - $cipherOptions['KeySize'] - ); - - if (!empty($cipherOptions['Aad'])) { - trigger_error("'Aad' has been supplied for content encryption" - . " with " . $cipherTextStream->getAesName() . ". The" - . " PHP SDK encryption client can decrypt an object" - . " encrypted in this way, but other AWS SDKs may not be" - . " able to.", E_USER_WARNING); - } - - $appendStream = new AppendStream([ - $cipherTextStream->createStream() - ]); - $cipherOptions['Tag'] = $cipherTextStream->getTag(); - $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); - return $appendStream; - } - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php deleted file mode 100644 index bca019bb..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProvider.php +++ /dev/null @@ -1,123 +0,0 @@ -kmsClient = $kmsClient; - $this->kmsKeyId = $kmsKeyId; - } - - public function fromDecryptionEnvelope(MetadataEnvelope $envelope) - { - if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) { - throw new \RuntimeException('Not able to detect the materials description.'); - } - - $materialsDescription = json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ); - - if ( - empty($materialsDescription['kms_cmk_id']) - && empty($materialsDescription['aws:x-amz-cek-alg']) - ) { - throw new \RuntimeException('Not able to detect kms_cmk_id (legacy' - . ' implementation) or aws:x-amz-cek-alg (current implementation)' - . ' from kms materials description.'); - } - - return new self( - $this->kmsClient, - isset($materialsDescription['kms_cmk_id']) - ? $materialsDescription['kms_cmk_id'] - : null - ); - } - - /** - * The KMS key id for use in matching this Provider to its keys, - * consistently with other SDKs as 'kms_cmk_id'. - * - * @return array - */ - public function getMaterialsDescription() - { - return ['kms_cmk_id' => $this->kmsKeyId]; - } - - public function getWrapAlgorithmName() - { - return self::WRAP_ALGORITHM_NAME; - } - - /** - * Takes a content encryption key (CEK) and description to return an encrypted - * key by using KMS' Encrypt API. - * - * @param string $unencryptedCek Key for use in encrypting other data - * that itself needs to be encrypted by the - * Provider. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - public function encryptCek($unencryptedCek, $materialDescription) - { - $encryptedDataKey = $this->kmsClient->encrypt([ - 'Plaintext' => $unencryptedCek, - 'KeyId' => $this->kmsKeyId, - 'EncryptionContext' => $materialDescription - ]); - return base64_encode($encryptedDataKey['CiphertextBlob']); - } - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key by using KMS' Decrypt API. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - public function decryptCek($encryptedCek, $materialDescription) - { - $result = $this->kmsClient->decrypt([ - 'CiphertextBlob' => $encryptedCek, - 'EncryptionContext' => $materialDescription - ]); - - return $result['Plaintext']; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php b/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php deleted file mode 100644 index d0d923e1..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php +++ /dev/null @@ -1,101 +0,0 @@ -kmsClient = $kmsClient; - $this->kmsKeyId = $kmsKeyId; - } - - /** - * @inheritDoc - */ - public function getWrapAlgorithmName() - { - return self::WRAP_ALGORITHM_NAME; - } - - /** - * @inheritDoc - */ - public function decryptCek($encryptedCek, $materialDescription, $options) - { - $params = [ - 'CiphertextBlob' => $encryptedCek, - 'EncryptionContext' => $materialDescription - ]; - if (empty($options['@KmsAllowDecryptWithAnyCmk'])) { - if (empty($this->kmsKeyId)) { - throw new CryptoException('KMS CMK ID was not specified and the' - . ' operation is not opted-in to attempting to use any valid' - . ' CMK it discovers. Please specify a CMK ID, or explicitly' - . ' enable attempts to use any valid KMS CMK with the' - . ' @KmsAllowDecryptWithAnyCmk option.'); - } - $params['KeyId'] = $this->kmsKeyId; - } - - $result = $this->kmsClient->decrypt($params); - return $result['Plaintext']; - } - - /** - * @inheritDoc - */ - public function generateCek($keySize, $context, $options) - { - if (empty($this->kmsKeyId)) { - throw new CryptoException('A KMS key id is required for encryption' - . ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been' - . ' instantiated with a KMS key id.'); - } - $options = array_change_key_case($options); - if ( - !isset($options['@kmsencryptioncontext']) - || !is_array($options['@kmsencryptioncontext']) - ) { - throw new CryptoException("'@KmsEncryptionContext' is a" - . " required argument when using KmsMaterialsProviderV2, and" - . " must be an associative array (or empty array)."); - } - if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) { - throw new CryptoException("Conflict in reserved @KmsEncryptionContext" - . " key aws:x-amz-cek-alg. This value is reserved for the S3" - . " Encryption Client and cannot be set by the user."); - } - $context = array_merge($options['@kmsencryptioncontext'], $context); - $result = $this->kmsClient->generateDataKey([ - 'KeyId' => $this->kmsKeyId, - 'KeySpec' => "AES_{$keySize}", - 'EncryptionContext' => $context - ]); - return [ - 'Plaintext' => $result['Plaintext'], - 'Ciphertext' => base64_encode($result['CiphertextBlob']), - 'UpdatedContext' => $context - ]; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php deleted file mode 100644 index afbcaf13..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProvider.php +++ /dev/null @@ -1,105 +0,0 @@ - true, - 192 => true, - 256 => true, - ]; - - /** - * Returns if the requested size is supported by AES. - * - * @param int $keySize Size of the requested key in bits. - * - * @return bool - */ - public static function isSupportedKeySize($keySize) - { - return isset(self::$supportedKeySizes[$keySize]); - } - - /** - * Performs further initialization of the MaterialsProvider based on the - * data inside the MetadataEnvelope. - * - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be read from. - * - * @return MaterialsProvider - * - * @throws \RuntimeException Thrown when there is an empty or improperly - * formed materials description in the envelope. - * - * @internal - */ - abstract public function fromDecryptionEnvelope(MetadataEnvelope $envelope); - - /** - * Returns the material description for this Provider so it can be verified - * by encryption mechanisms. - * - * @return string - */ - abstract public function getMaterialsDescription(); - - /** - * Returns the wrap algorithm name for this Provider. - * - * @return string - */ - abstract public function getWrapAlgorithmName(); - - /** - * Takes a content encryption key (CEK) and description to return an - * encrypted key according to the Provider's specifications. - * - * @param string $unencryptedCek Key for use in encrypting other data - * that itself needs to be encrypted by the - * Provider. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - abstract public function encryptCek($unencryptedCek, $materialDescription); - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key according to the Provider's specifications. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - abstract public function decryptCek($encryptedCek, $materialDescription); - - /** - * @param string $keySize Length of a cipher key in bits for generating a - * random content encryption key (CEK). - * - * @return string - */ - public function generateCek($keySize) - { - return openssl_random_pseudo_bytes($keySize / 8); - } - - /** - * @param string $openSslName Cipher OpenSSL name to use for generating - * an initialization vector. - * - * @return string - */ - public function generateIv($openSslName) - { - return openssl_random_pseudo_bytes( - openssl_cipher_iv_length($openSslName) - ); - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php deleted file mode 100644 index 71093d54..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/MaterialsProviderInterface.php +++ /dev/null @@ -1,61 +0,0 @@ - true, - 256 => true, - ]; - - /** - * Returns if the requested size is supported by AES. - * - * @param int $keySize Size of the requested key in bits. - * - * @return bool - */ - public static function isSupportedKeySize($keySize) - { - return isset(self::$supportedKeySizes[$keySize]); - } - - /** - * Returns the wrap algorithm name for this Provider. - * - * @return string - */ - abstract public function getWrapAlgorithmName(); - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key according to the Provider's specifications. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * decrypting the CEK. - * @param string $options Options for use in decrypting the CEK. - * - * @return string - */ - abstract public function decryptCek($encryptedCek, $materialDescription, $options); - - /** - * @param string $keySize Length of a cipher key in bits for generating a - * random content encryption key (CEK). - * @param array $context Context map needed for key encryption - * @param array $options Additional options to be used in CEK generation - * - * @return array - */ - abstract public function generateCek($keySize, $context, $options); - - /** - * @param string $openSslName Cipher OpenSSL name to use for generating - * an initialization vector. - * - * @return string - */ - public function generateIv($openSslName) - { - return openssl_random_pseudo_bytes( - openssl_cipher_iv_length($openSslName) - ); - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php deleted file mode 100644 index 3206c0d8..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataEnvelope.php +++ /dev/null @@ -1,61 +0,0 @@ -getConstants()) as $constant) { - self::$constants[$constant] = true; - } - } - - return array_keys(self::$constants); - } - - /** - * @return void - */ - #[\ReturnTypeWillChange] - public function offsetSet($name, $value) - { - $constants = self::getConstantValues(); - if (is_null($name) || !in_array($name, $constants)) { - throw new InvalidArgumentException('MetadataEnvelope fields must' - . ' must match a predefined offset; use the header constants.'); - } - - $this->data[$name] = $value; - } - - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - return $this->data; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php b/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php deleted file mode 100644 index 370ef585..00000000 --- a/test-server/php-v2-server/local-php-sdk/Crypto/MetadataStrategyInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -instructionFileSuffix; - } - - protected function determineGetObjectStrategy( - $result, - $instructionFileSuffix - ) { - if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) { - return new HeadersMetadataStrategy(); - } - - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - } - - protected function getMetadataStrategy(array $args, $instructionFileSuffix) - { - if (!empty($args['@MetadataStrategy'])) { - if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) { - return $args['@MetadataStrategy']; - } - - if (is_string($args['@MetadataStrategy'])) { - switch ($args['@MetadataStrategy']) { - case HeadersMetadataStrategy::class: - return new HeadersMetadataStrategy(); - case InstructionFileMetadataStrategy::class: - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - default: - throw new \InvalidArgumentException('Could not match the' - . ' specified string in "MetadataStrategy" to a' - . ' predefined strategy.'); - } - } else { - throw new \InvalidArgumentException('The metadata strategy that' - . ' was passed to "MetadataStrategy" was unrecognized.'); - } - } elseif ($instructionFileSuffix) { - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - } - - return null; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php deleted file mode 100644 index 592f68bc..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php +++ /dev/null @@ -1,19 +0,0 @@ - $value) { - $args['Metadata'][$header] = $value; - } - - return $args; - } - - /** - * Generates a MetadataEnvelope according to the metadata headers from the - * GetObject result. - * - * @param array $args Arguments from Command and Result that contains - * S3 Object information, relevant headers, and command - * configuration. - * - * @return MetadataEnvelope - */ - public function load(array $args) - { - $envelope = new MetadataEnvelope(); - $constantValues = MetadataEnvelope::getConstantValues(); - - foreach ($constantValues as $constant) { - if (!empty($args['Metadata'][$constant])) { - $envelope[$constant] = $args['Metadata'][$constant]; - } - } - - return $envelope; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php b/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php deleted file mode 100644 index 25384bb3..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php +++ /dev/null @@ -1,90 +0,0 @@ -suffix = empty($suffix) - ? self::DEFAULT_FILE_SUFFIX - : $suffix; - $this->client = $client; - } - - /** - * Places the information in the MetadataEnvelope to a location on S3. - * - * @param MetadataEnvelope $envelope Encryption data to save according to - * the strategy. - * @param array $args Starting arguments for PutObject, used for saving - * extra the instruction file. - * - * @return array Updated arguments for PutObject. - */ - public function save(MetadataEnvelope $envelope, array $args) - { - $this->client->putObject([ - 'Bucket' => $args['Bucket'], - 'Key' => $args['Key'] . $this->suffix, - 'Body' => json_encode($envelope) - ]); - - return $args; - } - - /** - * Uses the strategy's client to retrieve the instruction file from S3 and generates - * a MetadataEnvelope from its contents. - * - * @param array $args Arguments from Command and Result that contains - * S3 Object information, relevant headers, and command - * configuration. - * - * @return MetadataEnvelope - */ - public function load(array $args) - { - $result = $this->client->getObject([ - 'Bucket' => $args['Bucket'], - 'Key' => $args['Key'] . $this->suffix - ]); - - $metadataHeaders = json_decode($result['Body'], true); - $envelope = new MetadataEnvelope(); - $constantValues = MetadataEnvelope::getConstantValues(); - - foreach ($constantValues as $constant) { - if (!empty($metadataHeaders[$constant])) { - $envelope[$constant] = $metadataHeaders[$constant]; - } - } - - return $envelope; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php deleted file mode 100644 index 1ddd4862..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClient.php +++ /dev/null @@ -1,339 +0,0 @@ -client = $client; - $this->instructionFileSuffix = $instructionFileSuffix; - MetricsBuilder::appendMetricsCaptureMiddleware( - $this->client->getHandlerList(), - MetricsBuilder::S3_CRYPTO_V1N - ); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - /** - * Encrypts the data in the 'Body' field of $args and promises to upload it - * to the specified location on S3. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. Only the - * Cipher option is required. Accepts the following: - * - Cipher: (string) cbc|gcm - * See also: AbstractCryptoClient::$supportedCiphers. Note that - * cbc is deprecated and gcm should be used when possible. - * - KeySize: (int) 128|192|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. Note if you pass in Aad for gcm encryption, the - * PHP SDK will be able to decrypt the resulting object, but other - * AWS SDKs may not be able to do so. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if using an InstructionFileMetadataHandler. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $envelope = new MetadataEnvelope(); - - return Promise\Create::promiseFor($this->encrypt( - Psr7\Utils::streamFor($args['Body']), - $args['@CipherOptions'] ?: [], - $provider, - $envelope - ))->then( - function ($encryptedBodyStream) use ($args) { - $hash = new PhpHash('sha256'); - $hashingEncryptedBodyStream = new HashingStream( - $encryptedBodyStream, - $hash, - self::getContentShaDecorator($args) - ); - return [$hashingEncryptedBodyStream, $args]; - } - )->then( - function ($putObjectContents) use ($strategy, $envelope) { - list($bodyStream, $args) = $putObjectContents; - if ($strategy === null) { - $strategy = self::getDefaultStrategy(); - } - - $updatedArgs = $strategy->save($envelope, $args); - $updatedArgs['Body'] = $bodyStream; - return $updatedArgs; - } - )->then( - function ($args) { - unset($args['@CipherOptions']); - return $this->client->putObjectAsync($args); - } - ); - } - - private static function getContentShaDecorator(&$args) - { - return function ($hash) use (&$args) { - $args['ContentSHA256'] = bin2hex($hash); - }; - } - - /** - * Encrypts the data in the 'Body' field of $args and uploads it to the - * specified location on S3. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Cipher: (string) cbc|gcm - * See also: AbstractCryptoClient::$supportedCiphers. Note that - * cbc is deprecated and gcm should be used when possible. - * - KeySize: (int) 128|192|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. Note if you pass in Aad for gcm encryption, the - * PHP SDK will be able to decrypt the resulting object, but other - * AWS SDKs may not be able to do so. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if an using an InstructionFileMetadataHandler was - * determined. - * - * @return \Aws\Result PutObject call result with the details of uploading - * the encrypted file. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObject(array $args) - { - return $this->putObjectAsync($args)->wait(); - } - - /** - * Promises to retrieve an object from S3 and decrypt the data in the - * 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading - * MetadataEnvelope information. Defaults to determining based on object - * response headers. Can either be a class implementing MetadataStrategy, - * a class name of a predefined strategy, or empty/null to default. - * - @InstructionFileSuffix: (string) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler is being used. - * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when required arguments are not - * passed or are passed incorrectly. - */ - public function getObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $saveAs = null; - if (!empty($args['SaveAs'])) { - $saveAs = $args['SaveAs']; - } - - $promise = $this->client->getObjectAsync($args) - ->then( - function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { - if ($strategy === null) { - $strategy = $this->determineGetObjectStrategy( - $result, - $instructionFileSuffix - ); - } - - $envelope = $strategy->load($args + [ - 'Metadata' => $result['Metadata'] - ]); - - $provider = $provider->fromDecryptionEnvelope($envelope); - - $result['Body'] = $this->decrypt( - $result['Body'], - $provider, - $envelope, - isset($args['@CipherOptions']) - ? $args['@CipherOptions'] - : [] - ); - return $result; - } - )->then( - function ($result) use ($saveAs) { - if (!empty($saveAs)) { - file_put_contents( - $saveAs, - (string) $result['Body'], - LOCK_EX - ); - } - return $result; - } - ); - - return $promise; - } - - /** - * Retrieves an object from S3 and decrypts the data in the 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - @InstructionFileSuffix: (string|null) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler was detected. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - * @return \Aws\Result GetObject call result with the 'Body' field - * wrapped in a decryption stream with its metadata - * information. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function getObject(array $args) - { - return $this->getObjectAsync($args)->wait(); - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php deleted file mode 100644 index 39a98dcf..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionClientV2.php +++ /dev/null @@ -1,447 +0,0 @@ - - * use Local\Aws\Crypto\KmsMaterialsProviderV2; - * use Aws\S3\Crypto\S3EncryptionClientV2; - * use Aws\S3\S3Client; - * - * $encryptionClient = new S3EncryptionClientV2( - * new S3Client([ - * 'region' => 'us-west-2', - * 'version' => 'latest' - * ]) - * ); - * $materialsProvider = new KmsMaterialsProviderV2( - * new KmsClient([ - * 'profile' => 'default', - * 'region' => 'us-east-1', - * 'version' => 'latest', - * ], - * 'your-kms-key-id' - * ); - * - * $encryptionClient->putObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * '@KmsEncryptionContext' => ['foo' => 'bar'], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * 'Body' => 'your-encrypted-data', - * ]); - * - * - * Example read call (using objects from previous example): - * - * - * $encryptionClient->getObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * ]); - * - */ -class S3EncryptionClientV2 extends AbstractCryptoClientV2 -{ - use CipherBuilderTrait; - use CryptoParamsTraitV2; - use DecryptionTraitV2; - use EncryptionTraitV2; - use UserAgentTrait; - - const CRYPTO_VERSION = '2.1'; - - private $client; - private $instructionFileSuffix; - private $legacyWarningCount; - - /** - * @param S3Client $client The S3Client to be used for true uploading and - * retrieving objects from S3 when using the - * encryption client. - * @param string|null $instructionFileSuffix Suffix for a client wide - * default when using instruction - * files for metadata storage. - */ - public function __construct( - S3Client $client, - $instructionFileSuffix = null - ) { - $this->client = $client; - $this->instructionFileSuffix = $instructionFileSuffix; - $this->legacyWarningCount = 0; - MetricsBuilder::appendMetricsCaptureMiddleware( - $this->client->getHandlerList(), - MetricsBuilder::S3_CRYPTO_V2 - ); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - /** - * Encrypts the data in the 'Body' field of $args and promises to upload it - * to the specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. Only the - * Cipher option is required. Accepts the following: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if using an InstructionFileMetadataHandler. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $envelope = new MetadataEnvelope(); - - return Promise\Create::promiseFor($this->encrypt( - Psr7\Utils::streamFor($args['Body']), - $args, - $provider, - $envelope - ))->then( - function ($encryptedBodyStream) use ($args) { - $hash = new PhpHash('sha256'); - $hashingEncryptedBodyStream = new HashingStream( - $encryptedBodyStream, - $hash, - self::getContentShaDecorator($args) - ); - return [$hashingEncryptedBodyStream, $args]; - } - )->then( - function ($putObjectContents) use ($strategy, $envelope) { - list($bodyStream, $args) = $putObjectContents; - if ($strategy === null) { - $strategy = self::getDefaultStrategy(); - } - - $updatedArgs = $strategy->save($envelope, $args); - $updatedArgs['Body'] = $bodyStream; - return $updatedArgs; - } - )->then( - function ($args) { - unset($args['@CipherOptions']); - return $this->client->putObjectAsync($args); - } - ); - } - - private static function getContentShaDecorator(&$args) - { - return function ($hash) use (&$args) { - $args['ContentSHA256'] = bin2hex($hash); - }; - } - - /** - * Encrypts the data in the 'Body' field of $args and uploads it to the - * specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if an using an InstructionFileMetadataHandler was - * determined. - * - * @return \Aws\Result PutObject call result with the details of uploading - * the encrypted file. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObject(array $args) - { - return $this->putObjectAsync($args)->wait(); - } - - /** - * Promises to retrieve an object from S3 and decrypt the data in the - * 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading - * MetadataEnvelope information. Defaults to determining based on object - * response headers. Can either be a class implementing MetadataStrategy, - * a class name of a predefined strategy, or empty/null to default. - * - @InstructionFileSuffix: (string) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler is being used. - * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when required arguments are not - * passed or are passed incorrectly. - */ - public function getObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - if ( - !isset($args['@SecurityProfile']) - || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) - ) { - throw new CryptoException("@SecurityProfile is required and must be" - . " set to 'V2' or 'V2_AND_LEGACY'"); - } - - // Only throw this legacy warning once per client - if ( - in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) - && $this->legacyWarningCount < 1 - ) { - $this->legacyWarningCount++; - trigger_error( - "This S3 Encryption Client operation is configured to" - . " read encrypted data with legacy encryption modes. If you" - . " don't have objects encrypted with these legacy modes," - . " you should disable support for them to enhance security. ", - E_USER_WARNING - ); - } - - $saveAs = null; - if (!empty($args['SaveAs'])) { - $saveAs = $args['SaveAs']; - } - - $promise = $this->client->getObjectAsync($args) - ->then( - function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { - if ($strategy === null) { - $strategy = $this->determineGetObjectStrategy( - $result, - $instructionFileSuffix - ); - } - - $envelope = $strategy->load($args + [ - 'Metadata' => $result['Metadata'] - ]); - - $result['Body'] = $this->decrypt( - $result['Body'], - $provider, - $envelope, - $args - ); - return $result; - } - )->then( - function ($result) use ($saveAs) { - if (!empty($saveAs)) { - file_put_contents( - $saveAs, - (string) $result['Body'], - LOCK_EX - ); - } - return $result; - } - ); - - return $promise; - } - - /** - * Retrieves an object from S3 and decrypts the data in the 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - @InstructionFileSuffix: (string|null) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler was detected. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return \Aws\Result GetObject call result with the 'Body' field - * wrapped in a decryption stream with its metadata - * information. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function getObject(array $args) - { - return $this->getObjectAsync($args)->wait(); - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php deleted file mode 100644 index 4fdb7a77..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php +++ /dev/null @@ -1,169 +0,0 @@ -appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); - $this->client = $client; - $config['params'] = []; - if (!empty($config['bucket'])) { - $config['params']['Bucket'] = $config['bucket']; - } - if (!empty($config['key'])) { - $config['params']['Key'] = $config['key']; - } - - $this->provider = $this->getMaterialsProvider($config); - unset($config['@MaterialsProvider']); - - $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); - unset($config['@InstructionFileSuffix']); - $this->strategy = $this->getMetadataStrategy( - $config, - $this->instructionFileSuffix - ); - if ($this->strategy === null) { - $this->strategy = self::getDefaultStrategy(); - } - unset($config['@MetadataStrategy']); - - $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); - - parent::__construct($client, $source, $config); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - private function getEncryptingDataPreparer() - { - return function () { - // Defer encryption work until promise is executed - $envelope = new MetadataEnvelope(); - - list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( - $this->source, - $this->config['@cipheroptions'] ?: [], - $this->provider, - $envelope - ))->then( - function ($bodyStream) use ($envelope) { - $params = $this->strategy->save( - $envelope, - $this->config['params'] - ); - return [$bodyStream, $params]; - } - )->wait(); - - $this->source->rewind(); - $this->config['params'] = $params; - }; - } -} \ No newline at end of file diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php b/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php deleted file mode 100644 index 813305ef..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php +++ /dev/null @@ -1,176 +0,0 @@ -appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); - $this->client = $client; - $config['params'] = []; - if (!empty($config['bucket'])) { - $config['params']['Bucket'] = $config['bucket']; - } - if (!empty($config['key'])) { - $config['params']['Key'] = $config['key']; - } - - $this->provider = $this->getMaterialsProvider($config); - unset($config['@MaterialsProvider']); - - $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); - unset($config['@InstructionFileSuffix']); - $this->strategy = $this->getMetadataStrategy( - $config, - $this->instructionFileSuffix - ); - if ($this->strategy === null) { - $this->strategy = self::getDefaultStrategy(); - } - unset($config['@MetadataStrategy']); - - $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); - - parent::__construct($client, $source, $config); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - private function getEncryptingDataPreparer() - { - return function () { - // Defer encryption work until promise is executed - $envelope = new MetadataEnvelope(); - - list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( - $this->source, - $this->config ?: [], - $this->provider, - $envelope - ))->then( - function ($bodyStream) use ($envelope) { - $params = $this->strategy->save( - $envelope, - $this->config['params'] - ); - return [$bodyStream, $params]; - } - )->wait(); - - $this->source->rewind(); - $this->config['params'] = $params; - }; - } -} diff --git a/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php b/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php deleted file mode 100644 index 7f95ca06..00000000 --- a/test-server/php-v2-server/local-php-sdk/S3EC/UserAgentTrait.php +++ /dev/null @@ -1,32 +0,0 @@ -getHandlerList(); - $list->appendBuild(Middleware::mapRequest( - function (RequestInterface $req) use ($agentString) { - if ( - !empty($req->getHeader('User-Agent')) - && !empty($req->getHeader('User-Agent')[0]) - ) { - $userAgent = $req->getHeader('User-Agent')[0]; - if (strpos($userAgent, $agentString) === false) { - $userAgent .= " {$agentString}"; - }; - } else { - $userAgent = $agentString; - } - - $req = $req->withHeader('User-Agent', $userAgent); - return $req; - } - )); - } -} diff --git a/test-server/php-v3-server/local-php-sdk b/test-server/php-v3-server/local-php-sdk new file mode 160000 index 00000000..d78bd3b2 --- /dev/null +++ b/test-server/php-v3-server/local-php-sdk @@ -0,0 +1 @@ +Subproject commit d78bd3b221890aac679ec3b6cb5abcb01fd42699 diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php b/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php deleted file mode 100644 index 02b0211e..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/AbstractCryptoClient.php +++ /dev/null @@ -1,121 +0,0 @@ -stream = $cipherText; - $this->key = $key; - $this->cipherMethod = clone $cipherMethod; - } - - public function getOpenSslName() - { - return $this->cipherMethod->getOpenSslName(); - } - - public function getAesName() - { - return $this->cipherMethod->getAesName(); - } - - public function getCurrentIv() - { - return $this->cipherMethod->getCurrentIv(); - } - - public function getSize(): ?int - { - $plainTextSize = $this->stream->getSize(); - - if ($this->cipherMethod->requiresPadding()) { - // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be - // added to the plaintext to make it an even number of blocks. The - // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and - // strlen($cipherText) - 1 - return null; - } - - return $plainTextSize; - } - - public function isWritable(): bool - { - return false; - } - - public function read($length): string - { - if ($length > strlen($this->buffer)) { - $this->buffer .= $this->decryptBlock( - (int) ( - self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) - ) - ); - } - - $data = substr($this->buffer, 0, $length); - $this->buffer = substr($this->buffer, $length); - - return $data ? $data : ''; - } - - public function seek($offset, $whence = SEEK_SET): void - { - if ($offset === 0 && $whence === SEEK_SET) { - $this->buffer = ''; - $this->cipherMethod->seek(0, SEEK_SET); - $this->stream->seek(0, SEEK_SET); - } else { - throw new LogicException('AES encryption streams only support being' - . ' rewound, not arbitrary seeking.'); - } - } - - private function decryptBlock($length) - { - if ($this->stream->eof()) { - return ''; - } - - $cipherText = ''; - do { - $cipherText .= $this->stream->read((int) ($length - strlen($cipherText))); - } while (strlen($cipherText) < $length && !$this->stream->eof()); - - $options = OPENSSL_RAW_DATA; - if ( - !$this->stream->eof() - && $this->stream->getSize() !== $this->stream->tell() - ) { - $options |= OPENSSL_ZERO_PADDING; - } - - $plaintext = openssl_decrypt( - $cipherText, - $this->cipherMethod->getOpenSslName(), - $this->key, - $options, - $this->cipherMethod->getCurrentIv() - ); - - $this->cipherMethod->update($cipherText); - - return $plaintext; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php deleted file mode 100644 index b024c563..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/AesEncryptingStream.php +++ /dev/null @@ -1,152 +0,0 @@ -stream = $plainText; - $this->key = $key; - $this->cipherMethod = clone $cipherMethod; - } - - public function getOpenSslName() - { - return $this->cipherMethod->getOpenSslName(); - } - - public function getAesName() - { - return $this->cipherMethod->getAesName(); - } - - public function getCurrentIv() - { - return $this->cipherMethod->getCurrentIv(); - } - - public function getSize(): ?int - { - $plainTextSize = $this->stream->getSize(); - - if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { - // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be - // added to the plaintext to make it an even number of blocks. - $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; - return $plainTextSize + $padding; - } - - return $plainTextSize; - } - - public function isWritable(): bool - { - return false; - } - - public function read($length): string - { - if ($length > strlen($this->buffer)) { - $this->buffer .= $this->encryptBlock( - (int) - self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE) - ); - } - - $data = substr($this->buffer, 0, $length); - $this->buffer = substr($this->buffer, $length); - - return $data ? $data : ''; - } - - public function seek($offset, $whence = SEEK_SET): void - { - if ($whence === SEEK_CUR) { - $offset = $this->tell() + $offset; - $whence = SEEK_SET; - } - - if ($whence === SEEK_SET) { - $this->buffer = ''; - $wholeBlockOffset - = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; - $this->stream->seek($wholeBlockOffset); - $this->cipherMethod->seek($wholeBlockOffset); - $this->read($offset - $wholeBlockOffset); - } else { - throw new LogicException('Unrecognized whence.'); - } - } - - private function encryptBlock($length) - { - if ($this->stream->eof()) { - return ''; - } - - $plainText = ''; - do { - $plainText .= $this->stream->read((int) ($length - strlen($plainText))); - } while (strlen($plainText) < $length && !$this->stream->eof()); - - $options = OPENSSL_RAW_DATA; - if ( - !$this->stream->eof() - || $this->stream->getSize() !== $this->stream->tell() - ) { - $options |= OPENSSL_ZERO_PADDING; - } - - $cipherText = openssl_encrypt( - $plainText, - $this->cipherMethod->getOpenSslName(), - $this->key, - $options, - $this->cipherMethod->getCurrentIv() - ); - - $this->cipherMethod->update($cipherText); - - return $cipherText; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php deleted file mode 100644 index aa87198a..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmDecryptingStream.php +++ /dev/null @@ -1,104 +0,0 @@ -cipherText = $cipherText; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->tag = $tag; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-gcm"; - } - - public function getAesName() - { - return 'AES/GCM/NoPadding'; - } - - public function getCurrentIv() - { - return $this->initializationVector; - } - - public function createStream() - { - - $result = \openssl_decrypt( - (string) $this->cipherText, - $this->getOpenSslName(), - $this->key, - OPENSSL_RAW_DATA, - $this->initializationVector, - $this->tag, - $this->aad - ); - if ($result === false) { - throw new CryptoException('The requested object could not be ' - . 'decrypted due to an invalid authentication tag.'); - } - return Psr7\Utils::streamFor($result); - - } - - public function isWritable(): bool - { - return false; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php deleted file mode 100644 index 45a3e82b..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/AesGcmEncryptingStream.php +++ /dev/null @@ -1,119 +0,0 @@ -plaintext = $plaintext; - $this->key = $key; - $this->initializationVector = $initializationVector; - $this->aad = $aad; - $this->tagLength = $tagLength; - $this->keySize = $keySize; - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-gcm"; - } - - /** - * Same as static method and retained for backwards compatibility - * - * @return string - */ - public function getAesName() - { - return self::getStaticAesName(); - } - - public function getCurrentIv() - { - return $this->initializationVector; - } - - public function createStream() - { - return Psr7\Utils::streamFor(\openssl_encrypt( - (string) $this->plaintext, - $this->getOpenSslName(), - $this->key, - OPENSSL_RAW_DATA, - $this->initializationVector, - $this->tag, - $this->aad, - $this->tagLength - )); - } - - /** - * @return string - */ - public function getTag() - { - return $this->tag; - } - - public function isWritable(): bool - { - return false; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php deleted file mode 100644 index 5227790a..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/AesStreamInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -baseIv = $this->iv = $iv; - $this->keySize = $keySize; - - if (strlen($iv) !== openssl_cipher_iv_length($this->getOpenSslName())) { - throw new InvalidArgumentException('Invalid initialization vector'); - } - } - - public function getOpenSslName() - { - return "aes-{$this->keySize}-cbc"; - } - - public function getAesName() - { - return 'AES/CBC/PKCS5Padding'; - } - - public function getCurrentIv() - { - return $this->iv; - } - - public function requiresPadding() - { - return true; - } - - public function seek($offset, $whence = SEEK_SET) - { - if ($offset === 0 && $whence === SEEK_SET) { - $this->iv = $this->baseIv; - } else { - throw new LogicException('CBC initialization only support being' - . ' rewound, not arbitrary seeking.'); - } - } - - public function update($cipherTextBlock) - { - $this->iv = substr($cipherTextBlock, self::BLOCK_SIZE * -1); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php b/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php deleted file mode 100644 index bb8e6772..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/Cipher/CipherBuilderTrait.php +++ /dev/null @@ -1,72 +0,0 @@ -decryptCek( - base64_decode( - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] - ), - json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ) - ); - $cipherOptions['KeySize'] = strlen($cek) * 8; - $cipherOptions['Cipher'] = $this->getCipherFromAesName( - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] - ); - - $decryptionStream = $this->getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ); - unset($cek); - - return $decryptionStream; - } - - private function getTagFromCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return (string) new LimitStream( - $cipherText, - $tagLength, - $cipherTextSize - $tagLength - ); - } - - private function getStrippedCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return new LimitStream( - $cipherText, - $cipherTextSize - $tagLength, - 0 - ); - } - - /** - * Generates a stream that wraps the cipher text with the proper cipher and - * uses the content encryption key (CEK) to decrypt the data when read. - * - * @param string $cipherText Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return AesStreamInterface - * - * @internal - */ - protected function getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ) { - $cipherTextStream = Psr7\Utils::streamFor($cipherText); - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ); - - return new AesGcmDecryptingStream( - $this->getStrippedCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ), - $cek, - $cipherOptions['Iv'], - $cipherOptions['Tag'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'] ?: null, - $cipherOptions['KeySize'] - ); - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - return new AesDecryptingStream( - $cipherTextStream, - $cek, - $cipherMethod - ); - } - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php deleted file mode 100644 index e8b583ea..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/DecryptionTraitV2.php +++ /dev/null @@ -1,254 +0,0 @@ -decryptCek( - base64_decode( - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] - ), - json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ), - $options - ); - $options['@CipherOptions']['KeySize'] = strlen($cek) * 8; - $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName( - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] - ); - - $this->validateOptionsAndEnvelope($options, $envelope); - - $decryptionStream = $this->getDecryptingStream( - $cipherText, - $cek, - $options['@CipherOptions'] - ); - unset($cek); - - return $decryptionStream; - } - - private function getTagFromCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return (string) new LimitStream( - $cipherText, - $tagLength, - $cipherTextSize - $tagLength - ); - } - - private function getStrippedCiphertextStream( - StreamInterface $cipherText, - $tagLength - ) { - $cipherTextSize = $cipherText->getSize(); - if ($cipherTextSize == null || $cipherTextSize <= 0) { - throw new \RuntimeException('Cannot decrypt a stream of unknown' - . ' size.'); - } - return new LimitStream( - $cipherText, - $cipherTextSize - $tagLength, - 0 - ); - } - - private function validateOptionsAndEnvelope($options, $envelope) - { - $allowedCiphers = AbstractCryptoClientV2::$supportedCiphers; - $allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps; - if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') { - $allowedCiphers = array_unique(array_merge( - $allowedCiphers, - AbstractCryptoClient::$supportedCiphers - )); - $allowedKeywraps = array_unique(array_merge( - $allowedKeywraps, - AbstractCryptoClient::$supportedKeyWraps - )); - } - - $v1SchemaException = new CryptoException("The requested object is encrypted" - . " with V1 encryption schemas that have been disabled by" - . " client configuration @SecurityProfile=V2. Retry with" - . " V2_AND_LEGACY enabled or reencrypt the object."); - - if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) { - if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) { - throw $v1SchemaException; - } - throw new CryptoException("The requested object is encrypted with" - . " the cipher '{$options['@CipherOptions']['Cipher']}', which is not" - . " supported for decryption with the selected security profile." - . " This profile allows decryption with: " - . implode(", ", $allowedCiphers)); - } - if ( - !in_array( - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], - $allowedKeywraps - ) - ) { - if ( - in_array( - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], - AbstractCryptoClient::$supportedKeyWraps - ) - ) { - throw $v1SchemaException; - } - throw new CryptoException("The requested object is encrypted with" - . " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," - . " which is not supported for decryption with the current security" - . " profile."); - } - - $matdesc = json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ); - if ( - isset($matdesc['aws:x-amz-cek-alg']) - && $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== - $matdesc['aws:x-amz-cek-alg'] - ) { - throw new CryptoException("There is a mismatch in specified content" - . " encryption algrithm between the materials description value" - . " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}" - . " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}."); - } - } - - /** - * Generates a stream that wraps the cipher text with the proper cipher and - * uses the content encryption key (CEK) to decrypt the data when read. - * - * @param string $cipherText Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return AesStreamInterface - * - * @internal - */ - protected function getDecryptingStream( - $cipherText, - $cek, - $cipherOptions - ) { - $cipherTextStream = Psr7\Utils::streamFor($cipherText); - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ); - - return new AesGcmDecryptingStream( - $this->getStrippedCiphertextStream( - $cipherTextStream, - $cipherOptions['TagLength'] - ), - $cek, - $cipherOptions['Iv'], - $cipherOptions['Tag'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'] ?: null, - $cipherOptions['KeySize'] - ); - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - return new AesDecryptingStream( - $cipherTextStream, - $cek, - $cipherMethod - ); - } - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php deleted file mode 100644 index 38c346a7..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTrait.php +++ /dev/null @@ -1,194 +0,0 @@ - true, - 'KeySize' => true, - 'Aad' => true, - ]; - - /** - * Dependency to generate a CipherMethod from a set of inputs for loading - * in to an AesEncryptingStream. - * - * @param string $cipherName Name of the cipher to generate for encrypting. - * @param string $iv Base Initialization Vector for the cipher. - * @param int $keySize Size of the encryption key, in bits, that will be - * used. - * - * @return Cipher\CipherMethod - * - * @internal - */ - abstract protected function buildCipherMethod($cipherName, $iv, $keySize); - - /** - * Builds an AesStreamInterface and populates encryption metadata into the - * supplied envelope. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * @param MaterialsProvider $provider A provider to supply and encrypt - * materials used in encryption. - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be added to. - * - * @return AesStreamInterface - * - * @throws \InvalidArgumentException Thrown when a value in $cipherOptions - * is not valid. - * - * @internal - */ - public function encrypt( - Stream $plaintext, - array $cipherOptions, - MaterialsProvider $provider, - MetadataEnvelope $envelope - ) { - $materialsDescription = $provider->getMaterialsDescription(); - - $cipherOptions = array_intersect_key( - $cipherOptions, - self::$allowedOptions - ); - - if (empty($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('An encryption cipher must be' - . ' specified in the "cipher_options".'); - } - - if (!self::isSupportedCipher($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('The cipher requested is not' - . ' supported by the SDK.'); - } - - if (empty($cipherOptions['KeySize'])) { - $cipherOptions['KeySize'] = 256; - } - if (!is_int($cipherOptions['KeySize'])) { - throw new \InvalidArgumentException('The cipher "KeySize" must be' - . ' an integer.'); - } - - if ( - !MaterialsProvider::isSupportedKeySize( - $cipherOptions['KeySize'] - ) - ) { - throw new \InvalidArgumentException('The cipher "KeySize" requested' - . ' is not supported by AES (128, 192, or 256).'); - } - - $cipherOptions['Iv'] = $provider->generateIv( - $this->getCipherOpenSslName( - $cipherOptions['Cipher'], - $cipherOptions['KeySize'] - ) - ); - - $cek = $provider->generateCek($cipherOptions['KeySize']); - - list($encryptingStream, $aesName) = $this->getEncryptingStream( - $plaintext, - $cek, - $cipherOptions - ); - - // Populate envelope data - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = - $provider->encryptCek( - $cek, - $materialsDescription - ); - unset($cek); - - $envelope[MetadataEnvelope::IV_HEADER] = - base64_encode($cipherOptions['Iv']); - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = - $provider->getWrapAlgorithmName(); - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; - $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = - strlen($plaintext); - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = - json_encode($materialsDescription); - if (!empty($cipherOptions['Tag'])) { - $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = - strlen($cipherOptions['Tag']) * 8; - } - - return $encryptingStream; - } - - /** - * Generates a stream that wraps the plaintext with the proper cipher and - * uses the content encryption key (CEK) to encrypt the data when read. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return array returns an array with two elements as follows: [string, AesStreamInterface] - * - * @internal - */ - protected function getEncryptingStream( - Stream $plaintext, - $cek, - &$cipherOptions - ) { - switch ($cipherOptions['Cipher']) { - case 'gcm': - $cipherOptions['TagLength'] = 16; - - $cipherTextStream = new AesGcmEncryptingStream( - $plaintext, - $cek, - $cipherOptions['Iv'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'], - $cipherOptions['KeySize'] - ); - - if (!empty($cipherOptions['Aad'])) { - trigger_error("'Aad' has been supplied for content encryption" - . " with " . $cipherTextStream->getAesName() . ". The" - . " PHP SDK encryption client can decrypt an object" - . " encrypted in this way, but other AWS SDKs may not be" - . " able to.", E_USER_WARNING); - } - - $appendStream = new AppendStream([ - $cipherTextStream->createStream() - ]); - $cipherOptions['Tag'] = $cipherTextStream->getTag(); - $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); - return [$appendStream, $cipherTextStream->getAesName()]; - default: - $cipherMethod = $this->buildCipherMethod( - $cipherOptions['Cipher'], - $cipherOptions['Iv'], - $cipherOptions['KeySize'] - ); - $cipherTextStream = new AesEncryptingStream( - $plaintext, - $cek, - $cipherMethod - ); - return [$cipherTextStream, $cipherTextStream->getAesName()]; - } - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php deleted file mode 100644 index 77c9e802..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/EncryptionTraitV2.php +++ /dev/null @@ -1,198 +0,0 @@ - true, - 'KeySize' => true, - 'Aad' => true, - ]; - - private static $encryptClasses = [ - 'gcm' => AesGcmEncryptingStream::class - ]; - - /** - * Dependency to generate a CipherMethod from a set of inputs for loading - * in to an AesEncryptingStream. - * - * @param string $cipherName Name of the cipher to generate for encrypting. - * @param string $iv Base Initialization Vector for the cipher. - * @param int $keySize Size of the encryption key, in bits, that will be - * used. - * - * @return Cipher\CipherMethod - * - * @internal - */ - abstract protected function buildCipherMethod($cipherName, $iv, $keySize); - - /** - * Builds an AesStreamInterface and populates encryption metadata into the - * supplied envelope. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param array $options Options for use in encryption, including cipher - * options, and encryption context. - * @param MaterialsProviderV2 $provider A provider to supply and encrypt - * materials used in encryption. - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be added to. - * - * @return StreamInterface - * - * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] - * is not valid. - *s - * @internal - */ - public function encrypt( - Stream $plaintext, - array $options, - MaterialsProviderV2 $provider, - MetadataEnvelope $envelope - ) { - $options = array_change_key_case($options); - $cipherOptions = array_intersect_key( - $options['@cipheroptions'], - self::$allowedOptions - ); - - if (empty($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('An encryption cipher must be' - . ' specified in @CipherOptions["Cipher"].'); - } - - $cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']); - - if (!self::isSupportedCipher($cipherOptions['Cipher'])) { - throw new \InvalidArgumentException('The cipher requested is not' - . ' supported by the SDK.'); - } - - if (empty($cipherOptions['KeySize'])) { - $cipherOptions['KeySize'] = 256; - } - if (!is_int($cipherOptions['KeySize'])) { - throw new \InvalidArgumentException('The cipher "KeySize" must be' - . ' an integer.'); - } - - if ( - !MaterialsProviderV2::isSupportedKeySize( - $cipherOptions['KeySize'] - ) - ) { - throw new \InvalidArgumentException('The cipher "KeySize" requested' - . ' is not supported by AES (128 or 256).'); - } - - $cipherOptions['Iv'] = $provider->generateIv( - $this->getCipherOpenSslName( - $cipherOptions['Cipher'], - $cipherOptions['KeySize'] - ) - ); - - $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; - $aesName = $encryptClass::getStaticAesName(); - $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; - - $keys = $provider->generateCek( - $cipherOptions['KeySize'], - $materialsDescription, - $options - ); - - // Some providers modify materials description based on options - if (isset($keys['UpdatedContext'])) { - $materialsDescription = $keys['UpdatedContext']; - } - - $encryptingStream = $this->getEncryptingStream( - $plaintext, - $keys['Plaintext'], - $cipherOptions - ); - - // Populate envelope data - $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; - unset($keys); - - $envelope[MetadataEnvelope::IV_HEADER] = - base64_encode($cipherOptions['Iv']); - $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = - $provider->getWrapAlgorithmName(); - $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; - $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = - strlen($plaintext); - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = - json_encode($materialsDescription); - if (!empty($cipherOptions['Tag'])) { - $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = - strlen($cipherOptions['Tag']) * 8; - } - - return $encryptingStream; - } - - /** - * Generates a stream that wraps the plaintext with the proper cipher and - * uses the content encryption key (CEK) to encrypt the data when read. - * - * @param Stream $plaintext Plain-text data to be encrypted using the - * materials, algorithm, and data provided. - * @param string $cek A content encryption key for use by the stream for - * encrypting the plaintext data. - * @param array $cipherOptions Options for use in determining the cipher to - * be used for encrypting data. - * - * @return array returns an array with two elements as follows: [string, AesStreamInterface] - * - * @internal - */ - protected function getEncryptingStream( - Stream $plaintext, - $cek, - &$cipherOptions - ) { - switch ($cipherOptions['Cipher']) { - // Only 'gcm' is supported for encryption currently - case 'gcm': - $cipherOptions['TagLength'] = 16; - $encryptClass = self::$encryptClasses['gcm']; - $cipherTextStream = new $encryptClass( - $plaintext, - $cek, - $cipherOptions['Iv'], - $cipherOptions['Aad'] = isset($cipherOptions['Aad']) - ? $cipherOptions['Aad'] - : '', - $cipherOptions['TagLength'], - $cipherOptions['KeySize'] - ); - - if (!empty($cipherOptions['Aad'])) { - trigger_error("'Aad' has been supplied for content encryption" - . " with " . $cipherTextStream->getAesName() . ". The" - . " PHP SDK encryption client can decrypt an object" - . " encrypted in this way, but other AWS SDKs may not be" - . " able to.", E_USER_WARNING); - } - - $appendStream = new AppendStream([ - $cipherTextStream->createStream() - ]); - $cipherOptions['Tag'] = $cipherTextStream->getTag(); - $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); - return $appendStream; - } - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php deleted file mode 100644 index bca019bb..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProvider.php +++ /dev/null @@ -1,123 +0,0 @@ -kmsClient = $kmsClient; - $this->kmsKeyId = $kmsKeyId; - } - - public function fromDecryptionEnvelope(MetadataEnvelope $envelope) - { - if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) { - throw new \RuntimeException('Not able to detect the materials description.'); - } - - $materialsDescription = json_decode( - $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], - true - ); - - if ( - empty($materialsDescription['kms_cmk_id']) - && empty($materialsDescription['aws:x-amz-cek-alg']) - ) { - throw new \RuntimeException('Not able to detect kms_cmk_id (legacy' - . ' implementation) or aws:x-amz-cek-alg (current implementation)' - . ' from kms materials description.'); - } - - return new self( - $this->kmsClient, - isset($materialsDescription['kms_cmk_id']) - ? $materialsDescription['kms_cmk_id'] - : null - ); - } - - /** - * The KMS key id for use in matching this Provider to its keys, - * consistently with other SDKs as 'kms_cmk_id'. - * - * @return array - */ - public function getMaterialsDescription() - { - return ['kms_cmk_id' => $this->kmsKeyId]; - } - - public function getWrapAlgorithmName() - { - return self::WRAP_ALGORITHM_NAME; - } - - /** - * Takes a content encryption key (CEK) and description to return an encrypted - * key by using KMS' Encrypt API. - * - * @param string $unencryptedCek Key for use in encrypting other data - * that itself needs to be encrypted by the - * Provider. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - public function encryptCek($unencryptedCek, $materialDescription) - { - $encryptedDataKey = $this->kmsClient->encrypt([ - 'Plaintext' => $unencryptedCek, - 'KeyId' => $this->kmsKeyId, - 'EncryptionContext' => $materialDescription - ]); - return base64_encode($encryptedDataKey['CiphertextBlob']); - } - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key by using KMS' Decrypt API. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - public function decryptCek($encryptedCek, $materialDescription) - { - $result = $this->kmsClient->decrypt([ - 'CiphertextBlob' => $encryptedCek, - 'EncryptionContext' => $materialDescription - ]); - - return $result['Plaintext']; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php b/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php deleted file mode 100644 index d0d923e1..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/KmsMaterialsProviderV2.php +++ /dev/null @@ -1,101 +0,0 @@ -kmsClient = $kmsClient; - $this->kmsKeyId = $kmsKeyId; - } - - /** - * @inheritDoc - */ - public function getWrapAlgorithmName() - { - return self::WRAP_ALGORITHM_NAME; - } - - /** - * @inheritDoc - */ - public function decryptCek($encryptedCek, $materialDescription, $options) - { - $params = [ - 'CiphertextBlob' => $encryptedCek, - 'EncryptionContext' => $materialDescription - ]; - if (empty($options['@KmsAllowDecryptWithAnyCmk'])) { - if (empty($this->kmsKeyId)) { - throw new CryptoException('KMS CMK ID was not specified and the' - . ' operation is not opted-in to attempting to use any valid' - . ' CMK it discovers. Please specify a CMK ID, or explicitly' - . ' enable attempts to use any valid KMS CMK with the' - . ' @KmsAllowDecryptWithAnyCmk option.'); - } - $params['KeyId'] = $this->kmsKeyId; - } - - $result = $this->kmsClient->decrypt($params); - return $result['Plaintext']; - } - - /** - * @inheritDoc - */ - public function generateCek($keySize, $context, $options) - { - if (empty($this->kmsKeyId)) { - throw new CryptoException('A KMS key id is required for encryption' - . ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been' - . ' instantiated with a KMS key id.'); - } - $options = array_change_key_case($options); - if ( - !isset($options['@kmsencryptioncontext']) - || !is_array($options['@kmsencryptioncontext']) - ) { - throw new CryptoException("'@KmsEncryptionContext' is a" - . " required argument when using KmsMaterialsProviderV2, and" - . " must be an associative array (or empty array)."); - } - if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) { - throw new CryptoException("Conflict in reserved @KmsEncryptionContext" - . " key aws:x-amz-cek-alg. This value is reserved for the S3" - . " Encryption Client and cannot be set by the user."); - } - $context = array_merge($options['@kmsencryptioncontext'], $context); - $result = $this->kmsClient->generateDataKey([ - 'KeyId' => $this->kmsKeyId, - 'KeySpec' => "AES_{$keySize}", - 'EncryptionContext' => $context - ]); - return [ - 'Plaintext' => $result['Plaintext'], - 'Ciphertext' => base64_encode($result['CiphertextBlob']), - 'UpdatedContext' => $context - ]; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php deleted file mode 100644 index afbcaf13..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProvider.php +++ /dev/null @@ -1,105 +0,0 @@ - true, - 192 => true, - 256 => true, - ]; - - /** - * Returns if the requested size is supported by AES. - * - * @param int $keySize Size of the requested key in bits. - * - * @return bool - */ - public static function isSupportedKeySize($keySize) - { - return isset(self::$supportedKeySizes[$keySize]); - } - - /** - * Performs further initialization of the MaterialsProvider based on the - * data inside the MetadataEnvelope. - * - * @param MetadataEnvelope $envelope A storage envelope for encryption - * metadata to be read from. - * - * @return MaterialsProvider - * - * @throws \RuntimeException Thrown when there is an empty or improperly - * formed materials description in the envelope. - * - * @internal - */ - abstract public function fromDecryptionEnvelope(MetadataEnvelope $envelope); - - /** - * Returns the material description for this Provider so it can be verified - * by encryption mechanisms. - * - * @return string - */ - abstract public function getMaterialsDescription(); - - /** - * Returns the wrap algorithm name for this Provider. - * - * @return string - */ - abstract public function getWrapAlgorithmName(); - - /** - * Takes a content encryption key (CEK) and description to return an - * encrypted key according to the Provider's specifications. - * - * @param string $unencryptedCek Key for use in encrypting other data - * that itself needs to be encrypted by the - * Provider. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - abstract public function encryptCek($unencryptedCek, $materialDescription); - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key according to the Provider's specifications. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * encrypting the $cek. - * - * @return string - */ - abstract public function decryptCek($encryptedCek, $materialDescription); - - /** - * @param string $keySize Length of a cipher key in bits for generating a - * random content encryption key (CEK). - * - * @return string - */ - public function generateCek($keySize) - { - return openssl_random_pseudo_bytes($keySize / 8); - } - - /** - * @param string $openSslName Cipher OpenSSL name to use for generating - * an initialization vector. - * - * @return string - */ - public function generateIv($openSslName) - { - return openssl_random_pseudo_bytes( - openssl_cipher_iv_length($openSslName) - ); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php deleted file mode 100644 index 71093d54..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MaterialsProviderInterface.php +++ /dev/null @@ -1,61 +0,0 @@ - true, - 256 => true, - ]; - - /** - * Returns if the requested size is supported by AES. - * - * @param int $keySize Size of the requested key in bits. - * - * @return bool - */ - public static function isSupportedKeySize($keySize) - { - return isset(self::$supportedKeySizes[$keySize]); - } - - /** - * Returns the wrap algorithm name for this Provider. - * - * @return string - */ - abstract public function getWrapAlgorithmName(); - - /** - * Takes an encrypted content encryption key (CEK) and material description - * for use decrypting the key according to the Provider's specifications. - * - * @param string $encryptedCek Encrypted key to be decrypted by the Provider - * for use decrypting other data. - * @param string $materialDescription Material Description for use in - * decrypting the CEK. - * @param string $options Options for use in decrypting the CEK. - * - * @return string - */ - abstract public function decryptCek($encryptedCek, $materialDescription, $options); - - /** - * @param string $keySize Length of a cipher key in bits for generating a - * random content encryption key (CEK). - * @param array $context Context map needed for key encryption - * @param array $options Additional options to be used in CEK generation - * - * @return array - */ - abstract public function generateCek($keySize, $context, $options); - - /** - * @param string $openSslName Cipher OpenSSL name to use for generating - * an initialization vector. - * - * @return string - */ - public function generateIv($openSslName) - { - return openssl_random_pseudo_bytes( - openssl_cipher_iv_length($openSslName) - ); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php deleted file mode 100644 index 045fb16d..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataEnvelope.php +++ /dev/null @@ -1,68 +0,0 @@ -getConstants()) as $constant) { - self::$constants[$constant] = true; - } - } - - return array_keys(self::$constants); - } - - /** - * @return void - */ - #[\ReturnTypeWillChange] - public function offsetSet($name, $value) - { - $constants = self::getConstantValues(); - if (is_null($name) || !in_array($name, $constants)) { - throw new InvalidArgumentException('MetadataEnvelope fields must' - . ' must match a predefined offset; use the header constants.'); - } - - $this->data[$name] = $value; - } - - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - return $this->data; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php b/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php deleted file mode 100644 index 370ef585..00000000 --- a/test-server/php-v3-server/local-php-sdk/Crypto/MetadataStrategyInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -instructionFileSuffix; - } - - protected function determineGetObjectStrategy( - $result, - $instructionFileSuffix - ) { - if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) { - return new HeadersMetadataStrategy(); - } - - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - } - - protected function getMetadataStrategy(array $args, $instructionFileSuffix) - { - if (!empty($args['@MetadataStrategy'])) { - if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) { - return $args['@MetadataStrategy']; - } - - if (is_string($args['@MetadataStrategy'])) { - switch ($args['@MetadataStrategy']) { - case HeadersMetadataStrategy::class: - return new HeadersMetadataStrategy(); - case InstructionFileMetadataStrategy::class: - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - default: - throw new \InvalidArgumentException('Could not match the' - . ' specified string in "MetadataStrategy" to a' - . ' predefined strategy.'); - } - } else { - throw new \InvalidArgumentException('The metadata strategy that' - . ' was passed to "MetadataStrategy" was unrecognized.'); - } - } elseif ($instructionFileSuffix) { - return new InstructionFileMetadataStrategy( - $this->client, - $instructionFileSuffix - ); - } - - return null; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php deleted file mode 100644 index 592f68bc..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/CryptoParamsTraitV2.php +++ /dev/null @@ -1,19 +0,0 @@ - $value) { - $args['Metadata'][$header] = $value; - } - - return $args; - } - - /** - * Generates a MetadataEnvelope according to the metadata headers from the - * GetObject result. - * - * @param array $args Arguments from Command and Result that contains - * S3 Object information, relevant headers, and command - * configuration. - * - * @return MetadataEnvelope - */ - public function load(array $args) - { - $envelope = new MetadataEnvelope(); - $constantValues = MetadataEnvelope::getConstantValues(); - - foreach ($constantValues as $constant) { - if (!empty($args['Metadata'][$constant])) { - $envelope[$constant] = $args['Metadata'][$constant]; - } - } - - return $envelope; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php b/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php deleted file mode 100644 index 517a296f..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/InstructionFileMetadataStrategy.php +++ /dev/null @@ -1,108 +0,0 @@ -suffix = empty($suffix) - ? self::DEFAULT_FILE_SUFFIX - : $suffix; - $this->client = $client; - } - - /** - * Places the information in the MetadataEnvelope to a location on S3. - * - * @param MetadataEnvelope $envelope Encryption data to save according to - * the strategy. - * @param array $args Starting arguments for PutObject, used for saving - * extra the instruction file. - * - * @return array Updated arguments for PutObject. - */ - public function save(MetadataEnvelope $envelope, array $args) - { - $this->client->putObject([ - 'Bucket' => $args['Bucket'], - 'Key' => $args['Key'] . $this->suffix, - 'Body' => json_encode($envelope) - ]); - - return $args; - } - - /** - * Uses the strategy's client to retrieve the instruction file from S3 and generates - * a MetadataEnvelope from its contents. - * - * @param array $args Arguments from Command and Result that contains - * S3 Object information, relevant headers, and command - * configuration. - * - * @return MetadataEnvelope - */ - public function load(array $args) - { - $result = $this->client->getObject([ - 'Bucket' => $args['Bucket'], - 'Key' => $args['Key'] . $this->suffix - ]); - - $metadataHeaders = json_decode($result['Body'], true); - $envelope = new MetadataEnvelope(); - $constantValues = MetadataEnvelope::getConstantValues(); - - foreach ($constantValues as $constant) { - if (!empty($metadataHeaders[$constant])) { - $envelope[$constant] = $metadataHeaders[$constant]; - } - } - - // check if we are reading a V3 object - // if it is a V3 object some data is stored in the object metadata and some - // as in the instruction file - //= aws-encryption-sdk-specification/s3-encryption/data-format/content-metadata.md#v3 - //# In the V3 format, the mapkeys "x-amz-c", "x-amz-d", and "x-amz-i" - //# MUST be stored exclusively in the Object Metadata - if (!empty($envelope[MetadataEnvelope::ENCRYPTED_DATA_KEY_V3])) { - // this data is stored in the original object's metadata - // V3 added x-amz-c, x-amz-d, x-amz-i, x-amz-3, x-amz-w, x-amz-m, x-amz-t - // x-amz-c, x-amz-d, x-amz-i are strictly stored on the object metadata - // the rest are stored in the instruction file - $contentCipherV3 = $args['Metadata'][MetadataEnvelope::CONTENT_CIPHER_V3]; - $keyCommitmentV3 = $args['Metadata'][MetadataEnvelope::KEY_COMMITMENT_V3]; - $messageIdV3 = $args['Metadata'][MetadataEnvelope::MESSAGE_ID_V3]; - - - } - - return $envelope; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php deleted file mode 100644 index 1ddd4862..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClient.php +++ /dev/null @@ -1,339 +0,0 @@ -client = $client; - $this->instructionFileSuffix = $instructionFileSuffix; - MetricsBuilder::appendMetricsCaptureMiddleware( - $this->client->getHandlerList(), - MetricsBuilder::S3_CRYPTO_V1N - ); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - /** - * Encrypts the data in the 'Body' field of $args and promises to upload it - * to the specified location on S3. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. Only the - * Cipher option is required. Accepts the following: - * - Cipher: (string) cbc|gcm - * See also: AbstractCryptoClient::$supportedCiphers. Note that - * cbc is deprecated and gcm should be used when possible. - * - KeySize: (int) 128|192|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. Note if you pass in Aad for gcm encryption, the - * PHP SDK will be able to decrypt the resulting object, but other - * AWS SDKs may not be able to do so. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if using an InstructionFileMetadataHandler. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $envelope = new MetadataEnvelope(); - - return Promise\Create::promiseFor($this->encrypt( - Psr7\Utils::streamFor($args['Body']), - $args['@CipherOptions'] ?: [], - $provider, - $envelope - ))->then( - function ($encryptedBodyStream) use ($args) { - $hash = new PhpHash('sha256'); - $hashingEncryptedBodyStream = new HashingStream( - $encryptedBodyStream, - $hash, - self::getContentShaDecorator($args) - ); - return [$hashingEncryptedBodyStream, $args]; - } - )->then( - function ($putObjectContents) use ($strategy, $envelope) { - list($bodyStream, $args) = $putObjectContents; - if ($strategy === null) { - $strategy = self::getDefaultStrategy(); - } - - $updatedArgs = $strategy->save($envelope, $args); - $updatedArgs['Body'] = $bodyStream; - return $updatedArgs; - } - )->then( - function ($args) { - unset($args['@CipherOptions']); - return $this->client->putObjectAsync($args); - } - ); - } - - private static function getContentShaDecorator(&$args) - { - return function ($hash) use (&$args) { - $args['ContentSHA256'] = bin2hex($hash); - }; - } - - /** - * Encrypts the data in the 'Body' field of $args and uploads it to the - * specified location on S3. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Cipher: (string) cbc|gcm - * See also: AbstractCryptoClient::$supportedCiphers. Note that - * cbc is deprecated and gcm should be used when possible. - * - KeySize: (int) 128|192|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. Note if you pass in Aad for gcm encryption, the - * PHP SDK will be able to decrypt the resulting object, but other - * AWS SDKs may not be able to do so. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if an using an InstructionFileMetadataHandler was - * determined. - * - * @return \Aws\Result PutObject call result with the details of uploading - * the encrypted file. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObject(array $args) - { - return $this->putObjectAsync($args)->wait(); - } - - /** - * Promises to retrieve an object from S3 and decrypt the data in the - * 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading - * MetadataEnvelope information. Defaults to determining based on object - * response headers. Can either be a class implementing MetadataStrategy, - * a class name of a predefined strategy, or empty/null to default. - * - @InstructionFileSuffix: (string) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler is being used. - * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when required arguments are not - * passed or are passed incorrectly. - */ - public function getObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $saveAs = null; - if (!empty($args['SaveAs'])) { - $saveAs = $args['SaveAs']; - } - - $promise = $this->client->getObjectAsync($args) - ->then( - function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { - if ($strategy === null) { - $strategy = $this->determineGetObjectStrategy( - $result, - $instructionFileSuffix - ); - } - - $envelope = $strategy->load($args + [ - 'Metadata' => $result['Metadata'] - ]); - - $provider = $provider->fromDecryptionEnvelope($envelope); - - $result['Body'] = $this->decrypt( - $result['Body'], - $provider, - $envelope, - isset($args['@CipherOptions']) - ? $args['@CipherOptions'] - : [] - ); - return $result; - } - )->then( - function ($result) use ($saveAs) { - if (!empty($saveAs)) { - file_put_contents( - $saveAs, - (string) $result['Body'], - LOCK_EX - ); - } - return $result; - } - ); - - return $promise; - } - - /** - * Retrieves an object from S3 and decrypts the data in the 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - @InstructionFileSuffix: (string|null) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler was detected. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - * @return \Aws\Result GetObject call result with the 'Body' field - * wrapped in a decryption stream with its metadata - * information. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function getObject(array $args) - { - return $this->getObjectAsync($args)->wait(); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php deleted file mode 100644 index 39a98dcf..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV2.php +++ /dev/null @@ -1,447 +0,0 @@ - - * use Local\Aws\Crypto\KmsMaterialsProviderV2; - * use Aws\S3\Crypto\S3EncryptionClientV2; - * use Aws\S3\S3Client; - * - * $encryptionClient = new S3EncryptionClientV2( - * new S3Client([ - * 'region' => 'us-west-2', - * 'version' => 'latest' - * ]) - * ); - * $materialsProvider = new KmsMaterialsProviderV2( - * new KmsClient([ - * 'profile' => 'default', - * 'region' => 'us-east-1', - * 'version' => 'latest', - * ], - * 'your-kms-key-id' - * ); - * - * $encryptionClient->putObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * '@KmsEncryptionContext' => ['foo' => 'bar'], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * 'Body' => 'your-encrypted-data', - * ]); - * - * - * Example read call (using objects from previous example): - * - * - * $encryptionClient->getObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * ]); - * - */ -class S3EncryptionClientV2 extends AbstractCryptoClientV2 -{ - use CipherBuilderTrait; - use CryptoParamsTraitV2; - use DecryptionTraitV2; - use EncryptionTraitV2; - use UserAgentTrait; - - const CRYPTO_VERSION = '2.1'; - - private $client; - private $instructionFileSuffix; - private $legacyWarningCount; - - /** - * @param S3Client $client The S3Client to be used for true uploading and - * retrieving objects from S3 when using the - * encryption client. - * @param string|null $instructionFileSuffix Suffix for a client wide - * default when using instruction - * files for metadata storage. - */ - public function __construct( - S3Client $client, - $instructionFileSuffix = null - ) { - $this->client = $client; - $this->instructionFileSuffix = $instructionFileSuffix; - $this->legacyWarningCount = 0; - MetricsBuilder::appendMetricsCaptureMiddleware( - $this->client->getHandlerList(), - MetricsBuilder::S3_CRYPTO_V2 - ); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - /** - * Encrypts the data in the 'Body' field of $args and promises to upload it - * to the specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. Only the - * Cipher option is required. Accepts the following: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if using an InstructionFileMetadataHandler. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $envelope = new MetadataEnvelope(); - - return Promise\Create::promiseFor($this->encrypt( - Psr7\Utils::streamFor($args['Body']), - $args, - $provider, - $envelope - ))->then( - function ($encryptedBodyStream) use ($args) { - $hash = new PhpHash('sha256'); - $hashingEncryptedBodyStream = new HashingStream( - $encryptedBodyStream, - $hash, - self::getContentShaDecorator($args) - ); - return [$hashingEncryptedBodyStream, $args]; - } - )->then( - function ($putObjectContents) use ($strategy, $envelope) { - list($bodyStream, $args) = $putObjectContents; - if ($strategy === null) { - $strategy = self::getDefaultStrategy(); - } - - $updatedArgs = $strategy->save($envelope, $args); - $updatedArgs['Body'] = $bodyStream; - return $updatedArgs; - } - )->then( - function ($args) { - unset($args['@CipherOptions']); - return $this->client->putObjectAsync($args); - } - ); - } - - private static function getContentShaDecorator(&$args) - { - return function ($hash) use (&$args) { - $args['ContentSHA256'] = bin2hex($hash); - }; - } - - /** - * Encrypts the data in the 'Body' field of $args and uploads it to the - * specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if an using an InstructionFileMetadataHandler was - * determined. - * - * @return \Aws\Result PutObject call result with the details of uploading - * the encrypted file. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObject(array $args) - { - return $this->putObjectAsync($args)->wait(); - } - - /** - * Promises to retrieve an object from S3 and decrypt the data in the - * 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading - * MetadataEnvelope information. Defaults to determining based on object - * response headers. Can either be a class implementing MetadataStrategy, - * a class name of a predefined strategy, or empty/null to default. - * - @InstructionFileSuffix: (string) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler is being used. - * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when required arguments are not - * passed or are passed incorrectly. - */ - public function getObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - if ( - !isset($args['@SecurityProfile']) - || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) - ) { - throw new CryptoException("@SecurityProfile is required and must be" - . " set to 'V2' or 'V2_AND_LEGACY'"); - } - - // Only throw this legacy warning once per client - if ( - in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) - && $this->legacyWarningCount < 1 - ) { - $this->legacyWarningCount++; - trigger_error( - "This S3 Encryption Client operation is configured to" - . " read encrypted data with legacy encryption modes. If you" - . " don't have objects encrypted with these legacy modes," - . " you should disable support for them to enhance security. ", - E_USER_WARNING - ); - } - - $saveAs = null; - if (!empty($args['SaveAs'])) { - $saveAs = $args['SaveAs']; - } - - $promise = $this->client->getObjectAsync($args) - ->then( - function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { - if ($strategy === null) { - $strategy = $this->determineGetObjectStrategy( - $result, - $instructionFileSuffix - ); - } - - $envelope = $strategy->load($args + [ - 'Metadata' => $result['Metadata'] - ]); - - $result['Body'] = $this->decrypt( - $result['Body'], - $provider, - $envelope, - $args - ); - return $result; - } - )->then( - function ($result) use ($saveAs) { - if (!empty($saveAs)) { - file_put_contents( - $saveAs, - (string) $result['Body'], - LOCK_EX - ); - } - return $result; - } - ); - - return $promise; - } - - /** - * Retrieves an object from S3 and decrypts the data in the 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - @InstructionFileSuffix: (string|null) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler was detected. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return \Aws\Result GetObject call result with the 'Body' field - * wrapped in a decryption stream with its metadata - * information. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function getObject(array $args) - { - return $this->getObjectAsync($args)->wait(); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php deleted file mode 100644 index 50719a72..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionClientV3.php +++ /dev/null @@ -1,447 +0,0 @@ - - * use Local\Aws\Crypto\KmsMaterialsProviderV2; - * use Aws\S3\Crypto\S3EncryptionClientV2; - * use Aws\S3\S3Client; - * - * $encryptionClient = new S3EncryptionClientV2( - * new S3Client([ - * 'region' => 'us-west-2', - * 'version' => 'latest' - * ]) - * ); - * $materialsProvider = new KmsMaterialsProviderV2( - * new KmsClient([ - * 'profile' => 'default', - * 'region' => 'us-east-1', - * 'version' => 'latest', - * ], - * 'your-kms-key-id' - * ); - * - * $encryptionClient->putObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * '@KmsEncryptionContext' => ['foo' => 'bar'], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * 'Body' => 'your-encrypted-data', - * ]); - * - * - * Example read call (using objects from previous example): - * - * - * $encryptionClient->getObject([ - * '@MaterialsProvider' => $materialsProvider, - * '@CipherOptions' => [ - * 'Cipher' => 'gcm', - * 'KeySize' => 256, - * ], - * 'Bucket' => 'your-bucket', - * 'Key' => 'your-key', - * ]); - * - */ -class S3EncryptionClientV3 extends AbstractCryptoClientV2 -{ - use CipherBuilderTrait; - use CryptoParamsTraitV2; - use DecryptionTraitV2; - use EncryptionTraitV2; - use UserAgentTrait; - - const CRYPTO_VERSION = '3.0'; - - private $client; - private $instructionFileSuffix; - private $legacyWarningCount; - - /** - * @param S3Client $client The S3Client to be used for true uploading and - * retrieving objects from S3 when using the - * encryption client. - * @param string|null $instructionFileSuffix Suffix for a client wide - * default when using instruction - * files for metadata storage. - */ - public function __construct( - S3Client $client, - $instructionFileSuffix = null - ) { - $this->client = $client; - $this->instructionFileSuffix = $instructionFileSuffix; - $this->legacyWarningCount = 0; - MetricsBuilder::appendMetricsCaptureMiddleware( - $this->client->getHandlerList(), - MetricsBuilder::S3_CRYPTO_V2 - ); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - /** - * Encrypts the data in the 'Body' field of $args and promises to upload it - * to the specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. Only the - * Cipher option is required. Accepts the following: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if using an InstructionFileMetadataHandler. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - $envelope = new MetadataEnvelope(); - - return Promise\Create::promiseFor($this->encrypt( - Psr7\Utils::streamFor($args['Body']), - $args, - $provider, - $envelope - ))->then( - function ($encryptedBodyStream) use ($args) { - $hash = new PhpHash('sha256'); - $hashingEncryptedBodyStream = new HashingStream( - $encryptedBodyStream, - $hash, - self::getContentShaDecorator($args) - ); - return [$hashingEncryptedBodyStream, $args]; - } - )->then( - function ($putObjectContents) use ($strategy, $envelope) { - list($bodyStream, $args) = $putObjectContents; - if ($strategy === null) { - $strategy = self::getDefaultStrategy(); - } - - $updatedArgs = $strategy->save($envelope, $args); - $updatedArgs['Body'] = $bodyStream; - return $updatedArgs; - } - )->then( - function ($args) { - unset($args['@CipherOptions']); - return $this->client->putObjectAsync($args); - } - ); - } - - private static function getContentShaDecorator(&$args) - { - return function ($hash) use (&$args) { - $args['ContentSHA256'] = bin2hex($hash); - }; - } - - /** - * Encrypts the data in the 'Body' field of $args and uploads it to the - * specified location on S3. - * - * Note that for PHP versions of < 7.1, this operation uses an AES-GCM - * polyfill for encryption since there is no native PHP support. The - * performance for large inputs will be a lot slower than for PHP 7.1+, so - * upgrading older PHP version environments may be necessary to use this - * effectively. - * - * @param array $args Arguments for encrypting an object and uploading it - * to S3 via PutObject. - * - * The required configuration arguments are as follows: - * - * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek - * encrypting/decrypting for encryption metadata. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Cipher: (string) gcm - * See also: AbstractCryptoClientV2::$supportedCiphers - * - KeySize: (int) 128|256 - * See also: MaterialsProvider::$supportedKeySizes - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. Note if you pass in - * Aad, the PHP SDK will be able to decrypt the resulting object, - * but other AWS SDKs may not be able to do so. - * - @KmsEncryptionContext: (array) Only required if using - * KmsMaterialsProviderV2. An associative array of key-value - * pairs to be added to the encryption context for KMS key encryption. An - * empty array may be passed if no additional context is desired. - * - * The optional configuration arguments are as follows: - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing - * MetadataEnvelope information. Defaults to using a - * HeadersMetadataStrategy. Can either be a class implementing - * MetadataStrategy, a class name of a predefined strategy, or empty/null - * to default. - * - @InstructionFileSuffix: (string|null) Suffix used when writing to an - * instruction file if an using an InstructionFileMetadataHandler was - * determined. - * - * @return \Aws\Result PutObject call result with the details of uploading - * the encrypted file. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function putObject(array $args) - { - return $this->putObjectAsync($args)->wait(); - } - - /** - * Promises to retrieve an object from S3 and decrypt the data in the - * 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading - * MetadataEnvelope information. Defaults to determining based on object - * response headers. Can either be a class implementing MetadataStrategy, - * a class name of a predefined strategy, or empty/null to default. - * - @InstructionFileSuffix: (string) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler is being used. - * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return PromiseInterface - * - * @throws \InvalidArgumentException Thrown when required arguments are not - * passed or are passed incorrectly. - */ - public function getObjectAsync(array $args) - { - $provider = $this->getMaterialsProvider($args); - unset($args['@MaterialsProvider']); - - $instructionFileSuffix = $this->getInstructionFileSuffix($args); - unset($args['@InstructionFileSuffix']); - - $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix); - unset($args['@MetadataStrategy']); - - if ( - !isset($args['@SecurityProfile']) - || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles) - ) { - throw new CryptoException("@SecurityProfile is required and must be" - . " set to 'V2' or 'V2_AND_LEGACY'"); - } - - // Only throw this legacy warning once per client - if ( - in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) - && $this->legacyWarningCount < 1 - ) { - $this->legacyWarningCount++; - trigger_error( - "This S3 Encryption Client operation is configured to" - . " read encrypted data with legacy encryption modes. If you" - . " don't have objects encrypted with these legacy modes," - . " you should disable support for them to enhance security. ", - E_USER_WARNING - ); - } - - $saveAs = null; - if (!empty($args['SaveAs'])) { - $saveAs = $args['SaveAs']; - } - - $promise = $this->client->getObjectAsync($args) - ->then( - function ($result) use ($provider, $instructionFileSuffix, $strategy, $args) { - if ($strategy === null) { - $strategy = $this->determineGetObjectStrategy( - $result, - $instructionFileSuffix - ); - } - - $envelope = $strategy->load($args + [ - 'Metadata' => $result['Metadata'] - ]); - - $result['Body'] = $this->decrypt( - $result['Body'], - $provider, - $envelope, - $args - ); - return $result; - } - )->then( - function ($result) use ($saveAs) { - if (!empty($saveAs)) { - file_put_contents( - $saveAs, - (string) $result['Body'], - LOCK_EX - ); - } - return $result; - } - ); - - return $promise; - } - - /** - * Retrieves an object from S3 and decrypts the data in the 'Body' field. - * - * @param array $args Arguments for retrieving an object from S3 via - * GetObject and decrypting it. - * - * The required configuration argument is as follows: - * - * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek - * encrypting/decrypting for decryption metadata. May have data loaded - * from the MetadataEnvelope upon decryption. - * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'. - * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2 - * content encryption and key wrap schemas are able to be decrypted. - * - 'V2_AND_LEGACY' indicates that objects encrypted with both - * S3EncryptionClientV2 and older legacy encryption clients are able - * to be decrypted. - * - * The optional configuration arguments are as follows: - * - * - SaveAs: (string) The path to a file on disk to save the decrypted - * object data. This will be handled by file_put_contents instead of the - * Guzzle sink. - * - @InstructionFileSuffix: (string|null) Suffix used when looking for an - * instruction file if an InstructionFileMetadataHandler was detected. - * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher - * is required. Accepts the following options: - * - Aad: (string) Additional authentication data. This option is - * passed directly to OpenSSL when using gcm. It is ignored when - * using cbc. - * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with - * KMS materials for any KMS key ID, instead of needing the KMS key ID to - * be specified and provided to the decrypt operation. Ignored for non-KMS - * materials providers. Defaults to false. - * - * @return \Aws\Result GetObject call result with the 'Body' field - * wrapped in a decryption stream with its metadata - * information. - * - * @throws \InvalidArgumentException Thrown when arguments above are not - * passed or are passed incorrectly. - */ - public function getObject(array $args) - { - return $this->getObjectAsync($args)->wait(); - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php deleted file mode 100644 index 4fdb7a77..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploader.php +++ /dev/null @@ -1,169 +0,0 @@ -appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); - $this->client = $client; - $config['params'] = []; - if (!empty($config['bucket'])) { - $config['params']['Bucket'] = $config['bucket']; - } - if (!empty($config['key'])) { - $config['params']['Key'] = $config['key']; - } - - $this->provider = $this->getMaterialsProvider($config); - unset($config['@MaterialsProvider']); - - $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); - unset($config['@InstructionFileSuffix']); - $this->strategy = $this->getMetadataStrategy( - $config, - $this->instructionFileSuffix - ); - if ($this->strategy === null) { - $this->strategy = self::getDefaultStrategy(); - } - unset($config['@MetadataStrategy']); - - $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); - - parent::__construct($client, $source, $config); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - private function getEncryptingDataPreparer() - { - return function () { - // Defer encryption work until promise is executed - $envelope = new MetadataEnvelope(); - - list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( - $this->source, - $this->config['@cipheroptions'] ?: [], - $this->provider, - $envelope - ))->then( - function ($bodyStream) use ($envelope) { - $params = $this->strategy->save( - $envelope, - $this->config['params'] - ); - return [$bodyStream, $params]; - } - )->wait(); - - $this->source->rewind(); - $this->config['params'] = $params; - }; - } -} \ No newline at end of file diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php b/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php deleted file mode 100644 index 813305ef..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/S3EncryptionMultipartUploaderV2.php +++ /dev/null @@ -1,176 +0,0 @@ -appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION); - $this->client = $client; - $config['params'] = []; - if (!empty($config['bucket'])) { - $config['params']['Bucket'] = $config['bucket']; - } - if (!empty($config['key'])) { - $config['params']['Key'] = $config['key']; - } - - $this->provider = $this->getMaterialsProvider($config); - unset($config['@MaterialsProvider']); - - $this->instructionFileSuffix = $this->getInstructionFileSuffix($config); - unset($config['@InstructionFileSuffix']); - $this->strategy = $this->getMetadataStrategy( - $config, - $this->instructionFileSuffix - ); - if ($this->strategy === null) { - $this->strategy = self::getDefaultStrategy(); - } - unset($config['@MetadataStrategy']); - - $config['prepare_data_source'] = $this->getEncryptingDataPreparer(); - - parent::__construct($client, $source, $config); - } - - private static function getDefaultStrategy() - { - return new HeadersMetadataStrategy(); - } - - private function getEncryptingDataPreparer() - { - return function () { - // Defer encryption work until promise is executed - $envelope = new MetadataEnvelope(); - - list($this->source, $params) = Promise\Create::promiseFor($this->encrypt( - $this->source, - $this->config ?: [], - $this->provider, - $envelope - ))->then( - function ($bodyStream) use ($envelope) { - $params = $this->strategy->save( - $envelope, - $this->config['params'] - ); - return [$bodyStream, $params]; - } - )->wait(); - - $this->source->rewind(); - $this->config['params'] = $params; - }; - } -} diff --git a/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php b/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php deleted file mode 100644 index 7f95ca06..00000000 --- a/test-server/php-v3-server/local-php-sdk/S3EC/UserAgentTrait.php +++ /dev/null @@ -1,32 +0,0 @@ -getHandlerList(); - $list->appendBuild(Middleware::mapRequest( - function (RequestInterface $req) use ($agentString) { - if ( - !empty($req->getHeader('User-Agent')) - && !empty($req->getHeader('User-Agent')[0]) - ) { - $userAgent = $req->getHeader('User-Agent')[0]; - if (strpos($userAgent, $agentString) === false) { - $userAgent .= " {$agentString}"; - }; - } else { - $userAgent = $agentString; - } - - $req = $req->withHeader('User-Agent', $userAgent); - return $req; - } - )); - } -} From 20c0f83d7ef1e76b8ae7cef6dc5a8b89f2b518d9 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 10:44:19 -0700 Subject: [PATCH 07/10] link to the local sdks --- test-server/php-v2-server/composer.json | 3 +-- test-server/php-v2-server/src/index.php | 4 ++-- test-server/php-v3-server/composer.json | 3 +-- test-server/php-v3-server/src/index.php | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index a6d517a9..5186458a 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -11,8 +11,7 @@ "autoload": { "psr-4": { "S3EC\\PhpV2Server\\": "src/", - "Local\\Aws\\S3\\Crypto\\": "local-php-sdk/S3EC/", - "Local\\Aws\\Crypto\\": "local-php-sdk/Crypto/" + "Aws\\": "local-php-sdk/src/" } }, "scripts": { diff --git a/test-server/php-v2-server/src/index.php b/test-server/php-v2-server/src/index.php index 59acda97..cc5dee29 100644 --- a/test-server/php-v2-server/src/index.php +++ b/test-server/php-v2-server/src/index.php @@ -5,8 +5,8 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Local\Aws\S3\Crypto\S3EncryptionClientV2; -use Local\Aws\Crypto\KmsMaterialsProviderV2; +use Aws\S3\Crypto\S3EncryptionClientV2; +use Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; diff --git a/test-server/php-v3-server/composer.json b/test-server/php-v3-server/composer.json index eb824537..cce774f8 100644 --- a/test-server/php-v3-server/composer.json +++ b/test-server/php-v3-server/composer.json @@ -11,8 +11,7 @@ "autoload": { "psr-4": { "S3EC\\PhpV2Server\\": "src/", - "Local\\Aws\\S3\\Crypto\\": "local-php-sdk/S3EC/", - "Local\\Aws\\Crypto\\": "local-php-sdk/Crypto/" + "Aws\\": "local-php-sdk/src/" } }, "scripts": { diff --git a/test-server/php-v3-server/src/index.php b/test-server/php-v3-server/src/index.php index a60e5a3f..b20367bb 100644 --- a/test-server/php-v3-server/src/index.php +++ b/test-server/php-v3-server/src/index.php @@ -5,8 +5,8 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Local\Aws\S3\Crypto\S3EncryptionClientV3; -use Local\Aws\Crypto\KmsMaterialsProviderV2; +use Aws\S3\Crypto\S3EncryptionClientV3; +use Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; From 67053f547f3ce33e260d7acf85c5f3404f50227b Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 12:25:40 -0700 Subject: [PATCH 08/10] correctly link to the local sdk --- .../amazon/encryption/s3/RoundTripTests.java | 14 ++++++------- test-server/php-v2-server/composer.json | 19 +++++++++++++---- test-server/php-v3-server/composer.json | 21 ++++++++++++++----- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 301a45f2..cd67f2e0 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -79,15 +79,15 @@ public class RoundTripTests { static { final Map servers = new LinkedHashMap<>(); servers.put(JAVA_V3, new LanguageServerTarget(JAVA_V3, "8080")); - // servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); - // servers.put(GO_V3, new LanguageServerTarget(GO_V3, "8082")); - // servers.put(NET_V2, new LanguageServerTarget(NET_V2, "8083")); - // servers.put(NET_V3, new LanguageServerTarget(NET_V3, "8084")); - // servers.put(CPP_V2, new LanguageServerTarget(CPP_V2, "8085")); + servers.put(PYTHON_V3, new LanguageServerTarget(PYTHON_V3, "8081")); + servers.put(GO_V3, new LanguageServerTarget(GO_V3, "8082")); + servers.put(NET_V2, new LanguageServerTarget(NET_V2, "8083")); + servers.put(NET_V3, new LanguageServerTarget(NET_V3, "8084")); + servers.put(CPP_V2, new LanguageServerTarget(CPP_V2, "8085")); servers.put(PHP_V2, new LanguageServerTarget(PHP_V2, "8087")); servers.put(PHP_V3, new LanguageServerTarget(PHP_V3, "8093")); - // servers.put(RUBY_V2, new LanguageServerTarget(RUBY_V2, "8086")); - // servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); + servers.put(RUBY_V2, new LanguageServerTarget(RUBY_V2, "8086")); + servers.put(RUBY_V3, new LanguageServerTarget(RUBY_V3, "8092")); serverMap = filterServers(servers); } diff --git a/test-server/php-v2-server/composer.json b/test-server/php-v2-server/composer.json index 5186458a..d5177951 100644 --- a/test-server/php-v2-server/composer.json +++ b/test-server/php-v2-server/composer.json @@ -3,15 +3,23 @@ "description": "PHP v2 implementation of the S3EC Test Server framework", "type": "project", "license": "Apache-2.0", + "repositories": [ + { + "type": "path", + "url": "./local-php-sdk", + "options": { + "symlink": true + } + } + ], "require": { "php": ">=7.4", - "aws/aws-sdk-php": "^3.356", + "aws/aws-sdk-php": "@dev", "ramsey/uuid": "^4.9" }, "autoload": { "psr-4": { - "S3EC\\PhpV2Server\\": "src/", - "Aws\\": "local-php-sdk/src/" + "S3EC\\PhpV2Server\\": "src/" } }, "scripts": { @@ -20,6 +28,9 @@ ] }, "config": { - "optimize-autoloader": true + "optimize-autoloader": true, + "platform": { + "php": "8.1" + } } } \ No newline at end of file diff --git a/test-server/php-v3-server/composer.json b/test-server/php-v3-server/composer.json index cce774f8..d5177951 100644 --- a/test-server/php-v3-server/composer.json +++ b/test-server/php-v3-server/composer.json @@ -3,23 +3,34 @@ "description": "PHP v2 implementation of the S3EC Test Server framework", "type": "project", "license": "Apache-2.0", + "repositories": [ + { + "type": "path", + "url": "./local-php-sdk", + "options": { + "symlink": true + } + } + ], "require": { "php": ">=7.4", - "aws/aws-sdk-php": "^3.356", + "aws/aws-sdk-php": "@dev", "ramsey/uuid": "^4.9" }, "autoload": { "psr-4": { - "S3EC\\PhpV2Server\\": "src/", - "Aws\\": "local-php-sdk/src/" + "S3EC\\PhpV2Server\\": "src/" } }, "scripts": { "start": [ - "php -S 0.0.0.0:8093 src/index.php" + "php -S 0.0.0.0:8087 src/index.php" ] }, "config": { - "optimize-autoloader": true + "optimize-autoloader": true, + "platform": { + "php": "8.1" + } } } \ No newline at end of file From 67b22f4f891c1bf5f61bca1567c894f66532e32c Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 12:27:52 -0700 Subject: [PATCH 09/10] clean up --- test-server/php-v3-server/src/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-server/php-v3-server/src/index.php b/test-server/php-v3-server/src/index.php index b20367bb..cc5dee29 100644 --- a/test-server/php-v3-server/src/index.php +++ b/test-server/php-v3-server/src/index.php @@ -5,7 +5,7 @@ require_once __DIR__ . '/get_object.php'; require_once __DIR__ . '/put_object.php'; -use Aws\S3\Crypto\S3EncryptionClientV3; +use Aws\S3\Crypto\S3EncryptionClientV2; use Aws\Crypto\KmsMaterialsProviderV2; use Aws\S3\S3Client; use Aws\Kms\KmsClient; @@ -157,7 +157,7 @@ function getCachedClient($clientId) // Recreate the AWS clients from stored configuration $s3Client = new S3Client($config['s3Config']); - $encryptionClient = new S3EncryptionClientV3($s3Client); + $encryptionClient = new S3EncryptionClientV2($s3Client); $kmsClient = new KmsClient($config['kmsConfig']); $materialsProvider = new KmsMaterialsProviderV2($kmsClient, $config['kmsKeyId']); @@ -183,7 +183,7 @@ function createDefaultClientTuple(): array ] ] ]); - $encryptionClient = new S3EncryptionClientV3($s3Client); + $encryptionClient = new S3EncryptionClientV2($s3Client); $kmsClient = new KmsClient([ 'region' => 'us-west-2', From cfb6c502b74239052cddc37233b9066111219756 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Thu, 25 Sep 2025 12:59:08 -0700 Subject: [PATCH 10/10] 8093 --- test-server/php-v3-server/composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-server/php-v3-server/composer.json b/test-server/php-v3-server/composer.json index d5177951..32c2b00c 100644 --- a/test-server/php-v3-server/composer.json +++ b/test-server/php-v3-server/composer.json @@ -1,6 +1,6 @@ { - "name": "aws/s3ec-php-v2-test-server", - "description": "PHP v2 implementation of the S3EC Test Server framework", + "name": "aws/s3ec-php-v3-test-server", + "description": "PHP v3 implementation of the S3EC Test Server framework", "type": "project", "license": "Apache-2.0", "repositories": [ @@ -24,7 +24,7 @@ }, "scripts": { "start": [ - "php -S 0.0.0.0:8087 src/index.php" + "php -S 0.0.0.0:8093 src/index.php" ] }, "config": {