Subversion Repositories oidplus

Rev

Rev 874 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /**
  4.  * OpenSSH Formatted EC 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\EC\Formats\Keys;
  17.  
  18. use phpseclib3\Common\Functions\Strings;
  19. use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
  20. use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
  21. use phpseclib3\Crypt\EC\Curves\Ed25519;
  22. use phpseclib3\Exception\UnsupportedCurveException;
  23. use phpseclib3\Math\BigInteger;
  24.  
  25. /**
  26.  * OpenSSH Formatted EC Key Handler
  27.  *
  28.  * @author  Jim Wigginton <terrafrost@php.net>
  29.  */
  30. abstract class OpenSSH extends Progenitor
  31. {
  32.     use Common;
  33.  
  34.     /**
  35.      * Supported Key Types
  36.      *
  37.      * @var array
  38.      */
  39.     protected static $types = [
  40.         'ecdsa-sha2-nistp256',
  41.         'ecdsa-sha2-nistp384',
  42.         'ecdsa-sha2-nistp521',
  43.         'ssh-ed25519'
  44.     ];
  45.  
  46.     /**
  47.      * Break a public or private key down into its constituent components
  48.      *
  49.      * @param string $key
  50.      * @param string $password optional
  51.      * @return array
  52.      */
  53.     public static function load($key, $password = '')
  54.     {
  55.         $parsed = parent::load($key, $password);
  56.  
  57.         if (isset($parsed['paddedKey'])) {
  58.             $paddedKey = $parsed['paddedKey'];
  59.             list($type) = Strings::unpackSSH2('s', $paddedKey);
  60.             if ($type != $parsed['type']) {
  61.                 throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
  62.             }
  63.             if ($type == 'ssh-ed25519') {
  64.                 list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey);
  65.                 $key = libsodium::load($key);
  66.                 $key['comment'] = $comment;
  67.                 return $key;
  68.             }
  69.             list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey);
  70.             $curve = self::loadCurveByParam(['namedCurve' => $curveName]);
  71.             $curve->rangeCheck($privateKey);
  72.             return [
  73.                 'curve' => $curve,
  74.                 'dA' => $privateKey,
  75.                 'QA' => self::extractPoint("\0$publicKey", $curve),
  76.                 'comment' => $comment
  77.             ];
  78.         }
  79.  
  80.         if ($parsed['type'] == 'ssh-ed25519') {
  81.             if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") {
  82.                 throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
  83.             }
  84.  
  85.             $curve = new Ed25519();
  86.             $qa = self::extractPoint($parsed['publicKey'], $curve);
  87.         } else {
  88.             list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']);
  89.             $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
  90.             $curve = new $curveName();
  91.  
  92.             $qa = self::extractPoint("\0" . $publicKey, $curve);
  93.         }
  94.  
  95.         return [
  96.             'curve' => $curve,
  97.             'QA' => $qa,
  98.             'comment' => $parsed['comment']
  99.         ];
  100.     }
  101.  
  102.     /**
  103.      * Returns the alias that corresponds to a curve
  104.      *
  105.      * @return string
  106.      */
  107.     private static function getAlias(BaseCurve $curve)
  108.     {
  109.         self::initialize_static_variables();
  110.  
  111.         $reflect = new \ReflectionClass($curve);
  112.         $name = $reflect->getShortName();
  113.  
  114.         $oid = self::$curveOIDs[$name];
  115.         $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) {
  116.             return $v == $oid;
  117.         });
  118.         $aliases = array_keys($aliases);
  119.  
  120.         for ($i = 0; $i < count($aliases); $i++) {
  121.             if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) {
  122.                 $alias = $aliases[$i];
  123.                 break;
  124.             }
  125.         }
  126.  
  127.         if (!isset($alias)) {
  128.             throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
  129.         }
  130.  
  131.         return $alias;
  132.     }
  133.  
  134.     /**
  135.      * Convert an EC public key to the appropriate format
  136.      *
  137.      * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve
  138.      * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
  139.      * @param array $options optional
  140.      * @return string
  141.      */
  142.     public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
  143.     {
  144.         $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
  145.  
  146.         if ($curve instanceof Ed25519) {
  147.             $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
  148.  
  149.             if (isset($options['binary']) ? $options['binary'] : self::$binary) {
  150.                 return $key;
  151.             }
  152.  
  153.             $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment;
  154.             return $key;
  155.         }
  156.  
  157.         $alias = self::getAlias($curve);
  158.  
  159.         $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
  160.         $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
  161.  
  162.         if (isset($options['binary']) ? $options['binary'] : self::$binary) {
  163.             return $key;
  164.         }
  165.  
  166.         $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment;
  167.  
  168.         return $key;
  169.     }
  170.  
  171.     /**
  172.      * Convert a private key to the appropriate format.
  173.      *
  174.      * @param \phpseclib3\Math\BigInteger $privateKey
  175.      * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve
  176.      * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
  177.      * @param string $secret optional
  178.      * @param string $password optional
  179.      * @param array $options optional
  180.      * @return string
  181.      */
  182.     public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
  183.     {
  184.         if ($curve instanceof Ed25519) {
  185.             if (!isset($secret)) {
  186.                 throw new \RuntimeException('Private Key does not have a secret set');
  187.             }
  188.             if (strlen($secret) != 32) {
  189.                 throw new \RuntimeException('Private Key secret is not of the correct length');
  190.             }
  191.  
  192.             $pubKey = $curve->encodePoint($publicKey);
  193.  
  194.             $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
  195.             $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $secret . $pubKey);
  196.  
  197.             return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
  198.         }
  199.  
  200.         $alias = self::getAlias($curve);
  201.  
  202.         $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
  203.         $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]);
  204.  
  205.         $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey);
  206.  
  207.         return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
  208.     }
  209. }
  210.