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