Completed
Push — master ( d8c018...19ac78 )
by Florent
06:38
created

JWKSet::convertKeyOpsToKeyUse()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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