Spomky-Labs /
jose
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /* |
||
| 4 | * The MIT License (MIT) |
||
| 5 | * |
||
| 6 | * Copyright (c) 2014-2017 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\KeyConverter; |
||
| 13 | |||
| 14 | use Assert\Assertion; |
||
| 15 | use Base64Url\Base64Url; |
||
| 16 | use FG\ASN1\ExplicitlyTaggedObject; |
||
| 17 | use FG\ASN1\ASNObject; |
||
| 18 | use FG\ASN1\Universal\BitString; |
||
| 19 | use FG\ASN1\Universal\Integer; |
||
| 20 | use FG\ASN1\Universal\ObjectIdentifier; |
||
| 21 | use FG\ASN1\Universal\OctetString; |
||
| 22 | use FG\ASN1\Universal\Sequence; |
||
| 23 | use Jose\Object\JWKInterface; |
||
| 24 | |||
| 25 | final class ECKey extends Sequence |
||
| 26 | { |
||
| 27 | /** |
||
| 28 | * @var bool |
||
| 29 | */ |
||
| 30 | private $private = false; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * @var array |
||
| 34 | */ |
||
| 35 | private $values = []; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @param \Jose\Object\JWKInterface|string|array $data |
||
| 39 | */ |
||
| 40 | public function __construct($data) |
||
| 41 | { |
||
| 42 | parent::__construct(); |
||
| 43 | |||
| 44 | if ($data instanceof JWKInterface) { |
||
| 45 | $this->loadJWK($data->getAll()); |
||
| 46 | } elseif (is_array($data)) { |
||
| 47 | $this->loadJWK($data); |
||
| 48 | } elseif (is_string($data)) { |
||
| 49 | $this->loadPEM($data); |
||
| 50 | } else { |
||
| 51 | throw new \InvalidArgumentException('Unsupported input'); |
||
| 52 | } |
||
| 53 | $this->private = isset($this->values['d']); |
||
| 54 | } |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @param string $data |
||
| 58 | * |
||
| 59 | * @throws \Exception |
||
| 60 | * @throws \FG\ASN1\Exception\ParserException |
||
| 61 | * |
||
| 62 | * @return array |
||
| 63 | */ |
||
| 64 | private function loadPEM($data) |
||
| 65 | { |
||
| 66 | $data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data)); |
||
| 67 | $asnObject = ASNObject::fromBinary($data); |
||
| 68 | |||
| 69 | Assertion::isInstanceOf($asnObject, Sequence::class); |
||
| 70 | $children = $asnObject->getChildren(); |
||
|
0 ignored issues
–
show
|
|||
| 71 | if (self::isPKCS8($children)) { |
||
| 72 | $children = self::loadPKCS8($children); |
||
| 73 | } |
||
| 74 | |||
| 75 | if (4 === count($children)) { |
||
| 76 | return $this->loadPrivatePEM($children); |
||
| 77 | } elseif (2 === count($children)) { |
||
| 78 | return $this->loadPublicPEM($children); |
||
| 79 | } |
||
| 80 | |||
| 81 | throw new \Exception('Unable to load the key'); |
||
| 82 | } |
||
| 83 | |||
| 84 | /** |
||
| 85 | * @param array $children |
||
| 86 | * |
||
| 87 | * @return array |
||
| 88 | */ |
||
| 89 | private function loadPKCS8(array $children) |
||
| 90 | { |
||
| 91 | $binary = hex2bin($children[2]->getContent()); |
||
| 92 | $asnObject = ASNObject::fromBinary($binary); |
||
| 93 | Assertion::isInstanceOf($asnObject, Sequence::class); |
||
| 94 | |||
| 95 | return $asnObject->getChildren(); |
||
|
0 ignored issues
–
show
The method
getChildren does only exist in FG\ASN1\Construct, but not in FG\ASN1\AbstractString a...d FG\ASN1\UnknownObject.
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
Loading history...
|
|||
| 96 | } |
||
| 97 | |||
| 98 | /** |
||
| 99 | * @param array $children |
||
| 100 | * |
||
| 101 | * @return bool |
||
| 102 | */ |
||
| 103 | private function isPKCS8(array $children) |
||
| 104 | { |
||
| 105 | if (3 !== count($children)) { |
||
| 106 | return false; |
||
| 107 | } |
||
| 108 | |||
| 109 | $classes = [0 => Integer::class, 1 => Sequence::class, 2 => OctetString::class]; |
||
| 110 | foreach ($classes as $k => $class) { |
||
| 111 | if (!$children[$k] instanceof $class) { |
||
| 112 | return false; |
||
| 113 | } |
||
| 114 | } |
||
| 115 | |||
| 116 | return true; |
||
| 117 | } |
||
| 118 | |||
| 119 | /** |
||
| 120 | * @param array $jwk |
||
| 121 | */ |
||
| 122 | private function loadJWK(array $jwk) |
||
| 123 | { |
||
| 124 | Assertion::true(array_key_exists('kty', $jwk), 'JWK is not an Elliptic Curve key'); |
||
| 125 | Assertion::eq($jwk['kty'], 'EC', 'JWK is not an Elliptic Curve key'); |
||
| 126 | Assertion::true(array_key_exists('crv', $jwk), 'Curve parameter is missing'); |
||
| 127 | Assertion::true(array_key_exists('x', $jwk), 'Point parameters are missing'); |
||
| 128 | Assertion::true(array_key_exists('y', $jwk), 'Point parameters are missing'); |
||
| 129 | |||
| 130 | $this->values = $jwk; |
||
| 131 | if (array_key_exists('d', $jwk)) { |
||
| 132 | $this->initPrivateKey(); |
||
| 133 | } else { |
||
| 134 | $this->initPublicKey(); |
||
| 135 | } |
||
| 136 | } |
||
| 137 | |||
| 138 | private function initPublicKey() |
||
| 139 | { |
||
| 140 | $oid_sequence = new Sequence(); |
||
| 141 | $oid_sequence->addChild(new ObjectIdentifier('1.2.840.10045.2.1')); |
||
| 142 | $oid_sequence->addChild(new ObjectIdentifier($this->getOID($this->values['crv']))); |
||
| 143 | $this->addChild($oid_sequence); |
||
| 144 | |||
| 145 | $bits = '04'; |
||
| 146 | $bits .= bin2hex(Base64Url::decode($this->values['x'])); |
||
| 147 | $bits .= bin2hex(Base64Url::decode($this->values['y'])); |
||
| 148 | $this->addChild(new BitString($bits)); |
||
| 149 | } |
||
| 150 | |||
| 151 | private function initPrivateKey() |
||
| 152 | { |
||
| 153 | $this->addChild(new Integer(1)); |
||
| 154 | $this->addChild(new OctetString(bin2hex(Base64Url::decode($this->values['d'])))); |
||
| 155 | |||
| 156 | $oid = new ObjectIdentifier($this->getOID($this->values['crv'])); |
||
| 157 | $this->addChild(new ExplicitlyTaggedObject(0, $oid)); |
||
| 158 | |||
| 159 | $bits = '04'; |
||
| 160 | $bits .= bin2hex(Base64Url::decode($this->values['x'])); |
||
| 161 | $bits .= bin2hex(Base64Url::decode($this->values['y'])); |
||
| 162 | $bit = new BitString($bits); |
||
| 163 | $this->addChild(new ExplicitlyTaggedObject(1, $bit)); |
||
| 164 | } |
||
| 165 | |||
| 166 | /** |
||
| 167 | * @param array $children |
||
| 168 | * |
||
| 169 | * @throws \Exception |
||
| 170 | * |
||
| 171 | * @return array |
||
| 172 | */ |
||
| 173 | private function loadPublicPEM(array $children) |
||
| 174 | { |
||
| 175 | Assertion::isInstanceOf($children[0], Sequence::class, 'Unsupported key type'); |
||
| 176 | |||
| 177 | $sub = $children[0]->getChildren(); |
||
| 178 | Assertion::isInstanceOf($sub[0], ObjectIdentifier::class, 'Unsupported key type'); |
||
| 179 | Assertion::eq('1.2.840.10045.2.1', $sub[0]->getContent(), 'Unsupported key type'); |
||
| 180 | |||
| 181 | Assertion::isInstanceOf($sub[1], ObjectIdentifier::class, 'Unsupported key type'); |
||
| 182 | Assertion::isInstanceOf($children[1], BitString::class, 'Unable to load the key'); |
||
| 183 | |||
| 184 | $bits = $children[1]->getContent(); |
||
| 185 | $bits_length = mb_strlen($bits, '8bit'); |
||
| 186 | |||
| 187 | Assertion::eq('04', mb_substr($bits, 0, 2, '8bit'), 'Unsupported key type'); |
||
| 188 | |||
| 189 | $this->values['kty'] = 'EC'; |
||
| 190 | $this->values['crv'] = $this->getCurve($sub[1]->getContent()); |
||
| 191 | $this->values['x'] = Base64Url::encode(hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit'))); |
||
| 192 | $this->values['y'] = Base64Url::encode(hex2bin(mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit'))); |
||
| 193 | } |
||
| 194 | |||
| 195 | /** |
||
| 196 | * @param \FG\ASN1\ASNObject $children |
||
| 197 | */ |
||
| 198 | private function verifyVersion(ASNObject $children) |
||
| 199 | { |
||
| 200 | Assertion::isInstanceOf($children, Integer::class, 'Unable to load the key'); |
||
| 201 | Assertion::eq(1, $children->getContent(), 'Unable to load the key'); |
||
| 202 | } |
||
| 203 | |||
| 204 | /** |
||
| 205 | * @param \FG\ASN1\ASNObject $children |
||
| 206 | * @param string|null $x |
||
| 207 | * @param string|null $y |
||
| 208 | */ |
||
| 209 | private function getXAndY(ASNObject $children, &$x, &$y) |
||
| 210 | { |
||
| 211 | Assertion::isInstanceOf($children, ExplicitlyTaggedObject::class, 'Unable to load the key'); |
||
| 212 | Assertion::isArray($children->getContent(), 'Unable to load the key'); |
||
| 213 | Assertion::isInstanceOf($children->getContent()[0], BitString::class, 'Unable to load the key'); |
||
| 214 | |||
| 215 | $bits = $children->getContent()[0]->getContent(); |
||
| 216 | $bits_length = mb_strlen($bits, '8bit'); |
||
| 217 | |||
| 218 | Assertion::eq('04', mb_substr($bits, 0, 2, '8bit'), 'Unsupported key type'); |
||
| 219 | |||
| 220 | $x = mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit'); |
||
| 221 | $y = mb_substr($bits, ($bits_length - 2) / 2 + 2, ($bits_length - 2) / 2, '8bit'); |
||
| 222 | } |
||
| 223 | |||
| 224 | /** |
||
| 225 | * @param \FG\ASN1\ASNObject $children |
||
| 226 | * |
||
| 227 | * @return string |
||
| 228 | */ |
||
| 229 | private function getD(ASNObject $children) |
||
| 230 | { |
||
| 231 | Assertion::isInstanceOf($children, '\FG\ASN1\Universal\OctetString', 'Unable to load the key'); |
||
| 232 | |||
| 233 | return $children->getContent(); |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * @param array $children |
||
| 238 | * |
||
| 239 | * @return array |
||
| 240 | */ |
||
| 241 | private function loadPrivatePEM(array $children) |
||
| 242 | { |
||
| 243 | $this->verifyVersion($children[0]); |
||
| 244 | $x = null; |
||
| 245 | $y = null; |
||
| 246 | $d = $this->getD($children[1]); |
||
| 247 | $this->getXAndY($children[3], $x, $y); |
||
| 248 | |||
| 249 | Assertion::isInstanceOf($children[2], ExplicitlyTaggedObject::class, 'Unable to load the key'); |
||
| 250 | Assertion::isArray($children[2]->getContent(), 'Unable to load the key'); |
||
| 251 | Assertion::isInstanceOf($children[2]->getContent()[0], ObjectIdentifier::class, 'Unable to load the key'); |
||
| 252 | |||
| 253 | $curve = $children[2]->getContent()[0]->getContent(); |
||
| 254 | |||
| 255 | $this->private = true; |
||
| 256 | $this->values['kty'] = 'EC'; |
||
| 257 | $this->values['crv'] = $this->getCurve($curve); |
||
| 258 | $this->values['d'] = Base64Url::encode(hex2bin($d)); |
||
| 259 | $this->values['x'] = Base64Url::encode(hex2bin($x)); |
||
| 260 | $this->values['y'] = Base64Url::encode(hex2bin($y)); |
||
| 261 | } |
||
| 262 | |||
| 263 | /** |
||
| 264 | * @return bool |
||
| 265 | */ |
||
| 266 | public function isPrivate() |
||
| 267 | { |
||
| 268 | return $this->private; |
||
| 269 | } |
||
| 270 | |||
| 271 | /** |
||
| 272 | * @param \Jose\KeyConverter\ECKey $private |
||
| 273 | * |
||
| 274 | * @return \Jose\KeyConverter\ECKey |
||
| 275 | */ |
||
| 276 | public static function toPublic(ECKey $private) |
||
| 277 | { |
||
| 278 | $data = $private->toArray(); |
||
| 279 | if (array_key_exists('d', $data)) { |
||
| 280 | unset($data['d']); |
||
| 281 | } |
||
| 282 | |||
| 283 | return new self($data); |
||
| 284 | } |
||
| 285 | |||
| 286 | /** |
||
| 287 | * @return string |
||
| 288 | */ |
||
| 289 | public function __toString() |
||
| 290 | { |
||
| 291 | return $this->toPEM(); |
||
| 292 | } |
||
| 293 | |||
| 294 | /** |
||
| 295 | * @return array |
||
| 296 | */ |
||
| 297 | public function toArray() |
||
| 298 | { |
||
| 299 | return $this->values; |
||
| 300 | } |
||
| 301 | |||
| 302 | /** |
||
| 303 | * @return string |
||
| 304 | */ |
||
| 305 | public function toDER() |
||
| 306 | { |
||
| 307 | return $this->getBinary(); |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * @return string |
||
| 312 | */ |
||
| 313 | public function toPEM() |
||
| 314 | { |
||
| 315 | $result = '-----BEGIN '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; |
||
| 316 | $result .= chunk_split(base64_encode($this->getBinary()), 64, PHP_EOL); |
||
| 317 | $result .= '-----END '.($this->private ? 'EC PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; |
||
| 318 | |||
| 319 | return $result; |
||
| 320 | } |
||
| 321 | |||
| 322 | /** |
||
| 323 | * @param $curve |
||
| 324 | * |
||
| 325 | * @return string |
||
| 326 | */ |
||
| 327 | private function getOID($curve) |
||
| 328 | { |
||
| 329 | $curves = $this->getSupportedCurves(); |
||
| 330 | $oid = array_key_exists($curve, $curves) ? $curves[$curve] : null; |
||
| 331 | |||
| 332 | Assertion::notNull($oid, 'Unsupported curve'); |
||
| 333 | |||
| 334 | return $oid; |
||
| 335 | } |
||
| 336 | |||
| 337 | /** |
||
| 338 | * @param string $oid |
||
| 339 | * |
||
| 340 | * @return string |
||
| 341 | */ |
||
| 342 | private function getCurve($oid) |
||
| 343 | { |
||
| 344 | $curves = $this->getSupportedCurves(); |
||
| 345 | $curve = array_search($oid, $curves, true); |
||
| 346 | Assertion::string($curve, 'Unsupported OID'); |
||
| 347 | |||
| 348 | return $curve; |
||
| 349 | } |
||
| 350 | |||
| 351 | /** |
||
| 352 | * @return array |
||
| 353 | */ |
||
| 354 | private function getSupportedCurves() |
||
| 355 | { |
||
| 356 | return [ |
||
| 357 | 'P-256' => '1.2.840.10045.3.1.7', |
||
| 358 | 'P-384' => '1.3.132.0.34', |
||
| 359 | 'P-521' => '1.3.132.0.35', |
||
| 360 | ]; |
||
| 361 | } |
||
| 362 | } |
||
| 363 |
It seems like the method you are trying to call exists only in some of the possible types.
Let’s take a look at an example:
Available Fixes
Add an additional type-check:
Only allow a single type to be passed if the variable comes from a parameter: