diff options
Diffstat (limited to 'third_party/heimdal/lib/gssapi/krb5/acquire_cred.c')
-rw-r--r-- | third_party/heimdal/lib/gssapi/krb5/acquire_cred.c | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c b/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c new file mode 100644 index 0000000..211dcaa --- /dev/null +++ b/third_party/heimdal/lib/gssapi/krb5/acquire_cred.c @@ -0,0 +1,686 @@ +/* + * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "gsskrb5_locl.h" + +/* + * Find an element in a cred store. Returns GSS_S_COMPLETE if the cred store + * is absent or well formed, irrespective of whether the element exists. The + * caller should check for *value != NULL before using; values are typically + * optional, hence this behavior. (The caller should validate the return + * value at least once though, to check it is well-formed.) + */ +OM_uint32 +__gsskrb5_cred_store_find(OM_uint32 *minor_status, + gss_const_key_value_set_t cred_store, + const char *key, + const char **value) +{ + size_t i; + + *value = NULL; + + if (cred_store == GSS_C_NO_CRED_STORE) + return GSS_S_COMPLETE; + else if (cred_store->count == 0) { + *minor_status = GSS_KRB5_S_G_BAD_USAGE; + return GSS_S_NO_CRED; + } + + for (i = 0; i < cred_store->count; i++) { + if (strcmp(key, cred_store->elements[i].key) == 0) { + if (*value) { + *value = NULL; + *minor_status = GSS_KRB5_S_G_BAD_USAGE; + return GSS_S_DUPLICATE_ELEMENT; + } + *value = cred_store->elements[i].value; + } + } + + return GSS_S_COMPLETE; +} + +OM_uint32 +__gsskrb5_ccache_lifetime(OM_uint32 *minor_status, + krb5_context context, + krb5_ccache id, + krb5_principal principal, + OM_uint32 *lifetime) +{ + krb5_error_code kret; + time_t left; + + kret = krb5_cc_get_lifetime(context, id, &left); + if (kret) { + *minor_status = kret; + return GSS_S_FAILURE; + } + + *lifetime = left; + + return GSS_S_COMPLETE; +} + + + + +static krb5_error_code +get_system_keytab(krb5_context context, + gss_const_key_value_set_t cred_store, + krb5_keytab *keytab) +{ + krb5_error_code kret; + const char *cs_ktname; + OM_uint32 tmp; + + __gsskrb5_cred_store_find(&tmp, cred_store, "keytab", &cs_ktname); + + HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex); + + if (cs_ktname) + kret = krb5_kt_resolve(context, cs_ktname, keytab); + else if (_gsskrb5_keytab != NULL) { + char *name = NULL; + + kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name); + if (kret == 0) { + kret = krb5_kt_resolve(context, name, keytab); + krb5_xfree(name); + } + } else + kret = krb5_kt_default(context, keytab); + + HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex); + + return (kret); +} + +static krb5_error_code +get_client_keytab(krb5_context context, + gss_const_key_value_set_t cred_store, + krb5_const_principal principal, + krb5_keytab *keytab) +{ + krb5_error_code ret; + const char *cs_ktname; + OM_uint32 tmp; + + __gsskrb5_cred_store_find(&tmp, cred_store, "client_keytab", &cs_ktname); + + if (cs_ktname) + ret = krb5_kt_resolve(context, cs_ktname, keytab); + else { + char *name = NULL; + ret = _krb5_kt_client_default_name(context, &name); + if (ret == 0) + ret = krb5_kt_resolve(context, name, keytab); + krb5_xfree(name); + } + + if (ret == 0 && principal) { + krb5_keytab_entry entry; + + ret = krb5_kt_get_entry(context, *keytab, principal, + 0, 0, &entry); + if (ret == 0) + krb5_kt_free_entry(context, &entry); + } + + if (ret) { + if (*keytab) { + krb5_kt_close(context, *keytab); + *keytab = NULL; + } + + ret = get_system_keytab(context, GSS_C_NO_CRED_STORE, keytab); + } + + return ret; +} + +static krb5_boolean +is_valid_password_cred_store(gss_const_key_value_set_t cred_store) +{ + size_t i; + + if (cred_store == GSS_C_NO_CRED_STORE) + return TRUE; + + /* XXX don't check keytab, someday we will allow password+acceptor creds */ + for (i = 0; i < cred_store->count; i++) { + if (strcmp(cred_store->elements[i].key, "ccache") == 0 || + strcmp(cred_store->elements[i].key, "client_keytab") == 0) + return FALSE; + } + + return TRUE; +} + +/* + * This function produces a cred with a MEMORY ccache containing a TGT + * acquired with a password. + */ +static OM_uint32 +acquire_cred_with_password(OM_uint32 *minor_status, + krb5_context context, + const char *password, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gsskrb5_cred handle) +{ + OM_uint32 ret = GSS_S_FAILURE; + krb5_creds cred; + krb5_init_creds_context ctx = NULL; + krb5_get_init_creds_opt *opt = NULL; + krb5_ccache ccache = NULL; + krb5_error_code kret; + time_t now; + OM_uint32 left; + const char *realm; + + if (!is_valid_password_cred_store(cred_store)) { + *minor_status = GSS_KRB5_S_G_BAD_PASSWORD_CRED_STORE; + return GSS_S_NO_CRED; + } + + if (cred_usage == GSS_C_ACCEPT) { + /* + * TODO: Here we should eventually support user2user (when we get + * support for that via an extension to the mechanism + * allowing for more than two security context tokens), + * and/or new unique MEMORY keytabs (we have MEMORY keytab + * support, but we don't have a keytab equivalent of + * krb5_cc_new_unique()). Either way, for now we can't + * support this. + */ + *minor_status = ENOTSUP; /* XXX Better error? */ + return GSS_S_FAILURE; + } + + memset(&cred, 0, sizeof(cred)); + + if (handle->principal == NULL) { + kret = krb5_get_default_principal(context, &handle->principal); + if (kret) + goto end; + } + realm = krb5_principal_get_realm(context, handle->principal); + + kret = krb5_get_init_creds_opt_alloc(context, &opt); + if (kret == 0) { + krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm, + opt); + kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, 0, + opt, &ctx); + } + if (kret == 0) + kret = _krb5_init_creds_set_fast_anon_pkinit_optimistic(context, ctx); + if (kret == 0) + kret = krb5_init_creds_set_password(context, ctx, password); + + /* + * Get the current time before the AS exchange so we don't + * accidentally end up returning a value that puts advertised + * expiration past the real expiration. + * + * We need to do this because krb5_cc_get_lifetime() returns a + * relative time that we need to add to the current time. We ought + * to have a version of krb5_cc_get_lifetime() that returns absolute + * time... + */ + krb5_timeofday(context, &now); + + if (kret == 0) + kret = krb5_init_creds_get(context, ctx); + if (kret == 0) + kret = krb5_init_creds_get_creds(context, ctx, &cred); + if (kret == 0) + kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache); + if (kret == 0) + kret = krb5_cc_initialize(context, ccache, cred.client); + if (kret == 0) + kret = krb5_init_creds_store(context, ctx, ccache); + if (kret == 0) + kret = krb5_cc_store_cred(context, ccache, &cred); + if (kret) + goto end; + + handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; + + ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache, + handle->principal, &left); + if (ret != GSS_S_COMPLETE) + goto end; + handle->endtime = now + left; + handle->ccache = ccache; + ccache = NULL; + ret = GSS_S_COMPLETE; + +end: + krb5_get_init_creds_opt_free(context, opt); + if (ctx) + krb5_init_creds_free(context, ctx); + if (ccache != NULL) + krb5_cc_destroy(context, ccache); + if (cred.client != NULL) + krb5_free_cred_contents(context, &cred); + if (ret != GSS_S_COMPLETE) + *minor_status = kret; + return (ret); +} + +/* + * Acquires an initiator credential from a ccache or using a keytab. + */ +static OM_uint32 +acquire_initiator_cred(OM_uint32 *minor_status, + krb5_context context, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gsskrb5_cred handle) +{ + OM_uint32 ret; + krb5_creds cred; + krb5_get_init_creds_opt *opt; + krb5_principal def_princ = NULL; + krb5_ccache def_ccache = NULL; + krb5_ccache ccache = NULL; /* we may store into this ccache */ + krb5_keytab keytab = NULL; + krb5_error_code kret = 0; + OM_uint32 left; + const char *cs_ccache_name; + time_t lifetime = 0; + time_t now; + + memset(&cred, 0, sizeof(cred)); + + ret = __gsskrb5_cred_store_find(minor_status, cred_store, + "ccache", &cs_ccache_name); + if (GSS_ERROR(ret)) + return ret; + + ret = GSS_S_FAILURE; + + /* + * Get current time early so we can set handle->endtime to a value that + * cannot accidentally be past the real endtime. We need a variant of + * krb5_cc_get_lifetime() that returns absolute endtime. + */ + krb5_timeofday(context, &now); + + /* + * First look for a ccache that has the desired_name (which may be + * the default credential name), unless a specific credential cache + * was included in cred_store. + * + * If we don't have an unexpired credential, acquire one with a + * keytab. + * + * If we acquire one with a keytab, save it in the ccache we found + * with the expired credential, if any. + * + * If we don't have any such ccache, then use a MEMORY ccache. + */ + + if (handle->principal != NULL && cs_ccache_name == NULL) { + /* + * Not default credential case. See if we can find a ccache in + * the cccol for the desired_name. + */ + kret = krb5_cc_cache_match(context, + handle->principal, + &ccache); + if (kret == 0) { + kret = krb5_cc_get_lifetime(context, ccache, &lifetime); + if (kret == 0) { + if (lifetime > 0) + goto found; + else + goto try_keytab; + } + } + /* + * Fall through. We shouldn't find this in the default ccache + * either, but we'll give it a try, then we'll try using a keytab. + */ + } + + /* + * Either desired_name was GSS_C_NO_NAME (default cred) or + * krb5_cc_cache_match() failed (or found expired). + */ + if (cs_ccache_name) + kret = krb5_cc_resolve(context, cs_ccache_name, &def_ccache); + else + kret = krb5_cc_default(context, &def_ccache); + if (kret != 0) + goto try_keytab; + kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime); + if (kret != 0) + lifetime = 0; + kret = krb5_cc_get_principal(context, def_ccache, &def_princ); + if (kret != 0) + goto try_keytab; + /* + * Have a default ccache; see if it matches desired_name. + */ + if (handle->principal == NULL || + krb5_principal_compare(context, handle->principal, + def_princ) == TRUE) { + /* + * It matches. + * + * If we end up trying a keytab then we can write the result to + * the default ccache. + */ + if (handle->principal == NULL) { + kret = krb5_copy_principal(context, def_princ, &handle->principal); + if (kret) + goto end; + } + if (ccache != NULL) + krb5_cc_close(context, ccache); + ccache = def_ccache; + def_ccache = NULL; + if (lifetime > 0) + goto found; + /* else we fall through and try using a keytab */ + } + +try_keytab: + if (handle->principal == NULL) { + /* We need to know what client principal to use */ + kret = krb5_get_default_principal(context, &handle->principal); + if (kret) + goto end; + } + kret = get_client_keytab(context, cred_store, handle->principal, &keytab); + if (kret) + goto end; + + kret = krb5_get_init_creds_opt_alloc(context, &opt); + if (kret) + goto end; + krb5_timeofday(context, &now); + kret = krb5_get_init_creds_keytab(context, &cred, handle->principal, + keytab, 0, NULL, opt); + krb5_get_init_creds_opt_free(context, opt); + if (kret) + goto end; + + /* + * We got a credential with a keytab. Save it if we can. + */ + if (ccache == NULL) { + /* + * There's no ccache we can overwrite with the credentials we acquired + * with a keytab. We'll use a MEMORY ccache then. + * + * Note that an application that falls into this repeatedly will do an + * AS exchange every time it acquires a credential handle. Hopefully + * this doesn't happen much. A workaround is to kinit -k once so that + * we always re-initialize the matched/default ccache here. I.e., once + * there's a FILE/DIR ccache, we'll keep it frash automatically if we + * have a keytab, but if there's no FILE/DIR ccache, then we'll + * get a fresh credential *every* time we're asked. + */ + kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache); + if (kret) + goto end; + handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; + } /* else we'll re-initialize whichever ccache we matched above */ + + kret = krb5_cc_initialize(context, ccache, cred.client); + if (kret) + goto end; + kret = krb5_cc_store_cred(context, ccache, &cred); + if (kret) + goto end; + +found: + assert(handle->principal != NULL); + ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache, + handle->principal, &left); + if (ret != GSS_S_COMPLETE) + goto end; + handle->endtime = now + left; + handle->ccache = ccache; + ccache = NULL; + ret = GSS_S_COMPLETE; + kret = 0; + +end: + if (ccache != NULL) { + if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0) + krb5_cc_destroy(context, ccache); + else + krb5_cc_close(context, ccache); + } + if (def_ccache != NULL) + krb5_cc_close(context, def_ccache); + if (cred.client != NULL) + krb5_free_cred_contents(context, &cred); + if (def_princ != NULL) + krb5_free_principal(context, def_princ); + if (keytab != NULL) + krb5_kt_close(context, keytab); + if (ret != GSS_S_COMPLETE && kret != 0) + *minor_status = kret; + return (ret); +} + +static OM_uint32 +acquire_acceptor_cred(OM_uint32 * minor_status, + krb5_context context, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gsskrb5_cred handle) +{ + OM_uint32 ret; + krb5_error_code kret; + + ret = GSS_S_FAILURE; + + kret = get_system_keytab(context, cred_store, &handle->keytab); + if (kret) + goto end; + + /* check that the requested principal exists in the keytab */ + if (handle->principal) { + krb5_keytab_entry entry; + + kret = krb5_kt_get_entry(context, handle->keytab, + handle->principal, 0, 0, &entry); + if (kret) + goto end; + krb5_kt_free_entry(context, &entry); + ret = GSS_S_COMPLETE; + } else { + /* + * Check if there is at least one entry in the keytab before + * declaring it as an useful keytab. + */ + krb5_keytab_entry tmp; + krb5_kt_cursor c; + + kret = krb5_kt_start_seq_get (context, handle->keytab, &c); + if (kret) + goto end; + if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) { + krb5_kt_free_entry(context, &tmp); + ret = GSS_S_COMPLETE; /* ok found one entry */ + } + krb5_kt_end_seq_get (context, handle->keytab, &c); + } +end: + if (ret != GSS_S_COMPLETE) { + if (handle->keytab != NULL) + krb5_kt_close(context, handle->keytab); + if (kret != 0) { + *minor_status = kret; + } + } + return (ret); +} + + +OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from +(OM_uint32 * minor_status, + gss_const_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gss_cred_id_t * output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec + ) +{ + krb5_context context; + gsskrb5_cred handle; + OM_uint32 ret; + const char *password = NULL; + + if (desired_mechs) { + int present = 0; + + ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM, + desired_mechs, &present); + if (ret) + return ret; + if (!present) { + *minor_status = 0; + return GSS_S_BAD_MECH; + } + } + + cred_usage &= GSS_C_OPTION_MASK; + + if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && + cred_usage != GSS_C_BOTH) { + *minor_status = GSS_KRB5_S_G_BAD_USAGE; + return GSS_S_FAILURE; + } + + ret = __gsskrb5_cred_store_find(minor_status, cred_store, + "password", &password); + if (GSS_ERROR(ret)) + return ret; + + GSSAPI_KRB5_INIT(&context); + + *output_cred_handle = GSS_C_NO_CREDENTIAL; + + handle = calloc(1, sizeof(*handle)); + if (handle == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + handle->destination_realm = NULL; + HEIMDAL_MUTEX_init(&handle->cred_id_mutex); + + if (desired_name != GSS_C_NO_NAME) { + ret = _gsskrb5_canon_name(minor_status, context, + desired_name, &handle->principal); + if (ret) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + free(handle); + return ret; + } + } + + if (password) { + ret = acquire_cred_with_password(minor_status, context, password, time_req, + desired_mechs, cred_usage, cred_store, handle); + if (ret != GSS_S_COMPLETE) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + krb5_free_principal(context, handle->principal); + free(handle); + return (ret); + } + } else { + /* + * Acquire a credential from the specified or background credential + * store (ccache, keytab). + */ + if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) { + ret = acquire_initiator_cred(minor_status, context, time_req, + desired_mechs, cred_usage, + cred_store, handle); + if (ret != GSS_S_COMPLETE) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + krb5_free_principal(context, handle->principal); + free(handle); + return (ret); + } + } + if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) { + ret = acquire_acceptor_cred(minor_status, context, time_req, + desired_mechs, cred_usage, + cred_store, handle); + if (ret != GSS_S_COMPLETE) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + krb5_free_principal(context, handle->principal); + free(handle); + return (ret); + } + } + } + ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms); + if (ret == GSS_S_COMPLETE) + ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM, + &handle->mechanisms); + handle->usage = cred_usage; + if (ret == GSS_S_COMPLETE) + ret = _gsskrb5_inquire_cred(minor_status, (gss_cred_id_t)handle, + NULL, time_rec, NULL, actual_mechs); + if (ret != GSS_S_COMPLETE) { + if (handle->mechanisms != NULL) + gss_release_oid_set(NULL, &handle->mechanisms); + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + krb5_free_principal(context, handle->principal); + free(handle); + return (ret); + } + *minor_status = 0; + *output_cred_handle = (gss_cred_id_t)handle; + return (GSS_S_COMPLETE); +} |