From 98503a9fb7703863fbb930a491e141143d21d27e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 02:00:23 -0700 Subject: [PATCH] GUAC-1171: Initial refactor to require that user credentials be provided prior to connect. --- src/common-ssh/guac_ssh.c | 201 +++++++++++++++++++++++++------------- src/common-ssh/guac_ssh.h | 38 +++---- 2 files changed, 147 insertions(+), 92 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index ef101f25..76b293bc 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -137,8 +137,126 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } +static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, + unsigned char** sig, size_t* sig_len, + const unsigned char* data, size_t data_len, void **abstract) { + + guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; + int length; + + /* Allocate space for signature */ + *sig = malloc(4096); + + /* Sign with key */ + length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); + if (length < 0) + return 1; + + *sig_len = length; + return 0; +} + +/** + * Callback for the keyboard-interactive authentication method. Currently + * suports just one prompt for the password. + */ +static void guac_common_ssh_kbd_callback(const char *name, int name_len, + const char *instruction, int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) { + + guac_common_ssh_session* common_session = + (guac_common_ssh_session*) *abstract; + + guac_client* client = common_session->client; + + /* Send password if only one prompt */ + if (num_prompts == 1) { + char* password = common_session->user->password; + responses[0].text = strdup(password); + responses[0].length = strlen(password); + } + + /* If more than one prompt, a single password is not enough */ + else + guac_client_log(client, GUAC_LOG_WARNING, + "Unsupported number of keyboard-interactive prompts: %i", + num_prompts); + +} + +static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) { + + guac_client* client = common_session->client; + guac_common_ssh_user* user = common_session->user; + LIBSSH2_SESSION* session = common_session->session; + + /* Get user credentials */ + char* username = user->username; + char* password = user->password; + guac_common_ssh_key* key = user->private_key; + + /* Get list of supported authentication methods */ + char* user_authlist = libssh2_userauth_list(session, username, + strlen(username)); + guac_client_log(client, GUAC_LOG_DEBUG, + "Supported authentication methods: %s", user_authlist); + + /* Authenticate with private key, if provided */ + if (key != NULL) { + + /* Check if public key auth is supported on the server */ + if (strstr(user_authlist, "publickey") == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication not supported"); + return 1; + } + + /* Attempt public key auth */ + if (libssh2_userauth_publickey(session, username, + (unsigned char*) key->public_key, key->public_key_length, + guac_common_ssh_sign_callback, (void**) key)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication failed: %s", error_message); + + return 1; + + } + + /* Private key authentication succeeded */ + return 0; + + } + + /* Authenticate with password */ + if (strstr(user_authlist, "password") != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Using password authentication method"); + return libssh2_userauth_password(session, username, password); + } + + /* Authenticate with password via keyboard-interactive auth */ + if (strstr(user_authlist, "keyboard-interactive") != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Using keyboard-interactive authentication method"); + return libssh2_userauth_keyboard_interactive(session, username, + &guac_common_ssh_kbd_callback); + } + + /* No known authentication types available */ + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE, + "No known authentication methods"); + return 1; + +} + guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port) { + const char* hostname, const char* port, guac_common_ssh_user* user) { int retval; @@ -212,12 +330,17 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, /* Free addrinfo */ freeaddrinfo(addresses); + /* Allocate new session */ + guac_common_ssh_session* common_session = + malloc(sizeof(guac_common_ssh_session)); + /* Open SSH session */ LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, - NULL, client); + NULL, common_session); if (session == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed."); + free(common_session); return NULL; } @@ -225,18 +348,22 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, if (libssh2_session_handshake(session, fd)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed."); + free(common_session); return NULL; } - /* Allocate new session */ - guac_common_ssh_session* common_session = - malloc(sizeof(guac_common_ssh_session)); - /* Store basic session data */ common_session->client = client; + common_session->user = user; common_session->session = session; common_session->fd = fd; + /* Attempt authentication */ + if (guac_common_ssh_authenticate(common_session)) { + free(common_session); + return NULL; + } + /* Return created session */ return common_session; @@ -248,65 +375,3 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { free(session); } -static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, - unsigned char** sig, size_t* sig_len, - const unsigned char* data, size_t data_len, void **abstract) { - - guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; - int length; - - /* Allocate space for signature */ - *sig = malloc(4096); - - /* Sign with key */ - length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); - if (length < 0) - return 1; - - *sig_len = length; - return 0; -} - -int guac_common_ssh_authenticate(guac_common_ssh_session* common_session, - guac_common_ssh_user* user) { - - guac_client* client = common_session->client; - LIBSSH2_SESSION* session = common_session->session; - - /* Get user credentials */ - char* username = user->username; - guac_common_ssh_key* key = user->private_key; - - /* Get list of supported authentication methods */ - char* user_authlist = libssh2_userauth_list(session, username, - strlen(username)); - guac_client_log(client, GUAC_LOG_DEBUG, - "Supported authentication methods: %s", user_authlist); - - /* Check if public key auth is supported on the server */ - if (strstr(user_authlist, "publickey") == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication not supported"); - return 1; - } - - /* Attempt public key auth */ - if (libssh2_userauth_publickey(session, username, - (unsigned char*) key->public_key, key->public_key_length, - guac_common_ssh_sign_callback, (void**) key)) { - - /* Abort on failure */ - char* error_message; - libssh2_session_last_error(session, &error_message, NULL, 0); - guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Public key authentication failed: %s", error_message); - - return 1; - - } - - /* Authentication succeeded */ - return 0; - -} - diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 90ccca4c..cf9e535e 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -39,6 +39,11 @@ typedef struct guac_common_ssh_session { */ guac_client* client; + /** + * The user that will be authenticating via SSH. + */ + guac_common_ssh_user* user; + /** * The underlying SSH session from libssh2. */ @@ -72,9 +77,9 @@ int guac_common_ssh_init(guac_client* client); void guac_common_ssh_uninit(); /** - * Connects to the SSH server running at the given hostname and port but does - * not perform any authentication. If an error occurs while connecting, the - * Guacamole client will automatically and fatally abort. + * Connects to the SSH server running at the given hostname and port, and + * authenticates as the given user. If an error occurs while connecting or + * authenticating, the Guacamole client will automatically and fatally abort. * * @param client * The Guacamole client that will be using SSH. @@ -85,12 +90,15 @@ void guac_common_ssh_uninit(); * @param port * The port to connect to on the given hostname. * + * @param user + * The user to authenticate as, once connected. + * * @return - * A new SSH session if the connection succeeds, or NULL if the connection - * was not successful. + * A new SSH session if the connection and authentication succeed, or NULL + * if the connection or authentication were not successful. */ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port); + const char* hostname, const char* port, guac_common_ssh_user* user); /** * Disconnects and destroys the given SSH session, freeing all associated @@ -101,23 +109,5 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, */ void guac_common_ssh_destroy_session(guac_common_ssh_session* session); -/** - * Authenticates the given user with the given, existing SSH session. If - * authentication fails, the Guacamole client will automatically and fatally - * abort. - * - * @param session - * The SSH session to authenticate with. - * - * @param user - * The user object describing the current user and their associated - * credentials. - * - * @return - * Zero if authentication succeeds, non-zero if authentication fails. - */ -int guac_common_ssh_authenticate(guac_common_ssh_session* session, - guac_common_ssh_user* user); - #endif