EncryptionTraitV2.php 7.1 KB

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