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.

JWE::_encryptContent()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 27
ccs 13
cts 13
cp 1
rs 9.8666
c 0
b 0
f 0
cc 4
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 1
    public function __toString(): string
90
    {
91 1
        return $this->toCompact();
92
    }
93
94
    /**
95
     * Initialize from compact serialization.
96
     */
97 6
    public static function fromCompact(string $data): self
98
    {
99 6
        return self::fromParts(explode('.', $data));
100
    }
101
102
    /**
103
     * Initialize from parts of compact serialization.
104
     *
105
     * @throws \UnexpectedValueException
106
     */
107 13
    public static function fromParts(array $parts): self
108
    {
109 13
        if (5 !== count($parts)) {
110 1
            throw new \UnexpectedValueException(
111 1
                'Invalid JWE compact serialization.');
112
        }
113 12
        $header = Header::fromJSON(Base64::urlDecode($parts[0]));
114 12
        $encrypted_key = Base64::urlDecode($parts[1]);
115 12
        $iv = Base64::urlDecode($parts[2]);
116 12
        $ciphertext = Base64::urlDecode($parts[3]);
117 12
        $auth_tag = Base64::urlDecode($parts[4]);
118 12
        return new self($header, $encrypted_key, $iv, $ciphertext, $auth_tag);
119
    }
120
121
    /**
122
     * Initialize by encrypting the given payload.
123
     *
124
     * @param string                     $payload  Payload
125
     * @param KeyManagementAlgorithm     $key_algo Key management algorithm
126
     * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
127
     * @param null|CompressionAlgorithm  $zip_algo Optional compression algorithm
128
     * @param null|Header                $header   Optional desired header.
129
     *                                             Algorithm specific parameters are
130
     *                                             automatically added.
131
     * @param null|string                $cek      Optional content encryption key.
132
     *                                             Randomly enerated if not set.
133
     * @param null|string                $iv       Optional initialization vector.
134
     *                                             Randomly generated if not set.
135
     *
136
     * @throws \RuntimeException If encrypt fails
137
     */
138 16
    public static function encrypt(string $payload,
139
        KeyManagementAlgorithm $key_algo, ContentEncryptionAlgorithm $enc_algo,
140
        ?CompressionAlgorithm $zip_algo = null, ?Header $header = null,
141
        ?string $cek = null, ?string $iv = null): self
142
    {
143
        // if header was not given, initialize empty
144 16
        if (!isset($header)) {
145 6
            $header = new Header();
146
        }
147
        // generate random CEK
148 16
        if (!isset($cek)) {
149 8
            $cek = $key_algo->cekForEncryption($enc_algo->keySize());
150
        }
151
        // generate random IV
152 16
        if (!isset($iv)) {
153 7
            $iv = openssl_random_pseudo_bytes($enc_algo->ivSize());
154
        }
155
        // compress
156 16
        if (isset($zip_algo)) {
157 3
            $payload = $zip_algo->compress($payload);
158 3
            $header = $header->withParameters(...$zip_algo->headerParameters());
159
        }
160 16
        return self::_encryptContent($payload, $cek, $iv,
161 16
            $key_algo, $enc_algo, $header);
162
    }
163
164
    /**
165
     * Decrypt the content using explicit algorithms.
166
     *
167
     * @param KeyManagementAlgorithm     $key_algo Key management algorithm
168
     * @param ContentEncryptionAlgorithm $enc_algo Content encryption algorithm
169
     *
170
     * @throws \RuntimeException If decrypt fails
171
     *
172
     * @return string Plaintext payload
173
     */
174 15
    public function decrypt(KeyManagementAlgorithm $key_algo,
175
        ContentEncryptionAlgorithm $enc_algo): string
176
    {
177
        // check that key management algorithm matches
178 15
        if ($key_algo->algorithmParamValue() !== $this->algorithmName()) {
179 1
            throw new \UnexpectedValueException(
180 1
                'Invalid key management algorithm.');
181
        }
182
        // check that encryption algorithm matches
183 14
        if ($enc_algo->encryptionAlgorithmParamValue() !== $this->encryptionAlgorithmName()) {
184 1
            throw new \UnexpectedValueException('Invalid encryption algorithm.');
185
        }
186 13
        $header = $this->header();
187
        // decrypt content encryption key
188 13
        $cek = $key_algo->decrypt($this->_encryptedKey, $header);
189
        // decrypt payload
190 13
        $aad = Base64::urlEncode($this->_protectedHeader->toJSON());
191 13
        $payload = $enc_algo->decrypt($this->_ciphertext, $cek,
192 13
            $this->_iv, $aad, $this->_authenticationTag);
193
        // decompress
194 13
        if ($header->hasCompressionAlgorithm()) {
195 2
            $payload = CompressionFactory::algoByHeader($header)->decompress($payload);
196
        }
197 13
        return $payload;
198
    }
199
200
    /**
201
     * Decrypt content using given JWK.
202
     *
203
     * Key management and content encryption algorithms are determined from the
204
     * header.
205
     *
206
     * @param JWK $jwk JSON Web Key
207
     *
208
     * @throws \RuntimeException If algorithm initialization fails
209
     *
210
     * @return string Plaintext payload
211
     */
212 2
    public function decryptWithJWK(JWK $jwk): string
213
    {
214 2
        $header = $this->header();
215 2
        $key_algo = KeyManagementAlgorithm::fromJWK($jwk, $header);
216 2
        $enc_algo = EncryptionAlgorithmFactory::algoByHeader($header);
217 2
        return $this->decrypt($key_algo, $enc_algo);
218
    }
219
220
    /**
221
     * Decrypt content using a key from the given JWK set.
222
     *
223
     * Correct key shall be sought by the key ID indicated by the header.
224
     *
225
     * @param JWKSet $set Set of JSON Web Keys
226
     *
227
     * @throws \RuntimeException If algorithm initialization fails
228
     *
229
     * @return string Plaintext payload
230
     */
231 5
    public function decryptWithJWKSet(JWKSet $set): string
232
    {
233 5
        if (!count($set)) {
234 1
            throw new \RuntimeException('No keys.');
235
        }
236 4
        $header = $this->header();
237 4
        $factory = new KeyAlgorithmFactory($header);
238 4
        $key_algo = $factory->algoByKeys($set);
239 3
        $enc_algo = EncryptionAlgorithmFactory::algoByHeader($header);
240 3
        return $this->decrypt($key_algo, $enc_algo);
241
    }
242
243
    /**
244
     * Get JOSE header.
245
     */
246 18
    public function header(): JOSE
247
    {
248 18
        return new JOSE($this->_protectedHeader);
249
    }
250
251
    /**
252
     * Get the name of the key management algorithm.
253
     */
254 15
    public function algorithmName(): string
255
    {
256 15
        return $this->header()->algorithm()->value();
257
    }
258
259
    /**
260
     * Get the name of the encryption algorithm.
261
     */
262 14
    public function encryptionAlgorithmName(): string
263
    {
264 14
        return $this->header()->encryptionAlgorithm()->value();
265
    }
266
267
    /**
268
     * Get encrypted CEK.
269
     */
270 1
    public function encryptedKey(): string
271
    {
272 1
        return $this->_encryptedKey;
273
    }
274
275
    /**
276
     * Get initialization vector.
277
     */
278 1
    public function initializationVector(): string
279
    {
280 1
        return $this->_iv;
281
    }
282
283
    /**
284
     * Get ciphertext.
285
     */
286 1
    public function ciphertext(): string
287
    {
288 1
        return $this->_ciphertext;
289
    }
290
291
    /**
292
     * Get authentication tag.
293
     */
294 1
    public function authenticationTag(): string
295
    {
296 1
        return $this->_authenticationTag;
297
    }
298
299
    /**
300
     * Convert to compact serialization.
301
     */
302 11
    public function toCompact(): string
303
    {
304 11
        return Base64::urlEncode($this->_protectedHeader->toJSON()) . '.' .
305 11
             Base64::urlEncode($this->_encryptedKey) . '.' .
306 11
             Base64::urlEncode($this->_iv) . '.' .
307 11
             Base64::urlEncode($this->_ciphertext) . '.' .
308 11
             Base64::urlEncode($this->_authenticationTag);
309
    }
310
311
    /**
312
     * Encrypt content with explicit parameters.
313
     *
314
     * @param string                     $plaintext Plaintext content to encrypt
315
     * @param string                     $cek       Content encryption key
316
     * @param string                     $iv        Initialization vector
317
     * @param KeyManagementAlgorithm     $key_algo  Key management algorithm
318
     * @param ContentEncryptionAlgorithm $enc_algo  Content encryption algorithm
319
     * @param Header                     $header    Header
320
     *
321
     * @throws \UnexpectedValueException
322
     */
323 16
    private static function _encryptContent(string $plaintext, string $cek,
324
        string $iv, KeyManagementAlgorithm $key_algo,
325
        ContentEncryptionAlgorithm $enc_algo, Header $header): self
326
    {
327
        // check that content encryption key has correct size
328 16
        if (strlen($cek) !== $enc_algo->keySize()) {
329 1
            throw new \UnexpectedValueException('Invalid key size.');
330
        }
331
        // check that initialization vector has correct size
332 15
        if (strlen($iv) !== $enc_algo->ivSize()) {
333 1
            throw new \UnexpectedValueException('Invalid IV size.');
334
        }
335
        // add key and encryption algorithm parameters to the header
336 14
        $header = $header->withParameters(...$key_algo->headerParameters())
337 14
            ->withParameters(...$enc_algo->headerParameters());
338
        // encrypt the content encryption key
339 14
        $encrypted_key = $key_algo->encrypt($cek, $header);
340
        // sanity check that header wasn't unset via reference
341 14
        if (!$header instanceof Header) {
342 1
            throw new \RuntimeException('Broken key algorithm.');
343
        }
344
        // additional authenticated data
345 13
        $aad = Base64::urlEncode($header->toJSON());
346
        // encrypt
347 13
        [$ciphertext, $auth_tag] = $enc_algo->encrypt($plaintext, $cek, $iv, $aad);
348
        // TODO: should aad be passed
349 13
        return new self($header, $encrypted_key, $iv, $ciphertext, $auth_tag);
350
    }
351
}
352