Completed
Push — master ( 672c87...f5002a )
by Florent
04:13 queued 02:04
created

JWKFactory::createFromKeySet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\KeyManagement;
15
16
use Base64Url\Base64Url;
17
use Jose\Component\Core\JWK;
18
use Jose\Component\Core\JWKSet;
19
use Jose\Component\Core\Util\Ecc\NistCurve;
20
use Jose\Component\KeyManagement\KeyConverter\KeyConverter;
21
use Jose\Component\KeyManagement\KeyConverter\RSAKey;
22
23
class JWKFactory
24
{
25
    /**
26
     * Creates a RSA key with the given key size and additional values.
27
     *
28
     * @param int   $size   The key size in bits
29
     * @param array $values values to configure the key
30
     *
31
     * @return JWK
32
     */
33
    public static function createRSAKey(int $size, array $values = []): JWK
34
    {
35
        if (0 !== $size % 8) {
36
            throw new \InvalidArgumentException('Invalid key size.');
37
        }
38
39
        if (384 > $size) {
40
            throw new \InvalidArgumentException('Key length is too short. It needs to be at least 384 bits.');
41
        }
42
43
        $key = openssl_pkey_new([
44
            'private_key_bits' => $size,
45
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
46
        ]);
47
        openssl_pkey_export($key, $out);
48
        $rsa = RSAKey::createFromPEM($out);
49
        $values = array_merge(
50
            $values,
51
            $rsa->toArray()
52
        );
53
54
        return JWK::create($values);
55
    }
56
57
    /**
58
     * Creates a EC key with the given curve and additional values.
59
     *
60
     * @param string $curve  The curve
61
     * @param array  $values values to configure the key
62
     *
63
     * @return JWK
64
     */
65
    public static function createECKey(string $curve, array $values = []): JWK
66
    {
67
        try {
68
            $jwk = self::createECKeyUsingOpenSSL($curve);
69
        } catch (\Exception $e) {
70
            $jwk = self::createECKeyUsingPurePhp($curve);
71
        }
72
        $values = array_merge($values, $jwk);
73
74
        return JWK::create($values);
75
    }
76
77
    /**
78
     * @param string $curve
79
     *
80
     * @return array
81
     */
82
    private static function createECKeyUsingPurePhp(string $curve): array
83
    {
84
        switch ($curve) {
85
            case 'P-256':
86
                $nistCurve = NistCurve::curve256();
87
88
                break;
89
            case 'P-384':
90
                $nistCurve = NistCurve::curve384();
91
92
                break;
93
            case 'P-521':
94
                $nistCurve = NistCurve::curve521();
95
96
                break;
97
            default:
98
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
99
        }
100
101
        $privateKey = $nistCurve->createPrivateKey();
102
        $publicKey = $nistCurve->createPublicKey($privateKey);
103
104
        return [
105
            'kty' => 'EC',
106
            'crv' => $curve,
107
            'd'   => Base64Url::encode(gmp_export($privateKey->getSecret())),
108
            'x'   => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
109
            'y'   => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
110
        ];
111
    }
112
113
    /**
114
     * @param string $curve
115
     *
116
     * @return array
117
     */
118
    private static function createECKeyUsingOpenSSL(string $curve): array
119
    {
120
        $key = openssl_pkey_new([
121
            'curve_name'       => self::getOpensslCurveName($curve),
122
            'private_key_type' => OPENSSL_KEYTYPE_EC,
123
        ]);
124
        $res = openssl_pkey_export($key, $out);
125
        if (false === $res) {
126
            throw new \RuntimeException('Unable to create the key');
127
        }
128
        $res = openssl_pkey_get_private($out);
129
130
        $details = openssl_pkey_get_details($res);
131
132
        return [
133
            'kty' => 'EC',
134
            'crv' => $curve,
135
            'x'   => Base64Url::encode(bin2hex($details['ec']['x'])),
136
            'y'   => Base64Url::encode(bin2hex($details['ec']['y'])),
137
            'd'   => Base64Url::encode(bin2hex($details['ec']['d'])),
138
        ];
139
    }
140
141
    /**
142
     * @param string $curve
143
     *
144
     * @return string
145
     */
146
    private static function getOpensslCurveName(string $curve): string
147
    {
148
        switch ($curve) {
149
            case 'P-256':
150
                return 'prime256v1';
151
            case 'P-384':
152
                return 'secp384r1';
153
            case 'P-521':
154
                return 'secp521r1';
155
            default:
156
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
157
        }
158
    }
159
160
    /**
161
     * Creates a octet key with the given key size and additional values.
162
     *
163
     * @param int   $size   The key size in bits
164
     * @param array $values values to configure the key
165
     *
166
     * @return JWK
167
     */
168
    public static function createOctKey(int $size, array $values = []): JWK
169
    {
170
        if (0 !== $size % 8) {
171
            throw new \InvalidArgumentException('Invalid key size.');
172
        }
173
        $values = array_merge(
174
            $values,
175
            [
176
                'kty' => 'oct',
177
                'k'   => Base64Url::encode(random_bytes($size / 8)),
178
            ]
179
        );
180
181
        return JWK::create($values);
182
    }
183
184
    /**
185
     * Creates a OKP key with the given curve and additional values.
186
     *
187
     * @param string $curve  The curve
188
     * @param array  $values values to configure the key
189
     *
190
     * @return JWK
191
     */
192
    public static function createOKPKey(string $curve, array $values = []): JWK
193
    {
194
        switch ($curve) {
195
            case 'X25519':
196
                $keyPair = sodium_crypto_box_keypair();
197
                $d = sodium_crypto_box_secretkey($keyPair);
198
                $x = sodium_crypto_box_publickey($keyPair);
199
200
                break;
201
            case 'Ed25519':
202
                $keyPair = sodium_crypto_sign_keypair();
203
                $d = sodium_crypto_sign_secretkey($keyPair);
204
                $x = sodium_crypto_sign_publickey($keyPair);
205
206
                break;
207
            default:
208
                throw new \InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
209
        }
210
211
        $values = array_merge(
212
            $values,
213
            [
214
                'kty' => 'OKP',
215
                'crv' => $curve,
216
                'x'   => Base64Url::encode($x),
217
                'd'   => Base64Url::encode($d),
218
            ]
219
        );
220
221
        return JWK::create($values);
222
    }
223
224
    /**
225
     * Creates a none key with the given additional values.
226
     * Please note that this key type is not pat of any specification.
227
     * It is used to prevent the use of the "none" algorithm with other key types.
228
     *
229
     * @param array $values values to configure the key
230
     *
231
     * @return JWK
232
     */
233
    public static function createNoneKey(array $values = []): JWK
234
    {
235
        $values = array_merge(
236
            $values,
237
            [
238
                'kty' => 'none',
239
                'alg' => 'none',
240
                'use' => 'sig',
241
            ]
242
        );
243
244
        return JWK::create($values);
245
    }
246
247
    /**
248
     * Creates a key from a Json string.
249
     *
250
     * @param string $value
251
     *
252
     * @return JWK|JWKSet
253
     */
254
    public static function createFromJsonObject(string $value)
255
    {
256
        $json = json_decode($value, true);
257
        if (!is_array($json)) {
258
            throw new \InvalidArgumentException('Invalid key or key set.');
259
        }
260
261
        return self::createFromValues($json);
262
    }
263
264
    /**
265
     * Creates a key or key set from the given input.
266
     *
267
     * @param array $values
268
     *
269
     * @return JWK|JWKSet
270
     */
271
    public static function createFromValues(array $values)
272
    {
273
        if (array_key_exists('keys', $values) && is_array($values['keys'])) {
274
            return JWKSet::createFromKeyData($values);
275
        }
276
277
        return JWK::create($values);
278
    }
279
280
    /**
281
     * This method create a JWK object using a shared secret.
282
     *
283
     * @param string $secret
284
     * @param array  $additional_values
285
     *
286
     * @return JWK
287
     */
288
    public static function createFromSecret(string $secret, array $additional_values = []): JWK
289
    {
290
        $values = array_merge(
291
            $additional_values,
292
            [
293
                'kty' => 'oct',
294
                'k'   => Base64Url::encode($secret),
295
            ]
296
        );
297
298
        return JWK::create($values);
299
    }
300
301
    /**
302
     * This method will try to load a X.509 certificate and convert it into a public key.
303
     *
304
     * @param string $file
305
     * @param array  $additional_values
306
     *
307
     * @return JWK
308
     */
309
    public static function createFromCertificateFile(string $file, array $additional_values = []): JWK
310
    {
311
        $values = KeyConverter::loadKeyFromCertificateFile($file);
312
        $values = array_merge($values, $additional_values);
313
314
        return JWK::create($values);
315
    }
316
317
    /**
318
     * Extract a keyfrom a key set identified by the given index .
319
     *
320
     * @param JWKSet     $jwkset
321
     * @param int|string $index
322
     *
323
     * @return JWK
324
     */
325
    public static function createFromKeySet(JWKSet $jwkset, $index): JWK
326
    {
327
        return $jwkset->get($index);
328
    }
329
330
    /**
331
     * This method will try to load a PKCS#12 file and convert it into a public key.
332
     *
333
     * @param string      $file
334
     * @param null|string $secret
335
     * @param array       $additional_values
336
     *
337
     * @throws \Exception
338
     *
339
     * @return JWK
340
     */
341
    public static function createFromPKCS12CertificateFile(string $file, ?string $secret = '', array $additional_values = []): JWK
342
    {
343
        $res = openssl_pkcs12_read(file_get_contents($file), $certs, $secret);
344
        if (false === $res || !is_array($certs) || !array_key_exists('pkey', $certs)) {
345
            throw new \RuntimeException('Unable to load the certificates.');
346
        }
347
348
        return self::createFromKey($certs['pkey'], null, $additional_values);
349
    }
350
351
    /**
352
     * This method will try to convert a X.509 certificate into a public key.
353
     *
354
     * @param string $certificate
355
     * @param array  $additional_values
356
     *
357
     * @return JWK
358
     */
359
    public static function createFromCertificate(string $certificate, array $additional_values = []): JWK
360
    {
361
        $values = KeyConverter::loadKeyFromCertificate($certificate);
362
        $values = array_merge($values, $additional_values);
363
364
        return JWK::create($values);
365
    }
366
367
    /**
368
     * This method will try to convert a X.509 certificate resource into a public key.
369
     *
370
     * @param resource $res
371
     * @param array    $additional_values
372
     *
373
     * @throws \Exception
374
     *
375
     * @return JWK
376
     */
377
    public static function createFromX509Resource($res, array $additional_values = []): JWK
378
    {
379
        $values = KeyConverter::loadKeyFromX509Resource($res);
380
        $values = array_merge($values, $additional_values);
381
382
        return JWK::create($values);
383
    }
384
385
    /**
386
     * This method will try to load and convert a key file into a JWK object.
387
     * If the key is encrypted, the password must be set.
388
     *
389
     * @param string      $file
390
     * @param null|string $password
391
     * @param array       $additional_values
392
     *
393
     * @throws \Exception
394
     *
395
     * @return JWK
396
     */
397
    public static function createFromKeyFile(string $file, ?string $password = null, array $additional_values = []): JWK
398
    {
399
        $values = KeyConverter::loadFromKeyFile($file, $password);
400
        $values = array_merge($values, $additional_values);
401
402
        return JWK::create($values);
403
    }
404
405
    /**
406
     * This method will try to load and convert a key into a JWK object.
407
     * If the key is encrypted, the password must be set.
408
     *
409
     * @param string      $key
410
     * @param null|string $password
411
     * @param array       $additional_values
412
     *
413
     * @throws \Exception
414
     *
415
     * @return JWK
416
     */
417
    public static function createFromKey(string $key, ?string $password = null, array $additional_values = []): JWK
418
    {
419
        $values = KeyConverter::loadFromKey($key, $password);
420
        $values = array_merge($values, $additional_values);
421
422
        return JWK::create($values);
423
    }
424
425
    /**
426
     * This method will try to load and convert a X.509 certificate chain into a public key.
427
     *
428
     * @param array $x5c
429
     * @param array $additional_values
430
     *
431
     * @return JWK
432
     */
433
    public static function createFromX5C(array $x5c, array $additional_values = []): JWK
434
    {
435
        $values = KeyConverter::loadFromX5C($x5c);
436
        $values = array_merge($values, $additional_values);
437
438
        return JWK::create($values);
439
    }
440
}
441