Failed Conditions
Push — createNoneKey ( a5b09c )
by Florent
05:44
created

JWKFactory::createFromX5U()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
c 4
b 1
f 1
dl 0
loc 16
rs 9.4285
cc 3
eloc 10
nc 3
nop 3
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\Factory;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use Jose\KeyConverter\KeyConverter;
17
use Jose\KeyConverter\RSAKey;
18
use Jose\Object\JWK;
19
use Jose\Object\JWKSet;
20
use Jose\Object\StorableJWK;
21
use Mdanter\Ecc\Curves\CurveFactory;
22
use Mdanter\Ecc\Curves\NistCurve;
23
use Mdanter\Ecc\EccFactory;
24
use Psr\Cache\CacheItemPoolInterface;
25
26
final class JWKFactory implements JWKFactoryInterface
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31
    public static function createStorableKey($filename, array $parameters)
32
    {
33
        return new StorableJWK($filename, $parameters);
34
    }
35
36
    /**
37
     * {@inheritdoc}
38
     */
39
    public static function createKey(array $config)
40
    {
41
        Assertion::keyExists($config, 'kty', 'The key "kty" must be set');
42
        $supported_types = ['RSA' => 'RSA', 'OKP' => 'OKP', 'EC' => 'EC', 'oct' => 'Oct', 'none' => 'None'];
43
        $kty = $config['kty'];
44
        Assertion::keyExists($supported_types, $kty, sprintf('The key type "%s" is not supported. Please use one of %s', $kty, json_encode(array_keys($supported_types))));
45
        $method = sprintf('create%sKey', $supported_types[$kty]);
46
47
        return self::$method($config);
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public static function createRSAKey(array $values)
54
    {
55
        Assertion::keyExists($values, 'size', 'The key size is not set.');
56
        $size = $values['size'];
57
        unset($values['size']);
58
59
        Assertion::true(0 === $size % 8, 'Invalid key size.');
60
        Assertion::greaterOrEqualThan($size, 384, 'Key length is too short. It needs to be at least 384 bits.');
61
62
        $key = openssl_pkey_new([
63
            'private_key_bits' => $size,
64
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
65
        ]);
66
        openssl_pkey_export($key, $out);
67
        $rsa = new RSAKey($out);
68
        $values = array_merge(
69
            $values,
70
            $rsa->toArray()
71
        );
72
73
        return new JWK($values);
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public static function createECKey(array $values)
80
    {
81
        Assertion::keyExists($values, 'crv', 'The curve is not set.');
82
        $curve = $values['crv'];
83
        $curve_name = self::getNistName($curve);
84
        $generator = CurveFactory::getGeneratorByName($curve_name);
85
        $private_key = $generator->createPrivateKey();
86
87
        $values = array_merge(
88
            $values,
89
            [
90
                'kty' => 'EC',
91
                'crv' => $curve,
92
                'x'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getX()),
93
                'y'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getY()),
94
                'd'   => self::encodeValue($private_key->getSecret()),
95
            ]
96
        );
97
98
        return new JWK($values);
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public static function createOctKey(array $values)
105
    {
106
        Assertion::keyExists($values, 'size', 'The key size is not set.');
107
        $size = $values['size'];
108
        unset($values['size']);
109
        Assertion::true(0 === $size % 8, 'Invalid key size.');
110
        $values = array_merge(
111
            $values,
112
            [
113
                'kty' => 'oct',
114
                'k'   => Base64Url::encode(random_bytes($size / 8)),
115
            ]
116
        );
117
118
        return new JWK($values);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public static function createOKPKey(array $values)
125
    {
126
        Assertion::keyExists($values, 'crv', 'The curve is not set.');
127
        $curve = $values['crv'];
128
        switch ($curve) {
129
            case 'X25519':
130
                Assertion::true(function_exists('curve25519_public'), sprintf('Unsupported "%s" curve', $curve));
131
                $d = random_bytes(32);
132
                $x = curve25519_public($d);
133
                break;
134
            case 'Ed25519':
135
                Assertion::true(function_exists('ed25519_publickey'), sprintf('Unsupported "%s" curve', $curve));
136
                $d = random_bytes(32);
137
                $x = ed25519_publickey($d);
138
                break;
139
            default:
140
                throw new \InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
141
        }
142
143
        $values = array_merge(
144
            $values,
145
            [
146
                'kty' => 'OKP',
147
                'crv' => $curve,
148
                'x'   => Base64Url::encode($x),
149
                'd'   => Base64Url::encode($d),
150
            ]
151
        );
152
153
        return new JWK($values);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public static function createNoneKey(array $values)
160
    {
161
        $values = array_merge(
162
            $values,
163
            [
164
                'kty' => 'none',
165
                'alg' => 'none',
166
            ]
167
        );
168
169
        return new JWK($values);
170
    }
171
172
    /**
173
     * @param string $value
174
     *
175
     * @return string
176
     */
177
    private static function encodeValue($value)
178
    {
179
        $value = gmp_strval($value);
180
181
        return Base64Url::encode(self::convertDecToBin($value));
182
    }
183
184
    /**
185
     * @param string $value
186
     *
187
     * @return string
188
     */
189
    private static function convertDecToBin($value)
190
    {
191
        $adapter = EccFactory::getAdapter();
192
193
        return hex2bin($adapter->decHex($value));
194
    }
195
196
    /**
197
     * @param string $curve
198
     *
199
     * @throws \InvalidArgumentException
200
     *
201
     * @return string
202
     */
203
    private static function getNistName($curve)
204
    {
205
        switch ($curve) {
206
            case 'P-256':
207
                return NistCurve::NAME_P256;
208
            case 'P-384':
209
                return NistCurve::NAME_P384;
210
            case 'P-521':
211
                return NistCurve::NAME_P521;
212
            default:
213
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
214
        }
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public static function createFromValues(array $values)
221
    {
222
        if (array_key_exists('keys', $values) && is_array($values['keys'])) {
223
            return new JWKSet($values);
224
        }
225
226
        return new JWK($values);
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public static function createFromCertificateFile($file, array $additional_values = [])
233
    {
234
        $values = KeyConverter::loadKeyFromCertificateFile($file);
235
        $values = array_merge($values, $additional_values);
236
237
        return new JWK($values);
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243
    public static function createFromCertificate($certificate, array $additional_values = [])
244
    {
245
        $values = KeyConverter::loadKeyFromCertificate($certificate);
246
        $values = array_merge($values, $additional_values);
247
248
        return new JWK($values);
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public static function createFromX509Resource($res, array $additional_values = [])
255
    {
256
        $values = KeyConverter::loadKeyFromX509Resource($res);
257
        $values = array_merge($values, $additional_values);
258
259
        return new JWK($values);
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public static function createFromKeyFile($file, $password = null, array $additional_values = [])
266
    {
267
        $values = KeyConverter::loadFromKeyFile($file, $password);
268
        $values = array_merge($values, $additional_values);
269
270
        return new JWK($values);
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public static function createFromKey($key, $password = null, array $additional_values = [])
277
    {
278
        $values = KeyConverter::loadFromKey($key, $password);
279
        $values = array_merge($values, $additional_values);
280
281
        return new JWK($values);
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public static function createFromJKU($jku, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null)
288
    {
289
        $content = self::getContent($jku, $allow_unsecured_connection, $cache);
290
291
        Assertion::keyExists($content, 'keys', 'Invalid content.');
292
293
        return new JWKSet($content);
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public static function createFromX5U($x5u, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null)
300
    {
301
        $content = self::getContent($x5u, $allow_unsecured_connection, $cache);
302
303
        $jwkset = new JWKSet();
304
        foreach ($content as $kid => $cert) {
305
            $jwk = KeyConverter::loadKeyFromCertificate($cert);
306
            Assertion::notEmpty($jwk, 'Invalid content.');
307
            if (is_string($kid)) {
308
                $jwk['kid'] = $kid;
309
            }
310
            $jwkset->addKey(new JWK($jwk));
311
        }
312
313
        return $jwkset;
314
    }
315
316
    /**
317
     * @param string                                 $url
318
     * @param bool                                   $allow_unsecured_connection
319
     * @param \Psr\Cache\CacheItemPoolInterface|null $cache
320
     *
321
     * @return array
322
     */
323
    private static function getContent($url, $allow_unsecured_connection, CacheItemPoolInterface $cache = null)
324
    {
325
        $cache_key = sprintf('%s-%s', 'JWKFactory-Content', hash('sha512', $url));
326
        if (null !== $cache) {
327
            $item = $cache->getItem($cache_key);
328
            if (!$item->isHit()) {
329
                $content = self::downloadContent($url, $allow_unsecured_connection);
330
                $item->set($content);
331
                $cache->save($item);
332
333
                return $content;
334
            } else {
335
                return $item->get();
336
            }
337
        }
338
339
        return self::downloadContent($url, $allow_unsecured_connection);
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public static function createFromX5C(array $x5c, array $additional_values = [])
346
    {
347
        $values = KeyConverter::loadFromX5C($x5c);
348
        $values = array_merge($values, $additional_values);
349
350
        return new JWK($values);
351
    }
352
353
    /**
354
     * @param string $url
355
     * @param bool   $allow_unsecured_connection
356
     *
357
     * @throws \InvalidArgumentException
358
     *
359
     * @return array
360
     */
361
    private static function downloadContent($url, $allow_unsecured_connection)
362
    {
363
        // The URL must be a valid URL and scheme must be https
364
        Assertion::false(
365
            false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED),
366
            'Invalid URL.'
367
        );
368
        Assertion::false(
369
            false === $allow_unsecured_connection && 'https://' !==  mb_substr($url, 0, 8, '8bit'),
370
            'Unsecured connection.'
371
        );
372
373
        $params = [
374
            CURLOPT_RETURNTRANSFER => true,
375
            CURLOPT_URL            => $url,
376
        ];
377
        if (false === $allow_unsecured_connection) {
378
            $params[CURLOPT_SSL_VERIFYPEER] = true;
379
            $params[CURLOPT_SSL_VERIFYHOST] = 2;
380
        }
381
382
        $ch = curl_init();
383
        curl_setopt_array($ch, $params);
384
        $content = curl_exec($ch);
385
        $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
386
        Assertion::eq(1, preg_match('/^application\/json([\s|;].*)?$/', $content_type), sprintf('Content type is not "application/json". It is "%s".', $content_type));
387
        curl_close($ch);
388
389
        Assertion::notEmpty($content, 'Unable to get content.');
390
        $content = json_decode($content, true);
391
        Assertion::isArray($content, 'Invalid content.');
392
393
        return $content;
394
    }
395
}
396