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

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