From 3ad44cb3d60c4c403e7a94c80ec04913ba99631c Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 7 Sep 2019 19:08:00 -0500 Subject: [PATCH] SSH2: backport setPreferredAlgorithms() / getAlgorithmsNegotiated() --- phpseclib/Net/SSH2.php | 769 +++++++++++++++++++++++++---------------- 1 file changed, 468 insertions(+), 301 deletions(-) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index fdc1da40..680e3c2b 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -214,6 +214,15 @@ class Net_SSH2 */ var $kex_algorithms = false; + /** + * Key Exchange Algorithm + * + * @see self::getMethodsNegotiated() + * @var string|false + * @access private + */ + var $kex_algorithm = false; + /** * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods * @@ -322,6 +331,15 @@ class Net_SSH2 */ var $languages_client_to_server = false; + /** + * Preferred Algorithms + * + * @see self::setPreferredAlgorithms() + * @var array + * @access private + */ + var $preferred = array(); + /** * Block Size for Server to Client Encryption * @@ -903,14 +921,6 @@ class Net_SSH2 */ var $bad_key_size_fix = false; - /** - * The selected decryption algorithm - * - * @var string - * @access private - */ - var $decrypt_algorithm = ''; - /** * Should we try to re-connect to re-establish keys? * @@ -1324,137 +1334,59 @@ class Net_SSH2 */ function _key_exchange($kexinit_payload_server = false) { - static $kex_algorithms = array( - 'diffie-hellman-group1-sha1', // REQUIRED - 'diffie-hellman-group14-sha1', // REQUIRED - 'diffie-hellman-group-exchange-sha1', // RFC 4419 - 'diffie-hellman-group-exchange-sha256', // RFC 4419 - ); + $preferred = $this->preferred; - static $server_host_key_algorithms = array( - 'rsa-sha2-256', // RFC 8332 - 'rsa-sha2-512', // RFC 8332 - 'ssh-rsa', // RECOMMENDED sign Raw RSA Key - 'ssh-dss' // REQUIRED sign Raw DSS Key - ); - - static $encryption_algorithms = false; - if ($encryption_algorithms === false) { - $encryption_algorithms = array( - // from : - 'arcfour256', - 'arcfour128', - - //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key - - // CTR modes from : - 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key - 'aes192-ctr', // RECOMMENDED AES with 192-bit key - 'aes256-ctr', // RECOMMENDED AES with 256-bit key - - 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key - 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key - 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key - - 'aes128-cbc', // RECOMMENDED AES with a 128-bit key - 'aes192-cbc', // OPTIONAL AES with a 192-bit key - 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key - - 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key - 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key - 'twofish256-cbc', - 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc" - // (this is being retained for historical reasons) - - 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode - - 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode - - '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode - - '3des-cbc', // REQUIRED three-key 3DES in CBC mode - //'none' // OPTIONAL no encryption; NOT RECOMMENDED - ); - - if (extension_loaded('openssl') && !extension_loaded('mcrypt')) { - // OpenSSL does not support arcfour256 in any capacity and arcfour128 / arcfour support is limited to - // instances that do not use continuous buffers - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('arcfour256', 'arcfour128', 'arcfour') - ); - } - - if (phpseclib_resolve_include_path('Crypt/RC4.php') === false) { - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('arcfour256', 'arcfour128', 'arcfour') - ); - } - if (phpseclib_resolve_include_path('Crypt/Rijndael.php') === false) { - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc') - ); - } - if (phpseclib_resolve_include_path('Crypt/Twofish.php') === false) { - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc') - ); - } - if (phpseclib_resolve_include_path('Crypt/Blowfish.php') === false) { - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('blowfish-ctr', 'blowfish-cbc') - ); - } - if (phpseclib_resolve_include_path('Crypt/TripleDES.php') === false) { - $encryption_algorithms = array_diff( - $encryption_algorithms, - array('3des-ctr', '3des-cbc') - ); - } - $encryption_algorithms = array_values($encryption_algorithms); - } - - $mac_algorithms = array( - // from : - 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) - - 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) - 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) - 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) - 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) - //'none' // OPTIONAL no MAC; NOT RECOMMENDED - ); - - static $compression_algorithms = array( - 'none' // REQUIRED no compression - //'zlib' // OPTIONAL ZLIB (LZ77) compression - ); + $kex_algorithms = isset($preferred['kex']) ? + $preferred['kex'] : + $this->getSupportedKEXAlgorithms(); + $server_host_key_algorithms = isset($preferred['hostkey']) ? + $preferred['hostkey'] : + $this->getSupportedHostKeyAlgorithms(); + $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ? + $preferred['server_to_client']['crypt'] : + $this->getSupportedEncryptionAlgorithms(); + $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ? + $preferred['client_to_server']['crypt'] : + $this->getSupportedEncryptionAlgorithms(); + $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ? + $preferred['server_to_client']['mac'] : + $this->getSupportedMACAlgorithms(); + $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ? + $preferred['client_to_server']['mac'] : + $this->getSupportedMACAlgorithms(); + $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ? + $preferred['server_to_client']['comp'] : + $this->getSupportedCompressionAlgorithms(); + $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ? + $preferred['client_to_server']['comp'] : + $this->getSupportedCompressionAlgorithms(); // some SSH servers have buggy implementations of some of the above algorithms switch (true) { case $this->server_identifier == 'SSH-2.0-SSHD': case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK': - $mac_algorithms = array_values(array_diff( - $mac_algorithms, - array('hmac-sha1-96', 'hmac-md5-96') - )); + if (!isset($preferred['server_to_client']['mac'])) { + $s2c_mac_algorithms = array_values(array_diff( + $s2c_mac_algorithms, + array('hmac-sha1-96', 'hmac-md5-96') + )); + } + if (!isset($preferred['client_to_server']['mac'])) { + $c2s_mac_algorithms = array_values(array_diff( + $c2s_mac_algorithms, + array('hmac-sha1-96', 'hmac-md5-96') + )); + } } - static $str_kex_algorithms, $str_server_host_key_algorithms, - $encryption_algorithms_server_to_client, $mac_algorithms_server_to_client, $compression_algorithms_server_to_client, - $encryption_algorithms_client_to_server, $mac_algorithms_client_to_server, $compression_algorithms_client_to_server; - - if (empty($str_kex_algorithms)) { - $str_kex_algorithms = implode(',', $kex_algorithms); - $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); - $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms); - $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms); - $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms); - } + $str_kex_algorithms = implode(',', $kex_algorithms); + $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); + $encryption_algorithms_server_to_client = implode(',', $s2c_encryption_algorithms); + $encryption_algorithms_client_to_server = implode(',', $c2s_encryption_algorithms); + $mac_algorithms_server_to_client = implode(',', $s2c_mac_algorithms); + $mac_algorithms_client_to_server = implode(',', $c2s_mac_algorithms); + $compression_algorithms_server_to_client = implode(',', $s2c_compression_algorithms); + $compression_algorithms_client_to_server = implode(',', $c2s_compression_algorithms); $client_cookie = crypt_random_string(16); @@ -1581,14 +1513,14 @@ class Net_SSH2 // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the // diffie-hellman key exchange as fast as possible - $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client); + $decrypt = $this->_array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client); $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt); if ($decryptKeyLength === null) { user_error('No compatible server to client encryption algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } - $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server); + $encrypt = $this->_array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server); $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt); if ($encryptKeyLength === null) { user_error('No compatible client to server encryption algorithms found'); @@ -1598,7 +1530,7 @@ class Net_SSH2 $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength; // through diffie-hellman key exchange a symmetric key is obtained - $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms); + $this->kex_algorithm = $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms); if ($kex_algorithm === false) { user_error('No compatible key exchange algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); @@ -1853,161 +1785,8 @@ class Net_SSH2 return false; } - switch ($encrypt) { - case '3des-cbc': - if (!class_exists('Crypt_TripleDES')) { - include_once 'Crypt/TripleDES.php'; - } - $this->encrypt = new Crypt_TripleDES(); - // $this->encrypt_block_size = 64 / 8 == the default - break; - case '3des-ctr': - if (!class_exists('Crypt_TripleDES')) { - include_once 'Crypt/TripleDES.php'; - } - $this->encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); - // $this->encrypt_block_size = 64 / 8 == the default - break; - case 'aes256-cbc': - case 'aes192-cbc': - case 'aes128-cbc': - if (!class_exists('Crypt_Rijndael')) { - include_once 'Crypt/Rijndael.php'; - } - $this->encrypt = new Crypt_Rijndael(); - $this->encrypt_block_size = 16; // eg. 128 / 8 - break; - case 'aes256-ctr': - case 'aes192-ctr': - case 'aes128-ctr': - if (!class_exists('Crypt_Rijndael')) { - include_once 'Crypt/Rijndael.php'; - } - $this->encrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR); - $this->encrypt_block_size = 16; // eg. 128 / 8 - break; - case 'blowfish-cbc': - if (!class_exists('Crypt_Blowfish')) { - include_once 'Crypt/Blowfish.php'; - } - $this->encrypt = new Crypt_Blowfish(); - $this->encrypt_block_size = 8; - break; - case 'blowfish-ctr': - if (!class_exists('Crypt_Blowfish')) { - include_once 'Crypt/Blowfish.php'; - } - $this->encrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR); - $this->encrypt_block_size = 8; - break; - case 'twofish128-cbc': - case 'twofish192-cbc': - case 'twofish256-cbc': - case 'twofish-cbc': - if (!class_exists('Crypt_Twofish')) { - include_once 'Crypt/Twofish.php'; - } - $this->encrypt = new Crypt_Twofish(); - $this->encrypt_block_size = 16; - break; - case 'twofish128-ctr': - case 'twofish192-ctr': - case 'twofish256-ctr': - if (!class_exists('Crypt_Twofish')) { - include_once 'Crypt/Twofish.php'; - } - $this->encrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR); - $this->encrypt_block_size = 16; - break; - case 'arcfour': - case 'arcfour128': - case 'arcfour256': - if (!class_exists('Crypt_RC4')) { - include_once 'Crypt/RC4.php'; - } - $this->encrypt = new Crypt_RC4(); - break; - case 'none': - //$this->encrypt = new Crypt_Null(); - } - - switch ($decrypt) { - case '3des-cbc': - if (!class_exists('Crypt_TripleDES')) { - include_once 'Crypt/TripleDES.php'; - } - $this->decrypt = new Crypt_TripleDES(); - break; - case '3des-ctr': - if (!class_exists('Crypt_TripleDES')) { - include_once 'Crypt/TripleDES.php'; - } - $this->decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); - break; - case 'aes256-cbc': - case 'aes192-cbc': - case 'aes128-cbc': - if (!class_exists('Crypt_Rijndael')) { - include_once 'Crypt/Rijndael.php'; - } - $this->decrypt = new Crypt_Rijndael(); - $this->decrypt_block_size = 16; - break; - case 'aes256-ctr': - case 'aes192-ctr': - case 'aes128-ctr': - if (!class_exists('Crypt_Rijndael')) { - include_once 'Crypt/Rijndael.php'; - } - $this->decrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR); - $this->decrypt_block_size = 16; - break; - case 'blowfish-cbc': - if (!class_exists('Crypt_Blowfish')) { - include_once 'Crypt/Blowfish.php'; - } - $this->decrypt = new Crypt_Blowfish(); - $this->decrypt_block_size = 8; - break; - case 'blowfish-ctr': - if (!class_exists('Crypt_Blowfish')) { - include_once 'Crypt/Blowfish.php'; - } - $this->decrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR); - $this->decrypt_block_size = 8; - break; - case 'twofish128-cbc': - case 'twofish192-cbc': - case 'twofish256-cbc': - case 'twofish-cbc': - if (!class_exists('Crypt_Twofish')) { - include_once 'Crypt/Twofish.php'; - } - $this->decrypt = new Crypt_Twofish(); - $this->decrypt_block_size = 16; - break; - case 'twofish128-ctr': - case 'twofish192-ctr': - case 'twofish256-ctr': - if (!class_exists('Crypt_Twofish')) { - include_once 'Crypt/Twofish.php'; - } - $this->decrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR); - $this->decrypt_block_size = 16; - break; - case 'arcfour': - case 'arcfour128': - case 'arcfour256': - if (!class_exists('Crypt_RC4')) { - include_once 'Crypt/RC4.php'; - } - $this->decrypt = new Crypt_RC4(); - break; - case 'none': - //$this->decrypt = new Crypt_Null(); - } - - $this->decrypt_algorithm = $decrypt; + $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt); + $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt); $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); @@ -2018,6 +1797,10 @@ class Net_SSH2 $this->encrypt->enableContinuousBuffer(); $this->encrypt->disablePadding(); + if ($this->encrypt->getBlockLength()) { + $this->encrypt_block_size = $this->encrypt->getBlockLength() >> 3; + } + $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); while ($this->encrypt_block_size > strlen($iv)) { $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); @@ -2029,6 +1812,8 @@ class Net_SSH2 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); + + $this->encrypt->name = $decrypt; } if ($this->decrypt) { @@ -2038,6 +1823,10 @@ class Net_SSH2 $this->decrypt->enableContinuousBuffer(); $this->decrypt->disablePadding(); + if ($this->decrypt->getBlockLength()) { + $this->decrypt_block_size = $this->decrypt->getBlockLength() >> 3; + } + $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); while ($this->decrypt_block_size > strlen($iv)) { $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); @@ -2049,6 +1838,8 @@ class Net_SSH2 $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); + + $this->decrypt->name = $decrypt; } /* The "arcfour128" algorithm is the RC4 cipher, as described in @@ -2065,7 +1856,7 @@ class Net_SSH2 $this->decrypt->decrypt(str_repeat("\0", 1536)); } - $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server); + $mac_algorithm = $this->_array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server); if ($mac_algorithm === false) { user_error('No compatible client to server message authentication algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); @@ -2093,8 +1884,9 @@ class Net_SSH2 $this->hmac_create = new Crypt_Hash('md5-96'); $createKeyLength = 16; } + $this->hmac_create->name = $mac_algorithm; - $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client); + $mac_algorithm = $this->_array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); if ($mac_algorithm === false) { user_error('No compatible server to client message authentication algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); @@ -2128,6 +1920,7 @@ class Net_SSH2 $checkKeyLength = 16; $this->hmac_size = 12; } + $this->hmac_check->name = $mac_algorithm; $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); while ($createKeyLength > strlen($key)) { @@ -2141,19 +1934,19 @@ class Net_SSH2 } $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); - $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client); - if ($compression_algorithm === false) { - user_error('No compatible server to client compression algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); - } - $this->decompress = $compression_algorithm == 'zlib'; - - $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server); + $compression_algorithm = $this->_array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server); if ($compression_algorithm === false) { user_error('No compatible client to server compression algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } - $this->compress = $compression_algorithm == 'zlib'; + //$this->decompress = $compression_algorithm == 'zlib'; + + $compression_algorithm = $this->_array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_client_to_server); + if ($compression_algorithm === false) { + user_error('No compatible server to client compression algorithms found'); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + //$this->compress = $compression_algorithm == 'zlib'; return true; } @@ -2201,6 +1994,89 @@ class Net_SSH2 return null; } + /** + * Maps an encryption algorithm name to an instance of a subclass of + * \phpseclib\Crypt\Base. + * + * @param string $algorithm Name of the encryption algorithm + * @return mixed Instance of \phpseclib\Crypt\Base or null for unknown + * @access private + */ + function _encryption_algorithm_to_crypt_instance($algorithm) + { + switch ($algorithm) { + case '3des-cbc': + if (!class_exists('Crypt_TripleDES')) { + include_once 'Crypt/TripleDES.php'; + } + return new Crypt_TripleDES(); + break; + case '3des-ctr': + if (!class_exists('Crypt_TripleDES')) { + include_once 'Crypt/TripleDES.php'; + } + return new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case 'aes256-cbc': + case 'aes192-cbc': + case 'aes128-cbc': + if (!class_exists('Crypt_Rijndael')) { + include_once 'Crypt/Rijndael.php'; + } + return new Crypt_Rijndael(); + break; + case 'aes256-ctr': + case 'aes192-ctr': + case 'aes128-ctr': + if (!class_exists('Crypt_Rijndael')) { + include_once 'Crypt/Rijndael.php'; + } + return new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR); + $this->encrypt_block_size = 16; // eg. 128 / 8 + break; + case 'blowfish-cbc': + if (!class_exists('Crypt_Blowfish')) { + include_once 'Crypt/Blowfish.php'; + } + return new Crypt_Blowfish(); + break; + case 'blowfish-ctr': + if (!class_exists('Crypt_Blowfish')) { + include_once 'Crypt/Blowfish.php'; + } + return new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR); + break; + case 'twofish128-cbc': + case 'twofish192-cbc': + case 'twofish256-cbc': + case 'twofish-cbc': + if (!class_exists('Crypt_Twofish')) { + include_once 'Crypt/Twofish.php'; + } + return new Crypt_Twofish(); + break; + case 'twofish128-ctr': + case 'twofish192-ctr': + case 'twofish256-ctr': + if (!class_exists('Crypt_Twofish')) { + include_once 'Crypt/Twofish.php'; + } + return new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR); + break; + case 'arcfour': + case 'arcfour128': + case 'arcfour256': + if (!class_exists('Crypt_RC4')) { + include_once 'Crypt/RC4.php'; + } + return new Crypt_RC4(); + break; + case 'none': + //return new Crypt_Null(); + } + return null; + } + /** * Tests whether or not proposed algorithm has a potential for issues * @@ -3477,7 +3353,7 @@ class Net_SSH2 // "implementations SHOULD check that the packet length is reasonable" // PuTTY uses 0x9000 as the actual max packet size and so to shall we if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { - if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt_algorithm) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt->name) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) { $this->bad_key_size_fix = true; $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); return false; @@ -4600,6 +4476,297 @@ class Net_SSH2 return $this->languages_client_to_server; } + /** + * Returns a list of algorithms the server supports + * + * @return array + * @access public + */ + public function getServerAlgorithms() + { + $this->_connect(); + + return array( + 'kex' => $this->kex_algorithms, + 'hostkey' => $this->server_host_key_algorithms, + 'client_to_server' => array( + 'crypt' => $this->encryption_algorithms_client_to_server, + 'mac' => $this->mac_algorithms_client_to_server, + 'comp' => $this->compression_algorithms_client_to_server, + 'lang' => $this->languages_client_to_server + ), + 'server_to_client' => array( + 'crypt' => $this->encryption_algorithms_server_to_client, + 'mac' => $this->mac_algorithms_server_to_client, + 'comp' => $this->compression_algorithms_server_to_client, + 'lang' => $this->languages_server_to_client + ) + ); + } + + /** + * Returns a list of KEX algorithms that phpseclib supports + * + * @return array + * @access public + */ + function getSupportedKEXAlgorithms() + { + $kex_algorithms = array( + 'diffie-hellman-group-exchange-sha256',// RFC 4419 + 'diffie-hellman-group-exchange-sha1', // RFC 4419 + + // Diffie-Hellman Key Agreement (DH) using integer modulo prime + // groups. + 'diffie-hellman-group14-sha1', // REQUIRED + 'diffie-hellman-group1-sha1', // REQUIRED + ); + + return $kex_algorithms; + } + + /** + * Returns a list of host key algorithms that phpseclib supports + * + * @return array + * @access public + */ + function getSupportedHostKeyAlgorithms() + { + return array( + 'rsa-sha2-256', // RFC 8332 + 'rsa-sha2-512', // RFC 8332 + 'ssh-rsa', // RECOMMENDED sign Raw RSA Key + 'ssh-dss' // REQUIRED sign Raw DSS Key + ); + } + + /** + * Returns a list of symmetric key algorithms that phpseclib supports + * + * @return array + * @access public + */ + function getSupportedEncryptionAlgorithms() + { + $algos = array( + // from : + 'arcfour256', + 'arcfour128', + + //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key + + // CTR modes from : + 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key + 'aes192-ctr', // RECOMMENDED AES with 192-bit key + 'aes256-ctr', // RECOMMENDED AES with 256-bit key + + 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key + 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key + 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key + + 'aes128-cbc', // RECOMMENDED AES with a 128-bit key + 'aes192-cbc', // OPTIONAL AES with a 192-bit key + 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key + + 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key + 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key + 'twofish256-cbc', + 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc" + // (this is being retained for historical reasons) + + 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode + + 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode + + '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode + + '3des-cbc', // REQUIRED three-key 3DES in CBC mode + + //'none' // OPTIONAL no encryption; NOT RECOMMENDED + ); + + $engines = array( + CRYPT_ENGINE_OPENSSL, + CRYPT_ENGINE_MCRYPT, + CRYPT_ENGINE_INTERNAL + ); + + $ciphers = array(); + foreach ($engines as $engine) { + foreach ($algos as $algo) { + $obj = $this->_encryption_algorithm_to_crypt_instance($algo); + if (strtolower(get_class($obj)) == 'crypt_rijndael') { + $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo)); + } + switch ($algo) { + case 'arcfour128': + case 'arcfour256': + if ($engine == CRYPT_ENGINE_INTERNAL) { + $algos = array_diff($algos, array($algo)); + $ciphers[] = $algo; + } else { + continue 2; + } + } + if ($obj->isValidEngine($engine)) { + $algos = array_diff($algos, array($algo)); + $ciphers[] = $algo; + } + } + } + + return $ciphers; + } + + /** + * Returns a list of MAC algorithms that phpseclib supports + * + * @return array + * @access public + */ + function getSupportedMACAlgorithms() + { + return array( + // from : + 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) + + 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) + 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) + 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) + 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) + //'none' // OPTIONAL no MAC; NOT RECOMMENDED + ); + } + + /** + * Returns a list of compression algorithms that phpseclib supports + * + * @return array + * @access public + */ + function getSupportedCompressionAlgorithms() + { + return array( + 'none' // REQUIRED no compression + //'zlib' // OPTIONAL ZLIB (LZ77) compression + ); + } + + /** + * Return list of negotiated algorithms + * + * Uses the same format as https://www.php.net/ssh2-methods-negotiated + * + * @return array + * @access public + */ + function getAlgorithmsNegotiated() + { + $this->_connect(); + + return array( + 'kex' => $this->kex_algorithm, + 'hostkey' => $this->signature_format, + 'client_to_server' => array( + 'crypt' => $this->encrypt->name, + 'mac' => $this->hmac_create->name, + 'comp' => 'none', + ), + 'server_to_client' => array( + 'crypt' => $this->decrypt->name, + 'mac' => $this->hmac_check->name, + 'comp' => 'none', + ) + ); + } + + /** + * Accepts an associative array with up to four parameters as described at + * + * + * @param array $methods + * @access public + */ + function setPreferredAlgorithms($methods) + { + $preferred = $methods; + + if (isset($preferred['kex'])) { + $preferred['kex'] = array_intersect( + $preferred['kex'], + $this->getSupportedKEXAlgorithms() + ); + } + + if (isset($preferred['hostkey'])) { + $preferred['hostkey'] = array_intersect( + $preferred['hostkey'], + $this->getSupportedHostKeyAlgorithms() + ); + } + + $keys = array('client_to_server', 'server_to_client'); + foreach ($keys as $key) { + if (isset($preferred[$key])) { + $a = &$preferred[$key]; + if (isset($a['crypt'])) { + $a['crypt'] = array_intersect( + $a['crypt'], + $this->getSupportedEncryptionAlgorithms() + ); + } + if (isset($a['comp'])) { + $a['comp'] = array_intersect( + $a['comp'], + $this->getSupportedCompressionAlgorithms() + ); + } + if (isset($a['mac'])) { + $a['mac'] = array_intersect( + $a['mac'], + $this->getSupportedMACAlgorithms() + ); + } + } + } + + $keys = array( + 'kex', + 'hostkey', + 'client_to_server/crypt', + 'client_to_server/comp', + 'client_to_server/mac', + 'server_to_client/crypt', + 'server_to_client/comp', + 'server_to_client/mac', + ); + foreach ($keys as $key) { + $p = $preferred; + $m = $methods; + + $subkeys = explode('/', $key); + foreach ($subkeys as $subkey) { + if (!isset($p[$subkey])) { + continue 2; + } + $p = $p[$subkey]; + $m = $m[$subkey]; + } + + if (count($p) != count($m)) { + $diff = array_diff($m, $p); + $msg = count($diff) == 1 ? + ' is not a supported algorithm' : + ' are not supported algorithms'; + user_error(implode(', ', $diff) . $msg); + return false; + } + } + + $this->preferred = $preferred; + } + /** * Returns the banner message. *