Completed
Push — develop ( 2c0de5...f9719b )
by Florent
02:40
created

JWKSet::canKeyBeUsedWithAlgorithm()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 11
rs 9.2
cc 4
eloc 6
nc 4
nop 2
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
     * {@inheritdoc}
149
     */
150
    public function offsetExists($offset)
151
    {
152
        return $this->hasKey($offset);
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158
    public function offsetGet($offset)
159
    {
160
        return $this->getKey($offset);
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function offsetSet($offset, $value)
167
    {
168
        $this->addKey($value);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function offsetUnset($offset)
175
    {
176
        $this->removeKey($offset);
177
    }
178
179
    /**
180
     * @param string      $type
181
     * @param string|null $algorithm
182
     * @param array       $restrictions
183
     *
184
     * @return \Jose\Object\JWKInterface|null
185
     */
186
    public function selectKey($type, $algorithm = null, array $restrictions = [])
187
    {
188
        Assertion::inArray($type, ['enc', 'sig']);
189
        Assertion::nullOrString($algorithm);
190
191
        $result = [];
192
        foreach ($this->keys as $key) {
193
            $ind = 0;
194
            
195
            // Check usage
196
            $can_use = $this->canKeyBeUsedFor($type, $key);
197
            if (false === $can_use) {
198
                continue;
199
            }
200
            $ind += $can_use;
201
            
202
            // Check algorithm
203
            $alg = $this->canKeyBeUsedWithAlgorithm($algorithm, $key);
204
            if (false === $alg) {
205
                continue;
206
            }
207
            $ind += $alg;
208
            
209
            // Validate restrictions
210
            if (false === $this->doesKeySatisfyRestrictions($restrictions, $key)) {
211
                continue;
212
            }
213
            
214
            // Add to the list with trust indicator
215
            $result[] = ['key' => $key, 'ind' => $ind];
216
        }
217
218
        //Return null if no key
219
        if (empty($result)) {
220
            return;
221
        }
222
223
        //Sort by trust indicator
224
        usort($result, [$this, 'sortKeys']);
225
        //Return the highest trust indicator (first key)
226
        return $result[0]['key'];
227
    }
228
229
    /**
230
     * @param string                    $type
231
     * @param \Jose\Object\JWKInterface $key
232
     *
233
     * @return bool|int
234
     */
235
    private function canKeyBeUsedFor($type, JWKInterface $key)
236
    {
237
        if ($key->has('use')) {
238
            return $type === $key->get('use') ? 1 : false;
239
        }
240
        if ($key->has('key_ops')) {
241
            return $type === self::convertKeyOpsToKeyUse($key->get('use')) ? 1 : false;
242
        }
243
        
244
        return 0;
245
    }
246
247
    /**
248
     * @param null|string               $algorithm
249
     * @param \Jose\Object\JWKInterface $key
250
     *
251
     * @return bool|int
252
     */
253
    private function canKeyBeUsedWithAlgorithm($algorithm, JWKInterface $key)
254
    {
255
        if (null === $algorithm) {
256
            return 0;
257
        }
258
        if ($key->has('alg')) {
259
            return $algorithm === $key->get('alg') ? 1 : false;
260
        }
261
        
262
        return 0;
263
    }
264
265
    /**
266
     * @param array                     $restrictions
267
     * @param \Jose\Object\JWKInterface $key
268
     *
269
     * @return bool
270
     */
271
    private function doesKeySatisfyRestrictions(array $restrictions, JWKInterface $key)
272
    {
273
        foreach ($restrictions as $k=>$v) {
274
            if (!$key->has($k) || $v !== $key->get($k)) {
275
                return false;
276
            }
277
        }
278
        
279
        return true;
280
    }
281
282
    /**
283
     * @param string $key_ops
284
     * 
285
     * @return string
286
     */
287
    private static function convertKeyOpsToKeyUse($key_ops)
288
    {
289
        switch ($key_ops) {
290
            case 'verify':
291
            case 'sign':
292
                return 'sig';
293
            case 'encrypt':
294
            case 'decrypt':
295
            case 'wrapKey':
296
            case 'unwrapKey':
297
                return 'enc';
298
            default:
299
                throw new \InvalidArgumentException(sprintf('Unsupported key operation value "%s"', $key_ops));
300
        }
301
    }
302
303
    /**
304
     * @param array $a
305
     * @param array $b
306
     *
307
     * @return int
308
     */
309
    public function sortKeys($a, $b)
310
    {
311
        if ($a['ind'] === $b['ind']) {
312
            return 0;
313
        }
314
        return ($a['ind'] > $b['ind']) ? -1 : 1;
315
    }
316
}
317