1<?php 2namespace Aws\Crypto; 3 4use GuzzleHttp\Psr7; 5use GuzzleHttp\Psr7\LimitStream; 6use Psr\Http\Message\StreamInterface; 7 8trait DecryptionTrait 9{ 10 /** 11 * Dependency to reverse lookup the openssl_* cipher name from the AESName 12 * in the MetadataEnvelope. 13 * 14 * @param $aesName 15 * 16 * @return string 17 * 18 * @internal 19 */ 20 abstract protected function getCipherFromAesName($aesName); 21 22 /** 23 * Dependency to generate a CipherMethod from a set of inputs for loading 24 * in to an AesDecryptingStream. 25 * 26 * @param string $cipherName Name of the cipher to generate for decrypting. 27 * @param string $iv Base Initialization Vector for the cipher. 28 * @param int $keySize Size of the encryption key, in bits, that will be 29 * used. 30 * 31 * @return Cipher\CipherMethod 32 * 33 * @internal 34 */ 35 abstract protected function buildCipherMethod($cipherName, $iv, $keySize); 36 37 /** 38 * Builds an AesStreamInterface using cipher options loaded from the 39 * MetadataEnvelope and MaterialsProvider. Can decrypt data from both the 40 * legacy and V2 encryption client workflows. 41 * 42 * @param string $cipherText Plain-text data to be encrypted using the 43 * materials, algorithm, and data provided. 44 * @param MaterialsProviderInterface $provider A provider to supply and encrypt 45 * materials used in encryption. 46 * @param MetadataEnvelope $envelope A storage envelope for encryption 47 * metadata to be read from. 48 * @param array $cipherOptions Additional verification options. 49 * 50 * @return AesStreamInterface 51 * 52 * @throws \InvalidArgumentException Thrown when a value in $cipherOptions 53 * is not valid. 54 * 55 * @internal 56 */ 57 public function decrypt( 58 $cipherText, 59 MaterialsProviderInterface $provider, 60 MetadataEnvelope $envelope, 61 array $cipherOptions = [] 62 ) { 63 $cipherOptions['Iv'] = base64_decode( 64 $envelope[MetadataEnvelope::IV_HEADER] 65 ); 66 67 $cipherOptions['TagLength'] = 68 $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] / 8; 69 70 $cek = $provider->decryptCek( 71 base64_decode( 72 $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] 73 ), 74 json_decode( 75 $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], 76 true 77 ) 78 ); 79 $cipherOptions['KeySize'] = strlen($cek) * 8; 80 $cipherOptions['Cipher'] = $this->getCipherFromAesName( 81 $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] 82 ); 83 84 $decryptionStream = $this->getDecryptingStream( 85 $cipherText, 86 $cek, 87 $cipherOptions 88 ); 89 unset($cek); 90 91 return $decryptionStream; 92 } 93 94 private function getTagFromCiphertextStream( 95 StreamInterface $cipherText, 96 $tagLength 97 ) { 98 $cipherTextSize = $cipherText->getSize(); 99 if ($cipherTextSize == null || $cipherTextSize <= 0) { 100 throw new \RuntimeException('Cannot decrypt a stream of unknown' 101 . ' size.'); 102 } 103 return (string) new LimitStream( 104 $cipherText, 105 $tagLength, 106 $cipherTextSize - $tagLength 107 ); 108 } 109 110 private function getStrippedCiphertextStream( 111 StreamInterface $cipherText, 112 $tagLength 113 ) { 114 $cipherTextSize = $cipherText->getSize(); 115 if ($cipherTextSize == null || $cipherTextSize <= 0) { 116 throw new \RuntimeException('Cannot decrypt a stream of unknown' 117 . ' size.'); 118 } 119 return new LimitStream( 120 $cipherText, 121 $cipherTextSize - $tagLength, 122 0 123 ); 124 } 125 126 /** 127 * Generates a stream that wraps the cipher text with the proper cipher and 128 * uses the content encryption key (CEK) to decrypt the data when read. 129 * 130 * @param string $cipherText Plain-text data to be encrypted using the 131 * materials, algorithm, and data provided. 132 * @param string $cek A content encryption key for use by the stream for 133 * encrypting the plaintext data. 134 * @param array $cipherOptions Options for use in determining the cipher to 135 * be used for encrypting data. 136 * 137 * @return AesStreamInterface 138 * 139 * @internal 140 */ 141 protected function getDecryptingStream( 142 $cipherText, 143 $cek, 144 $cipherOptions 145 ) { 146 $cipherTextStream = Psr7\Utils::streamFor($cipherText); 147 switch ($cipherOptions['Cipher']) { 148 case 'gcm': 149 $cipherOptions['Tag'] = $this->getTagFromCiphertextStream( 150 $cipherTextStream, 151 $cipherOptions['TagLength'] 152 ); 153 154 return new AesGcmDecryptingStream( 155 $this->getStrippedCiphertextStream( 156 $cipherTextStream, 157 $cipherOptions['TagLength'] 158 ), 159 $cek, 160 $cipherOptions['Iv'], 161 $cipherOptions['Tag'], 162 $cipherOptions['Aad'] = isset($cipherOptions['Aad']) 163 ? $cipherOptions['Aad'] 164 : null, 165 $cipherOptions['TagLength'] ?: null, 166 $cipherOptions['KeySize'] 167 ); 168 default: 169 $cipherMethod = $this->buildCipherMethod( 170 $cipherOptions['Cipher'], 171 $cipherOptions['Iv'], 172 $cipherOptions['KeySize'] 173 ); 174 return new AesDecryptingStream( 175 $cipherTextStream, 176 $cek, 177 $cipherMethod 178 ); 179 } 180 } 181} 182