Completed
Push — master ( d7fc29...6ab837 )
by Florent
03:14
created

Encrypter::findKeyEncryptionAlgorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4285
cc 1
eloc 5
nc 1
nop 1
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 Assert\Assertion;
15
use Base64Url\Base64Url;
16
17
final class Encrypter implements EncrypterInterface
18
{
19
    use Behaviour\HasKeyChecker;
20
    use Behaviour\HasJWAManager;
21
    use Behaviour\HasCompressionManager;
22
    use Behaviour\CommonCipheringMethods;
23
    use Behaviour\EncrypterTrait;
24
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public static function createEncrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ'])
29
    {
30
        $encrypter = new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods);
31
32
        return $encrypter;
33
    }
34
35
    /**
36
     * Decrypter constructor.
37
     *
38
     * @param string[]|\Jose\Algorithm\KeyEncryptionAlgorithmInterface[]     $key_encryption_algorithms
39
     * @param string[]|\Jose\Algorithm\ContentEncryptionAlgorithmInterface[] $content_encryption_algorithms
40
     * @param string[]|\Jose\Compression\CompressionInterface[]              $compression_methods
41
     */
42
    public function __construct(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods)
43
    {
44
        $this->setKeyEncryptionAlgorithms($key_encryption_algorithms);
45
        $this->setContentEncryptionAlgorithms($content_encryption_algorithms);
46
        $this->setCompressionMethods($compression_methods);
47
        $this->setJWAManager(Factory\AlgorithmManagerFactory::createAlgorithmManager(array_merge($key_encryption_algorithms, $content_encryption_algorithms)));
48
        $this->setCompressionManager(Factory\CompressionManagerFactory::createCompressionManager($compression_methods));
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function encrypt(Object\JWEInterface &$jwe)
55
    {
56
        Assertion::false($jwe->isEncrypted(), 'The JWE is already encrypted.');
57
        Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.');
58
        $additional_headers = [];
59
        $nb_recipients = $jwe->countRecipients();
60
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe);
61
        $compression_method = $this->getCompressionMethod($jwe);
62
        $key_management_mode = $this->getKeyManagementMode($jwe);
63
        $cek = $this->determineCEK($jwe, $content_encryption_algorithm, $key_management_mode, $additional_headers);
64
65
        for ($i = 0; $i < $nb_recipients; $i++) {
66
            $this->processRecipient($jwe, $jwe->getRecipient($i), $cek, $content_encryption_algorithm, $additional_headers);
67
        }
68
69
        if (!empty($additional_headers) && 1 === $jwe->countRecipients()) {
70
            $jwe = $jwe->withSharedProtectedHeaders(array_merge($jwe->getSharedProtectedHeaders(), $additional_headers));
71
        }
72
73
        $iv_size = $content_encryption_algorithm->getIVSize();
74
        $iv = $this->createIV($iv_size);
75
76
        $this->encryptJWE($jwe, $content_encryption_algorithm, $cek, $iv, $compression_method);
77
    }
78
79
    /**
80
     * @param \Jose\Object\JWEInterface                           $jwe
81
     * @param \Jose\Object\RecipientInterface                     $recipient
82
     * @param string                                              $cek
83
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
84
     * @param array                                               $additional_headers
85
     */
86
    private function processRecipient(Object\JWEInterface $jwe, Object\RecipientInterface &$recipient, $cek, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers)
87
    {
88
        if (null === $recipient->getRecipientKey()) {
89
            return;
90
        }
91
        $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
92
        $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
93
        $this->checkKeys($key_encryption_algorithm, $content_encryption_algorithm, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 92 can be null; however, Jose\Behaviour\EncrypterTrait::checkKeys() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
94
        $encrypted_content_encryption_key = $this->getEncryptedKey($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient->getRecipientKey());
0 ignored issues
show
Bug introduced by
It seems like $key_encryption_algorithm defined by $this->findKeyEncryption...ithm($complete_headers) on line 92 can be null; however, Jose\Encrypter::getEncryptedKey() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
95
        $recipient_headers = $recipient->getHeaders();
96
        if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) {
97
            $recipient_headers = array_merge($recipient_headers, $additional_headers);
98
            $additional_headers = [];
99
        }
100
101
        $recipient = Object\Recipient::createRecipientFromLoadedJWE($recipient_headers, $encrypted_content_encryption_key);
102
    }
103
104
    /**
105
     * @param \Jose\Object\JWEInterface                           $jwe
106
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
107
     * @param string                                              $cek
108
     * @param string                                              $iv
109
     * @param \Jose\Compression\CompressionInterface|null         $compression_method
110
     */
111
    private function encryptJWE(Object\JWEInterface &$jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $cek, $iv, Compression\CompressionInterface $compression_method = null)
112
    {
113
        if (!empty($jwe->getSharedProtectedHeaders())) {
114
            $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders())));
115
        }
116
117
        $tag = null;
118
        $payload = $this->preparePayload($jwe->getPayload(), $compression_method);
119
        $aad = null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD());
120
        $ciphertext = $content_encryption_algorithm->encryptContent($payload, $cek, $iv, $aad, $jwe->getEncodedSharedProtectedHeaders(), $tag);
121
        $jwe = $jwe->withCiphertext($ciphertext);
122
        $jwe = $jwe->withIV($iv);
123
124
        if (null !== $tag) {
125
            $jwe = $jwe->withTag($tag);
126
        }
127
    }
128
129
    /**
130
     * @param string                                      $payload
131
     * @param \Jose\Compression\CompressionInterface|null $compression_method
132
     *
133
     * @return string
134
     */
135
    private function preparePayload($payload, Compression\CompressionInterface $compression_method = null)
136
    {
137
        $prepared = is_string($payload) ? $payload : json_encode($payload);
138
        Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.');
139
140
        if (null === $compression_method) {
141
            return $prepared;
142
        }
143
        $compressed_payload = $compression_method->compress($prepared);
144
        Assertion::string($compressed_payload, 'Compression failed.');
145
146
        return $compressed_payload;
147
    }
148
149
    /**
150
     * @param array                                               $complete_headers
151
     * @param string                                              $cek
152
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
153
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
154
     * @param \Jose\Object\JWKInterface                           $recipient_key
155
     * @param array                                               $additional_headers
156
     *
157
     * @return string|null
158
     */
159
    private function getEncryptedKey(array $complete_headers, $cek, Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
160
    {
161
        if ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyEncryptionInterface) {
162
            return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
163
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyWrappingInterface) {
164
            return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers);
165
        } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyAgreementWrappingInterface) {
166
            return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key);
167
        }
168
    }
169
170
    /**
171
     * @param array                                                       $complete_headers
172
     * @param string                                                      $cek
173
     * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm
174
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface         $content_encryption_algorithm
175
     * @param array                                                       $additional_headers
176
     * @param \Jose\Object\JWKInterface                                   $recipient_key
177
     *
178
     * @return string
179
     */
180
    private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key)
181
    {
182
        $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers);
183
184
        return $jwt_cek;
185
    }
186
187
    /**
188
     * @param array                                                $complete_headers
189
     * @param string                                               $cek
190
     * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm
191
     * @param \Jose\Object\JWKInterface                            $recipient_key
192
     * @param array                                                $additional_headers
193
     *
194
     * @return string
195
     */
196
    private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, array &$additional_headers)
197
    {
198
        return $key_encryption_algorithm->encryptKey($recipient_key,  $cek, $complete_headers, $additional_headers);
199
    }
200
201
    /**
202
     * @param array                                              $complete_headers
203
     * @param string                                             $cek
204
     * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm
205
     * @param \Jose\Object\JWKInterface                          $recipient_key
206
     * @param array                                              $additional_headers
207
     *
208
     * @return string
209
     */
210
    private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, &$additional_headers)
211
    {
212
        return $key_encryption_algorithm->wrapKey($recipient_key, $cek, $complete_headers, $additional_headers);
213
    }
214
215
    /**
216
     * @param array $complete_headers
217
     *
218
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
219
     */
220
    private function findKeyEncryptionAlgorithm(array $complete_headers)
221
    {
222
        Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
223
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
224
        Assertion::isInstanceOf($key_encryption_algorithm, Algorithm\KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg']));
225
226
        return $key_encryption_algorithm;
227
    }
228
}
229