Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
827 daniel-mar 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
 *
874 daniel-mar 27
 * @category  System
28
 * @package   SSH\Agent
827 daniel-mar 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
 *
874 daniel-mar 48
 * @package SSH\Agent
827 daniel-mar 49
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 50
 * @access  public
827 daniel-mar 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
874 daniel-mar 85
     * @access private
827 daniel-mar 86
     */
87
    private $fsock;
88
 
89
    /**
90
     * Agent forwarding status
91
     *
92
     * @var int
874 daniel-mar 93
     * @access private
827 daniel-mar 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
874 daniel-mar 103
     * @access private
827 daniel-mar 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
874 daniel-mar 113
     * @access private
827 daniel-mar 114
     */
115
    private $expected_bytes = 0;
116
 
117
    /**
118
     * The current request channel
119
     *
120
     * @var int
874 daniel-mar 121
     * @access private
827 daniel-mar 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
874 daniel-mar 131
     * @access public
827 daniel-mar 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
874 daniel-mar 162
     * @access public
827 daniel-mar 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
874 daniel-mar 215
     * @access public
827 daniel-mar 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
874 daniel-mar 229
     * @access private
827 daniel-mar 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
874 daniel-mar 250
     * @access private
827 daniel-mar 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
874 daniel-mar 265
     * @access public
827 daniel-mar 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
}