[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/ -> PuTTY.php (source)

   1  <?php
   2  
   3  /**
   4   * PuTTY Formatted Key Handler
   5   *
   6   * See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
   7   *
   8   * PHP version 5
   9   *
  10   * @category  Crypt
  11   * @package   Common
  12   * @author    Jim Wigginton <[email protected]>
  13   * @copyright 2016 Jim Wigginton
  14   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  15   * @link      http://phpseclib.sourceforge.net
  16   */
  17  
  18  namespace phpseclib3\Crypt\Common\Formats\Keys;
  19  
  20  use ParagonIE\ConstantTime\Base64;
  21  use ParagonIE\ConstantTime\Hex;
  22  use phpseclib3\Common\Functions\Strings;
  23  use phpseclib3\Crypt\AES;
  24  use phpseclib3\Crypt\Hash;
  25  use phpseclib3\Crypt\Random;
  26  use phpseclib3\Exception\UnsupportedAlgorithmException;
  27  
  28  /**
  29   * PuTTY Formatted Key Handler
  30   *
  31   * @package Common
  32   * @author  Jim Wigginton <[email protected]>
  33   * @access  public
  34   */
  35  abstract class PuTTY
  36  {
  37      /**
  38       * Default comment
  39       *
  40       * @var string
  41       * @access private
  42       */
  43      private static $comment = 'phpseclib-generated-key';
  44  
  45      /**
  46       * Default version
  47       *
  48       * @var int
  49       * @access private
  50       */
  51      private static $version = 2;
  52  
  53      /**
  54       * Sets the default comment
  55       *
  56       * @access public
  57       * @param string $comment
  58       */
  59      public static function setComment($comment)
  60      {
  61          self::$comment = str_replace(["\r", "\n"], '', $comment);
  62      }
  63  
  64      /**
  65       * Sets the default version
  66       *
  67       * @access public
  68       * @param int $version
  69       */
  70      public static function setVersion($version)
  71      {
  72          if ($version != 2 && $version != 3) {
  73              throw new \RuntimeException('Only supported versions are 2 and 3');
  74          }
  75          self::$version = $version;
  76      }
  77  
  78      /**
  79       * Generate a symmetric key for PuTTY v2 keys
  80       *
  81       * @access private
  82       * @param string $password
  83       * @param int $length
  84       * @return string
  85       */
  86      private static function generateV2Key($password, $length)
  87      {
  88          $symkey = '';
  89          $sequence = 0;
  90          while (strlen($symkey) < $length) {
  91              $temp = pack('Na*', $sequence++, $password);
  92              $symkey .= Hex::decode(sha1($temp));
  93          }
  94          return substr($symkey, 0, $length);
  95      }
  96  
  97      /**
  98       * Generate a symmetric key for PuTTY v3 keys
  99       *
 100       * @access private
 101       * @param string $password
 102       * @param string $flavour
 103       * @param int $memory
 104       * @param int $passes
 105       * @param string $salt
 106       * @return array
 107       */
 108      private static function generateV3Key($password, $flavour, $memory, $passes, $salt)
 109      {
 110          if (!function_exists('sodium_crypto_pwhash')) {
 111              throw new \RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
 112          }
 113  
 114          switch ($flavour) {
 115              case 'Argon2i':
 116                  $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
 117                  break;
 118              case 'Argon2id':
 119                  $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
 120                  break;
 121              default:
 122                  throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
 123          }
 124  
 125          $length = 80; // keylen + ivlen + mac_keylen
 126          $temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
 127  
 128          $symkey = substr($temp, 0, 32);
 129          $symiv = substr($temp, 32, 16);
 130          $hashkey = substr($temp, -32);
 131  
 132          return compact('symkey', 'symiv', 'hashkey');
 133      }
 134  
 135      /**
 136       * Break a public or private key down into its constituent components
 137       *
 138       * @access public
 139       * @param string $key
 140       * @param string $password
 141       * @return array
 142       */
 143      public static function load($key, $password)
 144      {
 145          if (!Strings::is_stringable($key)) {
 146              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
 147          }
 148  
 149          if (strpos($key, 'BEGIN SSH2 PUBLIC KEY') !== false) {
 150              $lines = preg_split('#[\r\n]+#', $key);
 151              switch (true) {
 152                  case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
 153                      throw new \UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
 154                  case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
 155                      throw new \UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
 156              }
 157              $lines = array_splice($lines, 1, -1);
 158              $lines = array_map(function ($line) {
 159                  return rtrim($line, "\r\n");
 160              }, $lines);
 161              $data = $current = '';
 162              $values = [];
 163              $in_value = false;
 164              foreach ($lines as $line) {
 165                  switch (true) {
 166                      case preg_match('#^(.*?): (.*)#', $line, $match):
 167                          $in_value = $line[strlen($line) - 1] == '\\';
 168                          $current = strtolower($match[1]);
 169                          $values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
 170                          break;
 171                      case $in_value:
 172                          $in_value = $line[strlen($line) - 1] == '\\';
 173                          $values[$current] .= $in_value ? substr($line, 0, -1) : $line;
 174                          break;
 175                      default:
 176                          $data .= $line;
 177                  }
 178              }
 179  
 180              $components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
 181              if ($components === false) {
 182                  throw new \UnexpectedValueException('Unable to decode public key');
 183              }
 184              $components += $values;
 185              $components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
 186  
 187              return $components;
 188          }
 189  
 190          $components = [];
 191  
 192          $key = preg_split('#\r\n|\r|\n#', trim($key));
 193          if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
 194              return false;
 195          }
 196          $version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
 197          if ($version != 2 && $version != 3) {
 198              throw new \RuntimeException('Only v2 and v3 PuTTY private keys are supported');
 199          }
 200          $components['type'] = $type = rtrim($key[0]);
 201          if (!in_array($type, static::$types)) {
 202              $error = count(static::$types) == 1 ?
 203                  'Only ' . static::$types[0] . ' keys are supported. ' :
 204                  '';
 205              throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
 206          }
 207          $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
 208          $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
 209  
 210          $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
 211          $public = Base64::decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
 212  
 213          $source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
 214  
 215          extract(unpack('Nlength', Strings::shift($public, 4)));
 216          $newtype = Strings::shift($public, $length);
 217          if ($newtype != $type) {
 218              throw new \RuntimeException('The binary type does not match the human readable type field');
 219          }
 220  
 221          $components['public'] = $public;
 222  
 223          switch ($version) {
 224              case 3:
 225                  $hashkey = '';
 226                  break;
 227              case 2:
 228                  $hashkey = 'putty-private-key-file-mac-key';
 229          }
 230  
 231          $offset = $publicLength + 4;
 232          switch ($encryption) {
 233              case 'aes256-cbc':
 234                  $crypto = new AES('cbc');
 235                  switch ($version) {
 236                      case 3:
 237                          $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
 238                          $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
 239                          $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
 240                          $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
 241                          $salt = Hex::decode(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
 242  
 243                          extract(self::generateV3Key($password, $flavour, $memory, $passes, $salt));
 244  
 245                          break;
 246                      case 2:
 247                          $symkey = self::generateV2Key($password, 32);
 248                          $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
 249                          $hashkey .= $password;
 250                  }
 251          }
 252  
 253          switch ($version) {
 254              case 3:
 255                  $hash = new Hash('sha256');
 256                  $hash->setKey($hashkey);
 257                  break;
 258              case 2:
 259                  $hash = new Hash('sha1');
 260                  $hash->setKey(sha1($hashkey, true));
 261          }
 262  
 263          $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
 264          $private = Base64::decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
 265  
 266          if ($encryption != 'none') {
 267              $crypto->setKey($symkey);
 268              $crypto->setIV($symiv);
 269              $crypto->disablePadding();
 270              $private = $crypto->decrypt($private);
 271          }
 272  
 273          $source .= Strings::packSSH2('s', $private);
 274  
 275          $hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
 276          $hmac = Hex::decode($hmac);
 277  
 278          if (!hash_equals($hash->hash($source), $hmac)) {
 279              throw new \UnexpectedValueException('MAC validation error');
 280          }
 281  
 282          $components['private'] = $private;
 283  
 284          return $components;
 285      }
 286  
 287      /**
 288       * Wrap a private key appropriately
 289       *
 290       * @access private
 291       * @param string $public
 292       * @param string $private
 293       * @param string $type
 294       * @param string $password
 295       * @param array $options optional
 296       * @return string
 297       */
 298      protected static function wrapPrivateKey($public, $private, $type, $password, array $options = [])
 299      {
 300          $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
 301          $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
 302          $version = isset($options['version']) ? $options['version'] : self::$version;
 303  
 304          $key = "PuTTY-User-Key-File-$version: $type\r\n";
 305          $key .= "Encryption: $encryption\r\n";
 306          $key .= "Comment: $comment\r\n";
 307  
 308          $public = Strings::packSSH2('s', $type) . $public;
 309  
 310          $source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
 311  
 312          $public = Base64::encode($public);
 313          $key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
 314          $key .= chunk_split($public, 64);
 315  
 316          if (empty($password) && !is_string($password)) {
 317              $source .= Strings::packSSH2('s', $private);
 318              switch ($version) {
 319                  case 3:
 320                      $hash = new Hash('sha256');
 321                      $hash->setKey('');
 322                      break;
 323                  case 2:
 324                      $hash = new Hash('sha1');
 325                      $hash->setKey(sha1('putty-private-key-file-mac-key', true));
 326              }
 327          } else {
 328              $private .= Random::string(16 - (strlen($private) & 15));
 329              $source .= Strings::packSSH2('s', $private);
 330              $crypto = new AES('cbc');
 331  
 332              switch ($version) {
 333                  case 3:
 334                      $salt = Random::string(16);
 335                      $key .= "Key-Derivation: Argon2id\r\n";
 336                      $key .= "Argon2-Memory: 8192\r\n";
 337                      $key .= "Argon2-Passes: 13\r\n";
 338                      $key .= "Argon2-Parallelism: 1\r\n";
 339                      $key .= "Argon2-Salt: " . Hex::encode($salt) . "\r\n";
 340                      extract(self::generateV3Key($password, 'Argon2id', 8192, 13, $salt));
 341  
 342                      $hash = new Hash('sha256');
 343                      $hash->setKey($hashkey);
 344  
 345                      break;
 346                  case 2:
 347                      $symkey = self::generateV2Key($password, 32);
 348                      $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
 349                      $hashkey = 'putty-private-key-file-mac-key' . $password;
 350  
 351                      $hash = new Hash('sha1');
 352                      $hash->setKey(sha1($hashkey, true));
 353              }
 354  
 355              $crypto->setKey($symkey);
 356              $crypto->setIV($symiv);
 357              $crypto->disablePadding();
 358              $private = $crypto->encrypt($private);
 359              $mac = $hash->hash($source);
 360          }
 361  
 362          $private = Base64::encode($private);
 363          $key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
 364          $key .= chunk_split($private, 64);
 365          $key .= 'Private-MAC: ' . Hex::encode($hash->hash($source)) . "\r\n";
 366  
 367          return $key;
 368      }
 369  
 370      /**
 371       * Wrap a public key appropriately
 372       *
 373       * This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
 374       *
 375       * @access private
 376       * @param string $key
 377       * @param string $type
 378       * @return string
 379       */
 380      protected static function wrapPublicKey($key, $type)
 381      {
 382          $key = pack('Na*a*', strlen($type), $type, $key);
 383          $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
 384                 'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
 385                 chunk_split(Base64::encode($key), 64) .
 386                 '---- END SSH2 PUBLIC KEY ----';
 387          return $key;
 388      }
 389  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer