Failed Conditions
Push — JWKSet ( 3580c0 )
by Florent
04:07
created

JWKSets::convertKeyOpsToKeyUse()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.2222
c 0
b 0
f 0
cc 7
eloc 12
nc 7
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\Object;
13
use Assert\Assertion;
14
15
/**
16
 * Class JWKSets
17
 */
18
final class JWKSets implements JWKSetInterface
19
{
20
    /**
21
     * @var int
22
     */
23
    private $position = 0;
24
25
    /**
26
     * @var \Jose\Object\JWKSetInterface[]
27
     */
28
    private $jwksets = [];
29
30
    /**
31
     * JWKSets constructor.
32
     *
33
     * @param \Jose\Object\JWKSetInterface[] $jwksets
34
     */
35
    public function __construct(array $jwksets)
36
    {
37
        Assertion::allIsInstanceOf($jwksets, JWKSetInterface::class);
38
39
        $this->jwksets = $jwksets;
40
    }
41
42
    /**
43
     * PublicJWKSet constructor.
44
     *
45
     * @param \Jose\Object\JWKSetInterface $jwkset
46
     */
47
    public function addKeySet(JWKSetInterface $jwkset)
48
    {
49
        $this->jwksets[] = $jwkset;
50
    }
51
52
    /**
53
     * @inheritdoc
54
     */
55
    public function current()
56
    {
57
        return $this->position;
58
    }
59
60
    /**
61
     * @inheritdoc
62
     */
63
    public function next()
64
    {
65
        $this->position++;
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71
    public function key()
72
    {
73
        return $this->position;
74
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79
    public function valid()
80
    {
81
        return $this->current() instanceof JWKInterface;
82
    }
83
84
    /**
85
     * @inheritdoc
86
     */
87
    public function rewind()
88
    {
89
        $this->position = 0;
90
    }
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function offsetExists($offset)
96
    {
97
        return $this->hasKey($offset);
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103
    public function offsetGet($offset)
104
    {
105
        return $this->getKey($offset);
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function offsetSet($offset, $value)
112
    {
113
        //Not available
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function offsetUnset($offset)
120
    {
121
        //Not available
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function hasKey($index)
128
    {
129
        return array_key_exists($index, $this->getKeys());
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function getKey($index)
136
    {
137
        Assertion::true($this->hasKey($index), 'Undefined index.');
138
139
        return $this->getKeys()[$index];
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145
    public function getKeys()
146
    {
147
        $keys = [];
148
149
        foreach ($this->jwksets as $jwkset) {
150
            $keys =array_merge(
151
                $keys,
152
                $jwkset->getKeys()
153
            );
154
        }
155
156
        return $keys;
157
    }
158
159
    /**
160
     * @inheritdoc
161
     */
162
    public function addKey(JWKInterface $key)
163
    {
164
        //Not available
165
    }
166
167
    /**
168
     * @inheritdoc
169
     */
170
    public function removeKey($index)
171
    {
172
        //Not available
173
    }
174
175
    /**
176
     * @inheritdoc
177
     */
178
    public function countKeys()
179
    {
180
        return count($this->getKeys());
181
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186
    public function count()
187
    {
188
        return $this->countKeys();
189
    }
190
191
    /**
192
     * @inheritdoc
193
     */
194
    public function jsonSerialize()
195
    {
196
        return ['keys' => array_values($this->getKeys())];
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function selectKey($type, $algorithm = null, array $restrictions = [])
203
    {
204
        Assertion::inArray($type, ['enc', 'sig']);
205
        Assertion::nullOrString($algorithm);
206
207
        $result = [];
208
        $keys = $this->getKeys();
209
        foreach ($keys as $key) {
210
            $ind = 0;
211
212
            // Check usage
213
            $can_use = $this->canKeyBeUsedFor($type, $key);
214
            if (false === $can_use) {
215
                continue;
216
            }
217
            $ind += $can_use;
218
219
            // Check algorithm
220
            $alg = $this->canKeyBeUsedWithAlgorithm($algorithm, $key);
221
            if (false === $alg) {
222
                continue;
223
            }
224
            $ind += $alg;
225
226
            // Validate restrictions
227
            if (false === $this->doesKeySatisfyRestrictions($restrictions, $key)) {
228
                continue;
229
            }
230
231
            // Add to the list with trust indicator
232
            $result[] = ['key' => $key, 'ind' => $ind];
233
        }
234
235
        //Return null if no key
236
        if (empty($result)) {
237
            return;
238
        }
239
240
        //Sort by trust indicator
241
        usort($result, [$this, 'sortKeys']);
242
        //Return the highest trust indicator (first key)
243
        return $result[0]['key'];
244
    }
245
246
    /**
247
     * @param string                    $type
248
     * @param \Jose\Object\JWKInterface $key
249
     *
250
     * @return bool|int
251
     */
252
    private function canKeyBeUsedFor($type, JWKInterface $key)
253
    {
254
        if ($key->has('use')) {
255
            return $type === $key->get('use') ? 1 : false;
256
        }
257
        if ($key->has('key_ops')) {
258
            return $type === self::convertKeyOpsToKeyUse($key->get('use')) ? 1 : false;
259
        }
260
261
        return 0;
262
    }
263
264
    /**
265
     * @param null|string               $algorithm
266
     * @param \Jose\Object\JWKInterface $key
267
     *
268
     * @return bool|int
269
     */
270
    private function canKeyBeUsedWithAlgorithm($algorithm, JWKInterface $key)
271
    {
272
        if (null === $algorithm) {
273
            return 0;
274
        }
275
        if ($key->has('alg')) {
276
            return $algorithm === $key->get('alg') ? 1 : false;
277
        }
278
279
        return 0;
280
    }
281
282
    /**
283
     * @param array                     $restrictions
284
     * @param \Jose\Object\JWKInterface $key
285
     *
286
     * @return bool
287
     */
288
    private function doesKeySatisfyRestrictions(array $restrictions, JWKInterface $key)
289
    {
290
        foreach ($restrictions as $k => $v) {
291
            if (!$key->has($k) || $v !== $key->get($k)) {
292
                return false;
293
            }
294
        }
295
296
        return true;
297
    }
298
299
    /**
300
     * @param string $key_ops
301
     *
302
     * @return string
303
     */
304
    private static function convertKeyOpsToKeyUse($key_ops)
305
    {
306
        switch ($key_ops) {
307
            case 'verify':
308
            case 'sign':
309
                return 'sig';
310
            case 'encrypt':
311
            case 'decrypt':
312
            case 'wrapKey':
313
            case 'unwrapKey':
314
                return 'enc';
315
            default:
316
                throw new \InvalidArgumentException(sprintf('Unsupported key operation value "%s"', $key_ops));
317
        }
318
    }
319
320
    /**
321
     * @param array $a
322
     * @param array $b
323
     *
324
     * @return int
325
     */
326
    public function sortKeys($a, $b)
327
    {
328
        if ($a['ind'] === $b['ind']) {
329
            return 0;
330
        }
331
332
        return ($a['ind'] > $b['ind']) ? -1 : 1;
333
    }
334
}
335