diff --git a/auth_bypass.patch b/auth_bypass.patch new file mode 100644 index 0000000..6d2a8e2 --- /dev/null +++ b/auth_bypass.patch @@ -0,0 +1,82 @@ +diff --color -ru ../libssh-0.10.4/src/pki_crypto.c ./src/pki_crypto.c +--- ../libssh-0.10.4/src/pki_crypto.c 2023-04-27 13:24:03.105744519 +0200 ++++ ./src/pki_crypto.c 2023-04-27 13:30:24.016756496 +0200 +@@ -3186,8 +3186,12 @@ + unsigned char *raw_sig_data = NULL; + unsigned int raw_sig_len; + ++ /* Function return code ++ * Do not change this variable throughout the function until the signature ++ * is successfully verified! ++ */ + int rc = SSH_ERROR; +- int evp_rc; ++ int ok; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL || (signature->raw_sig == NULL +@@ -3202,8 +3206,8 @@ + } + + /* Check if public key and hash type are compatible */ +- rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); +- if (rc != SSH_OK) { ++ ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); ++ if (ok != SSH_OK) { + return SSH_ERROR; + } + +@@ -3248,8 +3252,8 @@ + } + + /* Verify the signature */ +- evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); +- if (evp_rc != 1){ ++ ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); ++ if (ok != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); +@@ -3257,32 +3261,30 @@ + } + + #ifdef HAVE_OPENSSL_EVP_DIGESTVERIFY +- evp_rc = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); ++ ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + #else +- evp_rc = EVP_DigestVerifyUpdate(ctx, input, input_len); +- if (evp_rc != 1) { ++ ok = EVP_DigestVerifyUpdate(ctx, input, input_len); ++ if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyUpdate() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + +- evp_rc = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); ++ ok = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); + #endif +- if (evp_rc == 1) { +- SSH_LOG(SSH_LOG_TRACE, "Signature valid"); +- rc = SSH_OK; +- } else { ++ if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Signature invalid: %s", + ERR_error_string(ERR_get_error(), NULL)); +- rc = SSH_ERROR; ++ goto out; + } + ++ SSH_LOG(SSH_LOG_TRACE, "Signature valid"); ++ rc = SSH_OK; ++ + out: +- if (ctx != NULL) { +- EVP_MD_CTX_free(ctx); +- } ++ EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return rc; + } diff --git a/covscan23.patch b/covscan23.patch new file mode 100644 index 0000000..c74d6ee --- /dev/null +++ b/covscan23.patch @@ -0,0 +1,228 @@ +diff --color -ru ../libssh-0.10.4/src/buffer.c ./src/buffer.c +--- ../libssh-0.10.4/src/buffer.c 2023-05-03 12:22:40.304114511 +0200 ++++ ./src/buffer.c 2023-05-03 12:24:11.578110236 +0200 +@@ -747,7 +747,8 @@ + */ + int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) + { +- if (buffer->pos + len < len || buffer->pos + len > buffer->used) { ++ if (buffer == NULL || buffer->pos + len < len || ++ buffer->pos + len > buffer->used) { + return SSH_ERROR; + } + +diff --color -ru ../libssh-0.10.4/src/gssapi.c ./src/gssapi.c +--- ../libssh-0.10.4/src/gssapi.c 2023-05-03 12:22:40.356115078 +0200 ++++ ./src/gssapi.c 2023-05-03 12:24:11.566110105 +0200 +@@ -444,11 +444,18 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ ssh_auth_reply_default(session, 0); ++ ssh_gssapi_free(session); ++ session->gssapi = NULL; ++ return SSH_PACKET_USED; ++ } + ssh_packet_send(session); + } + +@@ -857,6 +864,7 @@ + } + + SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ ++ int rc; + ssh_string oid_s; + gss_uint32 maj_stat, min_stat; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; +@@ -908,11 +916,15 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ goto error; ++ } + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } +@@ -975,6 +987,7 @@ + } + + SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ ++ int rc; + ssh_string token; + char *hexa; + OM_uint32 maj_stat, min_stat; +@@ -1027,11 +1040,15 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ goto error; ++ } + ssh_packet_send(session); + } + +diff --color -ru ../libssh-0.10.4/src/options.c ./src/options.c +--- ../libssh-0.10.4/src/options.c 2023-05-03 12:22:40.342114926 +0200 ++++ ./src/options.c 2023-05-03 12:24:11.582110280 +0200 +@@ -614,7 +614,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { ++ SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); + SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + if (i <= 0) { +@@ -816,7 +818,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { ++ SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + if (i < 0) { +@@ -2035,7 +2039,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { +- SAFE_FREE(q); ++ SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); ++ SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + +@@ -2062,7 +2068,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { +- SAFE_FREE(q); ++ SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); ++ SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + +diff --color -ru ../libssh-0.10.4/src/pki_container_openssh.c ./src/pki_container_openssh.c +--- ../libssh-0.10.4/src/pki_container_openssh.c 2023-05-03 12:22:40.314114620 +0200 ++++ ./src/pki_container_openssh.c 2023-05-03 12:24:11.566110105 +0200 +@@ -630,7 +630,11 @@ + goto error; + } + +- ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); ++ rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(kdf_buf); ++ goto error; ++ } + kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); + if (kdf_options == NULL){ + SSH_BUFFER_FREE(kdf_buf); +diff --color -ru ../libssh-0.10.4/tests/unittests/torture_options.c ./tests/unittests/torture_options.c +--- ../libssh-0.10.4/tests/unittests/torture_options.c 2023-05-03 12:22:40.343114937 +0200 ++++ ./tests/unittests/torture_options.c 2023-05-03 12:24:11.587110335 +0200 +@@ -319,6 +319,7 @@ + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); ++ assert_int_not_equal(session->opts.port, 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +@@ -1496,6 +1497,26 @@ + ssh_list_free(awaited_list); + } + ++static void torture_options_set_verbosity (void **state) ++{ ++ ssh_session session = *state; ++ int rc, new_level; ++ ++ rc = ssh_options_set(session, ++ SSH_OPTIONS_LOG_VERBOSITY_STR, ++ "3"); ++ assert_int_equal(rc, SSH_OK); ++ new_level = ssh_get_log_level(); ++ assert_int_equal(new_level, SSH_LOG_PACKET); ++ ++ rc = ssh_options_set(session, ++ SSH_OPTIONS_LOG_VERBOSITY_STR, ++ "datsun"); ++ assert_int_equal(rc, -1); ++ new_level = ssh_get_log_level(); ++ assert_int_not_equal(new_level, 0); ++} ++ + #ifdef WITH_SERVER + const char template[] = "temp_dir_XXXXXX"; + +@@ -1750,6 +1771,10 @@ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 23); ++ ++ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "twentythree"); ++ assert_int_equal(rc, -1); ++ assert_int_not_equal(bind->bindport, 0); + } + + static void torture_bind_options_log_verbosity(void **state) +@@ -1799,6 +1824,11 @@ + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + ++ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "verbosity"); ++ assert_int_equal(rc, -1); ++ new_level = ssh_get_log_level(); ++ assert_int_not_equal(new_level, 0); ++ + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); + } +@@ -2297,6 +2327,7 @@ + cmocka_unit_test_setup_teardown(torture_options_caret_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), ++ cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), + }; + + #ifdef WITH_SERVER diff --git a/libssh.spec b/libssh.spec index 77d4e23..57dc841 100644 --- a/libssh.spec +++ b/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.10.4 -Release: 8%{?dist} +Release: 9%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -46,6 +46,10 @@ Patch4: plus_sign.patch Patch5: memory_leak.patch Patch6: options_apply.patch Patch7: enable_sk_keys_by_config.patch +Patch8: null_dereference_rekey.patch +Patch9: auth_bypass.patch +Patch10: covscan23.patch +Patch11: rekey_test_fixup.patch %description The ssh library was designed to be used by programmers needing a working SSH @@ -138,6 +142,11 @@ popd %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config %changelog +* Wed May 10 2023 Norbert Pocs - 0.10.4-9 +- Fix CVE-2023-1667 and CVE-2023-2283 +- Fix issues found by cosvcan +- Resolves: rhbz#2182252, rhbz#2189740 + * Mon Jan 23 2023 Stanislav Zidek - 0.10.4-8 + libssh-0.10.4-8 - Extended CI to run internal tests in RHEL diff --git a/null_dereference_rekey.patch b/null_dereference_rekey.patch new file mode 100644 index 0000000..f9dbc9b --- /dev/null +++ b/null_dereference_rekey.patch @@ -0,0 +1,1861 @@ +diff --color -ru ../libssh-0.10.4/include/libssh/curve25519.h ./include/libssh/curve25519.h +--- ../libssh-0.10.4/include/libssh/curve25519.h 2022-07-07 15:53:51.000000000 +0200 ++++ ./include/libssh/curve25519.h 2023-04-27 14:35:27.030113530 +0200 +@@ -48,6 +48,7 @@ + + + int ssh_client_curve25519_init(ssh_session session); ++void ssh_client_curve25519_remove_callbacks(ssh_session session); + + #ifdef WITH_SERVER + void ssh_server_curve25519_init(ssh_session session); +diff --color -ru ../libssh-0.10.4/include/libssh/dh-gex.h ./include/libssh/dh-gex.h +--- ../libssh-0.10.4/include/libssh/dh-gex.h 2022-07-07 15:53:51.000000000 +0200 ++++ ./include/libssh/dh-gex.h 2023-04-27 14:35:27.030113530 +0200 +@@ -24,6 +24,7 @@ + #define SRC_DH_GEX_H_ + + int ssh_client_dhgex_init(ssh_session session); ++void ssh_client_dhgex_remove_callbacks(ssh_session session); + + #ifdef WITH_SERVER + void ssh_server_dhgex_init(ssh_session session); +diff --color -ru ../libssh-0.10.4/include/libssh/dh.h ./include/libssh/dh.h +--- ../libssh-0.10.4/include/libssh/dh.h 2023-04-27 14:25:46.353381599 +0200 ++++ ./include/libssh/dh.h 2023-04-27 14:36:02.691475297 +0200 +@@ -73,8 +73,10 @@ + ssh_key ssh_dh_get_next_server_publickey(ssh_session session); + int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); ++int dh_handshake(ssh_session session); + + int ssh_client_dh_init(ssh_session session); ++void ssh_client_dh_remove_callbacks(ssh_session session); + #ifdef WITH_SERVER + void ssh_server_dh_init(ssh_session session); + #endif /* WITH_SERVER */ +diff --color -ru ../libssh-0.10.4/include/libssh/ecdh.h ./include/libssh/ecdh.h +--- ../libssh-0.10.4/include/libssh/ecdh.h 2022-07-07 15:53:51.000000000 +0200 ++++ ./include/libssh/ecdh.h 2023-04-27 14:35:27.030113530 +0200 +@@ -45,6 +45,7 @@ + extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; + /* Backend-specific functions. */ + int ssh_client_ecdh_init(ssh_session session); ++void ssh_client_ecdh_remove_callbacks(ssh_session session); + int ecdh_build_k(ssh_session session); + + #ifdef WITH_SERVER +diff --color -ru ../libssh-0.10.4/include/libssh/kex.h ./include/libssh/kex.h +--- ../libssh-0.10.4/include/libssh/kex.h 2023-04-27 14:25:46.346381527 +0200 ++++ ./include/libssh/kex.h 2023-04-27 14:34:36.438602800 +0200 +@@ -33,7 +33,7 @@ + + SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +-int ssh_send_kex(ssh_session session, int server_kex); ++int ssh_send_kex(ssh_session session); + void ssh_list_kex(struct ssh_kex_struct *kex); + int ssh_set_client_kex(ssh_session session); + int ssh_kex_select_methods(ssh_session session); +diff --color -ru ../libssh-0.10.4/include/libssh/session.h ./include/libssh/session.h +--- ../libssh-0.10.4/include/libssh/session.h 2023-04-27 14:25:46.361381682 +0200 ++++ ./include/libssh/session.h 2023-04-27 14:36:02.692475307 +0200 +@@ -76,6 +76,11 @@ + /* Client successfully authenticated */ + #define SSH_SESSION_FLAG_AUTHENTICATED 2 + ++/* The KEXINIT message can be sent first by either of the parties so this flag ++ * indicates that the message was already sent to make sure it is sent and avoid ++ * sending it twice during key exchange to simplify the state machine. */ ++#define SSH_SESSION_FLAG_KEXINIT_SENT 4 ++ + /* codes to use with ssh_handle_packets*() */ + /* Infinite timeout */ + #define SSH_TIMEOUT_INFINITE -1 +@@ -138,10 +143,8 @@ + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + +- ssh_string banner; /* that's the issue banner from +- the server */ +- char *discon_msg; /* disconnect message from +- the remote host */ ++ ssh_string banner; /* that's the issue banner from the server */ ++ char *discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ + ssh_buffer in_buffer; + PACKET in_packet; +@@ -166,25 +169,33 @@ + uint32_t current_method; + } auth; + ++ /* Sending this flag before key exchange to save one round trip during the ++ * key exchange. This might make sense on high-latency connections. ++ * So far internal only for testing. Usable only on the client side -- ++ * there is no key exchange method that would start with server message */ ++ bool send_first_kex_follows; + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will +- * be ignored. Once that packet has been received and ignored, +- * this field is cleared. ++ * be ignored on the receiving side. Once that packet has been received and ++ * ignored, this field is cleared. ++ * On the sending side, this is set after we got peer KEXINIT message and we ++ * need to resend the initial message of the negotiated KEX algorithm. + */ +- int first_kex_follows_guess_wrong; ++ bool first_kex_follows_guess_wrong; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; +- struct ssh_crypto_struct *next_crypto; /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ ++ /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ ++ struct ssh_crypto_struct *next_crypto; + + struct ssh_list *channels; /* linked list of channels */ + uint32_t maxchannel; + ssh_agent agent; /* ssh agent */ + +-/* keyb interactive data */ ++ /* keyboard interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + +@@ -201,7 +212,8 @@ + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ +- int (*ssh_message_callback)( struct ssh_session_struct *session, ssh_message msg, void *userdata); ++ int (*ssh_message_callback)(struct ssh_session_struct *session, ++ ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); +diff --color -ru ../libssh-0.10.4/src/client.c ./src/client.c +--- ../libssh-0.10.4/src/client.c 2023-04-27 14:25:46.339381455 +0200 ++++ ./src/client.c 2023-04-27 14:36:02.692475307 +0200 +@@ -246,10 +246,13 @@ + * @warning this function returning is no proof that DH handshake is + * completed + */ +-static int dh_handshake(ssh_session session) ++int dh_handshake(ssh_session session) + { + int rc = SSH_AGAIN; + ++ SSH_LOG(SSH_LOG_TRACE, "dh_handshake_state = %d, kex_type = %d", ++ session->dh_handshake_state, session->next_crypto->kex_type); ++ + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + switch(session->next_crypto->kex_type){ +@@ -392,96 +395,103 @@ + { + int rc; + +- switch(session->session_state) { +- case SSH_SESSION_STATE_NONE: +- case SSH_SESSION_STATE_CONNECTING: +- break; +- case SSH_SESSION_STATE_SOCKET_CONNECTED: +- ssh_set_fd_towrite(session); +- ssh_send_banner(session, 0); +- +- break; +- case SSH_SESSION_STATE_BANNER_RECEIVED: +- if (session->serverbanner == NULL) { +- goto error; +- } +- set_status(session, 0.4f); +- SSH_LOG(SSH_LOG_DEBUG, +- "SSH server banner: %s", session->serverbanner); ++ SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); + +- /* Here we analyze the different protocols the server allows. */ +- rc = ssh_analyze_banner(session, 0); +- if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, +- "No version of SSH protocol usable (banner: %s)", +- session->serverbanner); +- goto error; +- } ++ switch (session->session_state) { ++ case SSH_SESSION_STATE_NONE: ++ case SSH_SESSION_STATE_CONNECTING: ++ break; ++ case SSH_SESSION_STATE_SOCKET_CONNECTED: ++ ssh_set_fd_towrite(session); ++ ssh_send_banner(session, 0); ++ ++ break; ++ case SSH_SESSION_STATE_BANNER_RECEIVED: ++ if (session->serverbanner == NULL) { ++ goto error; ++ } ++ set_status(session, 0.4f); ++ SSH_LOG(SSH_LOG_PROTOCOL, ++ "SSH server banner: %s", session->serverbanner); ++ ++ /* Here we analyze the different protocols the server allows. */ ++ rc = ssh_analyze_banner(session, 0); ++ if (rc < 0) { ++ ssh_set_error(session, SSH_FATAL, ++ "No version of SSH protocol usable (banner: %s)", ++ session->serverbanner); ++ goto error; ++ } + +- ssh_packet_register_socket_callback(session, session->socket); ++ ssh_packet_register_socket_callback(session, session->socket); ++ ++ ssh_packet_set_default_callbacks(session); ++ session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ rc = ssh_set_client_kex(session); ++ if (rc != SSH_OK) { ++ goto error; ++ } ++ rc = ssh_send_kex(session); ++ if (rc < 0) { ++ goto error; ++ } ++ set_status(session, 0.5f); + +- ssh_packet_set_default_callbacks(session); +- session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ break; ++ case SSH_SESSION_STATE_INITIAL_KEX: ++ /* TODO: This state should disappear in favor of get_key handle */ ++ break; ++ case SSH_SESSION_STATE_KEXINIT_RECEIVED: ++ set_status(session, 0.6f); ++ ssh_list_kex(&session->next_crypto->server_kex); ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { ++ /* in rekeying state if next_crypto client_kex might be empty */ + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } +- rc = ssh_send_kex(session, 0); ++ rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } +- set_status(session, 0.5f); ++ } ++ if (ssh_kex_select_methods(session) == SSH_ERROR) ++ goto error; ++ set_status(session, 0.8f); ++ session->session_state = SSH_SESSION_STATE_DH; + +- break; +- case SSH_SESSION_STATE_INITIAL_KEX: +- /* TODO: This state should disappear in favor of get_key handle */ +- break; +- case SSH_SESSION_STATE_KEXINIT_RECEIVED: +- set_status(session,0.6f); +- ssh_list_kex(&session->next_crypto->server_kex); +- if (session->next_crypto->client_kex.methods[0] == NULL) { +- /* in rekeying state if next_crypto client_kex is empty */ +- rc = ssh_set_client_kex(session); +- if (rc != SSH_OK) { +- goto error; +- } +- rc = ssh_send_kex(session, 0); +- if (rc < 0) { +- goto error; +- } +- } +- if (ssh_kex_select_methods(session) == SSH_ERROR) +- goto error; +- set_status(session,0.8f); +- session->session_state=SSH_SESSION_STATE_DH; +- if (dh_handshake(session) == SSH_ERROR) { +- goto error; +- } +- FALL_THROUGH; +- case SSH_SESSION_STATE_DH: +- if(session->dh_handshake_state==DH_STATE_FINISHED){ +- set_status(session,1.0f); +- session->connected = 1; +- if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) +- session->session_state = SSH_SESSION_STATE_AUTHENTICATED; +- else +- session->session_state=SSH_SESSION_STATE_AUTHENTICATING; +- } +- break; +- case SSH_SESSION_STATE_AUTHENTICATING: +- break; +- case SSH_SESSION_STATE_ERROR: ++ /* If the init packet was already sent in previous step, this will be no ++ * operation */ ++ if (dh_handshake(session) == SSH_ERROR) { + goto error; +- default: +- ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); ++ } ++ FALL_THROUGH; ++ case SSH_SESSION_STATE_DH: ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { ++ set_status(session, 1.0f); ++ session->connected = 1; ++ if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATED; ++ else ++ session->session_state=SSH_SESSION_STATE_AUTHENTICATING; ++ } ++ break; ++ case SSH_SESSION_STATE_AUTHENTICATING: ++ break; ++ case SSH_SESSION_STATE_ERROR: ++ goto error; ++ default: ++ ssh_set_error(session, SSH_FATAL, "Invalid state %d", ++ session->session_state); + } + + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; +- session->session_state=SSH_SESSION_STATE_ERROR; ++ session->session_state = SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); ++ + } + + /** @internal +diff --color -ru ../libssh-0.10.4/src/curve25519.c ./src/curve25519.c +--- ../libssh-0.10.4/src/curve25519.c 2023-04-27 14:25:46.333381393 +0200 ++++ ./src/curve25519.c 2023-04-27 14:35:27.031113541 +0200 +@@ -172,6 +172,11 @@ + return rc; + } + ++void ssh_client_curve25519_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); ++} ++ + static int ssh_curve25519_build_k(ssh_session session) + { + ssh_curve25519_pubkey k; +@@ -285,7 +290,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); ++ ssh_client_curve25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { +diff --color -ru ../libssh-0.10.4/src/dh.c ./src/dh.c +--- ../libssh-0.10.4/src/dh.c 2023-04-27 14:25:46.333381393 +0200 ++++ ./src/dh.c 2023-04-27 14:35:27.032113551 +0200 +@@ -352,6 +352,11 @@ + return SSH_ERROR; + } + ++void ssh_client_dh_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); ++} ++ + SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL; +@@ -361,7 +366,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); ++ ssh_client_dh_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); +diff --color -ru ../libssh-0.10.4/src/dh-gex.c ./src/dh-gex.c +--- ../libssh-0.10.4/src/dh-gex.c 2023-04-27 14:25:46.333381393 +0200 ++++ ./src/dh-gex.c 2023-04-27 14:35:27.031113541 +0200 +@@ -248,6 +248,11 @@ + return SSH_PACKET_USED; + } + ++void ssh_client_dhgex_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); ++} ++ + static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) + { + struct ssh_crypto_struct *crypto=session->next_crypto; +@@ -258,7 +263,7 @@ + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); + +- ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); ++ ssh_client_dhgex_remove_callbacks(session); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &server_pubkey, +diff --color -ru ../libssh-0.10.4/src/ecdh.c ./src/ecdh.c +--- ../libssh-0.10.4/src/ecdh.c 2023-04-27 14:25:46.333381393 +0200 ++++ ./src/ecdh.c 2023-04-27 14:35:27.032113551 +0200 +@@ -43,6 +43,11 @@ + .user = NULL + }; + ++void ssh_client_ecdh_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); ++} ++ + /** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS +@@ -55,7 +60,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); ++ ssh_client_ecdh_remove_callbacks(session); + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); +diff --color -ru ../libssh-0.10.4/src/gssapi.c ./src/gssapi.c +--- ../libssh-0.10.4/src/gssapi.c 2023-04-27 14:25:46.333381393 +0200 ++++ ./src/gssapi.c 2023-04-27 14:29:22.606512115 +0200 +@@ -229,6 +229,7 @@ + "indicate mechs", + maj_stat, + min_stat); ++ gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + +@@ -265,8 +266,10 @@ + return SSH_OK; + } + /* from now we have room for context */ +- if (ssh_gssapi_init(session) == SSH_ERROR) ++ if (ssh_gssapi_init(session) == SSH_ERROR) { ++ gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; ++ } + + name_buf.value = service_name; + name_buf.length = strlen(name_buf.value) + 1; +@@ -278,6 +281,7 @@ + "importing name", + maj_stat, + min_stat); ++ gss_release_oid_set(&min_stat, &both_supported); + return -1; + } + +@@ -345,6 +349,7 @@ + min_stat); + ptr = malloc(buffer.length + 1); + if (ptr == NULL) { ++ gss_release_buffer(&min_stat, &buffer); + return NULL; + } + memcpy(ptr, buffer.value, buffer.length); +@@ -428,6 +433,7 @@ + "Gssapi error", + maj_stat, + min_stat); ++ gss_release_buffer(&min_stat, &output_token); + ssh_auth_reply_default(session,0); + ssh_gssapi_free(session); + session->gssapi=NULL; +@@ -445,6 +451,9 @@ + (size_t)output_token.length, output_token.value); + ssh_packet_send(session); + } ++ ++ gss_release_buffer(&min_stat, &output_token); ++ + if(maj_stat == GSS_S_COMPLETE){ + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } +@@ -648,7 +657,7 @@ + static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) + { + OM_uint32 maj_stat, min_stat, lifetime; +- gss_OID_set actual_mechs; ++ gss_OID_set actual_mechs = GSS_C_NO_OID_SET; + gss_buffer_desc namebuf; + gss_name_t client_id = GSS_C_NO_NAME; + gss_OID oid; +@@ -710,6 +719,7 @@ + ret = SSH_OK; + + end: ++ gss_release_oid_set(&min_stat, &actual_mechs); + gss_release_name(&min_stat, &client_id); + return ret; + } +@@ -724,7 +734,7 @@ + int ssh_gssapi_auth_mic(ssh_session session) + { + size_t i; +- gss_OID_set selected; /* oid selected for authentication */ ++ gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; +@@ -801,6 +811,8 @@ + SSH_STRING_FREE(oids[i]); + } + free(oids); ++ gss_release_oid_set(&min_stat, &selected); ++ + if (rc != SSH_ERROR) { + return SSH_AUTH_AGAIN; + } +@@ -904,6 +916,8 @@ + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } ++ ++ gss_release_buffer(&min_stat, &output_token); + return SSH_PACKET_USED; + + error: +@@ -933,8 +947,10 @@ + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); ++ ++ SSH_BUFFER_FREE(mic_buffer); ++ + if (GSS_ERROR(maj_stat)){ +- SSH_BUFFER_FREE(mic_buffer); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, +@@ -947,8 +963,10 @@ + SSH2_MSG_USERAUTH_GSSAPI_MIC, + mic_token_buf.length, + (size_t)mic_token_buf.length, mic_token_buf.value); ++ ++ gss_release_buffer(&min_stat, &mic_token_buf); ++ + if (rc != SSH_OK) { +- SSH_BUFFER_FREE(mic_buffer); + ssh_set_error_oom(session); + return SSH_ERROR; + } +@@ -1017,6 +1035,8 @@ + ssh_packet_send(session); + } + ++ gss_release_buffer(&min_stat, &output_token); ++ + if (maj_stat == GSS_S_COMPLETE) { + ssh_gssapi_send_mic(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; +diff --color -ru ../libssh-0.10.4/src/kex.c ./src/kex.c +--- ../libssh-0.10.4/src/kex.c 2023-04-27 14:25:46.368381754 +0200 ++++ ./src/kex.c 2023-04-27 14:36:02.692475307 +0200 +@@ -28,6 +28,7 @@ + #include + #include + ++#include "libssh/libssh.h" + #include "libssh/priv.h" + #include "libssh/buffer.h" + #include "libssh/dh.h" +@@ -328,6 +329,10 @@ + + int is_wrong = 1; + ++ if (client_str == NULL || server_str == NULL) { ++ return is_wrong; ++ } ++ + colon = strchr(client_str, ','); + if (colon == NULL) { + client_kex_len = strlen(client_str); +@@ -354,6 +359,7 @@ + SSH_PACKET_CALLBACK(ssh_packet_kexinit) + { + int i, ok; ++ struct ssh_crypto_struct *crypto = session->next_crypto; + int server_kex = session->server; + ssh_string str = NULL; + char *strings[SSH_KEX_METHODS] = {0}; +@@ -367,35 +373,67 @@ + (void)type; + (void)user; + ++ SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); ++ + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { +- SSH_LOG(SSH_LOG_DEBUG, "Initiating key re-exchange"); ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { ++ SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); ++ /* Reset the sent flag if the re-kex was initiated by the peer */ ++ session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; ++ } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && ++ session->dh_handshake_state == DH_STATE_INIT_SENT) { ++ /* This happens only when we are sending our-guessed first kex ++ * packet right after our KEXINIT packet. */ ++ SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); ++ } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { ++ ssh_set_error(session, SSH_FATAL, ++ "SSH_KEXINIT received in wrong state"); ++ goto error; ++ } + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { +- ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); ++ ssh_set_error(session, SSH_FATAL, ++ "SSH_KEXINIT received in wrong state"); + goto error; + } + + if (server_kex) { +- len = ssh_buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); ++#ifdef WITH_SERVER ++ len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); + if (len != 16) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + +- ok = ssh_hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie); ++ ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); + if (ok < 0) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: adding cookie failed"); ++ goto error; ++ } ++ ++ ok = server_set_kex(session); ++ if (ok == SSH_ERROR) { + goto error; + } ++#endif + } else { +- len = ssh_buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); ++ len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); + if (len != 16) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + +- ok = ssh_hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie); ++ ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); + if (ok < 0) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: adding cookie failed"); ++ goto error; ++ } ++ ++ ok = ssh_set_client_kex(session); ++ if (ok == SSH_ERROR) { + goto error; + } + } +@@ -408,7 +446,8 @@ + + rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); + if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); ++ ssh_set_error(session, SSH_FATAL, ++ "Error adding string in hash buffer"); + goto error; + } + +@@ -421,14 +460,14 @@ + str = NULL; + } + +- /* copy the server kex info into an array of strings */ ++ /* copy the peer kex info into an array of strings */ + if (server_kex) { + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->client_kex.methods[i] = strings[i]; ++ crypto->client_kex.methods[i] = strings[i]; + } + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->server_kex.methods[i] = strings[i]; ++ crypto->server_kex.methods[i] = strings[i]; + } + } + +@@ -442,30 +481,48 @@ + * that its value is included when computing the session ID (see + * 'make_sessionid'). + */ +- if (server_kex) { +- rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); +- if (rc != 1) { +- goto error; +- } ++ rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); ++ if (rc != 1) { ++ goto error; ++ } + +- rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); +- if (rc < 0) { +- goto error; +- } ++ rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); ++ if (rc < 0) { ++ goto error; ++ } + +- rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); +- if (rc < 0) { +- goto error; +- } ++ rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); ++ if (rc < 0) { ++ goto error; ++ } ++ ++ /* ++ * Remember whether 'first_kex_packet_follows' was set and the client ++ * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message ++ * must be ignored on the server side. ++ * Client needs to start the Key exchange over with the correct method ++ */ ++ if (first_kex_packet_follows || session->send_first_kex_follows) { ++ char **client_methods = crypto->client_kex.methods; ++ char **server_methods = crypto->server_kex.methods; ++ session->first_kex_follows_guess_wrong = ++ cmp_first_kex_algo(client_methods[SSH_KEX], ++ server_methods[SSH_KEX]) || ++ cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], ++ server_methods[SSH_HOSTKEYS]); ++ SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", ++ session->first_kex_follows_guess_wrong ? "wrong" : "right"); ++ } + ++ if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports + * RFC 8308 extension negotiation. + */ +- ok = ssh_match_group(session->next_crypto->client_kex.methods[SSH_KEX], ++ ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], + KEX_EXTENSION_CLIENT); + if (ok) { +- const char *hostkeys = NULL; ++ const char *hostkeys = NULL, *wanted_hostkeys = NULL; + + /* The client supports extension negotiation */ + session->extensions |= SSH_EXT_NEGOTIATION; +@@ -475,14 +532,14 @@ + * by the client and enable the respective extensions to provide + * correct signature in the next packet if RSA is negotiated + */ +- hostkeys = session->next_crypto->client_kex.methods[SSH_HOSTKEYS]; ++ hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; ++ wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; + ok = ssh_match_group(hostkeys, "rsa-sha2-512"); + if (ok) { + /* Check if rsa-sha2-512 is allowed by config */ +- if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { +- char *is_allowed = +- ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], +- "rsa-sha2-512"); ++ if (wanted_hostkeys != NULL) { ++ char *is_allowed = ssh_find_matching(wanted_hostkeys, ++ "rsa-sha2-512"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } +@@ -492,10 +549,9 @@ + ok = ssh_match_group(hostkeys, "rsa-sha2-256"); + if (ok) { + /* Check if rsa-sha2-256 is allowed by config */ +- if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { +- char *is_allowed = +- ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], +- "rsa-sha2-256"); ++ if (wanted_hostkeys != NULL) { ++ char *is_allowed = ssh_find_matching(wanted_hostkeys, ++ "rsa-sha2-256"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } +@@ -511,7 +567,7 @@ + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); + rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", +- session->next_crypto->client_kex.methods[SSH_HOSTKEYS]); ++ hostkeys); + if (rsa_sig_ext == NULL) { + goto error; /* should never happen */ + } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { +@@ -530,24 +586,16 @@ + session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", + session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); + } +- +- /* +- * Remember whether 'first_kex_packet_follows' was set and the client +- * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message +- * must be ignored. +- */ +- if (first_kex_packet_follows) { +- session->first_kex_follows_guess_wrong = +- cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_KEX], +- session->next_crypto->server_kex.methods[SSH_KEX]) || +- cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_HOSTKEYS], +- session->next_crypto->server_kex.methods[SSH_HOSTKEYS]); +- } + } + + /* Note, that his overwrites authenticated state in case of rekeying */ + session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; +- session->dh_handshake_state = DH_STATE_INIT; ++ /* if we already sent our initial key exchange packet, do not reset the ++ * DH state. We will know if we were right with our guess only in ++ * dh_handshake_state() */ ++ if (session->send_first_kex_follows == false) { ++ session->dh_handshake_state = DH_STATE_INIT; ++ } + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +@@ -695,14 +743,18 @@ + int i; + size_t kex_len, len; + ++ /* Skip if already set, for example for the rekey or when we do the guessing ++ * it could have been already used to make some protocol decisions. */ ++ if (client->methods[0] != NULL) { ++ return SSH_OK; ++ } ++ + ok = ssh_get_random(client->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + +- memset(client->methods, 0, SSH_KEX_METHODS * sizeof(char **)); +- + /* Set the list of allowed algorithms in order of preference, if it hadn't + * been set yet. */ + for (i = 0; i < SSH_KEX_METHODS; i++) { +@@ -772,15 +824,89 @@ + return NULL; + } + ++static enum ssh_key_exchange_e ++kex_select_kex_type(const char *kex) ++{ ++ if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { ++ return SSH_KEX_DH_GROUP1_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { ++ return SSH_KEX_DH_GROUP14_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { ++ return SSH_KEX_DH_GROUP14_SHA256; ++ } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { ++ return SSH_KEX_DH_GROUP16_SHA512; ++ } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { ++ return SSH_KEX_DH_GROUP18_SHA512; ++#ifdef WITH_GEX ++ } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { ++ return SSH_KEX_DH_GEX_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { ++ return SSH_KEX_DH_GEX_SHA256; ++#endif /* WITH_GEX */ ++ } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP256; ++ } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP384; ++ } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP521; ++ } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { ++ return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; ++ } else if (strcmp(kex, "curve25519-sha256") == 0) { ++ return SSH_KEX_CURVE25519_SHA256; ++ } ++ /* should not happen. We should be getting only valid names at this stage */ ++ return 0; ++} ++ ++ ++/** @internal ++ * @brief Reverts guessed callbacks set during the dh_handshake() ++ * @param session session handle ++ * @returns void ++ */ ++static void revert_kex_callbacks(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_GROUP14_SHA256: ++ case SSH_KEX_DH_GROUP16_SHA512: ++ case SSH_KEX_DH_GROUP18_SHA512: ++ ssh_client_dh_remove_callbacks(session); ++ break; ++#ifdef WITH_GEX ++ case SSH_KEX_DH_GEX_SHA1: ++ case SSH_KEX_DH_GEX_SHA256: ++ ssh_client_dhgex_remove_callbacks(session); ++ break; ++#endif /* WITH_GEX */ ++#ifdef HAVE_ECDH ++ case SSH_KEX_ECDH_SHA2_NISTP256: ++ case SSH_KEX_ECDH_SHA2_NISTP384: ++ case SSH_KEX_ECDH_SHA2_NISTP521: ++ ssh_client_ecdh_remove_callbacks(session); ++ break; ++#endif ++#ifdef HAVE_CURVE25519 ++ case SSH_KEX_CURVE25519_SHA256: ++ case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: ++ ssh_client_curve25519_remove_callbacks(session); ++ break; ++#endif ++ } ++} ++ + /** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ + int ssh_kex_select_methods (ssh_session session) + { +- struct ssh_kex_struct *server = &session->next_crypto->server_kex; +- struct ssh_kex_struct *client = &session->next_crypto->client_kex; ++ struct ssh_crypto_struct *crypto = session->next_crypto; ++ struct ssh_kex_struct *server = &crypto->server_kex; ++ struct ssh_kex_struct *client = &crypto->client_kex; + char *ext_start = NULL; + const char *aead_hmac = NULL; ++ enum ssh_key_exchange_e kex_type; + int i; + + /* Here we should drop the ext-info-c from the list so we avoid matching. +@@ -791,52 +917,41 @@ + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); ++ crypto->kex_methods[i] = ssh_find_matching(server->methods[i], ++ client->methods[i]); + + if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { +- aead_hmac = ssh_find_aead_hmac(session->next_crypto->kex_methods[i-2]); ++ aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); + if (aead_hmac) { +- free(session->next_crypto->kex_methods[i]); +- session->next_crypto->kex_methods[i] = strdup(aead_hmac); ++ free(crypto->kex_methods[i]); ++ crypto->kex_methods[i] = strdup(aead_hmac); + } + } +- if (session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ +- ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", +- ssh_kex_descriptions[i],server->methods[i],client->methods[i]); ++ if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { ++ ssh_set_error(session, SSH_FATAL, ++ "kex error : no match for method %s: server [%s], " ++ "client [%s]", ssh_kex_descriptions[i], ++ server->methods[i], client->methods[i]); + return SSH_ERROR; +- } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { ++ } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ +- session->next_crypto->kex_methods[i] = strdup(""); ++ crypto->kex_methods[i] = strdup(""); + } + } +- if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA256; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group16-sha512") == 0){ +- 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; +-#ifdef WITH_GEX +- } 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; +-#endif /* WITH_GEX */ +- } 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){ +- session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP384; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp521") == 0){ +- session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP521; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256@libssh.org") == 0){ +- session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256; ++ ++ /* We can not set this value directly as the old value is needed to revert ++ * callbacks if we are client */ ++ kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); ++ if (session->client && session->first_kex_follows_guess_wrong) { ++ SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); ++ /* We need to remove the wrong callbacks and start kex again */ ++ revert_kex_callbacks(session); ++ session->dh_handshake_state = DH_STATE_INIT; ++ session->first_kex_follows_guess_wrong = false; + } +- SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", ++ crypto->kex_type = kex_type; ++ ++ SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + session->next_crypto->kex_methods[SSH_KEX], + session->next_crypto->kex_methods[SSH_HOSTKEYS], + session->next_crypto->kex_methods[SSH_CRYPT_C_S], +@@ -853,63 +968,116 @@ + + + /* this function only sends the predefined set of kex methods */ +-int ssh_send_kex(ssh_session session, int server_kex) ++int ssh_send_kex(ssh_session session) + { +- struct ssh_kex_struct *kex = (server_kex ? &session->next_crypto->server_kex : +- &session->next_crypto->client_kex); +- ssh_string str = NULL; +- int i; +- int rc; +- +- rc = ssh_buffer_pack(session->out_buffer, +- "bP", +- SSH2_MSG_KEXINIT, +- 16, +- kex->cookie); /* cookie */ +- if (rc != SSH_OK) +- goto error; +- if (ssh_hashbufout_add_cookie(session) < 0) { +- goto error; +- } ++ struct ssh_kex_struct *kex = (session->server ? ++ &session->next_crypto->server_kex : ++ &session->next_crypto->client_kex); ++ ssh_string str = NULL; ++ int i; ++ int rc; ++ int first_kex_packet_follows = 0; ++ ++ /* Only client can initiate the handshake methods we implement. If we ++ * already received the peer mechanisms, there is no point in guessing */ ++ if (session->client && ++ session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && ++ session->send_first_kex_follows) { ++ first_kex_packet_follows = 1; ++ } ++ ++ SSH_LOG(SSH_LOG_TRACE, ++ "Sending KEXINIT packet, first_kex_packet_follows = %d", ++ first_kex_packet_follows); ++ ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bP", ++ SSH2_MSG_KEXINIT, ++ 16, ++ kex->cookie); /* cookie */ ++ if (rc != SSH_OK) ++ goto error; ++ if (ssh_hashbufout_add_cookie(session) < 0) { ++ goto error; ++ } ++ ++ ssh_list_kex(kex); + +- ssh_list_kex(kex); ++ for (i = 0; i < SSH_KEX_METHODS; i++) { ++ str = ssh_string_from_char(kex->methods[i]); ++ if (str == NULL) { ++ goto error; ++ } ++ ++ rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); ++ if (rc < 0) { ++ goto error; ++ } ++ rc = ssh_buffer_add_ssh_string(session->out_buffer, str); ++ if (rc < 0) { ++ goto error; ++ } ++ SSH_STRING_FREE(str); ++ str = NULL; ++ } + +- for (i = 0; i < SSH_KEX_METHODS; i++) { +- str = ssh_string_from_char(kex->methods[i]); +- if (str == NULL) { +- goto error; ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bd", ++ first_kex_packet_follows, ++ 0); ++ if (rc != SSH_OK) { ++ goto error; + } + +- if (ssh_buffer_add_ssh_string(session->out_hashbuf, str) < 0) { +- goto error; ++ /* Prepare also the first_kex_packet_follows and reserved to 0 */ ++ rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); ++ if (rc < 0) { ++ goto error; + } +- if (ssh_buffer_add_ssh_string(session->out_buffer, str) < 0) { +- goto error; ++ rc = ssh_buffer_add_u32(session->out_hashbuf, 0); ++ if (rc < 0) { ++ goto error; + } +- SSH_STRING_FREE(str); +- str = NULL; +- } + +- rc = ssh_buffer_pack(session->out_buffer, +- "bd", +- 0, +- 0); +- if (rc != SSH_OK) { +- goto error; +- } ++ rc = ssh_packet_send(session); ++ if (rc == SSH_ERROR) { ++ return -1; ++ } + +- if (ssh_packet_send(session) == SSH_ERROR) { +- return -1; +- } ++ session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; ++ SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); ++ ++ /* If we indicated that we are sending the guessed key exchange packet, ++ * do it now. The packet is simple, but we need to do some preparations */ ++ if (first_kex_packet_follows) { ++ char *list = kex->methods[SSH_KEX]; ++ char *colon = strchr(list, ','); ++ size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); ++ char *kex_name = calloc(kex_name_len + 1, 1); ++ if (kex_name == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); ++ SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); ++ ++ session->next_crypto->kex_type = kex_select_kex_type(kex_name); ++ free(kex_name); ++ ++ /* run the first step of the DH handshake */ ++ session->dh_handshake_state = DH_STATE_INIT; ++ if (dh_handshake(session) == SSH_ERROR) { ++ goto error; ++ } ++ } ++ return 0; + +- SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); +- return 0; + error: +- ssh_buffer_reinit(session->out_buffer); +- ssh_buffer_reinit(session->out_hashbuf); +- SSH_STRING_FREE(str); ++ ssh_buffer_reinit(session->out_buffer); ++ ssh_buffer_reinit(session->out_hashbuf); ++ SSH_STRING_FREE(str); + +- return -1; ++ return -1; + } + + /* +@@ -952,7 +1120,7 @@ + } + + session->dh_handshake_state = DH_STATE_INIT; +- rc = ssh_send_kex(session, session->server); ++ rc = ssh_send_kex(session); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); + return rc; +@@ -1142,33 +1310,6 @@ + client_hash = session->in_hashbuf; + } + +- /* +- * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): +- * +- * boolean first_kex_packet_follows +- * uint32 0 (reserved for future extension) +- */ +- rc = ssh_buffer_add_u8(server_hash, 0); +- if (rc < 0) { +- goto error; +- } +- rc = ssh_buffer_add_u32(server_hash, 0); +- if (rc < 0) { +- goto error; +- } +- +- /* These fields are handled for the server case in ssh_packet_kexinit. */ +- if (session->client) { +- rc = ssh_buffer_add_u8(client_hash, 0); +- if (rc < 0) { +- goto error; +- } +- rc = ssh_buffer_add_u32(client_hash, 0); +- if (rc < 0) { +- goto error; +- } +- } +- + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; +diff --color -ru ../libssh-0.10.4/src/packet.c ./src/packet.c +--- ../libssh-0.10.4/src/packet.c 2023-04-27 14:25:46.334381403 +0200 ++++ ./src/packet.c 2023-04-27 14:34:36.432602740 +0200 +@@ -366,6 +366,11 @@ + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + ++ if (!session->server) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; +@@ -1410,18 +1415,23 @@ + } + } + +-void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){ +- session->socket_callbacks.data=ssh_packet_socket_callback; +- session->socket_callbacks.connected=NULL; +- session->socket_callbacks.controlflow = ssh_packet_socket_controlflow_callback; +- session->socket_callbacks.userdata=session; +- ssh_socket_set_callbacks(s,&session->socket_callbacks); ++void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) ++{ ++ struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; ++ ++ callbacks->data = ssh_packet_socket_callback; ++ callbacks->connected = NULL; ++ callbacks->controlflow = ssh_packet_socket_controlflow_callback; ++ callbacks->userdata = session; ++ ssh_socket_set_callbacks(s, callbacks); + } + + /** @internal + * @brief sets the callbacks for the packet layer + */ +-void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ ++void ++ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks) ++{ + if (session->packet_callbacks == NULL) { + session->packet_callbacks = ssh_list_new(); + if (session->packet_callbacks == NULL) { +@@ -1435,8 +1445,11 @@ + /** @internal + * @brief remove the callbacks from the packet layer + */ +-void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ ++void ++ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) ++{ + struct ssh_iterator *it = NULL; ++ + it = ssh_list_find(session->packet_callbacks, callbacks); + if (it != NULL) { + ssh_list_remove(session->packet_callbacks, it); +@@ -1446,12 +1459,15 @@ + /** @internal + * @brief sets the default packet handlers + */ +-void ssh_packet_set_default_callbacks(ssh_session session){ +- session->default_packet_callbacks.start=1; +- session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); +- session->default_packet_callbacks.user=session; +- session->default_packet_callbacks.callbacks=default_packet_handlers; +- ssh_packet_set_callbacks(session, &session->default_packet_callbacks); ++void ssh_packet_set_default_callbacks(ssh_session session) ++{ ++ struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; ++ ++ c->start = 1; ++ c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); ++ c->user = session; ++ c->callbacks = default_packet_handlers; ++ ssh_packet_set_callbacks(session, c); + } + + /** @internal +@@ -1930,7 +1946,7 @@ + memcpy(session->next_crypto->session_id, + session->current_crypto->session_id, + session_id_len); +- session->next_crypto->session_id_len = session_id_len; ++ session->next_crypto->session_id_len = session_id_len; + + return SSH_OK; + } +diff --color -ru ../libssh-0.10.4/src/packet_cb.c ./src/packet_cb.c +--- ../libssh-0.10.4/src/packet_cb.c 2023-04-27 14:25:46.334381403 +0200 ++++ ./src/packet_cb.c 2023-04-27 14:34:36.428602699 +0200 +@@ -156,6 +156,9 @@ + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Failed to verify server hostkey signature"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG,"Signature verified and valid"); +diff --color -ru ../libssh-0.10.4/src/server.c ./src/server.c +--- ../libssh-0.10.4/src/server.c 2023-04-27 14:25:46.347381537 +0200 ++++ ./src/server.c 2023-04-27 14:35:27.041113642 +0200 +@@ -92,7 +92,11 @@ + size_t len; + int ok; + +- ZERO_STRUCTP(server); ++ /* Skip if already set, for example for the rekey or when we do the guessing ++ * it could have been already used to make some protocol decisions. */ ++ if (server->methods[0] != NULL) { ++ return SSH_OK; ++ } + + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { +@@ -336,116 +340,119 @@ + * @brief A function to be called each time a step has been done in the + * connection. + */ +-static void ssh_server_connection_callback(ssh_session session){ ++static void ssh_server_connection_callback(ssh_session session) ++{ + int rc; + +- switch(session->session_state){ +- case SSH_SESSION_STATE_NONE: +- case SSH_SESSION_STATE_CONNECTING: +- case SSH_SESSION_STATE_SOCKET_CONNECTED: +- break; +- case SSH_SESSION_STATE_BANNER_RECEIVED: +- if (session->clientbanner == NULL) { +- goto error; +- } +- set_status(session, 0.4f); +- SSH_LOG(SSH_LOG_DEBUG, +- "SSH client banner: %s", session->clientbanner); +- +- /* Here we analyze the different protocols the server allows. */ +- rc = ssh_analyze_banner(session, 1); +- if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, +- "No version of SSH protocol usable (banner: %s)", +- session->clientbanner); +- goto error; +- } ++ switch (session->session_state) { ++ case SSH_SESSION_STATE_NONE: ++ case SSH_SESSION_STATE_CONNECTING: ++ case SSH_SESSION_STATE_SOCKET_CONNECTED: ++ break; ++ case SSH_SESSION_STATE_BANNER_RECEIVED: ++ if (session->clientbanner == NULL) { ++ goto error; ++ } ++ set_status(session, 0.4f); ++ SSH_LOG(SSH_LOG_PROTOCOL, ++ "SSH client banner: %s", session->clientbanner); ++ ++ /* Here we analyze the different protocols the server allows. */ ++ rc = ssh_analyze_banner(session, 1); ++ if (rc < 0) { ++ ssh_set_error(session, SSH_FATAL, ++ "No version of SSH protocol usable (banner: %s)", ++ session->clientbanner); ++ goto error; ++ } + +- /* from now, the packet layer is handling incoming packets */ +- ssh_packet_register_socket_callback(session, session->socket); ++ /* from now, the packet layer is handling incoming packets */ ++ ssh_packet_register_socket_callback(session, session->socket); + +- ssh_packet_set_default_callbacks(session); +- set_status(session, 0.5f); +- session->session_state=SSH_SESSION_STATE_INITIAL_KEX; +- if (ssh_send_kex(session, 1) < 0) { ++ ssh_packet_set_default_callbacks(session); ++ set_status(session, 0.5f); ++ session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ if (ssh_send_kex(session) < 0) { ++ goto error; ++ } ++ break; ++ case SSH_SESSION_STATE_INITIAL_KEX: ++ /* TODO: This state should disappear in favor of get_key handle */ ++ break; ++ case SSH_SESSION_STATE_KEXINIT_RECEIVED: ++ set_status(session, 0.6f); ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { ++ if (server_set_kex(session) == SSH_ERROR) + goto error; +- } +- break; +- case SSH_SESSION_STATE_INITIAL_KEX: +- /* TODO: This state should disappear in favor of get_key handle */ +- break; +- case SSH_SESSION_STATE_KEXINIT_RECEIVED: +- set_status(session,0.6f); +- if(session->next_crypto->server_kex.methods[0]==NULL){ +- if(server_set_kex(session) == SSH_ERROR) +- goto error; +- /* We are in a rekeying, so we need to send the server kex */ +- if(ssh_send_kex(session, 1) < 0) +- goto error; +- } +- ssh_list_kex(&session->next_crypto->client_kex); // log client kex +- if (ssh_kex_select_methods(session) < 0) { ++ /* We are in a rekeying, so we need to send the server kex */ ++ if (ssh_send_kex(session) < 0) + goto error; +- } +- if (crypt_set_algorithms_server(session) == SSH_ERROR) ++ } ++ ssh_list_kex(&session->next_crypto->client_kex); // log client kex ++ if (ssh_kex_select_methods(session) < 0) { ++ goto error; ++ } ++ if (crypt_set_algorithms_server(session) == SSH_ERROR) ++ goto error; ++ set_status(session, 0.8f); ++ session->session_state = SSH_SESSION_STATE_DH; ++ break; ++ case SSH_SESSION_STATE_DH: ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { ++ ++ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); ++ if (rc != SSH_OK) { + goto error; +- set_status(session,0.8f); +- session->session_state=SSH_SESSION_STATE_DH; +- break; +- case SSH_SESSION_STATE_DH: +- if(session->dh_handshake_state==DH_STATE_FINISHED){ +- +- rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); +- if (rc != SSH_OK) { +- goto error; +- } ++ } + ++ /* ++ * If the client supports extension negotiation, we will send ++ * our supported extensions now. This is the first message after ++ * sending NEWKEYS message and after turning on crypto. ++ */ ++ if (session->extensions & SSH_EXT_NEGOTIATION && ++ session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + /* +- * If the client supports extension negotiation, we will send +- * our supported extensions now. This is the first message after +- * sending NEWKEYS message and after turning on crypto. ++ * Only send an SSH_MSG_EXT_INFO message the first time the ++ * client undergoes NEWKEYS. It is unexpected for this message ++ * to be sent upon rekey, and may cause clients to log error ++ * messages. ++ * ++ * The session_state can not be used for this purpose because it ++ * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. ++ * So, use the connected flag which transitions from non-zero ++ * below. ++ * ++ * See also: ++ * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 + */ +- if (session->extensions & SSH_EXT_NEGOTIATION && +- session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { +- +- /* +- * Only send an SSH_MSG_EXT_INFO message the first time the client +- * undergoes NEWKEYS. It is unexpected for this message to be sent +- * upon rekey, and may cause clients to log error messages. +- * +- * The session_state can not be used for this purpose because it is +- * re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. So, +- * use the connected flag which transitions from non-zero below. +- * +- * See also: +- * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 +- */ +- if (session->connected == 0) { +- ssh_server_send_extensions(session); +- } ++ if (session->connected == 0) { ++ ssh_server_send_extensions(session); + } ++ } + +- set_status(session,1.0f); +- session->connected = 1; +- session->session_state=SSH_SESSION_STATE_AUTHENTICATING; +- if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) +- session->session_state = SSH_SESSION_STATE_AUTHENTICATED; ++ set_status(session, 1.0f); ++ session->connected = 1; ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATING; ++ if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + +- } +- break; +- case SSH_SESSION_STATE_AUTHENTICATING: +- break; +- case SSH_SESSION_STATE_ERROR: +- goto error; +- default: +- ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); ++ } ++ break; ++ case SSH_SESSION_STATE_AUTHENTICATING: ++ break; ++ case SSH_SESSION_STATE_ERROR: ++ goto error; ++ default: ++ ssh_set_error(session, SSH_FATAL, "Invalid state %d", ++ session->session_state); + } + + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; +- session->session_state=SSH_SESSION_STATE_ERROR; ++ session->session_state = SSH_SESSION_STATE_ERROR; + } + + /** +@@ -459,16 +466,17 @@ + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +-static size_t callback_receive_banner(const void *data, size_t len, void *user) { +- char *buffer = (char *) data; +- ssh_session session = (ssh_session) user; ++static size_t callback_receive_banner(const void *data, size_t len, void *user) ++{ ++ char *buffer = (char *)data; ++ ssh_session session = (ssh_session)user; + char *str = NULL; + size_t i; + size_t processed = 0; + + for (i = 0; i < len; i++) { + #ifdef WITH_PCAP +- if(session->pcap_ctx && buffer[i] == '\n') { ++ if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, +@@ -477,11 +485,11 @@ + } + #endif + if (buffer[i] == '\r') { +- buffer[i]='\0'; ++ buffer[i] = '\0'; + } + + if (buffer[i] == '\n') { +- buffer[i]='\0'; ++ buffer[i] = '\0'; + + str = strdup(buffer); + /* number of bytes read */ +@@ -494,10 +502,11 @@ + return processed; + } + +- if(i > 127) { ++ if (i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; +- ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); ++ ssh_set_error(session, SSH_FATAL, ++ "Receiving banner: too large banner"); + + return 0; + } +@@ -549,10 +558,14 @@ + } + + /* Do the banner and key exchange */ +-int ssh_handle_key_exchange(ssh_session session) { ++int ssh_handle_key_exchange(ssh_session session) ++{ + int rc; +- if (session->session_state != SSH_SESSION_STATE_NONE) +- goto pending; ++ ++ if (session->session_state != SSH_SESSION_STATE_NONE) { ++ goto pending; ++ } ++ + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; +@@ -563,27 +576,28 @@ + session->ssh_connection_callback = ssh_server_connection_callback; + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); +- session->socket_callbacks.data=callback_receive_banner; +- session->socket_callbacks.exception=ssh_socket_exception_callback; +- session->socket_callbacks.userdata=session; ++ session->socket_callbacks.data = callback_receive_banner; ++ session->socket_callbacks.exception = ssh_socket_exception_callback; ++ session->socket_callbacks.userdata = session; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } +- pending: ++pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, +- ssh_server_kex_termination,session); ++ ssh_server_kex_termination,session); + SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", +- session->session_state); +- if (rc != SSH_OK) +- return rc; ++ session->session_state); ++ if (rc != SSH_OK) { ++ return rc; ++ } + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { +- return SSH_ERROR; ++ return SSH_ERROR; + } + +- return SSH_OK; ++ return SSH_OK; + } + + /* messages */ +diff --color -ru ../libssh-0.10.4/src/token.c ./src/token.c +--- ../libssh-0.10.4/src/token.c 2023-04-27 14:25:46.346381527 +0200 ++++ ./src/token.c 2023-04-27 14:34:36.426602679 +0200 +@@ -87,7 +87,7 @@ + return NULL; + } + +- tokens->buffer= strdup(chain); ++ tokens->buffer = strdup(chain); + if (tokens->buffer == NULL) { + goto error; + } +diff --color -ru ../libssh-0.10.4/src/wrapper.c ./src/wrapper.c +--- ../libssh-0.10.4/src/wrapper.c 2022-08-30 13:26:05.000000000 +0200 ++++ ./src/wrapper.c 2023-04-27 14:31:16.130584204 +0200 +@@ -150,15 +150,16 @@ + SAFE_FREE(cipher); + } + +-struct ssh_crypto_struct *crypto_new(void) { +- struct ssh_crypto_struct *crypto; ++struct ssh_crypto_struct *crypto_new(void) ++{ ++ struct ssh_crypto_struct *crypto; + +- crypto = malloc(sizeof(struct ssh_crypto_struct)); +- if (crypto == NULL) { +- return NULL; +- } +- ZERO_STRUCTP(crypto); +- return crypto; ++ crypto = malloc(sizeof(struct ssh_crypto_struct)); ++ if (crypto == NULL) { ++ return NULL; ++ } ++ ZERO_STRUCTP(crypto); ++ return crypto; + } + + void crypto_free(struct ssh_crypto_struct *crypto) +diff --color -ru ../libssh-0.10.4/tests/client/torture_rekey.c ./tests/client/torture_rekey.c +--- ../libssh-0.10.4/tests/client/torture_rekey.c 2022-09-02 09:56:54.000000000 +0200 ++++ ./tests/client/torture_rekey.c 2023-04-27 14:38:20.352896968 +0200 +@@ -192,10 +192,11 @@ + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +- /* send ignore packets of up to 1KB to trigger rekey */ ++ /* send ignore packets of up to 1KB to trigger rekey. Send little bit more ++ * to make sure it completes with all different ciphers */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); +- for (i = 0; i < 16; i++) { ++ for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } +@@ -671,6 +672,92 @@ + #endif /* WITH_SFTP */ + + ++static void setup_server_for_good_guess(void *state) ++{ ++ const char *default_sshd_config = "KexAlgorithms curve25519-sha256"; ++ const char *fips_sshd_config = "KexAlgorithms ecdh-sha2-nistp256"; ++ const char *sshd_config = default_sshd_config; ++ ++ if (ssh_fips_mode()) { ++ sshd_config = fips_sshd_config; ++ } ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++} ++ ++static void torture_rekey_guess_send(void **state) ++{ ++ struct torture_state *s = *state; ++ ++ setup_server_for_good_guess(state); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ torture_rekey_send(state); ++} ++ ++static void torture_rekey_guess_wrong_send(void **state) ++{ ++ struct torture_state *s = *state; ++ const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; ++ ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ torture_rekey_send(state); ++} ++ ++#ifdef WITH_SFTP ++static void torture_rekey_guess_recv(void **state) ++{ ++ struct torture_state *s = *state; ++ int rc; ++ ++ setup_server_for_good_guess(state); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); ++ assert_ssh_return_code(s->ssh.session, rc); ++ ++ session_setup_sftp(state); ++ ++ torture_rekey_recv(state); ++} ++ ++static void torture_rekey_guess_wrong_recv(void **state) ++{ ++ struct torture_state *s = *state; ++ const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; ++ int rc; ++ ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); ++ assert_ssh_return_code(s->ssh.session, rc); ++ ++ session_setup_sftp(state); ++ ++ torture_rekey_recv(state); ++} ++#endif /* WITH_SFTP */ ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -704,6 +791,33 @@ + session_setup, + session_teardown), + /* TODO verify the two rekey are possible and the states are not broken after rekey */ ++ ++ cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, ++ session_setup, ++ session_teardown), ++ /* Note, that these tests modify the sshd_config so follow-up tests ++ * might get unexpected behavior if they do not update the server with ++ * torture_update_sshd_config() too */ ++ cmocka_unit_test_setup_teardown(torture_rekey_server_send, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_send, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_send, ++ session_setup, ++ session_teardown), ++#ifdef WITH_SFTP ++ cmocka_unit_test_setup_teardown(torture_rekey_server_recv, ++ session_setup_sftp_server, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_recv, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, ++ session_setup, ++ session_teardown), ++#endif /* WITH_SFTP */ + }; + + ssh_init(); +diff --color -ru ../libssh-0.10.4/tests/fuzz/ssh_server_fuzzer.c ./tests/fuzz/ssh_server_fuzzer.c +--- ../libssh-0.10.4/tests/fuzz/ssh_server_fuzzer.c 2022-07-07 15:53:51.000000000 +0200 ++++ ./tests/fuzz/ssh_server_fuzzer.c 2023-04-27 14:28:50.620219301 +0200 +@@ -186,6 +186,7 @@ + ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); + + ssh_callbacks_init(&server_cb); ++ ssh_set_server_callbacks(session, &server_cb); + + rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); + assert(rc == SSH_OK); diff --git a/rekey_test_fixup.patch b/rekey_test_fixup.patch new file mode 100644 index 0000000..ab595fe --- /dev/null +++ b/rekey_test_fixup.patch @@ -0,0 +1,21 @@ +diff --color -ru ../libssh-0.10.4/tests/client/torture_rekey.c ./tests/client/torture_rekey.c +--- ../libssh-0.10.4/tests/client/torture_rekey.c 2023-05-17 10:55:23.384684129 +0200 ++++ ./tests/client/torture_rekey.c 2023-05-17 10:56:29.819334665 +0200 +@@ -504,7 +504,7 @@ + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); +- ssh_handle_packets(s->ssh.session, 100); ++ ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ +@@ -582,7 +582,7 @@ + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); +- ssh_handle_packets(s->ssh.session, 100); ++ ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */