From 4c16e5f2040cddde2dab7c08957138c076a7e05d Mon Sep 17 00:00:00 2001 From: Isaac Boukris Date: Tue, 8 Jan 2019 07:19:20 +0200 Subject: 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@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) --- src/clients/kvno/kvno.c | 164 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 143 insertions(+), 21 deletions(-) (limited to 'src/clients') 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 #ifdef HAVE_UNISTD_H #include #endif #include +#include 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 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) -- cgit v1.1