Completed
Push — master ( ff5377...16526e )
by Florent
02:39
created

JWKFactory::createRandomX25519PrivateKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
rs 9.2
cc 2
eloc 14
nc 2
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\Factory;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use Jose\KeyConverter\KeyConverter;
17
use Jose\Object\JWK;
18
use Jose\Object\JWKSet;
19
use Mdanter\Ecc\Curves\CurveFactory;
20
use Mdanter\Ecc\Curves\NistCurve;
21
use Mdanter\Ecc\EccFactory;
22
23
final class JWKFactory
24
{
25
    /**
26
     * @param string $curve
27
     * @param array  $additional_values
28
     *
29
     * @return \Jose\Object\JWKInterface
30
     */
31
    public static function createRandomECPrivateKey($curve, array $additional_values = [])
32
    {
33
        $curve_name = self::getNistName($curve);
34
        $generator = CurveFactory::getGeneratorByName($curve_name);
35
        $private_key = $generator->createPrivateKey();
36
37
        $values = [
38
            'kty' => 'EC',
39
            'crv' => $curve,
40
            'x'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getX()),
41
            'y'   => self::encodeValue($private_key->getPublicKey()->getPoint()->getY()),
42
            'd'   => self::encodeValue($private_key->getSecret()),
43
        ];
44
45
        $values = array_merge(
46
            $values,
47
            $additional_values
48
        );
49
50
        return new JWK($values);
51
    }
52
53
    /**
54
     * @param array  $additional_values
55
     *
56
     * @return \Jose\Object\JWKInterface
57
     */
58
    public static function createRandomX25519PrivateKey(array $additional_values = [])
59
    {
60
        if (!function_exists('curve25519_public')) {
61
            throw new \InvalidArgumentException('Unsupported X25519 curves.');
62
        }
63
        $d = random_bytes(32);
64
        $x = curve25519_public($d);
65
        
66
        $values = [
67
            'kty' => 'OKP',
68
            'crv' => 'X25519',
69
            'x'   => Base64Url::encode($x),
70
            'd'   => Base64Url::encode($d),
71
        ];
72
73
        $values = array_merge(
74
            $values,
75
            $additional_values
76
        );
77
78
        return new JWK($values);
79
    }
80
81
    /**
82
     * @param string $value
83
     *
84
     * @return string
85
     */
86
    private static function encodeValue($value)
87
    {
88
        return Base64Url::encode(self::convertDecToBin($value));
89
    }
90
91
    /**
92
     * @param string $value
93
     *
94
     * @return string
95
     */
96
    private static function convertDecToBin($value)
97
    {
98
        $adapter = EccFactory::getAdapter();
99
100
        return hex2bin($adapter->decHex($value));
101
    }
102
103
    /**
104
     * @param string $curve
105
     *
106
     * @throws \InvalidArgumentException
107
     *
108
     * @return string
109
     */
110
    private static function getNistName($curve)
111
    {
112
        switch ($curve) {
113
            case 'P-256':
114
                return NistCurve::NAME_P256;
115
            case 'P-384':
116
                return NistCurve::NAME_P384;
117
            case 'P-521':
118
                return NistCurve::NAME_P521;
119
            default:
120
                throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
121
        }
122
    }
123
124
    /**
125
     * @param array $values
126
     *
127
     * @return \Jose\Object\JWKInterface|\Jose\Object\JWKSetInterface
128
     */
129
    public static function createFromValues(array $values)
130
    {
131
        if (array_key_exists('keys', $values) && is_array($values['keys'])) {
132
            return new JWKSet($values);
133
        }
134
135
        return new JWK($values);
136
    }
137
138
    /**
139
     * @param string $file
140
     * @param array  $additional_values
141
     *
142
     * @return \Jose\Object\JWKInterface
143
     */
144
    public static function createFromCertificateFile($file, array $additional_values = [])
145
    {
146
        $values = KeyConverter::loadKeyFromCertificateFile($file);
147
        $values = array_merge($values, $additional_values);
148
149
        return new JWK($values);
150
    }
151
152
    /**
153
     * @param string $certificate
154
     * @param array  $additional_values
155
     *
156
     * @return \Jose\Object\JWKInterface
157
     */
158
    public static function createFromCertificate($certificate, array $additional_values = [])
159
    {
160
        $values = KeyConverter::loadKeyFromCertificate($certificate);
161
        $values = array_merge($values, $additional_values);
162
163
        return new JWK($values);
164
    }
165
166
    /**
167
     * @param resource $res
168
     * @param array    $additional_values
169
     *
170
     * @return \Jose\Object\JWKInterface
171
     */
172
    public static function createFromX509Resource($res, array $additional_values = [])
173
    {
174
        $values = KeyConverter::loadKeyFromX509Resource($res);
175
        $values = array_merge($values, $additional_values);
176
177
        return new JWK($values);
178
    }
179
180
    /**
181
     * @param string      $file
182
     * @param null|string $password
183
     * @param array       $additional_values
184
     *
185
     * @return \Jose\Object\JWKInterface
186
     */
187
    public static function createFromKeyFile($file, $password = null, array $additional_values = [])
188
    {
189
        $values = KeyConverter::loadFromKeyFile($file, $password);
190
        $values = array_merge($values, $additional_values);
191
192
        return new JWK($values);
193
    }
194
195
    /**
196
     * @param string      $key
197
     * @param null|string $password
198
     * @param array       $additional_values
199
     *
200
     * @return \Jose\Object\JWKInterface
201
     */
202
    public static function createFromKey($key, $password = null, array $additional_values = [])
203
    {
204
        $values = KeyConverter::loadFromKey($key, $password);
205
        $values = array_merge($values, $additional_values);
206
207
        return new JWK($values);
208
    }
209
210
    /**
211
     * @param string $jku
212
     * @param bool   $allow_unsecured_connection
213
     *
214
     * @return \Jose\Object\JWKSetInterface
215
     */
216
    public static function createFromJKU($jku, $allow_unsecured_connection = false)
217
    {
218
        $content = self::downloadContent($jku, $allow_unsecured_connection);
219
        Assertion::keyExists($content, 'keys', 'Invalid content.');
220
221
        return new JWKSet($content);
222
    }
223
224
    /**
225
     * @param string $x5u
226
     * @param bool   $allow_unsecured_connection
227
     *
228
     * @return \Jose\Object\JWKSetInterface
229
     */
230
    public static function createFromX5U($x5u, $allow_unsecured_connection = false)
231
    {
232
        $content = self::downloadContent($x5u, $allow_unsecured_connection);
233
234
        $jwkset = new JWKSet();
235
        foreach ($content as $kid => $cert) {
236
            $jwk = KeyConverter::loadKeyFromCertificate($cert);
237
            Assertion::notEmpty($jwk, 'Invalid content.');
238
            if (is_string($kid)) {
239
                $jwk['kid'] = $kid;
240
            }
241
            $jwkset->addKey(new JWK($jwk));
242
        }
243
244
        return $jwkset;
245
    }
246
247
    /**
248
     * @param array $x5c
249
     * @param array $additional_values
250
     *
251
     * @return \Jose\Object\JWKInterface
252
     */
253
    public static function createFromX5C(array $x5c, array $additional_values = [])
254
    {
255
        $values = KeyConverter::loadFromX5C($x5c);
256
        $values = array_merge($values, $additional_values);
257
258
        return new JWK($values);
259
    }
260
261
    /**
262
     * @param string $url
263
     * @param bool   $allow_unsecured_connection
264
     *
265
     * @throws \InvalidArgumentException
266
     *
267
     * @return array
268
     */
269
    private static function downloadContent($url, $allow_unsecured_connection)
270
    {
271
        // The URL must be a valid URL and scheme must be https
272
        Assertion::false(
273
            false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED),
274
            'Invalid URL.'
275
        );
276
        Assertion::false(
277
            false === $allow_unsecured_connection && 'https://' !==  mb_substr($url, 0, 8, '8bit'),
278
            'Unsecured connection.'
279
        );
280
281
        $params = [
282
            CURLOPT_RETURNTRANSFER => true,
283
            CURLOPT_URL            => $url,
284
        ];
285
        if (false === $allow_unsecured_connection) {
286
            $params[CURLOPT_SSL_VERIFYPEER] = true;
287
            $params[CURLOPT_SSL_VERIFYHOST] = 2;
288
        }
289
290
        $ch = curl_init();
291
        curl_setopt_array($ch, $params);
292
        $content = curl_exec($ch);
293
        $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
294
        Assertion::eq(1, preg_match('/^application\/json([\s|;].*)?$/', $content_type), sprintf('Content type is not "application/json". It is "%s".', $content_type));
295
        curl_close($ch);
296
297
        Assertion::notEmpty($content, 'Unable to get content.');
298
        $content = json_decode($content, true);
299
        Assertion::isArray($content, 'Invalid content.');
300
301
        return $content;
302
    }
303
}
304