|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* The MIT License (MIT) |
|
5
|
|
|
* |
|
6
|
|
|
* Copyright (c) 2014-2016 Spomky-Labs |
|
7
|
|
|
* |
|
8
|
|
|
* This software may be modified and distributed under the terms |
|
9
|
|
|
* of the MIT license. See the LICENSE file for details. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Jose; |
|
13
|
|
|
|
|
14
|
|
|
use Base64Url\Base64Url; |
|
15
|
|
|
use Jose\Algorithm\ContentEncryptionAlgorithmInterface; |
|
16
|
|
|
use Jose\Algorithm\JWAManagerInterface; |
|
17
|
|
|
use Jose\Algorithm\KeyEncryption\DirectEncryptionInterface; |
|
18
|
|
|
use Jose\Algorithm\KeyEncryption\KeyAgreementInterface; |
|
19
|
|
|
use Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface; |
|
20
|
|
|
use Jose\Algorithm\KeyEncryption\KeyEncryptionInterface; |
|
21
|
|
|
use Jose\Algorithm\KeyEncryption\KeyWrappingInterface; |
|
22
|
|
|
use Jose\Algorithm\KeyEncryptionAlgorithmInterface; |
|
23
|
|
|
use Jose\Behaviour\HasCompressionManager; |
|
24
|
|
|
use Jose\Behaviour\HasJWAManager; |
|
25
|
|
|
use Jose\Behaviour\HasKeyChecker; |
|
26
|
|
|
use Jose\Compression\CompressionManagerInterface; |
|
27
|
|
|
use Jose\Object\JWEInterface; |
|
28
|
|
|
use Jose\Object\JWKInterface; |
|
29
|
|
|
use Jose\Object\Recipient; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
*/ |
|
33
|
|
|
final class Encrypter implements EncrypterInterface |
|
34
|
|
|
{ |
|
35
|
|
|
use HasKeyChecker; |
|
36
|
|
|
use HasJWAManager; |
|
37
|
|
|
use HasCompressionManager; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Encrypter constructor. |
|
41
|
|
|
* |
|
42
|
|
|
* @param \Jose\Algorithm\JWAManagerInterface $jwa_manager |
|
43
|
|
|
* @param \Jose\Compression\CompressionManagerInterface $compression_manager |
|
44
|
|
|
*/ |
|
45
|
|
|
public function __construct( |
|
46
|
|
|
JWAManagerInterface $jwa_manager, |
|
47
|
|
|
CompressionManagerInterface $compression_manager) |
|
48
|
|
|
{ |
|
49
|
|
|
$this->setJWAManager($jwa_manager); |
|
50
|
|
|
$this->setCompressionManager($compression_manager); |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* @param \Jose\Object\JWEInterface $jwe |
|
55
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
|
56
|
|
|
* @param \Jose\Object\JWKInterface|null $sender_key |
|
57
|
|
|
* @param array $recipient_headers |
|
58
|
|
|
* |
|
59
|
|
|
* @return \Jose\Object\JWEInterface |
|
60
|
|
|
*/ |
|
61
|
|
|
public function addRecipient(JWEInterface $jwe, JWKInterface $recipient_key, JWKInterface $sender_key = null, array $recipient_headers = []) |
|
62
|
|
|
{ |
|
63
|
|
|
$complete_headers = array_merge( |
|
64
|
|
|
$jwe->getSharedProtectedHeaders(), |
|
65
|
|
|
$jwe->getSharedHeaders(), |
|
66
|
|
|
$recipient_headers |
|
67
|
|
|
); |
|
68
|
|
|
|
|
69
|
|
|
$key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
|
70
|
|
|
|
|
71
|
|
|
// Content Encryption Algorithm |
|
72
|
|
|
$content_encryption_algorithm = $this->findContentEncryptionAlgorithm($complete_headers); |
|
73
|
|
|
|
|
74
|
|
|
if (null === $jwe->getCiphertext()) { |
|
75
|
|
|
// the content is not yet encrypted (no recipient) |
|
76
|
|
|
|
|
77
|
|
|
if (!empty($jwe->getSharedProtectedHeaders())) { |
|
78
|
|
|
$jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders()))); |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
// CEK |
|
82
|
|
|
$content_encryption_key = $this->getCEK( |
|
83
|
|
|
$complete_headers, |
|
84
|
|
|
$key_encryption_algorithm, |
|
85
|
|
|
$content_encryption_algorithm, |
|
86
|
|
|
$recipient_key, |
|
87
|
|
|
$sender_key |
|
88
|
|
|
); |
|
89
|
|
|
$jwe = $jwe->withContentEncryptionKey($content_encryption_key); |
|
90
|
|
|
|
|
91
|
|
|
// IV |
|
92
|
|
|
if (null !== $iv_size = $content_encryption_algorithm->getIVSize()) { |
|
93
|
|
|
$iv = $this->createIV($iv_size); |
|
94
|
|
|
$jwe = $jwe->withIV($iv); |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
// We encrypt the payload and get the tag |
|
98
|
|
|
$tag = null; |
|
99
|
|
|
$payload = $this->preparePayload($jwe->getPayload(), $complete_headers); |
|
100
|
|
|
|
|
101
|
|
|
$ciphertext = $content_encryption_algorithm->encryptContent( |
|
102
|
|
|
$payload, |
|
103
|
|
|
$content_encryption_key, |
|
104
|
|
|
$jwe->getIV(), |
|
105
|
|
|
$jwe->getAAD(), |
|
106
|
|
|
$jwe->getEncodedSharedProtectedHeaders(), |
|
107
|
|
|
$tag |
|
108
|
|
|
); |
|
109
|
|
|
$jwe = $jwe->withCiphertext($ciphertext); |
|
110
|
|
|
|
|
111
|
|
|
// Tag |
|
112
|
|
|
if (null !== $tag) { |
|
113
|
|
|
$jwe = $jwe->withTag($tag); |
|
114
|
|
|
} |
|
115
|
|
|
// JWT Ciphertext |
|
116
|
|
|
} else { |
|
|
|
|
|
|
117
|
|
|
// On vérifie le jey managmenet mode |
|
118
|
|
|
|
|
119
|
|
|
// On vérifie si le CEK est disponible. |
|
120
|
|
|
// - si dispo on quitte le if |
|
121
|
|
|
// - sinon on ne peut pas aller plus loin. Le JWE doit être décrypté avant |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
$recipient = new Recipient(); |
|
125
|
|
|
$recipient = $recipient->withHeaders($recipient_headers); |
|
126
|
|
|
|
|
127
|
|
|
$encrypted_content_encryption_key = $this->getEncryptedKey( |
|
128
|
|
|
$complete_headers, |
|
129
|
|
|
$jwe->getContentEncryptionKey(), |
|
130
|
|
|
$key_encryption_algorithm, |
|
131
|
|
|
$content_encryption_algorithm, |
|
132
|
|
|
$recipient_key, |
|
133
|
|
|
$sender_key |
|
134
|
|
|
); |
|
135
|
|
|
if (null !== $encrypted_content_encryption_key) { |
|
136
|
|
|
$recipient = $recipient->withEncryptedKey($encrypted_content_encryption_key); |
|
137
|
|
|
} |
|
138
|
|
|
$jwe = $jwe->addRecipient($recipient); |
|
139
|
|
|
|
|
140
|
|
|
return $jwe; |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* @param string $payload |
|
145
|
|
|
* @param array $complete_headers |
|
146
|
|
|
* |
|
147
|
|
|
* @return string |
|
148
|
|
|
*/ |
|
149
|
|
|
private function preparePayload($payload, array $complete_headers) |
|
150
|
|
|
{ |
|
151
|
|
|
$prepared = is_string($payload)?$payload:json_encode($payload); |
|
152
|
|
|
|
|
153
|
|
|
if(null === $prepared) { |
|
154
|
|
|
throw new \InvalidArgumentException('The payload is empty or cannot encoded into JSON.'); |
|
155
|
|
|
} |
|
156
|
|
|
if (!array_key_exists('zip', $complete_headers)) { |
|
157
|
|
|
return $prepared; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
$compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']); |
|
161
|
|
|
if (null === $compression_method) { |
|
162
|
|
|
throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip'])); |
|
163
|
|
|
} |
|
164
|
|
|
$compressed_payload = $compression_method->compress($prepared); |
|
165
|
|
|
if (!is_string($compressed_payload)) { |
|
166
|
|
|
throw new \RuntimeException('Compression failed.'); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
return $compressed_payload; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null) |
|
173
|
|
|
{ |
|
174
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionInterface) { |
|
175
|
|
|
return $key_encryption_algorithm->encryptKey( |
|
176
|
|
|
$recipient_key, |
|
177
|
|
|
$cek, |
|
178
|
|
|
$complete_headers |
|
179
|
|
|
); |
|
180
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) { |
|
181
|
|
|
return $key_encryption_algorithm->wrapKey( |
|
182
|
|
|
$recipient_key, |
|
183
|
|
|
$cek, |
|
184
|
|
|
$complete_headers |
|
185
|
|
|
); |
|
186
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) { |
|
187
|
|
|
if (!$sender_key instanceof JWKInterface) { |
|
188
|
|
|
throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.'); |
|
189
|
|
|
} |
|
190
|
|
|
$additional_header_values = []; |
|
191
|
|
|
$jwt_cek = $key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_header_values); |
|
|
|
|
|
|
192
|
|
|
//$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization); |
|
193
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) { |
|
194
|
|
|
$additional_header_values = []; |
|
195
|
|
|
$jwt_cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values); |
|
|
|
|
|
|
196
|
|
|
//$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization); |
|
197
|
|
|
} |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null) |
|
201
|
|
|
{ |
|
202
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionInterface) { |
|
203
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
|
204
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) { |
|
205
|
|
|
return $this->createCEK($content_encryption_algorithm->getCEKSize()); |
|
206
|
|
|
} elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) { |
|
207
|
|
|
return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key); |
|
208
|
|
|
} elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) { |
|
209
|
|
|
return $key_encryption_algorithm->getCEK($recipient_key); |
|
210
|
|
|
} else { |
|
211
|
|
|
throw new \RuntimeException('Unable to get key management mode.'); |
|
212
|
|
|
} |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
/** |
|
216
|
|
|
* @param array $complete_headers |
|
217
|
|
|
* |
|
218
|
|
|
* @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface |
|
219
|
|
|
*/ |
|
220
|
|
|
private function findKeyEncryptionAlgorithm(array $complete_headers) |
|
221
|
|
|
{ |
|
222
|
|
|
if (!array_key_exists('alg', $complete_headers)) { |
|
223
|
|
|
throw new \InvalidArgumentException('Parameter "alg" is missing.'); |
|
224
|
|
|
} |
|
225
|
|
|
$key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
|
226
|
|
|
if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) { |
|
227
|
|
|
return $key_encryption_algorithm; |
|
228
|
|
|
} |
|
229
|
|
|
throw new \InvalidArgumentException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg'])); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* @param array $complete_headers |
|
234
|
|
|
* |
|
235
|
|
|
* @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface |
|
236
|
|
|
*/ |
|
237
|
|
|
private function findContentEncryptionAlgorithm(array $complete_headers) |
|
238
|
|
|
{ |
|
239
|
|
|
if (!array_key_exists('enc', $complete_headers)) { |
|
240
|
|
|
throw new \InvalidArgumentException('Parameter "enc" is missing.'); |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
$content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']); |
|
244
|
|
|
if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) { |
|
245
|
|
|
throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc'])); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
return $content_encryption_algorithm; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* @param array $complete_headers |
|
253
|
|
|
* @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm |
|
254
|
|
|
* @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
|
255
|
|
|
* @param \Jose\Object\JWKInterface $recipient_key |
|
256
|
|
|
* @param \Jose\Object\JWKInterface|null $sender_key |
|
257
|
|
|
* |
|
258
|
|
|
* @return string |
|
259
|
|
|
*/ |
|
260
|
|
|
private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null) |
|
261
|
|
|
{ |
|
262
|
|
|
if (!$sender_key instanceof JWKInterface) { |
|
263
|
|
|
throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.'); |
|
264
|
|
|
} |
|
265
|
|
|
$additional_header_values = []; |
|
266
|
|
|
$cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values); |
|
267
|
|
|
|
|
268
|
|
|
return $cek; |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
/** |
|
272
|
|
|
* @param int $size |
|
273
|
|
|
* |
|
274
|
|
|
* @return string |
|
275
|
|
|
*/ |
|
276
|
|
|
private function createCEK($size) |
|
277
|
|
|
{ |
|
278
|
|
|
return $this->generateRandomString($size / 8); |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
/** |
|
282
|
|
|
* @param int $size |
|
283
|
|
|
* |
|
284
|
|
|
* @return string |
|
285
|
|
|
*/ |
|
286
|
|
|
private function createIV($size) |
|
287
|
|
|
{ |
|
288
|
|
|
return $this->generateRandomString($size / 8); |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
/** |
|
292
|
|
|
* @param int $length |
|
293
|
|
|
* |
|
294
|
|
|
* @return string |
|
295
|
|
|
*/ |
|
296
|
|
|
private function generateRandomString($length) |
|
297
|
|
|
{ |
|
298
|
|
|
if (function_exists('random_bytes')) { |
|
299
|
|
|
return random_bytes($length); |
|
300
|
|
|
} else { |
|
301
|
|
|
return openssl_random_pseudo_bytes($length); |
|
302
|
|
|
} |
|
303
|
|
|
} |
|
304
|
|
|
} |
|
305
|
|
|
|
This check looks for the
elsebranches ofifstatements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
elsebranches can be removed.could be turned into
This is much more concise to read.