Completed
Push — master ( 64584a...32940c )
by Florent
03:05
created

JWKFactory::createOKPKey()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 31
rs 8.8571
cc 3
eloc 23
nc 3
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\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 Mdanter\Ecc\Curves\CurveFactory;
21
use Mdanter\Ecc\Curves\NistCurve;
22
use Mdanter\Ecc\EccFactory;
23
24
final class JWKFactory
25
{
26
    /**
27
     * @param int   $size              Key size in bits
28
     * @param array $additional_values
29
     *
30
     * @return \Jose\Object\JWKInterface
31
     */
32
    public static function createRSAKey($size, array $additional_values = [])
33
    {
34
        Assertion::true(0 === $size%8, 'Invalid key size.');
35
        Assertion::greaterOrEqualThan($size, 384, 'Key length is too short. It needs to be at least 384 bits.');
36
37
        $key = openssl_pkey_new([
38
            'private_key_bits' => $size,
39
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
40
        ]);
41
        openssl_pkey_export($key, $out);
42
        $rsa = new RSAKey($out);
43
        $values = array_merge(
44
            $additional_values,
45
            $rsa->toArray()
46
        );
47
48
        return new JWK($values);
49
    }
50
    /**
51
     * @param string $curve
52
     * @param array  $additional_values
53
     *
54
     * @return \Jose\Object\JWKInterface
55
     */
56
    public static function createECKey($curve, array $additional_values = [])
57
    {
58
        $curve_name = self::getNistName($curve);
59
        $generator = CurveFactory::getGeneratorByName($curve_name);
60
        $private_key = $generator->createPrivateKey();
61
62
        $values = [
63
            'kty' => 'EC',
64
            'crv' => $curve,
65
            'x'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getX()),
66
            'y'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getY()),
67
            'd'   =>  self::encodeValue($private_key->getSecret()),
68
        ];
69
70
        $values = array_merge(
71
            $additional_values,
72
            $values
73
        );
74
75
        return new JWK($values);
76
    }
77
78
    /**
79
     * @param int   $size              Key size in bits
80
     * @param array $additional_values
81
     *
82
     * @return \Jose\Object\JWKInterface
83
     */
84
    public static function createOctKey($size, array $additional_values = [])
85
    {
86
        Assertion::true(0 === $size%8, 'Invalid key size.');
87
        $values = array_merge(
88
            $additional_values,
89
            [
90
                'kty' => 'oct',
91
                'k'   => Base64Url::encode(random_bytes($size/8)),
92
            ]
93
        );
94
        
95
        return new JWK($values);
96
    }
97
98
    /**
99
     * @param string $curve
100
     * @param array  $additional_values
101
     *
102
     * @return \Jose\Object\JWKInterface
103
     */
104
    public static function createOKPKey($curve, array $additional_values = [])
105
    {
106
        switch ($curve) {
107
            case 'X25519':
108
                Assertion::true(function_exists('curve25519_public'), sprintf('Unsupported "%s" curve', $curve));
109
                $d = random_bytes(32);
110
                $x = curve25519_public($d);
111
                break;
112
            case 'Ed25519':
113
                Assertion::methodExists(function_exists('ed25519_publickey'), sprintf('Unsupported "%s" curve', $curve));
0 ignored issues
show
Documentation introduced by
function_exists('ed25519_publickey') is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
114
                $d = random_bytes(32);
115
                $x = ed25519_publickey($d);
116
                break;
117
            default:
118
                throw new \InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
119
        }
120
121
        $values = [
122
            'kty' => 'OKP',
123
            'crv' => $curve,
124
            'x'   => Base64Url::encode($x),
125
            'd'   => Base64Url::encode($d),
126
        ];
127
128
        $values = array_merge(
129
            $additional_values,
130
            $values
131
        );
132
133
        return new JWK($values);
134
    }
135
136
    /**
137
     * @param string $value
138
     *
139
     * @return string
140
     */
141
    private static function encodeValue($value)
142
    {
143
        $value = gmp_strval($value);
144
145
        return Base64Url::encode(self::convertDecToBin($value));
146
    }
147
148
    /**
149
     * @param string $value
150
     *
151
     * @return string
152
     */
153
    private static function convertDecToBin($value)
154
    {
155
        $adapter = EccFactory::getAdapter();
156
157
        return hex2bin($adapter->decHex($value));
158
    }
159
160
    /**
161
     * @param string $curve
162
     *
163
     * @throws \InvalidArgumentException
164
     *
165
     * @return string
166
     */
167
    private static function getNistName($curve)
168
    {
169
        switch ($curve) {
170
            case 'P-256':
171
                return NistCurve::NAME_P256;
172
            case 'P-384':
173
                return NistCurve::NAME_P384;
174
            case 'P-521':
175
                return NistCurve::NAME_P521;
176
            default:
177
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
178
        }
179
    }
180
181
    /**
182
     * @param array $values
183
     *
184
     * @return \Jose\Object\JWKInterface|\Jose\Object\JWKSetInterface
185
     */
186
    public static function createFromValues(array $values)
187
    {
188
        if (array_key_exists('keys', $values) && is_array($values['keys'])) {
189
            return new JWKSet($values);
190
        }
191
192
        return new JWK($values);
193
    }
194
195
    /**
196
     * @param string $file
197
     * @param array  $additional_values
198
     *
199
     * @return \Jose\Object\JWKInterface
200
     */
201
    public static function createFromCertificateFile($file, array $additional_values = [])
202
    {
203
        $values = KeyConverter::loadKeyFromCertificateFile($file);
204
        $values = array_merge($values, $additional_values);
205
206
        return new JWK($values);
207
    }
208
209
    /**
210
     * @param string $certificate
211
     * @param array  $additional_values
212
     *
213
     * @return \Jose\Object\JWKInterface
214
     */
215
    public static function createFromCertificate($certificate, array $additional_values = [])
216
    {
217
        $values = KeyConverter::loadKeyFromCertificate($certificate);
218
        $values = array_merge($values, $additional_values);
219
220
        return new JWK($values);
221
    }
222
223
    /**
224
     * @param resource $res
225
     * @param array    $additional_values
226
     *
227
     * @return \Jose\Object\JWKInterface
228
     */
229
    public static function createFromX509Resource($res, array $additional_values = [])
230
    {
231
        $values = KeyConverter::loadKeyFromX509Resource($res);
232
        $values = array_merge($values, $additional_values);
233
234
        return new JWK($values);
235
    }
236
237
    /**
238
     * @param string      $file
239
     * @param null|string $password
240
     * @param array       $additional_values
241
     *
242
     * @return \Jose\Object\JWKInterface
243
     */
244
    public static function createFromKeyFile($file, $password = null, array $additional_values = [])
245
    {
246
        $values = KeyConverter::loadFromKeyFile($file, $password);
247
        $values = array_merge($values, $additional_values);
248
249
        return new JWK($values);
250
    }
251
252
    /**
253
     * @param string      $key
254
     * @param null|string $password
255
     * @param array       $additional_values
256
     *
257
     * @return \Jose\Object\JWKInterface
258
     */
259
    public static function createFromKey($key, $password = null, array $additional_values = [])
260
    {
261
        $values = KeyConverter::loadFromKey($key, $password);
262
        $values = array_merge($values, $additional_values);
263
264
        return new JWK($values);
265
    }
266
267
    /**
268
     * @param string $jku
269
     * @param bool   $allow_unsecured_connection
270
     *
271
     * @return \Jose\Object\JWKSetInterface
272
     */
273
    public static function createFromJKU($jku, $allow_unsecured_connection = false)
274
    {
275
        $content = self::downloadContent($jku, $allow_unsecured_connection);
276
        Assertion::keyExists($content, 'keys', 'Invalid content.');
277
278
        return new JWKSet($content);
279
    }
280
281
    /**
282
     * @param string $x5u
283
     * @param bool   $allow_unsecured_connection
284
     *
285
     * @return \Jose\Object\JWKSetInterface
286
     */
287
    public static function createFromX5U($x5u, $allow_unsecured_connection = false)
288
    {
289
        $content = self::downloadContent($x5u, $allow_unsecured_connection);
290
291
        $jwkset = new JWKSet();
292
        foreach ($content as $kid => $cert) {
293
            $jwk = KeyConverter::loadKeyFromCertificate($cert);
294
            Assertion::notEmpty($jwk, 'Invalid content.');
295
            if (is_string($kid)) {
296
                $jwk['kid'] = $kid;
297
            }
298
            $jwkset->addKey(new JWK($jwk));
299
        }
300
301
        return $jwkset;
302
    }
303
304
    /**
305
     * @param array $x5c
306
     * @param array $additional_values
307
     *
308
     * @return \Jose\Object\JWKInterface
309
     */
310
    public static function createFromX5C(array $x5c, array $additional_values = [])
311
    {
312
        $values = KeyConverter::loadFromX5C($x5c);
313
        $values = array_merge($values, $additional_values);
314
315
        return new JWK($values);
316
    }
317
318
    /**
319
     * @param string $url
320
     * @param bool   $allow_unsecured_connection
321
     *
322
     * @throws \InvalidArgumentException
323
     *
324
     * @return array
325
     */
326
    private static function downloadContent($url, $allow_unsecured_connection)
327
    {
328
        // The URL must be a valid URL and scheme must be https
329
        Assertion::false(
330
            false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED),
331
            'Invalid URL.'
332
        );
333
        Assertion::false(
334
            false === $allow_unsecured_connection && 'https://' !==  mb_substr($url, 0, 8, '8bit'),
335
            'Unsecured connection.'
336
        );
337
338
        $params = [
339
            CURLOPT_RETURNTRANSFER => true,
340
            CURLOPT_URL            => $url,
341
        ];
342
        if (false === $allow_unsecured_connection) {
343
            $params[CURLOPT_SSL_VERIFYPEER] = true;
344
            $params[CURLOPT_SSL_VERIFYHOST] = 2;
345
        }
346
347
        $ch = curl_init();
348
        curl_setopt_array($ch, $params);
349
        $content = curl_exec($ch);
350
        $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
351
        Assertion::eq(1, preg_match('/^application\/json([\s|;].*)?$/', $content_type), sprintf('Content type is not "application/json". It is "%s".', $content_type));
352
        curl_close($ch);
353
354
        Assertion::notEmpty($content, 'Unable to get content.');
355
        $content = json_decode($content, true);
356
        Assertion::isArray($content, 'Invalid content.');
357
358
        return $content;
359
    }
360
}
361