diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 9ab28998..4ef80a4c 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -283,6 +283,7 @@ typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session se /* * @brief handle the beginning of a GSSAPI authentication, server side. + * Callback should select the oid and also acquire the server credential. * @param session current session handler * @param user the username of the client * @param n_oid number of available oids @@ -365,6 +366,7 @@ struct ssh_server_callbacks_struct { */ ssh_channel_open_request_session_callback channel_open_request_session_function; /** This function will be called when a new gssapi authentication is attempted. + * This should select the oid and acquire credential for the server. */ ssh_gssapi_select_oid_callback gssapi_select_oid_function; /** This function will be called when a gssapi token comes in. diff --git a/src/gssapi.c b/src/gssapi.c index 71178b64..32451283 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -194,6 +194,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, return ssh_auth_reply_default(session, 0); } } + /* Default implementation for selecting oid and acquiring credential */ gss_create_empty_oid_set(&min_stat, &both_supported); maj_stat = gss_indicate_mechs(&min_stat, &supported); @@ -655,7 +656,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) &session->gssapi->client.creds, &actual_mechs, NULL); if (GSS_ERROR(maj_stat)) { - ssh_gssapi_log_error(SSH_LOG_DEBUG, + ssh_gssapi_log_error(SSH_LOG_WARN, "acquiring credential", maj_stat, min_stat); diff --git a/tests/client/torture_gssapi_auth.c b/tests/client/torture_gssapi_auth.c index 12022ab7..6a070e98 100644 --- a/tests/client/torture_gssapi_auth.c +++ b/tests/client/torture_gssapi_auth.c @@ -177,7 +177,9 @@ torture_gssapi_auth_server_identity(void **state) "kadmin.local list_principals", "echo bar | kinit alice"); - ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, "invalid.libssh.site"); + 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); @@ -191,7 +193,9 @@ torture_gssapi_auth_server_identity(void **state) "kadmin.local list_principals", "echo bar | kinit alice"); - ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, "server.libssh.site"); + 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); @@ -243,7 +247,6 @@ torture_gssapi_auth_delegate_creds(void **state) torture_teardown_kdc_server(state); } - int torture_run_tests(void) { diff --git a/tests/gss/kdcsetup.sh b/tests/gss/kdcsetup.sh index c08b47d6..c5e38bc8 100755 --- a/tests/gss/kdcsetup.sh +++ b/tests/gss/kdcsetup.sh @@ -12,7 +12,7 @@ cat< "$WORKDIR"/k/kdc.conf key_stash_file = $WORKDIR/stash kdc_listen = $(hostname -f) kdc_tcp_listen = $(hostname -f) - default_principal_flags = +preauth + default_principal_flags = +preauth,+forwardable } [logging] kdc = FILE:$WORKDIR/kdc.log @@ -22,6 +22,7 @@ EOF cat< "$WORKDIR"/k/krb5.conf [libdefaults] default_realm = LIBSSH.SITE + forwardable = true [realms] LIBSSH.SITE = { diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index 4326e2e9..a368c512 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -17,7 +17,9 @@ set(LIBSSH_SERVER_TESTS if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) set(LIBSSH_SERVER_TESTS ${LIBSSH_SERVER_TESTS} - torture_gssapi_server_auth) + torture_gssapi_server_auth + torture_gssapi_server_auth_cb + torture_gssapi_server_delegation) endif() include_directories(${libssh_SOURCE_DIR}/include diff --git a/tests/server/torture_gssapi_server_auth.c b/tests/server/torture_gssapi_server_auth.c index 1dbbb649..4f57c258 100644 --- a/tests/server/torture_gssapi_server_auth.c +++ b/tests/server/torture_gssapi_server_auth.c @@ -31,8 +31,8 @@ free_test_server_state(void **state) SAFE_FREE(tss); } -static int -setup_default_server(void **state) +static void +setup_config(void **state) { struct torture_state *s = NULL; struct server_state_st *ss = NULL; @@ -48,10 +48,6 @@ setup_default_server(void **state) 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)); @@ -135,9 +131,6 @@ setup_default_server(void **state) 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); @@ -145,6 +138,28 @@ setup_default_server(void **state) /* Do not use global configuration */ ss->parse_global_config = false; + tss->state = s; + tss->ss = ss; + + *state = 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 pid_str[1024]; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + /* Start the server using the default values */ pid = fork_run_server(ss, free_test_server_state, &tss); if (pid < 0) { @@ -257,9 +272,8 @@ session_teardown(void **state) return 0; } - static void -torture_gssapi_server_auth(void **state) +torture_gssapi_server_auth_no_client_cred(void **state) { struct test_server_st *tss = *state; struct torture_state *s; @@ -290,6 +304,27 @@ torture_gssapi_server_auth(void **state) rc = ssh_userauth_gssapi(session); assert_int_equal(rc, SSH_AUTH_DENIED); torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_invalid_host(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); + /* Invalid host principal */ torture_setup_kdc_server( (void **)&s, @@ -302,6 +337,27 @@ torture_gssapi_server_auth(void **state) rc = ssh_userauth_gssapi(session); assert_int_equal(rc, SSH_AUTH_ERROR); torture_teardown_kdc_server((void **)&s); +} + +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); + /* Valid */ torture_setup_kdc_server( (void **)&s, @@ -317,11 +373,72 @@ torture_gssapi_server_auth(void **state) torture_teardown_kdc_server((void **)&s); } +static void +torture_gssapi_auth_server_identity(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_ssh_return_code(session, rc); + + /* Invalid server identity option */ + 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"); + 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((void **)&s); + + /* Valid server identity option*/ + 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"); + 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((void **)&s); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_invalid_host, + 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_server_auth, session_setup, session_teardown), diff --git a/tests/server/torture_gssapi_server_auth_cb.c b/tests/server/torture_gssapi_server_auth_cb.c new file mode 100644 index 00000000..9b403bd8 --- /dev/null +++ b/tests/server/torture_gssapi_server_auth_cb.c @@ -0,0 +1,479 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/gssapi.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 void +setup_config(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; + + 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; + + /* 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; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static ssh_string +select_oid(ssh_session session, + const char *user, + int n_oid, + ssh_string *oids, + void *userdata) +{ + /* Choose the first oid */ + return oids[0]; +} + +static int +accept_sec_ctx(ssh_session session, + ssh_string input_token, + ssh_string *output_token, + void *userdata) +{ + ssh_string token; + OM_uint32 min_stat; + gss_buffer_desc itoken, otoken = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_channel_bindings_t input_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + itoken.length = ssh_string_len(input_token); + itoken.value = ssh_string_data(input_token); + + gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &itoken, + input_bindings, + &client_name, + NULL /*mech_oid*/, + &otoken, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + + if (client_name != GSS_C_NO_NAME) { + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + token = ssh_string_new(otoken.length); + ssh_string_fill(token, otoken.value, otoken.length); + *output_token = token; + + gss_release_buffer(&min_stat, &otoken); + gss_release_name(&min_stat, &client_name); + SSH_STRING_FREE(input_token); + + return 0; +} + +static int +verify_mic(ssh_session session, + ssh_string mic, + void *mic_buffer, + size_t mic_buffer_size, + void *userdata) +{ + /* Verify without checking */ + return 0; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->gssapi_select_oid_function = select_oid; + ss->server_cb->gssapi_accept_sec_ctx_function = accept_sec_ctx; + ss->server_cb->gssapi_verify_mic_function = verify_mic; + ss->server_cb->userdata = &sdata; + + /* 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); + + SAFE_FREE(tss->ss->server_cb); + 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_cb_no_client_cred(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); +} + +static void +torture_gssapi_server_auth_cb_invalid_host(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); + + /* 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); +} + +static void +torture_gssapi_server_auth_cb(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); + + /* 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_cb_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/server/torture_gssapi_server_delegation.c b/tests/server/torture_gssapi_server_delegation.c new file mode 100644 index 00000000..2cdb77dc --- /dev/null +++ b/tests/server/torture_gssapi_server_delegation.c @@ -0,0 +1,375 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#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 void +setup_config(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; + + 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; + + /* 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; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +auth_gssapi_mic(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + OM_uint32 min_stat; + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + assert_non_null(creds); + + gss_release_cred(&min_stat, creds); + + return SSH_AUTH_SUCCESS; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->auth_gssapi_mic_function = auth_gssapi_mic; + ss->server_cb->userdata = &sdata; + + /* 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); + + SAFE_FREE(tss->ss->server_cb); + 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 = NULL; + 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 = NULL; + 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_delegate_creds(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session 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; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + 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"); + + 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((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_delegate_creds, + session_setup, + session_teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/torture.c b/tests/torture.c index a76cae78..fffdab32 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -583,10 +583,7 @@ void torture_setup_socket_dir(void **state) assert_non_null(s->socket_dir); #ifdef WITH_GSSAPI - snprintf(gss_dir, - sizeof(gss_dir), - "%s/gss", - s->socket_dir); + 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); @@ -945,17 +942,21 @@ int torture_wait_for_daemon(unsigned int seconds) 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); + int rc; + rc = 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); + if (rc < 0 || rc >= (int)size) { + fail_msg("snprintf failed"); + } } void @@ -965,21 +966,21 @@ torture_set_env_from_str(const char *env) vars = ssh_tokenize(env, ' '); if (vars == NULL) { - fail_msg("bad environment string"); + fail_msg("failed to tokenize 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"); + fail_msg("invalid environment string format"); } 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"); + fail_msg("invalid environment string format"); } ssh_tokens_free(var); } @@ -1031,14 +1032,14 @@ void torture_setup_libssh_server(void **state, const char *server_path) if (s->srv_additional_config != NULL) { printed = snprintf(extra_options, sizeof(extra_options), " %s ", s->srv_additional_config); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { fail_msg("Failed to print additional config!"); /* Unreachable */ __builtin_unreachable(); } } else { printed = snprintf(extra_options, sizeof(extra_options), " "); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { fail_msg("Failed to print empty additional config!"); /* Unreachable */ __builtin_unreachable(); @@ -1068,7 +1069,7 @@ void torture_setup_libssh_server(void **state, const char *server_path) ld_preload, force_fips, kdc_env); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(env)) { fail_msg("Failed to print env!"); /* Unreachable */ __builtin_unreachable(); @@ -1090,7 +1091,7 @@ void torture_setup_libssh_server(void **state, const char *server_path) s->srv_config, s->log_file ? " -l " : "", s->log_file ? s->log_file : "", extra_options, TORTURE_SSH_SERVER); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(start_cmd)) { fail_msg("Failed to print start command!"); /* Unreachable */ __builtin_unreachable(); @@ -1154,14 +1155,17 @@ static int torture_start_sshd_server(void **state) 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 = 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); + if (rc < 0 || rc >= (int)sizeof(sshd_start_cmd)) { + fail_msg("snprintf failed"); + } rc = system(sshd_start_cmd); assert_return_code(rc, errno); @@ -1214,7 +1218,10 @@ torture_setup_kdc_server(void **state, /* 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 = snprintf(command, sizeof(command), "rm -rf %s/*", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } rc = system(command); assert_return_code(rc, errno); @@ -1230,11 +1237,14 @@ torture_setup_kdc_server(void **state, 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 = snprintf(command, + sizeof(command), + "%s/tests/gss/kdcsetup.sh %s", + BINARYDIR, + s->socket_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } rc = system(command); assert_return_code(rc, errno); assert_int_equal(rc, 0); @@ -1257,10 +1267,13 @@ torture_teardown_kdc_server(void **state) { struct torture_state *s = *state; int rc; - char temp[1024] = {0}; + char pid_path[1024] = {0}; - snprintf(temp, sizeof(temp), "%s/pid", s->gss_dir); - rc = torture_terminate_process(temp); + rc = snprintf(pid_path, sizeof(pid_path), "%s/pid", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(pid_path)) { + fail_msg("snprintf failed"); + } + rc = torture_terminate_process(pid_path); assert_return_code(rc, errno); }