aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/libssh/crypto.h8
-rw-r--r--include/libssh/dh.h11
-rw-r--r--include/libssh/misc.h1
-rw-r--r--include/libssh/packet.h3
-rw-r--r--include/libssh/session.h1
-rw-r--r--src/client.c15
-rw-r--r--src/dh.c367
-rw-r--r--src/kex.c6
-rw-r--r--src/misc.c30
-rw-r--r--src/packet.c14
-rw-r--r--src/packet_cb.c37
-rw-r--r--src/server.c140
-rw-r--r--src/wrapper.c2
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 <asn@cryptomilk.org>
* Copyright (c) 2012 by Dmitriy Kuznetsov <dk@yandex.ru>
+ * Copyright (c) 2014-2015 by Yanis Kurganov <yanis.kurganov@gmail.com>
*
* 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 <yanis.kurganov@gmail.com>
*
* 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);