1<?php
2/**
3 * Pure-PHP ssh-agent client.
4 *
5 * PHP version 5
6 *
7 * @category  System
8 * @package   SSH\Agent
9 * @author    Jim Wigginton <terrafrost@php.net>
10 * @copyright 2009 Jim Wigginton
11 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
12 * @link      http://phpseclib.sourceforge.net
13 * @internal  See http://api.libssh.org/rfc/PROTOCOL.agent
14 */
15
16namespace phpseclib\System\SSH\Agent;
17
18use phpseclib\System\SSH\Agent;
19
20/**
21 * Pure-PHP ssh-agent client identity object
22 *
23 * Instantiation should only be performed by \phpseclib\System\SSH\Agent class.
24 * This could be thought of as implementing an interface that phpseclib\Crypt\RSA
25 * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
26 * The methods in this interface would be getPublicKey and sign since those are the
27 * methods phpseclib looks for to perform public key authentication.
28 *
29 * @package SSH\Agent
30 * @author  Jim Wigginton <terrafrost@php.net>
31 * @access  internal
32 */
33class Identity
34{
35    /**@+
36     * Signature Flags
37     *
38     * See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3
39     *
40     * @access private
41     */
42    const SSH_AGENT_RSA2_256 = 2;
43    const SSH_AGENT_RSA2_512 = 4;
44    /**#@-*/
45
46    /**
47     * Key Object
48     *
49     * @var \phpseclib\Crypt\RSA
50     * @access private
51     * @see self::getPublicKey()
52     */
53    var $key;
54
55    /**
56     * Key Blob
57     *
58     * @var string
59     * @access private
60     * @see self::sign()
61     */
62    var $key_blob;
63
64    /**
65     * Socket Resource
66     *
67     * @var resource
68     * @access private
69     * @see self::sign()
70     */
71    var $fsock;
72
73    /**
74     * Signature flags
75     *
76     * @var int
77     * @access private
78     * @see self::sign()
79     * @see self::setHash()
80     */
81    var $flags = 0;
82
83    /**
84     * Default Constructor.
85     *
86     * @param resource $fsock
87     * @return \phpseclib\System\SSH\Agent\Identity
88     * @access private
89     */
90    function __construct($fsock)
91    {
92        $this->fsock = $fsock;
93    }
94
95    /**
96     * Set Public Key
97     *
98     * Called by \phpseclib\System\SSH\Agent::requestIdentities()
99     *
100     * @param \phpseclib\Crypt\RSA $key
101     * @access private
102     */
103    function setPublicKey($key)
104    {
105        $this->key = $key;
106        $this->key->setPublicKey();
107    }
108
109    /**
110     * Set Public Key
111     *
112     * Called by \phpseclib\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key
113     * but this saves a small amount of computation.
114     *
115     * @param string $key_blob
116     * @access private
117     */
118    function setPublicKeyBlob($key_blob)
119    {
120        $this->key_blob = $key_blob;
121    }
122
123    /**
124     * Get Public Key
125     *
126     * Wrapper for $this->key->getPublicKey()
127     *
128     * @param int $format optional
129     * @return mixed
130     * @access public
131     */
132    function getPublicKey($format = null)
133    {
134        return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format);
135    }
136
137    /**
138     * Set Signature Mode
139     *
140     * Doesn't do anything as ssh-agent doesn't let you pick and choose the signature mode. ie.
141     * ssh-agent's only supported mode is \phpseclib\Crypt\RSA::SIGNATURE_PKCS1
142     *
143     * @param int $mode
144     * @access public
145     */
146    function setSignatureMode($mode)
147    {
148    }
149
150    /**
151     * Set Hash
152     *
153     * ssh-agent doesn't support using hashes for RSA other than SHA1
154     *
155     * @param string $hash
156     * @access public
157     */
158    function setHash($hash)
159    {
160        $this->flags = 0;
161        switch ($hash) {
162            case 'sha1':
163                break;
164            case 'sha256':
165                $this->flags = self::SSH_AGENT_RSA2_256;
166                break;
167            case 'sha512':
168                $this->flags = self::SSH_AGENT_RSA2_512;
169                break;
170            default:
171                user_error('The only supported hashes for RSA are sha1, sha256 and sha512');
172        }
173    }
174
175    /**
176     * Create a signature
177     *
178     * See "2.6.2 Protocol 2 private key signature request"
179     *
180     * @param string $message
181     * @return string
182     * @access public
183     */
184    function sign($message)
185    {
186        // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE
187        $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, $this->flags);
188        $packet = pack('Na*', strlen($packet), $packet);
189        if (strlen($packet) != fputs($this->fsock, $packet)) {
190            user_error('Connection closed during signing');
191            return false;
192        }
193
194        $temp = fread($this->fsock, 4);
195        if (strlen($temp) != 4) {
196            user_error('Connection closed during signing');
197            return false;
198        }
199        $length = current(unpack('N', $temp));
200        $type = ord(fread($this->fsock, 1));
201        if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) {
202            user_error('Unable to retrieve signature');
203            return false;
204        }
205
206        $signature_blob = fread($this->fsock, $length - 1);
207        if (strlen($signature_blob) != $length - 1) {
208            user_error('Connection closed during signing');
209            return false;
210        }
211        $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
212        if ($length != strlen($signature_blob)) {
213            user_error('Malformed signature blob');
214        }
215        $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
216        if ($length > strlen($signature_blob) + 4) {
217            user_error('Malformed signature blob');
218        }
219        $type = $this->_string_shift($signature_blob, $length);
220        $this->_string_shift($signature_blob, 4);
221
222        return $signature_blob;
223    }
224
225    /**
226     * String Shift
227     *
228     * Inspired by array_shift
229     *
230     * @param string $string
231     * @param int $index
232     * @return string
233     * @access private
234     */
235    function _string_shift(&$string, $index = 1)
236    {
237        $substr = substr($string, 0, $index);
238        $string = substr($string, $index);
239        return $substr;
240    }
241}
242