GUACAMOLE-527: Merge support for SSH host key checking.

This commit is contained in:
Michael Jumper 2018-06-25 17:22:09 -07:00
commit addb473148
13 changed files with 233 additions and 8 deletions

View File

@ -22,6 +22,9 @@
#include "config.h" #include "config.h"
#include <guacamole/client.h>
#include <libssh2.h>
#include <openssl/ossl_typ.h> #include <openssl/ossl_typ.h>
/** /**
@ -166,5 +169,52 @@ void guac_common_ssh_key_free(guac_common_ssh_key* key);
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
int length, unsigned char* sig); int length, unsigned char* sig);
/**
* Verifies the host key for the given hostname/port combination against
* one or more known_hosts entries. The known_host entries can either be a
* single host_key, provided by the client, or a set of known_hosts entries
* provided in the /etc/guacamole/ssh_known_hosts file. Failure to correctly
* load the known_hosts entries will result in a connection abort and a returned
* error code. A return code of zero indiciates that either no known_hosts entries
* were provided, or that the verification succeeded (match). Negative values
* indicate internal libssh2 error codes; positive values indicate a failure
* during verification of the host key against the known hosts.
*
* @param session
* A pointer to the LIBSSH2_SESSION structure of the SSH connection already
* in progress.
*
* @param client
* The current guac_client instance for which the known_hosts checking is
* being performed.
*
* @param host_key
* The known host entry provided by the client. If this is non-null and not
* empty, it will be the only host key loaded and used for verification. If
* this is null or empty an attempt will be made to read the
* /etc/guacamole/ssh_known_hosts file and load entries from it.
*
* @param hostname
* The hostname or IP of the server that is being verified.
*
* @param port
* The port number of the server being verified.
*
* @param remote_hostkey
* The host key of the remote system being verified.
*
* @param remote_hostkey_len
* The length of the remote host key being verified
*
* @return
* The status of the known_hosts check. This will be zero if no entries
* are provided or if the match succeeds, negative to indicate internal
* libssh2 errors, or positive to indicate failures during host key
* checking.
*/
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
const char* host_key, const char* hostname, int port, const char* remote_hostkey,
const size_t remote_hostkey_len);
#endif #endif

View File

@ -98,7 +98,8 @@ void guac_common_ssh_uninit();
* if the connection or authentication were not successful. * if the connection or authentication were not successful.
*/ */
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive); const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive,
const char* host_key);
/** /**
* Disconnects and destroys the given SSH session, freeing all associated * Disconnects and destroys the given SSH session, freeing all associated

View File

@ -35,6 +35,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
char* passphrase) { char* passphrase) {
@ -245,3 +246,86 @@ int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
} }
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
const char* host_key, const char* hostname, int port, const char* remote_hostkey,
const size_t remote_hostkey_len) {
LIBSSH2_KNOWNHOSTS* ssh_known_hosts = libssh2_knownhost_init(session);
int known_hosts = 0;
/* Add host key provided from settings */
if (host_key && strcmp(host_key, "") != 0) {
known_hosts = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key),
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
/* readline function returns 0 on success, so we increment to indicate a valid entry */
if (known_hosts == 0)
known_hosts++;
}
/* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */
else {
const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts";
if (access(guac_known_hosts, F_OK) != -1)
known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
}
/* If there's an error provided, abort connection and return that. */
if (known_hosts < 0) {
char* errmsg;
int errval = libssh2_session_last_error(session, &errmsg, NULL, 0);
guac_client_log(client, GUAC_LOG_ERROR,
"Error %d trying to load SSH host keys: %s", errval, errmsg);
libssh2_knownhost_free(ssh_known_hosts);
return known_hosts;
}
/* No host keys were loaded, so we bail out checking and continue the connection. */
else if (known_hosts == 0) {
guac_client_log(client, GUAC_LOG_WARNING,
"No known host keys provided, host identity will not be verified.");
libssh2_knownhost_free(ssh_known_hosts);
return known_hosts;
}
/* Check remote host key against known hosts */
int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port,
remote_hostkey, remote_hostkey_len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
NULL);
/* Deal with the return of the host key check */
switch (kh_check) {
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
guac_client_log(client, GUAC_LOG_DEBUG,
"Host key match found for %s", hostname);
break;
case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
guac_client_log(client, GUAC_LOG_ERROR,
"Host key not found for %s.", hostname);
break;
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
guac_client_log(client, GUAC_LOG_ERROR,
"Host key does not match known hosts entry for %s", hostname);
break;
case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
default:
guac_client_log(client, GUAC_LOG_ERROR,
"Host %s could not be checked against known hosts.",
hostname);
}
/* Return the check value */
libssh2_knownhost_free(ssh_known_hosts);
return kh_check;
}

View File

@ -35,6 +35,7 @@
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h> #include <pthread.h>
#include <pwd.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -414,7 +415,8 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
} }
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive) { const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive,
const char* host_key) {
int retval; int retval;
@ -518,6 +520,42 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
return NULL; return NULL;
} }
/* Get host key of remote system we're connecting to */
size_t remote_hostkey_len;
const char *remote_hostkey = libssh2_session_hostkey(session, &remote_hostkey_len, NULL);
/* Failure to retrieve a host key means we should abort */
if (!remote_hostkey) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Failed to get host key for %s", hostname);
free(common_session);
close(fd);
return NULL;
}
/* SSH known host key checking. */
int known_host_check = guac_common_ssh_verify_host_key(session, client, host_key,
hostname, atoi(port), remote_hostkey,
remote_hostkey_len);
/* Abort on any error codes */
if (known_host_check != 0) {
char* err_msg;
libssh2_session_last_error(session, &err_msg, NULL, 0);
if (known_host_check < 0)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Error occurred attempting to check host key: %s", err_msg);
if (known_host_check > 0)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Host key did not match any provided known host keys. %s", err_msg);
free(common_session);
close(fd);
return NULL;
}
/* Store basic session data */ /* Store basic session data */
common_session->client = client; common_session->client = client;
common_session->user = user; common_session->user = user;
@ -560,4 +598,3 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) {
free(session); free(session);
} }

View File

@ -974,7 +974,8 @@ void* guac_rdp_client_thread(void* data) {
/* Attempt SSH connection */ /* Attempt SSH connection */
rdp_client->sftp_session = rdp_client->sftp_session =
guac_common_ssh_create_session(client, settings->sftp_hostname, guac_common_ssh_create_session(client, settings->sftp_hostname,
settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval); settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval,
settings->sftp_host_key);
/* Fail if SSH connection does not succeed */ /* Fail if SSH connection does not succeed */
if (rdp_client->sftp_session == NULL) { if (rdp_client->sftp_session == NULL) {

View File

@ -81,6 +81,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
#ifdef ENABLE_COMMON_SSH #ifdef ENABLE_COMMON_SSH
"enable-sftp", "enable-sftp",
"sftp-hostname", "sftp-hostname",
"sftp-host-key",
"sftp-port", "sftp-port",
"sftp-username", "sftp-username",
"sftp-password", "sftp-password",
@ -355,6 +356,11 @@ enum RDP_ARGS_IDX {
*/ */
IDX_SFTP_HOSTNAME, IDX_SFTP_HOSTNAME,
/**
* The public SSH host key of the SFTP server. Optional.
*/
IDX_SFTP_HOST_KEY,
/** /**
* The port of the SSH server to connect to for SFTP. If blank, the default * The port of the SSH server to connect to for SFTP. If blank, the default
* SSH port of "22" will be used. * SSH port of "22" will be used.
@ -822,6 +828,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOSTNAME, settings->hostname); IDX_SFTP_HOSTNAME, settings->hostname);
/* The public SSH host key. */
settings->sftp_host_key =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_SFTP_HOST_KEY, NULL);
/* Port for SFTP connection */ /* Port for SFTP connection */
settings->sftp_port = settings->sftp_port =
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
@ -999,6 +1010,7 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) {
/* Free SFTP settings */ /* Free SFTP settings */
free(settings->sftp_directory); free(settings->sftp_directory);
free(settings->sftp_root_directory); free(settings->sftp_root_directory);
free(settings->sftp_host_key);
free(settings->sftp_hostname); free(settings->sftp_hostname);
free(settings->sftp_passphrase); free(settings->sftp_passphrase);
free(settings->sftp_password); free(settings->sftp_password);

View File

@ -342,6 +342,11 @@ typedef struct guac_rdp_settings {
*/ */
char* sftp_hostname; char* sftp_hostname;
/**
* The public SSH host key.
*/
char* sftp_host_key;
/** /**
* The port of the SSH server to connect to for SFTP. * The port of the SSH server to connect to for SFTP.
*/ */

View File

@ -31,6 +31,7 @@
/* Client plugin arguments */ /* Client plugin arguments */
const char* GUAC_SSH_CLIENT_ARGS[] = { const char* GUAC_SSH_CLIENT_ARGS[] = {
"hostname", "hostname",
"host-key",
"port", "port",
"username", "username",
"password", "password",
@ -68,6 +69,11 @@ enum SSH_ARGS_IDX {
*/ */
IDX_HOSTNAME, IDX_HOSTNAME,
/**
* The Base64-encoded public SSH host key. Optional.
*/
IDX_HOST_KEY,
/** /**
* The port to connect to. Optional. * The port to connect to. Optional.
*/ */
@ -247,6 +253,10 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_HOSTNAME, ""); IDX_HOSTNAME, "");
settings->host_key =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_HOST_KEY, NULL);
settings->username = settings->username =
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_USERNAME, NULL); IDX_USERNAME, NULL);
@ -384,6 +394,7 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) {
/* Free network connection information */ /* Free network connection information */
free(settings->hostname); free(settings->hostname);
free(settings->host_key);
free(settings->port); free(settings->port);
/* Free credentials */ /* Free credentials */
@ -417,4 +428,3 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) {
free(settings); free(settings);
} }

View File

@ -70,6 +70,11 @@ typedef struct guac_ssh_settings {
*/ */
char* hostname; char* hostname;
/**
* The public SSH host key.
*/
char* host_key;
/** /**
* The port of the SSH server to connect to. * The port of the SSH server to connect to.
*/ */

View File

@ -235,7 +235,8 @@ void* ssh_client_thread(void* data) {
/* Open SSH session */ /* Open SSH session */
ssh_client->session = guac_common_ssh_create_session(client, ssh_client->session = guac_common_ssh_create_session(client,
settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval); settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval,
settings->host_key);
if (ssh_client->session == NULL) { if (ssh_client->session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */ /* Already aborted within guac_common_ssh_create_session() */
return NULL; return NULL;
@ -275,7 +276,8 @@ void* ssh_client_thread(void* data) {
guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP...");
ssh_client->sftp_session = ssh_client->sftp_session =
guac_common_ssh_create_session(client, settings->hostname, guac_common_ssh_create_session(client, settings->hostname,
settings->port, ssh_client->user, settings->server_alive_interval); settings->port, ssh_client->user, settings->server_alive_interval,
settings->host_key);
if (ssh_client->sftp_session == NULL) { if (ssh_client->sftp_session == NULL) {
/* Already aborted within guac_common_ssh_create_session() */ /* Already aborted within guac_common_ssh_create_session() */
return NULL; return NULL;

View File

@ -60,6 +60,7 @@ const char* GUAC_VNC_CLIENT_ARGS[] = {
#ifdef ENABLE_COMMON_SSH #ifdef ENABLE_COMMON_SSH
"enable-sftp", "enable-sftp",
"sftp-hostname", "sftp-hostname",
"sftp-host-key",
"sftp-port", "sftp-port",
"sftp-username", "sftp-username",
"sftp-password", "sftp-password",
@ -208,6 +209,11 @@ enum VNC_ARGS_IDX {
*/ */
IDX_SFTP_USERNAME, IDX_SFTP_USERNAME,
/**
* The public SSH host key to identify the SFTP server.
*/
IDX_SFTP_HOST_KEY,
/** /**
* The password to provide when authenticating with the SSH server for * The password to provide when authenticating with the SSH server for
* SFTP (if not using a private key). * SFTP (if not using a private key).
@ -411,6 +417,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv,
IDX_SFTP_HOSTNAME, settings->hostname); IDX_SFTP_HOSTNAME, settings->hostname);
/* The public SSH host key. */
settings->sftp_host_key =
guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv,
IDX_SFTP_HOST_KEY, NULL);
/* Port for SFTP connection */ /* Port for SFTP connection */
settings->sftp_port = settings->sftp_port =
guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv,
@ -504,6 +515,7 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) {
/* Free SFTP settings */ /* Free SFTP settings */
free(settings->sftp_directory); free(settings->sftp_directory);
free(settings->sftp_root_directory); free(settings->sftp_root_directory);
free(settings->sftp_host_key);
free(settings->sftp_hostname); free(settings->sftp_hostname);
free(settings->sftp_passphrase); free(settings->sftp_passphrase);
free(settings->sftp_password); free(settings->sftp_password);

View File

@ -138,6 +138,11 @@ typedef struct guac_vnc_settings {
*/ */
char* sftp_hostname; char* sftp_hostname;
/**
* The public SSH host key.
*/
char* sftp_host_key;
/** /**
* The port of the SSH server to connect to for SFTP. * The port of the SSH server to connect to for SFTP.
*/ */

View File

@ -261,7 +261,8 @@ void* guac_vnc_client_thread(void* data) {
/* Attempt SSH connection */ /* Attempt SSH connection */
vnc_client->sftp_session = vnc_client->sftp_session =
guac_common_ssh_create_session(client, settings->sftp_hostname, guac_common_ssh_create_session(client, settings->sftp_hostname,
settings->sftp_port, vnc_client->sftp_user, settings->sftp_server_alive_interval); settings->sftp_port, vnc_client->sftp_user, settings->sftp_server_alive_interval,
settings->sftp_host_key);
/* Fail if SSH connection does not succeed */ /* Fail if SSH connection does not succeed */
if (vnc_client->sftp_session == NULL) { if (vnc_client->sftp_session == NULL) {