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