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

EncrypterTrait::getContentEncryptionAlgorithm()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 3
eloc 12
nc 3
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\Algorithm\KeyEncryptionAlgorithmInterface     $key_encryption_algorithm
24
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
25
     * @param \Jose\Object\JWKInterface                           $recipient_key
26
     */
27
    private function checkKeys(Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, Object\JWKInterface $recipient_key)
28
    {
29
        $this->checkKeyUsage($recipient_key, 'encryption');
0 ignored issues
show
Bug introduced by
It seems like checkKeyUsage() 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...
30
        if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) {
31
            $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...
32
        } else {
33
            $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...
34
        }
35
    }
36
37
    /**
38
     * @param \Jose\Object\JWEInterface                           $jwe
39
     * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm
40
     * @param string                                              $key_management_mode
41
     * @param array                                               $additional_headers
42
     *
43
     * @return string
44
     */
45
    private function determineCEK(Object\JWEInterface $jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $key_management_mode, array &$additional_headers)
46
    {
47
        switch ($key_management_mode) {
48
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_ENCRYPT:
49
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_WRAP:
50
                return $this->createCEK($content_encryption_algorithm->getCEKSize());
51
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_AGREEMENT:
52
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
53
                $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient(0)->getHeaders());
54
                $algorithm = $this->findKeyEncryptionAlgorithm($complete_headers);
0 ignored issues
show
Bug introduced by
It seems like findKeyEncryptionAlgorithm() 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...
55
56
                return $algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $content_encryption_algorithm->getAlgorithmName(), $jwe->getRecipient(0)->getRecipientKey(), $complete_headers, $additional_headers);
57
            case Algorithm\KeyEncryption\KeyEncryptionInterface::MODE_DIRECT:
58
                Assertion::eq(1, $jwe->countRecipients(), 'Unable to encrypt for multiple recipients using key agreement algorithms.');
59
                Assertion::eq($jwe->getRecipient(0)->getRecipientKey()->get('kty'), 'oct', 'Wrong key type.');
60
                Assertion::true($jwe->getRecipient(0)->getRecipientKey()->has('k'), 'The key parameter "k" is missing.');
61
62
                return Base64Url::decode($jwe->getRecipient(0)->getRecipientKey()->get('k'));
63
            default:
64
                throw new \InvalidArgumentException(sprintf('Unsupported key management mode "%s".', $key_management_mode));
65
        }
66
    }
67
68
    /**
69
     * @param \Jose\Object\JWEInterface $jwe
70
     *
71
     * @return string
72
     */
73
    private function getKeyManagementMode(Object\JWEInterface $jwe)
74
    {
75
        $mode = null;
76
        $recipients = $jwe->getRecipients();
77
78
        foreach ($recipients as $recipient) {
79
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
80
            Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.');
81
82
            $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
0 ignored issues
show
Bug introduced by
It seems like getJWAManager() 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...
83
            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']));
84
85
            if (null === $mode) {
86
                $mode = $key_encryption_algorithm->getKeyManagementMode();
87
            } else {
88
                Assertion::true($this->areKeyManagementModesCompatible($mode, $key_encryption_algorithm->getKeyManagementMode()), 'Foreign key management mode forbidden.');
89
            }
90
        }
91
92
        return $mode;
93
    }
94
95
    /**
96
     * @param \Jose\Object\JWEInterface $jwe
97
     *
98
     * @return \Jose\Compression\CompressionInterface|null
99
     */
100
    private function getCompressionMethod(Object\JWEInterface $jwe)
101
    {
102
        $method = null;
103
        $nb_recipients = $jwe->countRecipients();
104
105
        for ($i = 0; $i < $nb_recipients; $i++) {
106
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $jwe->getRecipient($i)->getHeaders());
107
            if (array_key_exists('zip', $complete_headers)) {
108
                if (null === $method) {
109
                    if (0 === $i) {
110
                        $method = $complete_headers['zip'];
111
                    } else {
112
                        throw new \InvalidArgumentException('Inconsistent "zip" parameter.');
113
                    }
114
                } else {
115
                    Assertion::eq($method, $complete_headers['zip'], 'Inconsistent "zip" parameter.');
116
                }
117
            } else {
118
                Assertion::eq(null, $method, 'Inconsistent "zip" parameter.');
119
            }
120
        }
121
122
        if (null === $method) {
123
            return;
124
        }
125
126
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
0 ignored issues
show
Bug introduced by
It seems like getCompressionManager() 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...
127
        Assertion::isInstanceOf($compression_method, Compression\CompressionInterface::class, sprintf('Compression method "%s" not supported.', $method));
128
129
        return $compression_method;
130
    }
131
132
    /**
133
     * @param \Jose\Object\JWEInterface $jwe
134
     *
135
     * @return \Jose\Algorithm\ContentEncryptionAlgorithmInterface
136
     */
137
    private function getContentEncryptionAlgorithm(Object\JWEInterface $jwe)
138
    {
139
        $algorithm = null;
140
141
        foreach ($jwe->getRecipients() as $recipient) {
142
            $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders());
143
            Assertion::keyExists($complete_headers, 'enc', 'Parameter "enc" is missing.');
144
            if (null === $algorithm) {
145
                $algorithm = $complete_headers['enc'];
146
            } else {
147
                Assertion::eq($algorithm, $complete_headers['enc'], 'Foreign content encryption algorithms are not allowed.');
148
            }
149
        }
150
151
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
0 ignored issues
show
Bug introduced by
It seems like getJWAManager() 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...
152
        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));
153
154
        return $content_encryption_algorithm;
155
    }
156
157
    /**
158
     * @param string $current
159
     * @param string $new
160
     *
161
     * @return bool
162
     */
163
    private function areKeyManagementModesCompatible($current, $new)
164
    {
165
        $agree = Algorithm\KeyEncryptionAlgorithmInterface::MODE_AGREEMENT;
166
        $dir = Algorithm\KeyEncryptionAlgorithmInterface::MODE_DIRECT;
167
        $enc = Algorithm\KeyEncryptionAlgorithmInterface::MODE_ENCRYPT;
168
        $wrap = Algorithm\KeyEncryptionAlgorithmInterface::MODE_WRAP;
169
        $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,];
170
171
        if (array_key_exists($current.$new, $supported_key_management_mode_combinations)) {
172
            return $supported_key_management_mode_combinations[$current.$new];
173
        }
174
175
        return false;
176
    }
177
178
    /**
179
     * @param int $size
180
     *
181
     * @return string
182
     */
183
    private function createCEK($size)
184
    {
185
        return random_bytes($size / 8);
186
    }
187
188
    /**
189
     * @param int $size
190
     *
191
     * @return string
192
     */
193
    private function createIV($size)
194
    {
195
        return random_bytes($size / 8);
196
    }
197
}
198