diff options
author | Greg Hudson <ghudson@mit.edu> | 2012-08-20 13:36:43 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2012-09-11 01:15:43 -0400 |
commit | 42c237dbfdb4316eb2ebf20c4041c48219afd6f5 (patch) | |
tree | 6491f174fac08d7e5b0f82d520ffc795c68a1579 /src | |
parent | abc6b8ce993c4c8393228ab5a01f26e7c5b251b2 (diff) | |
download | krb5-42c237dbfdb4316eb2ebf20c4041c48219afd6f5.tar.gz krb5-42c237dbfdb4316eb2ebf20c4041c48219afd6f5.tar.xz krb5-42c237dbfdb4316eb2ebf20c4041c48219afd6f5.zip |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/gssapi/krb5/Makefile.in | 6 | ||||
-rw-r--r-- | src/lib/gssapi/krb5/export_cred.c | 444 | ||||
-rw-r--r-- | src/lib/gssapi/krb5/gssapiP_krb5.h | 12 | ||||
-rw-r--r-- | src/lib/gssapi/krb5/gssapi_krb5.c | 2 | ||||
-rw-r--r-- | 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 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; +} |