Cipher
11 months ago
Polyfill
11 months ago
AbstractCryptoClient.php
11 months ago
AbstractCryptoClientV2.php
11 months ago
AesDecryptingStream.php
11 months ago
AesEncryptingStream.php
11 months ago
AesGcmDecryptingStream.php
11 months ago
AesGcmEncryptingStream.php
11 months ago
AesStreamInterface.php
11 months ago
AesStreamInterfaceV2.php
11 months ago
DecryptionTrait.php
11 months ago
DecryptionTraitV2.php
11 months ago
EncryptionTrait.php
11 months ago
EncryptionTraitV2.php
11 months ago
KmsMaterialsProvider.php
11 months ago
KmsMaterialsProviderV2.php
11 months ago
MaterialsProvider.php
11 months ago
MaterialsProviderInterface.php
11 months ago
MaterialsProviderInterfaceV2.php
11 months ago
MaterialsProviderV2.php
11 months ago
MetadataEnvelope.php
11 months ago
MetadataStrategyInterface.php
11 months ago
EncryptionTraitV2.php
197 lines
| 1 | <?php |
| 2 | namespace Aws\Crypto; |
| 3 | |
| 4 | use GuzzleHttp\Psr7; |
| 5 | use GuzzleHttp\Psr7\AppendStream; |
| 6 | use GuzzleHttp\Psr7\Stream; |
| 7 | use Psr\Http\Message\StreamInterface; |
| 8 | |
| 9 | trait EncryptionTraitV2 |
| 10 | { |
| 11 | private static $allowedOptions = [ |
| 12 | 'Cipher' => true, |
| 13 | 'KeySize' => true, |
| 14 | 'Aad' => true, |
| 15 | ]; |
| 16 | |
| 17 | private static $encryptClasses = [ |
| 18 | 'gcm' => AesGcmEncryptingStream::class |
| 19 | ]; |
| 20 | |
| 21 | /** |
| 22 | * Dependency to generate a CipherMethod from a set of inputs for loading |
| 23 | * in to an AesEncryptingStream. |
| 24 | * |
| 25 | * @param string $cipherName Name of the cipher to generate for encrypting. |
| 26 | * @param string $iv Base Initialization Vector for the cipher. |
| 27 | * @param int $keySize Size of the encryption key, in bits, that will be |
| 28 | * used. |
| 29 | * |
| 30 | * @return Cipher\CipherMethod |
| 31 | * |
| 32 | * @internal |
| 33 | */ |
| 34 | abstract protected function buildCipherMethod($cipherName, $iv, $keySize); |
| 35 | |
| 36 | /** |
| 37 | * Builds an AesStreamInterface and populates encryption metadata into the |
| 38 | * supplied envelope. |
| 39 | * |
| 40 | * @param Stream $plaintext Plain-text data to be encrypted using the |
| 41 | * materials, algorithm, and data provided. |
| 42 | * @param array $options Options for use in encryption, including cipher |
| 43 | * options, and encryption context. |
| 44 | * @param MaterialsProviderV2 $provider A provider to supply and encrypt |
| 45 | * materials used in encryption. |
| 46 | * @param MetadataEnvelope $envelope A storage envelope for encryption |
| 47 | * metadata to be added to. |
| 48 | * |
| 49 | * @return StreamInterface |
| 50 | * |
| 51 | * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] |
| 52 | * is not valid. |
| 53 | *s |
| 54 | * @internal |
| 55 | */ |
| 56 | public function encrypt( |
| 57 | Stream $plaintext, |
| 58 | array $options, |
| 59 | MaterialsProviderV2 $provider, |
| 60 | MetadataEnvelope $envelope |
| 61 | ) { |
| 62 | $options = array_change_key_case($options); |
| 63 | $cipherOptions = array_intersect_key( |
| 64 | $options['@cipheroptions'], |
| 65 | self::$allowedOptions |
| 66 | ); |
| 67 | |
| 68 | if (empty($cipherOptions['Cipher'])) { |
| 69 | throw new \InvalidArgumentException('An encryption cipher must be' |
| 70 | . ' specified in @CipherOptions["Cipher"].'); |
| 71 | } |
| 72 | |
| 73 | $cipherOptions['Cipher'] = strtolower($cipherOptions['Cipher']); |
| 74 | |
| 75 | if (!self::isSupportedCipher($cipherOptions['Cipher'])) { |
| 76 | throw new \InvalidArgumentException('The cipher requested is not' |
| 77 | . ' supported by the SDK.'); |
| 78 | } |
| 79 | |
| 80 | if (empty($cipherOptions['KeySize'])) { |
| 81 | $cipherOptions['KeySize'] = 256; |
| 82 | } |
| 83 | if (!is_int($cipherOptions['KeySize'])) { |
| 84 | throw new \InvalidArgumentException('The cipher "KeySize" must be' |
| 85 | . ' an integer.'); |
| 86 | } |
| 87 | |
| 88 | if (!MaterialsProviderV2::isSupportedKeySize( |
| 89 | $cipherOptions['KeySize'] |
| 90 | )) { |
| 91 | throw new \InvalidArgumentException('The cipher "KeySize" requested' |
| 92 | . ' is not supported by AES (128 or 256).'); |
| 93 | } |
| 94 | |
| 95 | $cipherOptions['Iv'] = $provider->generateIv( |
| 96 | $this->getCipherOpenSslName( |
| 97 | $cipherOptions['Cipher'], |
| 98 | $cipherOptions['KeySize'] |
| 99 | ) |
| 100 | ); |
| 101 | |
| 102 | $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; |
| 103 | $aesName = $encryptClass::getStaticAesName(); |
| 104 | $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; |
| 105 | |
| 106 | $keys = $provider->generateCek( |
| 107 | $cipherOptions['KeySize'], |
| 108 | $materialsDescription, |
| 109 | $options |
| 110 | ); |
| 111 | |
| 112 | // Some providers modify materials description based on options |
| 113 | if (isset($keys['UpdatedContext'])) { |
| 114 | $materialsDescription = $keys['UpdatedContext']; |
| 115 | } |
| 116 | |
| 117 | $encryptingStream = $this->getEncryptingStream( |
| 118 | $plaintext, |
| 119 | $keys['Plaintext'], |
| 120 | $cipherOptions |
| 121 | ); |
| 122 | |
| 123 | // Populate envelope data |
| 124 | $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; |
| 125 | unset($keys); |
| 126 | |
| 127 | $envelope[MetadataEnvelope::IV_HEADER] = |
| 128 | base64_encode($cipherOptions['Iv']); |
| 129 | $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = |
| 130 | $provider->getWrapAlgorithmName(); |
| 131 | $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; |
| 132 | $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = |
| 133 | strlen($plaintext); |
| 134 | $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = |
| 135 | json_encode($materialsDescription); |
| 136 | if (!empty($cipherOptions['Tag'])) { |
| 137 | $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = |
| 138 | strlen($cipherOptions['Tag']) * 8; |
| 139 | } |
| 140 | |
| 141 | return $encryptingStream; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Generates a stream that wraps the plaintext with the proper cipher and |
| 146 | * uses the content encryption key (CEK) to encrypt the data when read. |
| 147 | * |
| 148 | * @param Stream $plaintext Plain-text data to be encrypted using the |
| 149 | * materials, algorithm, and data provided. |
| 150 | * @param string $cek A content encryption key for use by the stream for |
| 151 | * encrypting the plaintext data. |
| 152 | * @param array $cipherOptions Options for use in determining the cipher to |
| 153 | * be used for encrypting data. |
| 154 | * |
| 155 | * @return [AesStreamInterface, string] |
| 156 | * |
| 157 | * @internal |
| 158 | */ |
| 159 | protected function getEncryptingStream( |
| 160 | Stream $plaintext, |
| 161 | $cek, |
| 162 | &$cipherOptions |
| 163 | ) { |
| 164 | switch ($cipherOptions['Cipher']) { |
| 165 | // Only 'gcm' is supported for encryption currently |
| 166 | case 'gcm': |
| 167 | $cipherOptions['TagLength'] = 16; |
| 168 | $encryptClass = self::$encryptClasses['gcm']; |
| 169 | $cipherTextStream = new $encryptClass( |
| 170 | $plaintext, |
| 171 | $cek, |
| 172 | $cipherOptions['Iv'], |
| 173 | $cipherOptions['Aad'] = isset($cipherOptions['Aad']) |
| 174 | ? $cipherOptions['Aad'] |
| 175 | : '', |
| 176 | $cipherOptions['TagLength'], |
| 177 | $cipherOptions['KeySize'] |
| 178 | ); |
| 179 | |
| 180 | if (!empty($cipherOptions['Aad'])) { |
| 181 | trigger_error("'Aad' has been supplied for content encryption" |
| 182 | . " with " . $cipherTextStream->getAesName() . ". The" |
| 183 | . " PHP SDK encryption client can decrypt an object" |
| 184 | . " encrypted in this way, but other AWS SDKs may not be" |
| 185 | . " able to.", E_USER_WARNING); |
| 186 | } |
| 187 | |
| 188 | $appendStream = new AppendStream([ |
| 189 | $cipherTextStream->createStream() |
| 190 | ]); |
| 191 | $cipherOptions['Tag'] = $cipherTextStream->getTag(); |
| 192 | $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); |
| 193 | return $appendStream; |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 |