diff options
Diffstat (limited to 'libssh/src/kex.c')
-rw-r--r-- | libssh/src/kex.c | 316 |
1 files changed, 233 insertions, 83 deletions
diff --git a/libssh/src/kex.c b/libssh/src/kex.c index 7cd404f7..f1a1b566 100644 --- a/libssh/src/kex.c +++ b/libssh/src/kex.c @@ -35,6 +35,7 @@ #include "libssh/ssh2.h" #include "libssh/string.h" #include "libssh/curve25519.h" +#include "libssh/knownhosts.h" #ifdef HAVE_LIBGCRYPT # define BLOWFISH "blowfish-cbc," @@ -72,7 +73,7 @@ #ifdef HAVE_ECDH #define ECDH "ecdh-sha2-nistp256," -#define HOSTKEYS "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss" +#define HOSTKEYS "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,ssh-dss" #else #define HOSTKEYS "ssh-rsa,ssh-dss" #define ECDH "" @@ -87,8 +88,8 @@ static const char *default_methods[] = { HOSTKEYS, AES BLOWFISH DES, AES BLOWFISH DES, - "hmac-sha1", - "hmac-sha1", + "hmac-sha1,hmac-sha2-256,hmac-sha2-512", + "hmac-sha1,hmac-sha2-256,hmac-sha2-512", "none", "none", "", @@ -102,8 +103,8 @@ static const char *supported_methods[] = { HOSTKEYS, AES BLOWFISH DES, AES BLOWFISH DES, - "hmac-sha1", - "hmac-sha1", + "hmac-sha1,hmac-sha2-256,hmac-sha2-512", + "hmac-sha1,hmac-sha2-256,hmac-sha2-512", ZLIB, ZLIB, "", @@ -274,87 +275,180 @@ char *ssh_find_matching(const char *available_d, const char *preferred_d){ return NULL; } -SSH_PACKET_CALLBACK(ssh_packet_kexinit){ - int server_kex=session->server; - ssh_string str = NULL; - char *strings[KEX_METHODS_SIZE]; - int i; +/** + * @internal + * @brief returns whether the first client key exchange algorithm matches + * the first server key exchange algorithm + * @returns whether the first client key exchange algorithm matches + * the first server key exchange algorithm + */ +static int is_first_kex_packet_follows_guess_wrong(const char *client_kex, + const char *server_kex) { + int is_wrong = 1; + char **server_kex_tokens = NULL; + char **client_kex_tokens = NULL; + + if ((client_kex == NULL) || (server_kex == NULL)) { + goto out; + } - (void)type; - (void)user; - memset(strings, 0, sizeof(strings)); - if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED){ - SSH_LOG(SSH_LOG_WARNING, "Other side initiating key re-exchange"); - } else if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){ - ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); - goto error; - } - if (server_kex) { - if (buffer_get_data(packet,session->next_crypto->client_kex.cookie,16) != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); - goto error; - } + client_kex_tokens = tokenize(client_kex); - if (hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie) < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); - goto error; - } - } else { - if (buffer_get_data(packet,session->next_crypto->server_kex.cookie,16) != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); - goto error; - } + if (client_kex_tokens == NULL) { + goto out; + } - if (hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie) < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); - goto error; - } - } + if (client_kex_tokens[0] == NULL) { + goto freeout; + } - for (i = 0; i < KEX_METHODS_SIZE; i++) { - str = buffer_get_ssh_string(packet); - if (str == NULL) { - break; + server_kex_tokens = tokenize(server_kex); + if (server_kex_tokens == NULL) { + goto freeout; } - if (buffer_add_ssh_string(session->in_hashbuf, str) < 0) { - ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); - goto error; + is_wrong = (strcmp(client_kex_tokens[0], server_kex_tokens[0]) != 0); + + SAFE_FREE(server_kex_tokens[0]); + SAFE_FREE(server_kex_tokens); +freeout: + SAFE_FREE(client_kex_tokens[0]); + SAFE_FREE(client_kex_tokens); +out: + return is_wrong; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexinit){ + int i; + int server_kex=session->server; + ssh_string str = NULL; + char *strings[KEX_METHODS_SIZE]; + int rc = SSH_ERROR; + + uint8_t first_kex_packet_follows = 0; + uint32_t kexinit_reserved = 0; + + (void)type; + (void)user; + + memset(strings, 0, sizeof(strings)); + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED){ + SSH_LOG(SSH_LOG_WARNING, "Other side initiating key re-exchange"); + } else if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){ + ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); + goto error; } - strings[i] = ssh_string_to_char(str); - if (strings[i] == NULL) { - ssh_set_error_oom(session); - goto error; + if (server_kex) { + rc = buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); + if (rc != 16) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + rc = hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + } else { + rc = buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); + if (rc != 16) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + rc = hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } } - ssh_string_free(str); - str = NULL; - } - /* copy the server kex info into an array of strings */ - if (server_kex) { - for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->client_kex.methods[i] = strings[i]; + for (i = 0; i < KEX_METHODS_SIZE; i++) { + str = buffer_get_ssh_string(packet); + if (str == NULL) { + break; + } + + rc = buffer_add_ssh_string(session->in_hashbuf, str); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); + goto error; + } + + strings[i] = ssh_string_to_char(str); + if (strings[i] == NULL) { + ssh_set_error_oom(session); + goto error; + } + ssh_string_free(str); + str = NULL; } - } else { /* client */ - for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->server_kex.methods[i] = strings[i]; + + /* copy the server kex info into an array of strings */ + if (server_kex) { + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->client_kex.methods[i] = strings[i]; + } + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->server_kex.methods[i] = strings[i]; + } + } + + /* + * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): + * + * boolean first_kex_packet_follows + * uint32 0 (reserved for future extension) + * + * Notably if clients set 'first_kex_packet_follows', it is expected + * that its value is included when computing the session ID (see + * 'make_sessionid'). + */ + if (server_kex) { + rc = buffer_get_u8(packet, &first_kex_packet_follows); + if (rc != 1) { + goto error; + } + + rc = buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + + rc = buffer_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + + /* + * Remember whether 'first_kex_packet_follows' was set and the client + * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message + * must be ignored. + */ + if (first_kex_packet_follows) { + session->first_kex_follows_guess_wrong = + is_first_kex_packet_follows_guess_wrong(session->next_crypto->client_kex.methods[SSH_KEX], + session->next_crypto->server_kex.methods[SSH_KEX]); + } } - } - session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED; - session->dh_handshake_state=DH_STATE_INIT; - session->ssh_connection_callback(session); - return SSH_PACKET_USED; + session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; + session->dh_handshake_state = DH_STATE_INIT; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + error: - ssh_string_free(str); - for (i = 0; i < SSH_KEX_METHODS; i++) { - SAFE_FREE(strings[i]); - } + ssh_string_free(str); + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(strings[i]); + } - session->session_state = SSH_SESSION_STATE_ERROR; + session->session_state = SSH_SESSION_STATE_ERROR; - return SSH_PACKET_USED; + return SSH_PACKET_USED; } void ssh_list_kex(struct ssh_kex_struct *kex) { @@ -374,6 +468,53 @@ void ssh_list_kex(struct ssh_kex_struct *kex) { } /** + * @internal + * @brief selects the hostkey mechanisms to be chosen for the key exchange, + * as some hostkey mechanisms may be present in known_hosts file and preferred + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ +static char *ssh_client_select_hostkeys(ssh_session session){ + char methods_buffer[128]={0}; + static const char *preferred_hostkeys[]={"ecdsa-sha2-nistp521","ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp256", "ssh-rsa", "ssh-dss", "ssh-rsa1", NULL}; + char **methods; + int i,j; + int needcoma=0; + + methods = ssh_knownhosts_algorithms(session); + if (methods == NULL || methods[0] == NULL){ + SAFE_FREE(methods); + return NULL; + } + + for (i=0;preferred_hostkeys[i] != NULL; ++i){ + for (j=0; methods[j] != NULL; ++j){ + if(strcmp(preferred_hostkeys[i], methods[j]) == 0){ + if (verify_existing_algo(SSH_HOSTKEYS, methods[j])){ + if(needcoma) + strncat(methods_buffer,",",sizeof(methods_buffer)-strlen(methods_buffer)-1); + strncat(methods_buffer, methods[j], sizeof(methods_buffer)-strlen(methods_buffer)-1); + needcoma = 1; + } + } + } + } + for(i=0;methods[i]!= NULL; ++i){ + SAFE_FREE(methods[i]); + } + SAFE_FREE(methods); + + if(strlen(methods_buffer) > 0){ + SSH_LOG(SSH_LOG_DEBUG, "Changing host key method to \"%s\"", methods_buffer); + return strdup(methods_buffer); + } else { + SSH_LOG(SSH_LOG_DEBUG, "No supported kex method for existing key in known_hosts file"); + return NULL; + } + +} +/** * @brief sets the key exchange parameters to be sent to the server, * in function of the options and available methods. */ @@ -385,6 +526,13 @@ int set_client_kex(ssh_session session){ ssh_get_random(client->cookie, 16, 0); memset(client->methods, 0, KEX_METHODS_SIZE * sizeof(char **)); + /* first check if we have specific host key methods */ + if(session->opts.wanted_methods[SSH_HOSTKEYS] == NULL){ + /* Only if no override */ + session->opts.wanted_methods[SSH_HOSTKEYS] = + ssh_client_select_hostkeys(session); + } + for (i = 0; i < KEX_METHODS_SIZE; i++) { wanted = session->opts.wanted_methods[i]; if (wanted == NULL) @@ -434,14 +582,15 @@ int ssh_send_kex(ssh_session session, int server_kex) { &session->next_crypto->client_kex); ssh_string str = NULL; int i; - - if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXINIT) < 0) { - goto error; - } - if (buffer_add_data(session->out_buffer, kex->cookie, 16) < 0) { + int rc; + + rc = ssh_buffer_pack(session->out_buffer, + "bP", + SSH2_MSG_KEXINIT, + 16, + kex->cookie); /* cookie */ + if (rc != SSH_OK) goto error; - } - if (hashbufout_add_cookie(session) < 0) { goto error; } @@ -464,10 +613,11 @@ int ssh_send_kex(ssh_session session, int server_kex) { str = NULL; } - if (buffer_add_u8(session->out_buffer, 0) < 0) { - goto error; - } - if (buffer_add_u32(session->out_buffer, 0) < 0) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + 0, + 0); + if (rc != SSH_OK) { goto error; } @@ -477,8 +627,8 @@ int ssh_send_kex(ssh_session session, int server_kex) { return 0; error: - buffer_reinit(session->out_buffer); - buffer_reinit(session->out_hashbuf); + ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_hashbuf); ssh_string_free(str); return -1; |