Completed
Push — master ( c7f88f...ebb9ba )
by Florent
03:09
created

EncrypterTrait::findKeyEncryptionAlgorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
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\Behaviour;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use Jose\Algorithm;
17
use Jose\Compression;
18
use Jose\Object;
19
20
trait EncrypterTrait
21
{
22
    /**
23
     * @param \Jose\Object\JWKInterface $key
24
     * @param string                    $usage
25
     *
26
     * @throws \InvalidArgumentException
27
     *
28
     * @return bool
29
     */
30
    abstract protected function checkKeyUsage(Object\JWKInterface $key, $usage);
31
32
    /**
33
     * @return \Jose\Algorithm\JWAManagerInterface
34
     */
35
    abstract protected function getJWAManager();
36
37
    /**
38
     * @return \Jose\Compression\CompressionManagerInterface
39
     */
40
    abstract protected function getCompressionManager();
41
42
    /**
43
     * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
44
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
45
     * @param \Jose\Object\JWKInterface                           $recipient_key
46
     */
47
    private function checkKeys(Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, Object\JWKInterface $recipient_key)
48
    {
49
        $this->checkKeyUsage($recipient_key, 'encryption');
50
        if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) {
51
            $this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName());
0 ignored issues
show
Bug introduced by
It seems like checkKeyAlgorithm() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
52
        } else {
53
            $this->checkKeyAlgorithm($recipient_key, $content_encryption_algorithm->getAlgorithmName());
0 ignored issues
show
Bug introduced by
It seems like checkKeyAlgorithm() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
54
        }
55
    }
56
57
    /**
58
     * @param \Jose\Object\JWEInterface                           $jwe
59
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
60
     * @param string                                              $key_management_mode
61
     * @param array                                               $additional_headers
62
     *
63
     * @return string
64
     */
65
    private function determineCEK(Object\JWEInterface $jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $key_management_mode, array &$additional_headers)
66
    {
67
        switch ($key_management_mode) {
68
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_ENCRYPT:
69
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_WRAP:
70
                return $this->createCEK($content_encryption_algorithm->getCEKSize());
71
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_AGREEMENT:
72
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
73
                $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient(0)->getHeaders());
74
                $algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
75
76
                return $algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->getAlgorithmName(), $jwe->getRecipient(0)->getRecipientKey(), $complete_headers, $additional_headers);
77
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_DIRECT:
78
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
79
                Assertion::eq($jwe->getRecipient(0)->getRecipientKey()->get('kty'), 'oct', 'Wrong key type.');
80
                Assertion::true($jwe->getRecipient(0)->getRecipientKey()->has('k'), 'The key parameter "k" is missing.');
81
82
                return Base64Url::decode($jwe->getRecipient(0)->getRecipientKey()->get('k'));
83
            default:
84
                throw new \InvalidArgumentException(sprintf('Unsupported key management mode "%s".', $key_management_mode));
85
        }
86
    }
87
88
    /**
89
     * @param \Jose\Object\JWEInterface $jwe
90
     *
91
     * @return string
92
     */
93
    private function getKeyManagementMode(Object\JWEInterface $jwe)
94
    {
95
        $mode = null;
96
        $recipients = $jwe->getRecipients();
97
98
        foreach ($recipients as $recipient) {
99
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
100
            Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
101
102
            $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
103
            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']));
104
105
            if (null === $mode) {
106
                $mode = $key_encryption_algorithm->getKeyManagementMode();
107
            } else {
108
                Assertion::true($this->areKeyManagementModesCompatible($mode, $key_encryption_algorithm->getKeyManagementMode()), 'Foreign key management mode forbidden.');
109
            }
110
        }
111
112
        return $mode;
113
    }
114
115
    /**
116
     * @param \Jose\Object\JWEInterface $jwe
117
     *
118
     * @return \Jose\Compression\CompressionInterface|null
119
     */
120
    private function getCompressionMethod(Object\JWEInterface $jwe)
121
    {
122
        $method = null;
123
        $nb_recipients = $jwe->countRecipients();
124
125
        for ($i = 0; $i < $nb_recipients; $i++) {
126
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient($i)->getHeaders());
127
            if (array_key_exists('zip', $complete_headers)) {
128
                if (null === $method) {
129
                    if (0 === $i) {
130
                        $method = $complete_headers['zip'];
131
                    } else {
132
                        throw new \InvalidArgumentException('Inconsistent "zip" parameter.');
133
                    }
134
                } else {
135
                    Assertion::eq($method, $complete_headers['zip'], 'Inconsistent "zip" parameter.');
136
                }
137
            } else {
138
                Assertion::eq(null, $method, 'Inconsistent "zip" parameter.');
139
            }
140
        }
141
142
        if (null === $method) {
143
            return;
144
        }
145
146
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
147
        Assertion::isInstanceOf($compression_method, Compression\CompressionInterface::class, sprintf('Compression method "%s" not supported.', $method));
148
149
        return $compression_method;
150
    }
151
152
    /**
153
     * @param \Jose\Object\JWEInterface $jwe
154
     *
155
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
156
     */
157
    private function getContentEncryptionAlgorithm(Object\JWEInterface $jwe)
158
    {
159
        $algorithm = null;
160
161
        foreach ($jwe->getRecipients() as $recipient) {
162
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
163
            Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.');
164
            if (null === $algorithm) {
165
                $algorithm = $complete_headers['enc'];
166
            } else {
167
                Assertion::eq($algorithm, $complete_headers['enc'], 'Foreign content encryption algorithms are not allowed.');
168
            }
169
        }
170
171
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
172
        Assertion::isInstanceOf($content_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface::class, sprintf('The content encryption algorithm "%s" is not supported or not a content encryption algorithm instance.', $algorithm));
173
174
        return $content_encryption_algorithm;
175
    }
176
177
    /**
178
     * @param string $current
179
     * @param string $new
180
     *
181
     * @return bool
182
     */
183
    private function areKeyManagementModesCompatible($current, $new)
184
    {
185
        $agree = Algorithm\KeyEncryptionAlgorithmInterface::MODE_AGREEMENT;
186
        $dir = Algorithm\KeyEncryptionAlgorithmInterface::MODE_DIRECT;
187
        $enc = Algorithm\KeyEncryptionAlgorithmInterface::MODE_ENCRYPT;
188
        $wrap = Algorithm\KeyEncryptionAlgorithmInterface::MODE_WRAP;
189
        $supported_key_management_mode_combinations = [$enc.$enc     => true,$enc.$wrap    => true,$wrap.$enc    => true,$wrap.$wrap   => true,$agree.$agree => false,$agree.$dir   => false,$agree.$enc   => false,$agree.$wrap  => false,$dir.$agree   => false,$dir.$dir     => false,$dir.$enc     => false,$dir.$wrap    => false,$enc.$agree   => false,$enc.$dir     => false,$wrap.$agree  => false,$wrap.$dir    => false,];
190
191
        if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) {
192
            return $supported_key_management_mode_combinations[$current.$new];
193
        }
194
195
        return false;
196
    }
197
198
    /**
199
     * @param int $size
200
     *
201
     * @return string
202
     */
203
    private function createCEK($size)
204
    {
205
        return random_bytes($size / 8);
206
    }
207
208
    /**
209
     * @param int $size
210
     *
211
     * @return string
212
     */
213
    private function createIV($size)
214
    {
215
        return random_bytes($size / 8);
216
    }
217
218
    /**
219
     * @param array $complete_headers
220
     *
221
     * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface
222
     */
223
    private function findKeyEncryptionAlgorithm(array $complete_headers)
224
    {
225
        Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
226
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
227
        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']));
228
229
        return $key_encryption_algorithm;
230
    }
231
}
232