From ad1c58d1e5a2fc7382a9c641468c46d3c9d12996 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Wed, 7 Dec 2011 19:38:29 +0000 Subject: [PATCH] Add automated tests for S4U2Self and S4U2Proxy These tests mainly exercise the client-side GSSAPI code for S4U2Self and S4U2Proxy. They also exercise the KDC code for S4U2Self, but only the denial logic for S4U2Proxy since the DB2 back end doesn't support constrained delegation currently. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25531 dc483132-0cff-0310-8789-dd5450dbe970 --- src/tests/gssapi/Makefile.in | 19 ++- src/tests/gssapi/deps | 4 + src/tests/gssapi/t_s4u.c | 2 +- src/tests/gssapi/t_s4u.py | 76 +++++++++ src/tests/gssapi/t_s4u2proxy_krb5.c | 255 ++++++++++++++++++++++++++++ 5 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 src/tests/gssapi/t_s4u.py create mode 100644 src/tests/gssapi/t_s4u2proxy_krb5.c diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index d87ab9a3c..271921278 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -5,18 +5,19 @@ PROG_LIBPATH=-L$(TOPLIBD) PROG_RPATH=$(KRB5_LIBDIR) SRCS= $(srcdir)/t_accname.c $(srcdir)/t_ccselect.c $(srcdir)/t_imp_cred.c \ - $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c \ - $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c + $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_s4u2proxy_krb5.c \ + $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c OBJS= t_accname.o t_ccselect.o t_imp_cred.o t_imp_name.o t_s4u.o \ - t_namingexts.o t_gssexts.o t_spnego.o t_saslname.o + t_s4u2proxy_krb5.o t_namingexts.o t_gssexts.o t_spnego.o t_saslname.o -all:: t_accname t_ccselect t_imp_cred t_imp_name t_s4u t_namingexts t_gssexts \ - t_spnego t_saslname +all:: t_accname t_ccselect t_imp_cred t_imp_name t_s4u t_s4u2proxy_krb5 \ + t_namingexts t_gssexts t_spnego t_saslname -check-pytests:: t_accname t_ccselect t_imp_cred t_spnego +check-pytests:: t_accname t_ccselect t_imp_cred t_spnego t_s4u2proxy_krb5 t_s4u $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_s4u.py $(PYTESTFLAGS) t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS) @@ -28,6 +29,8 @@ t_imp_name: t_imp_name.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_imp_name t_imp_name.o $(GSS_LIBS) $(KRB5_BASE_LIBS) t_s4u: t_s4u.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_s4u t_s4u.o $(GSS_LIBS) $(KRB5_BASE_LIBS) +t_s4u2proxy_krb5: t_s4u2proxy_krb5.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ t_s4u2proxy_krb5.o $(GSS_LIBS) $(KRB5_BASE_LIBS) t_namingexts: t_namingexts.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_namingexts t_namingexts.o $(GSS_LIBS) $(KRB5_BASE_LIBS) t_gssexts: t_gssexts.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) @@ -38,5 +41,5 @@ t_saslname: t_saslname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_saslname t_saslname.o $(GSS_LIBS) $(KRB5_BASE_LIBS) clean:: - $(RM) t_accname t_ccselect t_imp_cred t_imp_name t_s4u t_namingexts \ - t_gssexts t_spnego t_saslname + $(RM) t_accname t_ccselect t_imp_cred t_imp_name t_s4u \ + t_s4u2proxy_krb5 t_namingexts t_gssexts t_spnego t_saslname diff --git a/src/tests/gssapi/deps b/src/tests/gssapi/deps index a87f6e0e0..377a3b37f 100644 --- a/src/tests/gssapi/deps +++ b/src/tests/gssapi/deps @@ -21,6 +21,10 @@ $(OUTPRE)t_s4u.$(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 \ t_s4u.c +$(OUTPRE)t_s4u2proxy_krb5.$(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 \ + t_s4u2proxy_krb5.c $(OUTPRE)t_namingexts.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_generic.h \ $(BUILDTOP)/include/gssapi/gssapi_krb5.h $(BUILDTOP)/include/krb5/krb5.h \ diff --git a/src/tests/gssapi/t_s4u.c b/src/tests/gssapi/t_s4u.c index 4108946dd..ef9016640 100644 --- a/src/tests/gssapi/t_s4u.c +++ b/src/tests/gssapi/t_s4u.c @@ -74,7 +74,7 @@ static void displayStatus_1(m, code, type) maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, &msg_ctx, &msg); - fprintf(stderr, "%s: %s\n", m, (char *)msg.value); + printf("%s: %s\n", m, (char *)msg.value); (void) gss_release_buffer(&min_stat, &msg); if (!msg_ctx) diff --git a/src/tests/gssapi/t_s4u.py b/src/tests/gssapi/t_s4u.py new file mode 100644 index 000000000..bd958f9f4 --- /dev/null +++ b/src/tests/gssapi/t_s4u.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +from k5test import * + +realm = K5Realm(start_kadmind=False, create_host=False, get_creds=False) +usercache = 'FILE:' + os.path.join(realm.testdir, 'usercache') +storagecache = 'FILE:' + os.path.join(realm.testdir, 'save') + +# Create two service principals with keys in the default keytab. +service1 = 'service/1@%s' % realm.realm +realm.addprinc(service1) +realm.extract_keytab(service1, realm.keytab) +service2 = 'service/2@%s' % realm.realm +realm.addprinc(service2) +realm.extract_keytab(service2, realm.keytab) + +# Get forwardable creds for service1 in the default cache. +realm.kinit(service1, None, ['-f', '-k']) + +# Try krb5 -> S4U2Proxy with forwardable user creds. This should fail +# at the S4U2Proxy step since the DB2 back end currently has no +# support for allowing it. +realm.kinit(realm.user_princ, password('user'), ['-f', '-c', usercache]) +output = realm.run_as_server(['./t_s4u2proxy_krb5', usercache, storagecache, + service1, service2], expected_code=1) +if ('auth1: ' + realm.user_princ not in output or + 'NOT_ALLOWED_TO_DELEGATE' not in output): + fail('krb5 -> s4u2proxy') + +# Again with SPNEGO. Bug #7045 prevents us from checking the error +# message, but we can at least exercise the code. +output = realm.run_as_server(['./t_s4u2proxy_krb5', '--spnego', usercache, + storagecache, service1, service2], + expected_code=1) +if ('auth1: ' + realm.user_princ not in output): + fail('krb5 -> s4u2proxy (SPNEGO)') + +# Try krb5 -> S4U2Proxy without forwardable user creds. This should +# result in no delegated credential being created by +# accept_sec_context. +realm.kinit(realm.user_princ, password('user'), ['-c', usercache]) +output = realm.run_as_server(['./t_s4u2proxy_krb5', usercache, storagecache, + service1, service2]) +if 'no credential delegated' not in output: + fail('krb5 -> no delegated cred') + +# Try S4U2Self. Ask for an S4U2Proxy step; this won't happen because +# service/1 isn't allowed to get a forwardable S4U2Self ticket. +output = realm.run_as_server(['./t_s4u', realm.user_princ, service2]) +if ('Warning: no delegated credentials handle' not in output or + 'Source name:\t' + realm.user_princ not in output): + fail('s4u2self') +output = realm.run_as_server(['./t_s4u', '--spnego', realm.user_princ, + service2]) +if ('Warning: no delegated credentials handle' not in output or + 'Source name:\t' + realm.user_princ not in output): + fail('s4u2self (SPNEGO)') + +# Correct that problem and try again. As above, the S4U2Proxy step +# won't actually succeed since we don't support that in DB2. +realm.run_kadminl('modprinc +ok_to_auth_as_delegate ' + service1) +output = realm.run_as_server(['./t_s4u', realm.user_princ, service2], + expected_code=1) +if 'NOT_ALLOWED_TO_DELEGATE' not in output: + fail('s4u2self') + +# Again with SPNEGO. This uses SPNEGO for the initial authentication, +# but still uses krb5 for S4U2Proxy (the delegated cred is returned as +# a krb5 cred, not a SPNEGO cred, and t_s4u uses the delegated cred +# directly rather than saving and reacquiring it) so bug #7045 does +# not apply and we can verify the error message. +output = realm.run_as_server(['./t_s4u', '--spnego', realm.user_princ, + service2], expected_code=1) +if 'NOT_ALLOWED_TO_DELEGATE' not in output: + fail('s4u2self') + +success('S4U test cases') diff --git a/src/tests/gssapi/t_s4u2proxy_krb5.c b/src/tests/gssapi/t_s4u2proxy_krb5.c new file mode 100644 index 000000000..7e7ba39c8 --- /dev/null +++ b/src/tests/gssapi/t_s4u2proxy_krb5.c @@ -0,0 +1,255 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* tests/gssapi/t_s4u2proxy_deleg.c - Test S4U2Proxy after krb5 auth */ +/* + * Copyright 2011 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include +#include +#include + +#include + +/* + * Usage: ./t_s4u2proxy_krb5 [--spnego] client_cache storage_cache + * service1 service2 + * + * This program performs a regular Kerberos or SPNEGO authentication from the + * default principal of client_cache to service1. If that authentication + * yields delegated credentials, the program stores those credentials in + * sorage_ccache and uses that cache to perform a second authentication to + * service2 using S4U2Proxy. + * + * The default keytab must contain keys for service1 and service2. The default + * ccache must contain a TGT for service1. service1 and service2 must be given + * as krb5 principal names. This program assumes that krb5 or SPNEGO + * authentication requires only one token exchange. + */ + +static void +display_status_1(const char *m, OM_uint32 code, int type) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc msg; + OM_uint32 msg_ctx; + + msg_ctx = 0; + while (1) { + maj_stat = gss_display_status(&min_stat, code, + type, GSS_C_NULL_OID, + &msg_ctx, &msg); + printf("%s: %s\n", m, (char *)msg.value); + (void) gss_release_buffer(&min_stat, &msg); + + if (!msg_ctx) + break; + } +} + +static void +gsserr(OM_uint32 maj_stat, OM_uint32 min_stat, const char *msg) +{ + display_status_1(msg, maj_stat, GSS_C_GSS_CODE); + display_status_1(msg, min_stat, GSS_C_MECH_CODE); + exit(1); +} + +static void +krb5err(krb5_context context, krb5_error_code code, const char *msg) +{ + const char *emsg = krb5_get_error_message(context, code); + + printf("%s: %s\n", msg, emsg); + krb5_free_error_message(context, emsg); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + const char *client_ccname, *storage_ccname, *service1, *service2; + krb5_context context = NULL; + krb5_error_code ret; + krb5_boolean use_spnego = FALSE; + krb5_ccache storage_ccache = NULL; + krb5_principal client_princ = NULL; + OM_uint32 minor, major; + gss_buffer_desc buf, token; + gss_OID mech; + gss_OID_set_desc mechs; + gss_name_t service1_name = GSS_C_NO_NAME; + gss_name_t service2_name = GSS_C_NO_NAME; + gss_name_t client_name = GSS_C_NO_NAME; + gss_cred_id_t service1_cred = GSS_C_NO_CREDENTIAL; + gss_cred_id_t deleg_cred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT; + gss_ctx_id_t acceptor_context = GSS_C_NO_CONTEXT; + gss_OID_desc spnego_mech = { 6, "\053\006\001\005\005\002" }; + + /* Parse arguments. */ + if (argc >= 2 && strcmp(argv[1], "--spnego") == 0) { + use_spnego = TRUE; + argc--; + argv++; + } + if (argc != 5) { + fprintf(stderr, "./t_s4u2proxy_krb5 [--spnego] client_cache " + "storage_ccache service1 service2\n"); + return 1; + } + client_ccname = argv[1]; + storage_ccname = argv[2]; + service1 = argv[3]; + service2 = argv[4]; + + mech = use_spnego ? (gss_OID)&spnego_mech : (gss_OID)gss_mech_krb5; + mechs.elements = mech; + mechs.count = 1; + ret = krb5_init_context(&context); + if (ret) + krb5err(context, ret, "krb5_init_context"); + + /* Get GSS name and GSS_C_BOTH cred for service1, using the default + * ccache. */ + buf.value = (char *)service1; + buf.length = strlen(service1); + major = gss_import_name(&minor, &buf, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, + &service1_name); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_import_name(service1)"); + major = gss_acquire_cred(&minor, service1_name, GSS_C_INDEFINITE, + &mechs, GSS_C_BOTH, &service1_cred, NULL, NULL); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_acquire_cred(service1)"); + + /* Get GSS name for service2. */ + buf.value = (char *)service2; + buf.length = strlen(service2); + major = gss_import_name(&minor, &buf, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, + &service2_name); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_import_name(service2)"); + + /* Create initiator context and get the first token, using the client + * ccache. */ + major = gss_krb5_ccache_name(&minor, client_ccname, NULL); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_krb5_ccache_name(1)"); + token.value = NULL; + token.length = 0; + major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, + &initiator_context, service1_name, mech, + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER, NULL, &token, NULL, NULL); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_init_sec_context(1)"); + + /* Pass the token to gss_accept_sec_context. */ + buf.value = NULL; + buf.length = 0; + major = gss_accept_sec_context(&minor, &acceptor_context, + service1_cred, &token, + GSS_C_NO_CHANNEL_BINDINGS, &client_name, + NULL, &buf, NULL, NULL, &deleg_cred); + if (major != GSS_S_COMPLETE) + gsserr(major, minor, "gss_accept_sec_context(1)"); + gss_release_buffer(&minor, &token); + + /* Display and remember the client principal. */ + major = gss_display_name(&minor, client_name, &buf, NULL); + if (major != GSS_S_COMPLETE) + gsserr(major, minor, "gss_display_name(1)"); + printf("auth1: %.*s\n", (int)buf.length, (char *)buf.value); + /* Assumes buffer is null-terminated, which in our implementation it is. */ + ret = krb5_parse_name(context, buf.value, &client_princ); + if (ret) + krb5err(context, ret, "krb5_parse_name"); + gss_release_buffer(&minor, &buf); + + if (deleg_cred == GSS_C_NO_CREDENTIAL) { + printf("no credential delegated.\n"); + goto cleanup; + } + + /* Store the delegated credentials. */ + ret = krb5_cc_resolve(context, storage_ccname, &storage_ccache); + if (ret) + krb5err(context, ret, "krb5_cc_resolve"); + ret = krb5_cc_initialize(context, storage_ccache, client_princ); + if (ret) + krb5err(context, ret, "krb5_cc_initialize"); + major = gss_krb5_copy_ccache(&minor, deleg_cred, storage_ccache); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_krb5_copy_ccache"); + ret = krb5_cc_close(context, storage_ccache); + if (ret) + krb5err(context, ret, "krb5_cc_close"); + + gss_delete_sec_context(&minor, &initiator_context, GSS_C_NO_BUFFER); + gss_delete_sec_context(&minor, &acceptor_context, GSS_C_NO_BUFFER); + + /* Create initiator context and get the first token, using the storage + * ccache. */ + major = gss_krb5_ccache_name(&minor, storage_ccname, NULL); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_krb5_ccache_name(2)"); + token.value = NULL; + token.length = 0; + major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, + &initiator_context, service2_name, mech, + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER, NULL, &token, NULL, NULL); + if (GSS_ERROR(major)) + gsserr(major, minor, "gss_init_sec_context(2)"); + + /* Pass the token to gss_accept_sec_context. */ + buf.value = NULL; + buf.length = 0; + major = gss_accept_sec_context(&minor, &acceptor_context, + GSS_C_NO_CREDENTIAL, &token, + GSS_C_NO_CHANNEL_BINDINGS, &client_name, + NULL, &buf, NULL, NULL, &deleg_cred); + if (major != GSS_S_COMPLETE) + gsserr(major, minor, "gss_accept_sec_context(2)"); + gss_release_buffer(&minor, &token); + + major = gss_display_name(&minor, client_name, &buf, NULL); + if (major != GSS_S_COMPLETE) + gsserr(major, minor, "gss_display_name(2)"); + printf("auth2: %.*s\n", (int)buf.length, (char *)buf.value); + gss_release_buffer(&minor, &buf); + +cleanup: + gss_release_name(&minor, &client_name); + gss_release_name(&minor, &service1_name); + gss_release_name(&minor, &service2_name); + gss_release_cred(&minor, &service1_cred); + gss_release_cred(&minor, &deleg_cred); + gss_delete_sec_context(&minor, &initiator_context, GSS_C_NO_BUFFER); + gss_delete_sec_context(&minor, &acceptor_context, GSS_C_NO_BUFFER); + krb5_free_principal(context, client_princ); + krb5_free_context(context); + return 0; +} -- 2.26.2