From 6e43be5c7b99dbee49dc72b6f989f29fdd7e9356 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Mon, 20 Nov 2023 14:02:47 +0800 Subject: [PATCH] Implement Strict KEX mode As specified by OpenSSH with kex-strict-c-v00@openssh.com and kex-strict-s-v00@openssh.com. --- cli-session.c | 11 +++++++++++ common-algo.c | 6 ++++++ common-kex.c | 26 +++++++++++++++++++++++++- kex.h | 3 +++ process-packet.c | 34 +++++++++++++++++++--------------- ssh.h | 4 ++++ svr-session.c | 3 +++ 7 files changed, 71 insertions(+), 16 deletions(-) diff --git a/cli-session.c b/cli-session.c index 5981b2470..d261c8f82 100644 --- a/cli-session.c +++ b/cli-session.c @@ -46,6 +46,7 @@ static void cli_finished(void) ATTRIB_NORETURN; static void recv_msg_service_accept(void); static void cli_session_cleanup(void); static void recv_msg_global_request_cli(void); +static void cli_algos_initialise(void); struct clientsession cli_ses; /* GLOBAL */ @@ -117,6 +118,7 @@ void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection } chaninitialise(cli_chantypes); + cli_algos_initialise(); /* Set up cli_ses vars */ cli_session_init(proxy_cmd_pid); @@ -487,3 +489,12 @@ void cli_dropbear_log(int priority, const char* format, va_list param) { fflush(stderr); } +static void cli_algos_initialise(void) { + algo_type *algo; + for (algo = sshkex; algo->name; algo++) { + if (strcmp(algo->name, SSH_STRICT_KEX_S) == 0) { + algo->usable = 0; + } + } +} + diff --git a/common-algo.c b/common-algo.c index 378f0ca8e..f9d46ebb6 100644 --- a/common-algo.c +++ b/common-algo.c @@ -307,6 +307,12 @@ algo_type sshkex[] = { /* Set unusable by svr_algos_initialise() */ {SSH_EXT_INFO_C, 0, NULL, 1, NULL}, #endif +#endif +#if DROPBEAR_CLIENT + {SSH_STRICT_KEX_C, 0, NULL, 1, NULL}, +#endif +#if DROPBEAR_SERVER + {SSH_STRICT_KEX_S, 0, NULL, 1, NULL}, #endif {NULL, 0, NULL, 0, NULL} }; diff --git a/common-kex.c b/common-kex.c index ac8844246..8e33b12a6 100644 --- a/common-kex.c +++ b/common-kex.c @@ -183,6 +183,10 @@ void send_msg_newkeys() { gen_new_keys(); switch_keys(); + if (ses.kexstate.strict_kex) { + ses.transseq = 0; + } + TRACE(("leave send_msg_newkeys")) } @@ -193,7 +197,11 @@ void recv_msg_newkeys() { ses.kexstate.recvnewkeys = 1; switch_keys(); - + + if (ses.kexstate.strict_kex) { + ses.recvseq = 0; + } + TRACE(("leave recv_msg_newkeys")) } @@ -550,6 +558,10 @@ void recv_msg_kexinit() { ses.kexstate.recvkexinit = 1; + if (ses.kexstate.strict_kex && !ses.kexstate.donefirstkex && ses.recvseq != 1) { + dropbear_exit("First packet wasn't kexinit"); + } + TRACE(("leave recv_msg_kexinit")) } @@ -859,6 +871,18 @@ static void read_kex_algos() { } #endif + if (!ses.kexstate.donefirstkex) { + const char* strict_name; + if (IS_DROPBEAR_CLIENT) { + strict_name = SSH_STRICT_KEX_S; + } else { + strict_name = SSH_STRICT_KEX_C; + } + if (buf_has_algo(ses.payload, strict_name) == DROPBEAR_SUCCESS) { + ses.kexstate.strict_kex = 1; + } + } + algo = buf_match_algo(ses.payload, sshkex, kexguess2, &goodguess); allgood &= goodguess; if (algo == NULL || algo->data == NULL) { diff --git a/kex.h b/kex.h index 77cf21a37..7fcc3c252 100644 --- a/kex.h +++ b/kex.h @@ -83,6 +83,9 @@ struct KEXState { unsigned our_first_follows_matches : 1; + /* Boolean indicating that strict kex mode is in use */ + unsigned int strict_kex; + time_t lastkextime; /* time of the last kex */ unsigned int datatrans; /* data transmitted since last kex */ unsigned int datarecv; /* data received since last kex */ diff --git a/process-packet.c b/process-packet.c index 945416023..133a152d0 100644 --- a/process-packet.c +++ b/process-packet.c @@ -44,6 +44,7 @@ void process_packet() { unsigned char type; unsigned int i; + unsigned int first_strict_kex = ses.kexstate.strict_kex && !ses.kexstate.donefirstkex; time_t now; TRACE2(("enter process_packet")) @@ -54,22 +55,24 @@ void process_packet() { now = monotonic_now(); ses.last_packet_time_keepalive_recv = now; - /* These packets we can receive at any time */ - switch(type) { - case SSH_MSG_IGNORE: - goto out; - case SSH_MSG_DEBUG: - goto out; + if (type == SSH_MSG_DISCONNECT) { + /* Allowed at any time */ + dropbear_close("Disconnect received"); + } - case SSH_MSG_UNIMPLEMENTED: - /* debugging XXX */ - TRACE(("SSH_MSG_UNIMPLEMENTED")) - goto out; - - case SSH_MSG_DISCONNECT: - /* TODO cleanup? */ - dropbear_close("Disconnect received"); + /* These packets may be received at any time, + except during first kex with strict kex */ + if (!first_strict_kex) { + switch(type) { + case SSH_MSG_IGNORE: + goto out; + case SSH_MSG_DEBUG: + goto out; + case SSH_MSG_UNIMPLEMENTED: + TRACE(("SSH_MSG_UNIMPLEMENTED")) + goto out; + } } /* Ignore these packet types so that keepalives don't interfere with @@ -98,7 +101,8 @@ void process_packet() { if (type >= 1 && type <= 49 && type != SSH_MSG_SERVICE_REQUEST && type != SSH_MSG_SERVICE_ACCEPT - && type != SSH_MSG_KEXINIT) + && type != SSH_MSG_KEXINIT + && !first_strict_kex) { TRACE(("unknown allowed packet during kexinit")) recv_unimplemented(); diff --git a/ssh.h b/ssh.h index 1b4fec65f..ef3efdca0 100644 --- a/ssh.h +++ b/ssh.h @@ -100,6 +100,10 @@ #define SSH_EXT_INFO_C "ext-info-c" #define SSH_SERVER_SIG_ALGS "server-sig-algs" +/* OpenSSH strict KEX feature */ +#define SSH_STRICT_KEX_S "kex-strict-s-v00@openssh.com" +#define SSH_STRICT_KEX_C "kex-strict-c-v00@openssh.com" + /* service types */ #define SSH_SERVICE_USERAUTH "ssh-userauth" #define SSH_SERVICE_USERAUTH_LEN 12 diff --git a/svr-session.c b/svr-session.c index 769f0731d..a538e2c5c 100644 --- a/svr-session.c +++ b/svr-session.c @@ -370,6 +370,9 @@ static void svr_algos_initialise(void) { algo->usable = 0; } #endif + if (strcmp(algo->name, SSH_STRICT_KEX_C) == 0) { + algo->usable = 0; + } } }