diff options
Diffstat (limited to 'lib/gssapi/krb5/acquire_cred.c')
-rw-r--r-- | lib/gssapi/krb5/acquire_cred.c | 529 |
1 files changed, 341 insertions, 188 deletions
diff --git a/lib/gssapi/krb5/acquire_cred.c b/lib/gssapi/krb5/acquire_cred.c index 0f1f5f81cffc..9c880b334fd6 100644 --- a/lib/gssapi/krb5/acquire_cred.c +++ b/lib/gssapi/krb5/acquire_cred.c @@ -40,37 +40,16 @@ __gsskrb5_ccache_lifetime(OM_uint32 *minor_status, krb5_principal principal, OM_uint32 *lifetime) { - krb5_creds in_cred, out_cred; - krb5_const_realm realm; krb5_error_code kret; + time_t left; - memset(&in_cred, 0, sizeof(in_cred)); - in_cred.client = principal; - - realm = krb5_principal_get_realm(context, principal); - if (realm == NULL) { - _gsskrb5_clear_status (); - *minor_status = KRB5_PRINC_NOMATCH; /* XXX */ - return GSS_S_FAILURE; - } - - kret = krb5_make_principal(context, &in_cred.server, - realm, KRB5_TGS_NAME, realm, NULL); - if (kret) { - *minor_status = kret; - return GSS_S_FAILURE; - } - - kret = krb5_cc_retrieve_cred(context, id, 0, &in_cred, &out_cred); - krb5_free_principal(context, in_cred.server); + kret = krb5_cc_get_lifetime(context, id, &left); if (kret) { - *minor_status = 0; - *lifetime = 0; - return GSS_S_COMPLETE; + *minor_status = kret; + return GSS_S_FAILURE; } - *lifetime = out_cred.times.endtime; - krb5_free_cred_contents(context, &out_cred); + *lifetime = left; return GSS_S_COMPLETE; } @@ -101,146 +80,282 @@ get_keytab(krb5_context context, krb5_keytab *keytab) return (kret); } -static OM_uint32 acquire_initiator_cred - (OM_uint32 * minor_status, - krb5_context context, - gss_const_OID credential_type, - const void *credential_data, - const gss_name_t desired_name, - OM_uint32 time_req, - gss_const_OID desired_mech, - gss_cred_usage_t cred_usage, - gsskrb5_cred handle - ) +/* + * 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_const_OID desired_mech, + gss_cred_usage_t cred_usage, + gsskrb5_cred handle) { - OM_uint32 ret; + OM_uint32 ret = GSS_S_FAILURE; krb5_creds cred; - krb5_principal def_princ; krb5_get_init_creds_opt *opt; - krb5_ccache ccache; - krb5_keytab keytab; + krb5_ccache ccache = NULL; krb5_error_code kret; + time_t now; + OM_uint32 left; + + 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; + } + kret = krb5_get_init_creds_opt_alloc(context, &opt); + if (kret) + goto end; + + /* + * 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); + + kret = krb5_get_init_creds_password(context, &cred, handle->principal, + password, NULL, NULL, 0, NULL, opt); + krb5_get_init_creds_opt_free(context, opt); + if (kret) + goto end; + + kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache); + if (kret) + goto end; + + kret = krb5_cc_initialize(context, ccache, cred.client); + if (kret) + goto end; - keytab = NULL; + 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; - def_princ = NULL; - ret = GSS_S_FAILURE; + ret = GSS_S_COMPLETE; + kret = 0; + +end: + if (ccache != NULL) + krb5_cc_destroy(context, ccache); + if (cred.client != NULL) + krb5_free_cred_contents(context, &cred); + if (ret != GSS_S_COMPLETE && kret != 0) + *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_const_OID desired_mech, + gss_cred_usage_t cred_usage, + gsskrb5_cred handle) +{ + OM_uint32 ret = GSS_S_FAILURE; + 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; + time_t lifetime = 0; + time_t now; + memset(&cred, 0, sizeof(cred)); /* - * If we have a preferred principal, lets try to find it in all - * caches, otherwise, fall back to default cache, ignore all - * errors while searching. + * 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); - if (credential_type != GSS_C_NO_OID && - !gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) { - kret = KRB5_NOCREDS_SUPPLIED; /* XXX */ - goto end; - } + /* + * First look for a ccache that has the desired_name (which may be + * the default credential name). + * + * 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) { - kret = krb5_cc_cache_match (context, - handle->principal, - &ccache); + if (handle->principal != 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) { - ret = GSS_S_COMPLETE; - goto found; + 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. + */ } - if (ccache == NULL) { - kret = krb5_cc_default(context, &ccache); - if (kret) - goto end; - } - kret = krb5_cc_get_principal(context, ccache, &def_princ); - if (kret != 0) { - /* we'll try to use a keytab below */ - krb5_cc_close(context, ccache); - def_princ = NULL; - kret = 0; - } else if (handle->principal == NULL) { - kret = krb5_copy_principal(context, def_princ, &handle->principal); - if (kret) - goto end; - } else if (handle->principal != NULL && - krb5_principal_compare(context, handle->principal, - def_princ) == FALSE) { - krb5_free_principal(context, def_princ); - def_princ = NULL; - krb5_cc_close(context, ccache); - ccache = NULL; + /* + * Either desired_name was GSS_C_NO_NAME (default cred) or + * krb5_cc_cache_match() failed (or found expired). + */ + 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 */ } - if (def_princ == NULL) { - /* We have no existing credentials cache, - * so attempt to get a TGT using a keytab. - */ - if (handle->principal == NULL) { - kret = krb5_get_default_principal(context, &handle->principal); - if (kret) - goto end; - } - kret = krb5_get_init_creds_opt_alloc(context, &opt); - if (kret) - goto end; - if (credential_type != GSS_C_NO_OID && - gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) { - gss_buffer_t password = (gss_buffer_t)credential_data; - - /* XXX are we requiring password to be NUL terminated? */ - - kret = krb5_get_init_creds_password(context, &cred, - handle->principal, - password->value, - NULL, NULL, 0, NULL, opt); - } else { - kret = get_keytab(context, &keytab); - if (kret) { - krb5_get_init_creds_opt_free(context, opt); - goto end; - } - 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; - kret = krb5_cc_new_unique(context, krb5_cc_type_memory, - NULL, &ccache); - if (kret) - goto end; - kret = krb5_cc_initialize(context, ccache, cred.client); - if (kret) { - krb5_cc_destroy(context, ccache); - goto end; - } - kret = krb5_cc_store_cred(context, ccache, &cred); - if (kret) { - krb5_cc_destroy(context, ccache); - goto end; - } - handle->lifetime = cred.times.endtime; - handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; - } else { - ret = __gsskrb5_ccache_lifetime(minor_status, - context, - ccache, - handle->principal, - &handle->lifetime); - if (ret != GSS_S_COMPLETE) { - krb5_cc_close(context, ccache); - goto end; - } - kret = 0; +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; } - found: + kret = get_keytab(context, &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) @@ -252,28 +367,19 @@ end: return (ret); } -static OM_uint32 acquire_acceptor_cred - (OM_uint32 * minor_status, - krb5_context context, - gss_const_OID credential_type, - const void *credential_data, - const gss_name_t desired_name, - OM_uint32 time_req, - gss_const_OID desired_mech, - gss_cred_usage_t cred_usage, - gsskrb5_cred handle - ) +static OM_uint32 +acquire_acceptor_cred(OM_uint32 * minor_status, + krb5_context context, + OM_uint32 time_req, + gss_const_OID desired_mech, + gss_cred_usage_t cred_usage, + gsskrb5_cred handle) { OM_uint32 ret; krb5_error_code kret; ret = GSS_S_FAILURE; - if (credential_type != GSS_C_NO_OID) { - kret = EINVAL; - goto end; - } - kret = get_keytab(context, &handle->keytab); if (kret) goto end; @@ -318,7 +424,7 @@ end: OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred (OM_uint32 * minor_status, - const gss_name_t desired_name, + gss_const_name_t desired_name, OM_uint32 time_req, const gss_OID_set desired_mechs, gss_cred_usage_t cred_usage, @@ -366,7 +472,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext (OM_uint32 * minor_status, - const gss_name_t desired_name, + gss_const_name_t desired_name, gss_const_OID credential_type, const void *credential_data, OM_uint32 time_req, @@ -381,25 +487,26 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext cred_usage &= GSS_C_OPTION_MASK; - if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) { + 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; } GSSAPI_KRB5_INIT(&context); - *output_cred_handle = NULL; + *output_cred_handle = GSS_C_NO_CREDENTIAL; handle = calloc(1, sizeof(*handle)); if (handle == NULL) { *minor_status = ENOMEM; - return (GSS_S_FAILURE); + return GSS_S_FAILURE; } HEIMDAL_MUTEX_init(&handle->cred_id_mutex); if (desired_name != GSS_C_NO_NAME) { - ret = _gsskrb5_canon_name(minor_status, context, 1, NULL, + ret = _gsskrb5_canon_name(minor_status, context, desired_name, &handle->principal); if (ret) { HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); @@ -407,29 +514,75 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext return ret; } } - if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) { - ret = acquire_initiator_cred(minor_status, context, - credential_type, credential_data, - desired_name, time_req, - desired_mech, cred_usage, 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, - credential_type, credential_data, - desired_name, time_req, - desired_mech, cred_usage, handle); - if (ret != GSS_S_COMPLETE) { - HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); - krb5_free_principal(context, handle->principal); - free(handle); - return (ret); - } + + if (credential_type != GSS_C_NO_OID && + gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) { + /* Acquire a cred with a password */ + gss_const_buffer_t pwbuf = credential_data; + char *pw; + + if (pwbuf == NULL) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + free(handle); + *minor_status = KRB5_NOCREDS_SUPPLIED; /* see below */ + return GSS_S_CALL_INACCESSIBLE_READ; + } + + /* NUL-terminate the password, if it wasn't already */ + pw = strndup(pwbuf->value, pwbuf->length); + if (pw == NULL) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + free(handle); + *minor_status = krb5_enomem(context); + return GSS_S_CALL_INACCESSIBLE_READ; + } + ret = acquire_cred_with_password(minor_status, context, pw, time_req, + desired_mech, cred_usage, handle); + free(pw); + if (ret != GSS_S_COMPLETE) { + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + krb5_free_principal(context, handle->principal); + free(handle); + return (ret); + } + } else if (credential_type != GSS_C_NO_OID) { + /* + * _gss_acquire_cred_ext() called with something other than a password. + * + * Not supported. + * + * _gss_acquire_cred_ext() is not a supported public interface, so + * we don't have to try too hard as to minor status codes here. + */ + HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); + free(handle); + *minor_status = ENOTSUP; + return GSS_S_FAILURE; + } else { + /* + * Acquire a credential from the 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_mech, cred_usage, 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_mech, cred_usage, 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) |