Completed
Push — v2.0.x ( 45cd5b...255948 )
by Florent
12:36
created

Encrypter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 1
eloc 5
nc 1
nop 2
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->compressPayloadIfNeeded($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 {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements 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 else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
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 compressPayloadIfNeeded($payload, array $complete_headers)
150
    {
151
        if (!array_key_exists('zip', $complete_headers)) {
152
            return $payload;
153
        }
154
155
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($complete_headers['zip']);
156
        if (null === $compression_method) {
157
            throw new \RuntimeException(sprintf('Compression method "%s" not supported', $complete_headers['zip']));
158
        }
159
        $compressed_payload = $compression_method->compress($payload);
160
        if (!is_string($compressed_payload)) {
161
            throw new \RuntimeException('Compression failed.');
162
        }
163
164
        return $compressed_payload;
165
    }
166
167
    private function getEncryptedKey(array $complete_headers, $cek, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
168
    {
169
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
170
            return $key_encryption_algorithm->encryptKey(
171
                    $recipient_key,
172
                    $cek,
173
                    $complete_headers
174
                );
175
        } elseif ($key_encryption_algorithm instanceof KeyWrappingInterface) {
176
            return $key_encryption_algorithm->wrapKey(
177
                $recipient_key,
178
                $cek,
179
                $complete_headers
180
            );
181
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
182
            if (!$sender_key instanceof JWKInterface) {
183
                throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
184
            }
185
            $additional_header_values = [];
186
            $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($sender_key, $recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_header_values);
0 ignored issues
show
Unused Code introduced by
$jwt_cek is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
187
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
188
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
189
            $additional_header_values = [];
190
            $jwt_cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
0 ignored issues
show
Unused Code introduced by
$jwt_cek is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Bug introduced by
It seems like $sender_key defined by parameter $sender_key on line 167 can be null; however, Jose\Algorithm\KeyEncryp...face::getAgreementKey() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
191
            //$this->updateHeader($additional_header_values, $protected_header, $recipient_header, $serialization);
192
        }
193
    }
194
195
    private function getCEK(array $complete_headers, KeyEncryptionAlgorithmInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
196
    {
197
        if ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
198
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
199
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
200
            return $this->createCEK($content_encryption_algorithm->getCEKSize());
201
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
202
            return $this->calculateAgreementKey($complete_headers, $key_encryption_algorithm, $content_encryption_algorithm, $recipient_key, $sender_key);
203
        } elseif ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
204
            return $key_encryption_algorithm->getCEK($recipient_key);
205
        } else {
206
            throw new \RuntimeException('Unable to get key management mode.');
207
        }
208
    }
209
210
    /**
211
     * @param array $complete_headers
212
     *
213
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
214
     */
215
    private function findKeyEncryptionAlgorithm(array $complete_headers)
216
    {
217
        if (!array_key_exists('alg', $complete_headers)) {
218
            throw new \InvalidArgumentException('Parameter "alg" is missing.');
219
        }
220
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
221
        if ($key_encryption_algorithm instanceof KeyEncryptionAlgorithmInterface) {
222
            return $key_encryption_algorithm;
223
        }
224
        throw new \RuntimeException(sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
225
    }
226
227
    /**
228
     * @param array $complete_headers
229
     *
230
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
231
     */
232
    private function findContentEncryptionAlgorithm(array $complete_headers)
233
    {
234
        if (!array_key_exists('enc', $complete_headers)) {
235
            throw new \InvalidArgumentException('Parameter "enc" is missing.');
236
        }
237
238
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['enc']);
239
        if (!$content_encryption_algorithm instanceof ContentEncryptionAlgorithmInterface) {
240
            throw new \RuntimeException(sprintf('The algorithm "%s" is not enabled or does not implement ContentEncryptionInterface.', $complete_headers['enc']));
241
        }
242
243
        return $content_encryption_algorithm;
244
    }
245
246
    /**
247
     * @param array                                               $complete_headers
248
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementInterface $key_encryption_algorithm
249
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
250
     * @param \Jose\Object\JWKInterface                           $recipient_key
251
     * @param \Jose\Object\JWKInterface|null                      $sender_key
252
     *
253
     * @return string
254
     */
255
    private function calculateAgreementKey(array $complete_headers, KeyAgreementInterface $key_encryption_algorithm, ContentEncryptionAlgorithmInterface $content_encryption_algorithm, JWKInterface $recipient_key, JWKInterface $sender_key = null)
256
    {
257
        if (!$sender_key instanceof JWKInterface) {
258
            throw new \RuntimeException('The sender key must be set using Key Agreement or Key Agreement with Wrapping algorithms.');
259
        }
260
        $additional_header_values = [];
261
        $cek = $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $sender_key, $recipient_key, $complete_headers, $additional_header_values);
262
263
        return $cek;
264
    }
265
266
    /**
267
     * @param int $size
268
     *
269
     * @return string
270
     */
271
    private function createCEK($size)
272
    {
273
        return $this->generateRandomString($size / 8);
274
    }
275
276
    /**
277
     * @param int $size
278
     *
279
     * @return string
280
     */
281
    private function createIV($size)
282
    {
283
        return $this->generateRandomString($size / 8);
284
    }
285
286
    /**
287
     * @param int $length
288
     *
289
     * @return string
290
     */
291
    private function generateRandomString($length)
292
    {
293
        if (function_exists('random_bytes')) {
294
            return random_bytes($length);
295
        } else {
296
            return openssl_random_pseudo_bytes($length);
297
        }
298
    }
299
}
300