[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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

   1  <?php
   2  
   3  /**
   4   * OpenSSH Key Handler
   5   *
   6   * PHP version 5
   7   *
   8   * Place in $HOME/.ssh/authorized_keys
   9   *
  10   * @author    Jim Wigginton <terrafrost@php.net>
  11   * @copyright 2015 Jim Wigginton
  12   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  13   * @link      http://phpseclib.sourceforge.net
  14   */
  15  
  16  namespace phpseclib3\Crypt\Common\Formats\Keys;
  17  
  18  use phpseclib3\Common\Functions\Strings;
  19  use phpseclib3\Crypt\AES;
  20  use phpseclib3\Crypt\Random;
  21  
  22  /**
  23   * OpenSSH Formatted RSA Key Handler
  24   *
  25   * @author  Jim Wigginton <terrafrost@php.net>
  26   */
  27  abstract class OpenSSH
  28  {
  29      /**
  30       * Default comment
  31       *
  32       * @var string
  33       */
  34      protected static $comment = 'phpseclib-generated-key';
  35  
  36      /**
  37       * Binary key flag
  38       *
  39       * @var bool
  40       */
  41      protected static $binary = false;
  42  
  43      /**
  44       * Sets the default comment
  45       *
  46       * @param string $comment
  47       */
  48      public static function setComment($comment)
  49      {
  50          self::$comment = str_replace(["\r", "\n"], '', $comment);
  51      }
  52  
  53      /**
  54       * Break a public or private key down into its constituent components
  55       *
  56       * $type can be either ssh-dss or ssh-rsa
  57       *
  58       * @param string $key
  59       * @param string $password
  60       * @return array
  61       */
  62      public static function load($key, $password = '')
  63      {
  64          if (!Strings::is_stringable($key)) {
  65              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
  66          }
  67  
  68          // key format is described here:
  69          // https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
  70  
  71          if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
  72              $key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
  73              $key = Strings::base64_decode($key);
  74              $magic = Strings::shift($key, 15);
  75              if ($magic != "openssh-key-v1\0") {
  76                  throw new \RuntimeException('Expected openssh-key-v1');
  77              }
  78              list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
  79              if ($numKeys != 1) {
  80                  // if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
  81                  // would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
  82                  // that to the appropriate key loading parser $numKey times or something
  83                  throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
  84              }
  85              switch ($ciphername) {
  86                  case 'none':
  87                      break;
  88                  case 'aes256-ctr':
  89                      if ($kdfname != 'bcrypt') {
  90                          throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)');
  91                      }
  92                      list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
  93                      $crypto = new AES('ctr');
  94                      //$crypto->setKeyLength(256);
  95                      //$crypto->disablePadding();
  96                      $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
  97                      break;
  98                  default:
  99                      throw new \RuntimeException('The only supported cipherse are: none, aes256-ctr (' . $ciphername . ' is being used)');
 100              }
 101  
 102              list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
 103              list($type) = Strings::unpackSSH2('s', $publicKey);
 104              if (isset($crypto)) {
 105                  $paddedKey = $crypto->decrypt($paddedKey);
 106              }
 107              list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey);
 108              // any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
 109              if ($checkint1 != $checkint2) {
 110                  throw new \RuntimeException('The two checkints do not match');
 111              }
 112              self::checkType($type);
 113  
 114              return compact('type', 'publicKey', 'paddedKey');
 115          }
 116  
 117          $parts = explode(' ', $key, 3);
 118  
 119          if (!isset($parts[1])) {
 120              $key = base64_decode($parts[0]);
 121              $comment = false;
 122          } else {
 123              $asciiType = $parts[0];
 124              self::checkType($parts[0]);
 125              $key = base64_decode($parts[1]);
 126              $comment = isset($parts[2]) ? $parts[2] : false;
 127          }
 128          if ($key === false) {
 129              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
 130          }
 131  
 132          list($type) = Strings::unpackSSH2('s', $key);
 133          self::checkType($type);
 134          if (isset($asciiType) && $asciiType != $type) {
 135              throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
 136          }
 137          if (strlen($key) <= 4) {
 138              throw new \UnexpectedValueException('Key appears to be malformed');
 139          }
 140  
 141          $publicKey = $key;
 142  
 143          return compact('type', 'publicKey', 'comment');
 144      }
 145  
 146      /**
 147       * Toggle between binary and printable keys
 148       *
 149       * Printable keys are what are generated by default. These are the ones that go in
 150       * $HOME/.ssh/authorized_key.
 151       *
 152       * @param bool $enabled
 153       */
 154      public static function setBinaryOutput($enabled)
 155      {
 156          self::$binary = $enabled;
 157      }
 158  
 159      /**
 160       * Checks to see if the type is valid
 161       *
 162       * @param string $candidate
 163       */
 164      private static function checkType($candidate)
 165      {
 166          if (!in_array($candidate, static::$types)) {
 167              throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types));
 168          }
 169      }
 170  
 171      /**
 172       * Wrap a private key appropriately
 173       *
 174       * @param string $publicKey
 175       * @param string $privateKey
 176       * @param string $password
 177       * @param array $options
 178       * @return string
 179       */
 180      protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options)
 181      {
 182          list(, $checkint) = unpack('N', Random::string(4));
 183  
 184          $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
 185          $paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
 186                       $privateKey .
 187                       Strings::packSSH2('s', $comment);
 188  
 189          $usesEncryption = !empty($password) && is_string($password);
 190  
 191          /*
 192             from http://tools.ietf.org/html/rfc4253#section-6 :
 193  
 194             Note that the length of the concatenation of 'packet_length',
 195             'padding_length', 'payload', and 'random padding' MUST be a multiple
 196             of the cipher block size or 8, whichever is larger.
 197           */
 198          $blockSize = $usesEncryption ? 16 : 8;
 199          $paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize;
 200          for ($i = 1; $i <= $paddingLength; $i++) {
 201              $paddedKey .= chr($i);
 202          }
 203          if (!$usesEncryption) {
 204              $key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
 205          } else {
 206              $rounds = isset($options['rounds']) ? $options['rounds'] : 16;
 207              $salt = Random::string(16);
 208              $kdfoptions = Strings::packSSH2('sN', $salt, $rounds);
 209              $crypto = new AES('ctr');
 210              $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
 211              $paddedKey = $crypto->encrypt($paddedKey);
 212              $key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey);
 213          }
 214          $key = "openssh-key-v1\0$key";
 215  
 216          return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
 217                 chunk_split(Strings::base64_encode($key), 70, "\n") .
 218                 "-----END OPENSSH PRIVATE KEY-----\n";
 219      }
 220  }