Why does PHP's ssh2_auth_pubkey_file require a public key? - php

I can log into a server using only a private key via command line. Why does this PHP function require a public key also?
$connection = ssh2_connect($server_address, $port, array('hostkey'=>'ssh-rsa'));
if (!#ssh2_auth_pubkey_file($connection, $username, $public_key_path, $private_key_path, $password))
{
echo '<h3 class="error">Unable to authenticate. Check ssh key pair.</h3>';
break;
}
echo '<h3 class="success">Authenticated.</h3>';
I am working on a personal use test script to check firewall settings and access permissions as I adjust and deploy new servers. I'm mostly just curious as this seems to indicate I am missing some information about how ssh works. But I'm also annoyed that I have to give two paths when it seems I should only need one.

I do not have a direct experience with PHP SSH2 functions. But PHP ssh2_auth_pubkey_file internally calls libssh2_userauth_publickey_fromfile_ex from libssh2, whose documentation says about the publickey parameter:
Path name of the public key file.
(e.g. /etc/ssh/hostkey.pub). If libssh2 is built against OpenSSL, this option
can be set to NULL.
So maybe you can pass null in PHP (as PHP builds against OpenSSL). If not, it's only a limitation of PHP SSH2 API. Not a something that comes intrinsically from SSH as such.
For the reason why SSH APIes usually allow specifying a separate public key file, when key-pair file (usually not-really-correctly called private key file) is enough, see my answer to:
Purpose of pubkey parameter of JSch.addIdentity
I believe that given the PHP SSH API, the argument is rather useless, as the API does not even allow you to have multiple keys loaded and you have to specify the passphrase upfront anyway.

When you "log on to a web site," you always present the public key.
The corresponding private key should be very-securely kept on the server so that it can use it to validate the public keys that are presented. Private keys should never be "out in the wild."
Alternatives exist – often, the server only contains a "signing key" (for self-issued certificates), or it simply relies on the fact that the presented key has been signed by a recognized authority.

Related

How to tell if phpseclib sftp response is a challenge with a password request when key doesn't match

Recently someone inadvertently changed the keyfile used for my ssh/sftp to a remote server. I deduced this when I tried to ssh to the server from the command line and I got challenged with a password request, which indicated that the key was no longer recognised.
How would I make my php program detect an unexpected password challenge? Currently I have this:
$sftp = new SFTP(self::DOMAIN_NAME);
$Key = new RSA();
$private_rsa_key = file_get_contents('/home/ddfs/.ssh/' . self::KEY_FILE);
$Key->loadKey($private_rsa_key);
$rc = $sftp->login(self::USER, $Key);
$errors = $sftp->getSFTPErrors();
At the moment I see $rc is set to FALSE and $errors is an empty array.
SSH initiated password change requests
SSH has a mechanism built into it for password resets. My reading of RFC4252 § 8 implies that SSH_MSG_USERAUTH_PASSWD_CHANGEREQ packets should only be sent in response to a "password" SSH_MSG_USERAUTH_REQUEST but who knows how the OpenSSH devs interpreted that section of the RFC.
Since you're doing public key authentication phpseclib would be sending a "publickey" SSH_MSG_USERAUTH_REQUEST so it seems like SSH_MSG_USERAUTH_PASSWD_CHANGEREQ wouldn't be a valid response, but again, who knows.
If the server did respond with a SSH_MSG_USERAUTH_PASSWD_CHANGEREQ packet than you could do $sftp->getErrors() (instead of getSFTPErrors) and look for one that starts with SSH_MSG_USERAUTH_PASSWD_CHANGEREQ:. Maybe even do $sftp->getLastError().
getSFTPErrors returns errors with the SFTP layer - not the SSH2 layer. SFTP as a protocol doesn't know about authentication - that's handled entirely by the SSH layer. ie. it's not SFTP errors you'd want to look at but SSH errors.
Reference code: https://github.com/phpseclib/phpseclib/blob/1.0.7/phpseclib/Net/SSH2.php#L2219
Other possible password request mechanisms
It's possible that password request isn't coming from SSH's built-in authentication mechanism. It's possible you're getting a SSH_MSG_USERAUTH_SUCCESS response from the "publickey" SSH_MSG_USERAUTH_REQUEST.
At this point I can see two possibilities:
It could be a banner message that you're seeing. You can get those by doing $sftp->getBannerMessage().
It's possible you're only seeing this error when you SSH into the server as opposed to SFTP'ing into it. ie. it's possible you wouldn't see the error unless you did $ssh->exec() or $ssh->write(). At this point the "error" could be communicated to you via stderr or stdout.
To know for sure I'd have to see the SSH logs. The phpseclib logs may or may not be sufficient. I mean you could do $sftp->exec('pwd'); or $sftp->read('[prompt]'); but my guess is that you're not already doing that. If you wanted to go that route you could do define('NET_SSH2_LOGGING', 2); and then echo $sftp->getLog() after you do either $sftp->exec() or $sftp->read().
The PuTTY logs might be more useful. To get them you can go to PuTTY->Session->Logging, check the "SSH packets" radio button and then connect as usual.
Unfortunately, OpenSSH does not, to the best of my knowledge, log the raw / decrypted SSH2 packets so OpenSSH isn't going to be too useful here.

GnuPG isn't working in PHP with nginx

I think that probably I'm missing something, but I don't see it right now. I want create a simple form where users can encrypt automatically messages between them (form message to user2 -> encrypt(message) -> user2 receive it and decrypt). I'm using nginx, I installed gnupg following their instructions and add it to my php.ini (now it shows that GnuPG is enabled with GPGME Version 1.4.3 and Extension Version 1.3.6) I want use a specific keyring located at /usr/share/nginx/.gnupg I tried the following code:
$iterator = new gnupg_keylistiterator("developer");
foreach($iterator as $fingerprint => $userid) {
echo $fingerprint." -> " . $userid . "\n";
}
var_dump($iterator);
And I just obtain the following response from var_dump:
object(gnupg_keylistiterator)#1 (0) { }
Maybe my question is an idiot question, but I never used gnupg in php and I want learning, but I'm stunk since yesterday and I don't understand why it doesn't work...
Thanks for your time
The most common issue is that you imported the keys to another keyring than later is searched for keys. GnuPG uses a per-(system)-user "GnuPG home directory", each containing individual keyrings. If you import a key as the administrator or a developer you import the key to your own keyring, while usually the web server running the PHP application is executed in another user context and will not find this key, resulting in an empty result when listing the keys from within PHP.
You can set this by setting up an environment variable before initializing the GnuPG binding.
putenv("GNUPGHOME=/tmp"); // Set GnuPG home directory to the temp folder
$res = gnupg_init(); // Initialize GnuPG
Obviously /tmp does not actually qualify as a reasonable directory, choose something where your application stores application data anyway. It should not be a directory accessible through HTTP.
As an alternative, gnupg_import($res, $pubkey) the key before using it (but this will result in some performance penalty for importing the key).

SSH2 in PHP to connect to remote server

I'm using the following code to connect locally using SSH2, but I'm trying to work out how public/private keys are handled when connecting to a remote server.
$SSH_CONNECTION = ssh2_connect('localhost', 22, array('hostkey'=>'ssh-rsa'));
ssh2_auth_pubkey_file($SSH_CONNECTION, 'username','/path/to/id_rsa.pub','/path/to/id_rsa')
If I'm connecting to a remote server, does there need to be a key file (public or private?) on the remote server, and how do I reference it? I'm not sure if the code is the same or exactly how it works.
Appreciate any explanation.
In order for the remote server to accept the keys, you need to put a copy of the public key in a file called authorized_keys in the .ssh directory in the home folder of the user you are logging in as.
So to authenticate as bob on a remote server, you would have a file called authorized_keys in /home/bob/.ssh on the remote with the public key in it (e.g. ssh-rsa AAAA..<long-string-of-text>..cXrTp bob#host) (you can have more than one authorized keys, each one goes on it's own line in the file).
The id_rsa and id_rsa.pub files need to be on the client system where you call ssh2_auth_pubkey_file and readable by your PHP script.
This article about SSH Keys gives a good explanation about how to generate a keypair for key based authentication and how you can transfer the key to the host as well.
Without the public key in the authorized_keys file for the user you are trying to authenticate as, the authentication will not work.
Also, be sure to take the necessary security precautions by protecting the private key with a passphrase, making the private key unreadble by other users on the system, and protecting the passphrase in your script or controlling access to the file your script will read it from if it will be stored.

Deployment using ssh with key without providing passphrase for private key (ssh-agent)

Wherein lies the difference between Capistrano and Rocketeer when it comes to the passphrase for a private key?
I already have both Capistrano and Rocketeer deployment strategies set up properly and working. Capistrano lets ssh-agent provide the passphrase - Rocketeer, as it seems, does not. The question is not about how but why the passphrase is needed.
Background:
I want to use Rocketeer for deployment of a Laravel application instead of Capistrano. It seems as if it delegates the SSH connection to Laravel.
After setting only the remote server's name in the configuration and running a check, after some prompts for credentials Rocketeer stores the needed passphrase and the path to my desired private key in a non-version-controlled file.
I do not want to have credentials for establishing a SSH connection stored on my disk - especially not the passphrase to any of my private keys.
So, why is anything more than the server's name required?
I see that Laravel has those fields prepared in its remotes config - I just could not find out which component is responsible eventually and why it does not leave the SSH connection completely to the system itself.
Is it Rocketeer, Laravel, Symfony, phpseclib or even php itself underneath that needs that many information for establishing a SSH connection?
It's Laravel's missing implementation of phpseclib's ssh-agent that requires that many information for establishing a SSH connection.
That's why Rocketeer does not allow to rely on the ssh-agent next to username/password and privatekey/passphrase authentication as does Capistrano.
A proposal was stated and merged to include phpseclib's undocumented implementation for using the ssh-agent instead of an explicit key.
Rocketeer would profit from this as it relies on said implementation of phpseclib in Laravel.
(Thanks to #hannesvdvreken, #ThomasPayer and #passioncoder for pointing in the right directions)
There are some thing you might want to know.
You can use the default app/config/remote.php or you can use the Rocketeer config.php that gets published under app/packages/anahkiasen/rocketeer.
I tend to use the Laravel file. I made a copy of that file into the app/config/development folder which is ignored by git with .gitignore. I only write down the passkey of my private key down in that file. It will get merged with the array in app/config/remote.php.
Here's my app/config/development/remote.php file:
return array(
'connections' => array(
'staging' => array(
'keyphrase' => 'your-secret-here',
),
'production' => array(
'keyphrase' => 'your-secret-here',
),
),
);
Hope this helps.

How to use the PHP built-in server with Windows Authentication (NTLM) to fill $_SERVER["LOGON_USER"]?

I have scripts that use the $_SERVER["LOGON_USER"] which is obtained on my servers through IIS authentication settings. I want this same variable to contain my domain\username when running locally, or at least to have a way to set it when I fire up the PHP built-in server on localhost.
How can I configure PHP on my machine to obtain and fill this variable the same way it does when running through IIS?
PS: I have seen this question, but the answer addresses $_ENV, not $_SERVER.
This is a workaround, if anyone has a better/proper solution (i.e. enabling NTLM), please post it as an answer and I'll accept it.
I was able to fill that variable using a router script. According to the docs, this script is run at the start of each HTTP request, so I use it to set this variable when running locally.
Also in my case, my environment had these two variables set, USERDOMAIN and USERNAME, so I used them to form the LOGON_USER server variable.
routerCredentials.php
<?php
$_SERVER["LOGON_USER"] = getenv("USERDOMAIN") . "\\" . getenv("USERNAME");
return false; // serve the requested resource as-is.
To use it, you just have to point to that file when you start the PHP built-in server:
php -S localhost:8000 "c:\somepath\routerCredentials.php"

Categories