From 428243bb783ac58ecf228a5704b5709c26d9239b Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 28 May 2018 10:12:07 -0400 Subject: [PATCH] GUACAMOLE-527: Move host key checking to a separate function. --- src/common-ssh/common-ssh/key.h | 50 ++++++++++++++++++++ src/common-ssh/key.c | 78 +++++++++++++++++++++++++++++++ src/common-ssh/ssh.c | 83 +++++++++++---------------------- 3 files changed, 154 insertions(+), 57 deletions(-) diff --git a/src/common-ssh/common-ssh/key.h b/src/common-ssh/common-ssh/key.h index 1754deae..576ba1ba 100644 --- a/src/common-ssh/common-ssh/key.h +++ b/src/common-ssh/common-ssh/key.h @@ -22,6 +22,9 @@ #include "config.h" +#include +#include + #include /** @@ -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 length, unsigned char* sig); +/** + * Verifies the fingerprint 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 fingerprint 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 fingerprint + * The fingering of the server being verified. + * + * @param fp_len + * The length of the fingerprint 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* fingerprint, + const size_t fp_len); + #endif diff --git a/src/common-ssh/key.c b/src/common-ssh/key.c index a05d696d..570da8e8 100644 --- a/src/common-ssh/key.c +++ b/src/common-ssh/key.c @@ -245,3 +245,81 @@ 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* fingerprint, + const size_t fp_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"; + 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) { + + guac_client_log(client, GUAC_LOG_ERROR, + "Failure trying to load SSH host keys."); + + 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) { + libssh2_knownhost_free(ssh_known_hosts); + return known_hosts; + } + + + /* Check fingerprint against known hosts */ + int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port, + fingerprint, fp_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; + +} diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 4bd3a3dc..efdef838 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -520,69 +520,38 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, return NULL; } + /* Get fingerprint of host we're connecting to */ + size_t fp_len; + int fp_type; + const char *fingerprint = libssh2_session_hostkey(session, &fp_len, &fp_type); + + /* Failure to generate a fingerprint means we should abort */ + if (!fingerprint) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to get fingerprint for host %s", hostname); + return NULL; + } + /* SSH known host key checking. */ - LIBSSH2_KNOWNHOSTS *ssh_known_hosts = libssh2_knownhost_init(session); - int num_known_hosts = 0; + int known_host_check = guac_common_ssh_verify_host_key(session, client, host_key, + hostname, atoi(port), fingerprint, + fp_len); - /* Add host key provided from settings */ - if (host_key && strcmp(host_key, "") != 0) { + /* Abort on any error codes */ + if (known_host_check != 0) { + char* err_msg; + int err_len; + libssh2_session_last_error(session, &err_msg, &err_len, 0); - int kh_add = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key), - LIBSSH2_KNOWNHOST_FILE_OPENSSH); - num_known_hosts++; - - if (kh_add) - guac_client_log(client, GUAC_LOG_WARNING, "Failed to add provided host key" - " to known hosts store for %s. Error was %d", hostname, kh_add); - - } - - /* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */ - else { - const char *known_hosts = "/etc/guacamole/ssh_known_hosts"; - num_known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH); - } - - /* If we've found a provided set of host keys, check against them. */ - if (num_known_hosts > 0) { - /* Get fingerprint of host we're connecting to */ - size_t fp_len; - int fp_type; - const char *fingerprint = libssh2_session_hostkey(session, &fp_len, &fp_type); - - if (!fingerprint) + if (known_host_check < 0) guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Failed to get fingerprint for host %s", hostname); + "Error occurred attempting to check host key: %s", err_msg); - /* Check fingerprint against known hosts */ - struct libssh2_knownhost *host; - int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, atoi(port), - fingerprint, fp_len, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW, - &host); + if (known_host_check > 0) + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Host fingerprint did not match any provided known host keys: %s", err_msg); - libssh2_knownhost_free(ssh_known_hosts); - - 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_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Host key not found for %s.", hostname); - break; - case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Host key does not match known hosts entry for %s", hostname); - break; - case LIBSSH2_KNOWNHOST_CHECK_FAILURE: - default: - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Host %s could not be checked against known hosts.", - hostname); - } + return NULL; } /* Store basic session data */