/* * kex.c - key exchange * * This file is part of the SSH Library * * Copyright (c) 2003-2008 by Aris Adamantiadis * * The SSH Library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * The SSH Library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SSH Library; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ #include "config.h" #include #include #include #include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/dh.h" #include "libssh/kex.h" #include "libssh/session.h" #include "libssh/ssh2.h" #include "libssh/string.h" #ifdef HAVE_LIBGCRYPT #define BLOWFISH "blowfish-cbc," #define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," #define DES "3des-cbc" #elif defined HAVE_LIBCRYPTO #ifdef HAVE_OPENSSL_BLOWFISH_H #define BLOWFISH "blowfish-cbc," #else #define BLOWFISH "" #endif #ifdef HAVE_OPENSSL_AES_H #ifdef BROKEN_AES_CTR #define AES "aes256-cbc,aes192-cbc,aes128-cbc," #else #define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," #endif /* BROKEN_AES_CTR */ #else #define AES "" #endif #define DES "3des-cbc" #endif #ifdef WITH_ZLIB #define ZLIB "none,zlib,zlib@openssh.com" #else #define ZLIB "none" #endif #ifdef HAVE_ECDH #define KEY_EXCHANGE "ecdh-sha2-nistp256,diffie-hellman-group1-sha1" #define HOSTKEYS "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss" #else #define KEY_EXCHANGE "diffie-hellman-group1-sha1" #define HOSTKEYS "ssh-rsa,ssh-dss" #endif #define KEX_METHODS_SIZE 10 static const char *default_methods[] = { KEY_EXCHANGE, HOSTKEYS, AES BLOWFISH DES, AES BLOWFISH DES, "hmac-sha1", "hmac-sha1", "none", "none", "", "", NULL }; const char *supported_methods[] = { KEY_EXCHANGE, HOSTKEYS, AES BLOWFISH DES, AES BLOWFISH DES, "hmac-sha1", "hmac-sha1", ZLIB, ZLIB, "", "", NULL }; /* descriptions of the key exchange packet */ const char *ssh_kex_nums[] = { "kex algos", "server host key algo", "encryption client->server", "encryption server->client", "mac algo client->server", "mac algo server->client", "compression algo client->server", "compression algo server->client", "languages client->server", "languages server->client", NULL }; /* tokenize will return a token of strings delimited by ",". the first element has to be freed */ static char **tokenize(const char *chain){ char **tokens; int n=1; int i=0; char *tmp; char *ptr; tmp = strdup(chain); if (tmp == NULL) { return NULL; } ptr = tmp; while(*ptr){ if(*ptr==','){ n++; *ptr=0; } ptr++; } /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */ tokens=malloc(sizeof(char *) * (n+1) ); /* +1 for the null */ if (tokens == NULL) { SAFE_FREE(tmp); return NULL; } ptr=tmp; for(i=0;iserver; ssh_string str = NULL; char *strings[KEX_METHODS_SIZE]; int i; enter_function(); (void)type; (void)user; memset(strings, 0, sizeof(strings)); 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; } 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 (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; } } for (i = 0; i < KEX_METHODS_SIZE; i++) { str = buffer_get_ssh_string(packet); if (str == NULL) { break; } if (buffer_add_ssh_string(session->in_hashbuf, str) < 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; } /* 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]; } } leave_function(); session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED; 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]); } session->session_state = SSH_SESSION_STATE_ERROR; leave_function(); return SSH_PACKET_USED; } void ssh_list_kex(ssh_session session, struct ssh_kex_struct *kex) { int i = 0; #ifdef DEBUG_CRYPTO ssh_print_hexa("session cookie", kex->cookie, 16); #endif if(kex->methods==NULL){ ssh_log(session, SSH_LOG_RARE,"kex->methods is NULL"); return; } for(i = 0; i < SSH_KEX_METHODS; i++) { ssh_log(session, SSH_LOG_FUNCTIONS, "%s: %s", ssh_kex_nums[i], kex->methods[i]); } } /** * @brief sets the key exchange parameters to be sent to the server, * in function of the options and available methods. */ int set_client_kex(ssh_session session){ struct ssh_kex_struct *client= &session->next_crypto->client_kex; const char *wanted; int i; ssh_get_random(client->cookie, 16, 0); memset(client->methods, 0, KEX_METHODS_SIZE * sizeof(char **)); for (i = 0; i < KEX_METHODS_SIZE; i++) { wanted = session->opts.wanted_methods[i]; if (wanted == NULL) wanted = default_methods[i]; client->methods[i] = strdup(wanted); } return SSH_OK; } /** @brief Select the different methods on basis of client's and * server's kex messages, and watches out if a match is possible. */ int ssh_kex_select_methods (ssh_session session){ struct ssh_kex_struct *server = &session->next_crypto->server_kex; struct ssh_kex_struct *client = &session->next_crypto->client_kex; int rc = SSH_ERROR; int i; enter_function(); for (i = 0; i < KEX_METHODS_SIZE; i++) { session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); if(session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", ssh_kex_nums[i],server->methods[i],client->methods[i]); goto error; } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { /* we can safely do that for languages */ session->next_crypto->kex_methods[i] = strdup(""); } } if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; } rc = SSH_OK; error: leave_function(); return rc; } /* this function only sends the predefined set of kex methods */ int ssh_send_kex(ssh_session session, int server_kex) { struct ssh_kex_struct *kex = (server_kex ? &session->next_crypto->server_kex : &session->next_crypto->client_kex); ssh_string str = NULL; int i; enter_function(); if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXINIT) < 0) { goto error; } if (buffer_add_data(session->out_buffer, kex->cookie, 16) < 0) { goto error; } if (hashbufout_add_cookie(session) < 0) { goto error; } ssh_list_kex(session, kex); for (i = 0; i < KEX_METHODS_SIZE; i++) { str = ssh_string_from_char(kex->methods[i]); if (str == NULL) { goto error; } if (buffer_add_ssh_string(session->out_hashbuf, str) < 0) { goto error; } if (buffer_add_ssh_string(session->out_buffer, str) < 0) { goto error; } ssh_string_free(str); } if (buffer_add_u8(session->out_buffer, 0) < 0) { goto error; } if (buffer_add_u32(session->out_buffer, 0) < 0) { goto error; } if (packet_send(session) == SSH_ERROR) { leave_function(); return -1; } leave_function(); return 0; error: buffer_reinit(session->out_buffer); buffer_reinit(session->out_hashbuf); ssh_string_free(str); leave_function(); return -1; } /* returns 1 if at least one of the name algos is in the default algorithms table */ int verify_existing_algo(int algo, const char *name){ char *ptr; if(algo>9 || algo <0) return -1; ptr=ssh_find_matching(supported_methods[algo],name); if(ptr){ free(ptr); return 1; } return 0; } /* vim: set ts=2 sw=2 et cindent: */