From 74d42ca38be87f89708a4f48a57f752625afae0c Mon Sep 17 00:00:00 2001 From: Gauravsingh Sisodia Date: Fri, 29 Mar 2024 06:47:36 +0000 Subject: [PATCH] feat: add tests for gssapi-with-mic feat: tests set hostname for sshd, make GSSAPIStrictAcceptorCheck yes pass feat: add GSSAPI_TESTING cmake option feat: gssapi libssh server test feat: make kdc setup and teardown functions feat: add kinit, kadmin scripts to kdc setup function feat: add some client gssapi auth tests Signed-off-by: Gauravsingh Sisodia Reviewed-by: Jakub Jelen Reviewed-by: Sahana Prasad --- .gitlab-ci.yml | 8 +- DefineOptions.cmake | 3 +- src/gssapi.c | 3 + tests/CMakeLists.txt | 2 + tests/client/CMakeLists.txt | 6 + tests/client/torture_gssapi_auth.c | 273 +++++++++++++++++ tests/etc/hosts.in | 2 + tests/gss/kdcsetup.sh | 52 ++++ tests/server/CMakeLists.txt | 11 +- tests/server/test_server/default_cb.c | 6 +- tests/server/test_server/main.c | 3 +- tests/server/torture_gssapi_server_auth.c | 338 ++++++++++++++++++++++ tests/torture.c | 169 ++++++++++- tests/torture.h | 10 + 14 files changed, 867 insertions(+), 19 deletions(-) create mode 100644 tests/client/torture_gssapi_auth.c create mode 100755 tests/gss/kdcsetup.sh create mode 100644 tests/server/torture_gssapi_server_auth.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 849738e0..a8bfc422 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ workflow: variables: CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON" CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON" - CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON" + CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DGSSAPI_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON" CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS before_script: &build - uname -a @@ -445,8 +445,6 @@ alpine/openssl_3.0.x/musl: ############################################################################### tumbleweed/openssl_3.0.x/x86_64/gcc: extends: .tumbleweed - variables: - CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" tumbleweed/openssl_3.0.x/x86/gcc: extends: .tumbleweed @@ -464,7 +462,7 @@ tumbleweed/openssl_3.0.x/x86/gcc: tumbleweed/openssl_3.0.x/x86_64/gcc7: extends: .tumbleweed variables: - CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7" tumbleweed/openssl_3.0.x/x86/gcc7: extends: .tumbleweed @@ -481,7 +479,7 @@ tumbleweed/openssl_3.0.x/x86/gcc7: tumbleweed/openssl_3.0.x/x86_64/clang: extends: .tumbleweed variables: - CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++" tumbleweed/mbedtls-3.6.x/x86_64/gcc: extends: .tumbleweed diff --git a/DefineOptions.cmake b/DefineOptions.cmake index d449ef34..f1a6a244 100644 --- a/DefineOptions.cmake +++ b/DefineOptions.cmake @@ -16,6 +16,7 @@ option(WITH_PKCS11_PROVIDER "Use the PKCS#11 provider for accessing pkcs11 objec option(UNIT_TESTING "Build with unit tests" OFF) option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" OFF) +option(GSSAPI_TESTING "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" OFF) option(WITH_BENCHMARKS "Build benchmarks tools; enables unit testing and client tests" OFF) option(WITH_EXAMPLES "Build examples" ON) option(WITH_NACL "Build with libnacl (curve25519)" ON) @@ -38,7 +39,7 @@ if (WITH_BENCHMARKS) set(CLIENT_TESTING ON) endif() -if (UNIT_TESTING OR CLIENT_TESTING OR SERVER_TESTING) +if (UNIT_TESTING OR CLIENT_TESTING OR SERVER_TESTING OR GSSAPI_TESTING) set(BUILD_STATIC_LIB ON) endif() diff --git a/src/gssapi.c b/src/gssapi.c index 7fbaa804..fec92a72 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -1060,6 +1060,9 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; } + ssh_gssapi_free(session); + session->gssapi = NULL; + return SSH_PACKET_USED; error: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23d71195..89b95d08 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -342,6 +342,8 @@ if (WITH_PKCS11_URI) file(COPY pkcs11/setup-softhsm-tokens.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pkcs11 FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) endif (WITH_PKCS11_URI) +file(COPY gss/kdcsetup.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gss FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) + message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") configure_file(tests_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/tests_config.h) diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 44b82964..078b4878 100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -39,6 +39,12 @@ if (HAVE_PTHREAD) torture_proxyjump) endif() +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_gssapi_auth) +endif() + if (DEFAULT_C_NO_DEPRECATION_FLAGS) set_source_files_properties(torture_knownhosts.c PROPERTIES diff --git a/tests/client/torture_gssapi_auth.c b/tests/client/torture_gssapi_auth.c new file mode 100644 index 00000000..12022ab7 --- /dev/null +++ b/tests/client/torture_gssapi_auth.c @@ -0,0 +1,273 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPICleanupCredentials yes\n" + "GSSAPIStrictAcceptorCheck yes\n"); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + 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_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_gssapi_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* No client credential */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Invalid host principal */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_client_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid client identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "bob"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + + /* Valid client identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server(state); + + /* Valid server identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_delegate_creds(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server(state); +} + + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_client_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_delegate_creds, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/etc/hosts.in b/tests/etc/hosts.in index 8891125d..3c0b0142 100644 --- a/tests/etc/hosts.in +++ b/tests/etc/hosts.in @@ -1,5 +1,7 @@ 127.0.0.10 server.libssh.site 127.0.0.21 client.libssh.site +127.0.0.11 kdc.libssh.site + 123.0.0.11 testing fd00::5357:5f0a testing diff --git a/tests/gss/kdcsetup.sh b/tests/gss/kdcsetup.sh new file mode 100755 index 00000000..c08b47d6 --- /dev/null +++ b/tests/gss/kdcsetup.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +SOCKDIR=$1 +WORKDIR=$SOCKDIR/gss + +mkdir "$WORKDIR"/k "$WORKDIR"/d + +cat< "$WORKDIR"/k/kdc.conf +[realms] + LIBSSH.SITE = { + database_name = $WORKDIR/principal + key_stash_file = $WORKDIR/stash + kdc_listen = $(hostname -f) + kdc_tcp_listen = $(hostname -f) + default_principal_flags = +preauth + } +[logging] + kdc = FILE:$WORKDIR/kdc.log + debug = true +EOF + +cat< "$WORKDIR"/k/krb5.conf +[libdefaults] + default_realm = LIBSSH.SITE + +[realms] + LIBSSH.SITE = { + kdc = $(hostname -f) + } +[domain_realm] + .$(hostname -d) = LIBSSH.SITE + +EOF + +kdb5_util -P foo create -s + +bash "$WORKDIR"/kadmin.sh + +krb5kdc -w 1 -P "$WORKDIR"/pid + +# Wait till KDC binds to the ports, 0x58 is port 88 +i=0 +while [ ! -S "$SOCKDIR"/T0B0058 ] && [ ! -S "$SOCKDIR"/U0B0058 ]; do + i=$((i + 1)) + [ "$i" -eq 5 ] && exit 1 + sleep 1 +done + +bash "$WORKDIR"/kinit.sh + +klist +exit 0 diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index 4f2dd6e2..4326e2e9 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -14,11 +14,20 @@ set(LIBSSH_SERVER_TESTS torture_sftpserver ) +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_SERVER_TESTS + ${LIBSSH_SERVER_TESTS} + torture_gssapi_server_auth) +endif() + include_directories(${libssh_SOURCE_DIR}/include ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} test_server) +set(TORTURE_SERVER_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_SERVER_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + if (ARGP_INCLUDE_DIR) include_directories(${ARGP_INCLUDE_DIR}) endif () @@ -41,7 +50,7 @@ foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) TEST ${_SRV_TEST} PROPERTY - ENVIRONMENT ${TORTURE_ENVIRONMENT}) + ENVIRONMENT ${TORTURE_SERVER_ENVIRONMENT}) endif() endforeach() diff --git a/tests/server/test_server/default_cb.c b/tests/server/test_server/default_cb.c index a4ac81b0..02826824 100644 --- a/tests/server/test_server/default_cb.c +++ b/tests/server/test_server/default_cb.c @@ -193,7 +193,6 @@ int auth_gssapi_mic_cb(ssh_session session, printf("Received some gssapi credentials\n"); } else { printf("Not received any forwardable creds\n"); - goto denied; } printf("Authenticated\n"); @@ -203,8 +202,6 @@ int auth_gssapi_mic_cb(ssh_session session, return SSH_AUTH_SUCCESS; -denied: - sdata->auth_attempts++; null_userdata: return SSH_AUTH_DENIED; } @@ -910,7 +907,8 @@ void default_handle_session_cb(ssh_event event, } else { ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | - SSH_AUTH_METHOD_PUBLICKEY); + SSH_AUTH_METHOD_PUBLICKEY| + SSH_AUTH_METHOD_GSSAPI_MIC); } ssh_event_add_session(event, session); diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index 97ad8b80..cf153c26 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -272,7 +272,8 @@ static int init_server_state(struct server_state_st *state, state->auth_methods = atoi(arguments->auth_methods); } else { state->auth_methods = SSH_AUTH_METHOD_PASSWORD | - SSH_AUTH_METHOD_PUBLICKEY; + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_GSSAPI_MIC; } state->with_pcap = arguments->with_pcap; diff --git a/tests/server/torture_gssapi_server_auth.c b/tests/server/torture_gssapi_server_auth.c new file mode 100644 index 00000000..1dbbb649 --- /dev/null +++ b/tests/server/torture_gssapi_server_auth.c @@ -0,0 +1,338 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int +setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + tss->state = s; + tss->ss = ss; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + + +static void +torture_gssapi_server_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void**)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/torture.c b/tests/torture.c index a361d415..a76cae78 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -562,6 +562,7 @@ void torture_setup_socket_dir(void **state) const char *p; size_t len; char *env = NULL; + char gss_dir[1024] = {0}; int rc; s = calloc(1, sizeof(struct torture_state)); @@ -581,6 +582,16 @@ void torture_setup_socket_dir(void **state) s->socket_dir = torture_make_temp_dir(TORTURE_SOCKET_DIR); assert_non_null(s->socket_dir); +#ifdef WITH_GSSAPI + snprintf(gss_dir, + sizeof(gss_dir), + "%s/gss", + s->socket_dir); + rc = mkdir(gss_dir, 0755); + assert_return_code(rc, errno); + s->gss_dir = strdup(gss_dir); +#endif + p = s->socket_dir; /* pcap file */ @@ -931,6 +942,50 @@ int torture_wait_for_daemon(unsigned int seconds) return 1; } +void +torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size) +{ + snprintf(env, + size, + "KRB5CCNAME=%s/cc " + "KRB5_CONFIG=%s/k/krb5.conf " + "KRB5_KDC_PROFILE=%s/k " + "KRB5_KTNAME=%s/d/ssh.keytab " + "KRB5RCACHETYPE=none ", + gss_dir, + gss_dir, + gss_dir, + gss_dir); +} + +void +torture_set_env_from_str(const char *env) +{ + struct ssh_tokens_st *vars = NULL, *var = NULL; + + vars = ssh_tokenize(env, ' '); + if (vars == NULL) { + fail_msg("bad environment string"); + } + + for (int i = 0; vars->tokens[i]; i++) { + var = ssh_tokenize(vars->tokens[i], '='); + if (var == NULL) { + ssh_tokens_free(vars); + fail_msg("bad environment string"); + } + if (var->tokens[0] != NULL && var->tokens[1] != NULL) { + setenv(var->tokens[0], var->tokens[1], 1); + } else { + ssh_tokens_free(var); + ssh_tokens_free(vars); + fail_msg("bad environment string"); + } + ssh_tokens_free(var); + } + ssh_tokens_free(vars); +} + /** * @brief Run a libssh based server under timeout. * @@ -956,6 +1011,7 @@ void torture_setup_libssh_server(void **state, const char *server_path) char start_cmd[1024]; char timeout_cmd[512]; char env[1024]; + char kdc_env[255]; char extra_options[1024]; int rc; char *ld_preload = NULL; @@ -995,15 +1051,23 @@ void torture_setup_libssh_server(void **state, const char *server_path) force_fips = ""; } + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + /* Write the environment setting */ /* OPENSSL variable is needed to enable SHA1 */ - printed = snprintf(env, sizeof(env), + printed = snprintf(env, + sizeof(env), "SOCKET_WRAPPER_DIR=%s " "SOCKET_WRAPPER_DEFAULT_IFACE=10 " "LD_PRELOAD=%s " "%s " - "OPENSSL_ENABLE_SHA1_SIGNATURES=1", - s->socket_dir, ld_preload, force_fips); + "OPENSSL_ENABLE_SHA1_SIGNATURES=1 " + "NSS_WRAPPER_HOSTNAME=server.libssh.site " + "%s ", + s->socket_dir, + ld_preload, + force_fips, + kdc_env); if (printed < 0) { fail_msg("Failed to print env!"); /* Unreachable */ @@ -1080,18 +1144,29 @@ static int torture_start_sshd_server(void **state) struct torture_state *s = *state; char sshd_start_cmd[1024]; int rc; + char kdc_env[255] = {0}; /* Set the default interface for the server */ setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); setenv("PAM_WRAPPER", "1", 1); - snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), - SSHD_EXECUTABLE " -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", - s->srv_config, s->socket_dir, s->socket_dir); +#ifdef WITH_GSSAPI + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); +#endif + snprintf(sshd_start_cmd, + sizeof(sshd_start_cmd), + "%s " SSHD_EXECUTABLE + " -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", + kdc_env, + s->srv_config, + s->socket_dir, + s->socket_dir); rc = system(sshd_start_cmd); assert_return_code(rc, errno); + unsetenv("NSS_WRAPPER_HOSTNAME"); setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); unsetenv("PAM_WRAPPER"); @@ -1113,10 +1188,91 @@ void torture_setup_sshd_server(void **state, bool pam) assert_int_equal(rc, 0); } +#ifdef WITH_GSSAPI +/** + * @brief Setup KDC for GSSAPI testing + * + * This should be called after sshd or libssh server's setup functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * @param[in] kadmin_script kadmin commands to be executed on the KDC + * @param[in] kinit_script kinit commands to get the TGT + * + */ +void +torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script) +{ + struct torture_state *s = *state; + int rc; + char command[1024] = {0}; + char kdc_env[255] = {0}; + char kadmin_file[255] = {0}; + char kinit_file[255] = {0}; + + /* Remove the previous files and folders, but keep the same directory + * because we pass only one temporary directory to the server */ + snprintf(command, sizeof(command), "rm -rf %s/*", s->gss_dir); + rc = system(command); + assert_return_code(rc, errno); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "11", 1); + setenv("NSS_WRAPPER_HOSTNAME", "kdc.libssh.site", 1); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + snprintf(kadmin_file, sizeof(kadmin_file), "%s/kadmin.sh", s->gss_dir); + snprintf(kinit_file, sizeof(kinit_file), "%s/kinit.sh", s->gss_dir); + + torture_write_file(kadmin_file, kadmin_script); + torture_write_file(kinit_file, kinit_script); + + snprintf(command, + sizeof(command), + "%s/tests/gss/kdcsetup.sh %s", + BINARYDIR, + s->socket_dir); + rc = system(command); + assert_return_code(rc, errno); + assert_int_equal(rc, 0); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + /* Back to client */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); +} + +/** + * @brief Teardown KDC + * + * This should be called before sshd or libssh server's teardown functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void +torture_teardown_kdc_server(void **state) +{ + struct torture_state *s = *state; + int rc; + char temp[1024] = {0}; + + snprintf(temp, sizeof(temp), "%s/pid", s->gss_dir); + rc = torture_terminate_process(temp); + assert_return_code(rc, errno); +} + +#endif /* WITH_GSSAPI */ + void torture_free_state(struct torture_state *s) { free(s->srv_config); free(s->socket_dir); +#ifdef WITH_GSSAPI + free(s->gss_dir); +#endif free(s->pcap_file); free(s->log_file); free(s->srv_pidfile); @@ -1196,7 +1352,6 @@ void torture_teardown_sshd_server(void **state) rc = torture_terminate_process(s->srv_pidfile); assert_return_code(rc, errno); - torture_teardown_socket_dir(state); } #endif /* SSHD_EXECUTABLE */ diff --git a/tests/torture.h b/tests/torture.h index f8198452..906c9a9f 100644 --- a/tests/torture.h +++ b/tests/torture.h @@ -67,6 +67,7 @@ struct torture_sftp { struct torture_state { char *socket_dir; + char *gss_dir; char *pcap_file; char *log_file; char *srv_pidfile; @@ -148,6 +149,15 @@ void torture_setup_create_libssh_config(void **state); void torture_setup_libssh_server(void **state, const char *server_path); +#ifdef WITH_GSSAPI +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script); +void torture_teardown_kdc_server(void **state); +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size); +void torture_set_env_from_str(const char *env); +#endif /* WITH_GSSAPI */ + #if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) __attribute__((weak)) int torture_run_tests(void); #else