Completed
Push — master ( bb7a81...f07d4e )
by Florent
02:21
created

JWKFactory::createRandomECPrivateKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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