S3EncryptionClient.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <?php
  2. namespace Aws\S3\Crypto;
  3. use Aws\Crypto\DecryptionTrait;
  4. use Aws\HashingStream;
  5. use Aws\PhpHash;
  6. use Aws\Crypto\AbstractCryptoClient;
  7. use Aws\Crypto\EncryptionTrait;
  8. use Aws\Crypto\MetadataEnvelope;
  9. use Aws\Crypto\MaterialsProvider;
  10. use Aws\Crypto\Cipher\CipherBuilderTrait;
  11. use Aws\S3\S3Client;
  12. use GuzzleHttp\Promise;
  13. use GuzzleHttp\Promise\PromiseInterface;
  14. use GuzzleHttp\Psr7;
  15. /**
  16. * Provides a wrapper for an S3Client that supplies functionality to encrypt
  17. * data on putObject[Async] calls and decrypt data on getObject[Async] calls.
  18. *
  19. * Legacy implementation using older encryption workflow.
  20. *
  21. * AWS strongly recommends the upgrade to the S3EncryptionClientV2 (over the
  22. * S3EncryptionClient), as it offers updated data security best practices to our
  23. * customers who upgrade. S3EncryptionClientV2 contains breaking changes, so this
  24. * will require planning by engineering teams to migrate. New workflows should
  25. * just start with S3EncryptionClientV2.
  26. *
  27. * @deprecated
  28. */
  29. class S3EncryptionClient extends AbstractCryptoClient
  30. {
  31. use CipherBuilderTrait;
  32. use CryptoParamsTrait;
  33. use DecryptionTrait;
  34. use EncryptionTrait;
  35. use UserAgentTrait;
  36. const CRYPTO_VERSION = '1n';
  37. private $client;
  38. private $instructionFileSuffix;
  39. /**
  40. * @param S3Client $client The S3Client to be used for true uploading and
  41. * retrieving objects from S3 when using the
  42. * encryption client.
  43. * @param string|null $instructionFileSuffix Suffix for a client wide
  44. * default when using instruction
  45. * files for metadata storage.
  46. */
  47. public function __construct(
  48. S3Client $client,
  49. $instructionFileSuffix = null
  50. ) {
  51. $this->appendUserAgent($client, 'feat/s3-encrypt/' . self::CRYPTO_VERSION);
  52. $this->client = $client;
  53. $this->instructionFileSuffix = $instructionFileSuffix;
  54. }
  55. private static function getDefaultStrategy()
  56. {
  57. return new HeadersMetadataStrategy();
  58. }
  59. /**
  60. * Encrypts the data in the 'Body' field of $args and promises to upload it
  61. * to the specified location on S3.
  62. *
  63. * @param array $args Arguments for encrypting an object and uploading it
  64. * to S3 via PutObject.
  65. *
  66. * The required configuration arguments are as follows:
  67. *
  68. * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
  69. * encrypting/decrypting for encryption metadata.
  70. * - @CipherOptions: (array) Cipher options for encrypting data. Only the
  71. * Cipher option is required. Accepts the following:
  72. * - Cipher: (string) cbc|gcm
  73. * See also: AbstractCryptoClient::$supportedCiphers. Note that
  74. * cbc is deprecated and gcm should be used when possible.
  75. * - KeySize: (int) 128|192|256
  76. * See also: MaterialsProvider::$supportedKeySizes
  77. * - Aad: (string) Additional authentication data. This option is
  78. * passed directly to OpenSSL when using gcm. It is ignored when
  79. * using cbc. Note if you pass in Aad for gcm encryption, the
  80. * PHP SDK will be able to decrypt the resulting object, but other
  81. * AWS SDKs may not be able to do so.
  82. *
  83. * The optional configuration arguments are as follows:
  84. *
  85. * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
  86. * MetadataEnvelope information. Defaults to using a
  87. * HeadersMetadataStrategy. Can either be a class implementing
  88. * MetadataStrategy, a class name of a predefined strategy, or empty/null
  89. * to default.
  90. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
  91. * instruction file if using an InstructionFileMetadataHandler.
  92. *
  93. * @return PromiseInterface
  94. *
  95. * @throws \InvalidArgumentException Thrown when arguments above are not
  96. * passed or are passed incorrectly.
  97. */
  98. public function putObjectAsync(array $args)
  99. {
  100. $provider = $this->getMaterialsProvider($args);
  101. unset($args['@MaterialsProvider']);
  102. $instructionFileSuffix = $this->getInstructionFileSuffix($args);
  103. unset($args['@InstructionFileSuffix']);
  104. $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
  105. unset($args['@MetadataStrategy']);
  106. $envelope = new MetadataEnvelope();
  107. return Promise\Create::promiseFor($this->encrypt(
  108. Psr7\Utils::streamFor($args['Body']),
  109. $args['@CipherOptions'] ?: [],
  110. $provider,
  111. $envelope
  112. ))->then(
  113. function ($encryptedBodyStream) use ($args) {
  114. $hash = new PhpHash('sha256');
  115. $hashingEncryptedBodyStream = new HashingStream(
  116. $encryptedBodyStream,
  117. $hash,
  118. self::getContentShaDecorator($args)
  119. );
  120. return [$hashingEncryptedBodyStream, $args];
  121. }
  122. )->then(
  123. function ($putObjectContents) use ($strategy, $envelope) {
  124. list($bodyStream, $args) = $putObjectContents;
  125. if ($strategy === null) {
  126. $strategy = self::getDefaultStrategy();
  127. }
  128. $updatedArgs = $strategy->save($envelope, $args);
  129. $updatedArgs['Body'] = $bodyStream;
  130. return $updatedArgs;
  131. }
  132. )->then(
  133. function ($args) {
  134. unset($args['@CipherOptions']);
  135. return $this->client->putObjectAsync($args);
  136. }
  137. );
  138. }
  139. private static function getContentShaDecorator(&$args)
  140. {
  141. return function ($hash) use (&$args) {
  142. $args['ContentSHA256'] = bin2hex($hash);
  143. };
  144. }
  145. /**
  146. * Encrypts the data in the 'Body' field of $args and uploads it to the
  147. * specified location on S3.
  148. *
  149. * @param array $args Arguments for encrypting an object and uploading it
  150. * to S3 via PutObject.
  151. *
  152. * The required configuration arguments are as follows:
  153. *
  154. * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
  155. * encrypting/decrypting for encryption metadata.
  156. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
  157. * is required. Accepts the following options:
  158. * - Cipher: (string) cbc|gcm
  159. * See also: AbstractCryptoClient::$supportedCiphers. Note that
  160. * cbc is deprecated and gcm should be used when possible.
  161. * - KeySize: (int) 128|192|256
  162. * See also: MaterialsProvider::$supportedKeySizes
  163. * - Aad: (string) Additional authentication data. This option is
  164. * passed directly to OpenSSL when using gcm. It is ignored when
  165. * using cbc. Note if you pass in Aad for gcm encryption, the
  166. * PHP SDK will be able to decrypt the resulting object, but other
  167. * AWS SDKs may not be able to do so.
  168. *
  169. * The optional configuration arguments are as follows:
  170. *
  171. * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
  172. * MetadataEnvelope information. Defaults to using a
  173. * HeadersMetadataStrategy. Can either be a class implementing
  174. * MetadataStrategy, a class name of a predefined strategy, or empty/null
  175. * to default.
  176. * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
  177. * instruction file if an using an InstructionFileMetadataHandler was
  178. * determined.
  179. *
  180. * @return \Aws\Result PutObject call result with the details of uploading
  181. * the encrypted file.
  182. *
  183. * @throws \InvalidArgumentException Thrown when arguments above are not
  184. * passed or are passed incorrectly.
  185. */
  186. public function putObject(array $args)
  187. {
  188. return $this->putObjectAsync($args)->wait();
  189. }
  190. /**
  191. * Promises to retrieve an object from S3 and decrypt the data in the
  192. * 'Body' field.
  193. *
  194. * @param array $args Arguments for retrieving an object from S3 via
  195. * GetObject and decrypting it.
  196. *
  197. * The required configuration argument is as follows:
  198. *
  199. * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
  200. * encrypting/decrypting for decryption metadata. May have data loaded
  201. * from the MetadataEnvelope upon decryption.
  202. *
  203. * The optional configuration arguments are as follows:
  204. *
  205. * - SaveAs: (string) The path to a file on disk to save the decrypted
  206. * object data. This will be handled by file_put_contents instead of the
  207. * Guzzle sink.
  208. *
  209. * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading
  210. * MetadataEnvelope information. Defaults to determining based on object
  211. * response headers. Can either be a class implementing MetadataStrategy,
  212. * a class name of a predefined strategy, or empty/null to default.
  213. * - @InstructionFileSuffix: (string) Suffix used when looking for an
  214. * instruction file if an InstructionFileMetadataHandler is being used.
  215. * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher
  216. * is required. Accepts the following options:
  217. * - Aad: (string) Additional authentication data. This option is
  218. * passed directly to OpenSSL when using gcm. It is ignored when
  219. * using cbc.
  220. *
  221. * @return PromiseInterface
  222. *
  223. * @throws \InvalidArgumentException Thrown when required arguments are not
  224. * passed or are passed incorrectly.
  225. */
  226. public function getObjectAsync(array $args)
  227. {
  228. $provider = $this->getMaterialsProvider($args);
  229. unset($args['@MaterialsProvider']);
  230. $instructionFileSuffix = $this->getInstructionFileSuffix($args);
  231. unset($args['@InstructionFileSuffix']);
  232. $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
  233. unset($args['@MetadataStrategy']);
  234. $saveAs = null;
  235. if (!empty($args['SaveAs'])) {
  236. $saveAs = $args['SaveAs'];
  237. }
  238. $promise = $this->client->getObjectAsync($args)
  239. ->then(
  240. function ($result) use (
  241. $provider,
  242. $instructionFileSuffix,
  243. $strategy,
  244. $args
  245. ) {
  246. if ($strategy === null) {
  247. $strategy = $this->determineGetObjectStrategy(
  248. $result,
  249. $instructionFileSuffix
  250. );
  251. }
  252. $envelope = $strategy->load($args + [
  253. 'Metadata' => $result['Metadata']
  254. ]);
  255. $provider = $provider->fromDecryptionEnvelope($envelope);
  256. $result['Body'] = $this->decrypt(
  257. $result['Body'],
  258. $provider,
  259. $envelope,
  260. isset($args['@CipherOptions'])
  261. ? $args['@CipherOptions']
  262. : []
  263. );
  264. return $result;
  265. }
  266. )->then(
  267. function ($result) use ($saveAs) {
  268. if (!empty($saveAs)) {
  269. file_put_contents(
  270. $saveAs,
  271. (string)$result['Body'],
  272. LOCK_EX
  273. );
  274. }
  275. return $result;
  276. }
  277. );
  278. return $promise;
  279. }
  280. /**
  281. * Retrieves an object from S3 and decrypts the data in the 'Body' field.
  282. *
  283. * @param array $args Arguments for retrieving an object from S3 via
  284. * GetObject and decrypting it.
  285. *
  286. * The required configuration argument is as follows:
  287. *
  288. * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
  289. * encrypting/decrypting for decryption metadata. May have data loaded
  290. * from the MetadataEnvelope upon decryption.
  291. *
  292. * The optional configuration arguments are as follows:
  293. *
  294. * - SaveAs: (string) The path to a file on disk to save the decrypted
  295. * object data. This will be handled by file_put_contents instead of the
  296. * Guzzle sink.
  297. * - @InstructionFileSuffix: (string|null) Suffix used when looking for an
  298. * instruction file if an InstructionFileMetadataHandler was detected.
  299. * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
  300. * is required. Accepts the following options:
  301. * - Aad: (string) Additional authentication data. This option is
  302. * passed directly to OpenSSL when using gcm. It is ignored when
  303. * using cbc.
  304. *
  305. * @return \Aws\Result GetObject call result with the 'Body' field
  306. * wrapped in a decryption stream with its metadata
  307. * information.
  308. *
  309. * @throws \InvalidArgumentException Thrown when arguments above are not
  310. * passed or are passed incorrectly.
  311. */
  312. public function getObject(array $args)
  313. {
  314. return $this->getObjectAsync($args)->wait();
  315. }
  316. }