PluginProbe
Media Cloud Sync / 1.0.0
Media Cloud Sync v1.0.0
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
media-cloud-sync / includes / sdk / s3 / Aws / S3 / Crypto / S3EncryptionClientV2.php

S3EncryptionClientV2.php in Media Cloud Sync 1.0.0, at includes/sdk/s3/Aws/S3/Crypto/S3EncryptionClientV2.php

372 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\Aws\S3\Crypto;
4
5 use Dudlewebs\WPMCS\s3\Aws\Crypto\DecryptionTraitV2;
6 use Dudlewebs\WPMCS\s3\Aws\Exception\CryptoException;
7 use Dudlewebs\WPMCS\s3\Aws\HashingStream;
8 use Dudlewebs\WPMCS\s3\Aws\PhpHash;
9 use Dudlewebs\WPMCS\s3\Aws\Crypto\AbstractCryptoClientV2;
10 use Dudlewebs\WPMCS\s3\Aws\Crypto\EncryptionTraitV2;
11 use Dudlewebs\WPMCS\s3\Aws\Crypto\MetadataEnvelope;
12 use Dudlewebs\WPMCS\s3\Aws\Crypto\MaterialsProvider;
13 use Dudlewebs\WPMCS\s3\Aws\Crypto\Cipher\CipherBuilderTrait;
14 use Dudlewebs\WPMCS\s3\Aws\S3\S3Client;
15 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise;
16 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
17 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7;
18 /**
19 * Provides a wrapper for an S3Client that supplies functionality to encrypt
20 * data on putObject[Async] calls and decrypt data on getObject[Async] calls.
21 *
22 * AWS strongly recommends the upgrade to the S3EncryptionClientV2 (over the
23 * S3EncryptionClient), as it offers updated data security best practices to our
24 * customers who upgrade. S3EncryptionClientV2 contains breaking changes, so this
25 * will require planning by engineering teams to migrate. New workflows should
26 * just start with S3EncryptionClientV2.
27 *
28 * Note that for PHP versions of < 7.1, this class uses an AES-GCM polyfill
29 * for encryption since there is no native PHP support. The performance for large
30 * inputs will be a lot slower than for PHP 7.1+, so upgrading older PHP version
31 * environments may be necessary to use this effectively.
32 *
33 * Example write path:
34 *
35 * <code>
36 * use Aws\Crypto\KmsMaterialsProviderV2;
37 * use Aws\S3\Crypto\S3EncryptionClientV2;
38 * use Aws\S3\S3Client;
39 *
40 * $encryptionClient = new S3EncryptionClientV2(
41 * new S3Client([
42 * 'region' => 'us-west-2',
43 * 'version' => 'latest'
44 * ])
45 * );
46 * $materialsProvider = new KmsMaterialsProviderV2(
47 * new KmsClient([
48 * 'profile' => 'default',
49 * 'region' => 'us-east-1',
50 * 'version' => 'latest',
51 * ],
52 * 'your-kms-key-id'
53 * );
54 *
55 * $encryptionClient->putObject([
56 * '@MaterialsProvider' => $materialsProvider,
57 * '@CipherOptions' => [
58 * 'Cipher' => 'gcm',
59 * 'KeySize' => 256,
60 * ],
61 * '@KmsEncryptionContext' => ['foo' => 'bar'],
62 * 'Bucket' => 'your-bucket',
63 * 'Key' => 'your-key',
64 * 'Body' => 'your-encrypted-data',
65 * ]);
66 * </code>
67 *
68 * Example read call (using objects from previous example):
69 *
70 * <code>
71 * $encryptionClient->getObject([
72 * '@MaterialsProvider' => $materialsProvider,
73 * '@CipherOptions' => [
74 * 'Cipher' => 'gcm',
75 * 'KeySize' => 256,
76 * ],
77 * 'Bucket' => 'your-bucket',
78 * 'Key' => 'your-key',
79 * ]);
80 * </code>
81 */
82 class S3EncryptionClientV2 extends AbstractCryptoClientV2
83 {
84 use CipherBuilderTrait;
85 use CryptoParamsTraitV2;
86 use DecryptionTraitV2;
87 use EncryptionTraitV2;
88 use UserAgentTrait;
89 const CRYPTO_VERSION = '2.1';
90 private $client;
91 private $instructionFileSuffix;
92 private $legacyWarningCount;
93 /**
94 * @param S3Client $client The S3Client to be used for true uploading and
95 * retrieving objects from S3 when using the
96 * encryption client.
97 * @param string|null $instructionFileSuffix Suffix for a client wide
98 * default when using instruction
99 * files for metadata storage.
100 */
101 public function __construct(S3Client $client, $instructionFileSuffix = null)
102 {
103 $this->appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION);
104 $this->client = $client;
105 $this->instructionFileSuffix = $instructionFileSuffix;
106 $this->legacyWarningCount = 0;
107 }
108 private static function getDefaultStrategy()
109 {
110 return new HeadersMetadataStrategy();
111 }
112 /**
113 * Encrypts the data in the 'Body' field of $args and promises to upload it
114 * to the specified location on S3.
115 *
116 * Note that for PHP versions of < 7.1, this operation uses an AES-GCM
117 * polyfill for encryption since there is no native PHP support. The
118 * performance for large inputs will be a lot slower than for PHP 7.1+, so
119 * upgrading older PHP version environments may be necessary to use this
120 * effectively.
121 *
122 * @param array $args Arguments for encrypting an object and uploading it
123 * to S3 via PutObject.
124 *
125 * The required configuration arguments are as follows:
126 *
127 * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek
128 * encrypting/decrypting for encryption metadata.
129 * - @CipherOptions: (array) Cipher options for encrypting data. Only the
130 * Cipher option is required. Accepts the following:
131 * - Cipher: (string) gcm
132 * See also: AbstractCryptoClientV2::$supportedCiphers
133 * - KeySize: (int) 128|256
134 * See also: MaterialsProvider::$supportedKeySizes
135 * - Aad: (string) Additional authentication data. This option is
136 * passed directly to OpenSSL when using gcm. Note if you pass in
137 * Aad, the PHP SDK will be able to decrypt the resulting object,
138 * but other AWS SDKs may not be able to do so.
139 * - @KmsEncryptionContext: (array) Only required if using
140 * KmsMaterialsProviderV2. An associative array of key-value
141 * pairs to be added to the encryption context for KMS key encryption. An
142 * empty array may be passed if no additional context is desired.
143 *
144 * The optional configuration arguments are as follows:
145 *
146 * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
147 * MetadataEnvelope information. Defaults to using a
148 * HeadersMetadataStrategy. Can either be a class implementing
149 * MetadataStrategy, a class name of a predefined strategy, or empty/null
150 * to default.
151 * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
152 * instruction file if using an InstructionFileMetadataHandler.
153 *
154 * @return PromiseInterface
155 *
156 * @throws \InvalidArgumentException Thrown when arguments above are not
157 * passed or are passed incorrectly.
158 */
159 public function putObjectAsync(array $args)
160 {
161 $provider = $this->getMaterialsProvider($args);
162 unset($args['@MaterialsProvider']);
163 $instructionFileSuffix = $this->getInstructionFileSuffix($args);
164 unset($args['@InstructionFileSuffix']);
165 $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
166 unset($args['@MetadataStrategy']);
167 $envelope = new MetadataEnvelope();
168 return Promise\Create::promiseFor($this->encrypt(Psr7\Utils::streamFor($args['Body']), $args, $provider, $envelope))->then(function ($encryptedBodyStream) use($args) {
169 $hash = new PhpHash('sha256');
170 $hashingEncryptedBodyStream = new HashingStream($encryptedBodyStream, $hash, self::getContentShaDecorator($args));
171 return [$hashingEncryptedBodyStream, $args];
172 })->then(function ($putObjectContents) use($strategy, $envelope) {
173 list($bodyStream, $args) = $putObjectContents;
174 if ($strategy === null) {
175 $strategy = self::getDefaultStrategy();
176 }
177 $updatedArgs = $strategy->save($envelope, $args);
178 $updatedArgs['Body'] = $bodyStream;
179 return $updatedArgs;
180 })->then(function ($args) {
181 unset($args['@CipherOptions']);
182 return $this->client->putObjectAsync($args);
183 });
184 }
185 private static function getContentShaDecorator(&$args)
186 {
187 return function ($hash) use(&$args) {
188 $args['ContentSHA256'] = \bin2hex($hash);
189 };
190 }
191 /**
192 * Encrypts the data in the 'Body' field of $args and uploads it to the
193 * specified location on S3.
194 *
195 * Note that for PHP versions of < 7.1, this operation uses an AES-GCM
196 * polyfill for encryption since there is no native PHP support. The
197 * performance for large inputs will be a lot slower than for PHP 7.1+, so
198 * upgrading older PHP version environments may be necessary to use this
199 * effectively.
200 *
201 * @param array $args Arguments for encrypting an object and uploading it
202 * to S3 via PutObject.
203 *
204 * The required configuration arguments are as follows:
205 *
206 * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
207 * encrypting/decrypting for encryption metadata.
208 * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
209 * is required. Accepts the following options:
210 * - Cipher: (string) gcm
211 * See also: AbstractCryptoClientV2::$supportedCiphers
212 * - KeySize: (int) 128|256
213 * See also: MaterialsProvider::$supportedKeySizes
214 * - Aad: (string) Additional authentication data. This option is
215 * passed directly to OpenSSL when using gcm. Note if you pass in
216 * Aad, the PHP SDK will be able to decrypt the resulting object,
217 * but other AWS SDKs may not be able to do so.
218 * - @KmsEncryptionContext: (array) Only required if using
219 * KmsMaterialsProviderV2. An associative array of key-value
220 * pairs to be added to the encryption context for KMS key encryption. An
221 * empty array may be passed if no additional context is desired.
222 *
223 * The optional configuration arguments are as follows:
224 *
225 * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
226 * MetadataEnvelope information. Defaults to using a
227 * HeadersMetadataStrategy. Can either be a class implementing
228 * MetadataStrategy, a class name of a predefined strategy, or empty/null
229 * to default.
230 * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
231 * instruction file if an using an InstructionFileMetadataHandler was
232 * determined.
233 *
234 * @return \Aws\Result PutObject call result with the details of uploading
235 * the encrypted file.
236 *
237 * @throws \InvalidArgumentException Thrown when arguments above are not
238 * passed or are passed incorrectly.
239 */
240 public function putObject(array $args)
241 {
242 return $this->putObjectAsync($args)->wait();
243 }
244 /**
245 * Promises to retrieve an object from S3 and decrypt the data in the
246 * 'Body' field.
247 *
248 * @param array $args Arguments for retrieving an object from S3 via
249 * GetObject and decrypting it.
250 *
251 * The required configuration argument is as follows:
252 *
253 * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek
254 * encrypting/decrypting for decryption metadata. May have data loaded
255 * from the MetadataEnvelope upon decryption.
256 * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'.
257 * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2
258 * content encryption and key wrap schemas are able to be decrypted.
259 * - 'V2_AND_LEGACY' indicates that objects encrypted with both
260 * S3EncryptionClientV2 and older legacy encryption clients are able
261 * to be decrypted.
262 *
263 * The optional configuration arguments are as follows:
264 *
265 * - SaveAs: (string) The path to a file on disk to save the decrypted
266 * object data. This will be handled by file_put_contents instead of the
267 * Guzzle sink.
268 *
269 * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading
270 * MetadataEnvelope information. Defaults to determining based on object
271 * response headers. Can either be a class implementing MetadataStrategy,
272 * a class name of a predefined strategy, or empty/null to default.
273 * - @InstructionFileSuffix: (string) Suffix used when looking for an
274 * instruction file if an InstructionFileMetadataHandler is being used.
275 * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher
276 * is required. Accepts the following options:
277 * - Aad: (string) Additional authentication data. This option is
278 * passed directly to OpenSSL when using gcm. It is ignored when
279 * using cbc.
280 * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with
281 * KMS materials for any KMS key ID, instead of needing the KMS key ID to
282 * be specified and provided to the decrypt operation. Ignored for non-KMS
283 * materials providers. Defaults to false.
284 *
285 * @return PromiseInterface
286 *
287 * @throws \InvalidArgumentException Thrown when required arguments are not
288 * passed or are passed incorrectly.
289 */
290 public function getObjectAsync(array $args)
291 {
292 $provider = $this->getMaterialsProvider($args);
293 unset($args['@MaterialsProvider']);
294 $instructionFileSuffix = $this->getInstructionFileSuffix($args);
295 unset($args['@InstructionFileSuffix']);
296 $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
297 unset($args['@MetadataStrategy']);
298 if (!isset($args['@SecurityProfile']) || !\in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles)) {
299 throw new CryptoException("@SecurityProfile is required and must be" . " set to 'V2' or 'V2_AND_LEGACY'");
300 }
301 // Only throw this legacy warning once per client
302 if (\in_array($args['@SecurityProfile'], self::$legacySecurityProfiles) && $this->legacyWarningCount < 1) {
303 $this->legacyWarningCount++;
304 \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);
305 }
306 $saveAs = null;
307 if (!empty($args['SaveAs'])) {
308 $saveAs = $args['SaveAs'];
309 }
310 $promise = $this->client->getObjectAsync($args)->then(function ($result) use($provider, $instructionFileSuffix, $strategy, $args) {
311 if ($strategy === null) {
312 $strategy = $this->determineGetObjectStrategy($result, $instructionFileSuffix);
313 }
314 $envelope = $strategy->load($args + ['Metadata' => $result['Metadata']]);
315 $result['Body'] = $this->decrypt($result['Body'], $provider, $envelope, $args);
316 return $result;
317 })->then(function ($result) use($saveAs) {
318 if (!empty($saveAs)) {
319 \file_put_contents($saveAs, (string) $result['Body'], \LOCK_EX);
320 }
321 return $result;
322 });
323 return $promise;
324 }
325 /**
326 * Retrieves an object from S3 and decrypts the data in the 'Body' field.
327 *
328 * @param array $args Arguments for retrieving an object from S3 via
329 * GetObject and decrypting it.
330 *
331 * The required configuration argument is as follows:
332 *
333 * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek
334 * encrypting/decrypting for decryption metadata. May have data loaded
335 * from the MetadataEnvelope upon decryption.
336 * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'.
337 * - 'V2' indicates that only objects encrypted with S3EncryptionClientV2
338 * content encryption and key wrap schemas are able to be decrypted.
339 * - 'V2_AND_LEGACY' indicates that objects encrypted with both
340 * S3EncryptionClientV2 and older legacy encryption clients are able
341 * to be decrypted.
342 *
343 * The optional configuration arguments are as follows:
344 *
345 * - SaveAs: (string) The path to a file on disk to save the decrypted
346 * object data. This will be handled by file_put_contents instead of the
347 * Guzzle sink.
348 * - @InstructionFileSuffix: (string|null) Suffix used when looking for an
349 * instruction file if an InstructionFileMetadataHandler was detected.
350 * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
351 * is required. Accepts the following options:
352 * - Aad: (string) Additional authentication data. This option is
353 * passed directly to OpenSSL when using gcm. It is ignored when
354 * using cbc.
355 * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with
356 * KMS materials for any KMS key ID, instead of needing the KMS key ID to
357 * be specified and provided to the decrypt operation. Ignored for non-KMS
358 * materials providers. Defaults to false.
359 *
360 * @return \Aws\Result GetObject call result with the 'Body' field
361 * wrapped in a decryption stream with its metadata
362 * information.
363 *
364 * @throws \InvalidArgumentException Thrown when arguments above are not
365 * passed or are passed incorrectly.
366 */
367 public function getObject(array $args)
368 {
369 return $this->getObjectAsync($args)->wait();
370 }
371 }
372