krb5 commit: Add kvno option for user-to-user
Greg Hudson
ghudson at mit.edu
Mon Aug 27 12:29:52 EDT 2018
https://github.com/krb5/krb5/commit/409e0657f8a859d7f3a342ebc1e15755180fef61
commit 409e0657f8a859d7f3a342ebc1e15755180fef61
Author: Greg Hudson <ghudson at mit.edu>
Date: Fri Aug 24 11:40:39 2018 -0400
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)
doc/user/user_commands/kvno.rst | 7 +++
src/appl/user_user/t_user2user.py | 6 ---
src/clients/kvno/kvno.c | 89 ++++++++++++++++++++++++++++++++-----
src/tests/Makefile.in | 1 +
src/tests/t_u2u.py | 27 +++++++++++
5 files changed, 113 insertions(+), 17 deletions(-)
diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst
index 31ca244..369ca79 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 0d50d66..2c054f1 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 57f7a78..f4fa048 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 <com_err.h>
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 aed23e5..e27617e 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 0000000..8905dc2
--- /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])
More information about the cvs-krb5
mailing list