/** * Default Constructor. * * Connects to an SSHv1 server * * @param String $host * @param optional Integer $port * @param optional Integer $timeout * @param optional Integer $cipher * @return SSH1 * @access public */ public function __construct($host, $port = 22, $timeout = 10, $cipher = SSH1_CIPHER_3DES) { $this->protocol_flags = array(1 => 'SSH1_MSG_DISCONNECT', 2 => 'SSH1_SMSG_PUBLIC_KEY', 3 => 'SSH1_CMSG_SESSION_KEY', 4 => 'SSH1_CMSG_USER', 9 => 'SSH1_CMSG_AUTH_PASSWORD', 10 => 'SSH1_CMSG_REQUEST_PTY', 12 => 'SSH1_CMSG_EXEC_SHELL', 13 => 'SSH1_CMSG_EXEC_CMD', 14 => 'SSH1_SMSG_SUCCESS', 15 => 'SSH1_SMSG_FAILURE', 16 => 'SSH1_CMSG_STDIN_DATA', 17 => 'SSH1_SMSG_STDOUT_DATA', 18 => 'SSH1_SMSG_STDERR_DATA', 19 => 'SSH1_CMSG_EOF', 20 => 'SSH1_SMSG_EXITSTATUS', 33 => 'SSH1_CMSG_EXIT_CONFIRMATION'); $this->_define_array($this->protocol_flags); $this->fsock = @fsockopen($host, $port, $errno, $errstr, $timeout); if (!$this->fsock) { user_error(rtrim("Cannot connect to {$host}. Error {$errno}. {$errstr}"), E_USER_NOTICE); return; } $this->server_identification = $init_line = fgets($this->fsock, 255); if (defined('SSH1_LOGGING')) { $this->protocol_flags_log[] = '<-'; $this->protocol_flags_log[] = '->'; if (SSH1_LOGGING == SSH1_LOG_COMPLEX) { $this->message_log[] = $this->server_identification; $this->message_log[] = $this->identifier . "\r\n"; } } if (!preg_match('#SSH-([0-9\\.]+)-(.+)#', $init_line, $parts)) { user_error('Can only connect to SSH servers', E_USER_NOTICE); return; } if ($parts[1][0] != 1) { user_error("Cannot connect to SSH {$parts['1']} servers", E_USER_NOTICE); return; } fputs($this->fsock, $this->identifier . "\r\n"); $response = $this->_get_binary_packet(); if ($response[SSH1_RESPONSE_TYPE] != SSH1_SMSG_PUBLIC_KEY) { user_error('Expected SSH_SMSG_PUBLIC_KEY', E_USER_NOTICE); return; } $anti_spoofing_cookie = $this->_string_shift($response[SSH1_RESPONSE_DATA], 8); $this->_string_shift($response[SSH1_RESPONSE_DATA], 4); $temp = unpack('nlen', $this->_string_shift($response[SSH1_RESPONSE_DATA], 2)); $server_key_public_exponent = new BigInteger($this->_string_shift($response[SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->server_key_public_exponent = $server_key_public_exponent; $temp = unpack('nlen', $this->_string_shift($response[SSH1_RESPONSE_DATA], 2)); $server_key_public_modulus = new BigInteger($this->_string_shift($response[SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->server_key_public_modulus = $server_key_public_modulus; $this->_string_shift($response[SSH1_RESPONSE_DATA], 4); $temp = unpack('nlen', $this->_string_shift($response[SSH1_RESPONSE_DATA], 2)); $host_key_public_exponent = new BigInteger($this->_string_shift($response[SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->host_key_public_exponent = $host_key_public_exponent; $temp = unpack('nlen', $this->_string_shift($response[SSH1_RESPONSE_DATA], 2)); $host_key_public_modulus = new BigInteger($this->_string_shift($response[SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->host_key_public_modulus = $host_key_public_modulus; $this->_string_shift($response[SSH1_RESPONSE_DATA], 4); // get a list of the supported ciphers extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[SSH1_RESPONSE_DATA], 4))); foreach ($this->supported_ciphers as $mask => $name) { if (($supported_ciphers_mask & 1 << $mask) == 0) { unset($this->supported_ciphers[$mask]); } } // get a list of the supported authentications extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[SSH1_RESPONSE_DATA], 4))); foreach ($this->supported_authentications as $mask => $name) { if (($supported_authentications_mask & 1 << $mask) == 0) { unset($this->supported_authentications[$mask]); } } $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie)); $session_key = ''; for ($i = 0; $i < 32; $i++) { $session_key .= chr(Random(0, 255)); } $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0)); if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) { $double_encrypted_session_key = $this->_rsa_crypt($double_encrypted_session_key, array($server_key_public_exponent, $server_key_public_modulus)); $double_encrypted_session_key = $this->_rsa_crypt($double_encrypted_session_key, array($host_key_public_exponent, $host_key_public_modulus)); } else { $double_encrypted_session_key = $this->_rsa_crypt($double_encrypted_session_key, array($host_key_public_exponent, $host_key_public_modulus)); $double_encrypted_session_key = $this->_rsa_crypt($double_encrypted_session_key, array($server_key_public_exponent, $server_key_public_modulus)); } $cipher = isset($this->supported_ciphers[$cipher]) ? $cipher : SSH1_CIPHER_3DES; $data = pack('C2a*na*N', SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_SESSION_KEY', E_USER_NOTICE); return; } switch ($cipher) { //case SSH1_CIPHER_NONE: // $this->crypto = new Crypt_Null(); // break; case SSH1_CIPHER_DES: $this->crypto = new DES(); $this->crypto->disablePadding(); $this->crypto->enableContinuousBuffer(); $this->crypto->setKey(substr($session_key, 0, 8)); break; case SSH1_CIPHER_3DES: $this->crypto = new TripleDES(DES_MODE_3CBC); $this->crypto->disablePadding(); $this->crypto->enableContinuousBuffer(); $this->crypto->setKey(substr($session_key, 0, 24)); break; //case SSH1_CIPHER_RC4: // $this->crypto = new RC4(); // $this->crypto->enableContinuousBuffer(); // $this->crypto->setKey(substr($session_key, 0, 16)); // break; } $response = $this->_get_binary_packet(); if ($response[SSH1_RESPONSE_TYPE] != SSH1_SMSG_SUCCESS) { user_error('Expected SSH_SMSG_SUCCESS', E_USER_NOTICE); return; } $this->bitmap = SSH1_MASK_CONSTRUCTOR; }
/** * RSAVP1 * * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. * * @access private * @param BigInteger $s * @return BigInteger */ function _rsavp1($s) { if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { user_error('Signature representative out of range', E_USER_NOTICE); return false; } return $this->_exponentiate($s); }
/** * Returns the server public host key. * * Caching this the first time you connect to a server and checking the result on subsequent connections * is recommended. Returns false if the server signature is not signed correctly with the public host key. * * @return Mixed * @access public */ function getServerPublicHostKey() { $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4))); $this->_string_shift($server_public_host_key, $length); switch ($this->signature_format) { case 'ssh-dss': $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); /* The value for 'dss_signature_blob' is encoded as a string containing r, followed by s (which are 160-bit integers, without lengths or padding, unsigned, and in network byte order). */ $temp = unpack('Nlength', $this->_string_shift($signature, 4)); if ($temp['length'] != 40) { user_error('Invalid signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $r = new BigInteger($this->_string_shift($signature, 20), 256); $s = new BigInteger($this->_string_shift($signature, 20), 256); if ($r->compare($q) >= 0 || $s->compare($q) >= 0) { user_error('Invalid signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $w = $s->modInverse($q); $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16)); list(, $u1) = $u1->divide($q); $u2 = $w->multiply($r); list(, $u2) = $u2->divide($q); $g = $g->modPow($u1, $p); $y = $y->modPow($u2, $p); $v = $g->multiply($y); list(, $v) = $v->divide($p); list(, $v) = $v->divide($q); if (!$v->equals($r)) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; case 'ssh-rsa': $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $n = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $nLength = $temp['length']; /* $temp = unpack('Nlength', $this->_string_shift($signature, 4)); $signature = $this->_string_shift($signature, $temp['length']); if (!class_exists('RSA')) { require_once('Crypt/RSA.php'); } $rsa = new RSA(); $rsa->setSignatureMode(RSA_SIGNATURE_PKCS1); $rsa->loadKey(array('e' => $e, 'n' => $n), RSA_PUBLIC_FORMAT_RAW); if (!$rsa->verify($this->exchange_hash, $signature)) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } */ $temp = unpack('Nlength', $this->_string_shift($signature, 4)); $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256); // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the // following URL: // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { user_error('Invalid signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $s = $s->modPow($e, $n); $s = $s->toBytes(); $h = pack('N4H*', 0x302130, 0x906052b, 0xe03021a, 0x5000414, sha1($this->exchange_hash)); $h = chr(0x1) . str_repeat(chr(0xff), $nLength - 3 - strlen($h)) . $h; if ($s != $h) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } } return $this->server_public_host_key; }