summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2012-08-20 13:36:43 -0400
committerGreg Hudson <ghudson@mit.edu>2012-09-11 01:15:43 -0400
commit42c237dbfdb4316eb2ebf20c4041c48219afd6f5 (patch)
tree6491f174fac08d7e5b0f82d520ffc795c68a1579
parentabc6b8ce993c4c8393228ab5a01f26e7c5b251b2 (diff)
Add krb5 support for GSS cred export and import
Using the new internal JSON support to implement serialization and unserialization of krb5 GSS credentials. ticket: 7354
-rw-r--r--src/lib/gssapi/krb5/Makefile.in6
-rw-r--r--src/lib/gssapi/krb5/export_cred.c444
-rw-r--r--src/lib/gssapi/krb5/gssapiP_krb5.h12
-rw-r--r--src/lib/gssapi/krb5/gssapi_krb5.c2
-rw-r--r--src/lib/gssapi/krb5/import_cred.c653
5 files changed, 1117 insertions, 0 deletions
diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in
index ddd9ef999..3954e87f5 100644
--- a/src/lib/gssapi/krb5/Makefile.in
+++ b/src/lib/gssapi/krb5/Makefile.in
@@ -48,11 +48,13 @@ SRCS = \
$(srcdir)/disp_name.c \
$(srcdir)/disp_status.c \
$(srcdir)/duplicate_name.c \
+ $(srcdir)/export_cred.c \
$(srcdir)/export_name.c \
$(srcdir)/export_sec_context.c \
$(srcdir)/get_tkt_flags.c \
$(srcdir)/gssapi_krb5.c \
$(srcdir)/iakerb.c \
+ $(srcdir)/import_cred.c \
$(srcdir)/import_name.c \
$(srcdir)/import_sec_context.c \
$(srcdir)/indicate_mechs.c \
@@ -99,11 +101,13 @@ OBJS = \
$(OUTPRE)disp_name.$(OBJEXT) \
$(OUTPRE)disp_status.$(OBJEXT) \
$(OUTPRE)duplicate_name.$(OBJEXT) \
+ $(OUTPRE)export_cred.$(OBJEXT) \
$(OUTPRE)export_name.$(OBJEXT) \
$(OUTPRE)export_sec_context.$(OBJEXT) \
$(OUTPRE)get_tkt_flags.$(OBJEXT) \
$(OUTPRE)gssapi_krb5.$(OBJEXT) \
$(OUTPRE)iakerb.$(OBJEXT) \
+ $(OUTPRE)import_cred.$(OBJEXT) \
$(OUTPRE)import_name.$(OBJEXT) \
$(OUTPRE)import_sec_context.$(OBJEXT) \
$(OUTPRE)indicate_mechs.$(OBJEXT) \
@@ -153,11 +157,13 @@ STLIBOBJS = \
disp_name.o \
disp_status.o \
duplicate_name.o \
+ export_cred.o \
export_name.o \
export_sec_context.o \
get_tkt_flags.o \
gssapi_krb5.o \
iakerb.o \
+ import_cred.o \
import_name.o \
import_sec_context.o \
indicate_mechs.o \
diff --git a/src/lib/gssapi/krb5/export_cred.c b/src/lib/gssapi/krb5/export_cred.c
new file mode 100644
index 000000000..162547966
--- /dev/null
+++ b/src/lib/gssapi/krb5/export_cred.c
@@ -0,0 +1,444 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/gssapi/krb5/export_cred.c - krb5 export_cred implementation */
+/*
+ * Copyright (C) 2012 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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
+ * COPYRIGHT HOLDER OR CONTRIBUTORS 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 "k5-int.h"
+#include "k5-json.h"
+#include "gssapiP_krb5.h"
+
+/* Add v to array and then release it. Return -1 if v is NULL. */
+static int
+add(k5_json_array array, k5_json_value v)
+{
+ if (v == NULL || k5_json_array_add(array, v))
+ return -1;
+ k5_json_release(v);
+ return 0;
+}
+
+/* Return a JSON null or string value representing str. */
+static k5_json_value
+json_optional_string(const char *str)
+{
+ return (str == NULL) ? (k5_json_value)k5_json_null_create() :
+ (k5_json_value)k5_json_string_create(str);
+}
+
+/* Return a JSON null or array value representing princ. */
+static k5_json_value
+json_principal(krb5_context context, krb5_principal princ)
+{
+ char *princname;
+ k5_json_string str;
+
+ if (princ == NULL)
+ return k5_json_null_create();
+ if (krb5_unparse_name(context, princ, &princname))
+ return NULL;
+ str = k5_json_string_create(princname);
+ krb5_free_unparsed_name(context, princname);
+ return str;
+}
+
+/* Return a json null or array value representing etypes. */
+static k5_json_value
+json_etypes(krb5_enctype *etypes)
+{
+ k5_json_array array;
+
+ if (etypes == NULL)
+ return k5_json_null_create();
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ for (; *etypes != 0; etypes++) {
+ if (add(array, k5_json_number_create(*etypes)))
+ goto oom;
+ }
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON null or array value representing name. */
+static k5_json_value
+json_kgname(krb5_context context, krb5_gss_name_t name)
+{
+ k5_json_array array;
+
+ if (name == NULL)
+ return k5_json_null_create();
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, json_principal(context, name->princ)))
+ goto oom;
+ if (add(array, json_optional_string(name->service)))
+ goto oom;
+ if (add(array, json_optional_string(name->host)))
+ goto oom;
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON null or string value representing keytab. */
+static k5_json_value
+json_keytab(krb5_context context, krb5_keytab keytab)
+{
+ char name[1024];
+
+ if (keytab == NULL)
+ return k5_json_null_create();
+ if (krb5_kt_get_name(context, keytab, name, sizeof(name)))
+ return NULL;
+ return k5_json_string_create(name);
+}
+
+/* Return a JSON null or string value representing rcache. */
+static k5_json_value
+json_rcache(krb5_context context, krb5_rcache rcache)
+{
+ char *name;
+ k5_json_string str;
+
+ if (rcache == NULL)
+ return k5_json_null_create();
+ if (asprintf(&name, "%s:%s", krb5_rc_get_type(context, rcache),
+ krb5_rc_get_name(context, rcache)) < 0)
+ return NULL;
+ str = k5_json_string_create(name);
+ free(name);
+ return str;
+}
+
+/* Return a JSON array value representing keyblock. */
+static k5_json_value
+json_keyblock(krb5_keyblock *keyblock)
+{
+ k5_json_array array;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, k5_json_number_create(keyblock->enctype)))
+ goto oom;
+ if (add(array, k5_json_string_create_base64(keyblock->contents,
+ keyblock->length)))
+ goto oom;
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON array value representing addr. */
+static k5_json_value
+json_address(krb5_address *addr)
+{
+ k5_json_array array;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, k5_json_number_create(addr->addrtype)))
+ goto oom;
+ if (add(array, k5_json_string_create_base64(addr->contents, addr->length)))
+ goto oom;
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON null or array value representing addrs. */
+static k5_json_value
+json_addresses(krb5_address **addrs)
+{
+ k5_json_array array;
+
+ if (addrs == NULL)
+ return k5_json_null_create();
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ for (; *addrs != NULL; addrs++) {
+ if (add(array, json_address(*addrs))) {
+ k5_json_release(array);
+ return NULL;
+ }
+ }
+ return array;
+}
+
+/* Return a JSON array value representing ad. */
+static k5_json_value
+json_authdata_element(krb5_authdata *ad)
+{
+ k5_json_array array;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, k5_json_number_create(ad->ad_type)))
+ goto oom;
+ if (add(array, k5_json_string_create_base64(ad->contents, ad->length)))
+ goto oom;
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON null or array value representing authdata. */
+static k5_json_value
+json_authdata(krb5_authdata **authdata)
+{
+ k5_json_array array;
+
+ if (authdata == NULL)
+ return k5_json_null_create();
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ for (; *authdata != NULL; authdata++) {
+ if (add(array, json_authdata_element(*authdata))) {
+ k5_json_release(array);
+ return NULL;
+ }
+ }
+ return array;
+}
+
+/* Return a JSON array value representing creds. */
+static k5_json_value
+json_creds(krb5_context context, krb5_creds *creds)
+{
+ k5_json_array array;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, json_principal(context, creds->client)))
+ goto eom;
+ if (add(array, json_principal(context, creds->server)))
+ goto eom;
+ if (add(array, json_keyblock(&creds->keyblock)))
+ goto eom;
+ if (add(array, k5_json_number_create(creds->times.authtime)))
+ goto eom;
+ if (add(array, k5_json_number_create(creds->times.starttime)))
+ goto eom;
+ if (add(array, k5_json_number_create(creds->times.endtime)))
+ goto eom;
+ if (add(array, k5_json_number_create(creds->times.renew_till)))
+ goto eom;
+ if (add(array, k5_json_bool_create(creds->is_skey)))
+ goto eom;
+ if (add(array, k5_json_number_create(creds->ticket_flags)))
+ goto eom;
+ if (add(array, json_addresses(creds->addresses)))
+ goto eom;
+ if (add(array, k5_json_string_create_base64(creds->ticket.data,
+ creds->ticket.length)))
+ goto eom;
+ if (add(array, k5_json_string_create_base64(creds->second_ticket.data,
+ creds->second_ticket.length)))
+ goto eom;
+ if (add(array, json_authdata(creds->authdata)))
+ goto eom;
+ return array;
+eom:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON array value representing the contents of ccache. */
+static k5_json_value
+json_ccache_contents(krb5_context context, krb5_ccache ccache)
+{
+ krb5_error_code ret;
+ krb5_principal princ;
+ krb5_cc_cursor cursor;
+ krb5_creds creds;
+ k5_json_array array;
+ int st;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+
+ /* Put the principal in the first array entry. */
+ if (krb5_cc_get_principal(context, ccache, &princ))
+ goto err;
+ st = add(array, json_principal(context, princ));
+ krb5_free_principal(context, princ);
+ if (st)
+ goto err;
+
+ /* Put credentials in the remaining array entries. */
+ if (krb5_cc_start_seq_get(context, ccache, &cursor))
+ goto err;
+ while ((ret = krb5_cc_next_cred(context, ccache, &cursor, &creds)) == 0) {
+ if (add(array, json_creds(context, &creds))) {
+ krb5_free_cred_contents(context, &creds);
+ break;
+ }
+ krb5_free_cred_contents(context, &creds);
+ }
+ krb5_cc_end_seq_get(context, ccache, &cursor);
+ if (ret != KRB5_CC_END)
+ goto err;
+ return array;
+
+err:
+ k5_json_release(array);
+ return NULL;
+}
+
+/* Return a JSON null, string, or array value representing ccache. */
+static k5_json_value
+json_ccache(krb5_context context, krb5_ccache ccache)
+{
+ char *name;
+ k5_json_string str;
+
+ if (ccache == NULL)
+ return k5_json_null_create();
+ if (strcmp(krb5_cc_get_type(context, ccache), "MEMORY") == 0) {
+ return json_ccache_contents(context, ccache);
+ } else {
+ if (krb5_cc_get_full_name(context, ccache, &name))
+ return NULL;
+ str = k5_json_string_create(name);
+ free(name);
+ return str;
+ }
+}
+
+/* Return a JSON array value representing cred. */
+static k5_json_value
+json_kgcred(krb5_context context, krb5_gss_cred_id_t cred)
+{
+ k5_json_array array;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ return NULL;
+ if (add(array, k5_json_number_create(cred->usage)))
+ goto oom;
+ if (add(array, json_kgname(context, cred->name)))
+ goto oom;
+ if (add(array, json_principal(context, cred->impersonator)))
+ goto oom;
+ if (add(array, k5_json_bool_create(cred->default_identity)))
+ goto oom;
+ if (add(array, k5_json_bool_create(cred->iakerb_mech)))
+ goto oom;
+ /* Don't marshal cred->destroy_ccache. */
+ if (add(array, json_keytab(context, cred->keytab)))
+ goto oom;
+ if (add(array, json_rcache(context, cred->rcache)))
+ goto oom;
+ if (add(array, json_ccache(context, cred->ccache)))
+ goto oom;
+ if (add(array, json_keytab(context, cred->client_keytab)))
+ goto oom;
+ if (add(array, k5_json_bool_create(cred->have_tgt)))
+ goto oom;
+ if (add(array, k5_json_number_create(cred->expire)))
+ goto oom;
+ if (add(array, k5_json_number_create(cred->refresh_time)))
+ goto oom;
+ if (add(array, json_etypes(cred->req_enctypes)))
+ goto oom;
+ if (add(array, json_optional_string(cred->password)))
+ goto oom;
+ return array;
+oom:
+ k5_json_release(array);
+ return NULL;
+}
+
+OM_uint32 KRB5_CALLCONV
+krb5_gss_export_cred(OM_uint32 *minor_status, gss_cred_id_t cred_handle,
+ gss_buffer_t token)
+{
+ OM_uint32 status = GSS_S_COMPLETE;
+ krb5_context context;
+ krb5_error_code ret;
+ krb5_gss_cred_id_t cred;
+ k5_json_array array = NULL;
+ char *str = NULL;
+ krb5_data d;
+
+ ret = krb5_gss_init_context(&context);
+ if (ret) {
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ /* Validate and lock cred_handle. */
+ status = krb5_gss_validate_cred_1(minor_status, cred_handle, context);
+ if (status != GSS_S_COMPLETE)
+ return status;
+ cred = (krb5_gss_cred_id_t)cred_handle;
+
+ array = k5_json_array_create();
+ if (array == NULL)
+ goto oom;
+ if (add(array, k5_json_string_create(CRED_EXPORT_MAGIC)))
+ goto oom;
+ if (add(array, json_kgcred(context, cred)))
+ goto oom;
+
+ str = k5_json_encode(array);
+ if (str == NULL)
+ goto oom;
+ d = string2data(str);
+ if (data_to_gss(&d, token))
+ goto oom;
+ str = NULL;
+
+cleanup:
+ free(str);
+ k5_mutex_unlock(&cred->lock);
+ k5_json_release(array);
+ krb5_free_context(context);
+ return status;
+
+oom:
+ *minor_status = ENOMEM;
+ status = GSS_S_FAILURE;
+ goto cleanup;
+}
diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h
index 8785ec981..8215b10f0 100644
--- a/src/lib/gssapi/krb5/gssapiP_krb5.h
+++ b/src/lib/gssapi/krb5/gssapiP_krb5.h
@@ -1258,4 +1258,16 @@ krb5_gss_store_cred_into(
gss_OID_set *, /* elements_stored */
gss_cred_usage_t *); /* cred_usage_stored */
+OM_uint32 KRB5_CALLCONV
+krb5_gss_export_cred(OM_uint32 *minor_status, gss_cred_id_t cred_handle,
+ gss_buffer_t token);
+
+OM_uint32 KRB5_CALLCONV
+krb5_gss_import_cred(OM_uint32 *minor_status, gss_buffer_t token,
+ gss_cred_id_t *cred_handle);
+
+/* Magic string to identify exported krb5 GSS credentials. Increment this if
+ * the format changes. */
+#define CRED_EXPORT_MAGIC "K5C1"
+
#endif /* _GSSAPIP_KRB5_H_ */
diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c
index aad24fe2b..bff9f78d2 100644
--- a/src/lib/gssapi/krb5/gssapi_krb5.c
+++ b/src/lib/gssapi/krb5/gssapi_krb5.c
@@ -900,6 +900,8 @@ static struct gss_config krb5_mechanism = {
krb5_gss_acquire_cred_from,
krb5_gss_store_cred_into,
krb5_gss_acquire_cred_with_password,
+ krb5_gss_export_cred,
+ krb5_gss_import_cred,
};
#ifdef _GSS_STATIC_LINK
diff --git a/src/lib/gssapi/krb5/import_cred.c b/src/lib/gssapi/krb5/import_cred.c
new file mode 100644
index 000000000..4de6fa65e
--- /dev/null
+++ b/src/lib/gssapi/krb5/import_cred.c
@@ -0,0 +1,653 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/gssapi/krb5/import_cred.c - krb5 import_cred implementation */
+/*
+ * Copyright (C) 2012 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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
+ * COPYRIGHT HOLDER OR CONTRIBUTORS 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 "k5-int.h"
+#include "k5-json.h"
+#include "gssapiP_krb5.h"
+
+/* Return the idx element of array if it is of type tid; otherwise return
+ * NULL. The caller is responsible for checking the array length. */
+static k5_json_value
+check_element(k5_json_array array, size_t idx, k5_json_tid tid)
+{
+ k5_json_value v;
+
+ v = k5_json_array_get(array, idx);
+ return (k5_json_get_tid(v) == tid) ? v : NULL;
+}
+
+/* All of the json_to_x functions return 0 on success, -1 on failure (either
+ * from running out of memory or from defective input). */
+
+/* Convert a JSON value to a C string or to NULL. */
+static int
+json_to_optional_string(k5_json_value v, char **string_out)
+{
+ *string_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_STRING)
+ return -1;
+ *string_out = strdup(k5_json_string_utf8(v));
+ return (*string_out == NULL) ? -1 : 0;
+}
+
+/* Convert a JSON value to a principal or to NULL. */
+static int
+json_to_principal(krb5_context context, k5_json_value v,
+ krb5_principal *princ_out)
+{
+ *princ_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_STRING)
+ return -1;
+ if (krb5_parse_name(context, k5_json_string_utf8(v), princ_out))
+ return -1;
+ return 0;
+}
+
+/* Convert a JSON value to a zero-terminated enctypes list or to NULL. */
+static int
+json_to_etypes(k5_json_value v, krb5_enctype **etypes_out)
+{
+ krb5_enctype *etypes = NULL;
+ k5_json_array array;
+ k5_json_number n;
+ size_t len, i;
+
+ *etypes_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ len = k5_json_array_length(array);
+ etypes = calloc(len + 1, sizeof(*etypes));
+ for (i = 0; i < len; i++) {
+ n = check_element(array, i, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ etypes[i] = k5_json_number_value(n);
+ }
+ *etypes_out = etypes;
+ return 0;
+
+invalid:
+ free(etypes);
+ return -1;
+}
+
+/* Convert a JSON value to a krb5 GSS name or to NULL. */
+static int
+json_to_kgname(krb5_context context, k5_json_value v,
+ krb5_gss_name_t *name_out)
+{
+ k5_json_array array;
+ krb5_gss_name_t name = NULL;
+
+ *name_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ if (k5_json_array_length(array) != 3)
+ return -1;
+ name = calloc(1, sizeof(*name));
+ if (name == NULL)
+ return -1;
+ if (k5_mutex_init(&name->lock)) {
+ free(name);
+ return -1;
+ }
+
+ if (json_to_principal(context, k5_json_array_get(array, 0), &name->princ))
+ goto invalid;
+ if (json_to_optional_string(k5_json_array_get(array, 1), &name->service))
+ goto invalid;
+ if (json_to_optional_string(k5_json_array_get(array, 2), &name->host))
+ goto invalid;
+
+ *name_out = name;
+ return 0;
+
+invalid:
+ kg_release_name(context, &name);
+ return -1;
+}
+
+/* Convert a JSON value to a keytab handle or to NULL. */
+static int
+json_to_keytab(krb5_context context, k5_json_value v, krb5_keytab *keytab_out)
+{
+ *keytab_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_STRING)
+ return -1;
+ if (krb5_kt_resolve(context, k5_json_string_utf8(v), keytab_out))
+ return -1;
+ return 0;
+}
+
+/* Convert a JSON value to an rcache handle or to NULL. */
+static int
+json_to_rcache(krb5_context context, k5_json_value v, krb5_rcache *rcache_out)
+{
+ krb5_rcache rcache;
+
+ *rcache_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_STRING)
+ return -1;
+ if (krb5_rc_resolve_full(context, &rcache, (char *)k5_json_string_utf8(v)))
+ return -1;
+ if (krb5_rc_recover_or_initialize(context, rcache, context->clockskew)) {
+ krb5_rc_close(context, rcache);
+ return -1;
+ }
+ *rcache_out = rcache;
+ return 0;
+}
+
+/* Convert a JSON value to a keyblock, filling in keyblock. */
+static int
+json_to_keyblock(k5_json_value v, krb5_keyblock *keyblock)
+{
+ k5_json_array array;
+ k5_json_number n;
+ k5_json_string s;
+ size_t len;
+
+ memset(keyblock, 0, sizeof(*keyblock));
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ if (k5_json_array_length(array) != 2)
+ return -1;
+
+ n = check_element(array, 0, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ return -1;
+ keyblock->enctype = k5_json_number_value(n);
+
+ s = check_element(array, 1, K5_JSON_TID_STRING);
+ if (s == NULL)
+ return -1;
+ keyblock->contents = k5_json_string_unbase64(s, &len);
+ if (keyblock->contents == NULL)
+ return -1;
+ keyblock->length = len;
+ keyblock->magic = KV5M_KEYBLOCK;
+ return 0;
+}
+
+/* Convert a JSON value to a krb5 address. */
+static int
+json_to_address(k5_json_value v, krb5_address **addr_out)
+{
+ k5_json_array array;
+ krb5_address *addr = NULL;
+ k5_json_number n;
+ k5_json_string s;
+ size_t len;
+
+ *addr_out = NULL;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ if (k5_json_array_length(array) != 2)
+ return -1;
+
+ n = check_element(array, 0, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ return -1;
+ s = check_element(array, 1, K5_JSON_TID_STRING);
+ if (s == NULL)
+ return -1;
+
+ addr = malloc(sizeof(*addr));
+ if (addr == NULL)
+ return -1;
+ addr->addrtype = k5_json_number_value(n);
+ addr->contents = k5_json_string_unbase64(s, &len);
+ if (addr->contents == NULL) {
+ free(addr);
+ return -1;
+ }
+ addr->length = len;
+ addr->magic = KV5M_ADDRESS;
+ *addr_out = addr;
+ return 0;
+}
+
+/* Convert a JSON value to a null-terminated list of krb5 addresses or to
+ * NULL. */
+static int
+json_to_addresses(krb5_context context, k5_json_value v,
+ krb5_address ***addresses_out)
+{
+ k5_json_array array;
+ krb5_address **addrs = NULL;
+ size_t len, i;
+
+ *addresses_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ len = k5_json_array_length(array);
+ addrs = calloc(len + 1, sizeof(*addrs));
+ for (i = 0; i < len; i++) {
+ if (json_to_address(k5_json_array_get(array, i), &addrs[i]))
+ goto invalid;
+ }
+ addrs[i] = NULL;
+ *addresses_out = addrs;
+ return 0;
+
+invalid:
+ krb5_free_addresses(context, addrs);
+ return -1;
+}
+
+/* Convert a JSON value to an authdata element. */
+static int
+json_to_authdata_element(k5_json_value v, krb5_authdata **ad_out)
+{
+ k5_json_array array;
+ krb5_authdata *ad = NULL;
+ k5_json_number n;
+ k5_json_string s;
+ size_t len;
+
+ *ad_out = NULL;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ if (k5_json_array_length(array) != 2)
+ return -1;
+
+ n = check_element(array, 0, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ return -1;
+ s = check_element(array, 1, K5_JSON_TID_STRING);
+ if (s == NULL)
+ return -1;
+
+ ad = malloc(sizeof(*ad));
+ if (ad == NULL)
+ return -1;
+ ad->ad_type = k5_json_number_value(n);
+ ad->contents = k5_json_string_unbase64(s, &len);
+ if (ad->contents == NULL) {
+ free(ad);
+ return -1;
+ }
+ ad->length = len;
+ ad->magic = KV5M_AUTHDATA;
+ *ad_out = ad;
+ return 0;
+}
+
+/* Convert a JSON value to a null-terminated authdata list or to NULL. */
+static int
+json_to_authdata(krb5_context context, k5_json_value v,
+ krb5_authdata ***authdata_out)
+{
+ k5_json_array array;
+ krb5_authdata **authdata = NULL;
+ size_t len, i;
+
+ *authdata_out = NULL;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ len = k5_json_array_length(array);
+ authdata = calloc(len + 1, sizeof(*authdata));
+ for (i = 0; i < len; i++) {
+ if (json_to_authdata_element(k5_json_array_get(array, i),
+ &authdata[i]))
+ goto invalid;
+ }
+ authdata[i] = NULL;
+ *authdata_out = authdata;
+ return 0;
+
+invalid:
+ krb5_free_authdata(context, authdata);
+ return -1;
+}
+
+/* Convert a JSON value to a krb5 credential structure, filling in creds. */
+static int
+json_to_creds(krb5_context context, k5_json_value v, krb5_creds *creds)
+{
+ k5_json_array array;
+ k5_json_number n;
+ k5_json_bool b;
+ k5_json_string s;
+ size_t len;
+
+ memset(creds, 0, sizeof(*creds));
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ if (k5_json_array_length(array) != 13)
+ return -1;
+
+ if (json_to_principal(context, k5_json_array_get(array, 0),
+ &creds->client))
+ goto invalid;
+
+ if (json_to_principal(context, k5_json_array_get(array, 1),
+ &creds->server))
+ goto invalid;
+
+ if (json_to_keyblock(k5_json_array_get(array, 2), &creds->keyblock))
+ goto invalid;
+
+ n = check_element(array, 3, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ creds->times.authtime = k5_json_number_value(n);
+
+ n = check_element(array, 4, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ creds->times.starttime = k5_json_number_value(n);
+
+ n = check_element(array, 5, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ creds->times.endtime = k5_json_number_value(n);
+
+ n = check_element(array, 6, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ creds->times.renew_till = k5_json_number_value(n);
+
+ b = check_element(array, 7, K5_JSON_TID_BOOL);
+ if (b == NULL)
+ goto invalid;
+ creds->is_skey = k5_json_bool_value(b);
+
+ n = check_element(array, 8, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ creds->ticket_flags = k5_json_number_value(n);
+
+ if (json_to_addresses(context, k5_json_array_get(array, 9),
+ &creds->addresses))
+ goto invalid;
+
+ s = check_element(array, 10, K5_JSON_TID_STRING);
+ if (s == NULL)
+ goto invalid;
+ creds->ticket.data = k5_json_string_unbase64(s, &len);
+ if (creds->ticket.data == NULL)
+ goto invalid;
+ creds->ticket.length = len;
+
+ s = check_element(array, 11, K5_JSON_TID_STRING);
+ if (s == NULL)
+ goto invalid;
+ creds->second_ticket.data = k5_json_string_unbase64(s, &len);
+ if (creds->second_ticket.data == NULL)
+ goto invalid;
+ creds->second_ticket.length = len;
+
+ if (json_to_authdata(context, k5_json_array_get(array, 12),
+ &creds->authdata))
+ goto invalid;
+
+ creds->magic = KV5M_CREDS;
+ return 0;
+
+invalid:
+ krb5_free_cred_contents(context, creds);
+ memset(creds, 0, sizeof(*creds));
+ return -1;
+}
+
+/* Convert a JSON value to a ccache handle or to NULL. Set *new_out to true if
+ * the ccache handle is a newly created memory ccache, false otherwise. */
+static int
+json_to_ccache(krb5_context context, k5_json_value v, krb5_ccache *ccache_out,
+ krb5_boolean *new_out)
+{
+ krb5_error_code ret;
+ krb5_ccache ccache = NULL;
+ krb5_principal princ;
+ krb5_creds creds;
+ k5_json_array array;
+ size_t i, len;
+
+ *ccache_out = NULL;
+ *new_out = FALSE;
+ if (k5_json_get_tid(v) == K5_JSON_TID_NULL)
+ return 0;
+ if (k5_json_get_tid(v) == K5_JSON_TID_STRING) {
+ /* We got a reference to an external ccache; just resolve it. */
+ return krb5_cc_resolve(context, k5_json_string_utf8(v), ccache_out) ?
+ -1 : 0;
+ }
+
+ /* We got the contents of a memory ccache. */
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ return -1;
+ array = v;
+ len = k5_json_array_length(array);
+ if (len < 1)
+ return -1;
+
+ /* Initialize a new memory ccache using the principal in the first array
+ * entry.*/
+ if (krb5_cc_new_unique(context, "MEMORY", NULL, &ccache))
+ return -1;
+ if (json_to_principal(context, k5_json_array_get(array, 0), &princ))
+ goto invalid;
+ ret = krb5_cc_initialize(context, ccache, princ);
+ krb5_free_principal(context, princ);
+ if (ret)
+ goto invalid;
+
+ /* Add remaining array entries to the ccache as credentials. */
+ for (i = 1; i < len; i++) {
+ if (json_to_creds(context, k5_json_array_get(array, 1), &creds))
+ goto invalid;
+ ret = krb5_cc_store_cred(context, ccache, &creds);
+ krb5_free_cred_contents(context, &creds);
+ if (ret)
+ goto invalid;
+ }
+
+ *ccache_out = ccache;
+ *new_out = TRUE;
+ return 0;
+
+invalid:
+ (void)krb5_cc_destroy(context, ccache);
+ return -1;
+}
+
+/* Convert a JSON array value to a krb5 GSS credential. */
+static int
+json_to_kgcred(krb5_context context, k5_json_array array,
+ krb5_gss_cred_id_t *cred_out)
+{
+ krb5_gss_cred_id_t cred;
+ k5_json_number n;
+ k5_json_bool b;
+ krb5_boolean is_new;
+ OM_uint32 tmp;
+
+ *cred_out = NULL;
+ if (k5_json_array_length(array) != 14)
+ return -1;
+
+ cred = calloc(1, sizeof(*cred));
+ if (cred == NULL)
+ return -1;
+ if (k5_mutex_init(&cred->lock)) {
+ free(cred);
+ return -1;
+ }
+
+ n = check_element(array, 0, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ cred->usage = k5_json_number_value(n);
+
+ if (json_to_kgname(context, k5_json_array_get(array, 1), &cred->name))
+ goto invalid;
+
+ if (json_to_principal(context, k5_json_array_get(array, 2),
+ &cred->impersonator))
+ goto invalid;
+
+ b = check_element(array, 3, K5_JSON_TID_BOOL);
+ if (b == NULL)
+ goto invalid;
+ cred->default_identity = k5_json_bool_value(b);
+
+ b = check_element(array, 4, K5_JSON_TID_BOOL);
+ if (b == NULL)
+ goto invalid;
+ cred->iakerb_mech = k5_json_bool_value(b);
+
+ if (json_to_keytab(context, k5_json_array_get(array, 5), &cred->keytab))
+ goto invalid;
+
+ if (json_to_rcache(context, k5_json_array_get(array, 6), &cred->rcache))
+ goto invalid;
+
+ if (json_to_ccache(context, k5_json_array_get(array, 7), &cred->ccache,
+ &is_new))
+ goto invalid;
+ cred->destroy_ccache = is_new;
+
+ if (json_to_keytab(context, k5_json_array_get(array, 8),
+ &cred->client_keytab))
+ goto invalid;
+
+ b = check_element(array, 9, K5_JSON_TID_BOOL);
+ if (b == NULL)
+ goto invalid;
+ cred->have_tgt = k5_json_bool_value(b);
+
+ n = check_element(array, 10, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ cred->expire = k5_json_number_value(n);
+
+ n = check_element(array, 11, K5_JSON_TID_NUMBER);
+ if (n == NULL)
+ goto invalid;
+ cred->refresh_time = k5_json_number_value(n);
+
+ if (json_to_etypes(k5_json_array_get(array, 12), &cred->req_enctypes))
+ goto invalid;
+
+ if (json_to_optional_string(k5_json_array_get(array, 13), &cred->password))
+ goto invalid;
+
+ *cred_out = cred;
+ return 0;
+
+invalid:
+ (void)krb5_gss_release_cred(&tmp, (gss_cred_id_t *)&cred);
+ return -1;
+}
+
+OM_uint32 KRB5_CALLCONV
+krb5_gss_import_cred(OM_uint32 *minor_status, gss_buffer_t token,
+ gss_cred_id_t *cred_handle)
+{
+ OM_uint32 status = GSS_S_COMPLETE;
+ krb5_context context;
+ krb5_error_code ret;
+ krb5_gss_cred_id_t cred;
+ k5_json_value v = NULL;
+ k5_json_array array;
+ k5_json_string str;
+ char *copy = NULL;
+
+ ret = krb5_gss_init_context(&context);
+ if (ret) {
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ /* Decode token. */
+ copy = malloc(token->length + 1);
+ if (copy == NULL) {
+ status = GSS_S_FAILURE;
+ *minor_status = ENOMEM;
+ goto cleanup;
+ }
+ memcpy(copy, token->value, token->length);
+ copy[token->length] = '\0';
+ v = k5_json_decode(copy);
+ if (v == NULL)
+ goto invalid;
+
+ /* Decode the CRED_EXPORT_MAGIC array wrapper. */
+ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY)
+ goto invalid;
+ array = v;
+ if (k5_json_array_length(array) != 2)
+ goto invalid;
+ str = check_element(array, 0, K5_JSON_TID_STRING);
+ if (str == NULL ||
+ strcmp(k5_json_string_utf8(str), CRED_EXPORT_MAGIC) != 0)
+ goto invalid;
+ if (json_to_kgcred(context, k5_json_array_get(array, 1), &cred))
+ goto invalid;
+
+ *cred_handle = (gss_cred_id_t)cred;
+
+cleanup:
+ free(copy);
+ k5_json_release(v);
+ krb5_free_context(context);
+ return status;
+
+invalid:
+ status = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+}