Failed Conditions
Push — master ( 28d61e...98c33a )
by Florent
03:29
created

src/Component/KeyManagement/KeyConverter/ECKey.php (12 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\KeyConverter;
15
16
use Base64Url\Base64Url;
17
use FG\ASN1\ASNObject;
18
use FG\ASN1\ExplicitlyTaggedObject;
19
use FG\ASN1\Universal\BitString;
20
use FG\ASN1\Universal\Integer;
21
use FG\ASN1\Universal\ObjectIdentifier;
22
use FG\ASN1\Universal\OctetString;
23
use FG\ASN1\Universal\Sequence;
24
25
/**
26
 * @internal
27
 */
28
class ECKey
29
{
30
    /**
31
     * @var array
32
     */
33
    private $values = [];
34
35
    /**
36
     * ECKey constructor.
37
     *
38
     * @param array $data
39
     */
40
    private function __construct(array $data)
41
    {
42
        $this->loadJWK($data);
43
    }
44
45
    /**
46
     * @param string $pem
47
     *
48
     * @return ECKey
49
     */
50
    public static function createFromPEM(string $pem): self
51
    {
52
        $data = self::loadPEM($pem);
53
54
        return new self($data);
55
    }
56
57
    /**
58
     * @param string $data
59
     *
60
     * @throws \Exception
61
     *
62
     * @return array
63
     */
64
    private static function loadPEM(string $data): array
65
    {
66
        $data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data));
67
        $asnObject = ASNObject::fromBinary($data);
68
69
        if (!$asnObject instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
70
            throw new \InvalidArgumentException('Unable to load the key.');
71
        }
72
        $children = $asnObject->getChildren();
73
        if (self::isPKCS8($children)) {
74
            $children = self::loadPKCS8($children);
75
        }
76
77
        if (4 === count($children)) {
78
            return self::loadPrivatePEM($children);
79
        } elseif (2 === count($children)) {
80
            return self::loadPublicPEM($children);
81
        }
82
83
        throw new \Exception('Unable to load the key.');
84
    }
85
86
    /**
87
     * @param ASNObject[] $children
88
     *
89
     * @return array
90
     */
91
    private static function loadPKCS8(array $children): array
92
    {
93
        $binary = hex2bin($children[2]->getContent());
94
        $asnObject = ASNObject::fromBinary($binary);
95
        if (!$asnObject instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
96
            throw new \InvalidArgumentException('Unable to load the key.');
97
        }
98
99
        return $asnObject->getChildren();
100
    }
101
102
    /**
103
     * @param ASNObject[] $children
104
     *
105
     * @return array
106
     */
107
    private static function loadPublicPEM(array $children): array
108
    {
109
        if (!$children[0] instanceof Sequence) {
0 ignored issues
show
The class FG\ASN1\Universal\Sequence does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
110
            throw new \InvalidArgumentException('Unsupported key type.');
111
        }
112
113
        $sub = $children[0]->getChildren();
114
        if (!$sub[0] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
115
            throw new \InvalidArgumentException('Unsupported key type.');
116
        }
117
        if ('1.2.840.10045.2.1' !== $sub[0]->getContent()) {
118
            throw new \InvalidArgumentException('Unsupported key type.');
119
        }
120
        if (!$sub[1] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
121
            throw new \InvalidArgumentException('Unsupported key type.');
122
        }
123
        if (!$children[1] instanceof BitString) {
0 ignored issues
show
The class FG\ASN1\Universal\BitString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
124
            throw new \InvalidArgumentException('Unable to load the key.');
125
        }
126
127
        $bits = $children[1]->getContent();
128
        $bits_length = mb_strlen($bits, '8bit');
129
        if ('04' !== mb_substr($bits, 0, 2, '8bit')) {
130
            throw new \InvalidArgumentException('Unsupported key type');
131
        }
132
133
        $values = ['kty' => 'EC'];
134
        $values['crv'] = self::getCurve($sub[1]->getContent());
135
        $values['x'] = Base64Url::encode(hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit')));
136
        $values['y'] = Base64Url::encode(hex2bin(mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit')));
137
138
        return $values;
139
    }
140
141
    /**
142
     * @param string $oid
143
     *
144
     * @return string
145
     */
146
    private static function getCurve(string $oid): string
147
    {
148
        $curves = self::getSupportedCurves();
149
        $curve = array_search($oid, $curves, true);
150
        if (!is_string($curve)) {
151
            throw new \InvalidArgumentException('Unsupported OID.');
152
        }
153
154
        return $curve;
155
    }
156
157
    /**
158
     * @return array
159
     */
160
    private static function getSupportedCurves(): array
161
    {
162
        return [
163
            'P-256' => '1.2.840.10045.3.1.7',
164
            'P-384' => '1.3.132.0.34',
165
            'P-521' => '1.3.132.0.35',
166
        ];
167
    }
168
169
    /**
170
     * @param ASNObject $children
171
     */
172
    private static function verifyVersion(ASNObject $children)
173
    {
174
        if (!$children instanceof Integer || '1' !== $children->getContent()) {
0 ignored issues
show
The class FG\ASN1\Universal\Integer does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
175
            throw new \InvalidArgumentException('Unable to load the key.');
176
        }
177
    }
178
179
    /**
180
     * @param ASNObject   $children
181
     * @param string|null $x
182
     * @param string|null $y
183
     */
184
    private static function getXAndY(ASNObject $children, ?string &$x, ?string &$y)
185
    {
186
        if (!$children instanceof ExplicitlyTaggedObject || !is_array($children->getContent())) {
0 ignored issues
show
The class FG\ASN1\ExplicitlyTaggedObject does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
187
            throw new \InvalidArgumentException('Unable to load the key.');
188
        }
189
        if (!$children->getContent()[0] instanceof BitString) {
0 ignored issues
show
The class FG\ASN1\Universal\BitString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
190
            throw new \InvalidArgumentException('Unable to load the key.');
191
        }
192
193
        $bits = $children->getContent()[0]->getContent();
194
        $bits_length = mb_strlen($bits, '8bit');
195
196
        if ('04' !== mb_substr($bits, 0, 2, '8bit')) {
197
            throw new \InvalidArgumentException('Unsupported key type');
198
        }
199
200
        $x = mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit');
201
        $y = mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit');
202
    }
203
204
    /**
205
     * @param ASNObject $children
206
     *
207
     * @return string
208
     */
209
    private static function getD(ASNObject $children): string
210
    {
211
        if (!$children instanceof OctetString) {
0 ignored issues
show
The class FG\ASN1\Universal\OctetString does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
212
            throw new \InvalidArgumentException('Unable to load the key.');
213
        }
214
215
        return $children->getContent();
216
    }
217
218
    /**
219
     * @param array $children
220
     *
221
     * @return array
222
     */
223
    private static function loadPrivatePEM(array $children): array
224
    {
225
        self::verifyVersion($children[0]);
226
        $x = null;
227
        $y = null;
228
        $d = self::getD($children[1]);
229
        self::getXAndY($children[3], $x, $y);
230
231
        if (!$children[2] instanceof ExplicitlyTaggedObject || !is_array($children[2]->getContent())) {
0 ignored issues
show
The class FG\ASN1\ExplicitlyTaggedObject does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
232
            throw new \InvalidArgumentException('Unable to load the key.');
233
        }
234
        if (!$children[2]->getContent()[0] instanceof ObjectIdentifier) {
0 ignored issues
show
The class FG\ASN1\Universal\ObjectIdentifier does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
235
            throw new \InvalidArgumentException('Unable to load the key.');
236
        }
237
238
        $curve = $children[2]->getContent()[0]->getContent();
239
240
        $values = ['kty' => 'EC'];
241
        $values['crv'] = self::getCurve($curve);
242
        $values['d'] = Base64Url::encode(hex2bin($d));
243
        $values['x'] = Base64Url::encode(hex2bin($x));
244
        $values['y'] = Base64Url::encode(hex2bin($y));
245
246
        return $values;
247
    }
248
249
    /**
250
     * @param ASNObject[] $children
251
     *
252
     * @return bool
253
     */
254
    private static function isPKCS8(array $children): bool
255
    {
256
        if (3 !== count($children)) {
257
            return false;
258
        }
259
260
        $classes = [0 => Integer::class, 1 => Sequence::class, 2 => OctetString::class];
261
        foreach ($classes as $k => $class) {
262
            if (!$children[$k] instanceof $class) {
263
                return false;
264
            }
265
        }
266
267
        return true;
268
    }
269
270
    /**
271
     * @param ECKey $private
272
     *
273
     * @return ECKey
274
     */
275
    public static function toPublic(self $private): self
276
    {
277
        $data = $private->toArray();
278
        if (array_key_exists('d', $data)) {
279
            unset($data['d']);
280
        }
281
282
        return new self($data);
283
    }
284
285
    /**
286
     * @return array
287
     */
288
    public function toArray()
289
    {
290
        return $this->values;
291
    }
292
293
    /**
294
     * @param array $jwk
295
     */
296
    private function loadJWK(array $jwk)
297
    {
298
        $keys = [
299
            'kty' => 'The key parameter "kty" is missing.',
300
            'crv' => 'Curve parameter is missing',
301
            'x'   => 'Point parameters are missing.',
302
            'y'   => 'Point parameters are missing.',
303
        ];
304
        foreach ($keys as $k => $v) {
305
            if (!array_key_exists($k, $jwk)) {
306
                throw new \InvalidArgumentException($v);
307
            }
308
        }
309
310
        if ('EC' !== $jwk['kty']) {
311
            throw new \InvalidArgumentException('JWK is not an Elliptic Curve key.');
312
        }
313
        $this->values = $jwk;
314
    }
315
}
316