|
@@ -0,0 +1,232 @@
|
|
|
+From 6e43be5c7b99dbee49dc72b6f989f29fdd7e9356 Mon Sep 17 00:00:00 2001
|
|
|
+From: Matt Johnston <matt@ucc.asn.au>
|
|
|
+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.
|
|
|
+
|
|
|
+Upstream: https://github.com/mkj/dropbear/commit/6e43be5c7b99dbee49dc72b6f989f29fdd7e9356
|
|
|
+Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
|
|
|
+---
|
|
|
+ src/cli-session.c | 11 +++++++++++
|
|
|
+ src/common-algo.c | 6 ++++++
|
|
|
+ src/common-kex.c | 26 +++++++++++++++++++++++++-
|
|
|
+ src/kex.h | 3 +++
|
|
|
+ src/process-packet.c | 34 +++++++++++++++++++---------------
|
|
|
+ src/ssh.h | 4 ++++
|
|
|
+ src/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;
|
|
|
++ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|