1 |
efrain |
1 |
<?php
|
|
|
2 |
namespace Aws\Crypto;
|
|
|
3 |
|
|
|
4 |
use Aws\Exception\CryptoException;
|
|
|
5 |
use GuzzleHttp\Psr7;
|
|
|
6 |
use GuzzleHttp\Psr7\LimitStream;
|
|
|
7 |
use Psr\Http\Message\StreamInterface;
|
|
|
8 |
|
|
|
9 |
trait DecryptionTraitV2
|
|
|
10 |
{
|
|
|
11 |
/**
|
|
|
12 |
* Dependency to reverse lookup the openssl_* cipher name from the AESName
|
|
|
13 |
* in the MetadataEnvelope.
|
|
|
14 |
*
|
|
|
15 |
* @param $aesName
|
|
|
16 |
*
|
|
|
17 |
* @return string
|
|
|
18 |
*
|
|
|
19 |
* @internal
|
|
|
20 |
*/
|
|
|
21 |
abstract protected function getCipherFromAesName($aesName);
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
* Dependency to generate a CipherMethod from a set of inputs for loading
|
|
|
25 |
* in to an AesDecryptingStream.
|
|
|
26 |
*
|
|
|
27 |
* @param string $cipherName Name of the cipher to generate for decrypting.
|
|
|
28 |
* @param string $iv Base Initialization Vector for the cipher.
|
|
|
29 |
* @param int $keySize Size of the encryption key, in bits, that will be
|
|
|
30 |
* used.
|
|
|
31 |
*
|
|
|
32 |
* @return Cipher\CipherMethod
|
|
|
33 |
*
|
|
|
34 |
* @internal
|
|
|
35 |
*/
|
|
|
36 |
abstract protected function buildCipherMethod($cipherName, $iv, $keySize);
|
|
|
37 |
|
|
|
38 |
/**
|
|
|
39 |
* Builds an AesStreamInterface using cipher options loaded from the
|
|
|
40 |
* MetadataEnvelope and MaterialsProvider. Can decrypt data from both the
|
|
|
41 |
* legacy and V2 encryption client workflows.
|
|
|
42 |
*
|
|
|
43 |
* @param string $cipherText Plain-text data to be encrypted using the
|
|
|
44 |
* materials, algorithm, and data provided.
|
|
|
45 |
* @param MaterialsProviderInterfaceV2 $provider A provider to supply and encrypt
|
|
|
46 |
* materials used in encryption.
|
|
|
47 |
* @param MetadataEnvelope $envelope A storage envelope for encryption
|
|
|
48 |
* metadata to be read from.
|
|
|
49 |
* @param array $options Options used for decryption.
|
|
|
50 |
*
|
|
|
51 |
* @return AesStreamInterface
|
|
|
52 |
*
|
|
|
53 |
* @throws \InvalidArgumentException Thrown when a value in $cipherOptions
|
|
|
54 |
* is not valid.
|
|
|
55 |
*
|
|
|
56 |
* @internal
|
|
|
57 |
*/
|
|
|
58 |
public function decrypt(
|
|
|
59 |
$cipherText,
|
|
|
60 |
MaterialsProviderInterfaceV2 $provider,
|
|
|
61 |
MetadataEnvelope $envelope,
|
|
|
62 |
array $options = []
|
|
|
63 |
) {
|
|
|
64 |
$options['@CipherOptions'] = !empty($options['@CipherOptions'])
|
|
|
65 |
? $options['@CipherOptions']
|
|
|
66 |
: [];
|
|
|
67 |
$options['@CipherOptions']['Iv'] = base64_decode(
|
|
|
68 |
$envelope[MetadataEnvelope::IV_HEADER]
|
|
|
69 |
);
|
|
|
70 |
|
|
|
71 |
$options['@CipherOptions']['TagLength'] =
|
|
|
72 |
$envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] / 8;
|
|
|
73 |
|
|
|
74 |
$cek = $provider->decryptCek(
|
|
|
75 |
base64_decode(
|
|
|
76 |
$envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER]
|
|
|
77 |
),
|
|
|
78 |
json_decode(
|
|
|
79 |
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
|
|
|
80 |
true
|
|
|
81 |
),
|
|
|
82 |
$options
|
|
|
83 |
);
|
|
|
84 |
$options['@CipherOptions']['KeySize'] = strlen($cek) * 8;
|
|
|
85 |
$options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName(
|
|
|
86 |
$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]
|
|
|
87 |
);
|
|
|
88 |
|
|
|
89 |
$this->validateOptionsAndEnvelope($options, $envelope);
|
|
|
90 |
|
|
|
91 |
$decryptionStream = $this->getDecryptingStream(
|
|
|
92 |
$cipherText,
|
|
|
93 |
$cek,
|
|
|
94 |
$options['@CipherOptions']
|
|
|
95 |
);
|
|
|
96 |
unset($cek);
|
|
|
97 |
|
|
|
98 |
return $decryptionStream;
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
private function getTagFromCiphertextStream(
|
|
|
102 |
StreamInterface $cipherText,
|
|
|
103 |
$tagLength
|
|
|
104 |
) {
|
|
|
105 |
$cipherTextSize = $cipherText->getSize();
|
|
|
106 |
if ($cipherTextSize == null || $cipherTextSize <= 0) {
|
|
|
107 |
throw new \RuntimeException('Cannot decrypt a stream of unknown'
|
|
|
108 |
. ' size.');
|
|
|
109 |
}
|
|
|
110 |
return (string) new LimitStream(
|
|
|
111 |
$cipherText,
|
|
|
112 |
$tagLength,
|
|
|
113 |
$cipherTextSize - $tagLength
|
|
|
114 |
);
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
private function getStrippedCiphertextStream(
|
|
|
118 |
StreamInterface $cipherText,
|
|
|
119 |
$tagLength
|
|
|
120 |
) {
|
|
|
121 |
$cipherTextSize = $cipherText->getSize();
|
|
|
122 |
if ($cipherTextSize == null || $cipherTextSize <= 0) {
|
|
|
123 |
throw new \RuntimeException('Cannot decrypt a stream of unknown'
|
|
|
124 |
. ' size.');
|
|
|
125 |
}
|
|
|
126 |
return new LimitStream(
|
|
|
127 |
$cipherText,
|
|
|
128 |
$cipherTextSize - $tagLength,
|
|
|
129 |
|
|
|
130 |
);
|
|
|
131 |
}
|
|
|
132 |
|
|
|
133 |
private function validateOptionsAndEnvelope($options, $envelope)
|
|
|
134 |
{
|
|
|
135 |
$allowedCiphers = AbstractCryptoClientV2::$supportedCiphers;
|
|
|
136 |
$allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps;
|
|
|
137 |
if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') {
|
|
|
138 |
$allowedCiphers = array_unique(array_merge(
|
|
|
139 |
$allowedCiphers,
|
|
|
140 |
AbstractCryptoClient::$supportedCiphers
|
|
|
141 |
));
|
|
|
142 |
$allowedKeywraps = array_unique(array_merge(
|
|
|
143 |
$allowedKeywraps,
|
|
|
144 |
AbstractCryptoClient::$supportedKeyWraps
|
|
|
145 |
));
|
|
|
146 |
}
|
|
|
147 |
|
|
|
148 |
$v1SchemaException = new CryptoException("The requested object is encrypted"
|
|
|
149 |
. " with V1 encryption schemas that have been disabled by"
|
|
|
150 |
. " client configuration @SecurityProfile=V2. Retry with"
|
|
|
151 |
. " V2_AND_LEGACY enabled or reencrypt the object.");
|
|
|
152 |
|
|
|
153 |
if (!in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) {
|
|
|
154 |
if (in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) {
|
|
|
155 |
throw $v1SchemaException;
|
|
|
156 |
}
|
|
|
157 |
throw new CryptoException("The requested object is encrypted with"
|
|
|
158 |
. " the cipher '{$options['@CipherOptions']['Cipher']}', which is not"
|
|
|
159 |
. " supported for decryption with the selected security profile."
|
|
|
160 |
. " This profile allows decryption with: "
|
|
|
161 |
. implode(", ", $allowedCiphers));
|
|
|
162 |
}
|
|
|
163 |
if (!in_array(
|
|
|
164 |
$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER],
|
|
|
165 |
$allowedKeywraps
|
|
|
166 |
)) {
|
|
|
167 |
if (in_array(
|
|
|
168 |
$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER],
|
|
|
169 |
AbstractCryptoClient::$supportedKeyWraps)
|
|
|
170 |
) {
|
|
|
171 |
throw $v1SchemaException;
|
|
|
172 |
}
|
|
|
173 |
throw new CryptoException("The requested object is encrypted with"
|
|
|
174 |
. " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}',"
|
|
|
175 |
. " which is not supported for decryption with the current security"
|
|
|
176 |
. " profile.");
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
$matdesc = json_decode(
|
|
|
180 |
$envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
|
|
|
181 |
true
|
|
|
182 |
);
|
|
|
183 |
if (isset($matdesc['aws:x-amz-cek-alg'])
|
|
|
184 |
&& $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !==
|
|
|
185 |
$matdesc['aws:x-amz-cek-alg']
|
|
|
186 |
) {
|
|
|
187 |
throw new CryptoException("There is a mismatch in specified content"
|
|
|
188 |
. " encryption algrithm between the materials description value"
|
|
|
189 |
. " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}"
|
|
|
190 |
. " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}.");
|
|
|
191 |
}
|
|
|
192 |
}
|
|
|
193 |
|
|
|
194 |
/**
|
|
|
195 |
* Generates a stream that wraps the cipher text with the proper cipher and
|
|
|
196 |
* uses the content encryption key (CEK) to decrypt the data when read.
|
|
|
197 |
*
|
|
|
198 |
* @param string $cipherText Plain-text data to be encrypted using the
|
|
|
199 |
* materials, algorithm, and data provided.
|
|
|
200 |
* @param string $cek A content encryption key for use by the stream for
|
|
|
201 |
* encrypting the plaintext data.
|
|
|
202 |
* @param array $cipherOptions Options for use in determining the cipher to
|
|
|
203 |
* be used for encrypting data.
|
|
|
204 |
*
|
|
|
205 |
* @return AesStreamInterface
|
|
|
206 |
*
|
|
|
207 |
* @internal
|
|
|
208 |
*/
|
|
|
209 |
protected function getDecryptingStream(
|
|
|
210 |
$cipherText,
|
|
|
211 |
$cek,
|
|
|
212 |
$cipherOptions
|
|
|
213 |
) {
|
|
|
214 |
$cipherTextStream = Psr7\Utils::streamFor($cipherText);
|
|
|
215 |
switch ($cipherOptions['Cipher']) {
|
|
|
216 |
case 'gcm':
|
|
|
217 |
$cipherOptions['Tag'] = $this->getTagFromCiphertextStream(
|
|
|
218 |
$cipherTextStream,
|
|
|
219 |
$cipherOptions['TagLength']
|
|
|
220 |
);
|
|
|
221 |
|
|
|
222 |
return new AesGcmDecryptingStream(
|
|
|
223 |
$this->getStrippedCiphertextStream(
|
|
|
224 |
$cipherTextStream,
|
|
|
225 |
$cipherOptions['TagLength']
|
|
|
226 |
),
|
|
|
227 |
$cek,
|
|
|
228 |
$cipherOptions['Iv'],
|
|
|
229 |
$cipherOptions['Tag'],
|
|
|
230 |
$cipherOptions['Aad'] = isset($cipherOptions['Aad'])
|
|
|
231 |
? $cipherOptions['Aad']
|
|
|
232 |
: '',
|
|
|
233 |
$cipherOptions['TagLength'] ?: null,
|
|
|
234 |
$cipherOptions['KeySize']
|
|
|
235 |
);
|
|
|
236 |
default:
|
|
|
237 |
$cipherMethod = $this->buildCipherMethod(
|
|
|
238 |
$cipherOptions['Cipher'],
|
|
|
239 |
$cipherOptions['Iv'],
|
|
|
240 |
$cipherOptions['KeySize']
|
|
|
241 |
);
|
|
|
242 |
return new AesDecryptingStream(
|
|
|
243 |
$cipherTextStream,
|
|
|
244 |
$cek,
|
|
|
245 |
$cipherMethod
|
|
|
246 |
);
|
|
|
247 |
}
|
|
|
248 |
}
|
|
|
249 |
}
|