Completed
Push — master ( 6593bf...678353 )
by Florent
02:22
created

Decrypter::getKeysFromCompleteHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4286
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2015 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 Jose\Algorithm\ContentEncryption\ContentEncryptionInterface;
15
use Jose\Algorithm\JWAInterface;
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\Behaviour\HasCheckerManager;
22
use Jose\Behaviour\HasCompressionManager;
23
use Jose\Behaviour\HasJWAManager;
24
use Jose\Behaviour\HasKeyChecker;
25
use Jose\Behaviour\HasPayloadConverter;
26
use Jose\Checker\CheckerManagerInterface;
27
use Jose\Compression\CompressionManagerInterface;
28
use Jose\Object\JWEInterface;
29
use Jose\Object\JWKInterface;
30
use Jose\Object\JWKSetInterface;
31
use Jose\Payload\PayloadConverterManagerInterface;
32
33
/**
34
 */
35
final class Decrypter implements DecrypterInterface
36
{
37
    use HasKeyChecker;
38
    use HasJWAManager;
39
    use HasCheckerManager;
40
    use HasPayloadConverter;
41
    use HasCompressionManager;
42
43
    /**
44
     * Loader constructor.
45
     *
46
     * @param \Jose\Algorithm\JWAManagerInterface            $jwa_manager
47
     * @param \Jose\Payload\PayloadConverterManagerInterface $payload_converter_manager
48
     * @param \Jose\Compression\CompressionManagerInterface  $compression_manager
49
     * @param \Jose\Checker\CheckerManagerInterface          $checker_manager
50
     */
51
    public function __construct(
52
        JWAManagerInterface $jwa_manager,
53
        PayloadConverterManagerInterface $payload_converter_manager,
54
        CompressionManagerInterface $compression_manager,
55
        CheckerManagerInterface $checker_manager)
56
    {
57
        $this->setJWAManager($jwa_manager);
58
        $this->setPayloadConverter($payload_converter_manager);
59
        $this->setCompressionManager($compression_manager);
60
        $this->setCheckerManager($checker_manager);
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function decrypt(JWEInterface &$jwe, JWKSetInterface $jwk_set)
67
    {
68
        $this->checkCompleteHeader($jwe->getHeaders());
69
        $key_encryption_algorithm = $this->getKeyEncryptionAlgorithm($jwe->getHeader('alg'));
70
        $content_encryption_algorithm = $this->getContentEncryptionAlgorithm($jwe->getHeader('enc'));
71
72
        foreach ($jwk_set as $jwk) {
73
            if (!$this->checkKeyUsage($jwk, 'decryption')) {
74
                continue;
75
            }
76
            if (!$this->checkKeyAlgorithm($jwk, $key_encryption_algorithm->getAlgorithmName())) {
77
                continue;
78
            }
79
            try {
80
                $cek = $this->decryptCEK($key_encryption_algorithm, $content_encryption_algorithm, $jwk, $jwe->getEncryptedKey(), $jwe->getHeaders());
81
82
                if (null !== $cek) {
83
                    if (true === $this->decryptPayload($jwe, $cek, $content_encryption_algorithm)) {
84
                        $this->getCheckerManager()->checkJWT($jwe);
85
86
                        return true;
87
                    }
88
                }
89
            } catch (\Exception $e) {
90
                //We do nothing, we continue with other keys
91
            }
92
        }
93
94
        return false;
95
    }
96
97
    /**
98
     * @param \Jose\Algorithm\JWAInterface                                 $key_encryption_algorithm
99
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
100
     * @param \Jose\Object\JWKInterface                                    $key
101
     * @param string|null                                                  $encrypted_cek
102
     * @param array                                                        $header
103
     *
104
     * @return string|null
105
     */
106
    private function decryptCEK(JWAInterface $key_encryption_algorithm, ContentEncryptionInterface $content_encryption_algorithm, JWKInterface $key, $encrypted_cek, array $header)
107
    {
108
        if ($key_encryption_algorithm instanceof DirectEncryptionInterface) {
109
            return $key_encryption_algorithm->getCEK($key, $header);
110
        } elseif ($key_encryption_algorithm instanceof KeyAgreementInterface) {
111
            return $key_encryption_algorithm->getAgreementKey($content_encryption_algorithm->getCEKSize(), $key, null, $header);
112
        } elseif ($key_encryption_algorithm instanceof KeyAgreementWrappingInterface) {
113
            return $key_encryption_algorithm->unwrapAgreementKey($key, $encrypted_cek, $content_encryption_algorithm->getCEKSize(), $header);
114
        } elseif ($key_encryption_algorithm instanceof KeyEncryptionInterface) {
115
            return $key_encryption_algorithm->decryptKey($key, $encrypted_cek, $header);
116
        } else {
117
            throw new \RuntimeException('Unsupported CEK generation');
118
        }
119
    }
120
121
    /**
122
     * @param \Jose\Object\JWEInterface                                    $jwe
123
     * @param string                                                       $cek
124
     * @param \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface $content_encryption_algorithm
125
     *
126
     * @return \Jose\Object\JWEInterface
127
     */
128
    private function decryptPayload(JWEInterface &$jwe, $cek, $content_encryption_algorithm)
129
    {
130
        $payload = $content_encryption_algorithm->decryptContent(
131
            $jwe->getCiphertext(),
132
            $cek,
133
            $jwe->getIV(),
134
            $jwe->getAAD(),
135
            $jwe->getEncodedProtectedHeaders(),
136
            $jwe->getTag()
137
        );
138
139
        if (null === $payload) {
140
            return false;
141
        }
142
143
        if ($jwe->hasHeader('zip')) {
144
            $compression_method = $this->getCompressionMethod($jwe->getHeader('zip'));
145
            $payload = $compression_method->uncompress($payload);
146
            if (!is_string($payload)) {
147
                throw new \RuntimeException('Decompression failed');
148
            }
149
        }
150
151
        $payload = $this->getPayloadConverter()->convertStringToPayload($jwe->getHeaders(), $payload);
152
153
        $jwe = $jwe->withPayload($payload);
154
155
        return true;
156
    }
157
158
    /**
159
     * @param array $complete_header
160
     *
161
     * @throws \InvalidArgumentException
162
     */
163
    private function checkCompleteHeader(array $complete_header)
164
    {
165
        foreach (['enc', 'alg'] as $key) {
166
            if (!array_key_exists($key, $complete_header)) {
167
                throw new \InvalidArgumentException(sprintf("Parameters '%s' is missing.", $key));
168
            }
169
        }
170
    }
171
172
    /**
173
     * @param string $algorithm
174
     *
175
     * @return \Jose\Algorithm\KeyEncryption\DirectEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementInterface|\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface
176
     */
177
    private function getKeyEncryptionAlgorithm($algorithm)
178
    {
179
        $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
180
        foreach ([
181
                     '\Jose\Algorithm\KeyEncryption\DirectEncryptionInterface',
182
                     '\Jose\Algorithm\KeyEncryption\KeyEncryptionInterface',
183
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementInterface',
184
                     '\Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface',
185
                 ] as $class) {
186
            if ($key_encryption_algorithm instanceof $class) {
187
                return $key_encryption_algorithm;
188
            }
189
        }
190
        throw new \RuntimeException(sprintf("The key encryption algorithm '%s' is not supported or not a key encryption algorithm instance.", $algorithm));
191
    }
192
193
    /**
194
     * @param $algorithm
195
     *
196
     * @return \Jose\Algorithm\ContentEncryption\ContentEncryptionInterface
197
     */
198
    private function getContentEncryptionAlgorithm($algorithm)
199
    {
200
        $content_encryption_algorithm = $this->getJWAManager()->getAlgorithm($algorithm);
201
        if (!$content_encryption_algorithm instanceof ContentEncryptionInterface) {
202
            throw new \RuntimeException("The algorithm '".$algorithm."' does not implement ContentEncryptionInterface.");
203
        }
204
205
        return $content_encryption_algorithm;
206
    }
207
208
    /**
209
     * @param string $method
210
     *
211
     * @throws \InvalidArgumentException
212
     *
213
     * @return \Jose\Compression\CompressionInterface
214
     */
215
    private function getCompressionMethod($method)
216
    {
217
        $compression_method = $this->getCompressionManager()->getCompressionAlgorithm($method);
218
        if (null === $compression_method) {
219
            throw new \InvalidArgumentException(sprintf("Compression method '%s' not supported"), $method);
220
        }
221
222
        return $compression_method;
223
    }
224
}
225