Completed
Push — master ( e5bdab...756eed )
by Florent
13s
created

ECKey::p256KPublicKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\Core\Util;
15
16
use Base64Url\Base64Url;
17
use InvalidArgumentException;
18
use Jose\Component\Core\JWK;
19
use RuntimeException;
20
21
/**
22
 * @internal
23
 */
24
class ECKey
25
{
26
    public static function convertToPEM(JWK $jwk): string
27
    {
28
        if ($jwk->has('d')) {
29
            return self::convertPrivateKeyToPEM($jwk);
30
        }
31
32
        return self::convertPublicKeyToPEM($jwk);
33
    }
34
35
    public static function convertPublicKeyToPEM(JWK $jwk): string
36
    {
37
        switch ($jwk->get('crv')) {
38
            case 'P-256':
39
                $der = self::p256PublicKey();
40
41
                break;
42
            case 'secp256k1':
43
                $der = self::p256KPublicKey();
44
45
                break;
46
            case 'P-384':
47
                $der = self::p384PublicKey();
48
49
                break;
50
            case 'P-521':
51
                $der = self::p521PublicKey();
52
53
                break;
54
            default:
55
                throw new InvalidArgumentException('Unsupported curve.');
56
        }
57
        $der .= self::getKey($jwk);
58
        $pem = '-----BEGIN PUBLIC KEY-----'.PHP_EOL;
59
        $pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
60
        $pem .= '-----END PUBLIC KEY-----'.PHP_EOL;
61
62
        return $pem;
63
    }
64
65
    public static function convertPrivateKeyToPEM(JWK $jwk): string
66
    {
67
        switch ($jwk->get('crv')) {
68
            case 'P-256':
69
                $der = self::p256PrivateKey($jwk);
70
71
                break;
72
            case 'secp256k1':
73
                $der = self::p256KPrivateKey($jwk);
74
75
                break;
76
            case 'P-384':
77
                $der = self::p384PrivateKey($jwk);
78
79
                break;
80
            case 'P-521':
81
                $der = self::p521PrivateKey($jwk);
82
83
                break;
84
            default:
85
                throw new InvalidArgumentException('Unsupported curve.');
86
        }
87
        $der .= self::getKey($jwk);
88
        $pem = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL;
89
        $pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
90
        $pem .= '-----END EC PRIVATE KEY-----'.PHP_EOL;
91
92
        return $pem;
93
    }
94
95
    /**
96
     * Creates a EC key with the given curve and additional values.
97
     *
98
     * @param string $curve  The curve
99
     * @param array  $values values to configure the key
100
     */
101
    public static function createECKey(string $curve, array $values = []): JWK
102
    {
103
        $jwk = self::createECKeyUsingOpenSSL($curve);
104
        $values = array_merge($values, $jwk);
105
106
        return new JWK($values);
107
    }
108
109
    private static function getNistCurveSize(string $curve): int
110
    {
111
        switch ($curve) {
112
            case 'P-256':
113
            case 'secp256k1':
114
                return 256;
115
            case 'P-384':
116
                return 384;
117
            case 'P-521':
118
                return 521;
119
            default:
120
                throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
121
        }
122
    }
123
124
    private static function createECKeyUsingOpenSSL(string $curve): array
125
    {
126
        $key = openssl_pkey_new([
127
            'curve_name' => self::getOpensslCurveName($curve),
128
            'private_key_type' => OPENSSL_KEYTYPE_EC,
129
        ]);
130
        if (false === $key) {
131
            throw new RuntimeException('Unable to create the key');
132
        }
133
        $result = openssl_pkey_export($key, $out);
134
        if (false === $result) {
135
            throw new RuntimeException('Unable to create the key');
136
        }
137
        $res = openssl_pkey_get_private($out);
138
        if (false === $res) {
139
            throw new RuntimeException('Unable to create the key');
140
        }
141
        $details = openssl_pkey_get_details($res);
142
        $nistCurveSize = self::getNistCurveSize($curve);
143
144
        return [
145
            'kty' => 'EC',
146
            'crv' => $curve,
147
            'd' => Base64Url::encode(str_pad($details['ec']['d'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
148
            'x' => Base64Url::encode(str_pad($details['ec']['x'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
149
            'y' => Base64Url::encode(str_pad($details['ec']['y'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
150
        ];
151
    }
152
153
    private static function getOpensslCurveName(string $curve): string
154
    {
155
        switch ($curve) {
156
            case 'P-256':
157
                return 'prime256v1';
158
            case 'secp256k1':
159
                return 'secp256k1';
160
            case 'P-384':
161
                return 'secp384r1';
162
            case 'P-521':
163
                return 'secp521r1';
164
            default:
165
                throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
166
        }
167
    }
168
169
    private static function p256PublicKey(): string
170
    {
171
        return pack(
172
            'H*',
173
            '3059' // SEQUENCE, length 89
174
                .'3013' // SEQUENCE, length 19
175
                    .'0607' // OID, length 7
176
                        .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
177
                    .'0608' // OID, length 8
178
                        .'2a8648ce3d030107' // 1.2.840.10045.3.1.7 = P-256 Curve
179
                .'0342' // BIT STRING, length 66
180
                    .'00' // prepend with NUL - pubkey will follow
181
        );
182
    }
183
184
    private static function p256KPublicKey(): string
185
    {
186
        return pack(
187
            'H*',
188
            '3056' // SEQUENCE, length 86
189
                .'3010' // SEQUENCE, length 16
190
                    .'0607' // OID, length 7
191
                        .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
192
                    .'0605' // OID, length 8
193
                        .'2B8104000A' // 1.3.132.0.10 secp256k1
194
                .'0342' // BIT STRING, length 66
195
                    .'00' // prepend with NUL - pubkey will follow
196
        );
197
    }
198
199
    private static function p384PublicKey(): string
200
    {
201
        return pack(
202
            'H*',
203
            '3076' // SEQUENCE, length 118
204
                .'3010' // SEQUENCE, length 16
205
                    .'0607' // OID, length 7
206
                        .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
207
                    .'0605' // OID, length 5
208
                        .'2b81040022' // 1.3.132.0.34 = P-384 Curve
209
                .'0362' // BIT STRING, length 98
210
                    .'00' // prepend with NUL - pubkey will follow
211
        );
212
    }
213
214
    private static function p521PublicKey(): string
215
    {
216
        return pack(
217
            'H*',
218
            '30819b' // SEQUENCE, length 154
219
                .'3010' // SEQUENCE, length 16
220
                    .'0607' // OID, length 7
221
                        .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
222
                    .'0605' // OID, length 5
223
                        .'2b81040023' // 1.3.132.0.35 = P-521 Curve
224
                .'038186' // BIT STRING, length 134
225
                    .'00' // prepend with NUL - pubkey will follow
226
        );
227
    }
228
229
    private static function p256PrivateKey(JWK $jwk): string
230
    {
231
        $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
232
233
        return pack(
234
            'H*',
235
            '3077' // SEQUENCE, length 87+length($d)=32
236
                .'020101' // INTEGER, 1
237
                .'0420'   // OCTET STRING, length($d) = 32
238
                    .$d
239
                .'a00a' // TAGGED OBJECT #0, length 10
240
                    .'0608' // OID, length 8
241
                        .'2a8648ce3d030107' // 1.3.132.0.34 = P-256 Curve
242
                .'a144' //  TAGGED OBJECT #1, length 68
243
                    .'0342' // BIT STRING, length 66
244
                    .'00' // prepend with NUL - pubkey will follow
245
        );
246
    }
247
248
    private static function p256KPrivateKey(JWK $jwk): string
249
    {
250
        $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
251
252
        return pack(
253
            'H*',
254
            '3074' // SEQUENCE, length 84+length($d)=32
255
                .'020101' // INTEGER, 1
256
                .'0420'   // OCTET STRING, length($d) = 32
257
                    .$d
258
                .'a007' // TAGGED OBJECT #0, length 7
259
                    .'0605' // OID, length 5
260
                        .'2b8104000a' //  1.3.132.0.10 secp256k1
261
                .'a144' //  TAGGED OBJECT #1, length 68
262
                    .'0342' // BIT STRING, length 66
263
                    .'00' // prepend with NUL - pubkey will follow
264
        );
265
    }
266
267
    private static function p384PrivateKey(JWK $jwk): string
268
    {
269
        $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 48, "\0", STR_PAD_LEFT))[1];
270
271
        return pack(
272
            'H*',
273
            '3081a4' // SEQUENCE, length 116 + length($d)=48
274
                .'020101' // INTEGER, 1
275
                .'0430'   // OCTET STRING, length($d) = 30
276
                    .$d
277
                .'a007' // TAGGED OBJECT #0, length 7
278
                    .'0605' // OID, length 5
279
                        .'2b81040022' // 1.3.132.0.34 = P-384 Curve
280
                .'a164' //  TAGGED OBJECT #1, length 100
281
                    .'0362' // BIT STRING, length 98
282
                    .'00' // prepend with NUL - pubkey will follow
283
        );
284
    }
285
286
    private static function p521PrivateKey(JWK $jwk): string
287
    {
288
        $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 66, "\0", STR_PAD_LEFT))[1];
289
290
        return pack(
291
            'H*',
292
            '3081dc' // SEQUENCE, length 154 + length($d)=66
293
                .'020101' // INTEGER, 1
294
                .'0442'   // OCTET STRING, length(d) = 66
295
                    .$d
296
                .'a007' // TAGGED OBJECT #0, length 7
297
                    .'0605' // OID, length 5
298
                        .'2b81040023' // 1.3.132.0.35 = P-521 Curve
299
                .'a18189' //  TAGGED OBJECT #1, length 137
300
                    .'038186' // BIT STRING, length 134
301
                    .'00' // prepend with NUL - pubkey will follow
302
        );
303
    }
304
305
    private static function getKey(JWK $jwk): string
306
    {
307
        $nistCurveSize = self::getNistCurveSize($jwk->get('crv'));
308
        $length = (int) ceil($nistCurveSize / 8);
309
310
        return
311
            "\04"
312
            .str_pad(Base64Url::decode($jwk->get('x')), $length, "\0", STR_PAD_LEFT)
313
            .str_pad(Base64Url::decode($jwk->get('y')), $length, "\0", STR_PAD_LEFT);
314
    }
315
}
316