Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\S3\Crypto;
3
 
4
use Aws\Crypto\DecryptionTraitV2;
5
use Aws\Exception\CryptoException;
6
use Aws\HashingStream;
7
use Aws\PhpHash;
8
use Aws\Crypto\AbstractCryptoClientV2;
9
use Aws\Crypto\EncryptionTraitV2;
10
use Aws\Crypto\MetadataEnvelope;
11
use Aws\Crypto\MaterialsProvider;
12
use Aws\Crypto\Cipher\CipherBuilderTrait;
13
use Aws\S3\S3Client;
14
use GuzzleHttp\Promise;
15
use GuzzleHttp\Promise\PromiseInterface;
16
use GuzzleHttp\Psr7;
17
 
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
 
90
    const CRYPTO_VERSION = '2.1';
91
 
92
    private $client;
93
    private $instructionFileSuffix;
94
    private $legacyWarningCount;
95
 
96
    /**
97
     * @param S3Client $client The S3Client to be used for true uploading and
98
     *                         retrieving objects from S3 when using the
99
     *                         encryption client.
100
     * @param string|null $instructionFileSuffix Suffix for a client wide
101
     *                                           default when using instruction
102
     *                                           files for metadata storage.
103
     */
104
    public function __construct(
105
        S3Client $client,
106
        $instructionFileSuffix = null
107
    ) {
108
        $this->appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION);
109
        $this->client = $client;
110
        $this->instructionFileSuffix = $instructionFileSuffix;
111
        $this->legacyWarningCount = 0;
112
    }
113
 
114
    private static function getDefaultStrategy()
115
    {
116
        return new HeadersMetadataStrategy();
117
    }
118
 
119
    /**
120
     * Encrypts the data in the 'Body' field of $args and promises to upload it
121
     * to the specified location on S3.
122
     *
123
     * Note that for PHP versions of < 7.1, this operation uses an AES-GCM
124
     * polyfill for encryption since there is no native PHP support. The
125
     * performance for large inputs will be a lot slower than for PHP 7.1+, so
126
     * upgrading older PHP version environments may be necessary to use this
127
     * effectively.
128
     *
129
     * @param array $args Arguments for encrypting an object and uploading it
130
     *                    to S3 via PutObject.
131
     *
132
     * The required configuration arguments are as follows:
133
     *
134
     * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek
135
     *   encrypting/decrypting for encryption metadata.
136
     * - @CipherOptions: (array) Cipher options for encrypting data. Only the
137
     *   Cipher option is required. Accepts the following:
138
     *       - Cipher: (string) gcm
139
     *            See also: AbstractCryptoClientV2::$supportedCiphers
140
     *       - KeySize: (int) 128|256
141
     *            See also: MaterialsProvider::$supportedKeySizes
142
     *       - Aad: (string) Additional authentication data. This option is
143
     *            passed directly to OpenSSL when using gcm. Note if you pass in
144
     *            Aad, the PHP SDK will be able to decrypt the resulting object,
145
     *            but other AWS SDKs may not be able to do so.
146
     * - @KmsEncryptionContext: (array) Only required if using
147
     *   KmsMaterialsProviderV2. An associative array of key-value
148
     *   pairs to be added to the encryption context for KMS key encryption. An
149
     *   empty array may be passed if no additional context is desired.
150
     *
151
     * The optional configuration arguments are as follows:
152
     *
153
     * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
154
     *   MetadataEnvelope information. Defaults to using a
155
     *   HeadersMetadataStrategy. Can either be a class implementing
156
     *   MetadataStrategy, a class name of a predefined strategy, or empty/null
157
     *   to default.
158
     * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
159
     *   instruction file if using an InstructionFileMetadataHandler.
160
     *
161
     * @return PromiseInterface
162
     *
163
     * @throws \InvalidArgumentException Thrown when arguments above are not
164
     *                                   passed or are passed incorrectly.
165
     */
166
    public function putObjectAsync(array $args)
167
    {
168
        $provider = $this->getMaterialsProvider($args);
169
        unset($args['@MaterialsProvider']);
170
 
171
        $instructionFileSuffix = $this->getInstructionFileSuffix($args);
172
        unset($args['@InstructionFileSuffix']);
173
 
174
        $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
175
        unset($args['@MetadataStrategy']);
176
 
177
        $envelope = new MetadataEnvelope();
178
 
179
        return Promise\Create::promiseFor($this->encrypt(
180
            Psr7\Utils::streamFor($args['Body']),
181
            $args,
182
            $provider,
183
            $envelope
184
        ))->then(
185
            function ($encryptedBodyStream) use ($args) {
186
                $hash = new PhpHash('sha256');
187
                $hashingEncryptedBodyStream = new HashingStream(
188
                    $encryptedBodyStream,
189
                    $hash,
190
                    self::getContentShaDecorator($args)
191
                );
192
                return [$hashingEncryptedBodyStream, $args];
193
            }
194
        )->then(
195
            function ($putObjectContents) use ($strategy, $envelope) {
196
                list($bodyStream, $args) = $putObjectContents;
197
                if ($strategy === null) {
198
                    $strategy = self::getDefaultStrategy();
199
                }
200
 
201
                $updatedArgs = $strategy->save($envelope, $args);
202
                $updatedArgs['Body'] = $bodyStream;
203
                return $updatedArgs;
204
            }
205
        )->then(
206
            function ($args) {
207
                unset($args['@CipherOptions']);
208
                return $this->client->putObjectAsync($args);
209
            }
210
        );
211
    }
212
 
213
    private static function getContentShaDecorator(&$args)
214
    {
215
        return function ($hash) use (&$args) {
216
            $args['ContentSHA256'] = bin2hex($hash);
217
        };
218
    }
219
 
220
    /**
221
     * Encrypts the data in the 'Body' field of $args and uploads it to the
222
     * specified location on S3.
223
     *
224
     * Note that for PHP versions of < 7.1, this operation uses an AES-GCM
225
     * polyfill for encryption since there is no native PHP support. The
226
     * performance for large inputs will be a lot slower than for PHP 7.1+, so
227
     * upgrading older PHP version environments may be necessary to use this
228
     * effectively.
229
     *
230
     * @param array $args Arguments for encrypting an object and uploading it
231
     *                    to S3 via PutObject.
232
     *
233
     * The required configuration arguments are as follows:
234
     *
235
     * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
236
     *   encrypting/decrypting for encryption metadata.
237
     * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
238
     *   is required. Accepts the following options:
239
     *       - Cipher: (string) gcm
240
     *            See also: AbstractCryptoClientV2::$supportedCiphers
241
     *       - KeySize: (int) 128|256
242
     *            See also: MaterialsProvider::$supportedKeySizes
243
     *       - Aad: (string) Additional authentication data. This option is
244
     *            passed directly to OpenSSL when using gcm. Note if you pass in
245
     *            Aad, the PHP SDK will be able to decrypt the resulting object,
246
     *            but other AWS SDKs may not be able to do so.
247
     * - @KmsEncryptionContext: (array) Only required if using
248
     *   KmsMaterialsProviderV2. An associative array of key-value
249
     *   pairs to be added to the encryption context for KMS key encryption. An
250
     *   empty array may be passed if no additional context is desired.
251
     *
252
     * The optional configuration arguments are as follows:
253
     *
254
     * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
255
     *   MetadataEnvelope information. Defaults to using a
256
     *   HeadersMetadataStrategy. Can either be a class implementing
257
     *   MetadataStrategy, a class name of a predefined strategy, or empty/null
258
     *   to default.
259
     * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
260
     *   instruction file if an using an InstructionFileMetadataHandler was
261
     *   determined.
262
     *
263
     * @return \Aws\Result PutObject call result with the details of uploading
264
     *                     the encrypted file.
265
     *
266
     * @throws \InvalidArgumentException Thrown when arguments above are not
267
     *                                   passed or are passed incorrectly.
268
     */
269
    public function putObject(array $args)
270
    {
271
        return $this->putObjectAsync($args)->wait();
272
    }
273
 
274
    /**
275
     * Promises to retrieve an object from S3 and decrypt the data in the
276
     * 'Body' field.
277
     *
278
     * @param array $args Arguments for retrieving an object from S3 via
279
     *                    GetObject and decrypting it.
280
     *
281
     * The required configuration argument is as follows:
282
     *
283
     * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek
284
     *   encrypting/decrypting for decryption metadata. May have data loaded
285
     *   from the MetadataEnvelope upon decryption.
286
     * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'.
287
     *      - 'V2' indicates that only objects encrypted with S3EncryptionClientV2
288
     *        content encryption and key wrap schemas are able to be decrypted.
289
     *      - 'V2_AND_LEGACY' indicates that objects encrypted with both
290
     *        S3EncryptionClientV2 and older legacy encryption clients are able
291
     *        to be decrypted.
292
     *
293
     * The optional configuration arguments are as follows:
294
     *
295
     * - SaveAs: (string) The path to a file on disk to save the decrypted
296
     *   object data. This will be handled by file_put_contents instead of the
297
     *   Guzzle sink.
298
     *
299
     * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading
300
     *   MetadataEnvelope information. Defaults to determining based on object
301
     *   response headers. Can either be a class implementing MetadataStrategy,
302
     *   a class name of a predefined strategy, or empty/null to default.
303
     * - @InstructionFileSuffix: (string) Suffix used when looking for an
304
     *   instruction file if an InstructionFileMetadataHandler is being used.
305
     * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher
306
     *   is required. Accepts the following options:
307
     *       - Aad: (string) Additional authentication data. This option is
308
     *            passed directly to OpenSSL when using gcm. It is ignored when
309
     *            using cbc.
310
     * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with
311
     *   KMS materials for any KMS key ID, instead of needing the KMS key ID to
312
     *   be specified and provided to the decrypt operation. Ignored for non-KMS
313
     *   materials providers. Defaults to false.
314
     *
315
     * @return PromiseInterface
316
     *
317
     * @throws \InvalidArgumentException Thrown when required arguments are not
318
     *                                   passed or are passed incorrectly.
319
     */
320
    public function getObjectAsync(array $args)
321
    {
322
        $provider = $this->getMaterialsProvider($args);
323
        unset($args['@MaterialsProvider']);
324
 
325
        $instructionFileSuffix = $this->getInstructionFileSuffix($args);
326
        unset($args['@InstructionFileSuffix']);
327
 
328
        $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
329
        unset($args['@MetadataStrategy']);
330
 
331
        if (!isset($args['@SecurityProfile'])
332
            || !in_array($args['@SecurityProfile'], self::$supportedSecurityProfiles)
333
        ) {
334
            throw new CryptoException("@SecurityProfile is required and must be"
335
                . " set to 'V2' or 'V2_AND_LEGACY'");
336
        }
337
 
338
        // Only throw this legacy warning once per client
339
        if (in_array($args['@SecurityProfile'], self::$legacySecurityProfiles)
340
            && $this->legacyWarningCount < 1
341
        ) {
342
            $this->legacyWarningCount++;
343
            trigger_error(
344
                "This S3 Encryption Client operation is configured to"
345
                    . " read encrypted data with legacy encryption modes. If you"
346
                    . " don't have objects encrypted with these legacy modes,"
347
                    . " you should disable support for them to enhance security. ",
348
                E_USER_WARNING
349
            );
350
        }
351
 
352
        $saveAs = null;
353
        if (!empty($args['SaveAs'])) {
354
            $saveAs = $args['SaveAs'];
355
        }
356
 
357
        $promise = $this->client->getObjectAsync($args)
358
            ->then(
359
                function ($result) use (
360
                    $provider,
361
                    $instructionFileSuffix,
362
                    $strategy,
363
                    $args
364
                ) {
365
                    if ($strategy === null) {
366
                        $strategy = $this->determineGetObjectStrategy(
367
                            $result,
368
                            $instructionFileSuffix
369
                        );
370
                    }
371
 
372
                    $envelope = $strategy->load($args + [
373
                        'Metadata' => $result['Metadata']
374
                    ]);
375
 
376
                    $result['Body'] = $this->decrypt(
377
                        $result['Body'],
378
                        $provider,
379
                        $envelope,
380
                        $args
381
                    );
382
                    return $result;
383
                }
384
            )->then(
385
                function ($result) use ($saveAs) {
386
                    if (!empty($saveAs)) {
387
                        file_put_contents(
388
                            $saveAs,
389
                            (string)$result['Body'],
390
                            LOCK_EX
391
                        );
392
                    }
393
                    return $result;
394
                }
395
            );
396
 
397
        return $promise;
398
    }
399
 
400
    /**
401
     * Retrieves an object from S3 and decrypts the data in the 'Body' field.
402
     *
403
     * @param array $args Arguments for retrieving an object from S3 via
404
     *                    GetObject and decrypting it.
405
     *
406
     * The required configuration argument is as follows:
407
     *
408
     * - @MaterialsProvider: (MaterialsProviderInterface) Provides Cek, Iv, and Cek
409
     *   encrypting/decrypting for decryption metadata. May have data loaded
410
     *   from the MetadataEnvelope upon decryption.
411
     * - @SecurityProfile: (string) Must be set to 'V2' or 'V2_AND_LEGACY'.
412
     *      - 'V2' indicates that only objects encrypted with S3EncryptionClientV2
413
     *        content encryption and key wrap schemas are able to be decrypted.
414
     *      - 'V2_AND_LEGACY' indicates that objects encrypted with both
415
     *        S3EncryptionClientV2 and older legacy encryption clients are able
416
     *        to be decrypted.
417
     *
418
     * The optional configuration arguments are as follows:
419
     *
420
     * - SaveAs: (string) The path to a file on disk to save the decrypted
421
     *   object data. This will be handled by file_put_contents instead of the
422
     *   Guzzle sink.
423
     * - @InstructionFileSuffix: (string|null) Suffix used when looking for an
424
     *   instruction file if an InstructionFileMetadataHandler was detected.
425
     * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
426
     *   is required. Accepts the following options:
427
     *       - Aad: (string) Additional authentication data. This option is
428
     *            passed directly to OpenSSL when using gcm. It is ignored when
429
     *            using cbc.
430
     * - @KmsAllowDecryptWithAnyCmk: (bool) This allows decryption with
431
     *   KMS materials for any KMS key ID, instead of needing the KMS key ID to
432
     *   be specified and provided to the decrypt operation. Ignored for non-KMS
433
     *   materials providers. Defaults to false.
434
     *
435
     * @return \Aws\Result GetObject call result with the 'Body' field
436
     *                     wrapped in a decryption stream with its metadata
437
     *                     information.
438
     *
439
     * @throws \InvalidArgumentException Thrown when arguments above are not
440
     *                                   passed or are passed incorrectly.
441
     */
442
    public function getObject(array $args)
443
    {
444
        return $this->getObjectAsync($args)->wait();
445
    }
446
}