Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /**
  4.  * Pure-PHP ssh-agent client.
  5.  *
  6.  * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent}
  7.  *
  8.  * PHP version 5
  9.  *
  10.  * Here are some examples of how to use this library:
  11.  * <code>
  12.  * <?php
  13.  *    include 'vendor/autoload.php';
  14.  *
  15.  *    $agent = new \phpseclib3\System\SSH\Agent();
  16.  *
  17.  *    $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
  18.  *    if (!$ssh->login('username', $agent)) {
  19.  *        exit('Login Failed');
  20.  *    }
  21.  *
  22.  *    echo $ssh->exec('pwd');
  23.  *    echo $ssh->exec('ls -la');
  24.  * ?>
  25.  * </code>
  26.  *
  27.  * @category  System
  28.  * @package   SSH\Agent
  29.  * @author    Jim Wigginton <terrafrost@php.net>
  30.  * @copyright 2014 Jim Wigginton
  31.  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  32.  * @link      http://phpseclib.sourceforge.net
  33.  */
  34.  
  35. namespace phpseclib3\System\SSH;
  36.  
  37. use phpseclib3\Common\Functions\Strings;
  38. use phpseclib3\Crypt\PublicKeyLoader;
  39. use phpseclib3\Crypt\RSA;
  40. use phpseclib3\Exception\BadConfigurationException;
  41. use phpseclib3\System\SSH\Agent\Identity;
  42.  
  43. /**
  44.  * Pure-PHP ssh-agent client identity factory
  45.  *
  46.  * requestIdentities() method pumps out \phpseclib3\System\SSH\Agent\Identity objects
  47.  *
  48.  * @package SSH\Agent
  49.  * @author  Jim Wigginton <terrafrost@php.net>
  50.  * @access  public
  51.  */
  52. class Agent
  53. {
  54.     use Common\Traits\ReadBytes;
  55.  
  56.     // Message numbers
  57.  
  58.     // to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1)
  59.     const SSH_AGENTC_REQUEST_IDENTITIES = 11;
  60.     // this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2).
  61.     const SSH_AGENT_IDENTITIES_ANSWER = 12;
  62.     // the SSH1 request is SSH_AGENTC_RSA_CHALLENGE (3)
  63.     const SSH_AGENTC_SIGN_REQUEST = 13;
  64.     // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4)
  65.     const SSH_AGENT_SIGN_RESPONSE = 14;
  66.  
  67.     // Agent forwarding status
  68.  
  69.     // no forwarding requested and not active
  70.     const FORWARD_NONE = 0;
  71.     // request agent forwarding when opportune
  72.     const FORWARD_REQUEST = 1;
  73.     // forwarding has been request and is active
  74.     const FORWARD_ACTIVE = 2;
  75.  
  76.     /**
  77.      * Unused
  78.      */
  79.     const SSH_AGENT_FAILURE = 5;
  80.  
  81.     /**
  82.      * Socket Resource
  83.      *
  84.      * @var resource
  85.      * @access private
  86.      */
  87.     private $fsock;
  88.  
  89.     /**
  90.      * Agent forwarding status
  91.      *
  92.      * @var int
  93.      * @access private
  94.      */
  95.     private $forward_status = self::FORWARD_NONE;
  96.  
  97.     /**
  98.      * Buffer for accumulating forwarded authentication
  99.      * agent data arriving on SSH data channel destined
  100.      * for agent unix socket
  101.      *
  102.      * @var string
  103.      * @access private
  104.      */
  105.     private $socket_buffer = '';
  106.  
  107.     /**
  108.      * Tracking the number of bytes we are expecting
  109.      * to arrive for the agent socket on the SSH data
  110.      * channel
  111.      *
  112.      * @var int
  113.      * @access private
  114.      */
  115.     private $expected_bytes = 0;
  116.  
  117.     /**
  118.      * The current request channel
  119.      *
  120.      * @var int
  121.      * @access private
  122.      */
  123.     private $request_channel;
  124.  
  125.     /**
  126.      * Default Constructor
  127.      *
  128.      * @return \phpseclib3\System\SSH\Agent
  129.      * @throws \phpseclib3\Exception\BadConfigurationException if SSH_AUTH_SOCK cannot be found
  130.      * @throws \RuntimeException on connection errors
  131.      * @access public
  132.      */
  133.     public function __construct($address = null)
  134.     {
  135.         if (!$address) {
  136.             switch (true) {
  137.                 case isset($_SERVER['SSH_AUTH_SOCK']):
  138.                     $address = $_SERVER['SSH_AUTH_SOCK'];
  139.                     break;
  140.                 case isset($_ENV['SSH_AUTH_SOCK']):
  141.                     $address = $_ENV['SSH_AUTH_SOCK'];
  142.                     break;
  143.                 default:
  144.                     throw new BadConfigurationException('SSH_AUTH_SOCK not found');
  145.             }
  146.         }
  147.  
  148.         $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr);
  149.         if (!$this->fsock) {
  150.             throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)");
  151.         }
  152.     }
  153.  
  154.     /**
  155.      * Request Identities
  156.      *
  157.      * See "2.5.2 Requesting a list of protocol 2 keys"
  158.      * Returns an array containing zero or more \phpseclib3\System\SSH\Agent\Identity objects
  159.      *
  160.      * @return array
  161.      * @throws \RuntimeException on receipt of unexpected packets
  162.      * @access public
  163.      */
  164.     public function requestIdentities()
  165.     {
  166.         if (!$this->fsock) {
  167.             return [];
  168.         }
  169.  
  170.         $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES);
  171.         if (strlen($packet) != fputs($this->fsock, $packet)) {
  172.             throw new \RuntimeException('Connection closed while requesting identities');
  173.         }
  174.  
  175.         $length = current(unpack('N', $this->readBytes(4)));
  176.         $packet = $this->readBytes($length);
  177.  
  178.         list($type, $keyCount) = Strings::unpackSSH2('CN', $packet);
  179.         if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) {
  180.             throw new \RuntimeException('Unable to request identities');
  181.         }
  182.  
  183.         $identities = [];
  184.         for ($i = 0; $i < $keyCount; $i++) {
  185.             list($key_blob, $comment) = Strings::unpackSSH2('ss', $packet);
  186.             $temp = $key_blob;
  187.             list($key_type) = Strings::unpackSSH2('s', $temp);
  188.             switch ($key_type) {
  189.                 case 'ssh-rsa':
  190.                 case 'ssh-dss':
  191.                 case 'ssh-ed25519':
  192.                 case 'ecdsa-sha2-nistp256':
  193.                 case 'ecdsa-sha2-nistp384':
  194.                 case 'ecdsa-sha2-nistp521':
  195.                     $key = PublicKeyLoader::load($key_type . ' ' . base64_encode($key_blob));
  196.             }
  197.             // resources are passed by reference by default
  198.             if (isset($key)) {
  199.                 $identity = (new Identity($this->fsock))
  200.                     ->withPublicKey($key)
  201.                     ->withPublicKeyBlob($key_blob);
  202.                 $identities[] = $identity;
  203.                 unset($key);
  204.             }
  205.         }
  206.  
  207.         return $identities;
  208.     }
  209.  
  210.     /**
  211.      * Signal that agent forwarding should
  212.      * be requested when a channel is opened
  213.      *
  214.      * @return void
  215.      * @access public
  216.      */
  217.     public function startSSHForwarding()
  218.     {
  219.         if ($this->forward_status == self::FORWARD_NONE) {
  220.             $this->forward_status = self::FORWARD_REQUEST;
  221.         }
  222.     }
  223.  
  224.     /**
  225.      * Request agent forwarding of remote server
  226.      *
  227.      * @param \phpseclib3\Net\SSH2 $ssh
  228.      * @return bool
  229.      * @access private
  230.      */
  231.     private function request_forwarding($ssh)
  232.     {
  233.         if (!$ssh->requestAgentForwarding()) {
  234.             return false;
  235.         }
  236.  
  237.         $this->forward_status = self::FORWARD_ACTIVE;
  238.  
  239.         return true;
  240.     }
  241.  
  242.     /**
  243.      * On successful channel open
  244.      *
  245.      * This method is called upon successful channel
  246.      * open to give the SSH Agent an opportunity
  247.      * to take further action. i.e. request agent forwarding
  248.      *
  249.      * @param \phpseclib3\Net\SSH2 $ssh
  250.      * @access private
  251.      */
  252.     public function registerChannelOpen($ssh)
  253.     {
  254.         if ($this->forward_status == self::FORWARD_REQUEST) {
  255.             $this->request_forwarding($ssh);
  256.         }
  257.     }
  258.  
  259.     /**
  260.      * Forward data to SSH Agent and return data reply
  261.      *
  262.      * @param string $data
  263.      * @return string Data from SSH Agent
  264.      * @throws \RuntimeException on connection errors
  265.      * @access public
  266.      */
  267.     public function forwardData($data)
  268.     {
  269.         if ($this->expected_bytes > 0) {
  270.             $this->socket_buffer .= $data;
  271.             $this->expected_bytes -= strlen($data);
  272.         } else {
  273.             $agent_data_bytes = current(unpack('N', $data));
  274.             $current_data_bytes = strlen($data);
  275.             $this->socket_buffer = $data;
  276.             if ($current_data_bytes != $agent_data_bytes + 4) {
  277.                 $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes;
  278.                 return false;
  279.             }
  280.         }
  281.  
  282.         if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
  283.             throw new \RuntimeException('Connection closed attempting to forward data to SSH agent');
  284.         }
  285.  
  286.         $this->socket_buffer = '';
  287.         $this->expected_bytes = 0;
  288.  
  289.         $agent_reply_bytes = current(unpack('N', $this->readBytes(4)));
  290.  
  291.         $agent_reply_data = $this->readBytes($agent_reply_bytes);
  292.         $agent_reply_data = current(unpack('a*', $agent_reply_data));
  293.  
  294.         return pack('Na*', $agent_reply_bytes, $agent_reply_data);
  295.     }
  296. }
  297.