krb5 commit: Add new kvno protocol transition options

Greg Hudson ghudson at mit.edu
Wed Mar 13 19:39:11 EDT 2019


https://github.com/krb5/krb5/commit/4c16e5f2040cddde2dab7c08957138c076a7e05d
commit 4c16e5f2040cddde2dab7c08957138c076a7e05d
Author: Isaac Boukris <iboukris at gmail.com>
Date:   Tue Jan 8 07:19:20 2019 +0200

    Add new kvno protocol transition options
    
    Add kvno -I to do S4U2Self without treating the principal as an
    enterprise name (as -U does).  Add kvno -F to do S4U2Self with an
    X.509 certificate.
    
    [ghudson at mit.edu: changed code to read cert in PEM format; updated RST
    man page instead of generated nroff file, and regenerated nroff file]
    
    ticket: 8778 (new)

 doc/user/user_commands/kvno.rst |   12 +++-
 src/clients/kvno/kvno.c         |  164 ++++++++++++++++++++++++++++++++++-----
 src/man/kvno.man                |   16 +++-
 3 files changed, 167 insertions(+), 25 deletions(-)

diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst
index 88607df..3892f0c 100644
--- a/doc/user/user_commands/kvno.rst
+++ b/doc/user/user_commands/kvno.rst
@@ -13,7 +13,9 @@ SYNOPSIS
 [**-h**]
 [**-P**]
 [**-S** *sname*]
+[**-I** *for_user*]
 [**-U** *for_user*]
+[**-F** *cert_file*]
 [**--u2u** *ccache*]
 *service1 service2* ...
 
@@ -58,12 +60,20 @@ OPTIONS
     The service hostnames will be canonicalized according to the usual
     rules for constructing service principals.
 
-**-U** *for_user*
+**-I** *for_user*
     Specifies that protocol transition (S4U2Self) is to be used to
     acquire a ticket on behalf of *for_user*.  If constrained
     delegation is not requested, the service name must match the
     credentials cache client principal.
 
+**-U** *for_user*
+    Same as -I, but treats *for_user* as an enterprise name.
+
+**-F** *cert_file*
+    Specifies that protocol transition is to be used, identifying the
+    client principal with the X.509 certificate in *cert_file*.  The
+    certificate file must be in PEM format.
+
 **--u2u** *ccache*
     Requests a user-to-user ticket.  *ccache* must contain a local
     krbtgt ticket for the server principal.  The reported version
diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c
index f4fa048..66ef491 100644
--- a/src/clients/kvno/kvno.c
+++ b/src/clients/kvno/kvno.c
@@ -26,11 +26,14 @@
  */
 
 #include "k5-platform.h"
+#include "k5-buf.h"
+#include "k5-base64.h"
 #include <locale.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 #include <string.h>
+#include <ctype.h>
 
 static char *prog;
 static int quiet = 0;
@@ -39,14 +42,17 @@ static void
 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, _("\t[-k keytab] [-S sname] [{-I | -U} for_user | "
+                      "[-F cert_file] [-P]]\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, const char *u2u_ccname);
+                       char *for_user, int for_user_enterprise,
+                       char *for_user_cert_file, int proxy,
+                       const char *u2u_ccname);
 
 #include <com_err.h>
 static void extended_com_err_fn(const char *myprog, errcode_t code,
@@ -60,11 +66,13 @@ main(int argc, char *argv[])
         { "u2u", 1, NULL, OPTION_U2U },
         { NULL, 0, NULL, 0 }
     };
-    const char *shopts = "uCc:e:hk:qPS:U:";
+    const char *shopts = "uCc:e:hk:qPS:I:U:F:";
     int option;
     char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL;
     char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL;
-    int canon = 0, unknown = 0, proxy = 0;
+    char *for_user_cert_file = NULL;
+    int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0;
+    int impersonate = 0;
 
     setlocale(LC_ALL, "");
     set_com_err_hook(extended_com_err_fn);
@@ -111,8 +119,18 @@ main(int argc, char *argv[])
                 xusage();
             }
             break;
+        case 'I':
+            impersonate = 1;
+            for_user = optarg;
+            break;
         case 'U':
-            for_user = optarg; /* S4U2Self - protocol transition */
+            impersonate = 1;
+            for_user_enterprise = 1;
+            for_user = optarg;
+            break;
+        case 'F':
+            impersonate = 1;
+            for_user_cert_file = optarg;
             break;
         case OPTION_U2U:
             u2u_ccname = optarg;
@@ -123,8 +141,9 @@ main(int argc, char *argv[])
         }
     }
 
-    if (u2u_ccname != NULL && for_user != NULL) {
-        fprintf(stderr, _("Options --u2u and -P are mutually exclusive\n"));
+    if (u2u_ccname != NULL && impersonate) {
+        fprintf(stderr,
+                _("Options --u2u and -I|-U|-F are mutually exclusive\n"));
         xusage();
     }
 
@@ -133,9 +152,9 @@ main(int argc, char *argv[])
             fprintf(stderr, _("Option -P (constrained delegation) "
                               "requires keytab to be specified\n"));
             xusage();
-        } else if (for_user == NULL) {
+        } else if (!impersonate) {
             fprintf(stderr, _("Option -P (constrained delegation) requires "
-                              "option -U (protocol transition)\n"));
+                              "option -I|-U|-F (protocol transition)\n"));
             xusage();
         }
     }
@@ -144,7 +163,8 @@ main(int argc, char *argv[])
         xusage();
 
     do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name,
-               sname, canon, unknown, for_user, proxy, u2u_ccname);
+               sname, canon, unknown, for_user, for_user_enterprise,
+               for_user_cert_file, proxy, u2u_ccname);
     return 0;
 }
 
@@ -162,13 +182,103 @@ static void extended_com_err_fn(const char *myprog, errcode_t code,
     fprintf(stderr, "\n");
 }
 
+/* Read a line from fp into buf.  Trim any trailing whitespace, and return a
+ * pointer to the first non-whitespace character. */
+static const char *
+read_line(FILE *fp, char *buf, size_t bufsize)
+{
+    char *end, *begin;
+
+    if (fgets(buf, bufsize, fp) == NULL)
+        return NULL;
+
+    end = buf + strlen(buf);
+    while (end > buf && isspace((uint8_t)end[-1]))
+        *--end = '\0';
+
+    begin = buf;
+    while (isspace((uint8_t)*begin))
+        begin++;
+
+    return begin;
+}
+
+/* Read a certificate from file_name in PEM format, placing the DER
+ * representation of the certificate in *der_out. */
+static krb5_error_code
+read_pem_file(char *file_name, krb5_data *der_out)
+{
+    krb5_error_code ret = 0;
+    FILE *fp = NULL;
+    const char *begin_line = "-----BEGIN CERTIFICATE-----";
+    const char *end_line = "-----END ", *line;
+    char linebuf[256];
+    struct k5buf buf = EMPTY_K5BUF;
+    uint8_t *der_cert;
+    size_t dlen;
+
+    *der_out = empty_data();
+
+    fp = fopen(file_name, "r");
+    if (fp == NULL)
+        return errno;
+
+    for (;;) {
+        line = read_line(fp, linebuf, sizeof(linebuf));
+        if (line == NULL) {
+            ret = EINVAL;
+            k5_setmsg(context, ret, _("No begin line not found"));
+            goto cleanup;
+        }
+        if (strncmp(line, begin_line, strlen(begin_line)) == 0)
+            break;
+    }
+
+    k5_buf_init_dynamic(&buf);
+    for (;;) {
+        line = read_line(fp, linebuf, sizeof(linebuf));
+        if (line == NULL) {
+            ret = EINVAL;
+            k5_setmsg(context, ret, _("No end line found"));
+            goto cleanup;
+        }
+
+        if (strncmp(line, end_line, strlen(end_line)) == 0)
+            break;
+
+        /* Header lines would be expected for an actual privacy-enhanced mail
+         * message, but not for a certificate. */
+        if (*line == '\0' || strchr(line, ':') != NULL) {
+            ret = EINVAL;
+            k5_setmsg(context, ret, _("Unexpected header line"));
+            goto cleanup;
+        }
+
+        k5_buf_add(&buf, line);
+    }
+
+    der_cert = k5_base64_decode(buf.data, &dlen);
+    if (der_cert == NULL) {
+        ret = EINVAL;
+        k5_setmsg(context, ret, _("Invalid base64"));
+        goto cleanup;
+    }
+
+    *der_out = make_data(der_cert, dlen);
+
+cleanup:
+    fclose(fp);
+    k5_buf_free(&buf);
+    return ret;
+}
+
 /* Request a single service ticket and display its status (unless quiet is
  * set).  On failure, display an error message and return non-zero. */
 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_data *u2u_ticket)
+     krb5_flags options, int unknown, krb5_principal for_user_princ,
+     krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket)
 {
     krb5_error_code ret;
     krb5_principal server = NULL;
@@ -204,7 +314,7 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
     if (u2u_ticket != NULL)
         in_creds.second_ticket = *u2u_ticket;
 
-    if (for_user_princ != NULL) {
+    if (for_user_princ != NULL || for_user_cert != NULL) {
         if (!proxy && !krb5_principal_compare(context, me, server)) {
             ret = EINVAL;
             com_err(prog, ret,
@@ -215,7 +325,8 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
         in_creds.client = for_user_princ;
         in_creds.server = me;
         ret = krb5_get_credentials_for_user(context, options, ccache,
-                                            &in_creds, NULL, &out_creds);
+                                            &in_creds, for_user_cert,
+                                            &out_creds);
     } else {
         in_creds.client = me;
         in_creds.server = server;
@@ -320,17 +431,18 @@ cleanup:
 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, const char *u2u_ccname)
+           char *for_user, int for_user_enterprise,
+           char *for_user_cert_file, int proxy, const char *u2u_ccname)
 {
     krb5_error_code ret;
-    int i, errors;
+    int i, errors, flags;
     krb5_enctype etype;
     krb5_ccache ccache;
     krb5_principal me;
     krb5_keytab keytab = NULL;
     krb5_principal for_user_princ = NULL;
     krb5_flags options = canon ? KRB5_GC_CANONICALIZE : 0;
-    krb5_data *u2u_ticket = NULL;
+    krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL;
 
     ret = krb5_init_context(&context);
     if (ret) {
@@ -366,15 +478,24 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
     }
 
     if (for_user) {
-        ret = krb5_parse_name_flags(context, for_user,
-                                    KRB5_PRINCIPAL_PARSE_ENTERPRISE,
-                                    &for_user_princ);
+        flags = for_user_enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
+        ret = krb5_parse_name_flags(context, for_user, flags, &for_user_princ);
         if (ret) {
             com_err(prog, ret, _("while parsing principal name %s"), for_user);
             exit(1);
         }
     }
 
+    if (for_user_cert_file != NULL) {
+        ret = read_pem_file(for_user_cert_file, &cert_data);
+        if (ret) {
+            com_err(prog, ret, _("while reading certificate file %s"),
+                    for_user_cert_file);
+            exit(1);
+        }
+        user_cert = &cert_data;
+    }
+
     if (u2u_ccname != NULL) {
         ret = get_u2u_ticket(u2u_ccname, &u2u_ticket);
         if (ret) {
@@ -394,7 +515,7 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
     errors = 0;
     for (i = 0; i < count; i++) {
         if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,
-                 for_user_princ, proxy, u2u_ticket) != 0)
+                 for_user_princ, user_cert, proxy, u2u_ticket) != 0)
             errors++;
     }
 
@@ -404,6 +525,7 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
     krb5_free_principal(context, for_user_princ);
     krb5_cc_close(context, ccache);
     krb5_free_data(context, u2u_ticket);
+    krb5_free_data_contents(context, &cert_data);
     krb5_free_context(context);
 
     if (errors)
diff --git a/src/man/kvno.man b/src/man/kvno.man
index ad4bfd5..129c9f2 100644
--- a/src/man/kvno.man
+++ b/src/man/kvno.man
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "KVNO" "1" " " "1.17" "MIT Kerberos"
+.TH "KVNO" "1" " " "1.18" "MIT Kerberos"
 .SH NAME
 kvno \- print key version numbers of Kerberos principals
 .
@@ -39,7 +39,9 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
 [\fB\-h\fP]
 [\fB\-P\fP]
 [\fB\-S\fP \fIsname\fP]
+[\fB\-I\fP \fIfor_user\fP]
 [\fB\-U\fP \fIfor_user\fP]
+[\fB\-F\fP \fIcert_file\fP]
 [\fB\-\-u2u\fP \fIccache\fP]
 \fIservice1 service2\fP ...
 .SH DESCRIPTION
@@ -79,12 +81,20 @@ constructed from those hostnames and the service name \fIsname\fP\&.
 The service hostnames will be canonicalized according to the usual
 rules for constructing service principals.
 .TP
-\fB\-U\fP \fIfor_user\fP
+\fB\-I\fP \fIfor_user\fP
 Specifies that protocol transition (S4U2Self) is to be used to
 acquire a ticket on behalf of \fIfor_user\fP\&.  If constrained
 delegation is not requested, the service name must match the
 credentials cache client principal.
 .TP
+\fB\-U\fP \fIfor_user\fP
+Same as \-I, but treats \fIfor_user\fP as an enterprise name.
+.TP
+\fB\-F\fP \fIcert_file\fP
+Specifies that protocol transition is to be used, identifying the
+client principal with the X.509 certificate in \fIcert_file\fP\&.  The
+certificate file must be in PEM format.
+.TP
 \fB\-\-u2u\fP \fIccache\fP
 Requests a user\-to\-user ticket.  \fIccache\fP must contain a local
 krbtgt ticket for the server principal.  The reported version
@@ -107,6 +117,6 @@ kinit(1), kdestroy(1), kerberos(7)
 .SH AUTHOR
 MIT
 .SH COPYRIGHT
-1985-2018, MIT
+1985-2019, MIT
 .\" Generated by docutils manpage writer.
 .


More information about the cvs-krb5 mailing list