From ff287aee52ff0fc0e810d3666fcde228d2516bbb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Jul 2015 01:31:15 -0700 Subject: [PATCH] GUAC-1171: Separate key/password management from SSH connection. Maintain connection information within session object. --- src/common-ssh/Makefile.am | 6 +- src/common-ssh/guac_ssh.c | 129 ++++++++------------------------- src/common-ssh/guac_ssh.h | 99 +++++++++++++------------ src/common-ssh/guac_ssh_user.c | 84 +++++++++++++++++++++ src/common-ssh/guac_ssh_user.h | 111 ++++++++++++++++++++++++++++ 5 files changed, 281 insertions(+), 148 deletions(-) create mode 100644 src/common-ssh/guac_ssh_user.c create mode 100644 src/common-ssh/guac_ssh_user.h diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 8d0f9612..9d6b671f 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -29,13 +29,15 @@ libguac_common_ssh_la_SOURCES = \ guac_sftp.c \ guac_ssh.c \ guac_ssh_buffer.c \ - guac_ssh_key.c + guac_ssh_key.c \ + guac_ssh_user.c noinst_HEADERS = \ guac_sftp.h \ guac_ssh.h \ guac_ssh_buffer.h \ - guac_ssh_key.h + guac_ssh_key.h \ + guac_ssh_user.h libguac_common_ssh_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 87347943..ef101f25 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -137,32 +137,8 @@ void guac_common_ssh_uninit() { guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); } -/** - * Connects to the SSH server running at the given hostname and port, but does - * not perform any authentication. Authentication must be immediately performed - * after creation of the session for the session to become usable. If an error - * occurs while connecting, the Guacamole client will automatically and fatally - * abort. - * - * @param client - * The Guacamole client that will be using SSH. - * - * @param hostname - * The hostname of the SSH server to connect to. - * - * @param port - * The port to connect to on the given hostname. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. - * - * @return - * A new SSH session if the connection succeeds, or NULL if the connection - * was not successful. - */ -static LIBSSH2_SESSION* guac_common_ssh_connect(guac_client* client, - const char* hostname, const char* port, int* socket_fd) { +guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, + const char* hostname, const char* port) { int retval; @@ -252,30 +228,27 @@ static LIBSSH2_SESSION* guac_common_ssh_connect(guac_client* client, return NULL; } - /* Save file descriptor */ - if (socket_fd != NULL) - *socket_fd = fd; + /* 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->session = session; + common_session->fd = fd; /* Return created session */ - return session; + return common_session; } -LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, - const char* hostname, const char* port, - const char* username, const char* password, - int* socket_fd) { - - LIBSSH2_SESSION* session = guac_common_ssh_connect(client, - hostname, port, socket_fd); - - /* STUB */ - - return session; - +void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { + libssh2_session_disconnect(session->session, "Bye"); + libssh2_session_free(session->session); + free(session); } -static int __sign_callback(LIBSSH2_SESSION* 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) { @@ -294,73 +267,33 @@ static int __sign_callback(LIBSSH2_SESSION* session, return 0; } -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, - const char* hostname, const char* port, - const char* username, char* private_key, char* passphrase, - int* socket_fd) { +int guac_common_ssh_authenticate(guac_common_ssh_session* common_session, + guac_common_ssh_user* user) { - LIBSSH2_SESSION* session = guac_common_ssh_connect(client, - hostname, port, socket_fd); + guac_client* client = common_session->client; + LIBSSH2_SESSION* session = common_session->session; - guac_client_log(client, GUAC_LOG_DEBUG, - "Attempting private key import (WITHOUT passphrase)"); + /* Get user credentials */ + char* username = user->username; + guac_common_ssh_key* key = user->private_key; - /* Attempt to read key without passphrase */ - guac_common_ssh_key* key = guac_common_ssh_key_alloc(private_key, - strlen(private_key), ""); - -#if 0 - /* On failure, attempt with passphrase */ - if (key == NULL) { - - /* Log failure of initial attempt */ - guac_client_log(client, GUAC_LOG_DEBUG, - "Initial import failed: %s", guac_common_ssh_key_error()); - - guac_client_log(client, GUAC_LOG_DEBUG, - "Re-attempting private key import (WITH passphrase)"); - - /* Prompt for passphrase if missing */ - if (client_data->key_passphrase[0] == 0) - guac_terminal_prompt(client_data->term, "Key passphrase: ", - client_data->key_passphrase, sizeof(client_data->key_passphrase), false); - - /* Import key with passphrase */ - client_data->key = guac_common_ssh_key_alloc(client_data->key_base64, - strlen(client_data->key_base64), - client_data->key_passphrase); - - /* If still failing, give up */ - if (client_data->key == NULL) { - guac_client_abort(client, - GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Auth key import failed: %s", guac_common_ssh_key_error()); - return NULL; - } - - } /* end decrypt key with passphrase */ -#endif - - /* Success */ - guac_client_log(client, GUAC_LOG_INFO, "Auth key successfully imported."); - - /* Get list of suported authentication methods */ + /* 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 suported on the server */ + /* 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 suported"); - return NULL; + "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, - __sign_callback, (void**) key)) { + guac_common_ssh_sign_callback, (void**) key)) { /* Abort on failure */ char* error_message; @@ -368,12 +301,12 @@ LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, "Public key authentication failed: %s", error_message); - return NULL; + return 1; } - /* Return new session on success */ - return session; + /* Authentication succeeded */ + return 0; } diff --git a/src/common-ssh/guac_ssh.h b/src/common-ssh/guac_ssh.h index 7027e043..90ccca4c 100644 --- a/src/common-ssh/guac_ssh.h +++ b/src/common-ssh/guac_ssh.h @@ -23,9 +23,34 @@ #ifndef GUAC_COMMON_SSH_H #define GUAC_COMMON_SSH_H +#include "guac_ssh_user.h" + #include #include +/** + * An SSH session, backed by libssh2 and associated with a particular + * Guacamole client. + */ +typedef struct guac_common_ssh_session { + + /** + * The Guacamole client using this SSH session. + */ + guac_client* client; + + /** + * The underlying SSH session from libssh2. + */ + LIBSSH2_SESSION* session; + + /** + * The file descriptor of the socket being used for the SSH connection. + */ + int fd; + +} guac_common_ssh_session; + /** * Initializes the underlying SSH and encryption libraries used by Guacamole. * This function must be called before any other guac_common_ssh_*() functions @@ -47,9 +72,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 using the - * given username and password for 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 but does + * not perform any authentication. If an error occurs while connecting, the + * Guacamole client will automatically and fatally abort. * * @param client * The Guacamole client that will be using SSH. @@ -60,61 +85,39 @@ void guac_common_ssh_uninit(); * @param port * The port to connect to on the given hostname. * - * @param username - * The username to authenticate as. - * - * @param password - * The password to provide when authenticating as the given user. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. - * * @return - * A new SSH session if the connection and authentication succeed, or - * NULL if the connection or authentication were not successful. + * A new SSH session if the connection succeeds, or NULL if the connection + * was not successful. */ -LIBSSH2_SESSION* guac_common_ssh_connect_password(guac_client* client, - const char* hostname, const char* port, - const char* username, const char* password, - int* socket_fd); +guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, + const char* hostname, const char* port); /** - * Connects to the SSH server running at the given hostname and port using the - * given username and private key for authentication. If an error occurs while - * connecting, the Guacamole client will automatically and fatally abort. + * Disconnects and destroys the given SSH session, freeing all associated + * resources. * - * @param client - * The Guacamole client that will be using SSH. + * @param session + * The SSH session to destroy. + */ +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 hostname - * The hostname of the SSH server to connect to. + * @param session + * The SSH session to authenticate with. * - * @param port - * The port to connect to on the given hostname. - * - * @param username - * The username to authenticate as. - * - * @param private_key - * The base64-encoded private key to use when authenticating. - * - * @param passphrase - * The passphrase to use when importing the private key, if any, or NULL - * if no passphrase should be used. - * - * @param socket_fd - * A pointer to an integer in which the newly-allocated file descriptor for - * the SSH socket should be stored. + * @param user + * The user object describing the current user and their associated + * credentials. * * @return - * A new SSH session if the connection and authentication succeed, or - * NULL if the connection or authentication were not successful. + * Zero if authentication succeeds, non-zero if authentication fails. */ -LIBSSH2_SESSION* guac_common_ssh_connect_private_key(guac_client* client, - const char* hostname, const char* port, - const char* username, char* private_key, char* passphrase, - int* socket_fd); +int guac_common_ssh_authenticate(guac_common_ssh_session* session, + guac_common_ssh_user* user); #endif diff --git a/src/common-ssh/guac_ssh_user.c b/src/common-ssh/guac_ssh_user.c new file mode 100644 index 00000000..84aad9c5 --- /dev/null +++ b/src/common-ssh/guac_ssh_user.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "guac_ssh_key.h" +#include "guac_ssh_user.h" + +#include +#include + +guac_common_ssh_user* guac_common_ssh_create_user(const char* username) { + + guac_common_ssh_user* user = malloc(sizeof(guac_common_ssh_user)); + + /* Init user */ + user->username = strdup(username); + user->password = NULL; + user->private_key = NULL; + + return user; + +} + +void guac_common_ssh_destroy_user(guac_common_ssh_user* user) { + + /* Free private key, if present */ + if (user->private_key != NULL) + guac_common_ssh_key_free(user->private_key); + + /* Free all other data */ + free(user->password); + free(user->username); + +} + +void guac_common_ssh_user_set_password(guac_common_ssh_user* user, + const char* password) { + + /* Replace current password with given value */ + free(user->password); + user->password = strdup(password); + +} + +int guac_common_ssh_user_import_key(guac_common_ssh_user* user, + char* private_key, char* passphrase) { + + /* Free existing private key, if present */ + if (user->private_key != NULL) + guac_common_ssh_key_free(user->private_key); + + /* Attempt to read key without passphrase if none given */ + if (passphrase == NULL) + user->private_key = guac_common_ssh_key_alloc(private_key, + strlen(private_key), ""); + + /* Otherwise, use provided passphrase */ + else + user->private_key = guac_common_ssh_key_alloc(private_key, + strlen(private_key), passphrase); + + /* Fail if key could not be read */ + return user->private_key == NULL; + +} + diff --git a/src/common-ssh/guac_ssh_user.h b/src/common-ssh/guac_ssh_user.h new file mode 100644 index 00000000..760523a5 --- /dev/null +++ b/src/common-ssh/guac_ssh_user.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_COMMON_SSH_USER_H +#define GUAC_COMMON_SSH_USER_H + +#include "guac_ssh_key.h" + +/** + * Data describing an SSH user, including their credentials. + */ +typedef struct guac_common_ssh_user { + + /** + * The username of this user. + */ + char* username; + + /** + * The password which should be used to authenticate this user, if any, or + * NULL if a private key will be used instead. + */ + char* password; + + /** + * The private key which should be used to authenticate this user, if any, + * or NULL if a password will be used instead. + */ + guac_common_ssh_key* private_key; + +} guac_common_ssh_user; + +/** + * Creates a new SSH user with the given username. When additionally populated + * with a password or private key, this user can then be used for + * authentication. + * + * @param username + * The username of the user being created. + * + * @return + * A new SSH user having the given username, but no associated password + * or private key. + */ +guac_common_ssh_user* guac_common_ssh_create_user(const char* username); + +/** + * Destroys the given user object, releasing all associated resources. + * + * @param user + * The user to destroy. + */ +void guac_common_ssh_destroy_user(guac_common_ssh_user* user); + +/** + * Associates the given user with the given password, such that that password + * is used for future authentication attempts. + * + * @param user + * The user to associate with the given password. + * + * @param password + * The password to associate with the given user. + */ +void guac_common_ssh_user_set_password(guac_common_ssh_user* user, + const char* password); + +/** + * Imports the given private key, associating that key with the given user. If + * necessary to decrypt the key, a passphrase may be specified. The private key + * must be provided in base64 form. If the private key is imported + * successfully, it will be used for future authentication attempts. + * + * @param user + * The user to associate with the given private key. + * + * @param private_key + * The base64-encoded private key to import. + * + * @param passphrase + * The passphrase to use to decrypt the given private key, or NULL if no + * passphrase should be used. + * + * @return + * Zero if the private key is successfully imported, or non-zero if the + * private key could not be imported due to an error. + */ +int guac_common_ssh_user_import_key(guac_common_ssh_user* user, + char* private_key, char* passphrase); + +#endif +