I've spent a couple of days wrestling with this problem. I suppose it's time to ask for help.

I need to be able to send and receive files to and from a remote server (mediaTemple (gs), specifically), over SSH. I would like to minimize, if not eliminate entirely, the need to use passwords. I would prefer to authenticate wherever possible using RSA keys.

I am able to get a little bit further using passwords, but I'd like to stick to my original plan of using public key authentication a while longer before I begin making concessions.

I have the luxury of testing my code on two different "clients": a Windows box (using copSSH, which is a cygwin-based openSSH implementation) and a Linux box. The abovementioned test machines attempt to go out and connect to the remote mediaTemple (gs) server.

The Windows box is a home PC, and I am attempting this with Apache running with root permissions (to eliminate problems in testing). The Linux box is another mediaTemple box (a (dv), specifically), which is on the same LAN as the "server", and Apache is running as "nobody", most likely.

Both test machines are running only slightly disparate versions of the relevant software, and the results, thus far, have been identical. Given the test scenario described above, the problem appears not to be with the firewall, or anything else related to the network topology. I'm not convinced that my problems are platform-specific, either.

Here is the code, in its simplest form:

$con = ssh2_connect('remote.host.com', '22', array('hostkey', 'ssh-rsa'));
ssh2_auth_pubkey_file($con, $user, $pubKeyFile, $privKeyFile);

This code fails with:

Warning: ssh2_auth_pubkey_file() [function.ssh2-auth-pubkey-file]: Authentication failed for joe.user using public key in [...] on line...

On both client machines, Apache has read-access to the key files defined in $pubKeyFile and $privKeyFile. On the Linux client, the directory containing the key files is in a directory that is several levels deep with owner root:apache, and the public and private key files within the directory have owner apache:apache and 700 permissions.

In other words, Apache is able to read the key files just fine, and the 700 permissions ensure that SSH-related executables (such as scp) do not complain about unsecured permissions on the key files. I can echo the key contents to the browser, so Apache can obviously read the key contents.

I have logged into the target server from each of the test clients, using key file authentication, so it's working/enabled, and the client machines have been added to the known_hosts file on the server.

If I use a password, instead of key authentication, like this

$con = ssh2_connect('remote.host.com', '22');
ssh2_auth_password($con, 'joe.user', 'some-password');

I am able to establish a connection and issue commands to the remote server.

Must the key files be located in a specific place on the client (such as in a "real" user's home directory -- difficult, given that Apache doesn't have a home directory)? Or should PHP be able to use key files in any location on the filesystem, as long as Apache can read the files and the the permissions are otherwise set as restrictively as possible?

This person had the same problem, and as is the case with most of these SSH/PHP questions, the thread dead-ends:

ssh_auth_pubkey_file fails:
http://forums.devnetwork.net/viewtopic.php?f=30&t=87758

Thanks for any help. Once I solve the public key authentication problem, I'll describe the issues that I encounter with scp, etc. once authenticated.

    Thanks for your response, tron00!

    I rather appreciate the fact that you read my entire post and were able to encapsulate your response in a single line!

    Yes, I am able to SSH without using passwords, when I issue the same commands from within my OS's terminal.

    As these things usually go, it's "just working" now. I've made so many changes to my code over the last few days that I have no idea what resolved my issue!

    I will post back as soon as I make that determination.

    I'll leave the thread unresolved until that time.

      6 days later
      cbj4074;10933858 wrote:

      I will post back as soon as I [determine what, exactly, I did to make public key authentication work as expected] .

      Well, here's the short and long of it. It seems to work fine, both on Windows 7 with Apache 2 and on CentOS 5 Final.

      To install the SSH functions on Windows, I had to grab the PECL DLL files and enable the extension (i.e., add "extension=php_ssh2.dll" to php.ini).

      On CentOS, I had to build the module from source, but it was easy:

      Install source and compile ssh2 extension for PHP:

      rpm -Uvh libssh2-0.17-1.el5.rf.i386.rpm
      rpm -Uvh libssh2-devel-0.17-1.el5.rf.i386.rpm
      tar -zxvf ssh2-0.11.0.tgz
      cd ssh2-0.11.0
      phpize && ./configure --with-ssh2 && make
      

      Copy the module to PHP's modules directory:
      (Get PHP module directory with: cat /etc/php.ini | grep extension_dir | grep -v ";")

      cp ./ssh2-0.11.0/modules/ssh2.so /usr/lib/php/modules/
      

      Of course, Apache needs to know about the module, too (this example demonstrates the procedure on a Plesk-enabled webserver):

      vim /etc/php.d/ssh2.ini
      

      (Note that in the above example, your server configuration may require you to enable the SSH2 module in a different file!)

      The first two lines of ssh2.ini (or an appropriate section of whatever your configuration file happens to be) should contain:

      ; Enable SSH2 extension module
      extension=ssh2.so
      

      Refresh the Apache server configuration files:

      /usr/local/psa/admin/bin/websrvmng -a
      

      What good is the above, absent a "real-life" PHP example? "As always, I never stop workin'" (to quote the Diceman):

      <?php
      
      //Based largely on code from the comments at:
      //http://php.net/manual/en/function.ssh2-connect.php
      class ssh2
      {
      
      private $host = 'host';
      private $user = 'user';
      private $port = '22';
      private $password = 'password';
      private $con = null;
      private $pubKeyFile = null;
      private $privKeyFile = null;
      private $shell_type = 'xterm';
      private $shell = null;
      private $log = array();
      private $sftp = null;
      private $knownHost = null;
      
      function __construct($host = '', $port = '', $knownHost = NULL)
      {
      	if ($host != '') {
      		$this->host = $host;
      	}
      	if ($port != '') {
      		$this->port = $port;
      	}
      	if (!is_null($knownHost)) {
      		$this->knownHost = $knownHost;
      	}
      
      //See PHP manual for further explanation.
      //XXX The remaining three callbacks still need to be implemented.
      $callbacks = array(
      	'ignore' => array($this, 'handleSshDisconnect'),
      	'debug' => array($this, 'handleSshDisconnect'),
      	'macerror' => array($this, 'handleSshDisconnect'),
      	'disconnect' => array($this, 'handleSshDisconnect'),
      );
      
      $this->con = ssh2_connect($this->host, $this->port, array('hostkey', 'ssh-rsa'), $callbacks);
      
      //If a value is not returned (nor the connection made null)
      //it appears as though some kind of time-out must be reached on a failure.
      //Adding a return value or setting the value to null reduced the
      //execution time from several minutes to near-instant in my tests.
      //Is anyone able to confirm?
      if (!$this->con) {
      	$this->log[] = 'Connection failed!';
      	$this->con = NULL;
      }
      else {
      	//If a host fingerprint has been required, check it.
      	if (!is_null($this->knownHost)) {
      		$fingerprint = ssh2_fingerprint($this->con, SSH2_FINGERPRINT_MD5 | SSH2_FINGERPRINT_HEX);
      
      		if (is_string($fingerprint)) {
      			if (strcasecmp($fingerprint, $this->knownHost) != 0) {
      				$this->log[] = 'HOSTKEY MISMATCH! (Expected "' . $this->knownHost . '" and received "' . $fingerprint . '" [comparison is case-insensitive]; possible Man-In-The-Middle Attack?';
      				$this->con = NULL;
      			}
      		}
      		else {
      			$this->log[] = 'Could not determine remote host fingerprint (returned value was "' . $fingerprint . '"); aborting connection';
      			$this->con = NULL;
      		}
      	}
      }
      }
      
      function getCon()
      {
      	return $this->con;
      }
      
      function getSftp()
      {
      	return $this->sftp;
      }
      
      function authPassword($user = '', $password = '')
      {
      	if ($user != '') {
      		$this->user = $user;
      	}
      	if ($password != '') {
      		$this->password = $password;
      	}
      	if (!ssh2_auth_password($this->con, $this->user, $this->password)) {
      		$this->log[] = 'Authorization failed!';
      		return FALSE;
      	}
      	else {
      		return TRUE;
      	}
      }
      
      //XXX For additional discussion, see:
      //http://phpbuilder.com/board/showthread.php?t=10369507
      function authKeyFile($user, $pubKeyFile, $privKeyFile)
      {
      	if ($user != '') {
      		$this->user = $user;
      	}
      	if ($pubKeyFile != '') {
      		$this->pubKeyFile = $pubKeyFile;
      	}
      	if ($privKeyFile != '') {
      		$this->privKeyFile = $privKeyFile;
      	}
      
      if (!ssh2_auth_pubkey_file($this->con, $this->user, $this->pubKeyFile, $this->privKeyFile)) {
      	$this->log[] = 'Public key authorization failed (public key "' . $this->pubKeyFile . '" and private key "' . $this->privKeyFile . '")!';
      	return FALSE;
      }
      else {
      	return TRUE;
      }
      }
      
      function addPubKey()
      {
      	$res = $this->cmdExec('env');
      	var_dump($res);
      	#$pkey = ssh2_publickey_init($this->con);
      	#var_dump($pkey);
      	#die;
      }
      
      function openShell($shell_type = '')
      {
      	if ($shell_type != '') {
      		$this->shell_type = $shell_type;
      	}
      	$this->shell = ssh2_shell($this->con, $this->shell_type);
      	if (!$this->shell) {
      		$this->log[] = 'Shell connection failed!';
      		return FALSE;
      	}
      	else {
      		return TRUE;
      	}
      }
      
      function writeShell($command = '')
      {
      	fwrite($this->shell, $command . PHP_EOL);
      	return NULL;
      }
      
      function cmdExec()
      {
      	$argc = func_num_args();
      	$argv = func_get_args();
      
      $cmd = '';
      for ($i = 0; $i < $argc; $i ++) {
      	if ($i != ($argc - 1)) {
      		$cmd .= $argv[$i] . ' && ';
      	} else {
      		$cmd .= $argv[$i];
      	}
      }
      
      $stream = ssh2_exec($this->con, $cmd);
      if ($stream !== FALSE) {
      	stream_set_blocking($stream, true);
      
      	$contents = '';
      	while($line = fgets($stream)) {
      		flush();
      		$contents .= $line;
      	}
      
      	fclose($stream);
      
      	return $contents;
      }
      else {
      	$this->log[] = 'SSH command execution failed!';
      	return FALSE;
      }
      }
      
      function sftpCon()
      {
      	$sftp = ssh2_sftp($this->con);
      	$this->sftp = $sftp;
      	return NULL;
      }
      
      function getLog()
      {
      	return $this->log;
      }
      
      //XXX Does the $language parameter actually need to be specified in the call?
      //I guess we'll find out the first time it happens!
      function handleSshDisconnect($reason, $message, $language) {
      	$error = printf("Server disconnected with reason code [%d] and message: %s" . PHP_EOL, $reason, $message);
      	return $error;
      }
      
      }
      

      To implement the above class, try something like this:

      function connectToServer()
      {
      	//Establish an SSH session, so we can use file_exists()
      	//on the remote server (requires sftp wrapper).
      	$ssh = new ssh2(SOME_SERVER, '22', SOME_SERVER_FINGERPRINT);
      
      if ($ssh->getCon() !== NULL) {
      	//We were able to open an SSH session and the remote
      	//server has the fingerprint that we expect. It's safe
      	//to supply authentication credentials (as safe as possible, anyway).
      
      	if ($ssh->authKeyFile(SSH_USER, SSH_PUBKEY, SSH_PRIVKEY)) {
      		//If authenticaton was successful, establish
      		//an SFTP session so that sftp wrappers are available.
      		$ssh->sftpCon();
      
      		return $ssh;
      	}
      	else {
      		return FALSE;
      	}
      }
      else {
      	return FALSE;
      }
      }
      
      $result = connectToServer();
      

      Don't forget to define the constants, which appear in UPPERCASE letters, if you decide to use the above code. For example:

      define('NAME_OF_CONSTANT', $value);
      
        Write a Reply...