gss_export_name_ call.
+Initiator credentials
+---------------------
+
+A GSSAPI client application uses gss_init_sec_context_ to establish a
+security context. The *initiator_cred_handle* parameter determines
+what tickets are used to establish the connection. An application can
+either pass **GSS_C_NO_CREDENTIAL** to use the default client
+credential, or it can use gss_acquire_cred_ beforehand to acquire an
+initiator credential. The call to gss_acquire_cred_ may include a
+*desired_name* parameter, or it may pass **GSS_C_NO_NAME** if it does
+not have a specific name preference.
+
+If the desired name for a krb5 initiator credential is a host-based
+name, it is converted to a principal name of the form
+``service/hostname`` in the local realm, where *hostname* is the local
+hostname if not specified. The hostname will be canonicalized using
+forward name resolution, and possibly also using reverse name
+resolution depending on the value of the **rdns** variable in
+:ref:`libdefaults`.
+
+If a desired name is specified in the call to gss_acquire_cred_, the
+krb5 mechanism will attempt to find existing tickets for that client
+principal name in the default credential cache or collection. If the
+default cache type does not support a collection, and the default
+cache contains credentials for a different principal than the desired
+name, a **GSS_S_CRED_UNAVAIL** error will be returned with a minor
+code indicating a mismatch.
+
+If no existing tickets are available for the desired name, but the
+name has an entry in the default client :ref:`keytab_definition`, the
+krb5 mechanism will acquire initial tickets for the name using the
+default client keytab.
+
+If no desired name is specified, credential acquisition will be
+deferred until the credential is used in a call to
+gss_init_sec_context_ or gss_inquire_cred_. If the call is to
+gss_init_sec_context_, the target name will be used to choose a client
+principal name using the credential cache selection facility. (This
+facility might, for instance, try to choose existing tickets for a
+client principal in the same realm as the target service). If there
+are no existing tickets for the chosen principal, but it is present in
+the default client keytab, the krb5 mechanism will acquire initial
+tickets using the keytab.
+
+If the target name cannot be used to select a client principal
+(because the credentials are used in a call to gss_inquire_cred_), or
+if the credential cache selection facility cannot choose a principal
+for it, the default credential cache will be selected if it exists and
+contains tickets.
+
+If the default credential cache does exist, but the default keytab
+does exist, the krb5 mechanism will try to acquire initial tickets for
+the first principal in the default client keytab.
+
+If the krb5 mechanism acquires initial tickets using the default
+client keytab, the resulting tickets will be stored in the default
+cache or collection, and will be refreshed by future calls to
+gss_acquire_cred_ as they approach their expire time.
+
+
Acceptor names
--------------
.. _gss_acquire_cred: http://tools.ietf.org/html/rfc2744.html#section-5.2
.. _gss_export_name: http://tools.ietf.org/html/rfc2744.html#section-5.13
.. _gss_import_name: http://tools.ietf.org/html/rfc2744.html#section-5.16
+.. _gss_init_sec_context: http://tools.ietf.org/html/rfc2744.html#section-5.19
+.. _gss_inquire_cred: http://tools.ietf.org/html/rfc2744.html#section-5.21
# 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)
# 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')
/* 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
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)
{
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) {
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;
}
#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
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;
}
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
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;
}
* 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;
}
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;
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;
/* 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;
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);
{
krb5_error_code code;
- if (cred->iakerb_mech == 0 || cred->password == NULL) {
+ if (cred->iakerb_mech == 0) {
code = EINVAL;
goto cleanup;
}
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;
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);
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);
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;
} 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);
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;
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)
--- /dev/null
+/* -*- 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;
+}
--- /dev/null
+/* -*- 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;
+}
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')
--- /dev/null
+#!/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')