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