diff options
author | Aris Adamantiadis <aris@0xbadc0de.be> | 2018-11-06 21:44:46 +0100 |
---|---|---|
committer | Andreas Schneider <asn@cryptomilk.org> | 2019-01-24 11:56:23 +0100 |
commit | 574bfb54595870033af2927c692af5f0afd3073c (patch) | |
tree | 05171d2347d2255618ec0dd66811b738088c86b4 | |
parent | 154eb9191408a63483481efeafa633d3b467da18 (diff) | |
download | libssh-574bfb54595870033af2927c692af5f0afd3073c.tar.gz libssh-574bfb54595870033af2927c692af5f0afd3073c.tar.xz libssh-574bfb54595870033af2927c692af5f0afd3073c.zip |
dh-gex: Add client implementation
Signed-off-by: Aris Adamantiadis <aris@0xbadc0de.be>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
-rw-r--r-- | include/libssh/crypto.h | 3 | ||||
-rw-r--r-- | include/libssh/dh-gex.h | 37 | ||||
-rw-r--r-- | include/libssh/session.h | 1 | ||||
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/client.c | 5 | ||||
-rw-r--r-- | src/dh-gex.c | 254 | ||||
-rw-r--r-- | src/kex.c | 28 | ||||
-rw-r--r-- | src/packet.c | 8 |
8 files changed, 335 insertions, 2 deletions
diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index eb01ac28..72b6a1d6 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -58,6 +58,9 @@ 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_GEX_SHA1, + SSH_KEX_DH_GEX_SHA256, /* ecdh-sha2-nistp256 */ SSH_KEX_ECDH_SHA2_NISTP256, /* ecdh-sha2-nistp384 */ diff --git a/include/libssh/dh-gex.h b/include/libssh/dh-gex.h new file mode 100644 index 00000000..ea93e6d1 --- /dev/null +++ b/include/libssh/dh-gex.h @@ -0,0 +1,37 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis <aris@0xbadc0de.be> + * + * 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. + */ + + +#ifndef SRC_DH_GEX_H_ +#define SRC_DH_GEX_H_ + +/* Minimum, recommanded and maximum size of DH group */ +#define DH_PMIN 2048 +#define DH_PREQ 2048 +#define DH_PMAX 8192 + +int ssh_client_dhgex_init(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_dhgex_init(ssh_session session); +#endif /* WITH_SERVER */ + +#endif /* SRC_DH_GEX_H_ */ diff --git a/include/libssh/session.h b/include/libssh/session.h index 5761fa2d..5159f216 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -49,6 +49,7 @@ enum ssh_session_state_e { enum ssh_dh_state_e { DH_STATE_INIT=0, + DH_STATE_REQUEST_SENT, DH_STATE_INIT_SENT, DH_STATE_NEWKEYS_SENT, DH_STATE_FINISHED diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d48ff258..ff3b3a47 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,6 +126,7 @@ set(libssh_SRCS connector.c curve25519.c dh.c + dh-gex.c ecdh.c error.c getpass.c diff --git a/src/client.c b/src/client.c index d009a8b0..64e81115 100644 --- a/src/client.c +++ b/src/client.c @@ -38,6 +38,7 @@ #include "libssh/socket.h" #include "libssh/session.h" #include "libssh/dh.h" +#include "libssh/dh-gex.h" #include "libssh/ecdh.h" #include "libssh/threads.h" #include "libssh/misc.h" @@ -253,6 +254,10 @@ static int dh_handshake(ssh_session session) { case SSH_KEX_DH_GROUP18_SHA512: rc = ssh_client_dh_init(session); break; + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_client_dhgex_init(session); + break; #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP384: diff --git a/src/dh-gex.c b/src/dh-gex.c new file mode 100644 index 00000000..407c2391 --- /dev/null +++ b/src/dh-gex.c @@ -0,0 +1,254 @@ +/* + * dh-gex.c - diffie-hellman group exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis <aris@0xbadc0de.be> + * + * 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 "libssh/priv.h" +#include "libssh/dh-gex.h" +#include "libssh/libssh.h" +#include "libssh/ssh2.h" +#include "libssh/callbacks.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group); +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply); + +static ssh_packet_callback dhgex_client_callbacks[] = { + ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */ + NULL, /* SSH_MSG_KEX_DH_GEX_INIT */ + ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */ +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_GROUP, + .n_callbacks = 3, + .callbacks = dhgex_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief initiates a diffie-hellman-group-exchange kex + */ +int ssh_client_dhgex_init(ssh_session session) +{ + int rc; + + rc = ssh_dh_init_common(session); + if (rc != SSH_OK){ + goto error; + } + + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_pack(session->out_buffer, + "bddd", + SSH2_MSG_KEX_DH_GEX_REQUEST, + DH_PMIN, + DH_PREQ, + DH_PMAX); + if (rc != SSH_OK) { + goto error; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks); + session->dh_handshake_state = DH_STATE_REQUEST_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + return rc; +error: + ssh_dh_cleanup(session); + return SSH_ERROR; +} + +/** @internal + * @brief handle a DH_GEX_GROUP packet, client side. This packet contains + * the group parameters. + */ +SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) +{ + int rc; + int blen; + bignum pmin1 = NULL, one = NULL; + bignum_CTX ctx = bignum_ctx_new(); + + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_GROUP received"); + + if (bignum_ctx_invalid(ctx)) { + goto error; + } + + if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_GROUP in invalid state"); + goto error; + } + one = bignum_new(); + pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + ssh_set_error_oom(session); + goto error; + } + session->next_crypto->dh_group_is_mutable = 1; + rc = ssh_buffer_unpack(packet, + "BB", + &session->next_crypto->p, + &session->next_crypto->g); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet"); + goto error; + } + /* basic checks */ + rc = bignum_set_word(one, 1); + if (rc != 1) { + goto error; + } + blen = bignum_num_bits(session->next_crypto->p); + if (blen < DH_PMIN || blen > DH_PMAX) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh group parameter p: %d not in [%d:%d]", + blen, + DH_PMIN, + DH_PMAX); + goto error; + } + if (bignum_cmp(session->next_crypto->p, one) <= 0) { + /* p must be positive and preferably bigger than one */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + } + if (!bignum_is_bit_set(session->next_crypto->p, 0)) { + /* p must be a prime and therefore not divisible by 2 */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + bignum_sub(pmin1, session->next_crypto->p, one); + if (bignum_cmp(session->next_crypto->g, one) <= 0 || + bignum_cmp(session->next_crypto->g, pmin1) > 0) { + /* generator must be at least 2 and smaller than p-1*/ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g"); + goto error; + } + /* compute and send DH public parameter */ + rc = ssh_dh_generate_secret(session, session->next_crypto->x); + if (rc == SSH_ERROR) { + goto error; + } + session->next_crypto->e = bignum_new(); + if (session->next_crypto->e == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = bignum_mod_exp(session->next_crypto->e, + session->next_crypto->g, + session->next_crypto->x, + session->next_crypto->p, + ctx); + if (rc != 1) { + goto error; + } + + bignum_ctx_free(ctx); + ctx = NULL; + + rc = ssh_buffer_pack(session->out_buffer, + "bB", + SSH2_MSG_KEX_DH_GEX_INIT, + session->next_crypto->e); + if (rc != SSH_OK) { + goto error; + } + + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_PACKET_USED; +error: + bignum_safe_free(one); + bignum_safe_free(pmin1); + if(!bignum_ctx_invalid(ctx)) { + bignum_ctx_free(ctx); + } + ssh_dh_cleanup(session); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) +{ + struct ssh_crypto_struct *crypto=session->next_crypto; + int rc; + ssh_string pubkey_blob = NULL; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_REPLY received"); + + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &crypto->f, + &crypto->dh_server_signature); + + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet"); + goto error; + } + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + ssh_string_free(pubkey_blob); + if (rc != 0) { + goto error; + } + + rc = ssh_dh_build_k(session); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +error: + ssh_dh_cleanup(session); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} @@ -31,6 +31,7 @@ #include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/dh.h" +#include "libssh/dh-gex.h" #include "libssh/kex.h" #include "libssh/session.h" #include "libssh/ssh2.h" @@ -115,7 +116,13 @@ #define CHACHA20 "chacha20-poly1305@openssh.com," -#define KEY_EXCHANGE CURVE25519 ECDH "diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" +#define KEY_EXCHANGE \ + CURVE25519 \ + ECDH \ + "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ + "diffie-hellman-group-exchange-sha256," \ + "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1," \ + "diffie-hellman-group-exchange-sha1" #define KEX_METHODS_SIZE 10 /* RFC 8308 */ @@ -826,6 +833,10 @@ int ssh_kex_select_methods (ssh_session session){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group18-sha512") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP18_SHA512; + } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ + session->next_crypto->kex_type=SSH_KEX_DH_GEX_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_GEX_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], "ecdh-sha2-nistp384") == 0){ @@ -1080,6 +1091,19 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_buffer_pack(buf, + "dddBBBB", + DH_PMIN, DH_PREQ, DH_PMAX, + session->next_crypto->p, + session->next_crypto->g, + session->next_crypto->e, + session->next_crypto->f); + if (rc != SSH_OK) { + goto error; + } + break; #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP384: @@ -1126,6 +1150,7 @@ int ssh_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_GEX_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); @@ -1139,6 +1164,7 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_KEX_DH_GEX_SHA256: session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; session->next_crypto->mac_type = SSH_MAC_SHA256; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); diff --git a/src/packet.c b/src/packet.c index 8fa10283..39ecf362 100644 --- a/src/packet.c +++ b/src/packet.c @@ -388,6 +388,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * States required: * - session_state == SSH_SESSION_STATE_DH * - dh_handshake_state == DH_STATE_INIT_SENT + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) * * Transitions: * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT @@ -398,7 +399,8 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se break; } - if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + if (session->dh_handshake_state != DH_STATE_INIT_SENT && + session->dh_handshake_state != DH_STATE_REQUEST_SENT) { rc = SSH_PACKET_DENIED; break; } @@ -1273,6 +1275,10 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) ssh_packet_process(session, session->in_packet.type); break; case SSH_PACKET_DENIED: + ssh_set_error(session, + SSH_FATAL, + "Packet filter: rejected packet (type %d)", + session->in_packet.type); goto error; case SSH_PACKET_UNKNOWN: ssh_packet_send_unimplemented(session, session->recv_seq - 1); |