1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-11-27 13:21:11 +03:00

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>
This commit is contained in:
Aris Adamantiadis
2018-11-06 21:44:46 +01:00
committed by Andreas Schneider
parent 154eb91914
commit 574bfb5459
8 changed files with 335 additions and 2 deletions

View File

@@ -58,6 +58,9 @@ enum ssh_key_exchange_e {
SSH_KEX_DH_GROUP1_SHA1=1, SSH_KEX_DH_GROUP1_SHA1=1,
/* diffie-hellman-group14-sha1 */ /* diffie-hellman-group14-sha1 */
SSH_KEX_DH_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 */ /* ecdh-sha2-nistp256 */
SSH_KEX_ECDH_SHA2_NISTP256, SSH_KEX_ECDH_SHA2_NISTP256,
/* ecdh-sha2-nistp384 */ /* ecdh-sha2-nistp384 */

37
include/libssh/dh-gex.h Normal file
View File

@@ -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_ */

View File

@@ -49,6 +49,7 @@ enum ssh_session_state_e {
enum ssh_dh_state_e { enum ssh_dh_state_e {
DH_STATE_INIT=0, DH_STATE_INIT=0,
DH_STATE_REQUEST_SENT,
DH_STATE_INIT_SENT, DH_STATE_INIT_SENT,
DH_STATE_NEWKEYS_SENT, DH_STATE_NEWKEYS_SENT,
DH_STATE_FINISHED DH_STATE_FINISHED

View File

@@ -126,6 +126,7 @@ set(libssh_SRCS
connector.c connector.c
curve25519.c curve25519.c
dh.c dh.c
dh-gex.c
ecdh.c ecdh.c
error.c error.c
getpass.c getpass.c

View File

@@ -38,6 +38,7 @@
#include "libssh/socket.h" #include "libssh/socket.h"
#include "libssh/session.h" #include "libssh/session.h"
#include "libssh/dh.h" #include "libssh/dh.h"
#include "libssh/dh-gex.h"
#include "libssh/ecdh.h" #include "libssh/ecdh.h"
#include "libssh/threads.h" #include "libssh/threads.h"
#include "libssh/misc.h" #include "libssh/misc.h"
@@ -253,6 +254,10 @@ static int dh_handshake(ssh_session session) {
case SSH_KEX_DH_GROUP18_SHA512: case SSH_KEX_DH_GROUP18_SHA512:
rc = ssh_client_dh_init(session); rc = ssh_client_dh_init(session);
break; break;
case SSH_KEX_DH_GEX_SHA1:
case SSH_KEX_DH_GEX_SHA256:
rc = ssh_client_dhgex_init(session);
break;
#ifdef HAVE_ECDH #ifdef HAVE_ECDH
case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_ECDH_SHA2_NISTP384: case SSH_KEX_ECDH_SHA2_NISTP384:

254
src/dh-gex.c Normal file
View File

@@ -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;
}

View File

@@ -31,6 +31,7 @@
#include "libssh/priv.h" #include "libssh/priv.h"
#include "libssh/buffer.h" #include "libssh/buffer.h"
#include "libssh/dh.h" #include "libssh/dh.h"
#include "libssh/dh-gex.h"
#include "libssh/kex.h" #include "libssh/kex.h"
#include "libssh/session.h" #include "libssh/session.h"
#include "libssh/ssh2.h" #include "libssh/ssh2.h"
@@ -115,7 +116,13 @@
#define CHACHA20 "chacha20-poly1305@openssh.com," #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 #define KEX_METHODS_SIZE 10
/* RFC 8308 */ /* RFC 8308 */
@@ -826,6 +833,10 @@ int ssh_kex_select_methods (ssh_session session){
session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; 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){ } 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; 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){ } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){
session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256;
} else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp384") == 0){ } 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; goto error;
} }
break; 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 #ifdef HAVE_ECDH
case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_ECDH_SHA2_NISTP384: case SSH_KEX_ECDH_SHA2_NISTP384:
@@ -1126,6 +1150,7 @@ int ssh_make_sessionid(ssh_session session)
switch (session->next_crypto->kex_type) { switch (session->next_crypto->kex_type) {
case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP1_SHA1:
case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA1:
case SSH_KEX_DH_GEX_SHA1:
session->next_crypto->digest_len = SHA_DIGEST_LENGTH; session->next_crypto->digest_len = SHA_DIGEST_LENGTH;
session->next_crypto->mac_type = SSH_MAC_SHA1; session->next_crypto->mac_type = SSH_MAC_SHA1;
session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); 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_ECDH_SHA2_NISTP256:
case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256:
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
case SSH_KEX_DH_GEX_SHA256:
session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; session->next_crypto->digest_len = SHA256_DIGEST_LENGTH;
session->next_crypto->mac_type = SSH_MAC_SHA256; session->next_crypto->mac_type = SSH_MAC_SHA256;
session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len);

View File

@@ -388,6 +388,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
* States required: * States required:
* - session_state == SSH_SESSION_STATE_DH * - session_state == SSH_SESSION_STATE_DH
* - dh_handshake_state == DH_STATE_INIT_SENT * - dh_handshake_state == DH_STATE_INIT_SENT
* or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex)
* *
* Transitions: * Transitions:
* - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT * - 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; 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; rc = SSH_PACKET_DENIED;
break; 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); ssh_packet_process(session, session->in_packet.type);
break; break;
case SSH_PACKET_DENIED: case SSH_PACKET_DENIED:
ssh_set_error(session,
SSH_FATAL,
"Packet filter: rejected packet (type %d)",
session->in_packet.type);
goto error; goto error;
case SSH_PACKET_UNKNOWN: case SSH_PACKET_UNKNOWN:
ssh_packet_send_unimplemented(session, session->recv_seq - 1); ssh_packet_send_unimplemented(session, session->recv_seq - 1);