gssapi: Import elric1's gss-token
authorNicolas Williams <nico@twosigma.com>
Mon, 18 Nov 2019 21:54:39 +0000 (15:54 -0600)
committerNicolas Williams <nico@twosigma.com>
Wed, 20 Nov 2019 05:00:41 +0000 (23:00 -0600)
lib/gssapi/Makefile.am
lib/gssapi/gss-token.1 [new file with mode: 0644]
lib/gssapi/gss-token.c [new file with mode: 0644]

index 464dd9b7eb01a99da6bf537603b970c7dd652d96..c9ba40643692036343c30dd20368061813be4b24 100644 (file)
@@ -235,7 +235,7 @@ libgssapi_la_LIBADD = \
        $(LIB_hcrypto) \
        $(LIBADD_roken)
 
-man_MANS = gssapi.3 gss_acquire_cred.3 mech/mech.5
+man_MANS = gssapi.3 gss_acquire_cred.3 mech/mech.5 gss-token.1
 
 include_HEADERS = gssapi.h
 noinst_HEADERS = \
@@ -311,7 +311,7 @@ test_cfx_SOURCES = krb5/test_cfx.c
 
 check_PROGRAMS = test_acquire_cred $(TESTS)
 
-bin_PROGRAMS = gsstool
+bin_PROGRAMS = gsstool gss-token
 noinst_PROGRAMS = test_cred test_kcred test_context test_ntlm test_add_store_cred
 
 test_context_SOURCES = test_context.c test_common.c test_common.h
@@ -332,6 +332,7 @@ LDADD = libgssapi.la \
 
 dist_gsstool_SOURCES = gsstool.c
 nodist_gsstool_SOURCES = gss-commands.c gss-commands.h
+dist_gss_token_SOURCES = gss-token.c
 
 gsstool_LDADD = libgssapi.la \
        $(top_builddir)/lib/sl/libsl.la \
@@ -339,6 +340,10 @@ gsstool_LDADD = libgssapi.la \
        $(LIB_readline) \
        $(LIB_roken)
 
+gss_token_LDADD = libgssapi.la \
+       $(top_builddir)/lib/krb5/libkrb5.la \
+       $(LIB_roken)
+
 gss-commands.c gss-commands.h: gss-commands.in
        $(SLC) $(srcdir)/gss-commands.in
 
diff --git a/lib/gssapi/gss-token.1 b/lib/gssapi/gss-token.1
new file mode 100644 (file)
index 0000000..431cd94
--- /dev/null
@@ -0,0 +1,75 @@
+.\"
+.\"
+.Dd May 12, 2014
+.Os
+.Dt GSS-TOKEN 1
+.Sh NAME
+.Nm gss-token
+.Nd generate and consume base64 GSS tokens
+.Sh SYNOPSIS
+.Nm
+.Op Fl DNn
+.Op Fl c count
+.Ar service@host
+.Nm
+.Fl r
+.Op Fl MNln
+.Op Fl C Ar ccache
+.Op Fl c count
+.Op Ar service@host
+.Sh DESCRIPTION
+.Nm
+generates and consumes base64 encoded GSS tokens.
+It is mostly useful for testing.
+.Pp
+.Nm
+supports the following options:
+.Bl -tag -width indentxxxx
+.It Fl C Ar ccache
+write an accepted delegated credential into
+.Ar ccache .
+This only makes sense if
+.Fl r
+is specified.
+.It Fl D
+delegate credentials.
+This only makes sense as a client, that is when
+.Fl r
+is not specified.
+.It Fl M
+copy the default ccache to a MEMORY: ccache before each
+separate write operation.
+The default ccache will not pick up any obtained service
+tickets.
+If specified with
+.Fl c ,
+the cache will revert to its original state before each
+new token is written.
+This can be used to load test the KDC.
+.It Fl N
+prepend
+.Dq Negotiate\ 
+to generated tokens and expect it on consumed tokens.
+.It Fl c Ar count
+repeat the operation
+.Ar count
+times.
+This is good for very basic benchmarking.
+.It Fl l
+loop infinitely in read mode.
+This is to support a multiple round trip GSS mechanism.
+.It Fl n
+do not output the generated token.
+.It Fl r
+read a token rather than generate a token.
+.El
+.Pp
+.Nm
+takes one argument, a
+.Ar host@service
+specifier.
+The argument is required when generating a token but is optional if
+consuming (reading) a token.
+.Sh SEE ALSO
+.Xr gssapi 3 ,
+.Xr kerberos 8 .
diff --git a/lib/gssapi/gss-token.c b/lib/gssapi/gss-token.c
new file mode 100644 (file)
index 0000000..48de484
--- /dev/null
@@ -0,0 +1,563 @@
+/* */
+
+/*-
+ * Copyright (c) 1997-2011 Roland C. Dowdeswell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer and
+ *    dedication in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <errno.h>
+#ifdef __APPLE__
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+#include <krb5.h>
+#include <base64.h>
+#include <getarg.h>
+#include <roken.h>
+#include <vers.h>
+
+#define GBAIL(x, _maj, _min)   do {                                    \
+               if (GSS_ERROR(_maj)) {                                  \
+                       char    *the_gss_err;                           \
+                                                                       \
+                       ret = 1;                                        \
+                       the_gss_err = gss_mk_err(_maj, _min, x);        \
+                       if (the_gss_err)                                \
+                               fprintf(stderr, "%s\n", the_gss_err);   \
+                       else                                            \
+                               fprintf(stderr, "err making err\n");    \
+                       free(the_gss_err);                              \
+                       goto bail;                                      \
+               }                                                       \
+       } while (0)
+
+#define K5BAIL(x)      do {                                            \
+               kret = x;                                               \
+               if (kret) {                                             \
+                       const char      *k5err;                         \
+                                                                       \
+                       k5err = krb5_get_error_message(kctx, kret);     \
+                       if (k5err) {                                    \
+                               fprintf(stderr, "%s in %s:%s\n", k5err, \
+                                   #x, __func__);                      \
+                               krb5_free_error_message(kctx, k5err);   \
+                       } else {                                        \
+                               fprintf(stderr, "unknown error %d in "  \
+                                   "%s:%s\n", kret, #x, __func__);     \
+                       }                                               \
+                       exit(1); /* XXXrcd: shouldn't exit */           \
+               }                                                       \
+       } while (0)
+
+
+/*
+ * global variables
+ */
+
+int    nflag = 0;
+
+static char *
+gss_mk_err(OM_uint32 maj_stat, OM_uint32 min_stat, const char *preamble)
+{
+       gss_buffer_desc  status;
+       OM_uint32        new_stat;
+       OM_uint32        cur_stat;
+       OM_uint32        msg_ctx = 0;
+       OM_uint32        ret;
+       int              type;
+       size_t           newlen;
+       char            *str = NULL;
+       char            *tmp = NULL;
+
+       cur_stat = maj_stat;
+       type = GSS_C_GSS_CODE;
+
+       for (;;) {
+
+               /*
+                * GSS_S_FAILURE produces a rather unhelpful message, so
+                * we skip straight to the mech specific error in this case.
+                */
+
+               if (type == GSS_C_GSS_CODE && cur_stat == GSS_S_FAILURE) {
+                       type = GSS_C_MECH_CODE;
+                       cur_stat = min_stat;
+               }
+
+               ret = gss_display_status(&new_stat, cur_stat, type,
+                   GSS_C_NO_OID, &msg_ctx, &status);
+
+               if (GSS_ERROR(ret))
+                       return str;     /* XXXrcd: hmmm, not quite?? */
+
+               if (str)
+                       newlen = strlen(str);
+               else
+                       newlen = strlen(preamble);
+
+               newlen += status.length + 3;
+
+               tmp = str;
+               str = malloc(newlen);
+
+               if (!str) {
+                       gss_release_buffer(&new_stat, &status);
+                       return tmp;     /* XXXrcd: hmmm, not quite?? */
+               }
+
+               snprintf(str, newlen, "%s%s%.*s", tmp?tmp:preamble,
+                   tmp?", ":": ", (int)status.length, (char *)status.value);
+
+               gss_release_buffer(&new_stat, &status);
+               free(tmp);
+
+               /*
+                * If we are finished processing for maj_stat, then
+                * move onto min_stat.
+                */
+
+               if (msg_ctx == 0 && type == GSS_C_GSS_CODE && min_stat != 0) {
+                       type = GSS_C_MECH_CODE;
+                       cur_stat = min_stat;
+                       continue;
+               }
+
+               if (msg_ctx == 0)
+                       break;
+       }
+
+       return str;
+}
+
+static int
+write_one_token(gss_name_t service, int delegate, int negotiate)
+{
+       gss_ctx_id_t     ctx = GSS_C_NO_CONTEXT;
+       gss_buffer_desc  in;
+       gss_buffer_desc  out;
+       OM_uint32        maj;
+       OM_uint32        min;
+       OM_uint32        flags = 0;
+       int              ret = 0;
+       char            *base64_output = NULL;
+
+       in.length  = 0;
+       in.value   = 0;
+       out.length = 0;
+       out.value  = 0;
+
+       if (delegate)
+               flags |= GSS_C_DELEG_FLAG;
+
+        maj = gss_init_sec_context(&min, GSS_C_NO_CREDENTIAL, &ctx, service,
+           GSS_C_NO_OID, flags, 0, GSS_C_NO_CHANNEL_BINDINGS, &in, NULL, &out,
+           NULL, NULL);
+
+       GBAIL("gss_init_sec_context", maj, min);
+
+       if (rk_base64_encode(out.value, out.length, &base64_output) < 0) {
+               fprintf(stderr, "Out of memory.\n");
+               ret = 1;
+               goto bail;
+       }
+
+       if (!nflag)
+               printf("%s%s\n", negotiate?"Negotiate ":"", base64_output);
+
+bail:
+       if (out.value)
+               gss_release_buffer(&min, &out);
+
+       if (ctx != GSS_C_NO_CONTEXT) {
+               /*
+                * XXXrcd: here we ignore the fact that we might have an
+                *         output token as this program doesn't do terribly
+                *         well in that case.
+                */
+               gss_delete_sec_context(&min, &ctx, NULL);
+       }
+
+       free(base64_output);
+
+       return ret;
+}
+
+static krb5_error_code
+copy_cache(krb5_context kctx, krb5_ccache from, krb5_ccache to)
+{
+       krb5_error_code kret;
+       krb5_principal  princ = NULL;
+       krb5_cc_cursor  cursor;
+       krb5_creds      cred;
+
+       K5BAIL(krb5_cc_get_principal(kctx, from, &princ));
+       K5BAIL(krb5_cc_initialize(kctx, to, princ));
+       K5BAIL(krb5_cc_start_seq_get(kctx, from, &cursor));
+       for (;;) {
+               kret = krb5_cc_next_cred(kctx, from, &cursor, &cred);
+               if (kret)
+                       break;
+               kret = krb5_cc_store_cred(kctx, to, &cred);
+               krb5_free_cred_contents(kctx, &cred);
+               if (kret)
+                       break;
+       }
+       krb5_cc_end_seq_get(kctx, from, &cursor);
+
+       if (kret == KRB5_CC_END)
+               kret = 0;
+       K5BAIL(kret);
+
+       if (princ)
+               krb5_free_principal(kctx, princ);
+
+       return kret;
+}
+
+static int
+write_token(gss_name_t service, int delegate, int negotiate, int memcache,
+           size_t count)
+{
+       krb5_error_code kret;
+       krb5_context    kctx = NULL;
+       krb5_ccache     def_cache = NULL;
+       krb5_ccache     mem_cache = NULL;
+       size_t          i;
+
+       if (memcache) {
+               K5BAIL(krb5_init_context(&kctx));
+               K5BAIL(krb5_cc_default(kctx, &def_cache));
+               K5BAIL(krb5_cc_resolve(kctx, "MEMORY:mem_cache", &mem_cache));
+               putenv("KRB5CCNAME=MEMORY:mem_cache");
+       }
+
+       for (i=0; i < count; i++) {
+               if (memcache)
+                       K5BAIL(copy_cache(kctx, def_cache, mem_cache));
+               kret = write_one_token(service, delegate, negotiate);
+
+               if (!nflag && i < count - 1)
+                       printf("\n");
+       }
+
+       if (kctx)
+               krb5_free_context(kctx);
+       if (def_cache)
+               krb5_cc_close(kctx, def_cache);
+       if (mem_cache)
+               krb5_cc_close(kctx, mem_cache);
+
+       return kret;
+}
+
+static char *
+read_buffer(FILE *fp)
+{
+       char     buf[65536];
+       char    *p;
+       char    *ret = NULL;
+       size_t   buflen;
+       size_t   retlen = 0;
+
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               if ((p = strchr(buf, '\n')) == NULL) {
+                       fprintf(stderr, "Long line, exiting.\n");
+                       exit(1);
+               }
+
+               *p = '\0';
+
+               buflen = strlen(buf);
+
+               if (buflen == 0)
+                       break;
+
+               ret = realloc(ret, retlen + buflen + 1);
+
+               memcpy(ret + retlen, buf, buflen);
+               ret[retlen + buflen] = '\0';
+       }
+
+       if (ferror(stdin)) {
+               perror("fgets");
+               exit(1);
+       }
+
+       return ret;
+}
+
+static int
+read_one_token(gss_name_t service, const char *ccname, int negotiate)
+{
+       gss_cred_id_t    cred = NULL;
+       gss_cred_id_t    deleg_creds = NULL;
+        gss_name_t       client;
+        gss_OID          mech_oid;
+        gss_ctx_id_t     ctx = GSS_C_NO_CONTEXT;
+        gss_buffer_desc  in = GSS_C_EMPTY_BUFFER;
+        gss_buffer_desc  out, dname;
+       krb5_context     kctx = NULL;
+       krb5_ccache      ccache = NULL;
+       krb5_error_code  kret;
+        OM_uint32        maj, min;
+       size_t           len;
+       char            *inbuf = NULL;
+       char            *tmp;
+       int              ret = 0;
+
+       if (service) {
+               maj = gss_acquire_cred(&min, service, 0, NULL, GSS_C_ACCEPT,
+                   &cred, NULL, NULL);
+               GBAIL("gss_acquire_cred", maj, min);
+       }
+
+       inbuf = read_buffer(stdin);
+       if (!inbuf)
+               /* Just a couple of \n's in a row or EOF, not an error. */
+               return 0;
+
+       tmp = inbuf;
+       if (negotiate) {
+               if (strncasecmp("Negotiate ", inbuf, 10)) {
+                       fprintf(stderr, "Token doesn't begin with "
+                           "\"Negotiate \"\n");
+                       return -1;
+               }
+
+               tmp += 10;
+       }
+
+       len = strlen(tmp);
+       in.value = malloc(len + 1);
+       if (!in.value) {
+               fprintf(stderr, "Out of memory.\n");
+               return -1;
+       }
+       ret = rk_base64_decode(tmp, in.value);
+       if (ret < 0) {
+               free(in.value);
+               if (errno == EOVERFLOW)
+                       fprintf(stderr, "Token is too big\n");
+               else
+                       fprintf(stderr, "Token encoding is not valid base64\n");
+               return -1;
+       }
+
+       out.length = 0;
+       out.value  = 0;
+        maj = gss_accept_sec_context(&min, &ctx, cred, &in,
+           GSS_C_NO_CHANNEL_BINDINGS, &client, &mech_oid, &out,
+           NULL, NULL, &deleg_creds);
+
+       GBAIL("gss_accept_sec_context", maj, min);
+
+       /*
+        * XXXrcd: not bothering to clean up because we're about to exit.
+        *         Probably should fix this in case the code is used as
+        *         an example by someone.
+        */
+
+       maj = gss_display_name(&min, client, &dname, NULL);
+       GBAIL("gss_display_name", maj, min);
+
+       if (!nflag)
+               printf("Authenticated: %.*s\n", (int)dname.length,
+                   (char *)dname.value);
+
+       if (ccname) {
+#ifdef HAVE_GSS_STORE_CRED_INTO
+               gss_key_value_set_desc          store;
+               gss_key_value_element_desc      elem;
+               int                             overwrite_cred = 1;
+               int                             default_cred = 0;
+
+               elem.key = "ccache";
+               elem.value = ccname;
+               store.count = 1;
+               store.elements = &elem;
+
+               maj = gss_store_cred_into(&min, deleg_creds, GSS_C_INITIATE,
+                   GSS_C_NO_OID, overwrite_cred, default_cred, &store, NULL,
+                   NULL);
+               GBAIL("gss_store_cred_into", maj, min);
+#else
+               K5BAIL(krb5_init_context(&kctx));
+               K5BAIL(krb5_cc_resolve(kctx, ccname, &ccache));
+
+               maj = gss_krb5_copy_ccache(&min, deleg_creds, ccache);
+               GBAIL("gss_krb5_copy_ccache", maj, min);
+#endif
+       }
+
+bail:
+       if (kctx)
+               krb5_free_context(kctx);
+       if (ccache)
+               krb5_cc_close(kctx, ccache);
+       if (cred)
+               gss_release_cred(&min, &cred);
+       if (deleg_creds)
+               gss_release_cred(&min, &deleg_creds);
+
+       free(in.value);
+       free(inbuf);
+
+       return ret;
+}
+
+static int
+read_token(gss_name_t service, const char *ccname, int negotiate, size_t count)
+{
+       size_t  i;
+       int     ret;
+
+       for (i=0; i < count; i++) {
+               ret = read_one_token(service, ccname, negotiate);
+       }
+
+       return ret;
+}
+
+static gss_name_t
+import_service(char *service)
+{
+       gss_buffer_desc name;
+       gss_name_t      svc = NULL;
+       OM_uint32       maj;
+       OM_uint32       min;
+       int             ret = 0;
+
+       name.length = strlen(service);
+       name.value  = service;
+
+       maj = gss_import_name(&min, &name, GSS_C_NT_HOSTBASED_SERVICE, &svc);
+
+       GBAIL("gss_import_name", maj, min);
+
+bail:
+       if (ret)
+               exit(1);
+       return svc;
+}
+
+static void
+usage(int ecode)
+{
+       FILE *f = ecode == 0 ? stdout : stderr;
+       fprintf(f, "Usage: gss-token [-DNn] [-c count] service@host\n");
+       fprintf(f, "       gss-token -r [-Nln] [-C ccache] [-c count] "
+           "[service@host]\n");
+       exit(ecode);
+}
+
+int
+main(int argc, char **argv)
+{
+       OM_uint32        min;
+       gss_name_t       service = NULL;
+       size_t           count = 1;
+       int              Dflag = 0;
+       int              Mflag = 0;
+       int              Nflag = 0;
+       int              hflag = 0;
+       int              lflag = 0;
+       int              rflag = 0;
+       int              version_flag = 0;
+       int              ret = 0;
+       int              optidx = 0;
+       char            *ccname = NULL;
+       struct getargs   args[] = {
+           { "help", 'h', arg_flag, &hflag, NULL, NULL },
+           { "version", 0, arg_flag, &version_flag, NULL, NULL },
+           { NULL, 'C', arg_string, &ccname, NULL, NULL },
+           { NULL, 'D', arg_flag, &Dflag, NULL, NULL },
+           { NULL, 'M', arg_flag, &Mflag, NULL, NULL },
+           { NULL, 'N', arg_flag, &Nflag, NULL, NULL },
+           { NULL, 'r', arg_flag, &rflag, NULL, NULL },
+           { NULL, 'l', arg_flag, &lflag, NULL, NULL },
+       };
+
+       setprogname(argv[0]);
+       if (argc == 1 || 
+           getarg(args, sizeof(args)/sizeof(args[0]), argc, argv, &optidx))
+           usage(1);
+       if (hflag)
+           usage(0);
+       if (version_flag) {
+           print_version(NULL);
+           return 0;
+       }
+
+       argc -= optidx;
+       argv += optidx;
+
+       if (argc > 0)
+               service = import_service(*argv);
+
+       if (!rflag) {
+               if (!argc) {
+                       fprintf(stderr, "Without -r, hostbased_service must "
+                           "be provided.\n");
+                       usage(1);
+               }
+               if (ccname) {
+                       fprintf(stderr, "Specifying a target ccache doesn't "
+                           "make sense without -r.\n");
+                       usage(1);
+               }
+               ret = write_token(service, Dflag, Nflag, Mflag, count);
+               goto done;
+       }
+
+       if (Dflag) {
+               fprintf(stderr, "Delegating credentials (-D) doesn't make "
+                   "sense when reading tokens (-r).\n");
+               usage(1);
+       }
+
+       do {
+               ret = read_token(service, ccname, Nflag, count);
+       } while (lflag && !ret && !feof(stdin));
+
+done:
+       if (service)
+               gss_release_name(&min, &service);
+
+       return ret;
+}