diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/appl/gss-sample/t_gss_sample.py | 23 | ||||
| -rw-r--r-- | src/include/k5-int.h | 1 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/acquire_cred.c | 511 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/gssapiP_krb5.h | 10 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/iakerb.c | 68 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/init_sec_context.c | 24 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/rel_cred.c | 3 | ||||
| -rw-r--r-- | src/lib/gssapi/krb5/val_cred.c | 2 | ||||
| -rw-r--r-- | src/tests/gssapi/Makefile.in | 8 | ||||
| -rw-r--r-- | src/tests/gssapi/ccinit.c | 72 | ||||
| -rw-r--r-- | src/tests/gssapi/ccrefresh.c | 80 | ||||
| -rw-r--r-- | src/tests/gssapi/t_ccselect.py | 2 | ||||
| -rw-r--r-- | src/tests/gssapi/t_client_keytab.py | 132 |
13 files changed, 705 insertions, 231 deletions
diff --git a/src/appl/gss-sample/t_gss_sample.py b/src/appl/gss-sample/t_gss_sample.py index cfac43d85..211da97c0 100644 --- a/src/appl/gss-sample/t_gss_sample.py +++ b/src/appl/gss-sample/t_gss_sample.py @@ -61,11 +61,17 @@ def tgs_test(realm, options): # Perform a test of the server and client with initial credentials # obtained through gss_acquire_cred_with_password(). -def as_test(realm, options): +def pw_test(realm, options): os.remove(realm.ccache) server_client_test(realm, options + ['-user', realm.user_princ, '-pass', password('user')]) +# Perform a test of the server and client with initial credentials +# obtained with the client keytab +def kt_test(realm, options): + os.remove(realm.ccache) + server_client_test(realm, options) + for realm in multipass_realms(): ccache_save(realm) @@ -75,10 +81,15 @@ for realm in multipass_realms(): # test default (i.e., krb5) mechanism with GSS_C_DCE_STYLE tgs_test(realm, ['-dce']) - as_test(realm, ['-krb5']) - as_test(realm, ['-spnego']) - as_test(realm, ['-iakerb']) - # test default (i.e., krb5) mechanism with GSS_C_DCE_STYLE - as_test(realm, ['-dce']) + pw_test(realm, ['-krb5']) + pw_test(realm, ['-spnego']) + pw_test(realm, ['-iakerb']) + pw_test(realm, ['-dce']) + + realm.extract_keytab(realm.user_princ, realm.client_keytab) + kt_test(realm, ['-krb5']) + kt_test(realm, ['-spnego']) + kt_test(realm, ['-iakerb']) + kt_test(realm, ['-dce']) success('GSS sample application') diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 69d30b3b5..c426acad9 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -280,6 +280,7 @@ typedef INT64_TYPE krb5_int64; /* Cache configuration variables */ #define KRB5_CONF_FAST_AVAIL "fast_avail" #define KRB5_CONF_PROXY_IMPERSONATOR "proxy_impersonator" +#define KRB5_CONF_REFRESH_TIME "refresh_time" /* Error codes used in KRB_ERROR protocol messages. Return values of library routines are based on a different error table diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c index 8b3199083..1972b1eb2 100644 --- a/src/lib/gssapi/krb5/acquire_cred.c +++ b/src/lib/gssapi/krb5/acquire_cred.c @@ -185,7 +185,6 @@ cleanup: static OM_uint32 acquire_accept_cred(krb5_context context, OM_uint32 *minor_status, - krb5_gss_name_t desired_name, krb5_keytab req_keytab, krb5_gss_cred_id_rec *cred) { @@ -223,9 +222,9 @@ acquire_accept_cred(krb5_context context, return GSS_S_CRED_UNAVAIL; } - if (desired_name != NULL) { + if (cred->name != NULL) { /* Make sure we keys matching the desired name in the keytab. */ - code = check_keytab(context, kt, desired_name); + code = check_keytab(context, kt, cred->name); if (code) { krb5_kt_close(context, kt); if (code == KRB5_KT_NOTFOUND) { @@ -238,15 +237,8 @@ acquire_accept_cred(krb5_context context, return GSS_S_CRED_UNAVAIL; } - assert(cred->name == NULL); - code = kg_duplicate_name(context, desired_name, &cred->name); - if (code) { - *minor_status = code; - return GSS_S_FAILURE; - } - /* Open the replay cache for this principal. */ - code = krb5_get_server_rcache(context, &desired_name->princ->data[0], + code = krb5_get_server_rcache(context, &cred->name->princ->data[0], &cred->rcache); if (code) { *minor_status = code; @@ -307,65 +299,6 @@ get_ccache_leash(krb5_context context, krb5_principal desired_princ, } #endif /* USE_LEASH */ -/* Prepare to acquire credentials into ccache using password at - * init_sec_context time. On success, cred takes ownership of ccache. */ -static krb5_error_code -prep_ccache(krb5_context context, krb5_gss_cred_id_rec *cred, - krb5_ccache ccache, krb5_principal desired_princ, - gss_buffer_t password) -{ - krb5_error_code code; - krb5_principal ccache_princ; - krb5_data pwdata = make_data(password->value, password->length), pwcopy; - krb5_boolean eq; - const char *cctype; - krb5_ccache newcache = NULL; - - /* Check the ccache principal or initialize a new cache. */ - code = krb5_cc_get_principal(context, ccache, &ccache_princ); - if (code == 0) { - eq = krb5_principal_compare(context, ccache_princ, desired_princ); - krb5_free_principal(context, ccache_princ); - if (!eq) { - cctype = krb5_cc_get_type(context, ccache); - if (krb5_cc_support_switch(context, cctype)) { - /* Make a new ccache within the collection. */ - code = krb5_cc_new_unique(context, cctype, NULL, &newcache); - if (code) - return code; - } else - return KG_CCACHE_NOMATCH; - } - } else if (code == KRB5_FCC_NOFILE) { - /* Cache file does not exist; create and initialize one. */ - code = krb5_cc_initialize(context, ccache, desired_princ); - if (code) - return code; - } else - return code; - - /* Save the desired principal as the credential name if not already set. */ - if (!cred->name) { - code = kg_init_name(context, desired_princ, NULL, NULL, NULL, 0, - &cred->name); - if (code) - return code; - } - - /* Stash the password for later. */ - code = krb5int_copy_data_contents_add0(context, &pwdata, &pwcopy); - if (code) - return code; - cred->password = pwcopy.data; - - if (newcache) { - krb5_cc_close(context, ccache); - cred->ccache = newcache; - } else - cred->ccache = ccache; - return 0; -} - /* Set fields in cred according to a ccache config entry whose key (in * principal form) is config_princ and whose value is value. */ static krb5_error_code @@ -386,36 +319,67 @@ scan_cc_config(krb5_context context, krb5_gss_cred_id_rec *cred, krb5_free_data_contents(context, &data0); if (code) return code; + } else if (data_eq_string(config_princ->data[1], KRB5_CONF_REFRESH_TIME) && + cred->refresh_time == 0) { + code = krb5int_copy_data_contents_add0(context, value, &data0); + if (code) + return code; + cred->refresh_time = atol(data0.data); + krb5_free_data_contents(context, &data0); } return 0; } -/* Check ccache and scan it for its expiry time. On success, cred takes - * ownership of ccache. */ +/* Return true if it appears that we can non-interactively get initial + * tickets for cred. */ +static krb5_boolean +can_get_initial_creds(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_error_code code; + krb5_keytab_entry entry; + + if (cred->password != NULL) + return TRUE; + + /* If we don't know the client principal yet, check for any keytab keys. */ + if (cred->name == NULL) + return !krb5_kt_have_content(context, cred->client_keytab); + + /* Check if we have a keytab key for the client principal. */ + code = krb5_kt_get_entry(context, cred->client_keytab, cred->name->princ, + 0, 0, &entry); + if (code) { + krb5_clear_error_message(context); + return FALSE; + } + krb5_free_keytab_entry_contents(context, &entry); + return TRUE; +} + +/* Scan cred->ccache for name, expiry time, impersonator, refresh time. */ static krb5_error_code -scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred, - krb5_ccache ccache, krb5_principal desired_princ) +scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred) { krb5_error_code code; + krb5_ccache ccache = cred->ccache; krb5_principal ccache_princ = NULL, tgt_princ = NULL; krb5_data *realm; krb5_cc_cursor cursor; krb5_creds creds; krb5_timestamp endtime; - int got_endtime = 0, is_tgt; + krb5_boolean is_tgt; /* Turn off OPENCLOSE mode while extensive frobbing is going on. */ code = krb5_cc_set_flags(context, ccache, 0); if (code) return code; + /* Credentials cache principal must match the initiator name. */ code = krb5_cc_get_principal(context, ccache, &ccache_princ); if (code != 0) - return code; - - /* Credentials cache principal must match the initiator name. */ - if (desired_princ != NULL && - !krb5_principal_compare(context, ccache_princ, desired_princ)) { + goto cleanup; + if (cred->name != NULL && + !krb5_principal_compare(context, ccache_princ, cred->name->princ)) { code = KG_CCACHE_NOMATCH; goto cleanup; } @@ -457,109 +421,298 @@ scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred, is_tgt = krb5_principal_compare(context, tgt_princ, creds.server); endtime = creds.times.endtime; krb5_free_cred_contents(context, &creds); - if (is_tgt || !got_endtime) - cred->expire = creds.times.endtime; - got_endtime = 1; + if (is_tgt) + cred->have_tgt = TRUE; + if (is_tgt || cred->expire == 0) + cred->expire = endtime; } krb5_cc_end_seq_get(context, ccache, &cursor); if (code && code != KRB5_CC_END) goto cleanup; code = 0; - if (!got_endtime) { /* ccache is empty. */ + if (cred->expire == 0 && !can_get_initial_creds(context, cred)) { code = KG_EMPTY_CCACHE; goto cleanup; } (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE); - cred->ccache = ccache; cleanup: + (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE); krb5_free_principal(context, ccache_princ); krb5_free_principal(context, tgt_princ); return code; } -/* get credentials corresponding to the default credential cache. - If successful, set the ccache-specific fields in cred. -*/ +/* Find an existing or destination ccache for cred->name. */ +static krb5_error_code +get_cache_for_name(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_error_code code; + krb5_boolean can_get, have_collection; + krb5_ccache defcc = NULL; + krb5_principal princ = NULL; + const char *cctype; + + assert(cred->name != NULL && cred->ccache == NULL); +#ifdef USE_LEASH + return get_ccache_leash(context, cred->name->princ, &cred->ccache); +#else + /* Check first whether we can acquire tickets, to avoid overwriting the + * extended error message from krb5_cc_cache_match. */ + can_get = can_get_initial_creds(context, cred); + + /* Look for an existing cache for the client principal. */ + code = krb5_cc_cache_match(context, cred->name->princ, &cred->ccache); + if (code == 0) + return scan_ccache(context, cred); + if (code != KRB5_CC_NOTFOUND || !can_get) + return code; + krb5_clear_error_message(context); + + /* There is no existing ccache, but we can acquire credentials. Get the + * default ccache to help decide where we should put them. */ + code = krb5_cc_default(context, &defcc); + if (code) + return code; + cctype = krb5_cc_get_type(context, defcc); + have_collection = krb5_cc_support_switch(context, cctype); + + /* We can use an empty default ccache if we're using a password or if + * there's no collection. */ + if (cred->password != NULL || !have_collection) { + if (krb5_cc_get_principal(context, defcc, &princ) == KRB5_FCC_NOFILE) { + cred->ccache = defcc; + defcc = NULL; + } + krb5_clear_error_message(context); + } + + /* Otherwise, try to use a new cache in the collection. */ + if (cred->ccache == NULL) { + if (!have_collection) { + code = KG_CCACHE_NOMATCH; + goto cleanup; + } + code = krb5_cc_new_unique(context, cctype, NULL, &cred->ccache); + if (code) + goto cleanup; + } + +cleanup: + krb5_free_principal(context, princ); + if (defcc != NULL) + krb5_cc_close(context, defcc); + return code; +#endif /* not USE_LEASH */ +} + +/* Try to set cred->name using the client keytab. */ +static krb5_error_code +get_name_from_client_keytab(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_error_code code; + krb5_principal princ; + + assert(cred->name == NULL); + code = k5_kt_get_principal(context, cred->client_keytab, &princ); + if (code) + return code; + code = kg_init_name(context, princ, NULL, NULL, NULL, KG_INIT_NAME_NO_COPY, + &cred->name); + if (code) { + krb5_free_principal(context, princ); + return code; + } + return 0; +} + +/* Make a note in ccache that we should attempt to refresh it from the client + * keytab at refresh_time. */ +static void +set_refresh_time(krb5_context context, krb5_ccache ccache, + krb5_timestamp refresh_time) +{ + char buf[128]; + krb5_data d; + + snprintf(buf, sizeof(buf), "%ld", (long)refresh_time); + d = string2data(buf); + (void)krb5_cc_set_config(context, ccache, NULL, KRB5_CONF_REFRESH_TIME, + &d); + krb5_clear_error_message(context); +} + +/* Return true if it's time to refresh cred from the client keytab. If + * returning true, avoid retrying for 30 seconds. */ +krb5_boolean +kg_cred_time_to_refresh(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_timestamp now; + + if (krb5_timeofday(context, &now)) + return FALSE; + if (cred->refresh_time != 0 && now >= cred->refresh_time) { + set_refresh_time(context, cred->ccache, cred->refresh_time + 30); + return TRUE; + } + return FALSE; +} + +/* If appropriate, make a note to refresh cred from the client keytab when it + * is halfway to expired. */ +void +kg_cred_set_initial_refresh(krb5_context context, krb5_gss_cred_id_rec *cred, + krb5_ticket_times *times) +{ + krb5_timestamp refresh; + + /* For now, we only mark keytab-acquired credentials for refresh. */ + if (cred->password != NULL) + return; + + /* Make a note to refresh these when they are halfway to expired. */ + refresh = times->starttime + (times->endtime - times->starttime) / 2; + set_refresh_time(context, cred->ccache, refresh); +} + +/* Get initial credentials using the supplied password or client keytab. */ +static krb5_error_code +get_initial_cred(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_error_code code; + krb5_get_init_creds_opt *opt = NULL; + krb5_creds creds; + + code = krb5_get_init_creds_opt_alloc(context, &opt); + if (code) + return code; + code = krb5_get_init_creds_opt_set_out_ccache(context, opt, cred->ccache); + if (code) + goto cleanup; + if (cred->password != NULL) { + code = krb5_get_init_creds_password(context, &creds, cred->name->princ, + cred->password, NULL, NULL, 0, + NULL, opt); + } else { + code = krb5_get_init_creds_keytab(context, &creds, cred->name->princ, + cred->client_keytab, 0, NULL, opt); + } + if (code) + goto cleanup; + kg_cred_set_initial_refresh(context, cred, &creds.times); + cred->have_tgt = TRUE; + cred->expire = creds.times.endtime; + krb5_free_cred_contents(context, &creds); +cleanup: + krb5_get_init_creds_opt_free(context, opt); + return code; +} + +/* Get initial credentials if we ought to and are able to. */ +static krb5_error_code +maybe_get_initial_cred(krb5_context context, krb5_gss_cred_id_rec *cred) +{ + krb5_error_code code; + + /* Don't get creds if we don't know the name or are doing IAKERB. */ + if (cred->name == NULL || cred->iakerb_mech) + return 0; + + /* Get creds if we have none or if it's time to refresh. */ + if (cred->expire == 0 || kg_cred_time_to_refresh(context, cred)) { + code = get_initial_cred(context, cred); + /* If we were trying to refresh and failed, we can keep going. */ + if (code && cred->expire == 0) + return code; + krb5_clear_error_message(context); + } + return 0; +} static OM_uint32 acquire_init_cred(krb5_context context, OM_uint32 *minor_status, krb5_ccache req_ccache, - krb5_principal desired_princ, gss_buffer_t password, krb5_gss_cred_id_rec *cred) { krb5_error_code code; - krb5_ccache ccache = NULL; + krb5_data pwdata, pwcopy; int caller_ccname = 0; - cred->ccache = NULL; - - /* Load the GSS ccache name, if specified, into the context. */ + /* Get ccache from caller if available. */ if (GSS_ERROR(kg_sync_ccache_name(context, minor_status))) return GSS_S_FAILURE; if (GSS_ERROR(kg_caller_provided_ccache_name(minor_status, &caller_ccname))) return GSS_S_FAILURE; - - /* Pick a credential cache. */ if (req_ccache != NULL) { - code = krb5_cc_dup(context, req_ccache, &ccache); + code = krb5_cc_dup(context, req_ccache, &cred->ccache); + if (code) + goto error; } else if (caller_ccname) { /* Caller's ccache name has been set as the context default. */ - code = krb5int_cc_default(context, &ccache); - } else if (desired_princ) { - /* Try to find an appropriate ccache for the desired name. */ -#ifdef USE_LEASH - code = get_ccache_leash(context, desired_princ, &ccache); -#else - code = krb5_cc_cache_match(context, desired_princ, &ccache); - if (code == KRB5_CC_NOTFOUND && password != GSS_C_NO_BUFFER) { - /* Grab the default ccache for now; if it's not empty, prep_ccache - * will create a new one of the default type or error out. */ - krb5_clear_error_message(context); - code = krb5_cc_default(context, &ccache); - } -#endif - } else - code = 0; - if (code != 0) { - *minor_status = code; - return GSS_S_CRED_UNAVAIL; + code = krb5int_cc_default(context, &cred->ccache); + if (code) + goto error; } - if (ccache != NULL) { - if (password != GSS_C_NO_BUFFER && desired_princ != NULL) - code = prep_ccache(context, cred, ccache, desired_princ, password); - else - code = scan_ccache(context, cred, ccache, desired_princ); - if (code != 0) { - krb5_cc_close(context, ccache); - *minor_status = code; - return GSS_S_CRED_UNAVAIL; + code = krb5_kt_client_default(context, &cred->client_keytab); + if (code) + goto error; + + if (password != GSS_C_NO_BUFFER) { + pwdata = make_data(password->value, password->length); + code = krb5int_copy_data_contents_add0(context, &pwdata, &pwcopy); + if (code) + goto error; + cred->password = pwcopy.data; + } + + if (cred->ccache != NULL) { + /* The caller specified a ccache; check what's in it. */ + code = scan_ccache(context, cred); + if (code == KRB5_FCC_NOFILE) { + /* See if we can get initial creds. If the caller didn't specify + * a name, pick one from the client keytab. */ + if (cred->name == NULL) { + if (!get_name_from_client_keytab(context, cred)) + code = 0; + } else if (can_get_initial_creds(context, cred)) { + code = 0; + } } - cred->ccache = ccache; - } else { - /* We haven't decided on a ccache or principal yet, but fail now if - * there are no krb5 credentials at all. */ + if (code) + goto error; + } else if (cred->name != NULL) { + /* The caller specified a name but not a ccache; pick a cache. */ + code = get_cache_for_name(context, cred); + if (code) + goto error; + } + +#ifndef USE_LEASH + /* If we haven't picked a name, make sure we have or can get any creds, + * unless we're using Leash and might be able to get them interactively. */ + if (cred->name == NULL && !can_get_initial_creds(context, cred)) { code = krb5_cccol_have_content(context); - if (code != 0) { - *minor_status = code; - return GSS_S_CRED_UNAVAIL; - } + if (code) + goto error; } +#endif - /* - * If the caller specified no ccache and no desired principal, leave - * cred->ccache and cred->name NULL. They will be resolved later by - * kg_cred_resolve(), possibly using the target principal name. - */ + code = maybe_get_initial_cred(context, cred); + if (code) + goto error; *minor_status = 0; return GSS_S_COMPLETE; + +error: + *minor_status = code; + return GSS_S_CRED_UNAVAIL; } static OM_uint32 @@ -619,13 +772,21 @@ acquire_cred(OM_uint32 *minor_status, gss_name_t desired_name, goto error_out; } + if (name != NULL) { + code = kg_duplicate_name(context, name, &cred->name); + if (code) { + *minor_status = code; + return GSS_S_FAILURE; + } + } + #ifndef LEAN_CLIENT /* * If requested, acquire credentials for accepting. This will fill * in cred->name if desired_princ is specified. */ if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) { - ret = acquire_accept_cred(context, minor_status, name, keytab, cred); + ret = acquire_accept_cred(context, minor_status, keytab, cred); if (ret != GSS_S_COMPLETE) goto error_out; } @@ -636,8 +797,7 @@ acquire_cred(OM_uint32 *minor_status, gss_name_t desired_name, * in cred->name if it wasn't set above. */ if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) { - ret = acquire_init_cred(context, minor_status, ccache, - name ? name->princ : NULL, password, cred); + ret = acquire_init_cred(context, minor_status, ccache, password, cred); if (ret != GSS_S_COMPLETE) goto error_out; } @@ -702,8 +862,7 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context, krb5_error_code code; krb5_gss_cred_id_t cred = (krb5_gss_cred_id_t)cred_handle; krb5_gss_name_t tname = (krb5_gss_name_t)target_name; - krb5_ccache ccache = NULL; - krb5_principal client_princ = NULL; + krb5_principal client_princ; *minor_status = 0; @@ -712,37 +871,73 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context, return maj; k5_mutex_assert_locked(&cred->lock); - if (cred->ccache != NULL || cred->usage == GSS_C_ACCEPT) + if (cred->usage == GSS_C_ACCEPT || cred->name != NULL) return GSS_S_COMPLETE; + /* acquire_init_cred should have set both name and ccache, or neither. */ + assert(cred->ccache == NULL); - /* Pick a credential cache. */ if (tname != NULL) { - code = krb5_cc_select(context, tname->princ, &ccache, &client_princ); + /* Use the target name to select an existing ccache or a principal. */ + code = krb5_cc_select(context, tname->princ, &cred->ccache, + &client_princ); if (code && code != KRB5_CC_NOTFOUND) goto kerr; + if (client_princ != NULL) { + code = kg_init_name(context, client_princ, NULL, NULL, NULL, + KG_INIT_NAME_NO_COPY, &cred->name); + if (code) { + krb5_free_principal(context, client_princ); + goto kerr; + } + } + if (cred->ccache != NULL) { + code = scan_ccache(context, cred); + if (code) + goto kerr; + } } - if (ccache == NULL) { - /* - * Ideally we would get credentials for client_princ if it is set. At - * the moment, we just get the default ccache (obtaining credentials if - * the platform supports it) and check it against client_princ below. - */ - code = krb5int_cc_default(context, &ccache); + + /* If we still haven't picked a client principal, try using an existing + * default ccache. (On Windows, this may acquire initial creds.) */ + if (cred->name == NULL) { + code = krb5int_cc_default(context, &cred->ccache); if (code) goto kerr; + code = scan_ccache(context, cred); + if (code == KRB5_FCC_NOFILE) { + /* Default ccache doesn't exist; fall through to client keytab. */ + krb5_cc_close(context, cred->ccache); + cred->ccache = NULL; + } else if (code) { + goto kerr; + } } - code = scan_ccache(context, cred, ccache, client_princ); - if (code) { - krb5_cc_close(context, ccache); - goto kerr; + /* If that didn't work, try getting a name from the client keytab. */ + if (cred->name == NULL) { + code = get_name_from_client_keytab(context, cred); + if (code) { + code = KG_EMPTY_CCACHE; + goto kerr; + } } - krb5_free_principal(context, client_princ); + if (cred->name != NULL && cred->ccache == NULL) { + /* Pick a cache for the name we chose (from krb5_cc_select or from the + * client keytab). */ + code = get_cache_for_name(context, cred); + if (code) + goto kerr; + } + + /* Resolve name to ccache and possibly get initial credentials. */ + code = maybe_get_initial_cred(context, cred); + if (code) + goto kerr; + return GSS_S_COMPLETE; kerr: - krb5_free_principal(context, client_princ); k5_mutex_unlock(&cred->lock); save_error_info(code, context); *minor_status = code; diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index 5621dbdaf..9b0d6cc37 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -183,7 +183,10 @@ typedef struct _krb5_gss_cred_id_rec { /* ccache (init) data */ krb5_ccache ccache; + krb5_keytab client_keytab; + krb5_boolean have_tgt; krb5_timestamp expire; + krb5_timestamp refresh_time; krb5_enctype *req_enctypes; /* limit negotiated enctypes to this list */ char *password; } krb5_gss_cred_id_rec, *krb5_gss_cred_id_t; @@ -476,6 +479,13 @@ krb5_to_gss_cred(krb5_context context, krb5_creds *creds, krb5_gss_cred_id_t *out_cred); +krb5_boolean +kg_cred_time_to_refresh(krb5_context context, krb5_gss_cred_id_rec *cred); + +void +kg_cred_set_initial_refresh(krb5_context context, krb5_gss_cred_id_rec *cred, + krb5_ticket_times *times); + OM_uint32 kg_cred_resolve(OM_uint32 *minor_status, krb5_context context, gss_cred_id_t cred_handle, gss_name_t target_name); diff --git a/src/lib/gssapi/krb5/iakerb.c b/src/lib/gssapi/krb5/iakerb.c index 1b3236e66..1d73e2dab 100644 --- a/src/lib/gssapi/krb5/iakerb.c +++ b/src/lib/gssapi/krb5/iakerb.c @@ -416,7 +416,7 @@ iakerb_init_creds_ctx(iakerb_ctx_id_t ctx, { krb5_error_code code; - if (cred->iakerb_mech == 0 || cred->password == NULL) { + if (cred->iakerb_mech == 0) { code = EINVAL; goto cleanup; } @@ -446,7 +446,13 @@ iakerb_init_creds_ctx(iakerb_ctx_id_t ctx, if (code != 0) goto cleanup; - code = krb5_init_creds_set_password(ctx->k5c, ctx->icc, cred->password); + if (cred->password != NULL) { + code = krb5_init_creds_set_password(ctx->k5c, ctx->icc, + cred->password); + } else { + code = krb5_init_creds_set_keytab(ctx->k5c, ctx->icc, + cred->client_keytab); + } if (code != 0) goto cleanup; @@ -547,10 +553,17 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, code = krb5_init_creds_step(ctx->k5c, ctx->icc, &in, &out, &realm, &flags); - if (code != 0) - goto cleanup; - if (!(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) { + if (code != 0) { + if (cred->have_tgt) { + /* We were trying to refresh; keep going with current creds. */ + ctx->state = IAKERB_TGS_REQ; + krb5_clear_error_message(ctx->k5c); + } else { + goto cleanup; + } + } else if (!(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) { krb5_init_creds_get_times(ctx->k5c, ctx->icc, ×); + kg_cred_set_initial_refresh(ctx->k5c, cred, ×); cred->expire = times.endtime; krb5_init_creds_free(ctx->k5c, ctx->icc); @@ -650,43 +663,18 @@ iakerb_get_initial_state(iakerb_ctx_id_t ctx, in_creds.times.endtime = now + time_req; } - code = krb5_get_credentials(ctx->k5c, KRB5_GC_CACHED, - cred->ccache, + /* Make an AS request if we have no creds or it's time to refresh them. */ + if (cred->expire == 0 || kg_cred_time_to_refresh(ctx->k5c, cred)) { + *state = IAKERB_AS_REQ; + code = 0; + goto cleanup; + } + + code = krb5_get_credentials(ctx->k5c, KRB5_GC_CACHED, cred->ccache, &in_creds, &out_creds); if (code == KRB5_CC_NOTFOUND || code == KRB5_CC_NOT_KTYPE) { - krb5_principal tgs; - krb5_data *realm = krb5_princ_realm(ctx->k5c, in_creds.client); - - /* If we have a TGT for the client realm, can proceed to TGS-REQ. */ - code = krb5_build_principal_ext(ctx->k5c, - &tgs, - realm->length, - realm->data, - KRB5_TGS_NAME_SIZE, - KRB5_TGS_NAME, - realm->length, - realm->data, - NULL); - if (code != 0) - goto cleanup; - - in_creds.server = tgs; - - /* It would be nice if we could return KRB5KRB_AP_ERR_TKT_EXPIRED if - * the TGT is expired, for consistency with the krb5 mech. As it - * stands, we won't see the expired TGT and will return - * KRB5_CC_NOTFOUND. */ - code = krb5_get_credentials(ctx->k5c, KRB5_GC_CACHED, - cred->ccache, - &in_creds, &out_creds); - if (code == KRB5_CC_NOTFOUND && cred->password != NULL) { - *state = IAKERB_AS_REQ; - code = 0; - } else if (code == 0) { - *state = IAKERB_TGS_REQ; - krb5_free_creds(ctx->k5c, out_creds); - } - krb5_free_principal(ctx->k5c, tgs); + *state = cred->have_tgt ? IAKERB_TGS_REQ : IAKERB_AS_REQ; + code = 0; } else if (code == 0) { *state = IAKERB_AP_REQ; krb5_free_creds(ctx->k5c, out_creds); diff --git a/src/lib/gssapi/krb5/init_sec_context.c b/src/lib/gssapi/krb5/init_sec_context.c index d0eda5f3d..d4c987a09 100644 --- a/src/lib/gssapi/krb5/init_sec_context.c +++ b/src/lib/gssapi/krb5/init_sec_context.c @@ -194,30 +194,6 @@ static krb5_error_code get_credentials(context, cred, server, now, code = krb5_get_credentials(context, flags, cred->ccache, &in_creds, &result_creds); - if (code == KRB5_CC_NOTFOUND && cred->password != NULL && - !cred->iakerb_mech) { - krb5_creds tgt_creds; - - memset(&tgt_creds, 0, sizeof(tgt_creds)); - - /* No TGT in the ccache, but we can get one with the password. */ - code = krb5_get_init_creds_password(context, &tgt_creds, - in_creds.client, cred->password, - NULL, NULL, 0, NULL, NULL); - if (code) - goto cleanup; - - code = krb5_cc_store_cred(context, cred->ccache, &tgt_creds); - if (code) { - krb5_free_cred_contents(context, &tgt_creds); - goto cleanup; - } - cred->expire = tgt_creds.times.endtime; - krb5_free_cred_contents(context, &tgt_creds); - - code = krb5_get_credentials(context, flags, cred->ccache, - &in_creds, &result_creds); - } if (code) goto cleanup; diff --git a/src/lib/gssapi/krb5/rel_cred.c b/src/lib/gssapi/krb5/rel_cred.c index a69fb19b9..8db745086 100644 --- a/src/lib/gssapi/krb5/rel_cred.c +++ b/src/lib/gssapi/krb5/rel_cred.c @@ -57,6 +57,9 @@ krb5_gss_release_cred(minor_status, cred_handle) } else code1 = 0; + if (cred->client_keytab) + krb5_kt_close(context, cred->client_keytab); + #ifndef LEAN_CLIENT if (cred->keytab) code2 = krb5_kt_close(context, cred->keytab); diff --git a/src/lib/gssapi/krb5/val_cred.c b/src/lib/gssapi/krb5/val_cred.c index 46a9ae186..234cf69c0 100644 --- a/src/lib/gssapi/krb5/val_cred.c +++ b/src/lib/gssapi/krb5/val_cred.c @@ -44,7 +44,7 @@ krb5_gss_validate_cred_1(OM_uint32 *minor_status, gss_cred_id_t cred_handle, return GSS_S_FAILURE; } - if (cred->ccache) { + if (cred->ccache && cred->expire != 0) { if ((code = krb5_cc_get_principal(context, cred->ccache, &princ))) { k5_mutex_unlock(&cred->lock); *minor_status = code; diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index 271921278..4ddd9c91d 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -14,11 +14,17 @@ OBJS= t_accname.o t_ccselect.o t_imp_cred.o t_imp_name.o t_s4u.o \ all:: t_accname t_ccselect t_imp_cred t_imp_name t_s4u t_s4u2proxy_krb5 \ t_namingexts t_gssexts t_spnego t_saslname -check-pytests:: t_accname t_ccselect t_imp_cred t_spnego t_s4u2proxy_krb5 t_s4u +check-pytests:: t_accname t_ccselect t_imp_cred t_spnego t_s4u2proxy_krb5 \ + t_s4u ccinit ccrefresh $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_s4u.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_client_keytab.py $(PYTESTFLAGS) +ccinit: ccinit.o $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o ccinit ccinit.o $(KRB5_BASE_LIBS) +ccrefresh: ccrefresh.o $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o ccrefresh ccrefresh.o $(KRB5_BASE_LIBS) t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS) t_ccselect: t_ccselect.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) diff --git a/src/tests/gssapi/ccinit.c b/src/tests/gssapi/ccinit.c new file mode 100644 index 000000000..b06f04404 --- /dev/null +++ b/src/tests/gssapi/ccinit.c @@ -0,0 +1,72 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/gssapi/ccinit.c - Initialize an empty ccache */ +/* + * 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. + */ + +/* + * This program initializes a ccache without attempting to get credentials in + * it. It is used to test some finer points of gss_acquire_cred behavior. + */ + +#include "k5-int.h" + +static void +check(krb5_error_code code) +{ + if (code != 0) { + com_err("ccinit", code, NULL); + abort(); + } +} + +int +main(int argc, char **argv) +{ + const char *ccname, *princname; + krb5_context context; + krb5_principal princ; + krb5_ccache ccache; + + if (argc != 3) { + fprintf(stderr, "Usage: %s ccname princname\n", argv[0]); + return 1; + } + ccname = argv[1]; + princname = argv[2]; + + check(krb5_init_context(&context)); + check(krb5_parse_name(context, princname, &princ)); + check(krb5_cc_resolve(context, ccname, &ccache)); + check(krb5_cc_initialize(context, ccache, princ)); + krb5_cc_close(context, ccache); + krb5_free_principal(context, princ); + krb5_free_context(context); + return 0; +} diff --git a/src/tests/gssapi/ccrefresh.c b/src/tests/gssapi/ccrefresh.c new file mode 100644 index 000000000..bff299e46 --- /dev/null +++ b/src/tests/gssapi/ccrefresh.c @@ -0,0 +1,80 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/gssapi/ccrefresh.c - Get or set refresh time on a ccache */ +/* + * 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. + */ + +/* + * This program sets the refresh time of an existing ccache to 1, forcing a + * refresh. + */ + +#include "k5-int.h" + +static void +check(krb5_error_code code) +{ + if (code != 0) { + com_err("ccrefresh", code, NULL); + abort(); + } +} + +int +main(int argc, char **argv) +{ + const char *ccname, *value = NULL; + krb5_context context; + krb5_ccache ccache; + krb5_data d; + + if (argc != 2 && argc != 3) { + fprintf(stderr, "Usage: %s ccname [value]\n", argv[0]); + return 1; + } + ccname = argv[1]; + if (argc == 3) + value = argv[2]; + + check(krb5_init_context(&context)); + check(krb5_cc_resolve(context, ccname, &ccache)); + if (value != NULL) { + d = string2data((char *)value); + check(krb5_cc_set_config(context, ccache, NULL, KRB5_CONF_REFRESH_TIME, + &d)); + } else { + check(krb5_cc_get_config(context, ccache, NULL, KRB5_CONF_REFRESH_TIME, + &d)); + printf("%.*s\n", (int)d.length, d.data); + krb5_free_data_contents(context, &d); + } + krb5_cc_close(context, ccache); + krb5_free_context(context); + return 0; +} diff --git a/src/tests/gssapi/t_ccselect.py b/src/tests/gssapi/t_ccselect.py index 5350d9209..ce25dfb57 100644 --- a/src/tests/gssapi/t_ccselect.py +++ b/src/tests/gssapi/t_ccselect.py @@ -123,7 +123,7 @@ if output != (bob + '\n'): fail('bob not chosen via primary cache when no .k5identity line matches.') output = r1.run_as_client(['./t_ccselect', 'gss:bogus@' + hostname], expected_code=1) -if 'does not match desired' not in output: +if 'Can\'t find client principal noprinc' not in output: fail('Expected error not seen when k5identity selects bad principal.') success('GSSAPI credential selection tests') diff --git a/src/tests/gssapi/t_client_keytab.py b/src/tests/gssapi/t_client_keytab.py new file mode 100644 index 000000000..71cb89e78 --- /dev/null +++ b/src/tests/gssapi/t_client_keytab.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +from k5test import * + +# Set up a basic realm and a client keytab containing two user principals. +# Point HOME at realm.testdir for tests using .k5identity. +realm = K5Realm(get_creds=False) +bob = 'bob@' + realm.realm +gssserver = 'gss:host@' + hostname +realm.env_client['HOME'] = realm.testdir +realm.addprinc(bob, password('bob')) +realm.extract_keytab(realm.user_princ, realm.client_keytab) +realm.extract_keytab(bob, realm.client_keytab) + +# Test 1: no name/cache specified, pick first principal from client keytab +out = realm.run_as_client(['./t_ccselect', realm.host_princ]) +if realm.user_princ not in out: + fail('Authenticated as wrong principal') +realm.run_as_client([kdestroy]) + +# Test 2: no name/cache specified, pick principal from k5identity +k5idname = os.path.join(realm.testdir, '.k5identity') +k5id = open(k5idname, 'w') +k5id.write('%s service=host host=%s\n' % (bob, hostname)) +k5id.close() +out = realm.run_as_client(['./t_ccselect', gssserver]) +if bob not in out: + fail('Authenticated as wrong principal') +os.remove(k5idname) +realm.run_as_client([kdestroy]) + +# Test 3: no name/cache specified, default ccache has name but no creds +realm.run_as_client(['./ccinit', realm.ccache, bob]) +out = realm.run_as_client(['./t_ccselect', realm.host_princ]) +if bob not in out: + fail('Authenticated as wrong principal') +# Leave tickets for next test. + +# Test 4: name specified, non-collectable default cache doesn't match +out = realm.run_as_client(['./t_ccselect', realm.host_princ, realm.user_princ], + expected_code=1) +if 'Principal in credential cache does not match desired name' not in out: + fail('Expected error not seen') +realm.run_as_client([kdestroy]) + +# Test 5: name specified, nonexistent default cache +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +# Leave tickets for next test. + +# Test 6: name specified, matches default cache, time to refresh +realm.run_as_client(['./ccrefresh', realm.ccache, '1']) +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +out = realm.run_as_client(['./ccrefresh', realm.ccache]) +if int(out) < 1000: + fail('Credentials apparently not refreshed') +realm.run_as_client([kdestroy]) + +# Test 7: empty ccache specified, pick first principal from client keytab +realm.run_as_client(['./t_imp_cred', realm.host_princ]) +realm.klist(realm.user_princ) +realm.run_as_client([kdestroy]) + +# Test 8: ccache specified with name but no creds; name not in client keytab +realm.run_as_client(['./ccinit', realm.ccache, realm.host_princ]) +out = realm.run_as_client(['./t_imp_cred', realm.host_princ], expected_code=1) +if 'Credential cache is empty' not in out: + fail('Expected error not seen') +realm.run_as_client([kdestroy]) + +# Test 9: ccache specified with name but no creds; name in client keytab +realm.run_as_client(['./ccinit', realm.ccache, bob]) +realm.run_as_client(['./t_imp_cred', realm.host_princ]) +realm.klist(bob) +# Leave tickets for next test. + +# Test 10: ccache specified with creds, time to refresh +realm.run_as_client(['./ccrefresh', realm.ccache, '1']) +realm.run_as_client(['./t_imp_cred', realm.host_princ]) +realm.klist(bob) +out = realm.run_as_client(['./ccrefresh', realm.ccache]) +if int(out) < 1000: + fail('Credentials apparently not refreshed') +realm.run_as_client([kdestroy]) + +# Use a cache collection for the remaining tests. +ccdir = os.path.join(realm.testdir, 'cc') +ccname = 'DIR:' + ccdir +os.mkdir(ccdir) +realm.env_client['KRB5CCNAME'] = ccname + +# Test 11: name specified, matching cache in collection with no creds +bobcache = os.path.join(ccdir, 'tktbob') +realm.run_as_client(['./ccinit', bobcache, bob]) +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +# Leave tickets for next test. + +# Test 12: name specified, matching cache in collection, time to refresh +realm.run_as_client(['./ccrefresh', bobcache, '1']) +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +out = realm.run_as_client(['./ccrefresh', bobcache]) +if int(out) < 1000: + fail('Credentials apparently not refreshed') +realm.run_as_client([kdestroy, '-A']) + +# Test 13: name specified, collection has default for different principal +realm.kinit(realm.user_princ, password('user')) +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +out = realm.run_as_client([klist]) +if 'Default principal: %s\n' % realm.user_princ not in out: + fail('Default cache overwritten by acquire_cred') +realm.run_as_client([kdestroy, '-A']) + +# Test 14: name specified, collection has no default cache +out = realm.run_as_client(['./t_ccselect', realm.host_princ, bob]) +if bob not in out: + fail('Authenticated as wrong principal') +# Make sure the tickets we acquired didn't become the default +out = realm.run_as_client([klist], expected_code=1) +if 'No credentials cache found' not in out: + fail('Expected error not seen') +realm.run_as_client([kdestroy, '-A']) + +success('Client keytab tests') |
