summaryrefslogtreecommitdiffstats
path: root/libssh/src/kex.c
diff options
context:
space:
mode:
Diffstat (limited to 'libssh/src/kex.c')
-rw-r--r--libssh/src/kex.c316
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;