diff --git a/CMakeLists.txt b/CMakeLists.txt index 5da4f7e4..05c8c44c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12.0) +cmake_minimum_required(VERSION 3.14.0) # Specify search path for CMake modules to be loaded by include() # and find_package() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 89989782..6ee0b688 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -112,6 +112,7 @@ add_subdirectory(unittests) find_program(SSH_EXECUTABLE NAMES ssh) find_program(SSH_KEYGEN_EXECUTABLE NAMES ssh-keygen) if (SSH_EXECUTABLE) + file(SIZE ${SSH_EXECUTABLE} SSH_EXECUTABLE_SIZE) execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) string(REGEX REPLACE "^.*OpenSSH_([0-9]+).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") string(REGEX REPLACE "^.*OpenSSH_[0-9]+.([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c index f6a633fc..d76a3e11 100644 --- a/tests/client/torture_rekey.c +++ b/tests/client/torture_rekey.c @@ -31,6 +31,7 @@ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" +#include "libssh/token.h" #include #include @@ -96,6 +97,7 @@ static int session_teardown(void **state) struct torture_state *s = *state; ssh_free(s->ssh.session); + s->ssh.session = NULL; return 0; } @@ -148,7 +150,7 @@ static void torture_rekey_default(void **state) ssh_disconnect(s->ssh.session); } -static void sanity_check_session(void **state) +static void sanity_check_session_size(void **state, uint64_t rekey_limit) { struct torture_state *s = *state; struct ssh_crypto_struct *c = NULL; @@ -156,9 +158,9 @@ static void sanity_check_session(void **state) c = s->ssh.session->current_crypto; assert_non_null(c); assert_int_equal(c->in_cipher->max_blocks, - bytes / c->in_cipher->blocksize); + rekey_limit / c->in_cipher->blocksize); assert_int_equal(c->out_cipher->max_blocks, - bytes / c->out_cipher->blocksize); + rekey_limit / c->out_cipher->blocksize); /* when strict kex is used, the newkeys reset the sequence number */ if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); @@ -170,6 +172,10 @@ static void sanity_check_session(void **state) assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); } } +static void sanity_check_session(void **state) +{ + sanity_check_session_size(state, bytes); +} /* We lower the rekey limits manually and check that the rekey * really happens when sending data @@ -275,7 +281,7 @@ static int session_setup_sftp_client(void **state) /* To trigger rekey by receiving data, the easiest thing is probably to * use sftp */ -static void torture_rekey_recv(void **state) +static void torture_rekey_recv_size(void **state, uint64_t rekey_limit) { struct torture_state *s = *state; struct ssh_crypto_struct *c = NULL; @@ -290,7 +296,7 @@ static void torture_rekey_recv(void **state) mode_t mask; int rc; - sanity_check_session(state); + sanity_check_session_size(state, rekey_limit); /* Copy the initial secret hash = session_id so we know we changed keys later */ c = s->ssh.session->current_crypto; assert_non_null(c); @@ -324,8 +330,10 @@ static void torture_rekey_recv(void **state) /* The rekey limit was restored in the new crypto to the same value */ c = s->ssh.session->current_crypto; - assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); - assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); /* Check that the secret hash is different than initially */ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); free(secret_hash); @@ -333,6 +341,11 @@ static void torture_rekey_recv(void **state) torture_sftp_close(s->ssh.tsftp); ssh_disconnect(s->ssh.session); } + +static void torture_rekey_recv(void **state) +{ + torture_rekey_recv_size(state, bytes); +} #endif /* WITH_SFTP */ /* Rekey time requires rekey after specified time and is off by default. @@ -836,6 +849,81 @@ static void torture_rekey_guess_wrong_recv(void **state) torture_rekey_recv(state); } + +static void torture_rekey_guess_all_combinations(void **state) +{ + struct torture_state *s = *state; + char sshd_config[256] = ""; + char client_kex[256] = ""; + const char *supported = NULL; + struct ssh_tokens_st *s_tok = NULL; + uint64_t rekey_limit = 0; + int rc, i, j; + + /* The rekey limit is 1/2 of the transferred file size so we will likely get + * 2 rekeys per test, which still runs for acceptable time */ + rekey_limit = atoll(SSH_EXECUTABLE_SIZE); + rekey_limit /= 2; + + if (ssh_fips_mode()) { + supported = ssh_kex_get_fips_methods(SSH_KEX); + } else { + supported = ssh_kex_get_supported_method(SSH_KEX); + } + assert_non_null(supported); + + s_tok = ssh_tokenize(supported, ','); + assert_non_null(s_tok); + for (i = 0; s_tok->tokens[i]; i++) { + /* Skip algorithms not supported by the OpenSSH server */ + if (strstr(OPENSSH_KEX, s_tok->tokens[i]) == NULL) { + SSH_LOG(SSH_LOG_INFO, "Server: %s [skipping]", s_tok->tokens[i]); + continue; + } + SSH_LOG(SSH_LOG_INFO, "Server: %s", s_tok->tokens[i]); + snprintf(sshd_config, + sizeof(sshd_config), + "KexAlgorithms %s", + s_tok->tokens[i]); + /* This sets an only supported kex algorithm that we do not have as + * a first option in the client */ + torture_update_sshd_config(state, sshd_config); + + for (j = 0; s_tok->tokens[j]; j++) { + if (i == j) { + continue; + } + + session_setup(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, + &rekey_limit); + assert_ssh_return_code(s->ssh.session, rc); + + /* Client kex preference will have the second of the pair and the + * server one as a second to negotiate on the second attempt */ + snprintf(client_kex, + sizeof(client_kex), + "%s,%s", + s_tok->tokens[j], + s_tok->tokens[i]); + SSH_LOG(SSH_LOG_INFO, "Client: %s", client_kex); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_KEY_EXCHANGE, + client_kex); + assert_ssh_return_code(s->ssh.session, rc); + session_setup_sftp(state); + torture_rekey_recv_size(state, rekey_limit); + session_teardown(state); + } + } + + ssh_tokens_free(s_tok); +} #endif /* WITH_SFTP */ int torture_run_tests(void) { @@ -905,6 +993,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, session_setup, session_teardown), + cmocka_unit_test(torture_rekey_guess_all_combinations), #endif /* WITH_SFTP */ }; diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake index 099f8cd2..365b0798 100644 --- a/tests/tests_config.h.cmake +++ b/tests/tests_config.h.cmake @@ -67,6 +67,7 @@ #cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" #cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}" #cmakedefine SSH_KEYGEN_EXECUTABLE "${SSH_KEYGEN_EXECUTABLE}" #cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}" #cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT}