1<?php 2 3/** 4 * Base Class for all asymmetric key ciphers 5 * 6 * PHP version 5 7 * 8 * @author Jim Wigginton <terrafrost@php.net> 9 * @copyright 2016 Jim Wigginton 10 * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 * @link http://phpseclib.sourceforge.net 12 */ 13 14namespace phpseclib3\Crypt\Common; 15 16use phpseclib3\Crypt\DSA; 17use phpseclib3\Crypt\Hash; 18use phpseclib3\Crypt\RSA; 19use phpseclib3\Exception\NoKeyLoadedException; 20use phpseclib3\Exception\UnsupportedFormatException; 21use phpseclib3\Math\BigInteger; 22 23/** 24 * Base Class for all asymmetric cipher classes 25 * 26 * @author Jim Wigginton <terrafrost@php.net> 27 */ 28abstract class AsymmetricKey 29{ 30 /** 31 * Precomputed Zero 32 * 33 * @var BigInteger 34 */ 35 protected static $zero; 36 37 /** 38 * Precomputed One 39 * 40 * @var BigInteger 41 */ 42 protected static $one; 43 44 /** 45 * Format of the loaded key 46 * 47 * @var string 48 */ 49 protected $format; 50 51 /** 52 * Hash function 53 * 54 * @var Hash 55 */ 56 protected $hash; 57 58 /** 59 * HMAC function 60 * 61 * @var Hash 62 */ 63 private $hmac; 64 65 /** 66 * Supported plugins (lower case) 67 * 68 * @see self::initialize_static_variables() 69 * @var array 70 */ 71 private static $plugins = []; 72 73 /** 74 * Invisible plugins 75 * 76 * @see self::initialize_static_variables() 77 * @var array 78 */ 79 private static $invisiblePlugins = []; 80 81 /** 82 * Key Comment 83 * 84 * @var null|string 85 */ 86 private $comment; 87 88 /** 89 * OpenSSL configuration file name. 90 * 91 * @see self::createKey() 92 * @var ?string 93 */ 94 protected static $configFile; 95 96 /** 97 * @param string $type 98 * @return array|string 99 */ 100 abstract public function toString($type, array $options = []); 101 102 /** 103 * The constructor 104 */ 105 protected function __construct() 106 { 107 self::initialize_static_variables(); 108 109 $this->hash = new Hash('sha256'); 110 $this->hmac = new Hash('sha256'); 111 } 112 113 /** 114 * Initialize static variables 115 */ 116 protected static function initialize_static_variables() 117 { 118 if (!isset(self::$zero)) { 119 self::$zero = new BigInteger(0); 120 self::$one = new BigInteger(1); 121 } 122 123 if (!isset(self::$configFile)) { 124 self::$configFile = dirname(__FILE__) . '/../../openssl.cnf'; 125 } 126 127 self::loadPlugins('Keys'); 128 if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') { 129 self::loadPlugins('Signature'); 130 } 131 } 132 133 /** 134 * Load the key 135 * 136 * @param string $key 137 * @param string $password optional 138 * @return PublicKey|PrivateKey 139 */ 140 public static function load($key, $password = false) 141 { 142 self::initialize_static_variables(); 143 144 $class = new \ReflectionClass(static::class); 145 if ($class->isFinal()) { 146 throw new \RuntimeException('load() should not be called from final classes (' . static::class . ')'); 147 } 148 149 $components = false; 150 foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { 151 if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) { 152 continue; 153 } 154 try { 155 $components = $format::load($key, $password); 156 } catch (\Exception $e) { 157 $components = false; 158 } 159 if ($components !== false) { 160 break; 161 } 162 } 163 164 if ($components === false) { 165 throw new NoKeyLoadedException('Unable to read key'); 166 } 167 168 $components['format'] = $format; 169 $components['secret'] = isset($components['secret']) ? $components['secret'] : ''; 170 $comment = isset($components['comment']) ? $components['comment'] : null; 171 $new = static::onLoad($components); 172 $new->format = $format; 173 $new->comment = $comment; 174 return $new instanceof PrivateKey ? 175 $new->withPassword($password) : 176 $new; 177 } 178 179 /** 180 * Loads a private key 181 * 182 * @return PrivateKey 183 * @param string|array $key 184 * @param string $password optional 185 */ 186 public static function loadPrivateKey($key, $password = '') 187 { 188 $key = self::load($key, $password); 189 if (!$key instanceof PrivateKey) { 190 throw new NoKeyLoadedException('The key that was loaded was not a private key'); 191 } 192 return $key; 193 } 194 195 /** 196 * Loads a public key 197 * 198 * @return PublicKey 199 * @param string|array $key 200 */ 201 public static function loadPublicKey($key) 202 { 203 $key = self::load($key); 204 if (!$key instanceof PublicKey) { 205 throw new NoKeyLoadedException('The key that was loaded was not a public key'); 206 } 207 return $key; 208 } 209 210 /** 211 * Loads parameters 212 * 213 * @return AsymmetricKey 214 * @param string|array $key 215 */ 216 public static function loadParameters($key) 217 { 218 $key = self::load($key); 219 if (!$key instanceof PrivateKey && !$key instanceof PublicKey) { 220 throw new NoKeyLoadedException('The key that was loaded was not a parameter'); 221 } 222 return $key; 223 } 224 225 /** 226 * Load the key, assuming a specific format 227 * 228 * @param string $type 229 * @param string $key 230 * @param string $password optional 231 * @return static 232 */ 233 public static function loadFormat($type, $key, $password = false) 234 { 235 self::initialize_static_variables(); 236 237 $components = false; 238 $format = strtolower($type); 239 if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) { 240 $format = self::$plugins[static::ALGORITHM]['Keys'][$format]; 241 $components = $format::load($key, $password); 242 } 243 244 if ($components === false) { 245 throw new NoKeyLoadedException('Unable to read key'); 246 } 247 248 $components['format'] = $format; 249 $components['secret'] = isset($components['secret']) ? $components['secret'] : ''; 250 251 $new = static::onLoad($components); 252 $new->format = $format; 253 return $new instanceof PrivateKey ? 254 $new->withPassword($password) : 255 $new; 256 } 257 258 /** 259 * Loads a private key 260 * 261 * @return PrivateKey 262 * @param string $type 263 * @param string $key 264 * @param string $password optional 265 */ 266 public static function loadPrivateKeyFormat($type, $key, $password = false) 267 { 268 $key = self::loadFormat($type, $key, $password); 269 if (!$key instanceof PrivateKey) { 270 throw new NoKeyLoadedException('The key that was loaded was not a private key'); 271 } 272 return $key; 273 } 274 275 /** 276 * Loads a public key 277 * 278 * @return PublicKey 279 * @param string $type 280 * @param string $key 281 */ 282 public static function loadPublicKeyFormat($type, $key) 283 { 284 $key = self::loadFormat($type, $key); 285 if (!$key instanceof PublicKey) { 286 throw new NoKeyLoadedException('The key that was loaded was not a public key'); 287 } 288 return $key; 289 } 290 291 /** 292 * Loads parameters 293 * 294 * @return AsymmetricKey 295 * @param string $type 296 * @param string|array $key 297 */ 298 public static function loadParametersFormat($type, $key) 299 { 300 $key = self::loadFormat($type, $key); 301 if (!$key instanceof PrivateKey && !$key instanceof PublicKey) { 302 throw new NoKeyLoadedException('The key that was loaded was not a parameter'); 303 } 304 return $key; 305 } 306 307 /** 308 * Validate Plugin 309 * 310 * @param string $format 311 * @param string $type 312 * @param string $method optional 313 * @return mixed 314 */ 315 protected static function validatePlugin($format, $type, $method = null) 316 { 317 $type = strtolower($type); 318 if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) { 319 throw new UnsupportedFormatException("$type is not a supported format"); 320 } 321 $type = self::$plugins[static::ALGORITHM][$format][$type]; 322 if (isset($method) && !method_exists($type, $method)) { 323 throw new UnsupportedFormatException("$type does not implement $method"); 324 } 325 326 return $type; 327 } 328 329 /** 330 * Load Plugins 331 * 332 * @param string $format 333 */ 334 private static function loadPlugins($format) 335 { 336 if (!isset(self::$plugins[static::ALGORITHM][$format])) { 337 self::$plugins[static::ALGORITHM][$format] = []; 338 foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) { 339 if ($file->getExtension() != 'php') { 340 continue; 341 } 342 $name = $file->getBasename('.php'); 343 if ($name[0] == '.') { 344 continue; 345 } 346 $type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name; 347 $reflect = new \ReflectionClass($type); 348 if ($reflect->isTrait()) { 349 continue; 350 } 351 self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type; 352 if ($reflect->hasConstant('IS_INVISIBLE')) { 353 self::$invisiblePlugins[static::ALGORITHM][] = $type; 354 } 355 } 356 } 357 } 358 359 /** 360 * Returns a list of supported formats. 361 * 362 * @return array 363 */ 364 public static function getSupportedKeyFormats() 365 { 366 self::initialize_static_variables(); 367 368 return self::$plugins[static::ALGORITHM]['Keys']; 369 } 370 371 /** 372 * Sets the OpenSSL config file path 373 * 374 * Set to the empty string to use the default config file 375 * 376 * @param string $val 377 */ 378 public static function setOpenSSLConfigPath($val) 379 { 380 self::$configFile = $val; 381 } 382 383 /** 384 * Add a fileformat plugin 385 * 386 * The plugin needs to either already be loaded or be auto-loadable. 387 * Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin. 388 * 389 * @see self::load() 390 * @param string $fullname 391 * @return bool 392 */ 393 public static function addFileFormat($fullname) 394 { 395 self::initialize_static_variables(); 396 397 if (class_exists($fullname)) { 398 $meta = new \ReflectionClass($fullname); 399 $shortname = $meta->getShortName(); 400 self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname; 401 if ($meta->hasConstant('IS_INVISIBLE')) { 402 self::$invisiblePlugins[static::ALGORITHM][] = strtolower($shortname); 403 } 404 } 405 } 406 407 /** 408 * Returns the format of the loaded key. 409 * 410 * If the key that was loaded wasn't in a valid or if the key was auto-generated 411 * with RSA::createKey() then this will throw an exception. 412 * 413 * @see self::load() 414 * @return mixed 415 */ 416 public function getLoadedFormat() 417 { 418 if (empty($this->format)) { 419 throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"'); 420 } 421 422 $meta = new \ReflectionClass($this->format); 423 return $meta->getShortName(); 424 } 425 426 /** 427 * Returns the key's comment 428 * 429 * Not all key formats support comments. If you want to set a comment use toString() 430 * 431 * @return null|string 432 */ 433 public function getComment() 434 { 435 return $this->comment; 436 } 437 438 /** 439 * Force engine (useful for unit testing) 440 */ 441 public static function forceEngine($engine = null) 442 { 443 if (!isset($engine)) { 444 static::$forcedEngine = null; 445 return; 446 } 447 switch ($engine) { 448 case 'PHP': 449 case 'OpenSSL': 450 case 'libsodium': 451 static::$forcedEngine = $engine; 452 break; 453 default: 454 throw new \InvalidArgumentException('Valid engines are null, PHP, OpenSSL or libsodium'); 455 } 456 } 457 458 public static function getForcedEngine() 459 { 460 return static::$forcedEngine; 461 } 462 463 /** 464 * __toString() magic method 465 * 466 * @return string 467 */ 468 public function __toString() 469 { 470 return $this->toString('PKCS8'); 471 } 472 473 /** 474 * Determines which hashing function should be used 475 * 476 * @param string $hash 477 */ 478 public function withHash($hash) 479 { 480 $new = clone $this; 481 482 $new->hash = new Hash($hash); 483 $new->hmac = new Hash($hash); 484 485 return $new; 486 } 487 488 /** 489 * Returns the hash algorithm currently being used 490 * 491 */ 492 public function getHash() 493 { 494 return clone $this->hash; 495 } 496 497 /** 498 * Compute the pseudorandom k for signature generation, 499 * using the process specified for deterministic DSA. 500 * 501 * @param string $h1 502 * @return string 503 */ 504 protected function computek($h1) 505 { 506 $v = str_repeat("\1", strlen($h1)); 507 508 $k = str_repeat("\0", strlen($h1)); 509 510 $x = $this->int2octets($this->x); 511 $h1 = $this->bits2octets($h1); 512 513 $this->hmac->setKey($k); 514 $k = $this->hmac->hash($v . "\0" . $x . $h1); 515 $this->hmac->setKey($k); 516 $v = $this->hmac->hash($v); 517 $k = $this->hmac->hash($v . "\1" . $x . $h1); 518 $this->hmac->setKey($k); 519 $v = $this->hmac->hash($v); 520 521 $qlen = $this->q->getLengthInBytes(); 522 523 while (true) { 524 $t = ''; 525 while (strlen($t) < $qlen) { 526 $v = $this->hmac->hash($v); 527 $t = $t . $v; 528 } 529 $k = $this->bits2int($t); 530 531 if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) { 532 break; 533 } 534 $k = $this->hmac->hash($v . "\0"); 535 $this->hmac->setKey($k); 536 $v = $this->hmac->hash($v); 537 } 538 539 return $k; 540 } 541 542 /** 543 * Integer to Octet String 544 * 545 * @param BigInteger $v 546 * @return string 547 */ 548 private function int2octets($v) 549 { 550 $out = $v->toBytes(); 551 $rolen = $this->q->getLengthInBytes(); 552 if (strlen($out) < $rolen) { 553 return str_pad($out, $rolen, "\0", STR_PAD_LEFT); 554 } elseif (strlen($out) > $rolen) { 555 return substr($out, -$rolen); 556 } else { 557 return $out; 558 } 559 } 560 561 /** 562 * Bit String to Integer 563 * 564 * @param string $in 565 * @return BigInteger 566 */ 567 protected function bits2int($in) 568 { 569 $v = new BigInteger($in, 256); 570 $vlen = strlen($in) << 3; 571 $qlen = $this->q->getLength(); 572 if ($vlen > $qlen) { 573 return $v->bitwise_rightShift($vlen - $qlen); 574 } 575 return $v; 576 } 577 578 /** 579 * Bit String to Octet String 580 * 581 * @param string $in 582 * @return string 583 */ 584 private function bits2octets($in) 585 { 586 $z1 = $this->bits2int($in); 587 $z2 = $z1->subtract($this->q); 588 return $z2->compare(self::$zero) < 0 ? 589 $this->int2octets($z1) : 590 $this->int2octets($z2); 591 } 592} 593