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:
committed by
Andreas Schneider
parent
154eb91914
commit
574bfb5459
@@ -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 */
|
||||
|
||||
37
include/libssh/dh-gex.h
Normal file
37
include/libssh/dh-gex.h
Normal 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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -126,6 +126,7 @@ set(libssh_SRCS
|
||||
connector.c
|
||||
curve25519.c
|
||||
dh.c
|
||||
dh-gex.c
|
||||
ecdh.c
|
||||
error.c
|
||||
getpass.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:
|
||||
|
||||
254
src/dh-gex.c
Normal file
254
src/dh-gex.c
Normal 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;
|
||||
}
|
||||
28
src/kex.c
28
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user