From c3ac160017ccf902426f6a2126feb4d3d0f6268c Mon Sep 17 00:00:00 2001 From: Yanis Kurganov Date: Fri, 23 Jan 2015 16:34:51 +0300 Subject: Diffie-Hellman Group Exchange Signed-off-by: Yanis Kurganov --- include/libssh/crypto.h | 8 +- include/libssh/dh.h | 11 +- include/libssh/misc.h | 1 + include/libssh/packet.h | 3 + include/libssh/session.h | 1 + src/client.c | 15 +- src/dh.c | 367 +++++++++++++++++++++++++++++++++-------------- src/kex.c | 6 +- src/misc.c | 30 ++++ src/packet.c | 14 +- src/packet_cb.c | 37 ++++- src/server.c | 140 ++++++++++++++++-- src/wrapper.c | 2 + 13 files changed, 499 insertions(+), 136 deletions(-) diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 61a2b27b..b2980a13 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -53,6 +53,10 @@ enum ssh_key_exchange_e { SSH_KEX_DH_GROUP1_SHA1=1, /* diffie-hellman-group14-sha1 */ SSH_KEX_DH_GROUP14_SHA1, + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GROUP_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GROUP_SHA256, /* ecdh-sha2-nistp256 */ SSH_KEX_ECDH_SHA2_NISTP256, /* curve25519-sha256@libssh.org */ @@ -60,7 +64,9 @@ enum ssh_key_exchange_e { }; struct ssh_crypto_struct { - bignum e,f,x,k,y; + bignum p,g,e,f,x,k,y; + unsigned int pmin,pbits,pmax; /* preferred size in bits of the group the server will send */ + int old_gex; /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD used */ #ifdef HAVE_ECDH EC_KEY *ecdh_privkey; ssh_string ecdh_client_pubkey; diff --git a/include/libssh/dh.h b/include/libssh/dh.h index 95b76cdd..4282078d 100644 --- a/include/libssh/dh.h +++ b/include/libssh/dh.h @@ -30,16 +30,23 @@ int dh_generate_f(ssh_session session); int dh_generate_x(ssh_session session); int dh_generate_y(ssh_session session); +int dh_generate_p_by_pbits(ssh_session session); +int dh_generate_p_by_kex_type(ssh_session session); +int dh_generate_g(ssh_session session); + int ssh_crypto_init(void); void ssh_crypto_finalize(void); -ssh_string dh_get_e(ssh_session session); ssh_string dh_get_f(ssh_session session); + int dh_import_f(ssh_session session,ssh_string f_string); int dh_import_e(ssh_session session, ssh_string e_string); void dh_import_pubkey(ssh_session session,ssh_string pubkey_string); int dh_build_k(ssh_session session); -int ssh_client_dh_init(ssh_session session); + +int ssh_client_dh_group_init(ssh_session session); +int ssh_client_dh_gex_init(ssh_session session); +int ssh_client_dh_gex_reply(ssh_session session, ssh_buffer packet); int ssh_client_dh_reply(ssh_session session, ssh_buffer packet); int make_sessionid(ssh_session session); diff --git a/include/libssh/misc.h b/include/libssh/misc.h index 1e69b069..7d1a524d 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -88,5 +88,6 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); int ssh_match_group(const char *group, const char *object); +int ssh_find_in_commasep_string(const char *in, const char *what); #endif /* MISC_H_ */ diff --git a/include/libssh/packet.h b/include/libssh/packet.h index d8ef35bb..21ec559a 100644 --- a/include/libssh/packet.h +++ b/include/libssh/packet.h @@ -60,11 +60,14 @@ SSH_PACKET_CALLBACK(ssh_packet_unimplemented); SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_dh_gex_reply); SSH_PACKET_CALLBACK(ssh_packet_newkeys); SSH_PACKET_CALLBACK(ssh_packet_service_accept); #ifdef WITH_SERVER SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +SSH_PACKET_CALLBACK(ssh_packet_kexdh_gex_init); +SSH_PACKET_CALLBACK(ssh_packet_kexdh_gex_request); #endif int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); diff --git a/include/libssh/session.h b/include/libssh/session.h index 60d78578..44c19a94 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -45,6 +45,7 @@ enum ssh_session_state_e { enum ssh_dh_state_e { DH_STATE_INIT=0, + DH_STATE_GEX_REQUEST_SENT, DH_STATE_INIT_SENT, DH_STATE_NEWKEYS_SENT, DH_STATE_FINISHED diff --git a/src/client.c b/src/client.c index 64ce5dec..4da8bd31 100644 --- a/src/client.c +++ b/src/client.c @@ -197,7 +197,7 @@ end: * completed */ static int dh_handshake(ssh_session session) { - + enum ssh_dh_state_e dh_handshake_state = DH_STATE_INIT_SENT; int rc = SSH_AGAIN; switch (session->dh_handshake_state) { @@ -205,7 +205,12 @@ static int dh_handshake(ssh_session session) { switch(session->next_crypto->kex_type){ case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: - rc = ssh_client_dh_init(session); + rc = ssh_client_dh_group_init(session); + break; + case SSH_KEX_DH_GROUP_SHA1: + case SSH_KEX_DH_GROUP_SHA256: + rc = ssh_client_dh_gex_init(session); + dh_handshake_state = DH_STATE_GEX_REQUEST_SENT; break; #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: @@ -225,7 +230,11 @@ static int dh_handshake(ssh_session session) { return SSH_ERROR; } - session->dh_handshake_state = DH_STATE_INIT_SENT; + session->dh_handshake_state = dh_handshake_state; + break; + case DH_STATE_GEX_REQUEST_SENT: + /* wait until ssh_packet_dh_reply is called */ + break; case DH_STATE_INIT_SENT: /* wait until ssh_packet_dh_reply is called */ break; diff --git a/src/dh.c b/src/dh.c index e489a1d5..e7e07e23 100644 --- a/src/dh.c +++ b/src/dh.c @@ -6,6 +6,7 @@ * Copyright (c) 2003-2013 by Aris Adamantiadis * Copyright (c) 2009-2013 by Andreas Schneider * Copyright (c) 2012 by Dmitriy Kuznetsov + * Copyright (c) 2014-2015 by Yanis Kurganov * * 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 @@ -82,8 +83,8 @@ static unsigned char p_group1_value[] = { 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; -#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ +#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ static unsigned char p_group14_value[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, @@ -111,15 +112,9 @@ static unsigned char p_group14_value[] = { #define P_GROUP14_LEN 256 /* Size in bytes of the p number for group 14 */ -static unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ -static bignum g; -static bignum p_group1; -static bignum p_group14; -static int ssh_crypto_initialized; +#define G_VALUE 2 /* G is defined as 2 by the ssh2 standards */ -static bignum select_p(enum ssh_key_exchange_e type) { - return type == SSH_KEX_DH_GROUP14_SHA1 ? p_group14 : p_group1; -} +static int ssh_crypto_initialized; int ssh_get_random(void *where, int len, int strong){ @@ -144,7 +139,6 @@ int ssh_get_random(void *where, int len, int strong){ /* - * This inits the values g and p which are used for DH key agreement * FIXME: Make the function thread safe by adding a semaphore or mutex. */ int ssh_crypto_init(void) { @@ -155,74 +149,23 @@ int ssh_crypto_init(void) { gcry_control(GCRYCTL_INIT_SECMEM, 4096); gcry_control(GCRYCTL_INITIALIZATION_FINISHED,0); } -#endif - - g = bignum_new(); - if (g == NULL) { - return -1; - } - bignum_set_word(g,g_int); - -#ifdef HAVE_LIBGCRYPT - bignum_bin2bn(p_group1_value, P_GROUP1_LEN, &p_group1); - if (p_group1 == NULL) { - bignum_free(g); - g = NULL; - return -1; - } - bignum_bin2bn(p_group14_value, P_GROUP14_LEN, &p_group14); - if (p_group14 == NULL) { - bignum_free(g); - bignum_free(p_group1); - g = NULL; - p_group1 = NULL; - return -1; - } - #elif defined HAVE_LIBCRYPTO - p_group1 = bignum_new(); - if (p_group1 == NULL) { - bignum_free(g); - g = NULL; - return -1; - } - bignum_bin2bn(p_group1_value, P_GROUP1_LEN, p_group1); - - p_group14 = bignum_new(); - if (p_group14 == NULL) { - bignum_free(g); - bignum_free(p_group1); - g = NULL; - p_group1 = NULL; - return -1; - } - bignum_bin2bn(p_group14_value, P_GROUP14_LEN, p_group14); - OpenSSL_add_all_algorithms(); - #endif - ssh_crypto_initialized = 1; } - return 0; } void ssh_crypto_finalize(void) { if (ssh_crypto_initialized) { - bignum_free(g); - g = NULL; - bignum_free(p_group1); - p_group1 = NULL; - bignum_free(p_group14); - p_group14 = NULL; #ifdef HAVE_LIBGCRYPT gcry_control(GCRYCTL_TERM_SECMEM); #elif defined HAVE_LIBCRYPTO EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); #endif - ssh_crypto_initialized=0; + ssh_crypto_initialized = 0; } } @@ -285,11 +228,11 @@ int dh_generate_e(ssh_session session) { } #ifdef HAVE_LIBGCRYPT - bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, - select_p(session->next_crypto->kex_type)); + bignum_mod_exp(session->next_crypto->e, session->next_crypto->g, + session->next_crypto->x, session->next_crypto->p); #elif defined HAVE_LIBCRYPTO - bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, - select_p(session->next_crypto->kex_type), ctx); + bignum_mod_exp(session->next_crypto->e, session->next_crypto->g, + session->next_crypto->x, session->next_crypto->p, ctx); #endif #ifdef DEBUG_CRYPTO @@ -320,11 +263,11 @@ int dh_generate_f(ssh_session session) { } #ifdef HAVE_LIBGCRYPT - bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, - select_p(session->next_crypto->kex_type)); + bignum_mod_exp(session->next_crypto->f, session->next_crypto->g, + session->next_crypto->y, session->next_crypto->p); #elif defined HAVE_LIBCRYPTO - bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, - select_p(session->next_crypto->kex_type), ctx); + bignum_mod_exp(session->next_crypto->f, session->next_crypto->g, + session->next_crypto->y, session->next_crypto->p, ctx); #endif #ifdef DEBUG_CRYPTO @@ -338,10 +281,6 @@ int dh_generate_f(ssh_session session) { return 0; } -ssh_string dh_get_e(ssh_session session) { - return make_bignum_string(session->next_crypto->e); -} - /* used by server */ ssh_string dh_get_f(ssh_session session) { return make_bignum_string(session->next_crypto->f); @@ -378,6 +317,85 @@ int dh_import_e(ssh_session session, ssh_string e_string) { return 0; } +/* p number */ +static int dh_import_p(ssh_session session, ssh_string p_string) +{ + session->next_crypto->p = make_string_bn(p_string); + if (session->next_crypto->p == NULL) { + return SSH_ERROR; + } +#ifdef DEBUG_CRYPTO + ssh_print_bignum("p",session->next_crypto->p); +#endif + return SSH_OK; +} + +static int dh_generate_p(ssh_session session, int use_group14) +{ + const unsigned char* p_value; + size_t p_size; + ssh_string p_string; + int rc; + + p_value = use_group14 ? p_group14_value : p_group1_value; + p_size = use_group14 ? P_GROUP14_LEN : P_GROUP1_LEN; + + p_string = ssh_string_new(p_size); + if (p_string == NULL) { + return SSH_ERROR; + } + + ssh_string_fill(p_string, p_value, p_size); + rc = dh_import_p(session, p_string); + + ssh_string_burn(p_string); + ssh_string_free(p_string); + + return rc; +} + +int dh_generate_p_by_pbits(ssh_session session) +{ + unsigned int pbytes; + int use_group14; + pbytes = session->next_crypto->pbits / 8; + use_group14 = pbytes >= P_GROUP14_LEN ? 1 : 0; + return dh_generate_p(session, use_group14); +} + +int dh_generate_p_by_kex_type(ssh_session session) +{ + int use_group14; + use_group14 = session->next_crypto->kex_type == SSH_KEX_DH_GROUP14_SHA1 ? 1 : 0; + return dh_generate_p(session, use_group14); +} + +/* g number */ +static int dh_import_g(ssh_session session, ssh_string g_string) +{ + session->next_crypto->g = make_string_bn(g_string); + if (session->next_crypto->g == NULL) { + return SSH_ERROR; + } +#ifdef DEBUG_CRYPTO + ssh_print_bignum("g",session->next_crypto->g); +#endif + return SSH_OK; +} + +int dh_generate_g(ssh_session session) +{ + session->next_crypto->g = bignum_new(); + if (session->next_crypto->g == NULL) { + return SSH_ERROR; + } + bignum_set_word(session->next_crypto->g, G_VALUE); +#ifdef DEBUG_CRYPTO + ssh_print_bignum("g",session->next_crypto->g); +#endif + return SSH_OK; +} + int dh_build_k(ssh_session session) { #ifdef HAVE_LIBCRYPTO bignum_CTX ctx = bignum_ctx_new(); @@ -398,18 +416,18 @@ int dh_build_k(ssh_session session) { #ifdef HAVE_LIBGCRYPT if(session->client) { bignum_mod_exp(session->next_crypto->k, session->next_crypto->f, - session->next_crypto->x, select_p(session->next_crypto->kex_type)); + session->next_crypto->x, session->next_crypto->p); } else { bignum_mod_exp(session->next_crypto->k, session->next_crypto->e, - session->next_crypto->y, select_p(session->next_crypto->kex_type)); + session->next_crypto->y, session->next_crypto->p); } #elif defined HAVE_LIBCRYPTO if (session->client) { bignum_mod_exp(session->next_crypto->k, session->next_crypto->f, - session->next_crypto->x, select_p(session->next_crypto->kex_type), ctx); + session->next_crypto->x, session->next_crypto->p, ctx); } else { bignum_mod_exp(session->next_crypto->k, session->next_crypto->e, - session->next_crypto->y, select_p(session->next_crypto->kex_type), ctx); + session->next_crypto->y, session->next_crypto->p, ctx); } #endif @@ -428,43 +446,147 @@ int dh_build_k(ssh_session session) { return 0; } -/** @internal - * @brief Starts diffie-hellman-group1 key exchange - */ -int ssh_client_dh_init(ssh_session session){ - ssh_string e = NULL; - int rc; +static int init_pbits(ssh_session session) +{ + struct ssh_kex_struct* kex = session->server ? + &session->next_crypto->client_kex : + &session->next_crypto->server_kex; + struct ssh_cipher_struct* ciphertab = ssh_get_ciphertab(); + struct ssh_cipher_struct* cs_cipher = NULL; + struct ssh_cipher_struct* sc_cipher = NULL; + unsigned int nbits, hlen = session->next_crypto->kex_type == + SSH_KEX_DH_GROUP_SHA256 ? SHA256_DIGEST_LENGTH : SHA_DIGEST_LENGTH; + int found; + size_t i; + + for (i = 0; ciphertab[i].name; ++i) { + if (cs_cipher == NULL) { + found = ssh_find_in_commasep_string(kex->methods[SSH_CRYPT_C_S], ciphertab[i].name); + if (found) { + cs_cipher = &ciphertab[i]; + } + } + if (sc_cipher == NULL) { + found = ssh_find_in_commasep_string(kex->methods[SSH_CRYPT_S_C], ciphertab[i].name); + if (found) { + sc_cipher = &ciphertab[i]; + } + } + } - if (dh_generate_x(session) < 0) { - goto error; - } - if (dh_generate_e(session) < 0) { - goto error; - } + if (cs_cipher == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't agree a client-to-server cipher (available: %s)", + kex->methods[SSH_CRYPT_C_S]); + return SSH_ERROR; + } + if (sc_cipher == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't agree a server-to-client cipher (available: %s)", + kex->methods[SSH_CRYPT_S_C]); + return SSH_ERROR; + } - e = dh_get_e(session); - if (e == NULL) { - goto error; - } + /* Start with the maximum key length of either cipher */ + nbits = cs_cipher->keylen > sc_cipher->keylen ? cs_cipher->keylen : sc_cipher->keylen; - rc = ssh_buffer_pack(session->out_buffer, "bS", SSH2_MSG_KEXDH_INIT, e); - if (rc != SSH_OK) { - goto error; - } + /* The keys are based on a hash. So cap the key size at hlen bits */ + if (nbits > hlen * 8) { + nbits = hlen * 8; + } - ssh_string_burn(e); - ssh_string_free(e); - e=NULL; + /* DH group size */ + session->next_crypto->pbits = 512 << ((nbits - 1) / 64); + session->next_crypto->old_gex = 1; - rc = packet_send(session); - return rc; - error: - if(e != NULL){ - ssh_string_burn(e); - ssh_string_free(e); - } + return SSH_OK; +} - return SSH_ERROR; +/** @internal + * @brief Starts diffie-hellman key exchange + */ +static int ssh_client_dh_init(ssh_session session, int type) +{ + int rc; + rc = dh_generate_x(session); + if (rc < 0) { + return SSH_ERROR; + } + rc = dh_generate_e(session); + if (rc < 0) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, "bB", type, session->next_crypto->e); + if (rc != SSH_OK) { + return SSH_ERROR; + } + return packet_send(session); +} + +int ssh_client_dh_group_init(ssh_session session) +{ + int rc; + rc = dh_generate_p_by_kex_type(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot create p number"); + return SSH_ERROR; + } + rc = dh_generate_g(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot create g number"); + return SSH_ERROR; + } + return ssh_client_dh_init(session, SSH2_MSG_KEXDH_INIT); +} + +int ssh_client_dh_gex_init(ssh_session session) +{ + int rc; + rc = init_pbits(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot init pbits"); + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, "bd", + SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, + session->next_crypto->pbits); + if (rc != SSH_OK) { + return SSH_ERROR; + } + rc = packet_send(session); + SSH_LOG(SSH_LOG_PROTOCOL, "SSH2_MSG_KEX_DH_GEX_REQUEST_OLD sent"); + return rc; +} + +int ssh_client_dh_gex_reply(ssh_session session, ssh_buffer packet) +{ + ssh_string num; + int rc; + num = buffer_get_ssh_string(packet); + if (num == NULL) { + ssh_set_error(session,SSH_FATAL, "No p number in packet"); + return SSH_ERROR; + } + rc = dh_import_p(session, num); + ssh_string_burn(num); + ssh_string_free(num); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot import p number"); + return SSH_ERROR; + } + num = buffer_get_ssh_string(packet); + if (num == NULL) { + ssh_set_error(session,SSH_FATAL, "No g number in packet"); + return SSH_ERROR; + } + rc = dh_import_g(session, num); + ssh_string_burn(num); + ssh_string_free(num); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot import g number"); + return SSH_ERROR; + } + return ssh_client_dh_init(session, SSH2_MSG_KEX_DH_GEX_INIT); } int ssh_client_dh_reply(ssh_session session, ssh_buffer packet){ @@ -594,7 +716,30 @@ int make_sessionid(ssh_session session) { if (rc != SSH_OK) { goto error; } - + } else if (session->next_crypto->kex_type == SSH_KEX_DH_GROUP_SHA1 || + session->next_crypto->kex_type == SSH_KEX_DH_GROUP_SHA256) { + if (session->next_crypto->old_gex) { + rc = ssh_buffer_pack(buf, + "dBBBB", + session->next_crypto->pbits, + session->next_crypto->p, + session->next_crypto->g, + session->next_crypto->e, + session->next_crypto->f); + } else { + rc = ssh_buffer_pack(buf, + "dddBBBB", + session->next_crypto->pmin, + session->next_crypto->pbits, + session->next_crypto->pmax, + session->next_crypto->p, + session->next_crypto->g, + session->next_crypto->e, + session->next_crypto->f); + } + if (rc != SSH_OK) { + goto error; + } #ifdef HAVE_ECDH } else if (session->next_crypto->kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { if (session->next_crypto->ecdh_client_pubkey == NULL || @@ -636,6 +781,7 @@ int make_sessionid(ssh_session session) { switch (session->next_crypto->kex_type) { case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP_SHA1: session->next_crypto->digest_len = SHA_DIGEST_LENGTH; session->next_crypto->mac_type = SSH_MAC_SHA1; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); @@ -646,6 +792,7 @@ int make_sessionid(ssh_session session) { sha1(buffer_get_rest(buf), buffer_get_rest_len(buf), session->next_crypto->secret_hash); break; + case SSH_KEX_DH_GROUP_SHA256: case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; diff --git a/src/kex.c b/src/kex.c index 2e963bbb..f59f69c8 100644 --- a/src/kex.c +++ b/src/kex.c @@ -85,7 +85,7 @@ #define ECDH "" #endif -#define KEY_EXCHANGE CURVE25519 ECDH "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" +#define KEY_EXCHANGE CURVE25519 ECDH "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1" #define KEX_METHODS_SIZE 10 /* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ @@ -587,6 +587,10 @@ int ssh_kex_select_methods (ssh_session session){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; + } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ + session->next_crypto->kex_type=SSH_KEX_DH_GROUP_SHA1; + } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha256") == 0){ + session->next_crypto->kex_type=SSH_KEX_DH_GROUP_SHA256; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256@libssh.org") == 0){ diff --git a/src/misc.c b/src/misc.c index 64cda47b..dbbd09c5 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1031,6 +1031,36 @@ int ssh_match_group(const char *group, const char *object) return 0; } +int ssh_find_in_commasep_string(const char *in, const char *what) +{ + int in_len, what_len; + + if ((in == NULL) || (what == NULL)) { + return 0; /* don't deal with null args */ + } + + in_len = strlen(in); + what_len = strlen(what); + + while (1) { + /* Is it at the start of the string? */ + if (in_len >= what_len && /* haystack is long enough */ + !memcmp(what, in, what_len) && /* initial match */ + (in_len == what_len || in[what_len] == ',')) { + return 1; + } + /* If not, search for the next comma and resume after that. */ + /* If no comma found, terminate. */ + while (in_len > 0 && *in != ',') { + --in_len, ++in; + } + if (in_len == 0) { + return 0; + } + --in_len, ++in; /* skip over comma itself */ + } +} + /** @} */ /* vim: set ts=4 sw=4 et cindent: */ diff --git a/src/packet.c b/src/packet.c index d16cd165..470ebba9 100644 --- a/src/packet.c +++ b/src/packet.c @@ -73,9 +73,17 @@ static ssh_packet_callback default_packet_handlers[]= { #endif ssh_packet_dh_reply, // SSH2_MSG_KEXDH_REPLY 31 // SSH2_MSG_KEX_DH_GEX_GROUP 31 - NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32 - NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33 - NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#if WITH_SERVER + ssh_packet_kexdh_gex_init, // SSH2_MSG_KEX_DH_GEX_INIT 32 +#else + NULL, +#endif + ssh_packet_dh_gex_reply, // SSH2_MSG_KEX_DH_GEX_REPLY 33 +#if WITH_SERVER + ssh_packet_kexdh_gex_request, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#else + NULL, +#endif NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 35-49 diff --git a/src/packet_cb.c b/src/packet_cb.c index 4fe6402a..0024b72a 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -93,12 +93,14 @@ SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ } SSH_PACKET_CALLBACK(ssh_packet_dh_reply){ + enum ssh_dh_state_e dh_handshake_state = DH_STATE_NEWKEYS_SENT; int rc; (void)type; (void)user; - SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH_KEXDH_REPLY"); + SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH_MSG_KEXDH_REPLY"); if (session->session_state != SSH_SESSION_STATE_DH || - session->dh_handshake_state != DH_STATE_INIT_SENT){ + (session->dh_handshake_state != DH_STATE_GEX_REQUEST_SENT || + session->dh_handshake_state != DH_STATE_INIT_SENT)){ ssh_set_error(session,SSH_FATAL,"ssh_packet_dh_reply called in wrong state : %d:%d", session->session_state,session->dh_handshake_state); goto error; @@ -106,7 +108,12 @@ SSH_PACKET_CALLBACK(ssh_packet_dh_reply){ switch(session->next_crypto->kex_type){ case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: - rc=ssh_client_dh_reply(session, packet); + rc = ssh_client_dh_reply(session, packet); + break; + case SSH_KEX_DH_GROUP_SHA1: + case SSH_KEX_DH_GROUP_SHA256: + rc = ssh_client_dh_gex_reply(session, packet); + dh_handshake_state = DH_STATE_INIT_SENT; break; #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: @@ -123,7 +130,7 @@ SSH_PACKET_CALLBACK(ssh_packet_dh_reply){ goto error; } if(rc==SSH_OK) { - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + session->dh_handshake_state = dh_handshake_state; return SSH_PACKET_USED; } error: @@ -131,6 +138,28 @@ error: return SSH_PACKET_USED; } +SSH_PACKET_CALLBACK(ssh_packet_dh_gex_reply) +{ + int rc; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH2_MSG_KEX_DH_GEX_REPLY"); + if (session->session_state!= SSH_SESSION_STATE_DH && + session->dh_handshake_state != DH_STATE_INIT_SENT) { + ssh_set_error(session,SSH_FATAL,"ssh_packet_dh_gex_reply called in wrong state : %d:%d", + session->session_state,session->dh_handshake_state); + goto error; + } + rc = ssh_client_dh_reply(session, packet); + if (rc == SSH_OK) { + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + } +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + SSH_PACKET_CALLBACK(ssh_packet_newkeys){ ssh_string sig_blob = NULL; int rc; diff --git a/src/server.c b/src/server.c index 6a8a3fbe..9d4e2cb0 100644 --- a/src/server.c +++ b/src/server.c @@ -4,6 +4,7 @@ * This file is part of the SSH Library * * Copyright (c) 2004-2013 by Aris Adamantiadis + * Copyright (c) 2014-2015 by Yanis Kurganov * * 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 @@ -65,7 +66,7 @@ session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ } while (0) -static int dh_handshake_server(ssh_session session); +static int dh_handshake_server(ssh_session session, int reply_type); /** @@ -149,11 +150,8 @@ static int server_set_kex(ssh_session session) { return 0; } -/** @internal - * @brief parse an incoming SSH_MSG_KEXDH_INIT packet and complete - * key exchange - **/ -static int ssh_server_kexdh_init(ssh_session session, ssh_buffer packet){ +static int ssh_server_kexdh_init(ssh_session session, ssh_buffer packet, int reply_type) +{ ssh_string e; e = buffer_get_ssh_string(packet); if (e == NULL) { @@ -165,12 +163,93 @@ static int ssh_server_kexdh_init(ssh_session session, ssh_buffer packet){ session->session_state=SSH_SESSION_STATE_ERROR; } else { session->dh_handshake_state=DH_STATE_INIT_SENT; - dh_handshake_server(session); + dh_handshake_server(session, reply_type); } ssh_string_free(e); return SSH_OK; } +static int ssh_server_kexdh_group_init(ssh_session session, ssh_buffer packet) +{ + int rc; + rc = dh_generate_p_by_kex_type(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create p number"); + return SSH_ERROR; + } + rc = dh_generate_g(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create g number"); + return SSH_ERROR; + } + return ssh_server_kexdh_init(session, packet, SSH2_MSG_KEXDH_REPLY); +} + +static int ssh_server_kexdh_gex_send_group(ssh_session session) +{ + int rc; + SSH_LOG(SSH_LOG_PROTOCOL, + "Preferred size of the group in a client request: %u bits", + session->next_crypto->pbits); + rc = dh_generate_p_by_pbits(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create p number"); + return SSH_ERROR; + } + rc = dh_generate_g(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create g number"); + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, + "bBB", + SSH2_MSG_KEX_DH_GEX_GROUP, + session->next_crypto->p, + session->next_crypto->g); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; + } + rc = packet_send(session); + if (rc != SSH_OK) { + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "SSH2_MSG_KEX_DH_GEX_GROUP sent"); + session->dh_handshake_state = DH_STATE_GEX_REQUEST_SENT; + return SSH_OK; +} + +static int ssh_server_kexdh_gex_old_init(ssh_session session, ssh_buffer packet) +{ + uint32_t pbits; + int rc; + rc = ssh_buffer_unpack(packet, "d", &pbits); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "No n in client request"); + return SSH_ERROR; + } + session->next_crypto->pbits = pbits; + session->next_crypto->old_gex = 1; + return ssh_server_kexdh_gex_send_group(session); +} + +static int ssh_server_kexdh_gex_new_init(ssh_session session, ssh_buffer packet) +{ + uint32_t pmin, pbits, pmax; + int rc; + rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pbits, &pmax); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "No min|n|max in client request"); + return SSH_ERROR; + } + session->next_crypto->pmin = pmin; + session->next_crypto->pbits = pbits; + session->next_crypto->pmax = pmax; + session->next_crypto->old_gex = 0; + return ssh_server_kexdh_gex_send_group(session); +} + SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ int rc = SSH_ERROR; (void)type; @@ -194,7 +273,12 @@ SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ switch(session->next_crypto->kex_type){ case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: - rc=ssh_server_kexdh_init(session, packet); + rc=ssh_server_kexdh_group_init(session, packet); + break; + case SSH_KEX_DH_GROUP_SHA1: + case SSH_KEX_DH_GROUP_SHA256: + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEX_DH_GEX_REQUEST_OLD"); + rc=ssh_server_kexdh_gex_old_init(session, packet); break; #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: @@ -210,15 +294,47 @@ SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ ssh_set_error(session,SSH_FATAL,"Wrong kex type in ssh_packet_kexdh_init"); rc = SSH_ERROR; } - error: if (rc == SSH_ERROR) { session->session_state = SSH_SESSION_STATE_ERROR; } - return SSH_PACKET_USED; } +SSH_PACKET_CALLBACK(ssh_packet_kexdh_gex_init){ + int rc; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEX_DH_GEX_INIT"); + if (session->dh_handshake_state != DH_STATE_GEX_REQUEST_SENT) { + SSH_LOG(SSH_LOG_RARE, "Invalid state for SSH_MSG_KEX_DH_GEX_INIT"); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + rc = ssh_server_kexdh_init(session, packet, SSH2_MSG_KEX_DH_GEX_REPLY); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexdh_gex_request){ + int rc; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEX_DH_GEX_REQUEST"); + if (session->dh_handshake_state != DH_STATE_INIT) { + SSH_LOG(SSH_LOG_RARE,"Invalid state for SSH_MSG_KEX_DH_GEX_REQUEST"); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + rc = ssh_server_kexdh_gex_new_init(session, packet); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + int ssh_get_key_params(ssh_session session, ssh_key *privkey){ ssh_key pubkey; ssh_string pubkey_blob; @@ -262,7 +378,7 @@ int ssh_get_key_params(ssh_session session, ssh_key *privkey){ return SSH_OK; } -static int dh_handshake_server(ssh_session session) { +static int dh_handshake_server(ssh_session session, int reply_type) { ssh_key privkey; ssh_string sig_blob; ssh_string f; @@ -309,7 +425,7 @@ static int dh_handshake_server(ssh_session session) { rc = ssh_buffer_pack(session->out_buffer, "bSSS", - SSH2_MSG_KEXDH_REPLY, + reply_type, session->next_crypto->server_pubkey, f, sig_blob); diff --git a/src/wrapper.c b/src/wrapper.c index c1dd4d03..15e251ac 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -152,6 +152,8 @@ void crypto_free(struct ssh_crypto_struct *crypto){ cipher_free(crypto->in_cipher); cipher_free(crypto->out_cipher); + bignum_free(crypto->p); + bignum_free(crypto->g); bignum_free(crypto->e); bignum_free(crypto->f); bignum_free(crypto->x); -- cgit v1.2.3