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
|
|||
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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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 |
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.