GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Branch php72 (880eb0)
by Joni
05:58
created

JWE::_encryptContent()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 14
cts 14
cp 1
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 16
nc 4
nop 6
crap 4
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\JWX\JWE;
6
7
use Sop\JWX\JWE\CompressionAlgorithm\CompressionFactory;
8
use Sop\JWX\JWE\EncryptionAlgorithm\EncryptionAlgorithmFactory;
9
use Sop\JWX\JWE\KeyAlgorithm\KeyAlgorithmFactory;
10
use Sop\JWX\JWK\JWK;
11
use Sop\JWX\JWK\JWKSet;
12
use Sop\JWX\JWT\Header\Header;
13
use Sop\JWX\JWT\Header\JOSE;
14
use Sop\JWX\Util\Base64;
15
16
/**
17
 * Class to represent JWE structure.
18
 *
19
 * @see https://tools.ietf.org/html/rfc7516#section-3
20
 */
21
class JWE
22
{
23
    /**
24
     * Protected header.
25
     *
26
     * @var Header
27
     */
28
    protected $_protectedHeader;
29
30
    /**
31
     * Encrypted key.
32
     *
33
     * @var string
34
     */
35
    protected $_encryptedKey;
36
37
    /**
38
     * Initialization vector.
39
     *
40
     * @var string
41
     */
42
    protected $_iv;
43
44
    /**
45
     * Additional authenticated data.
46
     *
47
     * @var null|string
48
     */
49
    protected $_aad;
50
51
    /**
52
     * Ciphertext.
53
     *
54
     * @var string
55
     */
56
    protected $_ciphertext;
57
58
    /**
59
     * Authentication tag.
60
     *
61
     * @var string
62
     */
63
    protected $_authenticationTag;
64
65
    /**
66
     * Constructor.
67
     *
68
     * @param Header      $protected_header JWE Protected Header
69
     * @param string      $encrypted_key    Encrypted key
70
     * @param string      $iv               Initialization vector
71
     * @param string      $ciphertext       Ciphertext
72
     * @param string      $auth_tag         Authentication tag
73
     * @param null|string $aad              Additional authenticated data
74
     */
75 25
    public function __construct(Header $protected_header, string $encrypted_key,
76
        string $iv, string $ciphertext, string $auth_tag, ?string $aad = null)
77
    {
78 25
        $this->_protectedHeader = $protected_header;
79 25
        $this->_encryptedKey = $encrypted_key;
80 25
        $this->_iv = $iv;
81 25
        $this->_aad = $aad;
82 25
        $this->_ciphertext = $ciphertext;
83 25
        $this->_authenticationTag = $auth_tag;
84 25
    }
85
86
    /**
87
     * Convert JWE to string.
88
     *
89
     * @return string
90
     */
91 1
    public function __toString(): string
92
    {
93 1
        return $this->toCompact();
94
    }
95
96
    /**
97
     * Initialize from compact serialization.
98
     *
99
     * @param string $data
100
     *
101
     * @return self
102
     */
103 6
    public static function fromCompact(string $data): self
104
    {
105 6
        return self::fromParts(explode('.', $data));
106
    }
107
108
    /**
109
     * Initialize from parts of compact serialization.
110
     *
111
     * @param array $parts
112
     *
113
     * @throws \UnexpectedValueException
114
     *
115
     * @return self
116
     */
117 13
    public static function fromParts(array $parts): self
118
    {
119 13
        if (5 !== count($parts)) {
120 1
            throw new \UnexpectedValueException(
121 1
                'Invalid JWE compact serialization.');
122
        }
123 12
        $header = Header::fromJSON(Base64::urlDecode($parts[0]));
124 12
        $encrypted_key = Base64::urlDecode($parts[1]);
125 12
        $iv = Base64::urlDecode($parts[2]);
126 12
        $ciphertext = Base64::urlDecode($parts[3]);
127 12
        $auth_tag = Base64::urlDecode($parts[4]);
128 12
        return new self($header, $encrypted_key, $iv, $ciphertext, $auth_tag);
129
    }
130
131
    /**
132
     * Initialize by encrypting the given payload.
133
     *
134
     * @param string                     $payload  Payload
135
     * @param KeyManagementAlgorithm     $key_algo Key management algorithm
136
     * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
137
     * @param null|CompressionAlgorithm  $zip_algo Optional compression algorithm
138
     * @param null|Header                $header   Optional desired header.
139
     *                                             Algorithm specific parameters are
140
     *                                             automatically added.
141
     * @param null|string                $cek      Optional content encryption key.
142
     *                                             Randomly enerated if not set.
143
     * @param null|string                $iv       Optional initialization vector.
144
     *                                             Randomly generated if not set.
145
     *
146
     * @throws \RuntimeException If encrypt fails
147
     *
148
     * @return self
149
     */
150 16
    public static function encrypt(string $payload,
151
        KeyManagementAlgorithm $key_algo, ContentEncryptionAlgorithm $enc_algo,
152
        ?CompressionAlgorithm $zip_algo = null, ?Header $header = null,
153
        ?string $cek = null, ?string $iv = null): self
154
    {
155
        // if header was not given, initialize empty
156 16
        if (!isset($header)) {
157 6
            $header = new Header();
158
        }
159
        // generate random CEK
160 16
        if (!isset($cek)) {
161 8
            $cek = $key_algo->cekForEncryption($enc_algo->keySize());
162
        }
163
        // generate random IV
164 16
        if (!isset($iv)) {
165 7
            $iv = openssl_random_pseudo_bytes($enc_algo->ivSize());
166
        }
167
        // compress
168 16
        if (isset($zip_algo)) {
169 3
            $payload = $zip_algo->compress($payload);
170 3
            $header = $header->withParameters(...$zip_algo->headerParameters());
171
        }
172 16
        return self::_encryptContent($payload, $cek, $iv,
173 16
            $key_algo, $enc_algo, $header);
174
    }
175
176
    /**
177
     * Decrypt the content using explicit algorithms.
178
     *
179
     * @param KeyManagementAlgorithm     $key_algo Key management algorithm
180
     * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
181
     *
182
     * @throws \RuntimeException If decrypt fails
183
     *
184
     * @return string Plaintext payload
185
     */
186 15
    public function decrypt(KeyManagementAlgorithm $key_algo,
187
        ContentEncryptionAlgorithm $enc_algo): string
188
    {
189
        // check that key management algorithm matches
190 15
        if ($key_algo->algorithmParamValue() !== $this->algorithmName()) {
191 1
            throw new \UnexpectedValueException(
192 1
                'Invalid key management algorithm.');
193
        }
194
        // check that encryption algorithm matches
195 14
        if ($enc_algo->encryptionAlgorithmParamValue() !== $this->encryptionAlgorithmName()) {
196 1
            throw new \UnexpectedValueException('Invalid encryption algorithm.');
197
        }
198 13
        $header = $this->header();
199
        // decrypt content encryption key
200 13
        $cek = $key_algo->decrypt($this->_encryptedKey, $header);
201
        // decrypt payload
202 13
        $aad = Base64::urlEncode($this->_protectedHeader->toJSON());
203 13
        $payload = $enc_algo->decrypt($this->_ciphertext, $cek,
204 13
            $this->_iv, $aad, $this->_authenticationTag);
205
        // decompress
206 13
        if ($header->hasCompressionAlgorithm()) {
207 2
            $payload = CompressionFactory::algoByHeader($header)->decompress($payload);
208
        }
209 13
        return $payload;
210
    }
211
212
    /**
213
     * Decrypt content using given JWK.
214
     *
215
     * Key management and content encryption algorithms are determined from the
216
     * header.
217
     *
218
     * @param JWK $jwk JSON Web Key
219
     *
220
     * @throws \RuntimeException If algorithm initialization fails
221
     *
222
     * @return string Plaintext payload
223
     */
224 2
    public function decryptWithJWK(JWK $jwk): string
225
    {
226 2
        $header = $this->header();
227 2
        $key_algo = KeyManagementAlgorithm::fromJWK($jwk, $header);
228 2
        $enc_algo = EncryptionAlgorithmFactory::algoByHeader($header);
229 2
        return $this->decrypt($key_algo, $enc_algo);
230
    }
231
232
    /**
233
     * Decrypt content using a key from the given JWK set.
234
     *
235
     * Correct key shall be sought by the key ID indicated by the header.
236
     *
237
     * @param JWKSet $set Set of JSON Web Keys
238
     *
239
     * @throws \RuntimeException If algorithm initialization fails
240
     *
241
     * @return string Plaintext payload
242
     */
243 5
    public function decryptWithJWKSet(JWKSet $set): string
244
    {
245 5
        if (!count($set)) {
246 1
            throw new \RuntimeException('No keys.');
247
        }
248 4
        $header = $this->header();
249 4
        $factory = new KeyAlgorithmFactory($header);
250 4
        $key_algo = $factory->algoByKeys($set);
251 3
        $enc_algo = EncryptionAlgorithmFactory::algoByHeader($header);
252 3
        return $this->decrypt($key_algo, $enc_algo);
253
    }
254
255
    /**
256
     * Get JOSE header.
257
     *
258
     * @return JOSE
259
     */
260 18
    public function header(): JOSE
261
    {
262 18
        return new JOSE($this->_protectedHeader);
263
    }
264
265
    /**
266
     * Get the name of the key management algorithm.
267
     *
268
     * @return string
269
     */
270 15
    public function algorithmName(): string
271
    {
272 15
        return $this->header()->algorithm()->value();
273
    }
274
275
    /**
276
     * Get the name of the encryption algorithm.
277
     *
278
     * @return string
279
     */
280 14
    public function encryptionAlgorithmName(): string
281
    {
282 14
        return $this->header()->encryptionAlgorithm()->value();
283
    }
284
285
    /**
286
     * Get encrypted CEK.
287
     *
288
     * @return string
289
     */
290 1
    public function encryptedKey(): string
291
    {
292 1
        return $this->_encryptedKey;
293
    }
294
295
    /**
296
     * Get initialization vector.
297
     *
298
     * @return string
299
     */
300 1
    public function initializationVector(): string
301
    {
302 1
        return $this->_iv;
303
    }
304
305
    /**
306
     * Get ciphertext.
307
     *
308
     * @return string
309
     */
310 1
    public function ciphertext(): string
311
    {
312 1
        return $this->_ciphertext;
313
    }
314
315
    /**
316
     * Get authentication tag.
317
     *
318
     * @return string
319
     */
320 1
    public function authenticationTag(): string
321
    {
322 1
        return $this->_authenticationTag;
323
    }
324
325
    /**
326
     * Convert to compact serialization.
327
     *
328
     * @return string
329
     */
330 11
    public function toCompact(): string
331
    {
332 11
        return Base64::urlEncode($this->_protectedHeader->toJSON()) . '.' .
333 11
             Base64::urlEncode($this->_encryptedKey) . '.' .
334 11
             Base64::urlEncode($this->_iv) . '.' .
335 11
             Base64::urlEncode($this->_ciphertext) . '.' .
336 11
             Base64::urlEncode($this->_authenticationTag);
337
    }
338
339
    /**
340
     * Encrypt content with explicit parameters.
341
     *
342
     * @param string                     $plaintext Plaintext content to encrypt
343
     * @param string                     $cek       Content encryption key
344
     * @param string                     $iv        Initialization vector
345
     * @param KeyManagementAlgorithm     $key_algo  Key management algorithm
346
     * @param ContentEncryptionAlgorithm $enc_algo  Content encryption algorithm
347
     * @param Header                     $header    Header
348
     *
349
     * @throws \UnexpectedValueException
350
     *
351
     * @return self
352
     */
353 16
    private static function _encryptContent(string $plaintext, string $cek,
354
        string $iv, KeyManagementAlgorithm $key_algo,
355
        ContentEncryptionAlgorithm $enc_algo, Header $header): self
356
    {
357
        // check that content encryption key has correct size
358 16
        if (strlen($cek) !== $enc_algo->keySize()) {
359 1
            throw new \UnexpectedValueException('Invalid key size.');
360
        }
361
        // check that initialization vector has correct size
362 15
        if (strlen($iv) !== $enc_algo->ivSize()) {
363 1
            throw new \UnexpectedValueException('Invalid IV size.');
364
        }
365
        // add key and encryption algorithm parameters to the header
366 14
        $header = $header->withParameters(...$key_algo->headerParameters())
367 14
            ->withParameters(...$enc_algo->headerParameters());
368
        // encrypt the content encryption key
369 14
        $encrypted_key = $key_algo->encrypt($cek, $header);
370
        // sanity check that header wasn't unset via reference
371 14
        if (!$header instanceof Header) {
372 1
            throw new \RuntimeException('Broken key algorithm.');
373
        }
374
        // additional authenticated data
375 13
        $aad = Base64::urlEncode($header->toJSON());
376
        // encrypt
377 13
        [$ciphertext, $auth_tag] = $enc_algo->encrypt($plaintext, $cek, $iv, $aad);
378
        // TODO: should aad be passed
379 13
        return new self($header, $encrypted_key, $iv, $ciphertext, $auth_tag);
380
    }
381
}
382