From 18d7a3967cec8b2c9609d2dd7a135afbc89cac64 Mon Sep 17 00:00:00 2001 From: Bulitha Kawushika De Zoysa Date: Sat, 7 Feb 2026 13:43:55 +0530 Subject: [PATCH] tests: Add interoperability tests against TinySSH This adds a new test suite 'torture_tinyssh' that verifies interoperability with the TinySSH server using various key exchange methods. Fixes #271 Signed-off-by: Bulitha Kawushika De Zoysa Reviewed-by: Jakub Jelen --- tests/CMakeLists.txt | 22 +++ tests/client/CMakeLists.txt | 10 + tests/client/torture_tinyssh.c | 328 +++++++++++++++++++++++++++++++++ tests/keys/.ed25519.sk | Bin 0 -> 64 bytes tests/keys/ed25519.pk | 1 + tests/tests_config.h.cmake | 4 + 6 files changed, 365 insertions(+) create mode 100644 tests/client/torture_tinyssh.c create mode 100644 tests/keys/.ed25519.sk create mode 100644 tests/keys/ed25519.pk diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68cbf1bf..3ee0a380 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -259,6 +259,20 @@ else() set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found") endif() +find_program(TINYSSHD_EXECUTABLE + NAMES + tinysshd + PATHS + /sbin + /usr/sbin + /usr/local/sbin) + +if (TINYSSHD_EXECUTABLE) + message(STATUS "Found TinySSH server: ${TINYSSHD_EXECUTABLE}") +else() + message(STATUS "Could NOT find TinySSH server") +endif() + find_program(SSHD_EXECUTABLE NAME sshd @@ -391,6 +405,14 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # TINYSSH Key Setup + set(TINYSSH_KEY_DIR "${CMAKE_CURRENT_BINARY_DIR}/etc/tinyssh") + file(MAKE_DIRECTORY ${TINYSSH_KEY_DIR}) + + # Copy the TINYSSH hostkeys + file(COPY keys/ed25519.pk DESTINATION ${TINYSSH_KEY_DIR}) + file(COPY keys/.ed25519.sk DESTINATION ${TINYSSH_KEY_DIR}) + # Allow to auth with bob's public keys on alice and doe account configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY) diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 52e8d8a5..c0560666 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -36,6 +36,10 @@ if (WITH_PKCS11_URI) torture_auth_pkcs11) endif() +if (TINYSSHD_EXECUTABLE AND NCAT_EXECUTABLE) + set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} torture_tinyssh) +endif() + if (HAVE_PTHREAD) set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} @@ -92,6 +96,12 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) LINK_LIBRARIES ${TORTURE_LIBRARY} util ) + if (_CLI_TEST STREQUAL "torture_tinyssh") + target_compile_definitions(${_CLI_TEST} PRIVATE + TINYSSH_KEYS_DIR="${TINYSSH_KEY_DIR}" + ) + endif() + if (OSX) set_property( TEST diff --git a/tests/client/torture_tinyssh.c b/tests/client/torture_tinyssh.c new file mode 100644 index 00000000..07b6832c --- /dev/null +++ b/tests/client/torture_tinyssh.c @@ -0,0 +1,328 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2026 by Your Bulitha Kawushika De Zoysa + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "tests_config.h" + +#define LIBSSH_STATIC + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "torture.h" + +#include +#include +#include +#include +#include +#include + +#define TINYSSH_PIDFILE "tinyssh.pid" +#define TINYSSH_PORT 22 + +/* TINYSSH Server Setup and Teardown */ + +static int tinyssh_setup(void **state) +{ + struct torture_state *s = NULL; + char cmd[4096]; + char pid_path[1024]; + int rc; + + torture_setup_socket_dir(state); + s = *state; + + snprintf(pid_path, + sizeof(pid_path), + "%s/%s", + s->socket_dir, + TINYSSH_PIDFILE); + free(s->srv_pidfile); + s->srv_pidfile = strdup(pid_path); + if (s->srv_pidfile == NULL) { + return -1; + } + + snprintf(cmd, + sizeof(cmd), + "%s -l %s %d -k -c \"%s %s -v %s\" " + "> %s/tinyssh.log 2>&1 & echo $! > %s", + NCAT_EXECUTABLE, + TORTURE_SSH_SERVER, + TINYSSH_PORT, + TINYSSHD_EXECUTABLE, + "", + TINYSSH_KEYS_DIR, + s->socket_dir, + s->srv_pidfile); + + SSH_LOG(SSH_LOG_DEBUG, "Executing: %s\n", cmd); + + rc = system(cmd); + if (rc != 0) { + return -1; + } + + rc = torture_wait_for_daemon(15); + if (rc != 0) { + return -1; + } + + return 0; +} + +static int tinyssh_teardown(void **state) +{ + struct torture_state *s = *state; + torture_terminate_process(s->srv_pidfile); + torture_teardown_socket_dir(state); + return 0; +} + +/* LIBSSH Client Setup and Teardown */ + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + bool process_config = false; + int port = TINYSSH_PORT; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT, &port); + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "bob"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + if (s->ssh.session) { + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + } + return 0; +} + +/* Algorithms Helper */ + +static void test_specific_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hostkey, + int expected_rc) +{ + int rc; + char data[256]; + size_t len_to_test[] = {1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15, + 16, 20, 31, 32, 33, 63, 64, 65, 100, 127, 128}; + unsigned int i; + + /* Set Key Exchange */ + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + /* Set Ciphers */ + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + /* Set Hostkey */ + if (hostkey != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + + if (expected_rc == SSH_OK) { + assert_ssh_return_code(session, rc); + + if (cipher != NULL) { + const char *used_cipher = ssh_get_cipher_out(session); + assert_non_null(used_cipher); + assert_string_equal(used_cipher, cipher); + } + + if (hostkey != NULL) { + ssh_key pubkey = NULL; + const char *type_str = NULL; + + rc = ssh_get_server_publickey(session, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + type_str = ssh_key_type_to_char(ssh_key_type(pubkey)); + assert_non_null(type_str); + assert_string_equal(type_str, hostkey); + ssh_key_free(pubkey); + } + + memset(data, 0, sizeof(data)); + for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) { + memset(data, 'A', len_to_test[i]); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + } else { + assert_int_not_equal(rc, SSH_OK); + } +} + +/* Test Cases */ + +static void torture_tinyssh_curve25519(void **state) +{ + struct torture_state *s = *state; + test_specific_algorithm(s->ssh.session, + "curve25519-sha256", + NULL, + NULL, + SSH_OK); +} + +static void torture_tinyssh_curve25519_libssh(void **state) +{ + struct torture_state *s = *state; + test_specific_algorithm(s->ssh.session, + "curve25519-sha256@libssh.org", + NULL, + NULL, + SSH_OK); +} + +static void torture_tinyssh_sntrup761(void **state) +{ + struct torture_state *s = *state; + test_specific_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL, + NULL, + SSH_OK); +} + +static void torture_tinyssh_chacha20(void **state) +{ + struct torture_state *s = *state; + test_specific_algorithm(s->ssh.session, + NULL, + "chacha20-poly1305@openssh.com", + NULL, + SSH_OK); +} + +static void torture_tinyssh_neg_cipher(void **state) +{ + struct torture_state *s = *state; + + /* TinySSH does not support older ciphers like aes128-cbc.*/ + + test_specific_algorithm(s->ssh.session, + NULL, + "aes128-cbc", + NULL, + SSH_ERROR); +} + +static void torture_tinyssh_hostkey_ed25519(void **state) +{ + struct torture_state *s = *state; + test_specific_algorithm(s->ssh.session, NULL, NULL, "ssh-ed25519", SSH_OK); +} + +static void torture_tinyssh_neg_kex(void **state) +{ + struct torture_state *s = *state; + + /* TinySSH does not support legacy Diffie-Hellman groups or NIST curves.*/ + + test_specific_algorithm(s->ssh.session, + "diffie-hellman-group1-sha1", + NULL, + NULL, + SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519_libssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_sntrup761, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_hostkey_ed25519, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_chacha20, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_neg_cipher, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_tinyssh_neg_kex, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, tinyssh_setup, tinyssh_teardown); + + ssh_finalize(); + return rc; +} \ No newline at end of file diff --git a/tests/keys/.ed25519.sk b/tests/keys/.ed25519.sk new file mode 100644 index 0000000000000000000000000000000000000000..042390097e7a489c061581a4f60d064b2c356f55 GIT binary patch literal 64 zcmV-G0KfmOD06Nb@wuI$+y5A$DUZMu@uhT(JI*$3F{Fcw)L!jeJmED_@ WF&yCW6&@#Q3?|)Wvy|_OjlHBL7a@KC literal 0 HcmV?d00001 diff --git a/tests/keys/ed25519.pk b/tests/keys/ed25519.pk new file mode 100644 index 00000000..3d8fbc5c --- /dev/null +++ b/tests/keys/ed25519.pk @@ -0,0 +1 @@ +CKݛ1'j &e \ No newline at end of file diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake index f448df63..445b0e5e 100644 --- a/tests/tests_config.h.cmake +++ b/tests/tests_config.h.cmake @@ -86,3 +86,7 @@ #cmakedefine PKCS11SPY "${PKCS11SPY}" #cmakedefine HAVE_SK_DUMMY 1 #cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}" + +/* TinySSH Executable */ + +#cmakedefine TINYSSHD_EXECUTABLE "${TINYSSHD_EXECUTABLE}" \ No newline at end of file