S3EncryptionMultipartUploaderV2.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <?php
  2. namespace Aws\S3\Crypto;
  3. use Aws\Crypto\AbstractCryptoClientV2;
  4. use Aws\Crypto\EncryptionTraitV2;
  5. use Aws\Crypto\MetadataEnvelope;
  6. use Aws\Crypto\Cipher\CipherBuilderTrait;
  7. use Aws\S3\MultipartUploader;
  8. use Aws\S3\S3ClientInterface;
  9. use GuzzleHttp\Promise;
  10. /**
  11. * Encapsulates the execution of a multipart upload of an encrypted object to S3.
  12. *
  13. * Note that for PHP versions of < 7.1, this class uses an AES-GCM polyfill
  14. * for encryption since there is no native PHP support. The performance for large
  15. * inputs will be a lot slower than for PHP 7.1+, so upgrading older PHP version
  16. * environments may be necessary to use this effectively.
  17. */
  18. class S3EncryptionMultipartUploaderV2 extends MultipartUploader
  19. {
  20. use CipherBuilderTrait;
  21. use CryptoParamsTraitV2;
  22. use EncryptionTraitV2;
  23. use UserAgentTrait;
  24. CONST CRYPTO_VERSION = '2.1';
  25. /**
  26. * Returns if the passed cipher name is supported for encryption by the SDK.
  27. *
  28. * @param string $cipherName The name of a cipher to verify is registered.
  29. *
  30. * @return bool If the cipher passed is in our supported list.
  31. */
  32. public static function isSupportedCipher($cipherName)
  33. {
  34. return in_array($cipherName, AbstractCryptoClientV2::$supportedCiphers);
  35. }
  36. private $provider;
  37. private $instructionFileSuffix;
  38. private $strategy;
  39. /**
  40. * Creates a multipart upload for an S3 object after encrypting it.
  41. *
  42. * Note that for PHP versions of < 7.1, this class uses an AES-GCM polyfill
  43. * for encryption since there is no native PHP support. The performance for
  44. * large inputs will be a lot slower than for PHP 7.1+, so upgrading older
  45. * PHP version environments may be necessary to use this effectively.
  46. *
  47. * The required configuration options are as follows:
  48. *
  49. * - @MaterialsProvider: (MaterialsProviderV2) Provides Cek, Iv, and Cek
  50. * encrypting/decrypting for encryption metadata.
  51. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
  52. * is required. Accepts the following options:
  53. * - Cipher: (string) gcm
  54. * See also: AbstractCryptoClientV2::$supportedCiphers
  55. * - KeySize: (int) 128|256
  56. * See also: MaterialsProvider::$supportedKeySizes
  57. * - Aad: (string) Additional authentication data. This option is
  58. * passed directly to OpenSSL when using gcm.
  59. * - @KmsEncryptionContext: (array) Only required if using
  60. * KmsMaterialsProviderV2. An associative array of key-value
  61. * pairs to be added to the encryption context for KMS key encryption. An
  62. * empty array may be passed if no additional context is desired.
  63. * - bucket: (string) Name of the bucket to which the object is
  64. * being uploaded.
  65. * - key: (string) Key to use for the object being uploaded.
  66. *
  67. * The optional configuration arguments are as follows:
  68. *
  69. * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
  70. * MetadataEnvelope information. Defaults to using a
  71. * HeadersMetadataStrategy. Can either be a class implementing
  72. * MetadataStrategy, a class name of a predefined strategy, or empty/null
  73. * to default.
  74. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
  75. * instruction file if an using an InstructionFileMetadataHandler was
  76. * determined.
  77. * - acl: (string) ACL to set on the object being upload. Objects are
  78. * private by default.
  79. * - before_complete: (callable) Callback to invoke before the
  80. * `CompleteMultipartUpload` operation. The callback should have a
  81. * function signature like `function (Aws\Command $command) {...}`.
  82. * - before_initiate: (callable) Callback to invoke before the
  83. * `CreateMultipartUpload` operation. The callback should have a function
  84. * signature like `function (Aws\Command $command) {...}`.
  85. * - before_upload: (callable) Callback to invoke before any `UploadPart`
  86. * operations. The callback should have a function signature like
  87. * `function (Aws\Command $command) {...}`.
  88. * - concurrency: (int, default=int(5)) Maximum number of concurrent
  89. * `UploadPart` operations allowed during the multipart upload.
  90. * - params: (array) An array of key/value parameters that will be applied
  91. * to each of the sub-commands run by the uploader as a base.
  92. * Auto-calculated options will override these parameters. If you need
  93. * more granularity over parameters to each sub-command, use the before_*
  94. * options detailed above to update the commands directly.
  95. * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
  96. * doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
  97. * - state: (Aws\Multipart\UploadState) An object that represents the state
  98. * of the multipart upload and that is used to resume a previous upload.
  99. * When this option is provided, the `bucket`, `key`, and `part_size`
  100. * options are ignored.
  101. *
  102. * @param S3ClientInterface $client Client used for the upload.
  103. * @param mixed $source Source of the data to upload.
  104. * @param array $config Configuration used to perform the upload.
  105. */
  106. public function __construct(
  107. S3ClientInterface $client,
  108. $source,
  109. array $config = []
  110. ) {
  111. $this->appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION);
  112. $this->client = $client;
  113. $config['params'] = [];
  114. if (!empty($config['bucket'])) {
  115. $config['params']['Bucket'] = $config['bucket'];
  116. }
  117. if (!empty($config['key'])) {
  118. $config['params']['Key'] = $config['key'];
  119. }
  120. $this->provider = $this->getMaterialsProvider($config);
  121. unset($config['@MaterialsProvider']);
  122. $this->instructionFileSuffix = $this->getInstructionFileSuffix($config);
  123. unset($config['@InstructionFileSuffix']);
  124. $this->strategy = $this->getMetadataStrategy(
  125. $config,
  126. $this->instructionFileSuffix
  127. );
  128. if ($this->strategy === null) {
  129. $this->strategy = self::getDefaultStrategy();
  130. }
  131. unset($config['@MetadataStrategy']);
  132. $config['prepare_data_source'] = $this->getEncryptingDataPreparer();
  133. parent::__construct($client, $source, $config);
  134. }
  135. private static function getDefaultStrategy()
  136. {
  137. return new HeadersMetadataStrategy();
  138. }
  139. private function getEncryptingDataPreparer()
  140. {
  141. return function() {
  142. // Defer encryption work until promise is executed
  143. $envelope = new MetadataEnvelope();
  144. list($this->source, $params) = Promise\Create::promiseFor($this->encrypt(
  145. $this->source,
  146. $this->config ?: [],
  147. $this->provider,
  148. $envelope
  149. ))->then(
  150. function ($bodyStream) use ($envelope) {
  151. $params = $this->strategy->save(
  152. $envelope,
  153. $this->config['params']
  154. );
  155. return [$bodyStream, $params];
  156. }
  157. )->wait();
  158. $this->source->rewind();
  159. $this->config['params'] = $params;
  160. };
  161. }
  162. }