From: Greg Hudson Date: Fri, 24 Aug 2018 15:40:39 +0000 (-0400) Subject: Add kvno option for user-to-user X-Git-Tag: krb5-1.17-beta1~56 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=409e0657f8a859d7f3a342ebc1e15755180fef61;p=thirdparty%2Fkrb5.git Add kvno option for user-to-user Add a --u2u option to kvno, with an argument to specify a credential cache containing a krbtgt for the server principal. Move the -allow_svr test from appl/user_to_user to a new test script and add additional tests. Suggested by Chris Hecker. ticket: 8730 (new) --- diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst index 31ca244606..369ca79b74 100644 --- a/doc/user/user_commands/kvno.rst +++ b/doc/user/user_commands/kvno.rst @@ -14,6 +14,7 @@ SYNOPSIS [**-P**] [**-S** *sname*] [**-U** *for_user*] +[**--u2u** *ccache*] *service1 service2* ... @@ -63,6 +64,12 @@ OPTIONS delegation is not requested, the service name must match the credentials cache client principal. +**--u2u** *ccache* + Requests a user-to-user ticket. *ccache* must contain a local + krbtgt ticket for the server principal. The reported version + number will typically be 0, as the resulting ticket is not + encrypted in the server's long-term key. + ENVIRONMENT ----------- diff --git a/src/appl/user_user/t_user2user.py b/src/appl/user_user/t_user2user.py index 0d50d66858..2c054f1819 100755 --- a/src/appl/user_user/t_user2user.py +++ b/src/appl/user_user/t_user2user.py @@ -4,12 +4,6 @@ from k5test import * debug_compiled=1 for realm in multipass_realms(): - # Verify that -allow_svr denies regular TGS requests, but allows - # user-to-user TGS requests. - realm.run([kadminl, 'modprinc', '-allow_svr', realm.user_princ]) - realm.run([kvno, realm.user_princ], expected_code=1, - expected_msg='Server principal valid for user2user only') - if debug_compiled == 0: realm.start_in_inetd(['./uuserver', 'uuserver'], port=9999) else: diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c index 57f7a78321..f4fa048973 100644 --- a/src/clients/kvno/kvno.c +++ b/src/clients/kvno/kvno.c @@ -40,13 +40,13 @@ xusage() { fprintf(stderr, _("usage: %s [-C] [-u] [-c ccache] [-e etype]\n"), prog); fprintf(stderr, _("\t[-k keytab] [-S sname] [-U for_user [-P]]\n")); - fprintf(stderr, _("\tservice1 service2 ...\n")); + fprintf(stderr, _("\t[--u2u ccache] service1 service2 ...\n")); exit(1); } static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr, char *keytab_name, char *sname, int canon, int unknown, - char *for_user, int proxy); + char *for_user, int proxy, const char *u2u_ccname); #include static void extended_com_err_fn(const char *myprog, errcode_t code, @@ -55,9 +55,15 @@ static void extended_com_err_fn(const char *myprog, errcode_t code, int main(int argc, char *argv[]) { + enum { OPTION_U2U = 256 }; + struct option lopts[] = { + { "u2u", 1, NULL, OPTION_U2U }, + { NULL, 0, NULL, 0 } + }; + const char *shopts = "uCc:e:hk:qPS:U:"; int option; char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL; - char *sname = NULL, *for_user = NULL; + char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL; int canon = 0, unknown = 0, proxy = 0; setlocale(LC_ALL, ""); @@ -66,7 +72,7 @@ main(int argc, char *argv[]) prog = strrchr(argv[0], '/'); prog = prog ? (prog + 1) : argv[0]; - while ((option = getopt(argc, argv, "uCc:e:hk:qPS:U:")) != -1) { + while ((option = getopt_long(argc, argv, shopts, lopts, NULL)) != -1) { switch (option) { case 'C': canon = 1; @@ -108,12 +114,20 @@ main(int argc, char *argv[]) case 'U': for_user = optarg; /* S4U2Self - protocol transition */ break; + case OPTION_U2U: + u2u_ccname = optarg; + break; default: xusage(); break; } } + if (u2u_ccname != NULL && for_user != NULL) { + fprintf(stderr, _("Options --u2u and -P are mutually exclusive\n")); + xusage(); + } + if (proxy) { if (keytab_name == NULL) { fprintf(stderr, _("Option -P (constrained delegation) " @@ -130,7 +144,7 @@ main(int argc, char *argv[]) xusage(); do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name, - sname, canon, unknown, for_user, proxy); + sname, canon, unknown, for_user, proxy, u2u_ccname); return 0; } @@ -153,7 +167,8 @@ static void extended_com_err_fn(const char *myprog, errcode_t code, static krb5_error_code kvno(const char *name, krb5_ccache ccache, krb5_principal me, krb5_enctype etype, krb5_keytab keytab, const char *sname, - krb5_flags options, int unknown, krb5_principal for_user_princ, int proxy) + krb5_flags options, int unknown, krb5_principal for_user_princ, int proxy, + krb5_data *u2u_ticket) { krb5_error_code ret; krb5_principal server = NULL; @@ -186,6 +201,9 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me, in_creds.keyblock.enctype = etype; + if (u2u_ticket != NULL) + in_creds.second_ticket = *u2u_ticket; + if (for_user_princ != NULL) { if (!proxy && !krb5_principal_compare(context, me, server)) { ret = EINVAL; @@ -260,10 +278,49 @@ cleanup: return ret; } +/* Fetch the encoded local TGT for ccname's default client principal. */ +static krb5_error_code +get_u2u_ticket(const char *ccname, krb5_data **ticket_out) +{ + krb5_error_code ret; + krb5_ccache cc = NULL; + krb5_creds mcred, *creds = NULL; + + *ticket_out = NULL; + memset(&mcred, 0, sizeof(mcred)); + + ret = krb5_cc_resolve(context, ccname, &cc); + if (ret) + goto cleanup; + ret = krb5_cc_get_principal(context, cc, &mcred.client); + if (ret) + goto cleanup; + ret = krb5_build_principal_ext(context, &mcred.server, + mcred.client->realm.length, + mcred.client->realm.data, + KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, + mcred.client->realm.length, + mcred.client->realm.data, 0); + if (ret) + goto cleanup; + ret = krb5_get_credentials(context, KRB5_GC_CACHED, cc, &mcred, &creds); + if (ret) + goto cleanup; + + ret = krb5_copy_data(context, &creds->ticket, ticket_out); + +cleanup: + if (cc != NULL) + krb5_cc_close(context, cc); + krb5_free_cred_contents(context, &mcred); + krb5_free_creds(context, creds); + return ret; +} + static void do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, char *keytab_name, char *sname, int canon, int unknown, - char *for_user, int proxy) + char *for_user, int proxy, const char *u2u_ccname) { krb5_error_code ret; int i, errors; @@ -272,7 +329,8 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, krb5_principal me; krb5_keytab keytab = NULL; krb5_principal for_user_princ = NULL; - krb5_flags options; + krb5_flags options = canon ? KRB5_GC_CANONICALIZE : 0; + krb5_data *u2u_ticket = NULL; ret = krb5_init_context(&context); if (ret) { @@ -317,18 +375,26 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, } } + if (u2u_ccname != NULL) { + ret = get_u2u_ticket(u2u_ccname, &u2u_ticket); + if (ret) { + com_err(prog, ret, _("while getting user-to-user ticket from %s"), + u2u_ccname); + exit(1); + } + options |= KRB5_GC_USER_USER; + } + ret = krb5_cc_get_principal(context, ccache, &me); if (ret) { com_err(prog, ret, _("while getting client principal name")); exit(1); } - options = canon ? KRB5_GC_CANONICALIZE : 0; - errors = 0; for (i = 0; i < count; i++) { if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown, - for_user_princ, proxy) != 0) + for_user_princ, proxy, u2u_ticket) != 0) errors++; } @@ -337,6 +403,7 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, krb5_free_principal(context, me); krb5_free_principal(context, for_user_princ); krb5_cc_close(context, ccache); + krb5_free_data(context, u2u_ticket); krb5_free_context(context); if (errors) diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index aed23e570e..e27617ee2a 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -176,6 +176,7 @@ check-pytests: unlockiter $(RUNPYTEST) $(srcdir)/t_certauth.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_y2038.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_kdcpolicy.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_u2u.py $(PYTESTFLAGS) clean: $(RM) adata etinfo forward gcred hist hooks hrealm icinterleave icred diff --git a/src/tests/t_u2u.py b/src/tests/t_u2u.py new file mode 100644 index 0000000000..8905dc209a --- /dev/null +++ b/src/tests/t_u2u.py @@ -0,0 +1,27 @@ +from k5test import * + +realm = K5Realm(create_host=False) + +# Create a second user principal and get tickets for it. +u2u_ccache = 'FILE:' + os.path.join(realm.testdir, 'ccu2u') +realm.addprinc('alice', password('alice')) +realm.kinit('alice', password('alice'), ['-c', u2u_ccache]) + +# Verify that -allow_dup_skey denies u2u requests. +realm.run([kadminl, 'modprinc', '-allow_dup_skey', 'alice']) +realm.run([kvno, '--u2u', u2u_ccache, 'alice'], expected_code=1, + expected_msg='KDC policy rejects request') +realm.run([kadminl, 'modprinc', '+allow_dup_skey', 'alice']) + +# Verify that -allow_svr denies regular TGS requests, but allows +# user-to-user TGS requests. +realm.run([kadminl, 'modprinc', '-allow_svr', 'alice']) +realm.run([kvno, 'alice'], expected_code=1, + expected_msg='Server principal valid for user2user only') +realm.run([kvno, '--u2u', u2u_ccache, 'alice'], expected_msg='kvno = 0') +realm.run([kadminl, 'modprinc', '+allow_svr', 'alice']) + +# Try u2u against the client user. +realm.run([kvno, '--u2u', realm.ccache, realm.user_princ]) + +realm.run([klist])