krb5 commit: Add krb5 support for GSS cred export and import

Greg Hudson ghudson at MIT.EDU
Tue Sep 11 01:19:03 EDT 2012


https://github.com/krb5/krb5/commit/42c237dbfdb4316eb2ebf20c4041c48219afd6f5
commit 42c237dbfdb4316eb2ebf20c4041c48219afd6f5
Author: Greg Hudson <ghudson at mit.edu>
Date:   Mon Aug 20 13:36:43 2012 -0400

    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

 src/lib/gssapi/krb5/Makefile.in    |    6 +
 src/lib/gssapi/krb5/export_cred.c  |  444 ++++++++++++++++++++++++
 src/lib/gssapi/krb5/gssapiP_krb5.h |   12 +
 src/lib/gssapi/krb5/gssapi_krb5.c  |    2 +
 src/lib/gssapi/krb5/import_cred.c  |  653 ++++++++++++++++++++++++++++++++++++
 5 files changed, 1117 insertions(+), 0 deletions(-)

diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in
index ddd9ef9..3954e87 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 0000000..1625479
--- /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 8785ec9..8215b10 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 aad24fe..bff9f78 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 0000000..4de6fa6
--- /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;
+}


More information about the cvs-krb5 mailing list