Skip to content
This repository was archived by the owner on Jan 26, 2026. It is now read-only.

Commit 70565ac

Browse files
Jakujecryptomilk
authored andcommitted
CVE-2023-1667:kex: Add support for sending first_kex_packet_follows flag
This is not completely straightforward as it requires us to do some state shuffling. We introduce internal flag that can turn this on in client side, so far for testing only as we do not want to universally enable this. We also repurpose the server flag indicating the guess was wrong also for the client to make desired decisions. If we found out our guess was wrong, we need to hope the server was able to figure out this much, we need to revert the DH FSM state, drop the callbacks from the "wrong" key exchange method and initiate the right one. The server side is already tested by the pkd_hello_i1, which is executing tests against dropbrear clients, which is using this flag by default out of the box. Tested manually also with the pkd_hello --rekey to make sure the server is able to handle the rekeying with all key exchange methods. Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Norbert Pocs <npocs@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
1 parent fc1a8bb commit 70565ac

4 files changed

Lines changed: 93 additions & 15 deletions

File tree

include/libssh/dh.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ int ssh_dh_get_current_server_publickey_blob(ssh_session session,
7777
ssh_key ssh_dh_get_next_server_publickey(ssh_session session);
7878
int ssh_dh_get_next_server_publickey_blob(ssh_session session,
7979
ssh_string *pubkey_blob);
80+
int dh_handshake(ssh_session session);
8081

8182
int ssh_client_dh_init(ssh_session session);
8283
void ssh_client_dh_remove_callbacks(ssh_session session);

include/libssh/session.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,21 @@ struct ssh_session_struct {
169169
uint32_t current_method;
170170
} auth;
171171

172+
/* Sending this flag before key exchange to save one round trip during the
173+
* key exchange. This might make sense on high-latency connections.
174+
* So far internal only for testing. Usable only on the client side --
175+
* there is no key exchange method that would start with server message */
176+
bool send_first_kex_follows;
172177
/*
173178
* RFC 4253, 7.1: if the first_kex_packet_follows flag was set in
174179
* the received SSH_MSG_KEXINIT, but the guess was wrong, this
175180
* field will be set such that the following guessed packet will
176-
* be ignored. Once that packet has been received and ignored,
177-
* this field is cleared.
181+
* be ignored on the receiving side. Once that packet has been received and
182+
* ignored, this field is cleared.
183+
* On the sending side, this is set after we got peer KEXINIT message and we
184+
* need to resend the initial message of the negotiated KEX algorithm.
178185
*/
179-
int first_kex_follows_guess_wrong;
186+
bool first_kex_follows_guess_wrong;
180187

181188
ssh_buffer in_hashbuf;
182189
ssh_buffer out_hashbuf;

src/client.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,13 @@ int ssh_send_banner(ssh_session session, int server)
246246
* @warning this function returning is no proof that DH handshake is
247247
* completed
248248
*/
249-
static int dh_handshake(ssh_session session)
249+
int dh_handshake(ssh_session session)
250250
{
251251
int rc = SSH_AGAIN;
252252

253+
SSH_LOG(SSH_LOG_TRACE, "dh_handshake_state = %d, kex_type = %d",
254+
session->dh_handshake_state, session->next_crypto->kex_type);
255+
253256
switch (session->dh_handshake_state) {
254257
case DH_STATE_INIT:
255258
switch(session->next_crypto->kex_type){
@@ -391,6 +394,8 @@ static void ssh_client_connection_callback(ssh_session session)
391394
{
392395
int rc;
393396

397+
SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state);
398+
394399
switch (session->session_state) {
395400
case SSH_SESSION_STATE_NONE:
396401
case SSH_SESSION_STATE_CONNECTING:
@@ -453,6 +458,9 @@ static void ssh_client_connection_callback(ssh_session session)
453458
goto error;
454459
set_status(session, 0.8f);
455460
session->session_state = SSH_SESSION_STATE_DH;
461+
462+
/* If the init packet was already sent in previous step, this will be no
463+
* operation */
456464
if (dh_handshake(session) == SSH_ERROR) {
457465
goto error;
458466
}

src/kex.c

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <stdio.h>
2929
#include <stdbool.h>
3030

31+
#include "libssh/libssh.h"
3132
#include "libssh/priv.h"
3233
#include "libssh/buffer.h"
3334
#include "libssh/dh.h"
@@ -374,14 +375,19 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
374375
(void)type;
375376
(void)user;
376377

378+
SSH_LOG(SSH_LOG_TRACE, "KEXINIT received");
379+
377380
if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) {
378381
if (session->dh_handshake_state == DH_STATE_FINISHED) {
379382
SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange");
380383
/* Reset the sent flag if the re-kex was initiated by the peer */
381384
session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT;
382-
} else if (session->dh_handshake_state == DH_STATE_INIT_SENT) {
383-
SSH_LOG(SSH_LOG_DEBUG, "Receeved peer kexinit answer");
384-
} else {
385+
} else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT &&
386+
session->dh_handshake_state == DH_STATE_INIT_SENT) {
387+
/* This happens only when we are sending our-guessed first kex
388+
* packet right after our KEXINIT packet. */
389+
SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer.");
390+
} else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) {
385391
ssh_set_error(session, SSH_FATAL,
386392
"SSH_KEXINIT received in wrong state");
387393
goto error;
@@ -495,16 +501,19 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
495501
/*
496502
* Remember whether 'first_kex_packet_follows' was set and the client
497503
* guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message
498-
* must be ignored.
504+
* must be ignored on the server side.
505+
* Client needs to start the Key exchange over with the correct method
499506
*/
500-
if (first_kex_packet_follows) {
507+
if (first_kex_packet_follows || session->send_first_kex_follows) {
501508
char **client_methods = crypto->client_kex.methods;
502509
char **server_methods = crypto->server_kex.methods;
503510
session->first_kex_follows_guess_wrong =
504511
cmp_first_kex_algo(client_methods[SSH_KEX],
505512
server_methods[SSH_KEX]) ||
506513
cmp_first_kex_algo(client_methods[SSH_HOSTKEYS],
507514
server_methods[SSH_HOSTKEYS]);
515+
SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.",
516+
session->first_kex_follows_guess_wrong ? "wrong" : "right");
508517
}
509518

510519
if (server_kex) {
@@ -583,7 +592,12 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit)
583592

584593
/* Note, that his overwrites authenticated state in case of rekeying */
585594
session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED;
586-
session->dh_handshake_state = DH_STATE_INIT;
595+
/* if we already sent our initial key exchange packet, do not reset the
596+
* DH state. We will know if we were right with our guess only in
597+
* dh_handshake_state() */
598+
if (session->send_first_kex_follows == false) {
599+
session->dh_handshake_state = DH_STATE_INIT;
600+
}
587601
session->ssh_connection_callback(session);
588602
return SSH_PACKET_USED;
589603

@@ -892,8 +906,9 @@ int ssh_kex_select_methods (ssh_session session)
892906
struct ssh_crypto_struct *crypto = session->next_crypto;
893907
struct ssh_kex_struct *server = &crypto->server_kex;
894908
struct ssh_kex_struct *client = &crypto->client_kex;
895-
char *ext_start = NULL, *kex;
909+
char *ext_start = NULL;
896910
const char *aead_hmac = NULL;
911+
enum ssh_key_exchange_e kex_type;
897912
int i;
898913

899914
/* Here we should drop the ext-info-c from the list so we avoid matching.
@@ -925,8 +940,18 @@ int ssh_kex_select_methods (ssh_session session)
925940
crypto->kex_methods[i] = strdup("");
926941
}
927942
}
928-
kex = crypto->kex_methods[SSH_KEX];
929-
crypto->kex_type = kex_select_kex_type(kex);
943+
944+
/* We can not set this value directly as the old value is needed to revert
945+
* callbacks if we are client */
946+
kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]);
947+
if (session->client && session->first_kex_follows_guess_wrong) {
948+
SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX");
949+
/* We need to remove the wrong callbacks and start kex again */
950+
revert_kex_callbacks(session);
951+
session->dh_handshake_state = DH_STATE_INIT;
952+
session->first_kex_follows_guess_wrong = false;
953+
}
954+
crypto->kex_type = kex_type;
930955

931956
SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
932957
session->next_crypto->kex_methods[SSH_KEX],
@@ -953,6 +978,19 @@ int ssh_send_kex(ssh_session session)
953978
ssh_string str = NULL;
954979
int i;
955980
int rc;
981+
int first_kex_packet_follows = 0;
982+
983+
/* Only client can initiate the handshake methods we implement. If we
984+
* already received the peer mechanisms, there is no point in guessing */
985+
if (session->client &&
986+
session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED &&
987+
session->send_first_kex_follows) {
988+
first_kex_packet_follows = 1;
989+
}
990+
991+
SSH_LOG(SSH_LOG_TRACE,
992+
"Sending KEXINIT packet, first_kex_packet_follows = %d",
993+
first_kex_packet_follows);
956994

957995
rc = ssh_buffer_pack(session->out_buffer,
958996
"bP",
@@ -987,14 +1025,14 @@ int ssh_send_kex(ssh_session session)
9871025

9881026
rc = ssh_buffer_pack(session->out_buffer,
9891027
"bd",
990-
0,
1028+
first_kex_packet_follows,
9911029
0);
9921030
if (rc != SSH_OK) {
9931031
goto error;
9941032
}
9951033

9961034
/* Prepare also the first_kex_packet_follows and reserved to 0 */
997-
rc = ssh_buffer_add_u8(session->out_hashbuf, 0);
1035+
rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows);
9981036
if (rc < 0) {
9991037
goto error;
10001038
}
@@ -1010,6 +1048,30 @@ int ssh_send_kex(ssh_session session)
10101048

10111049
session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT;
10121050
SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent");
1051+
1052+
/* If we indicated that we are sending the guessed key exchange packet,
1053+
* do it now. The packet is simple, but we need to do some preparations */
1054+
if (first_kex_packet_follows) {
1055+
char *list = kex->methods[SSH_KEX];
1056+
char *colon = strchr(list, ',');
1057+
size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list);
1058+
char *kex_name = calloc(kex_name_len + 1, 1);
1059+
if (kex_name == NULL) {
1060+
ssh_set_error_oom(session);
1061+
goto error;
1062+
}
1063+
snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list);
1064+
SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name);
1065+
1066+
session->next_crypto->kex_type = kex_select_kex_type(kex_name);
1067+
free(kex_name);
1068+
1069+
/* run the first step of the DH handshake */
1070+
session->dh_handshake_state = DH_STATE_INIT;
1071+
if (dh_handshake(session) == SSH_ERROR) {
1072+
goto error;
1073+
}
1074+
}
10131075
return 0;
10141076

10151077
error:

0 commit comments

Comments
 (0)