From 02b3171380d5683212ae1deae67a8c6cd19e290a Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Fri, 27 Sep 2019 11:04:45 -0400 Subject: Add NegoEx tests Add a mock NegoEx-only GSS module, a test program which establishes a SPNEGO context, and a Python script to exercise a variety of NegoEx negotiation scenarios. ticket: 8851 --- .gitignore | 1 + src/Makefile.in | 1 + src/configure.ac | 1 + src/plugins/gssapi/negoextest/Makefile.in | 20 ++ src/plugins/gssapi/negoextest/deps | 15 + .../gssapi/negoextest/gss_negoextest.exports | 13 + src/plugins/gssapi/negoextest/main.c | 340 +++++++++++++++++++++ src/tests/gssapi/Makefile.in | 47 +-- src/tests/gssapi/deps | 4 + src/tests/gssapi/t_context.c | 60 ++++ src/tests/gssapi/t_negoex.py | 142 +++++++++ 11 files changed, 623 insertions(+), 21 deletions(-) create mode 100644 src/plugins/gssapi/negoextest/Makefile.in create mode 100644 src/plugins/gssapi/negoextest/deps create mode 100644 src/plugins/gssapi/negoextest/gss_negoextest.exports create mode 100644 src/plugins/gssapi/negoextest/main.c create mode 100644 src/tests/gssapi/t_context.c create mode 100644 src/tests/gssapi/t_negoex.py diff --git a/.gitignore b/.gitignore index bc15f78..adf6615 100644 --- a/.gitignore +++ b/.gitignore @@ -474,6 +474,7 @@ local.properties /src/tests/gssapi/t_add_cred /src/tests/gssapi/t_ccselect /src/tests/gssapi/t_ciflags +/src/tests/gssapi/t_context /src/tests/gssapi/t_credstore /src/tests/gssapi/t_enctypes /src/tests/gssapi/t_err diff --git a/src/Makefile.in b/src/Makefile.in index 691f86c..56c7a4e 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -13,6 +13,7 @@ SUBDIRS=util include lib \ @audit_plugin@ \ plugins/kadm5_hook/test \ plugins/kadm5_auth/test \ + plugins/gssapi/negoextest \ plugins/hostrealm/test \ plugins/localauth/test \ plugins/pwqual/test \ diff --git a/src/configure.ac b/src/configure.ac index 171bad4..79b0bd0 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -1499,6 +1499,7 @@ V5_AC_OUTPUT_MAKEFILE(. kdc kprop config-files build-tools man doc include plugins/certauth/test + plugins/gssapi/negoextest plugins/hostrealm/test plugins/localauth/test plugins/kadm5_hook/test diff --git a/src/plugins/gssapi/negoextest/Makefile.in b/src/plugins/gssapi/negoextest/Makefile.in new file mode 100644 index 0000000..62e8a37 --- /dev/null +++ b/src/plugins/gssapi/negoextest/Makefile.in @@ -0,0 +1,20 @@ +mydir=plugins$(S)gssapi$(S)negoextest +BUILDTOP=$(REL)..$(S)..$(S).. + +LIBBASE=gss_negoextest +LIBMAJOR=0 +LIBMINOR=0 +RELDIR=../plugins/gssapi/negoextest +SHLIB_EXPDEPS=$(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) +SHLIB_EXPLIBS=$(GSS_LIBS) $(KRB5_BASE_LIBS) + +STLIBOBJS=main.o + +SRCS=$(srcdir)/main.c + +all-unix: all-libs +install-unix: +clean-unix:: clean-libs clean-libobjs + +@libnover_frag@ +@libobj_frag@ diff --git a/src/plugins/gssapi/negoextest/deps b/src/plugins/gssapi/negoextest/deps new file mode 100644 index 0000000..1e18ff4 --- /dev/null +++ b/src/plugins/gssapi/negoextest/deps @@ -0,0 +1,15 @@ +# +# Generated makefile dependencies follow. +# +main.so main.po $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/gssapi/gssapi.h $(BUILDTOP)/include/gssapi/gssapi_alloc.h \ + $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + main.c diff --git a/src/plugins/gssapi/negoextest/gss_negoextest.exports b/src/plugins/gssapi/negoextest/gss_negoextest.exports new file mode 100644 index 0000000..7825ab1 --- /dev/null +++ b/src/plugins/gssapi/negoextest/gss_negoextest.exports @@ -0,0 +1,13 @@ +gss_accept_sec_context +gss_acquire_cred +gss_acquire_cred_with_password +gss_delete_sec_context +gss_display_status +gss_import_name +gss_init_sec_context +gss_inquire_sec_context_by_oid +gss_release_cred +gss_release_name +gssspi_exchange_meta_data +gssspi_query_mechanism_info +gssspi_query_meta_data diff --git a/src/plugins/gssapi/negoextest/main.c b/src/plugins/gssapi/negoextest/main.c new file mode 100644 index 0000000..6c340f4 --- /dev/null +++ b/src/plugins/gssapi/negoextest/main.c @@ -0,0 +1,340 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/gssapi/negoextest/main.c - GSS test module for NegoEx */ +/* + * Copyright (C) 2019 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "k5-int.h" +#include +#include +#include + +struct test_context { + int initiator; + uint8_t hops; /* hops remaining; 0 means established */ +}; + +OM_uint32 KRB5_CALLCONV +gss_init_sec_context(OM_uint32 *minor_status, + gss_cred_id_t claimant_cred_handle, + gss_ctx_id_t *context_handle, gss_name_t target_name, + gss_OID mech_type, OM_uint32 req_flags, + OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, gss_OID *actual_mech, + gss_buffer_t output_token, OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + struct test_context *ctx = (struct test_context *)*context_handle; + OM_uint32 major; + gss_buffer_desc tok; + const char *envstr; + uint8_t hops, mech_last_octet; + + if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { + envstr = getenv("HOPS"); + hops = (envstr != NULL) ? atoi(envstr) : 1; + assert(hops > 0); + } else if (input_token->length == 4 && + memcmp(input_token->value, "fail", 4) == 0) { + *minor_status = 12345; + return GSS_S_FAILURE; + } else { + hops = ((uint8_t *)input_token->value)[0]; + } + + mech_last_octet = ((uint8_t *)mech_type->elements)[mech_type->length - 1]; + envstr = getenv("INIT_FAIL"); + if (envstr != NULL && atoi(envstr) == mech_last_octet) + return GSS_S_FAILURE; + + if (ctx == NULL) { + ctx = malloc(sizeof(*ctx)); + assert(ctx != NULL); + ctx->initiator = 1; + ctx->hops = hops; + *context_handle = (gss_ctx_id_t)ctx; + } else if (ctx != NULL) { + assert(ctx->initiator); + ctx->hops--; + assert(ctx->hops == hops); + } + + if (ctx->hops > 0) { + /* Generate a token containing the remaining hop count. */ + ctx->hops--; + tok.value = &ctx->hops; + tok.length = 1; + major = gss_encapsulate_token(&tok, mech_type, output_token); + assert(major == GSS_S_COMPLETE); + } + + return (ctx->hops > 0) ? GSS_S_CONTINUE_NEEDED : GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_accept_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle, + gss_cred_id_t verifier_cred_handle, + gss_buffer_t input_token, + gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, gss_OID *mech_type, + gss_buffer_t output_token, OM_uint32 *ret_flags, + OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + struct test_context *ctx = (struct test_context *)*context_handle; + uint8_t hops, mech_last_octet; + const char *envstr; + + /* + * The unwrapped token sits at the end and is just one byte giving the + * remaining number of hops. The final octet of the mech encoding should + * be just prior to it. + */ + assert(input_token->length >= 2); + hops = ((uint8_t *)input_token->value)[input_token->length - 1]; + mech_last_octet = ((uint8_t *)input_token->value)[input_token->length - 2]; + + envstr = getenv("ACCEPT_FAIL"); + if (envstr != NULL && atoi(envstr) == mech_last_octet) { + output_token->value = gssalloc_strdup("fail"); + assert(output_token->value != NULL); + output_token->length = 4; + return GSS_S_FAILURE; + } + + if (*context_handle == GSS_C_NO_CONTEXT) { + ctx = malloc(sizeof(*ctx)); + assert(ctx != NULL); + ctx->initiator = 0; + ctx->hops = hops; + *context_handle = (gss_ctx_id_t)ctx; + } else { + assert(!ctx->initiator); + ctx->hops--; + assert(ctx->hops == hops); + } + + if (ctx->hops > 0) { + /* Generate a token containing the remaining hop count. */ + ctx->hops--; + output_token->value = gssalloc_malloc(1); + assert(output_token->value != NULL); + memcpy(output_token->value, &ctx->hops, 1); + output_token->length = 1; + } + + return (ctx->hops > 0) ? GSS_S_CONTINUE_NEEDED : GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_delete_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle, + gss_buffer_t output_token) +{ + free(*context_handle); + *context_handle = GSS_C_NO_CONTEXT; + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_acquire_cred(OM_uint32 *minor_status, gss_name_t desired_name, + OM_uint32 time_req, gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_acquire_cred_with_password(OM_uint32 *minor_status, + const gss_name_t desired_name, + const gss_buffer_t password, OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, OM_uint32 *time_rec) +{ + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle) +{ + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, gss_name_t *output_name) +{ + static int dummy; + + /* + * We don't need to remember anything about names, but we do need to + * distinguish them from GSS_C_NO_NAME (to determine the direction of + * gss_query_meta_data() and gss_exchange_meta_data()), so assign an + * arbitrary data pointer. + */ + *output_name = (gss_name_t)&dummy; + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_release_name(OM_uint32 *minor_status, gss_name_t *input_name) +{ + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_display_status(OM_uint32 *minor_status, OM_uint32 status_value, + int status_type, gss_OID mech_type, + OM_uint32 *message_context, gss_buffer_t status_string) +{ + if (status_type == GSS_C_MECH_CODE && status_value == 12345) { + status_string->value = gssalloc_strdup("failure from acceptor"); + assert(status_string->value != NULL); + status_string->length = strlen(status_string->value); + return GSS_S_COMPLETE; + } + return GSS_S_BAD_STATUS; +} + +OM_uint32 KRB5_CALLCONV +gssspi_query_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid, + gss_cred_id_t cred_handle, gss_ctx_id_t *context_handle, + const gss_name_t targ_name, OM_uint32 req_flags, + gss_buffer_t meta_data) +{ + const char *envstr; + uint8_t mech_last_octet; + int initiator = (targ_name != GSS_C_NO_NAME); + + mech_last_octet = ((uint8_t *)mech_oid->elements)[mech_oid->length - 1]; + envstr = getenv(initiator ? "INIT_QUERY_FAIL" : "ACCEPT_QUERY_FAIL"); + if (envstr != NULL && atoi(envstr) == mech_last_octet) + return GSS_S_FAILURE; + envstr = getenv(initiator ? "INIT_QUERY_NONE" : "ACCEPT_QUERY_NONE"); + if (envstr != NULL && atoi(envstr) == mech_last_octet) + return GSS_S_COMPLETE; + + meta_data->value = gssalloc_strdup("X"); + meta_data->length = 1; + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gssspi_exchange_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid, + gss_cred_id_t cred_handle, + gss_ctx_id_t *context_handle, + const gss_name_t targ_name, OM_uint32 req_flags, + gss_const_buffer_t meta_data) +{ + const char *envstr; + uint8_t mech_last_octet; + int initiator = (targ_name != GSS_C_NO_NAME); + + mech_last_octet = ((uint8_t *)mech_oid->elements)[mech_oid->length - 1]; + envstr = getenv(initiator ? "INIT_EXCHANGE_FAIL" : "ACCEPT_EXCHANGE_FAIL"); + if (envstr != NULL && atoi(envstr) == mech_last_octet) + return GSS_S_FAILURE; + + assert(meta_data->length == 1 && memcmp(meta_data->value, "X", 1) == 0); + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gssspi_query_mechanism_info(OM_uint32 *minor_status, gss_const_OID mech_oid, + unsigned char auth_scheme[16]) +{ + /* Copy the mech OID encoding and right-pad it with zeros. */ + memset(auth_scheme, 0, 16); + assert(mech_oid->length <= 16); + memcpy(auth_scheme, mech_oid->elements, mech_oid->length); + return GSS_S_COMPLETE; +} + +OM_uint32 KRB5_CALLCONV +gss_inquire_sec_context_by_oid(OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + struct test_context *ctx = (struct test_context *)context_handle; + OM_uint32 major; + uint8_t keybytes[32] = { 0 }; + uint8_t typebytes[4]; + gss_buffer_desc key, type; + const char *envstr; + int ask_verify; + + if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_KEY)) + ask_verify = 0; + else if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_VERIFY_KEY)) + ask_verify = 1; + else + return GSS_S_UNAVAILABLE; + + /* + * By default, make a key available only if the context is established. + * This can be overridden to "always", "init-always", "accept-always", + * or "never". + */ + envstr = getenv("KEY"); + if (envstr != NULL && strcmp(envstr, "never") == 0) { + return GSS_S_UNAVAILABLE; + } else if (ctx->hops > 0) { + if (envstr == NULL) + return GSS_S_UNAVAILABLE; + else if (strcmp(envstr, "init-always") == 0 && !ctx->initiator) + return GSS_S_UNAVAILABLE; + else if (strcmp(envstr, "accept-always") == 0 && ctx->initiator) + return GSS_S_UNAVAILABLE; + } + + /* Perturb the key so that each side's verifier key is equal to the other's + * checksum key. */ + keybytes[0] = ask_verify ^ ctx->initiator; + + /* Supply an all-zeros aes256-sha1 negoex key. */ + if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_KEY) || + gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_VERIFY_KEY)) { + store_32_le(ENCTYPE_AES256_CTS_HMAC_SHA1_96, typebytes); + key.value = keybytes; + key.length = sizeof(keybytes); + type.value = typebytes; + type.length = sizeof(typebytes); + major = gss_add_buffer_set_member(minor_status, &key, data_set); + if (major != GSS_S_COMPLETE) + return major; + return gss_add_buffer_set_member(minor_status, &type, data_set); + } + + return GSS_S_UNAVAILABLE; +} diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index a7b8da4..5cc1e0f 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -10,10 +10,11 @@ LOCALINCLUDES = -I$(srcdir)/../../lib/gssapi/mechglue \ SRCS= $(srcdir)/ccinit.c $(srcdir)/ccrefresh.c $(srcdir)/common.c \ $(srcdir)/t_accname.c $(srcdir)/t_add_cred.c $(srcdir)/t_ccselect.c \ - $(srcdir)/t_ciflags.c $(srcdir)/t_credstore.c $(srcdir)/t_enctypes.c \ - $(srcdir)/t_err.c $(srcdir)/t_export_cred.c $(srcdir)/t_export_name.c \ - $(srcdir)/t_gssexts.c $(srcdir)/t_imp_cred.c $(srcdir)/t_imp_name.c \ - $(srcdir)/t_invalid.c $(srcdir)/t_inq_cred.c $(srcdir)/t_inq_ctx.c \ + $(srcdir)/t_ciflags.c $(srcdir)/t_context.c $(srcdir)/t_credstore.c \ + $(srcdir)/t_enctypes.c $(srcdir)/t_err.c $(srcdir)/t_export_cred.c \ + $(srcdir)/t_export_name.c $(srcdir)/t_gssexts.c \ + $(srcdir)/t_imp_cred.c $(srcdir)/t_imp_name.c $(srcdir)/t_invalid.c \ + $(srcdir)/t_inq_cred.c $(srcdir)/t_inq_ctx.c \ $(srcdir)/t_inq_mechs_name.c $(srcdir)/t_iov.c \ $(srcdir)/t_lifetime.c $(srcdir)/t_namingexts.c $(srcdir)/t_oid.c \ $(srcdir)/t_pcontok.c $(srcdir)/t_prf.c $(srcdir)/t_s4u.c \ @@ -21,20 +22,20 @@ SRCS= $(srcdir)/ccinit.c $(srcdir)/ccrefresh.c $(srcdir)/common.c \ $(srcdir)/t_spnego.c $(srcdir)/t_srcattrs.c OBJS= ccinit.o ccrefresh.o common.o t_accname.o t_add_cred.o t_ccselect.o \ - t_ciflags.o t_credstore.o t_enctypes.o t_err.o t_export_cred.o \ - t_export_name.o t_gssexts.o t_imp_cred.o t_imp_name.o t_invalid.o \ - t_inq_cred.o t_inq_ctx.o t_inq_mechs_name.o t_iov.o t_lifetime.o \ - t_namingexts.o t_oid.o t_pcontok.o t_prf.o t_s4u.o t_s4u2proxy_krb5.o \ - t_saslname.o t_spnego.o t_srcattrs.o + t_ciflags.o t_context.o t_credstore.o t_enctypes.o t_err.o \ + t_export_cred.o t_export_name.o t_gssexts.o t_imp_cred.o t_imp_name.o \ + t_invalid.o t_inq_cred.o t_inq_ctx.o t_inq_mechs_name.o t_iov.o \ + t_lifetime.o t_namingexts.o t_oid.o t_pcontok.o t_prf.o t_s4u.o \ + t_s4u2proxy_krb5.o t_saslname.o t_spnego.o t_srcattrs.o COMMON_DEPS= common.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) COMMON_LIBS= common.o $(GSS_LIBS) $(KRB5_BASE_LIBS) -all: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags t_credstore \ - t_enctypes t_err t_export_cred t_export_name t_gssexts t_imp_cred \ - t_imp_name t_invalid t_inq_cred t_inq_ctx t_inq_mechs_name t_iov \ - t_lifetime t_namingexts t_oid t_pcontok t_prf t_s4u t_s4u2proxy_krb5 \ - t_saslname t_spnego t_srcattrs +all: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags t_context \ + t_credstore t_enctypes t_err t_export_cred t_export_name t_gssexts \ + t_imp_cred t_imp_name t_invalid t_inq_cred t_inq_ctx t_inq_mechs_name \ + t_iov t_lifetime t_namingexts t_oid t_pcontok t_prf t_s4u \ + t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs check-unix: t_oid $(RUN_TEST) ./t_invalid @@ -43,9 +44,9 @@ check-unix: t_oid $(RUN_TEST) ./t_imp_name check-pytests: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags \ - t_credstore t_enctypes t_err t_export_cred t_export_name t_imp_cred \ - t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime t_pcontok \ - t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs + t_context t_credstore t_enctypes t_err t_export_cred t_export_name \ + t_imp_cred t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime \ + t_pcontok t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_client_keytab.py $(PYTESTFLAGS) @@ -53,6 +54,7 @@ check-pytests: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags \ $(RUNPYTEST) $(srcdir)/t_export_cred.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_s4u.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_authind.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_negoex.py $(PYTESTFLAGS) ccinit: ccinit.o $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o ccinit ccinit.o $(KRB5_BASE_LIBS) @@ -66,6 +68,8 @@ t_ccselect: t_ccselect.o $(COMMON_DEPS) $(CC_LINK) -o $@ t_ccselect.o $(COMMON_LIBS) t_ciflags: t_ciflags.o $(COMMON_DEPS) $(CC_LINK) -o $@ t_ciflags.o $(COMMON_LIBS) +t_context: t_context.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_context.o $(COMMON_LIBS) t_credstore: t_credstore.o $(COMMON_DEPS) $(CC_LINK) -o $@ t_credstore.o $(COMMON_LIBS) t_enctypes: t_enctypes.o $(COMMON_DEPS) @@ -115,7 +119,8 @@ t_srcattrs: t_srcattrs.o $(COMMON_DEPS) clean: $(RM) ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags - $(RM) t_credstore t_enctypes t_err t_export_cred t_export_name - $(RM) t_gssexts t_imp_cred t_imp_name t_invalid t_inq_cred t_inq_ctx - $(RM) t_inq_mechs_name t_iov t_lifetime t_namingexts t_oid t_pcontok - $(RM) t_prf t_s4u t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs + $(RM) t_context t_credstore t_enctypes t_err t_export_cred + $(RM) t_export_name t_gssexts t_imp_cred t_imp_name t_invalid + $(RM) t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime + $(RM) t_namingexts t_oid t_pcontok t_prf t_s4u t_s4u2proxy_krb5 + $(RM) t_saslname t_spnego t_srcattrs diff --git a/src/tests/gssapi/deps b/src/tests/gssapi/deps index c8905c9..acd0e96 100644 --- a/src/tests/gssapi/deps +++ b/src/tests/gssapi/deps @@ -41,6 +41,10 @@ $(OUTPRE)t_ciflags.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ common.h t_ciflags.c +$(OUTPRE)t_context.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ + $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ + $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ + common.h t_context.c $(OUTPRE)t_credstore.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ diff --git a/src/tests/gssapi/t_context.c b/src/tests/gssapi/t_context.c new file mode 100644 index 0000000..65381ac --- /dev/null +++ b/src/tests/gssapi/t_context.c @@ -0,0 +1,60 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/gssapi/t_context.c - Simple context establishment harness */ +/* + * Copyright (C) 2019 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "common.h" + +int +main(int argc, char *argv[]) +{ + OM_uint32 minor, flags; + gss_name_t tname; + gss_ctx_id_t ictx, actx; + + if (argc < 2) { + fprintf(stderr, "Usage: %s targetname [acceptorname]\n", argv[0]); + return 1; + } + + tname = import_name(argv[1]); + + flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; + establish_contexts(&mech_spnego, GSS_C_NO_CREDENTIAL, GSS_C_NO_CREDENTIAL, + tname, flags, &ictx, &actx, NULL, NULL, NULL); + + (void)gss_release_name(&minor, &tname); + (void)gss_delete_sec_context(&minor, &ictx, NULL); + (void)gss_delete_sec_context(&minor, &actx, NULL); + return 0; +} diff --git a/src/tests/gssapi/t_negoex.py b/src/tests/gssapi/t_negoex.py new file mode 100644 index 0000000..88470d2 --- /dev/null +++ b/src/tests/gssapi/t_negoex.py @@ -0,0 +1,142 @@ +from k5test import * + +# The next arc after 2.25 is supposed to be a single-integer UUID, but +# since our gss_str_to_oid() can't handle arc values that don't fit in +# an unsigned long, we use random unsigned 32-bit integers instead. +# The final octet if the OID encoding will be used to identify the +# mechanism when changing the behavior of just one mech. +nxtest_oid1 = '2.25.1414534758' # final octet is 102 (0x66) +nxtest_oid2 = '2.25.1175737388' # final octet is 44 (0x2C) +nxtest_path = os.path.join(buildtop, 'plugins', 'gssapi', 'negoextest', + 'gss_negoextest.so') + +# Test gss_add_cred(). +realm = K5Realm(create_kdb=False) +with open(realm.gss_mech_config, 'w') as f: + f.write('negoextest %s %s\n' % (nxtest_oid1, nxtest_path)) + f.write('negoextest %s %s\n' % (nxtest_oid2, nxtest_path)) + +def test(envvars, **kw): + # Python 3.5: e = {**realm.env, **vars} + e = realm.env.copy() + e.update(envvars) + realm.run(['./t_context', 'h:host'], env=e, **kw) + +# Test varying numbers of hops, and spot-check that messages are sent +# in the appropriate sequence. + +mark('One hop') +msgs = ('sending [0]INITIATOR_NEGO: c0a28569-66ac-0000-0000-000000000000 ' + 'd1b08469-2ca8-0000-0000-000000000000', + 'sending [1]INITIATOR_META_DATA: c0a28569-66ac', + 'sending [2]INITIATOR_META_DATA: d1b08469-2ca8', + 'sending [3]AP_REQUEST: c0a28569-66ac', + 'sending [4]VERIFY: c0a28569-66ac', + 'received [0]INITIATOR_NEGO: c0a28569-66ac-0000-0000-000000000000 ' + 'd1b08469-2ca8-0000-0000-000000000000', + 'received [1]INITIATOR_META_DATA: c0a28569-66ac', + 'received [2]INITIATOR_META_DATA: d1b08469-2ca8', + 'received [3]AP_REQUEST: c0a28569-66ac', + 'received [4]VERIFY: c0a28569-66ac', + 'sending [5]ACCEPTOR_NEGO: c0a28569-66ac-0000-0000-000000000000 ' + 'd1b08469-2ca8-0000-0000-000000000000', + 'sending [6]ACCEPTOR_META_DATA: c0a28569-66ac', + 'sending [7]ACCEPTOR_META_DATA: d1b08469-2ca8', + 'sending [8]VERIFY: c0a28569-66ac', + 'received [5]ACCEPTOR_NEGO: c0a28569-66ac-0000-0000-000000000000 ' + 'd1b08469-2ca8-0000-0000-000000000000', + 'received [6]ACCEPTOR_META_DATA: c0a28569-66ac', + 'received [7]ACCEPTOR_META_DATA: d1b08469-2ca8', + 'received [8]VERIFY: c0a28569-66ac') +test({'HOPS': '1'}, expected_trace=msgs) + +mark('Two hops') +msgs = ('sending [7]CHALLENGE', 'sending [8]VERIFY', 'received [8]VERIFY', + 'sending [9]VERIFY') +test({'HOPS': '2'}, expected_trace=msgs) + +mark('Three hops') +msgs = ('sending [8]AP_REQUEST', 'sending [9]VERIFY', 'received [8]AP_REQUEST', + 'sending [10]VERIFY') +test({'HOPS': '3'}, expected_trace=msgs) + +mark('Four hops') +msgs = ('sending [9]CHALLENGE', 'sending [10]VERIFY', 'received [9]CHALLENGE', + 'sending [11]VERIFY') +test({'HOPS': '4'}, expected_trace=msgs) + +mark('Early keys, three hops') +msgs = ('sending [4]VERIFY', 'sending [9]VERIFY', 'sending [10]AP_REQUEST') +test({'HOPS': '3', 'KEY': 'always'}, expected_trace=msgs) + +mark('Early keys, four hops') +msgs = ('sending [4]VERIFY', 'sending [9]VERIFY', 'sending [10]AP_REQUEST', + 'sending [11]CHALLENGE') +test({'HOPS': '4', 'KEY': 'always'}, expected_trace=msgs) + +mark('No keys') +test({'KEY': 'never'}, expected_code=1, expected_msg='No NegoEx verify key') + +mark('No optimistic token') +msgs = ('sending [3]ACCEPTOR_NEGO', 'sending [6]AP_REQUEST', + 'sending [7]VERIFY', 'sending [8]VERIFY') +test({'NEGOEX_NO_OPTIMISTIC_TOKEN': ''}, expected_trace=msgs) + +mark('First mech initiator query fail') +msgs = ('sending [0]INITIATOR_NEGO: d1b08469-2ca8-0000-0000-000000000000', + 'sending [2]AP_REQUEST', 'sending [3]VERIFY', + 'sending [4]ACCEPTOR_NEGO: d1b08469-2ca8-0000-0000-000000000000', + 'sending [6]VERIFY') +test({'INIT_QUERY_FAIL': '102'}, expected_trace=msgs) + +mark('First mech acceptor query fail') +msgs = ('sending [0]INITIATOR_NEGO: c0a28569-66ac-0000-0000-000000000000 ' + 'd1b08469-2ca8-0000-0000-000000000000', + 'sending [3]AP_REQUEST: c0a28569-66ac', + 'sending [4]VERIFY: c0a28569-66ac', + 'sending [5]ACCEPTOR_NEGO: d1b08469-2ca8-0000-0000-000000000000', + 'sending [7]AP_REQUEST: d1b08469-2ca8', + 'sending [8]VERIFY: d1b08469-2ca8', + 'sending [9]VERIFY: d1b08469-2ca8') +test({'ACCEPT_QUERY_FAIL': '102'}, expected_trace=msgs) + +# Same messages as previous test. +mark('First mech acceptor exchange fail') +test({'ACCEPT_EXCHANGE_FAIL': '102'}, expected_trace=msgs) + +# Fail the optimistic mech's gss_exchange_meta_data() in the +# initiator. Since the acceptor has effectively selected the +# optimistic mech, this causes the authentication to fail. +mark('First mech initiator exchange fail, one hop') +test({'HOPS': '1', 'INIT_EXCHANGE_FAIL': '102'}, expected_code=1, + expected_msg='No mutually supported NegoEx authentication schemes') +mark('First mech initiator exchange fail, two hops, early keys') +test({'HOPS': '2', 'INIT_EXCHANGE_FAIL': '102', 'KEY': 'always'}, + expected_code=1, + expected_msg='No mutually supported NegoEx authentication schemes') +mark('First mech initiator exchange fail, two hops') +test({'HOPS': '2', 'INIT_EXCHANGE_FAIL': '102'}, expected_code=1, + expected_msg='No mutually supported NegoEx authentication schemes') + +mark('First mech init_sec_context fail') +msgs = ('sending [0]INITIATOR_NEGO: d1b08469-2ca8-0000-0000-000000000000', + 'sending [2]AP_REQUEST', 'sending [3]VERIFY', 'sending [6]VERIFY') +test({'INIT_FAIL': '102'}, expected_trace=msgs) + +mark('First mech accept_sec_context fail') +test({'HOPS': '2', 'ACCEPT_FAIL': '102'}, expected_code=1, + expected_msg='failure from acceptor') + +mark('ALERT from acceptor to initiator') +msgs = ('sending [3]AP_REQUEST', 'sending [4]VERIFY', 'sending [8]CHALLENGE', + 'sending [9]ALERT', 'received [9]ALERT', 'sending [10]AP_REQUEST', + 'sending [11]VERIFY', 'sending [12]VERIFY') +test({'HOPS': '3', 'KEY': 'init-always'}, expected_trace=msgs) + +mark('ALERT from initiator to acceptor') +msgs = ('sending [3]AP_REQUEST', 'sending [7]CHALLENGE', 'sending [8]VERIFY', + 'sending [9]AP_REQUEST', 'sending [10]ALERT', 'received [10]ALERT', + 'sending [11]CHALLENGE', 'sending [12]VERIFY', 'sending [13]VERIFY') +test({'HOPS': '4', 'KEY': 'accept-always'}, expected_trace=()) + +success('NegoEx tests') -- cgit v1.1