JWKFactory   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 6
dl 0
loc 290
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A createRSAKey() 0 26 4
A createECKey() 0 4 1
A createOctKey() 0 15 2
A createOKPKey() 0 36 4
A createNoneKey() 0 13 1
A createFromJsonObject() 0 9 2
A createFromValues() 0 8 3
A createFromSecret() 0 12 1
A createFromCertificateFile() 0 7 1
A createFromKeySet() 0 4 1
A createFromPKCS12CertificateFile() 0 13 4
A createFromCertificate() 0 7 1
A createFromX509Resource() 0 7 1
A createFromKeyFile() 0 7 1
A createFromKey() 0 7 1
A createFromX5C() 0 7 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 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 InvalidArgumentException;
18
use Jose\Component\Core\JWK;
19
use Jose\Component\Core\JWKSet;
20
use Jose\Component\Core\Util\ECKey;
21
use Jose\Component\KeyManagement\KeyConverter\KeyConverter;
22
use Jose\Component\KeyManagement\KeyConverter\RSAKey;
23
use RuntimeException;
24
use Throwable;
25
26
class JWKFactory
27
{
28
    /**
29
     * Creates a RSA key with the given key size and additional values.
30
     *
31
     * @param int   $size   The key size in bits
32
     * @param array $values values to configure the key
33
     *
34
     * @throws InvalidArgumentException if the key has an invalid size
35
     * @throws InvalidArgumentException if it is not possible to create the key
36
     */
37
    public static function createRSAKey(int $size, array $values = []): JWK
38
    {
39
        if (0 !== $size % 8) {
40
            throw new InvalidArgumentException('Invalid key size.');
41
        }
42
        if (512 > $size) {
43
            throw new InvalidArgumentException('Key length is too short. It needs to be at least 512 bits.');
44
        }
45
46
        $key = openssl_pkey_new([
47
            'private_key_bits' => $size,
48
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
49
        ]);
50
        if (false === $key) {
51
            throw new InvalidArgumentException('Unable to create the key');
52
        }
53
        $details = openssl_pkey_get_details($key);
54
        openssl_free_key($key);
55
        $rsa = RSAKey::createFromKeyDetails($details['rsa']);
56
        $values = array_merge(
57
            $values,
58
            $rsa->toArray()
59
        );
60
61
        return new JWK($values);
62
    }
63
64
    /**
65
     * Creates a EC key with the given curve and additional values.
66
     *
67
     * @param string $curve  The curve
68
     * @param array  $values values to configure the key
69
     */
70
    public static function createECKey(string $curve, array $values = []): JWK
71
    {
72
        return ECKey::createECKey($curve, $values);
73
    }
74
75
    /**
76
     * Creates a octet key with the given key size and additional values.
77
     *
78
     * @param int   $size   The key size in bits
79
     * @param array $values values to configure the key
80
     *
81
     * @throws InvalidArgumentException if the key has an invalid size
82
     */
83
    public static function createOctKey(int $size, array $values = []): JWK
84
    {
85
        if (0 !== $size % 8) {
86
            throw new InvalidArgumentException('Invalid key size.');
87
        }
88
        $values = array_merge(
89
            $values,
90
            [
91
                'kty' => 'oct',
92
                'k' => Base64Url::encode(random_bytes($size / 8)),
93
            ]
94
        );
95
96
        return new JWK($values);
97
    }
98
99
    /**
100
     * Creates a OKP key with the given curve and additional values.
101
     *
102
     * @param string $curve  The curve
103
     * @param array  $values values to configure the key
104
     *
105
     * @throws InvalidArgumentException if the extension "sobium" is not available
106
     * @throws InvalidArgumentException if the curve is not supported
107
     */
108
    public static function createOKPKey(string $curve, array $values = []): JWK
109
    {
110
        if (!\extension_loaded('sodium')) {
111
            throw new RuntimeException('The extension "sodium" is not available. Please install it to use this method');
112
        }
113
        switch ($curve) {
114
            case 'X25519':
115
                $keyPair = sodium_crypto_box_keypair();
116
                $secret = sodium_crypto_box_secretkey($keyPair);
117
                $x = sodium_crypto_box_publickey($keyPair);
118
119
                break;
120
            case 'Ed25519':
121
                $keyPair = sodium_crypto_sign_keypair();
122
                $secret = sodium_crypto_sign_secretkey($keyPair);
123
                $x = sodium_crypto_sign_publickey($keyPair);
124
125
                break;
126
            default:
127
                throw new InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
128
        }
129
        $secretLength = mb_strlen($secret, '8bit');
130
        $d = mb_substr($secret, 0, -$secretLength / 2, '8bit');
131
132
        $values = array_merge(
133
            $values,
134
            [
135
                'kty' => 'OKP',
136
                'crv' => $curve,
137
                'd' => Base64Url::encode($d),
138
                'x' => Base64Url::encode($x),
139
            ]
140
        );
141
142
        return new JWK($values);
143
    }
144
145
    /**
146
     * Creates a none key with the given additional values.
147
     * Please note that this key type is not pat of any specification.
148
     * It is used to prevent the use of the "none" algorithm with other key types.
149
     *
150
     * @param array $values values to configure the key
151
     */
152
    public static function createNoneKey(array $values = []): JWK
153
    {
154
        $values = array_merge(
155
            $values,
156
            [
157
                'kty' => 'none',
158
                'alg' => 'none',
159
                'use' => 'sig',
160
            ]
161
        );
162
163
        return new JWK($values);
164
    }
165
166
    /**
167
     * Creates a key from a Json string.
168
     *
169
     * @throws InvalidArgumentException if the key or keyset is not valid
170
     *
171
     * @return JWK|JWKSet
172
     */
173
    public static function createFromJsonObject(string $value)
174
    {
175
        $json = json_decode($value, true);
176
        if (!\is_array($json)) {
177
            throw new InvalidArgumentException('Invalid key or key set.');
178
        }
179
180
        return self::createFromValues($json);
181
    }
182
183
    /**
184
     * Creates a key or key set from the given input.
185
     *
186
     * @return JWK|JWKSet
187
     */
188
    public static function createFromValues(array $values)
189
    {
190
        if (\array_key_exists('keys', $values) && \is_array($values['keys'])) {
191
            return JWKSet::createFromKeyData($values);
192
        }
193
194
        return new JWK($values);
195
    }
196
197
    /**
198
     * This method create a JWK object using a shared secret.
199
     */
200
    public static function createFromSecret(string $secret, array $additional_values = []): JWK
201
    {
202
        $values = array_merge(
203
            $additional_values,
204
            [
205
                'kty' => 'oct',
206
                'k' => Base64Url::encode($secret),
207
            ]
208
        );
209
210
        return new JWK($values);
211
    }
212
213
    /**
214
     * This method will try to load a X.509 certificate and convert it into a public key.
215
     */
216
    public static function createFromCertificateFile(string $file, array $additional_values = []): JWK
217
    {
218
        $values = KeyConverter::loadKeyFromCertificateFile($file);
219
        $values = array_merge($values, $additional_values);
220
221
        return new JWK($values);
222
    }
223
224
    /**
225
     * Extract a keyfrom a key set identified by the given index .
226
     *
227
     * @param int|string $index
228
     */
229
    public static function createFromKeySet(JWKSet $jwkset, $index): JWK
230
    {
231
        return $jwkset->get($index);
232
    }
233
234
    /**
235
     * This method will try to load a PKCS#12 file and convert it into a public key.
236
     *
237
     * @throws InvalidArgumentException if the certificate cannot be loaded
238
     */
239
    public static function createFromPKCS12CertificateFile(string $file, ?string $secret = '', array $additional_values = []): JWK
240
    {
241
        try {
242
            openssl_pkcs12_read(file_get_contents($file), $certs, $secret);
243
        } catch (Throwable $throwable) {
244
            throw new RuntimeException('Unable to load the certificates.', $throwable->getCode(), $throwable);
245
        }
246
        if (!\is_array($certs) || !\array_key_exists('pkey', $certs)) {
247
            throw new RuntimeException('Unable to load the certificates.');
248
        }
249
250
        return self::createFromKey($certs['pkey'], null, $additional_values);
251
    }
252
253
    /**
254
     * This method will try to convert a X.509 certificate into a public key.
255
     */
256
    public static function createFromCertificate(string $certificate, array $additional_values = []): JWK
257
    {
258
        $values = KeyConverter::loadKeyFromCertificate($certificate);
259
        $values = array_merge($values, $additional_values);
260
261
        return new JWK($values);
262
    }
263
264
    /**
265
     * This method will try to convert a X.509 certificate resource into a public key.
266
     *
267
     * @param resource $res
268
     */
269
    public static function createFromX509Resource($res, array $additional_values = []): JWK
270
    {
271
        $values = KeyConverter::loadKeyFromX509Resource($res);
272
        $values = array_merge($values, $additional_values);
273
274
        return new JWK($values);
275
    }
276
277
    /**
278
     * This method will try to load and convert a key file into a JWK object.
279
     * If the key is encrypted, the password must be set.
280
     */
281
    public static function createFromKeyFile(string $file, ?string $password = null, array $additional_values = []): JWK
282
    {
283
        $values = KeyConverter::loadFromKeyFile($file, $password);
284
        $values = array_merge($values, $additional_values);
285
286
        return new JWK($values);
287
    }
288
289
    /**
290
     * This method will try to load and convert a key into a JWK object.
291
     * If the key is encrypted, the password must be set.
292
     */
293
    public static function createFromKey(string $key, ?string $password = null, array $additional_values = []): JWK
294
    {
295
        $values = KeyConverter::loadFromKey($key, $password);
296
        $values = array_merge($values, $additional_values);
297
298
        return new JWK($values);
299
    }
300
301
    /**
302
     * This method will try to load and convert a X.509 certificate chain into a public key.
303
     *
304
     * Be careful! The certificate chain is loaded, but it is NOT VERIFIED by any mean!
305
     * It is mandatory to verify the root CA or intermediate  CA are trusted.
306
     * If not done, it may lead to potential security issues.
307
     */
308
    public static function createFromX5C(array $x5c, array $additional_values = []): JWK
309
    {
310
        $values = KeyConverter::loadFromX5C($x5c);
311
        $values = array_merge($values, $additional_values);
312
313
        return new JWK($values);
314
    }
315
}
316