From aca4ba32860ffdd02f604480cb33bb23fd20e173 Mon Sep 17 00:00:00 2001 From: montdidier Date: Tue, 30 Dec 2014 10:44:31 +0800 Subject: [PATCH 01/19] SSH agent forwarding implementation --- phpseclib/Net/SSH2.php | 165 ++++++++++++++++--------- phpseclib/System/SSH/Agent.php | 153 +++++++++++++++++++++++ tests/Functional/Net/SSH2AgentTest.php | 22 ++++ travis/setup-secure-shell.sh | 2 + 4 files changed, 287 insertions(+), 55 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 897c78b4..9ae7d176 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -99,6 +99,10 @@ define('NET_SSH2_MASK_WINDOW_ADJUST', 0x00000020); define('NET_SSH2_CHANNEL_EXEC', 0); // PuTTy uses 0x100 define('NET_SSH2_CHANNEL_SHELL', 1); define('NET_SSH2_CHANNEL_SUBSYSTEM', 2); +define('NET_SSH2_CHANNEL_AGENT_REQUEST', 3); +define('NET_SSH2_CHANNEL_AGENT_FORWARD', 4); + + /**#@-*/ /**#@+ @@ -840,6 +844,14 @@ class Net_SSH2 */ var $windowRows = 24; + /** + * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario + * + * @var System_SSH_Agent + * @access private + */ + var $agent; + /** * Default Constructor. * @@ -2132,9 +2144,11 @@ class Net_SSH2 */ function _ssh_agent_login($username, $agent) { + $this->agent = $agent; $keys = $agent->requestIdentities(); foreach ($keys as $key) { if ($this->_privatekey_login($username, $key)) { + $this->agent->_on_login_success($this); return true; } } @@ -2234,6 +2248,7 @@ class Net_SSH2 return false; } + /** * Set Timeout * @@ -2311,6 +2326,7 @@ class Net_SSH2 if (!$this->_send_binary_packet($packet)) { return false; } + $response = $this->_get_binary_packet(); if ($response === false) { user_error('Connection closed by server'); @@ -2341,6 +2357,7 @@ class Net_SSH2 // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates. $packet = pack('CNNa*CNa*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SSH2_CHANNEL_EXEC], strlen('exec'), 'exec', 1, strlen($command), $command); + if (!$this->_send_binary_packet($packet)) { return false; } @@ -2808,6 +2825,8 @@ class Net_SSH2 } $payload = $this->_get_binary_packet(); } + default: + break; } // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in @@ -2830,23 +2849,6 @@ class Net_SSH2 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } - $payload = $this->_get_binary_packet(); - break; - case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 - $this->_string_shift($payload, 1); - extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length)); - - $this->_string_shift($payload, 4); // skip over client channel - extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); - - $packet = pack('CN3a*Na*', - NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, ''); - - if (!$this->_send_binary_packet($packet)) { - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); - } - $payload = $this->_get_binary_packet(); break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: @@ -2983,53 +2985,97 @@ class Net_SSH2 return ''; } - extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5))); + extract(unpack('Ctype', $this->_string_shift($response, 1))); - $this->window_size_server_to_client[$channel]-= strlen($response); - - // resize the window, if appropriate - if ($this->window_size_server_to_client[$channel] < 0) { - $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size); - if (!$this->_send_binary_packet($packet)) { - return false; - } - $this->window_size_server_to_client[$channel]+= $this->window_size; + if ($type == NET_SSH2_MSG_CHANNEL_OPEN) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + } else { + extract(unpack('Nchannel', $this->_string_shift($response, 4))); } - switch ($this->channel_status[$channel]) { - case NET_SSH2_MSG_CHANNEL_OPEN: - switch ($type) { - case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); - $this->server_channels[$channel] = $server_channel; - extract(unpack('Nwindow_size', $this->_string_shift($response, 4))); - $this->window_size_client_to_server[$channel] = $window_size; - $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); - $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; - return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); - //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: - default: - user_error('Unable to open channel'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + // will not be setup yet on incoming channel open request + if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) { + $this->window_size_server_to_client[$channel]-= strlen($response); + + // resize the window, if appropriate + if ($this->window_size_server_to_client[$channel] < 0) { + $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size); + if (!$this->_send_binary_packet($packet)) { + return false; } - break; - case NET_SSH2_MSG_CHANNEL_REQUEST: - switch ($type) { - case NET_SSH2_MSG_CHANNEL_SUCCESS: - return true; - case NET_SSH2_MSG_CHANNEL_FAILURE: - return false; - default: - user_error('Unable to fulfill channel request'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); - } - case NET_SSH2_MSG_CHANNEL_CLOSE: - return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); + $this->window_size_server_to_client[$channel]+= $this->window_size; + } + + switch ($this->channel_status[$channel]) { + case NET_SSH2_MSG_CHANNEL_OPEN:; + switch ($type) { + case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); + $this->server_channels[$channel] = $server_channel; + extract(unpack('Nwindow_size', $this->_string_shift($response, 4))); + $this->window_size_client_to_server[$channel] = $window_size; + $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); + $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; + return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); + //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: + default: + user_error('Unable to open channel'); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + break; + case NET_SSH2_MSG_CHANNEL_REQUEST: + switch ($type) { + case NET_SSH2_MSG_CHANNEL_SUCCESS: + return true; + case NET_SSH2_MSG_CHANNEL_FAILURE: + return false; + default: + user_error('Unable to fulfill channel request'); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + case NET_SSH2_MSG_CHANNEL_CLOSE: + return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); + } } // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA switch ($type) { + case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 + $data = $this->_string_shift($response, $length); + switch($data) { + case 'auth-agent': + case 'auth-agent@openssh.com': + if (!isset($this->agent)) { + break 2; + } + break; + default: + break 2; + } + + $new_channel = NET_SSH2_CHANNEL_AGENT_FORWARD; + + extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); + extract(unpack('Nremote_window_size', $this->_string_shift($response, 4))); + extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($response, 4))); + + $this->packet_size_client_to_server[$new_channel] = $remote_window_size; + $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; + $this->window_size_client_to_server[$new_channel] = $this->window_size; + + $packet_size = 0x4000; + + $packet = pack('CN4', + NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, $server_channel, $new_channel, $packet_size, $packet_size); + + $this->server_channels[$new_channel] = $server_channel; + $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; + + if (!$this->_send_binary_packet($packet)) { + return false; + } + break; case NET_SSH2_MSG_CHANNEL_DATA: /* if ($channel == NET_SSH2_CHANNEL_EXEC) { @@ -3042,6 +3088,15 @@ class Net_SSH2 */ extract(unpack('Nlength', $this->_string_shift($response, 4))); $data = $this->_string_shift($response, $length); + + if ($channel == NET_SSH2_CHANNEL_AGENT_FORWARD) { + $agent_response = $this->agent->_forward_data($data); + if (!is_bool($agent_response)) { + $this->_send_channel_packet($channel, $agent_response); + } + break; + } + if ($client_channel == $channel) { return $data; } diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 4c0ef733..47146045 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -1,4 +1,5 @@ request_forwarding) { + $this->request_forwarding = true; + $this->_request_forwarding($ssh); + } + } + + /** + * Request the remote server to stop forwarding authentication requests to the local SSH agent + * + * @param Net_SSH2 $ssh + * @return Boolean + * @access public + */ + function stopSSHForwarding($ssh) + { + if ($this->request_forwarding) { + $ssh->_close_channel(NET_SSH2_CHANNEL_AGENT_FORWARD); + $this->request_forwarding = false; + } + } + + /** + * The worker function to make a request to a remote server + * asking it to forward authentication requests to the local SSH + * agent + * + * @param Net_SSH2 $ssh + * @return Boolean + * @access private + */ + function _request_forwarding($ssh) + { + $ssh->window_size_server_to_client[NET_SSH2_CHANNEL_AGENT_REQUEST] = $ssh->window_size; + $packet_size = 0x4000; + + $packet = pack('CNa*N3', + NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SSH2_CHANNEL_AGENT_REQUEST, $ssh->window_size_server_to_client[NET_SSH2_CHANNEL_AGENT_REQUEST], $packet_size); + + $ssh->channel_status[NET_SSH2_CHANNEL_AGENT_REQUEST] = NET_SSH2_MSG_CHANNEL_OPEN; + + if (!$ssh->_send_binary_packet($packet)) { + return false; + } + + $response = $ssh->_get_channel_packet(NET_SSH2_CHANNEL_AGENT_REQUEST); + if ($response === false) { + return false; + } + + $packet = pack('CNNa*C', + NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[NET_SSH2_CHANNEL_AGENT_REQUEST], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1); + + $ssh->channel_status[NET_SSH2_CHANNEL_AGENT_REQUEST] = NET_SSH2_MSG_CHANNEL_REQUEST; + + if (!$ssh->_send_binary_packet($packet)) { + return false; + } + + $response = $ssh->_get_channel_packet(NET_SSH2_CHANNEL_AGENT_REQUEST); + if ($response === false) { + return false; + } + + return true; + } + + /** + * On Successful Login + * + * This method should be called upon successful SSH login if you + * wish to give the SSH Agent an opportunity to take further + * action. i.e. request agent forwarding + * + * @param Net_SSH2 $ssh + * @access private + */ + function _on_login_success($ssh) + { + if ($this->request_forwarding) { + $this->_request_forwarding($ssh); + } + } + + /** + * Forward data to SSH Agent and return data reply + * + * @param String $data + * @return data from SSH Agent + * @access private + */ + function _forward_data($data) + { + if ($this->expected_bytes > 0) { + $this->socket_buffer .= $data; + $this->expected_bytes -= strlen($data); + } else { + $agent_data_bytes = current(unpack('N', $data)); + $current_data_bytes = strlen($data); + $this->socket_buffer = $data; + if ($current_data_bytes != $agent_data_bytes + 4) { + $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes; + return false; + } + } + + if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) { + user_error('Connection closed attempting to forward data to SSH agent'); + } + + $this->socket_buffer = ''; + $this->expected_bytes = 0; + + $agent_reply_bytes = current(unpack('N', fread($this->fsock, 4))); + + $agent_reply_data = fread($this->fsock, $agent_reply_bytes); + $agent_reply_data = current(unpack('a*', $agent_reply_data)); + + return pack('Na*', $agent_reply_bytes, $agent_reply_data); + } } diff --git a/tests/Functional/Net/SSH2AgentTest.php b/tests/Functional/Net/SSH2AgentTest.php index d92bb705..1426f66c 100644 --- a/tests/Functional/Net/SSH2AgentTest.php +++ b/tests/Functional/Net/SSH2AgentTest.php @@ -27,5 +27,27 @@ class Functional_Net_SSH2AgentTest extends PhpseclibFunctionalTestCase $ssh->login($this->getEnv('SSH_USERNAME'), $agent), 'SSH2 login using Agent failed.' ); + + return array('ssh' => $ssh, 'ssh-agent' => $agent); + } + + /** + * @depends testAgentLogin + */ + public function testAgentForward($args) + { + $ssh = $args['ssh']; + $agent = $args['ssh-agent']; + + $hostname = $this->getEnv('SSH_HOSTNAME'); + $username = $this->getEnv('SSH_USERNAME'); + + $this->assertEquals($username, trim($ssh->exec('whoami'))); + + $agent->startSSHForwarding($ssh); + $this->assertEquals($username, trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); + $agent->stopSSHForwarding($ssh); + + return $args; } } diff --git a/travis/setup-secure-shell.sh b/travis/setup-secure-shell.sh index fdcaaa0d..22395aa7 100755 --- a/travis/setup-secure-shell.sh +++ b/travis/setup-secure-shell.sh @@ -28,4 +28,6 @@ ssh-add "$HOME/.ssh/id_rsa" # Allow the private key of the travis user to log in as phpseclib user sudo mkdir -p "/home/$USERNAME/.ssh/" sudo cp "$HOME/.ssh/id_rsa.pub" "/home/$USERNAME/.ssh/authorized_keys" +sudo ssh-keyscan -t rsa localhost > "/tmp/known_hosts" +sudo cp "/tmp/known_hosts" "/home/$USERNAME/.ssh/known_hosts" sudo chown "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh/" -R From 8dad92f5e5908118b1a23fe4c6aba7d324140f0b Mon Sep 17 00:00:00 2001 From: Chris Kruger Date: Tue, 30 Dec 2014 16:40:47 +0800 Subject: [PATCH 02/19] removed superfluous default case --- phpseclib/Net/SSH2.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 9ae7d176..e7e1b551 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -2825,8 +2825,6 @@ class Net_SSH2 } $payload = $this->_get_binary_packet(); } - default: - break; } // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in From dd0b3e6bd569377a65e05c2038802a2750b0571f Mon Sep 17 00:00:00 2001 From: montdidier Date: Mon, 12 Jan 2015 17:13:33 +0800 Subject: [PATCH 03/19] addresses low hanging fruit comments from terrafrost and bantu --- phpseclib/Net/SSH2.php | 7 +------ phpseclib/System/SSH/Agent.php | 2 +- tests/Functional/Net/SSH2AgentTest.php | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 9ae7d176..0a649fd9 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -101,8 +101,6 @@ define('NET_SSH2_CHANNEL_SHELL', 1); define('NET_SSH2_CHANNEL_SUBSYSTEM', 2); define('NET_SSH2_CHANNEL_AGENT_REQUEST', 3); define('NET_SSH2_CHANNEL_AGENT_FORWARD', 4); - - /**#@-*/ /**#@+ @@ -2248,7 +2246,6 @@ class Net_SSH2 return false; } - /** * Set Timeout * @@ -2825,8 +2822,6 @@ class Net_SSH2 } $payload = $this->_get_binary_packet(); } - default: - break; } // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in @@ -3007,7 +3002,7 @@ class Net_SSH2 } switch ($this->channel_status[$channel]) { - case NET_SSH2_MSG_CHANNEL_OPEN:; + case NET_SSH2_MSG_CHANNEL_OPEN: switch ($type) { case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 47146045..1b09a34d 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -437,7 +437,7 @@ class System_SSH_Agent function _forward_data($data) { if ($this->expected_bytes > 0) { - $this->socket_buffer .= $data; + $this->socket_buffer.= $data; $this->expected_bytes -= strlen($data); } else { $agent_data_bytes = current(unpack('N', $data)); diff --git a/tests/Functional/Net/SSH2AgentTest.php b/tests/Functional/Net/SSH2AgentTest.php index 1426f66c..af79b60d 100644 --- a/tests/Functional/Net/SSH2AgentTest.php +++ b/tests/Functional/Net/SSH2AgentTest.php @@ -46,7 +46,9 @@ class Functional_Net_SSH2AgentTest extends PhpseclibFunctionalTestCase $agent->startSSHForwarding($ssh); $this->assertEquals($username, trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); + $agent->stopSSHForwarding($ssh); + $this->assertEquals($username, 'failure'); return $args; } From 8571d0c6d79123b5ff22a5e13b113a010dd3799d Mon Sep 17 00:00:00 2001 From: montdidier Date: Tue, 13 Jan 2015 09:52:01 +0800 Subject: [PATCH 04/19] determining what failure to expect --- tests/Functional/Net/SSH2AgentTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Functional/Net/SSH2AgentTest.php b/tests/Functional/Net/SSH2AgentTest.php index af79b60d..e291cac2 100644 --- a/tests/Functional/Net/SSH2AgentTest.php +++ b/tests/Functional/Net/SSH2AgentTest.php @@ -48,7 +48,7 @@ class Functional_Net_SSH2AgentTest extends PhpseclibFunctionalTestCase $this->assertEquals($username, trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); $agent->stopSSHForwarding($ssh); - $this->assertEquals($username, 'failure'); + $this->assertEquals('failure?', trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); return $args; } From 25b328c440cdf1e291ec28b8823a3a615ae28e02 Mon Sep 17 00:00:00 2001 From: montdidier Date: Thu, 5 Feb 2015 13:19:57 +0800 Subject: [PATCH 05/19] removed stopSSHForwarding --- phpseclib/System/SSH/Agent.php | 15 --------------- tests/Functional/Net/SSH2AgentTest.php | 3 --- 2 files changed, 18 deletions(-) diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 1b09a34d..f07c267d 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -350,21 +350,6 @@ class System_SSH_Agent } } - /** - * Request the remote server to stop forwarding authentication requests to the local SSH agent - * - * @param Net_SSH2 $ssh - * @return Boolean - * @access public - */ - function stopSSHForwarding($ssh) - { - if ($this->request_forwarding) { - $ssh->_close_channel(NET_SSH2_CHANNEL_AGENT_FORWARD); - $this->request_forwarding = false; - } - } - /** * The worker function to make a request to a remote server * asking it to forward authentication requests to the local SSH diff --git a/tests/Functional/Net/SSH2AgentTest.php b/tests/Functional/Net/SSH2AgentTest.php index e291cac2..7ba57d5c 100644 --- a/tests/Functional/Net/SSH2AgentTest.php +++ b/tests/Functional/Net/SSH2AgentTest.php @@ -47,9 +47,6 @@ class Functional_Net_SSH2AgentTest extends PhpseclibFunctionalTestCase $agent->startSSHForwarding($ssh); $this->assertEquals($username, trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); - $agent->stopSSHForwarding($ssh); - $this->assertEquals('failure?', trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\''))); - return $args; } } From 1803bcac0bb6fc3329a9f3f43a65b8b38619e136 Mon Sep 17 00:00:00 2001 From: montdidier Date: Fri, 6 Feb 2015 11:28:23 +0800 Subject: [PATCH 06/19] moved agent forwarding channel handling to filter method and reusing existing open channels to request forwarding --- phpseclib/Net/SSH2.php | 116 ++++++++++++++++++++++----------- phpseclib/System/SSH/Agent.php | 68 +++++++++---------- 2 files changed, 111 insertions(+), 73 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 0a649fd9..6dd7dc1c 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -99,8 +99,7 @@ define('NET_SSH2_MASK_WINDOW_ADJUST', 0x00000020); define('NET_SSH2_CHANNEL_EXEC', 0); // PuTTy uses 0x100 define('NET_SSH2_CHANNEL_SHELL', 1); define('NET_SSH2_CHANNEL_SUBSYSTEM', 2); -define('NET_SSH2_CHANNEL_AGENT_REQUEST', 3); -define('NET_SSH2_CHANNEL_AGENT_FORWARD', 4); +define('NET_SSH2_CHANNEL_AGENT_FORWARD', 3); /**#@-*/ /**#@+ @@ -2146,7 +2145,6 @@ class Net_SSH2 $keys = $agent->requestIdentities(); foreach ($keys as $key) { if ($this->_privatekey_login($username, $key)) { - $this->agent->_on_login_success($this); return true; } } @@ -2491,6 +2489,24 @@ class Net_SSH2 } } + /** + * Return an available open channel + * + * @return Integer + * @access public + */ + function _get_open_channel() + { + $channel = NET_SSH2_CHANNEL_EXEC; + do { + if (array_key_exists($channel, $this->channel_status) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) { + return $channel; + } + } while ($channel++ < NET_SSH2_CHANNEL_SUBSYSTEM); + + user_error("Unable to find an open channel"); + } + /** * Returns the output of an interactive shell * @@ -2844,6 +2860,45 @@ class Net_SSH2 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 + $this->_string_shift($payload, 1); + extract(unpack('Nlength', $this->_string_shift($payload, 4))); + $data = $this->_string_shift($payload, $length); + extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); + switch($data) { + case 'auth-agent': + case 'auth-agent@openssh.com': + if (isset($this->agent)) { + $new_channel = NET_SSH2_CHANNEL_AGENT_FORWARD; + + extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4))); + extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4))); + + $this->packet_size_client_to_server[$new_channel] = $remote_window_size; + $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; + $this->window_size_client_to_server[$new_channel] = $this->window_size; + + $packet_size = 0x4000; + + $packet = pack('CN4', + NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, $server_channel, $new_channel, $packet_size, $packet_size); + + $this->server_channels[$new_channel] = $server_channel; + $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; + if (!$this->_send_binary_packet($packet)) { + return false; + } + } + default: + $packet = pack('CN3a*Na*', + NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, ''); + + if (!$this->_send_binary_packet($packet)) { + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + } $payload = $this->_get_binary_packet(); break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: @@ -3011,7 +3066,9 @@ class Net_SSH2 $this->window_size_client_to_server[$channel] = $window_size; $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; - return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); + $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); + $this->_on_channel_open(); + return $result; //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: default: user_error('Unable to open channel'); @@ -3036,41 +3093,6 @@ class Net_SSH2 // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA switch ($type) { - case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 - $data = $this->_string_shift($response, $length); - switch($data) { - case 'auth-agent': - case 'auth-agent@openssh.com': - if (!isset($this->agent)) { - break 2; - } - break; - default: - break 2; - } - - $new_channel = NET_SSH2_CHANNEL_AGENT_FORWARD; - - extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); - extract(unpack('Nremote_window_size', $this->_string_shift($response, 4))); - extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($response, 4))); - - $this->packet_size_client_to_server[$new_channel] = $remote_window_size; - $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; - $this->window_size_client_to_server[$new_channel] = $this->window_size; - - $packet_size = 0x4000; - - $packet = pack('CN4', - NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, $server_channel, $new_channel, $packet_size, $packet_size); - - $this->server_channels[$new_channel] = $server_channel; - $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; - - if (!$this->_send_binary_packet($packet)) { - return false; - } - break; case NET_SSH2_MSG_CHANNEL_DATA: /* if ($channel == NET_SSH2_CHANNEL_EXEC) { @@ -3528,6 +3550,22 @@ class Net_SSH2 return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); } + /** + * Helper function for agent->_on_channel_open() + * + * Used when channels are created to inform agent + * of said channel opening. Must be called after + * channel open confirmation received + * + * @access private + */ + function _on_channel_open() + { + if (isset($this->agent)) { + $this->agent->_on_channel_open($this); + } + } + /** * Returns all errors * diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index f07c267d..ccdfce02 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -64,6 +64,20 @@ define('SYSTEM_SSH_AGENT_FAILURE', 5); define('SYSTEM_SSH_AGENTC_SIGN_REQUEST', 13); // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4) define('SYSTEM_SSH_AGENT_SIGN_RESPONSE', 14); + + +/**@+ + * Agent forwarding status + * + * @access private + */ +// no forwarding requested and not active +define('SYSTEM_SSH_AGENT_FORWARD_NONE', 0); +// request agent forwarding when opportune +define('SYSTEM_SSH_AGENT_FORWARD_REQUEST', 1); +// forwarding has been request and is active +define('SYSTEM_SSH_AGENT_FORWARD_ACTIVE', 2); + /**#@-*/ /** @@ -227,11 +241,11 @@ class System_SSH_Agent var $fsock; /** - * Did we request agent forwarding + * Agent forwarding status * * @access private */ - var $request_forwarding; + var $forward_status = SYSTEM_SSH_AGENT_FORWARD_NONE; /** * Buffer for accumulating forwarded authentication @@ -336,7 +350,8 @@ class System_SSH_Agent } /** - * Request the remote server to forward authentication requests to the local SSH agent + * Signal that agent forwarding should + * be requested when a channel is opened * * @param Net_SSH2 $ssh * @return Boolean @@ -344,16 +359,13 @@ class System_SSH_Agent */ function startSSHForwarding($ssh) { - if (!$this->request_forwarding) { - $this->request_forwarding = true; - $this->_request_forwarding($ssh); + if ($this->forward_status == SYSTEM_SSH_AGENT_FORWARD_NONE) { + $this->forward_status = SYSTEM_SSH_AGENT_FORWARD_REQUEST; } } /** - * The worker function to make a request to a remote server - * asking it to forward authentication requests to the local SSH - * agent + * Request agent forwarding of remote server * * @param Net_SSH2 $ssh * @return Boolean @@ -361,53 +373,41 @@ class System_SSH_Agent */ function _request_forwarding($ssh) { - $ssh->window_size_server_to_client[NET_SSH2_CHANNEL_AGENT_REQUEST] = $ssh->window_size; - $packet_size = 0x4000; - - $packet = pack('CNa*N3', - NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SSH2_CHANNEL_AGENT_REQUEST, $ssh->window_size_server_to_client[NET_SSH2_CHANNEL_AGENT_REQUEST], $packet_size); - - $ssh->channel_status[NET_SSH2_CHANNEL_AGENT_REQUEST] = NET_SSH2_MSG_CHANNEL_OPEN; - - if (!$ssh->_send_binary_packet($packet)) { - return false; - } - - $response = $ssh->_get_channel_packet(NET_SSH2_CHANNEL_AGENT_REQUEST); - if ($response === false) { - return false; - } + $request_channel = $ssh->_get_open_channel(); $packet = pack('CNNa*C', - NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[NET_SSH2_CHANNEL_AGENT_REQUEST], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1); + NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[$request_channel], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1); - $ssh->channel_status[NET_SSH2_CHANNEL_AGENT_REQUEST] = NET_SSH2_MSG_CHANNEL_REQUEST; + $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; if (!$ssh->_send_binary_packet($packet)) { return false; } - $response = $ssh->_get_channel_packet(NET_SSH2_CHANNEL_AGENT_REQUEST); + $response = $ssh->_get_channel_packet($request_channel); if ($response === false) { return false; } + $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; + $this->forward_status = SYSTEM_SSH_AGENT_FORWARD_ACTIVE; + return true; } /** - * On Successful Login + * On successful channel open * - * This method should be called upon successful SSH login if you - * wish to give the SSH Agent an opportunity to take further - * action. i.e. request agent forwarding + * This method is called upon successful channel + * open to give the SSH Agent an opportunity + * to take further action. i.e. request agent forwarding * * @param Net_SSH2 $ssh * @access private */ - function _on_login_success($ssh) + function _on_channel_open($ssh) { - if ($this->request_forwarding) { + if ($this->forward_status == SYSTEM_SSH_AGENT_FORWARD_REQUEST) { $this->_request_forwarding($ssh); } } From 05f4ec133c5ef1077101c2b3de96a9076bbd57c3 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 8 Feb 2015 09:31:31 -0600 Subject: [PATCH 07/19] CHANGELOG: add 0.3.10 release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd3ca09..06004752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.10 - 2015-02-04 + +- simplify window size handling ([#538](https://github.com/phpseclib/phpseclib/pull/538)) +- slightly relax the conditions under which OpenSSL is used ([#598](https://github.com/phpseclib/phpseclib/pull/598)) +- fix issue with empty constructed context-specific tags in ASN1 ([#606](https://github.com/phpseclib/phpseclib/pull/606)) + ## 0.3.9 - 2014-11-09 - PHP 5.6 improvements ([#482](https://github.com/phpseclib/phpseclib/pull/482), [#491](https://github.com/phpseclib/phpseclib/issues/491)) From fd613e49cf130fb19c611eb5a5adec472b0d08f4 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Mon, 9 Feb 2015 07:56:10 -0600 Subject: [PATCH 08/19] CHANGELOG: add clarification --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06004752..2dd2c096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.3.10 - 2015-02-04 -- simplify window size handling ([#538](https://github.com/phpseclib/phpseclib/pull/538)) +- simplify SSH2 window size handling ([#538](https://github.com/phpseclib/phpseclib/pull/538)) - slightly relax the conditions under which OpenSSL is used ([#598](https://github.com/phpseclib/phpseclib/pull/598)) - fix issue with empty constructed context-specific tags in ASN1 ([#606](https://github.com/phpseclib/phpseclib/pull/606)) From 7e2dd901404cfdaf00b713071923821d5329d45c Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 1 Mar 2015 11:57:36 -0600 Subject: [PATCH 09/19] X509: make it so you can use File_ASN1_Element for custom X.509 extensions --- phpseclib/File/X509.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 36a6287b..75579b5d 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -1612,6 +1612,10 @@ class File_X509 if (is_array($extensions)) { $size = count($extensions); for ($i = 0; $i < $size; $i++) { + if (is_object($extensions[$i]) && strtolower(get_class($extensions[$i])) == 'file_asn1_element') { + continue; + } + $id = $extensions[$i]['extnId']; $value = &$extensions[$i]['extnValue']; From e686c095b63eadfa3ed60b1cc4c01c7e17d30b77 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 1 Mar 2015 12:05:49 -0600 Subject: [PATCH 10/19] X509: always base64-encode extensions for which _getMapping returns a bool --- phpseclib/File/X509.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 36a6287b..eaba023e 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -1589,7 +1589,7 @@ class File_X509 } } } - } elseif ($map) { + } else { $value = base64_encode($value); } } From 3e1aa8c213798c67d3616dde687d43ee5d6c7707 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 4 Mar 2015 08:03:02 -0600 Subject: [PATCH 11/19] Tests/X509: add unit test for unsupported extension encoding --- tests/Unit/File/X509/X509Test.php | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/Unit/File/X509/X509Test.php diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php new file mode 100644 index 00000000..4962976b --- /dev/null +++ b/tests/Unit/File/X509/X509Test.php @@ -0,0 +1,60 @@ + + * @copyright 2014 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +require_once 'File/X509.php'; + +class Unit_File_X509_X509Test extends PhpseclibTestCase +{ + public function testLoadUnsupportedExtension() + { + $test = '-----BEGIN CERTIFICATE----- +MIIG1jCCBL6gAwIBAgITUAAAAA0qg8bE6DhrLAAAAAAADTANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDExcuU2VjdXJlIEVudGVycHJpc2UgQ0EgMTAeFw0xNTAyMjMx +NTE1MDdaFw0xNjAyMjMxNTE1MDdaMD8xFjAUBgoJkiaJk/IsZAEZFgZzZWN1cmUx +DjAMBgNVBAMTBVVzZXJzMRUwEwYDVQQDEwxtZXRhY2xhc3NpbmcwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMdG1CzR/gTalbLN9J+2cvMGeD7wsR7S78 +HU5hdwE+kECROjRAcjFBOR57ezSDrkmhkTzo28tj0oAHjOh8N9vuXtASfZSCXugx +H+ImJ+E7PA4aXBp+0H2hohW9sXNNCFiVNmJLX66O4bxIeKtVRq/+eSNijV4OOEkC +zMyTHAUbOFP0t6KoJtM1syNoQ1+fKdfcjz5XtiEzSVcp2zf0MwNFSeZSgGQ0jh8A +Kd6YVKA8ZnrqOWZxKETT+bBNTjIT0ggjQfzcE4zW2RzrN7zWabUowoU92+DAp4s3 +sAEywX9ISSge62DEzTnZZSf9bpoScAfT8raRFA3BkoJ/s4c4CgfPAgMBAAGjggLm +MIIC4jAdBgNVHQ4EFgQULlIyJL9+ZwAI/SkVdsJMxFOVp+EwHwYDVR0jBBgwFoAU +5nEIMEUT5mMd1WepmviwgK7dIzwwggEKBgNVHR8EggEBMIH+MIH7oIH4oIH1hoG5 +bGRhcDovLy9DTj0uU2VjdXJlJTIwRW50ZXJwcmlzZSUyMENBJTIwMSxDTj1hdXRo +LENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxD +Tj1Db25maWd1cmF0aW9uLERDPXNlY3VyZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25M +aXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnSGN2h0dHA6 +Ly9jcmwuc2VjdXJlb2JzY3VyZS5jb20vP2FjdGlvbj1jcmwmY2E9ZW50ZXJwcmlz +ZTEwgccGCCsGAQUFBwEBBIG6MIG3MIG0BggrBgEFBQcwAoaBp2xkYXA6Ly8vQ049 +LlNlY3VyZSUyMEVudGVycHJpc2UlMjBDQSUyMDEsQ049QUlBLENOPVB1YmxpYyUy +MEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9 +c2VjdXJlP2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0 +aW9uQXV0aG9yaXR5MBcGCSsGAQQBgjcUAgQKHggAVQBzAGUAcjAOBgNVHQ8BAf8E +BAMCBaAwKQYDVR0lBCIwIAYKKwYBBAGCNwoDBAYIKwYBBQUHAwQGCCsGAQUFBwMC +MC4GA1UdEQQnMCWgIwYKKwYBBAGCNxQCA6AVDBNtZXRhY2xhc3NpbmdAc2VjdXJl +MEQGCSqGSIb3DQEJDwQ3MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIA +gDAHBgUrDgMCBzAKBggqhkiG9w0DBzANBgkqhkiG9w0BAQsFAAOCAgEAKNmjYh+h +cObJEM0CWgz50jOYKZ4M5iIxoAWgrYY9Pv+0O9aPjvPLzjd5bY322L8lxh5wy5my +DKmip+irzjdVdxzQfoyy+ceODmCbX9L6MfEDn0RBzdwjLe1/eOxE1na0sZztrVCc +yt5nI91NNGZJUcVqVQsIA/25FWlkvo/FTfuqTuXdQiEVM5MCKJI915anmTdugy+G +0CmBJALIxtyz5P7sZhaHZFNdpKnx82QsauErqjP9H0RXc6VXX5qt+tEDvYfSlFcc +0lv3aQnV/eIdfm7APJkQ3lmNWWQwdkVf7adXJ7KAAPHSt1yvSbVxThJR/jmIkyeQ +XW/TOP5m7JI/GrmvdlzI1AgwJ+zO8fOmCDuif99pDb1CvkzQ65RZ8p5J1ZV6hzlb +VvOhn4LDnT1jnTcEqigmx1gxM/5ifvMorXn/ItMjKPlb72vHpeF7OeKE8GHsvZAm +osHcKyJXbTIcXchmpZX1efbmCMJBqHgJ/qBTBMl9BX0+YqbTZyabRJSs9ezbTRn0 +oRYl21Q8EnvS71CemxEUkSsKJmfJKkQNCsOjc8AbX/V/X9R7LJkH3UEx6K2zQQKK +k6m17mi63YW/+iPCGOWZ2qXmY5HPEyyF2L4L4IDryFJ+8xLyw3pH9/yp5aHZDtp6 +833K6qyjgHJT+fUzSEYpiwF5rSBJIGClOCY= +-----END CERTIFICATE-----'; + + $x509 = new File_X509(); + + $spkac = $x509->loadX509($test); + + $this->assertEquals($a['tbsCertificate']['extensions'][8]['extnValue'], 'MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw=='); + } +} From cc08406a76734ece1a7f3c96eecc7869948aa8c8 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 4 Mar 2015 08:18:54 -0600 Subject: [PATCH 12/19] Tests/X509: fix unit test --- tests/Unit/File/X509/X509Test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php index 4962976b..b44f9b58 100644 --- a/tests/Unit/File/X509/X509Test.php +++ b/tests/Unit/File/X509/X509Test.php @@ -53,8 +53,8 @@ k6m17mi63YW/+iPCGOWZ2qXmY5HPEyyF2L4L4IDryFJ+8xLyw3pH9/yp5aHZDtp6 $x509 = new File_X509(); - $spkac = $x509->loadX509($test); + $cert = $x509->loadX509($test); - $this->assertEquals($a['tbsCertificate']['extensions'][8]['extnValue'], 'MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw=='); + $this->assertEquals($cert['tbsCertificate']['extensions'][8]['extnValue'], 'MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw=='); } } From 31e2308ea933bdd9890d0216e18a93682a150298 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 4 Mar 2015 23:37:33 -0600 Subject: [PATCH 13/19] Tests/X509: swap expected and actual value --- tests/Unit/File/X509/X509Test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php index b44f9b58..03d37096 100644 --- a/tests/Unit/File/X509/X509Test.php +++ b/tests/Unit/File/X509/X509Test.php @@ -55,6 +55,6 @@ k6m17mi63YW/+iPCGOWZ2qXmY5HPEyyF2L4L4IDryFJ+8xLyw3pH9/yp5aHZDtp6 $cert = $x509->loadX509($test); - $this->assertEquals($cert['tbsCertificate']['extensions'][8]['extnValue'], 'MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw=='); + $this->assertEquals('MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw==', $cert['tbsCertificate']['extensions'][8]['extnValue']); } } From dad8a9ef37ace0867296ddbbdbc32cde672e9d11 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 10 Mar 2015 01:39:13 -0500 Subject: [PATCH 14/19] Tests/X509: add unit test --- tests/Unit/File/X509/X509Test.php | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php index 03d37096..ff53001c 100644 --- a/tests/Unit/File/X509/X509Test.php +++ b/tests/Unit/File/X509/X509Test.php @@ -57,4 +57,67 @@ k6m17mi63YW/+iPCGOWZ2qXmY5HPEyyF2L4L4IDryFJ+8xLyw3pH9/yp5aHZDtp6 $this->assertEquals('MDUwDgYIKoZIhvcNAwICAgCAMA4GCCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBw==', $cert['tbsCertificate']['extensions'][8]['extnValue']); } + + public function testSaveUnsupportedExtension() + { + $x509 = new File_X509(); + $cert = $x509->loadX509('-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIQT52W2WawmStUwpV8tBV9TTANBgkqhkiG9w0BAQUFADBM +MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg +THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0xMTEwMjYwMDAwMDBaFw0x +MzA5MzAyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh +MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw +FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEA3rcmQ6aZhc04pxUJuc8PycNVjIjujI0oJyRLKl6g2Bb6YRhLz21ggNM1QDJy +wI8S2OVOj7my9tkVXlqGMaO6hqpryNlxjMzNJxMenUJdOPanrO/6YvMYgdQkRn8B +d3zGKokUmbuYOR2oGfs5AER9G5RqeC1prcB6LPrQ2iASmNMCAwEAAaOB5zCB5DAM +BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl +LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF +BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw +Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0 +ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF +AAOBgQAhrNWuyjSJWsKrUtKyNGadeqvu5nzVfsJcKLt0AMkQH0IT/GmKHiSgAgDp +ulvKGQSy068Bsn5fFNum21K5mvMSf3yinDtvmX3qUA12IxL/92ZzKbeVCq3Yi7Le +IOkKcGQRCMha8X2e7GmlpdWC1ycenlbN0nbVeSv3JUMcafC4+Q== +-----END CERTIFICATE-----'); + + $asn1 = new File_ASN1(); + + $value = $this->encodeOID('1.2.3.4'); + $ext = chr(FILE_ASN1_TYPE_OBJECT_IDENTIFIER) . $asn1->_encodeLength(strlen($value)) . $value; + $value = 'zzzzzzzzz'; + $ext.= chr(FILE_ASN1_TYPE_OCTET_STRING) . $asn1->_encodeLength(strlen($value)) . $value; + $ext = chr(FILE_ASN1_TYPE_SEQUENCE | 0x20) . $asn1->_encodeLength(strlen($ext)) . $ext; + + $cert['tbsCertificate']['extensions'][4] = new File_ASN1_Element($ext); + + $result = $x509->loadX509($x509->saveX509($cert)); + + $this->assertCount(5, $result['tbsCertificate']['extensions']); + } + + function encodeOID($oid) + { + if ($oid === false) { + user_error('Invalid OID'); + return false; + } + $value = ''; + $parts = explode('.', $oid); + $value = chr(40 * $parts[0] + $parts[1]); + for ($i = 2; $i < count($parts); $i++) { + $temp = ''; + if (!$parts[$i]) { + $temp = "\0"; + } else { + while ($parts[$i]) { + $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp; + $parts[$i] >>= 7; + } + $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); + } + $value.= $temp; + } + return $value; + } } From 23c65c383945cfc9c2293f45a7cbc6f1a68178ec Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 19 Mar 2015 07:53:19 -0500 Subject: [PATCH 15/19] SSH2: timeout improvements make it so that the timeout in the constructor behaves in the same way that timeout's set via setTimeout() do. eg. isTimeout() tells you if a timeout was thrown etc. --- phpseclib/Net/SSH2.php | 49 ++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index d6134562..54184730 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -938,7 +938,7 @@ class Net_SSH2 $this->host = $host; $this->port = $port; - $this->connectionTimeout = $timeout; + $this->timeout = $timeout; } /** @@ -955,36 +955,24 @@ class Net_SSH2 $this->bitmap |= NET_SSH2_MASK_CONSTRUCTOR; - $timeout = $this->connectionTimeout; + $this->curTimeout = $this->timeout; + $host = $this->host . ':' . $this->port; $this->last_packet = strtok(microtime(), ' ') + strtok(''); // == microtime(true) in PHP5 $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $timeout); + $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout); if (!$this->fsock) { user_error(rtrim("Cannot connect to $host. Error $errno. $errstr")); return false; } $elapsed = strtok(microtime(), ' ') + strtok('') - $start; - $timeout-= $elapsed; + $this->curTimeout-= $elapsed; - if ($timeout <= 0) { - user_error("Cannot connect to $host. Timeout error"); - return false; - } - - $read = array($this->fsock); - $write = $except = null; - - $sec = floor($timeout); - $usec = 1000000 * ($timeout - $sec); - - // on windows this returns a "Warning: Invalid CRT parameters detected" error - // the !count() is done as a workaround for - if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { - user_error("Cannot connect to $host. Banner timeout"); + if ($this->curTimeout <= 0) { + $this->is_timeout = true; return false; } @@ -1002,6 +990,29 @@ class Net_SSH2 $extra.= $temp; $temp = ''; } + + $read = array($this->fsock); + + if ($this->curTimeout) { + if ($this->curTimeout < 0) { + $this->is_timeout = true; + return false; + } + $read = array($this->fsock); + $write = $except = null; + $start = microtime(true); + $sec = floor($this->curTimeout); + $usec = 1000000 * ($this->curTimeout - $sec); + // on windows this returns a "Warning: Invalid CRT parameters detected" error + // the !count() is done as a workaround for + if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { + $this->is_timeout = true; + return false; + } + $elapsed = microtime(true) - $start; + $this->curTimeout-= $elapsed; + } + $temp.= fgets($this->fsock, 255); } From dfd57dfb897dd848dc7e434c36cd4885baabb73d Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 19 Mar 2015 22:39:43 -0500 Subject: [PATCH 16/19] SSH2: rm redundant code and make php4 compatable --- phpseclib/Net/SSH2.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 54184730..10ffd673 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -991,8 +991,6 @@ class Net_SSH2 $temp = ''; } - $read = array($this->fsock); - if ($this->curTimeout) { if ($this->curTimeout < 0) { $this->is_timeout = true; @@ -1000,7 +998,7 @@ class Net_SSH2 } $read = array($this->fsock); $write = $except = null; - $start = microtime(true); + $start = strtok(microtime(), ' ') + strtok(''); $sec = floor($this->curTimeout); $usec = 1000000 * ($this->curTimeout - $sec); // on windows this returns a "Warning: Invalid CRT parameters detected" error @@ -1009,7 +1007,7 @@ class Net_SSH2 $this->is_timeout = true; return false; } - $elapsed = microtime(true) - $start; + $elapsed = strtok(microtime(), ' ') + strtok('') - $start; $this->curTimeout-= $elapsed; } From 9723acc8853c5fe7ea9bda4a9a711a3e07575c84 Mon Sep 17 00:00:00 2001 From: montdidier Date: Tue, 24 Mar 2015 13:38:56 +0800 Subject: [PATCH 17/19] preference isset over array_key_exists, return false on failure, break after return channel opened --- phpseclib/Net/SSH2.php | 5 +++-- phpseclib/System/SSH/Agent.php | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 6dd7dc1c..4f55eb64 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -2499,12 +2499,12 @@ class Net_SSH2 { $channel = NET_SSH2_CHANNEL_EXEC; do { - if (array_key_exists($channel, $this->channel_status) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) { + if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) { return $channel; } } while ($channel++ < NET_SSH2_CHANNEL_SUBSYSTEM); - user_error("Unable to find an open channel"); + return false; } /** @@ -2891,6 +2891,7 @@ class Net_SSH2 return false; } } + break; default: $packet = pack('CN3a*Na*', NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, ''); diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index ccdfce02..86ca18ac 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -374,6 +374,10 @@ class System_SSH_Agent function _request_forwarding($ssh) { $request_channel = $ssh->_get_open_channel(); + if ($request_channel === false) { + user_error("failed to request channel"); + return false; + } $packet = pack('CNNa*C', NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[$request_channel], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1); From 3ff4212b9291f2c863a742f5692ca7312b81decb Mon Sep 17 00:00:00 2001 From: montdidier Date: Tue, 24 Mar 2015 13:40:42 +0800 Subject: [PATCH 18/19] removed unwarrented user_error --- phpseclib/System/SSH/Agent.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 86ca18ac..da20697d 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -375,7 +375,6 @@ class System_SSH_Agent { $request_channel = $ssh->_get_open_channel(); if ($request_channel === false) { - user_error("failed to request channel"); return false; } From 1294b08675743afc2533932ccce74d420827e6d4 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Fri, 27 Mar 2015 22:32:38 -0500 Subject: [PATCH 19/19] SSH2: rm unused $connectionTimeout variable --- phpseclib/Net/SSH2.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 10ffd673..ce5993d8 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -803,21 +803,6 @@ class Net_SSH2 */ var $port; - /** - * Timeout for initial connection - * - * Set by the constructor call. Calling setTimeout() is optional. If it's not called functions like - * exec() won't timeout unless some PHP setting forces it too. The timeout specified in the constructor, - * however, is non-optional. There will be a timeout, whether or not you set it. If you don't it'll be - * 10 seconds. It is used by fsockopen() and the initial stream_select in that function. - * - * @see Net_SSH2::Net_SSH2() - * @see Net_SSH2::_connect() - * @var Integer - * @access private - */ - var $connectionTimeout; - /** * Number of columns for terminal window size *