tests: Invoke all combinations of wrong guesses during rekey

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit d357a9f3e2)
This commit is contained in:
Jakub Jelen
2025-07-30 12:27:55 +02:00
parent 8e4d67aa9e
commit 7d85085d2a
4 changed files with 99 additions and 8 deletions

View File

@@ -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()

View File

@@ -105,6 +105,7 @@ add_subdirectory(unittests)
# OpenSSH Capabilities are required for all unit tests
find_program(SSH_EXECUTABLE NAMES ssh)
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}")

View File

@@ -31,6 +31,7 @@
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/crypto.h"
#include "libssh/token.h"
#include <errno.h>
#include <sys/types.h>
@@ -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 */
};

View File

@@ -65,6 +65,7 @@
#cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}"
#cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}"
#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}"
#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}"
#cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}"
#cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT}
#cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}"