aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAris Adamantiadis <aris@0xbadc0de.be>2018-11-06 21:44:46 +0100
committerAndreas Schneider <asn@cryptomilk.org>2019-01-24 11:56:23 +0100
commit574bfb54595870033af2927c692af5f0afd3073c (patch)
tree05171d2347d2255618ec0dd66811b738088c86b4
parent154eb9191408a63483481efeafa633d3b467da18 (diff)
downloadlibssh-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.h3
-rw-r--r--include/libssh/dh-gex.h37
-rw-r--r--include/libssh/session.h1
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/client.c5
-rw-r--r--src/dh-gex.c254
-rw-r--r--src/kex.c28
-rw-r--r--src/packet.c8
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;
+}
diff --git a/src/kex.c b/src/kex.c
index 235dbec0..1485cbdb 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -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);