krb5 commit: Add new kvno protocol transition options

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/4c16e5f2040cddde2dab7c08957138c076a7e05d
commit 4c16e5f2040cddde2dab7c08957138c076a7e05d
Author: Isaac Boukris <[email protected]>
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.
    
    [[email protected]: 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.
 .
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.