diff -upNr krb5-1.5.1 krb5-1.6 --- krb5/src/kdc/dispatch.c +++ krb5/src/kdc/dispatch.c @@ -94,7 +94,7 @@ dispatch(krb5_data *pkt, const krb5_full * pointer. */ if (!(retval = setup_server_realm(as_req->server))) { - retval = process_as_req(as_req, from, response); + retval = process_as_req(as_req, pkt, from, response); } krb5_free_kdc_req(kdc_context, as_req); } --- krb5/src/kdc/Makefile.in +++ krb5/src/kdc/Makefile.in @@ -13,7 +13,7 @@ PROG_LIBPATH=-L$(TOPLIBD) $(KRB4_LIBPATH KDB5_LIB_DEPS=$(DL_LIB) $(THREAD_LINKOPTS) PROG_RPATH=$(KRB5_LIBDIR) FAKEKA=@FAKEKA@ -DEFS= +DEFS=-DLIBDIR=\"$(KRB5_LIBDIR)\" all:: krb5kdc rtest $(FAKEKA) --- krb5/src/kdc/do_as_req.c +++ krb5/src/kdc/do_as_req.c @@ -50,8 +50,8 @@ static krb5_error_code prepare_error_as /*ARGSUSED*/ krb5_error_code -process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from, - krb5_data **response) +process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, + const krb5_fulladdr *from, krb5_data **response) { krb5_db_entry client, server; krb5_kdc_rep reply; @@ -78,6 +78,7 @@ process_as_req(krb5_kdc_req *request, co char ktypestr[128]; char rep_etypestr[128]; char fromstringbuf[70]; + void *pa_context = NULL; ticket_reply.enc_part.ciphertext.data = 0; e_data.data = 0; @@ -260,7 +261,8 @@ process_as_req(krb5_kdc_req *request, co * Check the preauthentication if it is there. */ if (request->padata) { - errcode = check_padata(kdc_context, &client, request, &enc_tkt_reply); + errcode = check_padata(kdc_context, &client, req_pkt, request, + &enc_tkt_reply, &pa_context, &e_data); if (errcode) { #ifdef KRBCONF_KDC_MODIFIES_KDB /* @@ -381,8 +383,8 @@ process_as_req(krb5_kdc_req *request, co reply_encpart.caddrs = enc_tkt_reply.caddrs; /* Fetch the padata info to be returned */ - errcode = return_padata(kdc_context, &client, request, &reply, client_key, - &encrypting_key); + errcode = return_padata(kdc_context, &client, req_pkt, request, + &reply, client_key, &encrypting_key, &pa_context); if (errcode) { status = "KDC_RETURN_PADATA"; goto errout; @@ -427,8 +429,11 @@ process_as_req(krb5_kdc_req *request, co #endif /* KRBCONF_KDC_MODIFIES_KDB */ errout: + if (pa_context) + free_padata_context(kdc_context, &pa_context); + if (status) { - char * emsg = 0; + const char * emsg = 0; if (errcode) emsg = krb5_get_error_message (kdc_context, errcode); --- krb5/src/kdc/kdc_preauth.c +++ krb5/src/kdc/kdc_preauth.c @@ -60,6 +60,13 @@ #include #include +#include "../include/krb5/preauth_plugin.h" + +#if TARGET_OS_MAC +static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/preauth", NULL }; /* should be a list */ +#else +static const char *objdirs[] = { LIBDIR "/krb5/plugins/preauth", NULL }; +#endif /* XXX This is ugly and should be in a header file somewhere */ #ifndef KRB5INT_DES_TYPES_DEFINED @@ -72,44 +79,76 @@ extern int mit_des_is_weak_key (mit_des_ typedef krb5_error_code (*verify_proc) (krb5_context, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, - krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data); typedef krb5_error_code (*edata_proc) (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, krb5_pa_data *data); typedef krb5_error_code (*return_proc) (krb5_context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa); + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context); + +typedef krb5_error_code (*freepa_proc) + (krb5_context, void *pa_module_context, void **pa_request_context); + +typedef krb5_error_code (*init_proc) + (krb5_context, void **); +typedef void (*fini_proc) + (krb5_context, void *); typedef struct _krb5_preauth_systems { - char * name; + const char *name; int type; int flags; + void *plugin_context; + init_proc init; + fini_proc fini; edata_proc get_edata; verify_proc verify_padata; return_proc return_padata; + freepa_proc free_pa_request_context; } krb5_preauth_systems; static krb5_error_code verify_enc_timestamp (krb5_context, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, - krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + void **pa_request_context, + krb5_data **e_data); static krb5_error_code get_etype_info (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, krb5_pa_data *data); static krb5_error_code get_etype_info2(krb5_context context, krb5_kdc_req *request, - krb5_db_entry *client, krb5_db_entry *server, - krb5_pa_data *pa_data); + krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + krb5_pa_data *pa_data); static krb5_error_code etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, @@ -122,58 +161,76 @@ etype_info_as_rep_helper(krb5_context co static krb5_error_code return_etype_info(krb5_context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa); + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + void **pa_request_context); static krb5_error_code return_etype_info2(krb5_context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa); + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + void **pa_request_context); static krb5_error_code return_pw_salt (krb5_context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa); + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + void **pa_request_context); /* SAM preauth support */ static krb5_error_code verify_sam_response (krb5_context, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, - krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data); static krb5_error_code get_sam_edata (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, krb5_pa_data *data); static krb5_error_code return_sam_data (krb5_context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa); -/* - * Preauth property flags - */ -#define PA_HARDWARE 0x00000001 -#define PA_REQUIRED 0x00000002 -#define PA_SUFFICIENT 0x00000004 - /* Not really a padata, so don't include it in the etype list*/ -#define PA_PSEUDO 0x00000008 + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context); -static krb5_preauth_systems preauth_systems[] = { +static krb5_preauth_systems static_preauth_systems[] = { { "timestamp", KRB5_PADATA_ENC_TIMESTAMP, 0, + NULL, + NULL, + NULL, 0, verify_enc_timestamp, 0 @@ -182,6 +239,9 @@ static krb5_preauth_systems preauth_syst "etype-info", KRB5_PADATA_ETYPE_INFO, 0, + NULL, + NULL, + NULL, get_etype_info, 0, return_etype_info @@ -190,6 +250,9 @@ static krb5_preauth_systems preauth_syst "etype-info2", KRB5_PADATA_ETYPE_INFO2, 0, + NULL, + NULL, + NULL, get_etype_info2, 0, return_etype_info2 @@ -198,6 +261,9 @@ static krb5_preauth_systems preauth_syst "pw-salt", KRB5_PADATA_PW_SALT, PA_PSEUDO, /* Don't include this in the error list */ + NULL, + NULL, + NULL, 0, 0, return_pw_salt @@ -206,6 +272,9 @@ static krb5_preauth_systems preauth_syst "sam-response", KRB5_PADATA_SAM_RESPONSE, 0, + NULL, + NULL, + NULL, 0, verify_sam_response, return_sam_data @@ -214,6 +283,9 @@ static krb5_preauth_systems preauth_syst "sam-challenge", KRB5_PADATA_SAM_CHALLENGE, PA_HARDWARE, /* causes get_preauth_hint_list to use this */ + NULL, + NULL, + NULL, get_sam_edata, 0, 0 @@ -221,13 +293,378 @@ static krb5_preauth_systems preauth_syst { "[end]", -1,} }; -#define MAX_PREAUTH_SYSTEMS (sizeof(preauth_systems)/sizeof(preauth_systems[0])) +static krb5_preauth_systems *preauth_systems; +static int n_preauth_systems; +static struct plugin_dir_handle preauth_plugins; + +krb5_error_code +load_preauth_plugins(krb5_context context) +{ + struct errinfo err; + void **preauth_plugins_ftables; + struct krb5plugin_preauth_server_ftable_v0 *ftable; + int module_count, i, j, k; + void *plugin_context; + init_proc server_init_proc = NULL; + + memset(&err, 0, sizeof(err)); + + /* Attempt to load all of the preauth plugins we can find. */ + PLUGIN_DIR_INIT(&preauth_plugins); + if (PLUGIN_DIR_OPEN(&preauth_plugins) == 0) { + if (krb5int_open_plugin_dirs(objdirs, NULL, + &preauth_plugins, &err) != 0) { + return KRB5_PLUGIN_NO_HANDLE; + } + } + + /* Get the method tables provided by the loaded plugins. */ + preauth_plugins_ftables = NULL; + if (krb5int_get_plugin_dir_data(&preauth_plugins, + "preauthentication_server_0_backport_1_6", + &preauth_plugins_ftables, &err) != 0) { + return KRB5_PLUGIN_NO_HANDLE; + } + + /* Count the valid modules. */ + module_count = sizeof(static_preauth_systems) + / sizeof(static_preauth_systems[0]); + if (preauth_plugins_ftables != NULL) { + for (i = 0; preauth_plugins_ftables[i] != NULL; i++) { + ftable = preauth_plugins_ftables[i]; + if ((ftable->flags_proc == NULL) && + (ftable->edata_proc == NULL) && + (ftable->verify_proc == NULL) && + (ftable->return_proc == NULL)) { + continue; + } + for (j = 0; + ftable->pa_type_list != NULL && + ftable->pa_type_list[j] > 0; + j++) { + module_count++; + } + } + } + + /* Build the complete list of supported preauthentication options, and + * leave room for a terminator entry. */ + preauth_systems = malloc(sizeof(krb5_preauth_systems) * (module_count + 1)); + if (preauth_systems == NULL) { + krb5int_free_plugin_dir_data(preauth_plugins_ftables); + return ENOMEM; + } + + /* Add the locally-supplied mechanisms to the dynamic list first. */ + for (i = 0, k = 0; + i < sizeof(static_preauth_systems) / sizeof(static_preauth_systems[0]); + i++) { + if (static_preauth_systems[i].type == -1) + break; + preauth_systems[k] = static_preauth_systems[i]; + /* Try to initialize the preauth system. If it fails, we'll remove it + * from the list of systems we'll be using. */ + plugin_context = NULL; + server_init_proc = static_preauth_systems[i].init; + if ((server_init_proc != NULL) && + ((*server_init_proc)(context, &plugin_context) != 0)) { + memset(&preauth_systems[k], 0, sizeof(preauth_systems[k])); + continue; + } + preauth_systems[k].plugin_context = plugin_context; + k++; + } + + /* Now add the dynamically-loaded mechanisms to the list. */ + if (preauth_plugins_ftables != NULL) { + for (i = 0; preauth_plugins_ftables[i] != NULL; i++) { + ftable = preauth_plugins_ftables[i]; + if ((ftable->flags_proc == NULL) && + (ftable->edata_proc == NULL) && + (ftable->verify_proc == NULL) && + (ftable->return_proc == NULL)) { + continue; + } + plugin_context = NULL; + for (j = 0; + ftable->pa_type_list != NULL && + ftable->pa_type_list[j] > 0; + j++) { + /* Try to initialize the plugin. If it fails, we'll remove it + * from the list of modules we'll be using. */ + if (j == 0) { + server_init_proc = ftable->init_proc; + if (server_init_proc != NULL) { + krb5_error_code initerr; + initerr = (*server_init_proc)(context, &plugin_context); + if (initerr) { + const char *emsg; + emsg = krb5_get_error_message(context, initerr); + if (emsg) { + krb5_klog_syslog(LOG_ERR, + "preauth %s failed to initialize: %s", + ftable->name, emsg); + krb5_free_error_message(context, emsg); + } + memset(&preauth_systems[k], 0, sizeof(preauth_systems[k])); + + break; /* skip all modules in this plugin */ + } + } + } + preauth_systems[k].name = ftable->name; + preauth_systems[k].type = ftable->pa_type_list[j]; + if (ftable->flags_proc != NULL) + preauth_systems[k].flags = ftable->flags_proc(context, preauth_systems[k].type); + else + preauth_systems[k].flags = 0; + preauth_systems[k].plugin_context = plugin_context; + preauth_systems[k].init = server_init_proc; + /* Only call fini once for each plugin */ + if (j == 0) + preauth_systems[k].fini = ftable->fini_proc; + else + preauth_systems[k].fini = NULL; + preauth_systems[k].get_edata = ftable->edata_proc; + preauth_systems[k].verify_padata = ftable->verify_proc; + preauth_systems[k].return_padata = ftable->return_proc; + preauth_systems[k].free_pa_request_context = + ftable->freepa_reqcontext_proc; + k++; + } + } + krb5int_free_plugin_dir_data(preauth_plugins_ftables); + } + n_preauth_systems = k; + /* Add the end-of-list marker. */ + preauth_systems[k].name = "[end]"; + preauth_systems[k].type = -1; + return 0; +} + +krb5_error_code +unload_preauth_plugins(krb5_context context) +{ + int i; + if (preauth_systems != NULL) { + for (i = 0; i < n_preauth_systems; i++) { + if (preauth_systems[i].fini != NULL) { + (*preauth_systems[i].fini)(context, + preauth_systems[i].plugin_context); + } + memset(&preauth_systems[i], 0, sizeof(preauth_systems[i])); + } + free(preauth_systems); + preauth_systems = NULL; + n_preauth_systems = 0; + krb5int_close_plugin_dirs(&preauth_plugins); + } + return 0; +} + +/* + * The make_padata_context() function creates a space for storing any context + * information which will be needed by return_padata() later. Each preauth + * type gets a context storage location of its own. + */ +struct request_pa_context { + int n_contexts; + struct { + krb5_preauth_systems *pa_system; + void *pa_context; + } *contexts; +}; + +static krb5_error_code +make_padata_context(krb5_context context, void **padata_context) +{ + int i; + struct request_pa_context *ret; + + ret = malloc(sizeof(*ret)); + if (ret == NULL) { + return ENOMEM; + } + + ret->n_contexts = n_preauth_systems; + ret->contexts = malloc(sizeof(ret->contexts[0]) * ret->n_contexts); + if (ret->contexts == NULL) { + free(ret); + return ENOMEM; + } + + memset(ret->contexts, 0, sizeof(ret->contexts[0]) * ret->n_contexts); + + for (i = 0; i < ret->n_contexts; i++) { + ret->contexts[i].pa_system = &preauth_systems[i]; + ret->contexts[i].pa_context = NULL; + } + + *padata_context = ret; + + return 0; +} + +/* + * The free_padata_context function frees any context information pointers + * which the check_padata() function created but which weren't already cleaned + * up by return_padata(). + */ +krb5_error_code +free_padata_context(krb5_context kcontext, void **padata_context) +{ + struct request_pa_context *context; + krb5_preauth_systems *preauth_system; + void **pctx, *mctx; + int i; + + if (padata_context == NULL) + return 0; + + context = *padata_context; + + for (i = 0; i < context->n_contexts; i++) { + if (context->contexts[i].pa_context != NULL) { + preauth_system = context->contexts[i].pa_system; + mctx = preauth_system->plugin_context; + if (preauth_system->free_pa_request_context != NULL) { + pctx = &context->contexts[i].pa_context; + (*preauth_system->free_pa_request_context)(kcontext, mctx, + pctx); + } + context->contexts[i].pa_context = NULL; + } + } + + free(context->contexts); + free(context); + + return 0; +} + +/* Retrieve a specified tl_data item from the given entry, and return its + * contents in a new krb5_data, which must be freed by the caller. */ +static krb5_error_code +get_entry_tl_data(krb5_context context, krb5_db_entry *entry, + krb5_int16 tl_data_type, krb5_data **result) +{ + krb5_tl_data *tl; + for (tl = entry->tl_data; tl != NULL; tl = tl->tl_data_next) { + if (tl->tl_data_type == tl_data_type) { + *result = malloc(sizeof(krb5_data)); + if (*result == NULL) { + return ENOMEM; + } + (*result)->magic = KV5M_DATA; + (*result)->data = malloc(tl->tl_data_length); + if ((*result)->data == NULL) { + free(*result); + *result = NULL; + return ENOMEM; + } + memcpy((*result)->data, tl->tl_data_contents, tl->tl_data_length); + return 0; + } + } + return ENOENT; +} + +/* + * Retrieve a specific piece of information pertaining to the entry or the + * request and return it in a new krb5_data item which the caller must free. + * + * This may require massaging data into a contrived format, but it will + * hopefully keep us from having to reveal library-internal functions to + * modules. + */ +static krb5_error_code +get_entry_data(krb5_context context, + krb5_kdc_req *request, krb5_db_entry *entry, + krb5_int32 type, + krb5_data **result) +{ + int i, k; + krb5_data *ret; + krb5_deltat *delta; + krb5_keyblock *keys; + krb5_key_data *entry_key; + + switch (type) { + case krb5plugin_preauth_entry_request_certificate: + return get_entry_tl_data(context, entry, + KRB5_TL_USER_CERTIFICATE, result); + break; + case krb5plugin_preauth_entry_max_time_skew: + ret = malloc(sizeof(krb5_data)); + if (ret == NULL) + return ENOMEM; + delta = malloc(sizeof(krb5_deltat)); + if (delta == NULL) { + free(ret); + return ENOMEM; + } + *delta = context->clockskew; + ret->data = (char *) delta; + ret->length = sizeof(*delta); + *result = ret; + return 0; + break; + case krb5plugin_preauth_keys: + ret = malloc(sizeof(krb5_data)); + if (ret == NULL) + return ENOMEM; + keys = malloc(sizeof(krb5_keyblock) * (request->nktypes + 1)); + if (keys == NULL) { + free(ret); + return ENOMEM; + } + ret->data = (char *) keys; + ret->length = sizeof(krb5_keyblock) * (request->nktypes + 1); + memset(ret->data, 0, ret->length); + k = 0; + for (i = 0; i < request->nktypes; i++) { + entry_key = NULL; + if (krb5_dbe_find_enctype(context, entry, request->ktype[i], + -1, 0, &entry_key) != 0) + continue; + if (krb5_dbekd_decrypt_key_data(context, &master_keyblock, + entry_key, &keys[k], NULL) != 0) { + if (keys[k].contents != NULL) + krb5_free_keyblock_contents(context, &keys[k]); + memset(&keys[k], 0, sizeof(keys[k])); + continue; + } + k++; + } + if (k > 0) { + *result = ret; + return 0; + } else { + free(keys); + free(ret); + } + break; + case krb5plugin_preauth_request_body: + ret = NULL; + encode_krb5_kdc_req_body(request, &ret); + if (ret != NULL) { + *result = ret; + return 0; + } + return ASN1_PARSE_ERROR; + break; + default: + break; + } + return ENOENT; +} static krb5_error_code find_pa_system(int type, krb5_preauth_systems **preauth) { - krb5_preauth_systems *ap = preauth_systems; - + krb5_preauth_systems *ap; + + ap = preauth_systems ? preauth_systems : static_preauth_systems; while ((ap->type != -1) && (ap->type != type)) ap++; if (ap->type == -1) @@ -236,6 +673,113 @@ find_pa_system(int type, krb5_preauth_sy return 0; } +static krb5_error_code +find_pa_context(krb5_preauth_systems *pa_sys, + struct request_pa_context *context, + void ***pa_context) +{ + int i; + + *pa_context = 0; + + if (context == NULL) + return KRB5KRB_ERR_GENERIC; + + for (i = 0; i < context->n_contexts; i++) { + if (context->contexts[i].pa_system == pa_sys) { + *pa_context = &context->contexts[i].pa_context; + return 0; + } + } + + return KRB5KRB_ERR_GENERIC; +} + +/* + * Create a list of indices into the preauth_systems array, sorted by order of + * preference. + */ +static krb5_boolean +pa_list_includes(krb5_pa_data **pa_data, krb5_preauthtype pa_type) +{ + while (*pa_data != NULL) { + if ((*pa_data)->pa_type == pa_type) + return TRUE; + pa_data++; + } + return FALSE; +} +static void +sort_pa_order(krb5_context context, krb5_kdc_req *request, int *pa_order) +{ + int i, j, k, n_repliers, n_key_replacers; + + /* First, set up the default order. */ + i = 0; + for (j = 0; j < n_preauth_systems; j++) { + if (preauth_systems[j].return_padata != NULL) + pa_order[i++] = j; + } + n_repliers = i; + pa_order[n_repliers] = -1; + + /* Reorder so that PA_REPLACES_KEY modules are listed first. */ + for (i = 0; i < n_repliers; i++) { + /* If this module replaces the key, then it's okay to leave it where it + * is in the order. */ + if (preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY) + continue; + /* If not, search for a module which does, and swap in the first one we + * find. */ + for (j = i + 1; j < n_repliers; j++) { + if (preauth_systems[pa_order[j]].flags & PA_REPLACES_KEY) { + k = pa_order[j]; + pa_order[j] = pa_order[i]; + pa_order[i] = k; + break; + } + } + } + + if (request->padata != NULL) { + /* Now reorder the subset of modules which replace the key, + * bubbling those which handle pa_data types provided by the + * client ahead of the others. */ + for (i = 0; preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY; i++) { + continue; + } + n_key_replacers = i; + for (i = 0; i < n_key_replacers; i++) { + if (pa_list_includes(request->padata, + preauth_systems[pa_order[i]].type)) + continue; + for (j = i + 1; j < n_key_replacers; j++) { + if (pa_list_includes(request->padata, + preauth_systems[pa_order[j]].type)) { + k = pa_order[j]; + pa_order[j] = pa_order[i]; + pa_order[i] = k; + break; + } + } + } + } +#ifdef DEBUG + krb5_klog_syslog(LOG_DEBUG, "original preauth mechanism list:"); + for (i = 0; i < n_preauth_systems; i++) { + if (preauth_systems[i].return_padata != NULL) + krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", preauth_systems[i].name, + preauth_systems[i].type); + } + krb5_klog_syslog(LOG_DEBUG, "sorted preauth mechanism list:"); + for (i = 0; pa_order[i] != -1; i++) { + krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", + preauth_systems[pa_order[i]].name, + preauth_systems[pa_order[i]].type); + } +#endif +} + const char *missing_required_preauth(krb5_db_entry *client, krb5_db_entry *server, krb5_enc_tkt_part *enc_tkt_reply) @@ -287,10 +831,10 @@ void get_preauth_hint_list(krb5_kdc_req e_data->data = 0; hw_only = isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH); - pa_data = malloc(sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1)); + pa_data = malloc(sizeof(krb5_pa_data *) * (n_preauth_systems+1)); if (pa_data == 0) return; - memset(pa_data, 0, sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1)); + memset(pa_data, 0, sizeof(krb5_pa_data *) * (n_preauth_systems+1)); pa = pa_data; for (ap = preauth_systems; ap->type != -1; ap++) { @@ -305,7 +849,8 @@ void get_preauth_hint_list(krb5_kdc_req (*pa)->magic = KV5M_PA_DATA; (*pa)->pa_type = ap->type; if (ap->get_edata) { - retval = (ap->get_edata)(kdc_context, request, client, server, *pa); + retval = (ap->get_edata)(kdc_context, request, client, server, + get_entry_data, ap->plugin_context, *pa); if (retval) { /* just failed on this type, continue */ free(*pa); @@ -335,23 +880,33 @@ errout: /* * This routine is called to verify the preauthentication information * for a V5 request. - * + * * Returns 0 if the pre-authentication is valid, non-zero to indicate * an error code of some sort. */ krb5_error_code -check_padata (krb5_context context, krb5_db_entry *client, - krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply) +check_padata (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, + krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, + void **padata_context, krb5_data *e_data) { krb5_error_code retval = 0; krb5_pa_data **padata; krb5_preauth_systems *pa_sys; - int pa_ok = 0, pa_found = 0; + void **pa_context; + krb5_data *pa_e_data = NULL, *tmp_e_data = NULL; + int pa_ok = 0, pa_found = 0; + krb5_error_code saved_retval = 0; + int use_saved_retval = 0; + const char *emsg; if (request->padata == 0) return 0; + if (make_padata_context(context, padata_context) != 0) { + return KRB5KRB_ERR_GENERIC; + } + #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, "checking padata"); #endif @@ -361,52 +916,128 @@ check_padata (krb5_context context, krb5 #endif if (find_pa_system((*padata)->pa_type, &pa_sys)) continue; + if (find_pa_context(pa_sys, *padata_context, &pa_context)) + continue; #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, ".. pa_type %s", pa_sys->name); #endif if (pa_sys->verify_padata == 0) continue; pa_found++; - retval = pa_sys->verify_padata(context, client, request, - enc_tkt_reply, *padata); + retval = pa_sys->verify_padata(context, client, req_pkt, request, + enc_tkt_reply, *padata, + get_entry_data, pa_sys->plugin_context, + pa_context, &tmp_e_data); if (retval) { - char * emsg = krb5_get_error_message (context, retval); + emsg = krb5_get_error_message (context, retval); krb5_klog_syslog (LOG_INFO, "preauth (%s) verify failure: %s", pa_sys->name, emsg); krb5_free_error_message (context, emsg); if (pa_sys->flags & PA_REQUIRED) { + /* free up any previous edata we might have been saving */ + if (pa_e_data != NULL) + krb5_free_data(context, pa_e_data); + pa_e_data = tmp_e_data; + tmp_e_data = NULL; + use_saved_retval = 0; /* Make sure we use the current retval */ pa_ok = 0; break; } + /* + * We'll return edata from either the first PA_REQUIRED module + * that fails, or the first non-PA_REQUIRED module that fails. + * Hang on to edata from the first non-PA_REQUIRED module. + * If we've already got one saved, simply discard this one. + */ + if (tmp_e_data != NULL) { + if (pa_e_data == NULL) { + /* save the first error code and e-data */ + pa_e_data = tmp_e_data; + tmp_e_data = NULL; + saved_retval = retval; + use_saved_retval = 1; + } else { + /* discard this extra e-data from non-PA_REQUIRED module */ + krb5_free_data(context, tmp_e_data); + tmp_e_data = NULL; + } + } } else { #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, ".. .. ok"); #endif + /* Ignore any edata returned on success */ + if (tmp_e_data != NULL) { + krb5_free_data(context, tmp_e_data); + tmp_e_data = NULL; + } pa_ok = 1; - if (pa_sys->flags & PA_SUFFICIENT) + if (pa_sys->flags & PA_SUFFICIENT) break; } } + + /* Don't bother copying and returning e-data on success */ + if (pa_ok && pa_e_data != NULL) { + krb5_free_data(context, pa_e_data); + pa_e_data = NULL; + } + /* Return any e-data from the preauth that caused us to exit the loop */ + if (pa_e_data != NULL) { + e_data->data = malloc(pa_e_data->length); + if (e_data->data == NULL) { + krb5_free_data(context, pa_e_data); + return KRB5KRB_ERR_GENERIC; + } + memcpy(e_data->data, pa_e_data->data, pa_e_data->length); + e_data->length = pa_e_data->length; + krb5_free_data(context, pa_e_data); + pa_e_data = NULL; + if (use_saved_retval != 0) + retval = saved_retval; + } + if (pa_ok) return 0; /* pa system was not found, but principal doesn't require preauth */ if (!pa_found && - !isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && - !isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH)) + !isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && + !isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH)) return 0; if (!pa_found) { - char *emsg = krb5_get_error_message(context, retval); + emsg = krb5_get_error_message(context, retval); krb5_klog_syslog (LOG_INFO, "no valid preauth type found: %s", emsg); krb5_free_error_message(context, emsg); } -/* The following switch statement allows us - * to return some preauth system errors back to the client. - */ - switch(retval) { - case KRB5KRB_AP_ERR_BAD_INTEGRITY: + /* The following switch statement allows us + * to return some preauth system errors back to the client. + */ + switch(retval) { + case KRB5KRB_AP_ERR_BAD_INTEGRITY: case KRB5KRB_AP_ERR_SKEW: + case KRB5KDC_ERR_ETYPE_NOSUPP: + /* rfc 4556 */ + case KRB5KDC_ERR_CLIENT_NOT_TRUSTED: + case KRB5KDC_ERR_INVALID_SIG: + case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED: + case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE: + case KRB5KDC_ERR_INVALID_CERTIFICATE: + case KRB5KDC_ERR_REVOKED_CERTIFICATE: + case KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN: + case KRB5KDC_ERR_CLIENT_NAME_MISMATCH: + case KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE: + case KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED: + case KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED: + case KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED: + case KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED: + /* earlier drafts of what became rfc 4556 */ + case KRB5KDC_ERR_CERTIFICATE_MISMATCH: + case KRB5KDC_ERR_KDC_NOT_TRUSTED: + case KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE: + /* This value is shared with KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED. */ + /* case KRB5KDC_ERR_KEY_TOO_WEAK: */ return retval; default: return KRB5KDC_ERR_PREAUTH_FAILED; @@ -418,9 +1049,10 @@ check_padata (krb5_context context, krb5 * structures which should be returned by the KDC to the client */ krb5_error_code -return_padata(krb5_context context, krb5_db_entry *client, +return_padata(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, - krb5_key_data *client_key, krb5_keyblock *encrypting_key) + krb5_key_data *client_key, krb5_keyblock *encrypting_key, + void **padata_context) { krb5_error_code retval; krb5_pa_data ** padata; @@ -428,7 +1060,15 @@ return_padata(krb5_context context, krb5 krb5_pa_data ** send_pa; krb5_pa_data * pa = 0; krb5_preauth_systems * ap; + int * pa_order; + int * pa_type; int size = 0; + void ** pa_context; + krb5_boolean key_modified; + krb5_keyblock original_key; + if ((!*padata_context)&& (make_padata_context(context, padata_context) != 0)) { + return KRB5KRB_ERR_GENERIC; + } for (ap = preauth_systems; ap->type != -1; ap++) { if (ap->return_padata) @@ -437,13 +1077,42 @@ return_padata(krb5_context context, krb5 if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL) return ENOMEM; + if ((pa_order = malloc((size+1) * sizeof(int))) == NULL) { + free(send_pa_list); + return ENOMEM; + } + sort_pa_order(context, request, pa_order); + + retval = krb5_copy_keyblock_contents(context, encrypting_key, + &original_key); + if (retval) { + free(send_pa_list); + free(pa_order); + return retval; + } + key_modified = FALSE; send_pa = send_pa_list; *send_pa = 0; - - for (ap = preauth_systems; ap->type != -1; ap++) { + + for (pa_type = pa_order; *pa_type != -1; pa_type++) { + ap = &preauth_systems[*pa_type]; + if (!key_modified) + if (original_key.enctype != encrypting_key->enctype) + key_modified = TRUE; + if (!key_modified) + if (original_key.length != encrypting_key->length) + key_modified = TRUE; + if (!key_modified) + if (memcmp(original_key.contents, encrypting_key->contents, + original_key.length) != 0) + key_modified = TRUE; + if (key_modified && (ap->flags & PA_REPLACES_KEY)) + continue; if (ap->return_padata == 0) continue; + if (find_pa_context(ap, *padata_context, &pa_context)) + continue; pa = 0; if (request->padata) { for (padata = request->padata; *padata; padata++) { @@ -453,9 +1122,12 @@ return_padata(krb5_context context, krb5 } } } - if ((retval = ap->return_padata(context, pa, client, request, reply, - client_key, encrypting_key, send_pa))) + if ((retval = ap->return_padata(context, pa, client, req_pkt, request, reply, + client_key, encrypting_key, send_pa, + get_entry_data, ap->plugin_context, + pa_context))) { goto cleanup; + } if (*send_pa) send_pa++; @@ -470,6 +1142,8 @@ return_padata(krb5_context context, krb5 } cleanup: + krb5_free_keyblock_contents(context, &original_key); + free(pa_order); if (send_pa_list) krb5_free_pa_data(context, send_pa_list); return (retval); @@ -508,8 +1182,13 @@ request_contains_enctype (krb5_context c static krb5_error_code verify_enc_timestamp(krb5_context context, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, - krb5_pa_data *pa) + krb5_pa_data *pa, + preauth_get_entry_data_proc ets_get_entry_data, + void *pa_system_context, + void **pa_request_context, + krb5_data **e_data) { krb5_pa_enc_ts * pa_enc = 0; krb5_error_code retval; @@ -749,6 +1428,8 @@ cleanup: static krb5_error_code get_etype_info(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc etype_get_entry_data, + void *pa_system_context, krb5_pa_data *pa_data) { int i; @@ -764,6 +1445,8 @@ get_etype_info(krb5_context context, krb static krb5_error_code get_etype_info2(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc etype_get_entry_data, + void *pa_system_context, krb5_pa_data *pa_data) { return etype_info_helper( context, request, client, server, pa_data, 1); @@ -849,10 +1532,14 @@ etype_info_as_rep_helper(krb5_context co static krb5_error_code return_etype_info2(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa) + krb5_pa_data **send_pa, + preauth_get_entry_data_proc etype_get_entry_data, + void *pa_system_context, + void **pa_request_context) { return etype_info_as_rep_helper(context, padata, client, request, reply, client_key, encrypting_key, send_pa, 1); @@ -862,10 +1549,14 @@ return_etype_info2(krb5_context context, static krb5_error_code return_etype_info(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, - krb5_pa_data **send_pa) + krb5_pa_data **send_pa, + preauth_get_entry_data_proc etypeget_entry_data, + void *pa_system_context, + void **pa_request_context) { return etype_info_as_rep_helper(context, padata, client, request, reply, client_key, encrypting_key, send_pa, 0); @@ -873,9 +1564,12 @@ return_etype_info(krb5_context context, static krb5_error_code return_pw_salt(krb5_context context, krb5_pa_data *in_padata, - krb5_db_entry *client, krb5_kdc_req *request, + krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, - krb5_keyblock *encrypting_key, krb5_pa_data **send_pa) + krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, + preauth_get_entry_data_proc etype_get_entry_data, + void *pa_system_context, + void **pa_request_context) { krb5_error_code retval; krb5_pa_data * padata; @@ -960,9 +1654,12 @@ cleanup: static krb5_error_code return_sam_data(krb5_context context, krb5_pa_data *in_padata, - krb5_db_entry *client, krb5_kdc_req *request, + krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, - krb5_keyblock *encrypting_key, krb5_pa_data **send_pa) + krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, + preauth_get_entry_data_proc sam_get_entry_data, + void *pa_system_context, + void **pa_request_context) { krb5_error_code retval; krb5_data scratch; @@ -1101,7 +1798,8 @@ static struct { static krb5_error_code get_sam_edata(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, - krb5_pa_data *pa_data) + preauth_get_entry_data_proc sam_get_entry_data, + void *pa_system_context, krb5_pa_data *pa_data) { krb5_error_code retval; krb5_sam_challenge sc; @@ -1472,8 +2170,13 @@ cleanup: static krb5_error_code verify_sam_response(krb5_context context, krb5_db_entry *client, + krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, - krb5_pa_data *pa) + krb5_pa_data *pa, + preauth_get_entry_data_proc sam_get_entry_data, + void *pa_system_context, + void **pa_request_context, + krb5_data **e_data) { krb5_error_code retval; krb5_data scratch; --- krb5/src/kdc/main.c +++ krb5/src/kdc/main.c @@ -382,10 +382,13 @@ setup_signal_handlers(void) (void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL); s_action.sa_handler = request_hup; (void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL); + s_action.sa_handler = SIG_IGN; + (void) sigaction(SIGPIPE, &s_action, (struct sigaction *) NULL); #else /* POSIX_SIGNALS */ signal(SIGINT, request_exit); signal(SIGTERM, request_exit); signal(SIGHUP, request_hup); + signal(SIGPIPE, SIG_IGN); #endif /* POSIX_SIGNALS */ return; @@ -711,6 +714,8 @@ int main(int argc, char **argv) setup_signal_handlers(); + load_preauth_plugins(kcontext); + retval = setup_sam(); if (retval) { com_err(argv[0], retval, "while initializing SAM"); @@ -738,6 +743,7 @@ int main(int argc, char **argv) errout++; } krb5_klog_syslog(LOG_INFO, "shutting down"); + unload_preauth_plugins(kcontext); krb5_klog_close(kdc_context); finish_realms(argv[0]); if (kdc_realmlist) --- krb5/src/kdc/kdc_util.h +++ krb5/src/kdc/kdc_util.h @@ -107,7 +107,7 @@ void rep_etypes2str(char *s, size_t len, krb5_kdc_rep *rep); /* do_as_req.c */ -krb5_error_code process_as_req (krb5_kdc_req *, +krb5_error_code process_as_req (krb5_kdc_req *, krb5_data *, const krb5_fulladdr *, krb5_data ** ); @@ -146,15 +146,23 @@ void get_preauth_hint_list (krb5_kdc_req krb5_db_entry *client, krb5_db_entry *server, krb5_data *e_data); +krb5_error_code load_preauth_plugins(krb5_context context); +krb5_error_code unload_preauth_plugins(krb5_context context); + krb5_error_code check_padata - (krb5_context context, krb5_db_entry *client, - krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply); + (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, + krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, + void **padata_context, krb5_data *e_data); krb5_error_code return_padata (krb5_context context, krb5_db_entry *client, - krb5_kdc_req *request, krb5_kdc_rep *reply, - krb5_key_data *client_key, krb5_keyblock *encrypting_key); + krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, krb5_keyblock *encrypting_key, + void **padata_context); +krb5_error_code free_padata_context + (krb5_context context, void **padata_context); + /* replay.c */ krb5_boolean kdc_check_lookaside (krb5_data *, krb5_data **); void kdc_insert_lookaside (krb5_data *, krb5_data *); @@ -191,4 +199,8 @@ void enable_v4_crossrealm(char *); ((X) == ADDRTYPE_INET ? AF_INET : -1) #endif +/* RFC 4120: KRB5KDC_ERR_KEY_TOO_WEAK + * RFC 4556: KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED */ +#define KRB5KDC_ERR_KEY_TOO_WEAK KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED + #endif /* __KRB5_KDC_UTIL__ */ --- krb5/src/lib/krb5/os/init_os_ctx.c +++ krb5/src/lib/krb5/os/init_os_ctx.c @@ -391,6 +391,8 @@ krb5_os_init_context(krb5_context ctx, k ctx->vtbl = 0; PLUGIN_DIR_INIT(&ctx->libkrb5_plugins); + PLUGIN_DIR_INIT(&ctx->preauth_plugins); + ctx->preauth_context = NULL; retval = os_init_paths(ctx, kdc); /* @@ -492,6 +494,11 @@ krb5_os_free_context(krb5_context ctx) ctx->profile = 0; } + if (ctx->preauth_context) { + krb5_free_preauth_context(ctx); + ctx->preauth_context = NULL; + } + krb5int_close_plugin_dirs (&ctx->preauth_plugins); krb5int_close_plugin_dirs (&ctx->libkrb5_plugins); #ifdef _WIN32 --- krb5/src/lib/krb5/krb/init_ctx.c +++ krb5/src/lib/krb5/krb/init_ctx.c @@ -97,7 +97,7 @@ krb5_init_secure_context(krb5_context *c return init_common (context, TRUE, FALSE); } -krb5_error_code KRB5_CALLCONV +krb5_error_code krb5int_init_context_kdc(krb5_context *context) { return init_common (context, FALSE, TRUE); @@ -272,6 +272,8 @@ krb5_free_context(krb5_context ctx) ctx->ser_ctx = 0; } + krb5_clear_error_message(ctx); + ctx->magic = 0; free(ctx); } @@ -534,6 +536,9 @@ krb5_copy_context(krb5_context ctx, krb5 nctx->prompt_types = NULL; nctx->os_context->default_ccname = NULL; + memset(&nctx->preauth_plugins, 0, sizeof(nctx->preauth_plugins)); + nctx->preauth_context = NULL; + memset(&nctx->libkrb5_plugins, 0, sizeof(nctx->libkrb5_plugins)); nctx->vtbl = NULL; nctx->locate_fptrs = NULL; --- krb5/src/lib/krb5/krb/preauth2.c 2005-01-17 12:32:26.000000000 -0500 +++ krb5/src/lib/krb5/krb/preauth2.c @@ -30,6 +30,18 @@ */ #include "k5-int.h" +#include "osconf.h" +#include + +#if !defined(_WIN32) +#include +#endif + +#if TARGET_OS_MAC +static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/preauth", NULL }; /* should be a list */ +#else +static const char *objdirs[] = { LIBDIR "/krb5/plugins/preauth", NULL }; +#endif typedef krb5_error_code (*pa_function)(krb5_context, krb5_kdc_req *request, @@ -49,8 +61,458 @@ typedef struct _pa_types_t { int flags; } pa_types_t; -#define PA_REAL 0x0001 -#define PA_INFO 0x0002 +/* Create the per-krb5_context context. This means loading the modules + * if we haven't done that yet (applications which never obtain initial + * credentials should never hit this routine), breaking up the module's + * list of support pa_types so that we can iterate over the modules more + * easily, and copying over the relevant parts of the module's table. */ +void KRB5_CALLCONV +krb5_init_preauth_context(krb5_context kcontext) +{ + int n_modules, n_tables, i, j, k; + void **tables; + struct krb5plugin_preauth_client_ftable_v0 *table; + krb5_preauth_context *context = NULL; + void *plugin_context; + krb5_preauthtype pa_type; + void **rcpp; + + /* Only do this once for each krb5_context */ + if (kcontext->preauth_context != NULL) + return; + + /* load the plugins for the current context */ + if (PLUGIN_DIR_OPEN(&kcontext->preauth_plugins) == 0) { + if (krb5int_open_plugin_dirs(objdirs, NULL, + &kcontext->preauth_plugins, + &kcontext->err) != 0) { + return; + } + } + + /* pull out the module function tables for all of the modules */ + tables = NULL; + if (krb5int_get_plugin_dir_data(&kcontext->preauth_plugins, + "preauthentication_client_0_backport_1_6", + &tables, + &kcontext->err) != 0) { + return; + } + if (tables == NULL) { + return; + } + + /* count how many modules we ended up loading, and how many preauth + * types we may claim to support as a result */ + n_modules = 0; + for (n_tables = 0; + (tables != NULL) && (tables[n_tables] != NULL); + n_tables++) { + table = tables[n_tables]; + if ((table->pa_type_list != NULL) && (table->process != NULL)) { + for (j = 0; table->pa_type_list[j] > 0; j++) { + n_modules++; + } + } + } + + /* allocate the space we need */ + context = malloc(sizeof(*context)); + if (context == NULL) { + krb5int_free_plugin_dir_data(tables); + return; + } + context->modules = malloc(sizeof(context->modules[0]) * n_modules); + if (context->modules == NULL) { + krb5int_free_plugin_dir_data(tables); + free(context); + return; + } + memset(context->modules, 0, sizeof(context->modules[0]) * n_modules); + context->n_modules = n_modules; + + /* fill in the structure */ + k = 0; + for (i = 0; i < n_tables; i++) { + table = tables[i]; + if ((table->pa_type_list != NULL) && (table->process != NULL)) { + plugin_context = NULL; + if ((table->init != NULL) && + ((*table->init)(kcontext, &plugin_context) != 0)) { +#ifdef DEBUG + fprintf (stderr, "init err, skipping module \"%s\"\n", + table->name); +#endif + continue; + } + + rcpp = NULL; + for (j = 0; table->pa_type_list[j] > 0; j++) { + pa_type = table->pa_type_list[j]; + context->modules[k].pa_type = pa_type; + context->modules[k].enctypes = table->enctype_list; + context->modules[k].plugin_context = plugin_context; + /* Only call client_fini once per plugin */ + if (j == 0) + context->modules[k].client_fini = table->fini; + else + context->modules[k].client_fini = NULL; + context->modules[k].ftable = table; + context->modules[k].name = table->name; + context->modules[k].flags = (*table->flags)(kcontext, pa_type); + context->modules[k].use_count = 0; + context->modules[k].client_process = table->process; + context->modules[k].client_tryagain = table->tryagain; + context->modules[k].request_context = NULL; + /* + * Only call request_init and request_fini once per plugin. + * Only the first module within each plugin will ever + * have request_context filled in. Every module within + * the plugin will have its request_context_pp pointing + * to that entry's request_context. That way all the + * modules within the plugin share the same request_context + */ + if (j == 0) { + context->modules[k].client_req_init = table->request_init; + context->modules[k].client_req_fini = table->request_fini; + rcpp = &context->modules[k].request_context; + } else { + context->modules[k].client_req_init = NULL; + context->modules[k].client_req_fini = NULL; + } + context->modules[k].request_context_pp = rcpp; +#ifdef DEBUG + fprintf (stderr, "init module \"%s\", pa_type %d, flag %d\n", + context->modules[k].name, + context->modules[k].pa_type, + context->modules[k].flags); +#endif + k++; + } + } + } + krb5int_free_plugin_dir_data(tables); + + /* return the result */ + kcontext->preauth_context = context; +} + +/* Zero the use counts for the modules herein. Usually used before we + * start processing any data from the server, at which point every module + * will again be able to take a crack at whatever the server sent. */ +void KRB5_CALLCONV +krb5_clear_preauth_context_use_counts(krb5_context context) +{ + int i; + if (context->preauth_context != NULL) { + for (i = 0; i < context->preauth_context->n_modules; i++) { + context->preauth_context->modules[i].use_count = 0; + } + } +} + +/* Free the per-krb5_context preauth_context. This means clearing any + * plugin-specific context which may have been created, and then + * freeing the context itself. */ +void KRB5_CALLCONV +krb5_free_preauth_context(krb5_context context) +{ + int i; + void *pctx; + if (context->preauth_context != NULL) { + for (i = 0; i < context->preauth_context->n_modules; i++) { + pctx = context->preauth_context->modules[i].plugin_context; + if (context->preauth_context->modules[i].client_fini != NULL) { + (*context->preauth_context->modules[i].client_fini)(context, pctx); + } + memset(&context->preauth_context->modules[i], 0, + sizeof(context->preauth_context->modules[i])); + } + if (context->preauth_context->modules != NULL) { + free(context->preauth_context->modules); + context->preauth_context->modules = NULL; + } + free(context->preauth_context); + context->preauth_context = NULL; + } +} + +/* Initialize the per-AS-REQ context. This means calling the client_req_init + * function to give the plugin a chance to allocate a per-request context. */ +void KRB5_CALLCONV +krb5_preauth_request_context_init(krb5_context context) +{ + int i; + void *rctx, *pctx; + + /* Limit this to only one attempt per context? */ + if (context->preauth_context == NULL) + krb5_init_preauth_context(context); + if (context->preauth_context != NULL) { + for (i = 0; i < context->preauth_context->n_modules; i++) { + pctx = context->preauth_context->modules[i].plugin_context; + if (context->preauth_context->modules[i].client_req_init != NULL) { + rctx = context->preauth_context->modules[i].request_context_pp; + (*context->preauth_context->modules[i].client_req_init) (context, pctx, rctx); + } + } + } +} + +/* Free the per-AS-REQ context. This means clearing any request-specific + * context which the plugin may have created. */ +void KRB5_CALLCONV +krb5_preauth_request_context_fini(krb5_context context) +{ + int i; + void *rctx, *pctx; + if (context->preauth_context != NULL) { + for (i = 0; i < context->preauth_context->n_modules; i++) { + pctx = context->preauth_context->modules[i].plugin_context; + rctx = context->preauth_context->modules[i].request_context; + if (rctx != NULL) { + if (context->preauth_context->modules[i].client_req_fini != NULL) { + (*context->preauth_context->modules[i].client_req_fini)(context, pctx, rctx); + } + context->preauth_context->modules[i].request_context = NULL; + } + } + } +} + +/* Add the named encryption type to the existing list of ktypes. */ +static void +grow_ktypes(krb5_enctype **out_ktypes, int *out_nktypes, krb5_enctype ktype) +{ + int i; + krb5_enctype *ktypes; + for (i = 0; i < *out_nktypes; i++) { + if ((*out_ktypes)[i] == ktype) + return; + } + ktypes = malloc((*out_nktypes + 2) * sizeof(ktype)); + if (ktypes) { + for (i = 0; i < *out_nktypes; i++) + ktypes[i] = (*out_ktypes)[i]; + ktypes[i++] = ktype; + ktypes[i] = 0; + free(*out_ktypes); + *out_ktypes = ktypes; + *out_nktypes = i; + } +} + +/* Add the given pa_data item to the list of items. Factored out here to make + * reading the do_preauth logic easier to read. */ +static int +grow_pa_list(krb5_pa_data ***out_pa_list, int *out_pa_list_size, + krb5_pa_data *addition) +{ + krb5_pa_data **pa_list; + int i; + + if (out_pa_list == NULL) { + return EINVAL; + } + + if (*out_pa_list == NULL) { + /* Allocate room for one entry and a NULL terminator. */ + pa_list = malloc(2 * sizeof(krb5_pa_data *)); + if (pa_list == NULL) + return ENOMEM; + pa_list[0] = addition; + pa_list[1] = NULL; + *out_pa_list = pa_list; + *out_pa_list_size = 1; + } else { + /* Allocate room for one more entry and a NULL terminator. */ + pa_list = malloc((*out_pa_list_size + 2) * sizeof(krb5_pa_data *)); + if (pa_list == NULL) + return ENOMEM; + for (i = 0; i < *out_pa_list_size; i++) + pa_list[i] = (*out_pa_list)[i]; + pa_list[i++] = addition; + pa_list[i++] = NULL; + free(*out_pa_list); + *out_pa_list = pa_list; + *out_pa_list_size = i; + } + return 0; +} + +/* + * Retrieve a specific piece of information required by the plugin and + * return it in a new krb5_data item. There are separate request_types + * to obtain the data and free it. + * + * This may require massaging data into a contrived format, but it will + * hopefully keep us from having to reveal library-internal functions + * or data to the plugin modules. + */ + +static krb5_error_code +client_data_proc(krb5_context kcontext, + krb5_preauth_client_rock *rock, + krb5_int32 request_type, + krb5_data **retdata) +{ + krb5_data *ret; + char *data; + + if (rock->magic != CLIENT_ROCK_MAGIC) + return EINVAL; + if (retdata == NULL) + return EINVAL; + + switch (request_type) { + case krb5plugin_preauth_client_get_etype: + { + krb5_enctype *eptr; + if (rock->as_reply == NULL) + return ENOENT; + ret = malloc(sizeof(krb5_data)); + if (ret == NULL) + return ENOMEM; + data = malloc(sizeof(krb5_enctype)); + if (data == NULL) { + free(ret); + return ENOMEM; + } + ret->data = data; + ret->length = sizeof(krb5_enctype); + eptr = (krb5_enctype *)data; + *eptr = rock->as_reply->enc_part.enctype; + *retdata = ret; + return 0; + } + break; + case krb5plugin_preauth_client_free_etype: + ret = *retdata; + if (ret == NULL) + return 0; + if (ret->data) + free(ret->data); + free(ret); + return 0; + break; + default: + return EINVAL; + } +} + +/* Tweak the request body, for now adding any enctypes which the module claims + * to add support for to the list, but in the future perhaps doing more + * involved things. */ +void KRB5_CALLCONV +krb5_preauth_prepare_request(krb5_context kcontext, + krb5_get_init_creds_opt *options, + krb5_kdc_req *request) +{ + int i, j; + + if (kcontext->preauth_context == NULL) { + return; + } + /* Add the module-specific enctype list to the request, but only if + * it's something we can safely modify. */ + if (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))) { + for (i = 0; i < kcontext->preauth_context->n_modules; i++) { + if (kcontext->preauth_context->modules[i].enctypes == NULL) + continue; + for (j = 0; kcontext->preauth_context->modules[i].enctypes[j] != 0; j++) { + grow_ktypes(&request->ktype, &request->nktypes, + kcontext->preauth_context->modules[i].enctypes[j]); + } + } + } +} + +/* Find the first module which provides for the named preauth type which also + * hasn't had a chance to run yet (INFO modules don't count, because as a rule + * they don't generate preauth data), and run it. */ +static krb5_error_code +krb5_run_preauth_plugins(krb5_context kcontext, + int module_required_flags, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *in_padata, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + krb5_data *salt, + krb5_data *s2kparams, + void *gak_data, + krb5_preauth_client_rock *get_data_rock, + krb5_keyblock *as_key, + krb5_pa_data ***out_pa_list, + int *out_pa_list_size, + int *module_ret, + int *module_flags) +{ + int i; + krb5_pa_data *out_pa_data; + krb5_error_code ret; + struct _krb5_preauth_context_module *module; + + if (kcontext->preauth_context == NULL) { + return ENOENT; + } + /* iterate over all loaded modules */ + for (i = 0; i < kcontext->preauth_context->n_modules; i++) { + module = &kcontext->preauth_context->modules[i]; + /* skip over those which don't match the preauth type */ + if (module->pa_type != in_padata->pa_type) + continue; + /* skip over those which don't match the flags (INFO vs REAL, mainly) */ + if ((module->flags & module_required_flags) == 0) + continue; + /* if it's a REAL module, try to call it only once per library call */ + if (module_required_flags & PA_REAL) { + if (module->use_count > 0) { +#ifdef DEBUG + fprintf(stderr, "skipping already-used module \"%s\"(%d)\n", + module->name, module->pa_type); +#endif + continue; + } + module->use_count++; + } + /* run the module's callback function */ + out_pa_data = NULL; +#ifdef DEBUG + fprintf(stderr, "using module \"%s\" (%d), flags = %d\n", + module->name, module->pa_type, module->flags); +#endif + ret = module->client_process(kcontext, + module->plugin_context, + *module->request_context_pp, + client_data_proc, + get_data_rock, + request, + encoded_request_body, + encoded_previous_request, + in_padata, + prompter, prompter_data, + gak_fct, gak_data, salt, s2kparams, + as_key, + &out_pa_data); + /* Make note of the module's flags and status. */ + *module_flags = module->flags; + *module_ret = ret; + /* Save the new preauth data item. */ + if (out_pa_data != NULL) { + ret = grow_pa_list(out_pa_list, out_pa_list_size, out_pa_data); + if (ret != 0) + return ret; + } + break; + } + if (i >= kcontext->preauth_context->n_modules) { + return ENOENT; + } + return 0; +} static krb5_error_code pa_salt(krb5_context context, @@ -101,8 +563,8 @@ krb5_error_code pa_enc_timestamp(krb5_co #ifdef DEBUG fprintf (stderr, "%s:%d: salt len=%d", __FILE__, __LINE__, salt->length); - if (salt->length > 0) - fprintf (stderr, " '%*s'", salt->length, salt->data); + if ((int) salt->length > 0) + fprintf (stderr, " '%.*s'", salt->length, salt->data); fprintf (stderr, "; *etype=%d request->ktype[0]=%d\n", *etype, request->ktype[0]); #endif @@ -819,15 +1281,88 @@ static const pa_types_t pa_types[] = { }, }; -krb5_error_code +/* + * If one of the modules can adjust its AS_REQ data using the contents of the + * err_reply, return 0. If it's the sort of correction which requires that we + * ask the user another question, we let the calling application deal with it. + */ +krb5_error_code KRB5_CALLCONV +krb5_do_preauth_tryagain(krb5_context kcontext, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data **padata, + krb5_pa_data ***return_padata, + krb5_error *err_reply, + krb5_data *salt, krb5_data *s2kparams, + krb5_enctype *etype, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, void *gak_data, + krb5_preauth_client_rock *get_data_rock) +{ + krb5_error_code ret; + krb5_pa_data *out_padata; + krb5_preauth_context *context; + struct _krb5_preauth_context_module *module; + int i, j; + int out_pa_list_size = 0; + + ret = KRB5KRB_ERR_GENERIC; + if (kcontext->preauth_context == NULL) { + return KRB5KRB_ERR_GENERIC; + } + context = kcontext->preauth_context; + if (context == NULL) { + return KRB5KRB_ERR_GENERIC; + } + + for (i = 0; padata[i] != NULL && padata[i]->pa_type != 0; i++) { + out_padata = NULL; + for (j = 0; j < context->n_modules; j++) { + module = &context->modules[j]; + if (module->pa_type != padata[i]->pa_type) { + continue; + } + if (module->client_tryagain == NULL) { + continue; + } + if ((*module->client_tryagain)(kcontext, + module->plugin_context, + *module->request_context_pp, + client_data_proc, + get_data_rock, + request, + encoded_request_body, + encoded_previous_request, + padata[i], + err_reply, + prompter, prompter_data, + gak_fct, gak_data, salt, s2kparams, + as_key, + &out_padata) == 0) { + if (out_padata != NULL) { + grow_pa_list(return_padata, &out_pa_list_size, out_padata); + return 0; + } + } + } + } + return ret; +} + +krb5_error_code KRB5_CALLCONV krb5_do_preauth(krb5_context context, krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, krb5_pa_data **in_padata, krb5_pa_data ***out_padata, krb5_data *salt, krb5_data *s2kparams, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter, void *prompter_data, - krb5_gic_get_as_key_fct gak_fct, void *gak_data) + krb5_gic_get_as_key_fct gak_fct, void *gak_data, + krb5_preauth_client_rock *get_data_rock) { int h, i, j, out_pa_list_size; int seen_etype_info2 = 0; @@ -844,9 +1379,9 @@ krb5_do_preauth(krb5_context context, } #ifdef DEBUG - fprintf (stderr, "salt len=%d", salt->length); - if (salt->length > 0) - fprintf (stderr, " '%*s'", salt->length, salt->data); + fprintf (stderr, "salt len=%d", (int) salt->length); + if ((int) salt->length > 0) + fprintf (stderr, " '%.*s'", salt->length, salt->data); fprintf (stderr, "; preauth data types:"); for (i = 0; in_padata[i]; i++) { fprintf (stderr, " %d", in_padata[i]->pa_type); @@ -953,7 +1488,7 @@ krb5_do_preauth(krb5_context context, fprintf (stderr, "etype info %d: etype %d salt len=%d", j, e->etype, e->length); if (e->length > 0 && e->length != KRB5_ETYPE_NO_SALT) - fprintf (stderr, " '%*s'", e->length, e->salt); + fprintf (stderr, " '%.*s'", e->length, e->salt); fprintf (stderr, "\n"); } #endif @@ -967,9 +1502,14 @@ krb5_do_preauth(krb5_context context, default: ; } - for (j=0; pa_types[j].type >= 0; j++) { + /* Try the internally-provided preauth type list. */ + if (!realdone) for (j=0; pa_types[j].type >= 0; j++) { if ((in_padata[i]->pa_type == pa_types[j].type) && (pa_types[j].flags & paorder[h])) { +#ifdef DEBUG + fprintf (stderr, "calling internal function for pa_type " + "%d, flag %d\n", pa_types[j].type, paorder[h]); +#endif out_pa = NULL; if ((ret = ((*pa_types[j].fct)(context, request, @@ -980,41 +1520,54 @@ krb5_do_preauth(krb5_context context, goto cleanup; } - if (out_pa) { - if (out_pa_list == NULL) { - if ((out_pa_list = - (krb5_pa_data **) - malloc(2*sizeof(krb5_pa_data *))) - == NULL) { - ret = ENOMEM; - goto cleanup; - } - } else { - if ((out_pa_list = - (krb5_pa_data **) - realloc(out_pa_list, - (out_pa_list_size+2)* - sizeof(krb5_pa_data *))) - == NULL) { - /* XXX this will leak the pointers which - have already been allocated. oh well. */ - ret = ENOMEM; - goto cleanup; - } - } - - out_pa_list[out_pa_list_size++] = out_pa; + ret = grow_pa_list(&out_pa_list, &out_pa_list_size, + out_pa); + if (ret != 0) { + goto cleanup; } if (paorder[h] == PA_REAL) realdone = 1; } } + + /* Try to use plugins now. */ + if (!realdone) { + krb5_init_preauth_context(context); + if (context->preauth_context != NULL) { + int module_ret, module_flags; +#ifdef DEBUG + fprintf (stderr, "trying modules for pa_type %d, flag %d\n", + in_padata[i]->pa_type, paorder[h]); +#endif + ret = krb5_run_preauth_plugins(context, + paorder[h], + request, + encoded_request_body, + encoded_previous_request, + in_padata[i], + prompter, + prompter_data, + gak_fct, + salt, s2kparams, + gak_data, + get_data_rock, + as_key, + &out_pa_list, + &out_pa_list_size, + &module_ret, + &module_flags); + if (ret == 0) { + if (module_ret == 0) { + if (paorder[h] == PA_REAL) { + realdone = 1; + } + } + } + } + } } } - if (out_pa_list) - out_pa_list[out_pa_list_size++] = NULL; - *out_padata = out_pa_list; if (etype_info) krb5_free_etype_info(context, etype_info); --- krb5/src/lib/krb5/krb/Makefile.in +++ krb5/src/lib/krb5/krb/Makefile.in @@ -6,7 +6,7 @@ RUN_SETUP = @KRB5_RUN_ENV@ PROG_LIBPATH=-L$(TOPLIBD) PROG_RPATH=$(KRB5_LIBDIR) LOCALINCLUDES = -I$(srcdir)/../os -I$(SRCTOP) -DEFS= +DEFS=-DLIBDIR=\"$(KRB5_LIBDIR)\" ##DOS##BUILDTOP = ..\..\.. ##DOS##PREFIXDIR=krb --- krb5/src/lib/krb5/krb/get_in_tkt.c 2005-08-15 20:38:17.000000000 -0400 +++ krb5/src/lib/krb5/krb/get_in_tkt.c @@ -78,6 +78,9 @@ typedef krb5_error_code (*git_decrypt_pr static krb5_error_code make_preauth_list (krb5_context, krb5_preauthtype *, int, krb5_pa_data ***); +static krb5_error_code sort_krb5_padata_sequence(krb5_context context, + krb5_data *realm, + krb5_pa_data **padata); /* * This function performs 32 bit bounded addition so we can generate @@ -105,7 +108,6 @@ static krb5_int32 krb5int_addint32 (krb5 static krb5_error_code send_as_request(krb5_context context, krb5_kdc_req *request, - krb5_timestamp *time_now, krb5_error ** ret_err_reply, krb5_kdc_rep ** ret_as_reply, int *use_master) @@ -116,17 +118,16 @@ send_as_request(krb5_context context, krb5_data reply; char k4_version; /* same type as *(krb5_data::data) */ int tcp_only = 0; + krb5_timestamp time_now; reply.data = 0; - - if ((retval = krb5_timeofday(context, time_now))) - goto cleanup; - /* - * XXX we know they are the same size... and we should do - * something better than just the current time - */ - request->nonce = (krb5_int32) *time_now; + /* set the nonce if the caller expects us to do it */ + if (request->nonce == 0) { + if ((retval = krb5_timeofday(context, &time_now))) + goto cleanup; + request->nonce = (krb5_int32) time_now; + } /* encode & send to KDC */ if ((retval = encode_krb5_as_req(request, &packet)) != 0) @@ -437,7 +438,6 @@ static const krb5_enctype get_in_tkt_enc 0 }; - krb5_error_code KRB5_CALLCONV krb5_get_in_tkt(krb5_context context, const krb5_flags options, @@ -486,6 +486,7 @@ krb5_get_in_tkt(krb5_context context, request.kdc_options = options; request.client = creds->client; request.server = creds->server; + request.nonce = 0; request.from = creds->times.starttime; request.till = creds->times.endtime; request.rtime = creds->times.renew_till; @@ -553,7 +554,17 @@ krb5_get_in_tkt(krb5_context context, err_reply = 0; as_reply = 0; - if ((retval = send_as_request(context, &request, &time_now, &err_reply, + + if ((retval = krb5_timeofday(context, &time_now))) + goto cleanup; + + /* + * XXX we know they are the same size... and we should do + * something better than just the current time + */ + request.nonce = (krb5_int32) time_now; + + if ((retval = send_as_request(context, &request, &err_reply, &as_reply, &use_master))) goto cleanup; @@ -565,6 +576,11 @@ krb5_get_in_tkt(krb5_context context, krb5_free_error(context, err_reply); if (retval) goto cleanup; + retval = sort_krb5_padata_sequence(context, + &request.server->realm, + padata); + if (retval) + goto cleanup; continue; } else { retval = (krb5_error_code) err_reply->error @@ -746,6 +762,79 @@ krb5_libdefault_boolean(krb5_context con return(0); } +/* Sort a pa_data sequence so that types named in the "preferred_preauth_types" + * libdefaults entry are listed before any others. */ +static krb5_error_code +sort_krb5_padata_sequence(krb5_context context, krb5_data *realm, + krb5_pa_data **padata) +{ + int i, j, base; + krb5_error_code ret; + const char *p; + long l; + char *q, *preauth_types = NULL; + krb5_pa_data *tmp; + int need_free_string = 1; + + if ((padata == NULL) || (padata[0] == NULL)) { + return 0; + } + + ret = krb5_libdefault_string(context, realm, "preferred_preauth_types", + &preauth_types); + if ((ret != 0) || (preauth_types == NULL)) { + /* Try to use PKINIT first. */ + preauth_types = "17, 16, 15, 14"; + need_free_string = 0; + } + +#ifdef DEBUG + fprintf (stderr, "preauth data types before sorting:"); + for (i = 0; padata[i]; i++) { + fprintf (stderr, " %d", padata[i]->pa_type); + } + fprintf (stderr, "\n"); +#endif + + base = 0; + for (p = preauth_types; *p != '\0';) { + /* skip whitespace to find an entry */ + p += strspn(p, ", "); + if (*p != '\0') { + /* see if we can extract a number */ + l = strtol(p, &q, 10); + if ((q != NULL) && (q > p)) { + /* got a valid number; search for a matchin entry */ + for (i = base; padata[i] != NULL; i++) { + /* bubble the matching entry to the front of the list */ + if (padata[i]->pa_type == l) { + tmp = padata[i]; + for (j = i; j > base; j--) + padata[j] = padata[j - 1]; + padata[base] = tmp; + base++; + break; + } + } + p = q; + } else { + break; + } + } + } + if (need_free_string) + free(preauth_types); + +#ifdef DEBUG + fprintf (stderr, "preauth data types after sorting:"); + for (i = 0; padata[i]; i++) + fprintf (stderr, " %d", padata[i]->pa_type); + fprintf (stderr, "\n"); +#endif + + return 0; +} + krb5_error_code KRB5_CALLCONV krb5_get_init_creds(krb5_context context, krb5_creds *creds, @@ -762,7 +851,8 @@ krb5_get_init_creds(krb5_context context { krb5_error_code ret; krb5_kdc_req request; - krb5_pa_data **padata; + krb5_data *encoded_request_body, *encoded_previous_request; + krb5_pa_data **preauth_to_use, **kdc_padata; int tempint; char *tempstr; krb5_deltat tkt_life; @@ -775,6 +865,7 @@ krb5_get_init_creds(krb5_context context krb5_kdc_rep *local_as_reply; krb5_timestamp time_now; krb5_enctype etype = 0; + krb5_preauth_client_rock get_data_rock; /* initialize everything which will be freed at cleanup */ @@ -784,19 +875,27 @@ krb5_get_init_creds(krb5_context context request.ktype = NULL; request.addresses = NULL; request.padata = NULL; - padata = NULL; + encoded_request_body = NULL; + encoded_previous_request = NULL; + preauth_to_use = NULL; + kdc_padata = NULL; as_key.length = 0; salt.length = 0; salt.data = NULL; local_as_reply = 0; + err_reply = NULL; + /* * Set up the basic request structure */ request.magic = KV5M_KDC_REQ; request.msg_type = KRB5_AS_REQ; + /* request.nonce is filled in when we send a request to the kdc */ + request.nonce = 0; + /* request.padata is filled in later */ request.kdc_options = context->kdc_default_options; @@ -921,7 +1020,9 @@ krb5_get_init_creds(krb5_context context goto cleanup; } - /* nonce is filled in by send_as_request */ + krb5_preauth_request_context_init(context); + + /* nonce is filled in by send_as_request if we don't take care of it */ if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) { request.ktype = options->etype_list; @@ -960,8 +1061,8 @@ krb5_get_init_creds(krb5_context context if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) { if ((ret = make_preauth_list(context, options->preauth_list, - options->preauth_list_length, - &padata))) + options->preauth_list_length, + &preauth_to_use))) goto cleanup; } @@ -975,44 +1076,118 @@ krb5_get_init_creds(krb5_context context salt.data = NULL; } - /* now, loop processing preauth data and talking to the kdc */ + /* set the request nonce */ + if ((ret = krb5_timeofday(context, &time_now))) + goto cleanup; + /* + * XXX we know they are the same size... and we should do + * something better than just the current time + */ + request.nonce = (krb5_int32) time_now; + + /* give the preauth plugins a chance to prep the request body */ + krb5_preauth_prepare_request(context, options, &request); + ret = encode_krb5_kdc_req_body(&request, &encoded_request_body); + if (ret) + goto cleanup; + + get_data_rock.magic = CLIENT_ROCK_MAGIC; + get_data_rock.as_reply = NULL; + + /* now, loop processing preauth data and talking to the kdc */ for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) { if (request.padata) { krb5_free_pa_data(context, request.padata); request.padata = NULL; } + if (!err_reply) { + /* either our first attempt, or retrying after PREAUTH_NEEDED */ + if ((ret = krb5_do_preauth(context, + &request, + encoded_request_body, + encoded_previous_request, + preauth_to_use, &request.padata, + &salt, &s2kparams, &etype, &as_key, + prompter, prompter_data, + gak_fct, gak_data, + &get_data_rock))) + goto cleanup; + } else { + if (preauth_to_use != NULL) { + /* + * Retry after an error other than PREAUTH_NEEDED, + * using e-data to figure out what to change. + */ + ret = krb5_do_preauth_tryagain(context, + &request, + encoded_request_body, + encoded_previous_request, + preauth_to_use, &request.padata, + err_reply, + &salt, &s2kparams, &etype, + &as_key, + prompter, prompter_data, + gak_fct, gak_data, + &get_data_rock); + } else { + /* No preauth supplied, so can't query the plug-ins. */ + ret = KRB5KRB_ERR_GENERIC; + } + if (ret) { + /* couldn't come up with anything better */ + ret = err_reply->error + ERROR_TABLE_BASE_krb5; + } + krb5_free_error(context, err_reply); + err_reply = NULL; + if (ret) + goto cleanup; + } - if ((ret = krb5_do_preauth(context, &request, - padata, &request.padata, - &salt, &s2kparams, &etype, &as_key, prompter, - prompter_data, gak_fct, gak_data))) + if (encoded_previous_request != NULL) { + krb5_free_data(context, encoded_previous_request); + encoded_previous_request = NULL; + } + ret = encode_krb5_as_req(&request, &encoded_previous_request); + if (ret) goto cleanup; - if (padata) { - krb5_free_pa_data(context, padata); - padata = 0; - } - err_reply = 0; local_as_reply = 0; - if ((ret = send_as_request(context, &request, &time_now, &err_reply, + if ((ret = send_as_request(context, &request, &err_reply, &local_as_reply, use_master))) goto cleanup; if (err_reply) { if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && err_reply->e_data.length > 0) { + /* reset the list of preauth types to try */ + if (preauth_to_use) { + krb5_free_pa_data(context, preauth_to_use); + preauth_to_use = NULL; + } ret = decode_krb5_padata_sequence(&err_reply->e_data, - &padata); + &preauth_to_use); krb5_free_error(context, err_reply); + err_reply = NULL; + if (ret) + goto cleanup; + ret = sort_krb5_padata_sequence(context, + &request.server->realm, + preauth_to_use); if (ret) goto cleanup; + /* continue to next iteration */ } else { - ret = (krb5_error_code) err_reply->error - + ERROR_TABLE_BASE_krb5; - krb5_free_error(context, err_reply); - goto cleanup; + if (err_reply->e_data.length > 0) { + /* continue to next iteration */ + } else { + /* error + no hints = give up */ + ret = (krb5_error_code) err_reply->error + + ERROR_TABLE_BASE_krb5; + krb5_free_error(context, err_reply); + goto cleanup; + } } } else if (local_as_reply) { break; @@ -1028,16 +1203,20 @@ krb5_get_init_creds(krb5_context context } /* process any preauth data in the as_reply */ - - if ((ret = krb5_do_preauth(context, &request, - local_as_reply->padata, &padata, + krb5_clear_preauth_context_use_counts(context); + if ((ret = sort_krb5_padata_sequence(context, &request.server->realm, + local_as_reply->padata))) + goto cleanup; + get_data_rock.as_reply = local_as_reply; + if ((ret = krb5_do_preauth(context, + &request, + encoded_request_body, encoded_previous_request, + local_as_reply->padata, &kdc_padata, &salt, &s2kparams, &etype, &as_key, prompter, - prompter_data, gak_fct, gak_data))) + prompter_data, gak_fct, gak_data, + &get_data_rock))) goto cleanup; - /* XXX if there's padata on output, something is wrong, but it's - not obviously an error */ - /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY, the AS_REP comes back encrypted in the user's longterm key instead of in the SAD. If there was a SAM preauth, there @@ -1090,6 +1269,15 @@ krb5_get_init_creds(krb5_context context ret = 0; cleanup: + krb5_preauth_request_context_fini(context); + if (encoded_previous_request != NULL) { + krb5_free_data(context, encoded_previous_request); + encoded_previous_request = NULL; + } + if (encoded_request_body != NULL) { + krb5_free_data(context, encoded_request_body); + encoded_request_body = NULL; + } if (request.server) krb5_free_principal(context, request.server); if (request.ktype && @@ -1099,8 +1287,10 @@ cleanup: (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)))) krb5_free_addresses(context, request.addresses); - if (padata) - krb5_free_pa_data(context, padata); + if (preauth_to_use) + krb5_free_pa_data(context, preauth_to_use); + if (kdc_padata) + krb5_free_pa_data(context, kdc_padata); if (request.padata) krb5_free_pa_data(context, request.padata); if (as_key.length) --- krb5/src/lib/krb5/error_tables/krb5_err.et +++ krb5/src/lib/krb5/error_tables/krb5_err.et @@ -103,26 +103,26 @@ error_code KRB5PLACEHOLD_58, "KRB5 error error_code KRB5PLACEHOLD_59, "KRB5 error code 59" error_code KRB5KRB_ERR_GENERIC, "Generic error (see e-text)" error_code KRB5KRB_ERR_FIELD_TOOLONG, "Field is too long for this implementation" -error_code KRB5PLACEHOLD_62, "KRB5 error code 62" -error_code KRB5PLACEHOLD_63, "KRB5 error code 63" -error_code KRB5PLACEHOLD_64, "KRB5 error code 64" -error_code KRB5PLACEHOLD_65, "KRB5 error code 65" -error_code KRB5PLACEHOLD_66, "KRB5 error code 66" +error_code KRB5KDC_ERR_CLIENT_NOT_TRUSTED, "Client not trusted" +error_code KRB5KDC_ERR_KDC_NOT_TRUSTED, "KDC not trusted" +error_code KRB5KDC_ERR_INVALID_SIG, "Invalid signature" +error_code KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED, "Key parameters not accepted" +error_code KRB5KDC_ERR_CERTIFICATE_MISMATCH, "Certificate mismatch" error_code KRB5PLACEHOLD_67, "KRB5 error code 67" error_code KRB5PLACEHOLD_68, "KRB5 error code 68" error_code KRB5PLACEHOLD_69, "KRB5 error code 69" -error_code KRB5PLACEHOLD_70, "KRB5 error code 70" -error_code KRB5PLACEHOLD_71, "KRB5 error code 71" -error_code KRB5PLACEHOLD_72, "KRB5 error code 72" -error_code KRB5PLACEHOLD_73, "KRB5 error code 73" -error_code KRB5PLACEHOLD_74, "KRB5 error code 74" -error_code KRB5PLACEHOLD_75, "KRB5 error code 75" -error_code KRB5PLACEHOLD_76, "KRB5 error code 76" -error_code KRB5PLACEHOLD_77, "KRB5 error code 77" -error_code KRB5PLACEHOLD_78, "KRB5 error code 78" -error_code KRB5PLACEHOLD_79, "KRB5 error code 79" -error_code KRB5PLACEHOLD_80, "KRB5 error code 80" -error_code KRB5PLACEHOLD_81, "KRB5 error code 81" +error_code KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE, "Can't verify certificate" +error_code KRB5KDC_ERR_INVALID_CERTIFICATE, "Invalid certificate" +error_code KRB5KDC_ERR_REVOKED_CERTIFICATE, "Revoked certificate" +error_code KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN, "Revocation status unknown" +error_code KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE, "Revocation status unavailable" +error_code KRB5KDC_ERR_CLIENT_NAME_MISMATCH, "Client name mismatch" +error_code KRB5KDC_ERR_KDC_NAME_MISMATCH, "KDC name mismatch" +error_code KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE, "Inconsistent key purpose" +error_code KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED, "Digest in certificate not accepted" +error_code KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED, "Checksum must be included" +error_code KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED, "Digest in signed-data not accepted" +error_code KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED, "Public key encryption not supported" error_code KRB5PLACEHOLD_82, "KRB5 error code 82" error_code KRB5PLACEHOLD_83, "KRB5 error code 83" error_code KRB5PLACEHOLD_84, "KRB5 error code 84" --- krb5/src/include/k5-int.h +++ krb5/src/include/k5-int.h @@ -835,6 +835,90 @@ error(MIT_DES_KEYSIZE does not equal KRB #ifndef KRB5_PREAUTH__ #define KRB5_PREAUTH__ +#include + +#define CLIENT_ROCK_MAGIC 0x4352434b +/* This structure is passed into the client preauth functions and passed + * back to the "get_data_proc" function so that it can locate the + * requested information. It is opaque to the plugin code and can be + * expanded in the future as new types of requests are defined which + * may require other things to be passed through. */ +typedef struct _krb5_preauth_client_rock { + krb5_magic magic; + krb5_kdc_rep *as_reply; +} krb5_preauth_client_rock; + +/* This structure lets us keep track of all of the modules which are loaded, + * turning the list of modules and their lists of implemented preauth types + * into a single list which we can walk easily. */ +typedef struct _krb5_preauth_context { + int n_modules; + struct _krb5_preauth_context_module { + /* Which of the possibly more than one preauth types which the + * module supports we're using at this point in the list. */ + krb5_preauthtype pa_type; + /* Encryption types which the client claims to support -- we + * copy them directly into the krb5_kdc_req structure during + * krb5_preauth_prepare_request(). */ + krb5_enctype *enctypes; + /* The plugin's per-plugin context and a function to clear it. */ + void *plugin_context; + void (*client_fini)(krb5_context context, void *plugin_context); + /* The module's table, and some of its members, copied here for + * convenience when we populated the list. */ + struct krb5plugin_preauth_client_ftable_v0 *ftable; + const char *name; + int flags, use_count; + krb5_error_code (*client_process)(krb5_context context, + void *plugin_context, + void *request_context, + preauth_get_client_data_proc get_data_proc, + krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data); + krb5_error_code (*client_tryagain)(krb5_context context, + void *plugin_context, + void *request_context, + preauth_get_client_data_proc get_data_proc, + krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *old_pa_data, + krb5_error *err_reply, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **new_pa_data); + void (*client_req_init)(krb5_context context, void *plugin_context, + void **request_context); + void (*client_req_fini)(krb5_context context, void *plugin_context, + void *request_context); + /* The per-request context which the client_req_init() function + * might allocate, which we'll need to clean up later by + * calling the client_req_fini() function. */ + void *request_context; + /* A pointer to the request_context pointer. All modules within + * a plugin will point at the request_context of the first + * module within the plugin. */ + void **request_context_pp; + } *modules; +} krb5_preauth_context; + typedef struct _krb5_pa_enc_ts { krb5_timestamp patimestamp; krb5_int32 pausec; @@ -961,14 +1045,41 @@ void krb5int_populate_gic_opt ( krb5_preauthtype *pre_auth_types, krb5_creds *creds); -krb5_error_code krb5_do_preauth -(krb5_context, krb5_kdc_req *, - krb5_pa_data **, krb5_pa_data ***, - krb5_data *salt, krb5_data *s2kparams, - krb5_enctype *, - krb5_keyblock *, - krb5_prompter_fct, void *, - krb5_gic_get_as_key_fct, void *); +krb5_error_code KRB5_CALLCONV krb5_do_preauth + (krb5_context context, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data **in_padata, krb5_pa_data ***out_padata, + krb5_data *salt, krb5_data *s2kparams, + krb5_enctype *etype, krb5_keyblock *as_key, + krb5_prompter_fct prompter, void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, void *gak_data, + krb5_preauth_client_rock *get_data_rock); +krb5_error_code KRB5_CALLCONV krb5_do_preauth_tryagain + (krb5_context context, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data **in_padata, krb5_pa_data ***out_padata, + krb5_error *err_reply, + krb5_data *salt, krb5_data *s2kparams, + krb5_enctype *etype, krb5_keyblock *as_key, + krb5_prompter_fct prompter, void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, void *gak_data, + krb5_preauth_client_rock *get_data_rock); +void KRB5_CALLCONV krb5_init_preauth_context + (krb5_context); +void KRB5_CALLCONV krb5_free_preauth_context + (krb5_context); +void KRB5_CALLCONV krb5_clear_preauth_context_use_counts + (krb5_context); +void KRB5_CALLCONV krb5_preauth_prepare_request + (krb5_context, krb5_get_init_creds_opt *, krb5_kdc_req *); +void KRB5_CALLCONV krb5_preauth_request_context_init + (krb5_context); +void KRB5_CALLCONV krb5_preauth_request_context_fini + (krb5_context); void KRB5_CALLCONV krb5_free_sam_challenge (krb5_context, krb5_sam_challenge * ); @@ -1059,6 +1174,10 @@ struct _krb5_context { struct krb5plugin_service_locate_ftable *vtbl; void (**locate_fptrs)(void); + /* preauth module stuff */ + struct plugin_dir_handle preauth_plugins; + krb5_preauth_context *preauth_context; + /* error detail info */ struct errinfo err; }; --- krb5/src/include/kdb.h +++ krb5/src/include/kdb.h @@ -171,6 +171,7 @@ typedef struct __krb5_key_salt_tuple { #define KRB5_TL_SECURID_STATE 0x0006 #define KRB5_TL_DB_ARGS 0x7fff #endif /* SECURID */ +#define KRB5_TL_USER_CERTIFICATE 0x0007 /* * Determines the number of failed KDC requests before DISALLOW_ALL_TIX is set --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/include/krb5/preauth_plugin.h @@ -0,0 +1,326 @@ +/* + * + * + * Copyright (c) 2006 Red Hat, Inc. + * Portions copyright (c) 2006 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. + * * Neither the name of Red Hat, Inc., nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE 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 OWNER + * 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. + * + * Preauthentication plugin definitions for Kerberos 5. + */ + +#ifndef KRB5_PREAUTH_PLUGIN_H_INCLUDED +#define KRB5_PREAUTH_PLUGIN_H_INCLUDED +#include + +/* + * While arguments of these types are passed-in, for the most part a preauth + * module can treat them as opaque. If we need keying data, we can ask for + * it directly. + */ +struct _krb5_db_entry_new; +struct _krb5_key_data; +struct _krb5_preauth_client_rock; + +/* + * Preauth mechanism property flags, unified from previous definitions in the + * KDC and libkrb5 sources. + */ + +/* Provides a real answer which we can send back to the KDC (client-only). The + * client assumes that one real answer will be enough. */ +#define PA_REAL 0x00000001 + +/* Doesn't provide a real answer, but must be given a chance to run before any + * REAL mechanism callbacks (client-only). */ +#define PA_INFO 0x00000002 + +/* Causes the KDC to include this mechanism in a list of supported preauth + * types if the user's DB entry flags the user as requiring hardware-based + * preauthentication (server-only). */ +#define PA_HARDWARE 0x00000004 + +/* Causes the KDC to include this mechanism in a list of supported preauth + * types if the user's DB entry flags the user as requiring preauthentication, + * and to fail preauthentication if we can't verify the client data. The + * flipside of PA_SUFFICIENT (server-only). */ +#define PA_REQUIRED 0x00000008 + +/* Causes the KDC to include this mechanism in a list of supported preauth + * types if the user's DB entry flags the user as requiring preauthentication, + * and to mark preauthentication as successful if we can verify the client + * data. The flipside of PA_REQUIRED (server-only). */ +#define PA_SUFFICIENT 0x00000010 + +/* Marks this preauthentication mechanism as one which changes the key which is + * used for encrypting the response to the client. Modules which have this + * flag have their server_return_proc called before modules which do not, and + * are passed over if a previously-called module has modified the encrypting + * key (server-only). */ +#define PA_REPLACES_KEY 0x00000020 + +/* Causes the KDC to check with this preauthentication module even if the + * client has no entry in the realm database. If the module returns a success + * code, continue processing and assume that its return_padata callback will + * supply us with a key for encrypting the AS reply (server-only). */ +/* #define PA_VIRTUAL (0x00000040 | PA_REPLACES_KEY) */ + +/* Not really a padata type, so don't include it in any list of preauth types + * which gets sent over the wire. */ +#define PA_PSEUDO 0x00000080 + +/* + * A server module's callback functions are allowed to request specific types + * of information about the given client or server record or request, even + * though the database records themselves are opaque to the module. + */ +enum krb5plugin_preauth_entry_request_type { + /* The returned krb5_data item holds a DER-encoded X.509 certificate. */ + krb5plugin_preauth_entry_request_certificate = 1, + /* The returned krb5_data_item holds a krb5_deltat. */ + krb5plugin_preauth_entry_max_time_skew = 2, + /* The returned krb5_data_item holds an array of krb5_keyblock structures, + * terminated by an entry with key type = 0. + * Each keyblock should have its contents freed in turn, and then the data + * item itself should be freed. */ + krb5plugin_preauth_keys = 3, + /* The returned krb5_data_item holds the request structure, re-encoded + * using DER. Unless the client implementation is the same as the server + * implementation, there's a good chance that the result will not match + * what the client sent, so don't go creating any fatal errors if it + * doesn't match up. */ + krb5plugin_preauth_request_body = 4 +}; +typedef krb5_error_code +(*preauth_get_entry_data_proc)(krb5_context, + krb5_kdc_req *, + struct _krb5_db_entry_new *, + krb5_int32 request_type, + krb5_data **); + +/* + * A client module's callback functions are allowed to request various + * information to enable it to process a request. + */ +enum krb5plugin_preauth_client_request_type { + /* The returned krb5_data item holds the enctype used to encrypt the + * encrypted portion of the AS_REP packet. */ + krb5plugin_preauth_client_get_etype = 1, + /* Free the data returned from krb5plugin_preauth_client_req_get_etype */ + krb5plugin_preauth_client_free_etype = 2 +}; +typedef krb5_error_code +(*preauth_get_client_data_proc)(krb5_context, + struct _krb5_preauth_client_rock *, + krb5_int32 request_type, + krb5_data **); + +/* + * A callback which will obtain the user's long-term AS key by prompting the + * user for the password, then salting it properly, and so on. For the moment, + * it's identical to the get_as_key callback used inside of libkrb5, but we + * define a new typedef here instead of making the existing one public to + * isolate ourselves from potential future changes. + */ +typedef krb5_error_code +(*preauth_get_as_key_proc)(krb5_context, + krb5_principal, + krb5_enctype, + krb5_prompter_fct, + void *prompter_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + void *gak_data); + +/* + * The function table / structure which a preauth client module must export as + * "preauthentication_client_0_backport_1_6". If the interfaces work correctly, future + * versions of the table will add either more callbacks or more arguments to + * callbacks, and in both cases we'll be able to wrap the v0 functions. + */ +typedef struct krb5plugin_preauth_client_ftable_v0 { + /* Not-usually-visible name. */ + char *name; + + /* Pointer to zero-terminated list of pa_types which this module can + * provide services for. */ + krb5_preauthtype *pa_type_list; + + /* Pointer to zero-terminated list of enc_types which this module claims + * to add support for. */ + krb5_enctype *enctype_list; + + /* Per-plugin initialization/cleanup. The init function is called + * by libkrb5 when the plugin is loaded, and the fini function is + * called before the plugin is unloaded. Both are optional and + * may be called multiple times in case the plugin is used in + * multiple contexts. The returned context lives the lifetime of + * the krb5_context */ + krb5_error_code (*init)(krb5_context context, void **plugin_context); + void (*fini)(krb5_context context, void *plugin_context); + /* A callback which returns flags indicating if the module is a "real" or + * an "info" mechanism, and so on. This function is called for each entry + * in the client_pa_type_list. */ + int (*flags)(krb5_context context, krb5_preauthtype pa_type); + /* Per-request initialization/cleanup. The request_init function is + * called when beginning to process a get_init_creds request and the + * request_fini function is called when processing of the request is + * complete. This is optional. It may be called multiple times in + * the lifetime of a krb5_context. */ + void (*request_init)(krb5_context context, void *plugin_context, + void **request_context); + void (*request_fini)(krb5_context context, void *plugin_context, + void *request_context); + /* Client function which processes server-supplied data in pa_data, + * returns created data in out_pa_data, storing any of its own state in + * client_context if data for the associated preauthentication type is + * needed. It is also called after the AS-REP is received if the AS-REP + * includes preauthentication data of the associated type. + * NOTE! the encoded_previous_request will be NULL the first time this + * function is called, because it is expected to only ever contain the data + * obtained from a previous call to this function. */ + krb5_error_code (*process)(krb5_context context, + void *plugin_context, + void *request_context, + preauth_get_client_data_proc get_data_proc, + struct _krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data); + /* Client function which can attempt to use e-data in the error response to + * try to recover from the given error. If this function is not NULL, and + * it stores data in out_pa_data which is different data from the contents + * of in_pa_data, then the client library will retransmit the request. */ + krb5_error_code (*tryagain)(krb5_context context, + void *plugin_context, + void *request_context, + preauth_get_client_data_proc get_data_proc, + struct _krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *in_pa_data, + krb5_error *error, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data); +} krb5plugin_preauth_client_ftable_v0; + +/* + * The function table / structure which a preauth server module must export as + * "preauthentication_server_0_backport_1_6". NOTE: replace "0" with "1" for the type and + * variable names if this gets picked up by upstream. If the interfaces work + * correctly, future versions of the table will add either more callbacks or + * more arguments to callbacks, and in both cases we'll be able to wrap the v0 + * functions. + */ +typedef struct krb5plugin_preauth_server_ftable_v0 { + /* Not-usually-visible name. */ + char *name; + + /* Pointer to zero-terminated list of pa_types which this module can + * provide services for. */ + krb5_preauthtype *pa_type_list; + + /* Per-plugin initialization/cleanup. The init function is called by the + * KDC when the plugin is loaded, and the fini function is called before + * the plugin is unloaded. Both are optional. */ + krb5_error_code (*init_proc)(krb5_context, void **); + void (*fini_proc)(krb5_context, void *); + /* Return the flags which the KDC should use for this module. This is a + * callback instead of a static value because the module may or may not + * wish to count itself as a hardware preauthentication module (in other + * words, the flags may be affected by the configuration, for example if a + * site administrator can force a particular preauthentication type to be + * supported using only hardware). This function is called for each entry + * entry in the server_pa_type_list. */ + int (*flags_proc)(krb5_context, krb5_preauthtype); + /* Get preauthentication data to send to the client as part of the "you + * need to use preauthentication" error. The module doesn't need to + * actually provide data if the protocol doesn't require it, but it should + * return either zero or non-zero to control whether its padata type is + * included in the list which is sent back to the client. Is not allowed + * to create a context because we have no guarantee that the client will + * ever call again (or that it will hit this server if it does), in which + * case a context might otherwise hang around forever. */ + krb5_error_code (*edata_proc)(krb5_context, krb5_kdc_req *request, + struct _krb5_db_entry_new *client, + struct _krb5_db_entry_new *server, + preauth_get_entry_data_proc, + void *pa_module_context, + krb5_pa_data *data); + /* Verify preauthentication data sent by the client, setting the + * TKT_FLG_PRE_AUTH or TKT_FLG_HW_AUTH flag in the enc_tkt_reply's "flags" + * field as appropriate, and returning nonzero on failure. Can create + * context data for consumption by the return_proc or freepa_proc below. */ + krb5_error_code (*verify_proc)(krb5_context, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data); + /* Generate preauthentication response data to send to the client as part + * of the AS-REP. If it needs to override the key which is used to encrypt + * the response, it can do so. The module is expected (but not required, + * if a freepa_proc is also provided) to free any context data it saved in + * "request_pa_context". */ + krb5_error_code (*return_proc)(krb5_context, krb5_pa_data * padata, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + struct _krb5_key_data *client_keys, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc, + void *pa_module_context, + void **pa_request_context); + /* Free up the server-side per-request context, in cases where + * server_return_proc() didn't or for whatever reason was not called. Can + * be NULL. */ + krb5_error_code (*freepa_reqcontext_proc)(krb5_context, + void *pa_module_context, + void **request_pa_context); +} krb5plugin_preauth_server_ftable_v0; +#endif /* KRB5_PREAUTH_PLUGIN_H_INCLUDED */ --- krb5/src/config/pre.in +++ krb5/src/config/pre.in @@ -194,6 +194,7 @@ prefix=@prefix@ INSTALL_PREFIX=$(prefix) INSTALL_EXEC_PREFIX=@exec_prefix@ exec_prefix=@exec_prefix@ +datarootdir=@datarootdir@ SHLIB_TAIL_COMP=@SHLIB_TAIL_COMP@ datadir = @datadir@ @@ -212,6 +213,7 @@ KRB5_SHLIBDIR = @libdir@$(SHLIB_TAIL_COM KRB5_INCDIR = @includedir@ MODULE_DIR = @libdir@/krb5/plugins KRB5_DB_MODULE_DIR = $(MODULE_DIR)/kdb +KRB5_PA_MODULE_DIR = $(MODULE_DIR)/preauth KRB5_LIBKRB5_MODULE_DIR = $(MODULE_DIR)/libkrb5 KRB5_INCSUBDIRS = \ $(KRB5_INCDIR)/krb5 \ --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/wpse/configure.in @@ -0,0 +1,14 @@ +K5_AC_INIT(configure.in) +enable_shared=yes +build_dynobj=yes +CONFIG_RULES + +AC_CHECK_HEADERS(errno.h string.h) + +KRB5_RUN_FLAGS +dnl The following is for check... +KRB5_BUILD_PROGRAM +KRB5_BUILD_LIBOBJS +KRB5_BUILD_LIBRARY_WITH_DEPS +AC_CONFIG_HEADERS(config.h) +V5_AC_OUTPUT_MAKEFILE --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/wpse/Makefile.in @@ -0,0 +1,42 @@ +thisconfigdir=. +myfulldir=plugins/preauth/wpse +mydir=. +BUILDTOP=$(REL)..$(S)..$(S).. +KRB5_RUN_ENV = @KRB5_RUN_ENV@ +KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ; +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) +MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR) +DEFS=@DEFS@ + +LOCALINCLUDES = -I../../../include/krb5 -I. + +LIBBASE=wpse +LIBMAJOR=0 +LIBMINOR=0 +SO_EXT=.so +RELDIR=../plugins/preauth/wpse +# Depends on libk5crypto and libkrb5 +SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(TOPLIBD)/libkrb5$(SHLIBEXT) +SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto $(SUPPORT_LIB) $(LIBS) + +SHLIB_DIRS=-L$(TOPLIBD) +SHLIB_RDIRS=$(KRB5_LIBDIR) +STOBJLISTS=OBJS.ST +STLIBOBJS=wpse_main.o + +SRCS=wpse_main.c + +all-unix:: $(LIBBASE)$(SO_EXT) +install-unix:: install-libs +clean-unix:: clean-libs clean-libobjs + +clean:: + $(RM) lib$(LIBBASE)$(SO_EXT) + +@libnover_frag@ +@libobj_frag@ + +# +++ Dependency line eater +++ --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/wpse/wpse_main.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2006 Red Hat, Inc. + * 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. + * * Neither the name of Red Hat, Inc., nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE 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 OWNER + * 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. + */ + +/* Worst. Preauthentication. Scheme. Ever. */ + +#ident "$Id$" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include +#include + +#include +#include + +/* This is not a standardized value. It's defined here only to make it easier + * to change in this module. */ +#define KRB5_PADATA_WPSE_REQ 131 + +static int +client_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) +{ + return PA_REAL; +} + +static krb5_error_code +client_init(krb5_context kcontext, void **ctx) +{ + int *pctx; + + pctx = malloc(sizeof(int)); + if (pctx == NULL) + return ENOMEM; + *pctx = 0; + *ctx = pctx; + return 0; +} + +static void +client_fini(krb5_context kcontext, void *ctx) +{ + int *pctx; + + pctx = ctx; + if (pctx) { +#ifdef DEBUG + fprintf(stderr, "wpse module called total of %d times\n", *pctx); +#endif + free(pctx); + } +} + +static krb5_error_code +client_process(krb5_context kcontext, + void *plugin_context, + void *request_context, + preauth_get_client_data_proc client_get_data_proc, + struct _krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data) +{ + krb5_pa_data *send_pa; + krb5_int32 nnonce, enctype; + krb5_keyblock *kb; + krb5_error_code status; + int *pctx; + +#ifdef DEBUG + fprintf(stderr, "%d bytes of preauthentication data (type %d)\n", + pa_data->length, pa_data->pa_type); +#endif + + pctx = plugin_context; + if (pctx) { + (*pctx)++; + } + + if (pa_data->length == 0) { + /* Create preauth data. */ + send_pa = malloc(sizeof(krb5_pa_data)); + if (send_pa == NULL) + return ENOMEM; + send_pa->pa_type = KRB5_PADATA_WPSE_REQ; + send_pa->length = 4; + send_pa->contents = malloc(4); + if (send_pa->contents == NULL) { + free(send_pa); + return ENOMEM; + } + /* Store the preauth data. */ + nnonce = htonl(request->nonce); + memcpy(send_pa->contents, &nnonce, 4); + *out_pa_data = send_pa; + } else { + /* A reply from the KDC. Conventionally this would be + * indicated by a different preauthentication type, but this + * mechanism/implementation doesn't do that. */ + if (pa_data->length > 4) { + memcpy(&enctype, pa_data->contents, 4); + kb = NULL; + status = krb5_init_keyblock(kcontext, ntohl(enctype), + pa_data->length - 4, &kb); + if (status != 0) + return status; + memcpy(kb->contents, pa_data->contents + 4, pa_data->length - 4); +#ifdef DEBUG + fprintf(stderr, "Recovered key type=%d, length=%d.\n", + kb->enctype, kb->length); +#endif + status = krb5_copy_keyblock_contents(kcontext, kb, as_key); + krb5_free_keyblock(kcontext, kb); + return status; + } + return KRB5KRB_ERR_GENERIC; + } + return 0; +} + +#define WPSE_MAGIC 0x77707365 +typedef struct _wpse_req_ctx +{ + int magic; + int value; +} wpse_req_ctx; + +static void +client_req_init(krb5_context kcontext, void *plugin_context, void **req_context_p) +{ + wpse_req_ctx *ctx; + + *req_context_p = NULL; + + /* Allocate a request context. Useful for verifying that we do in fact + * do per-request cleanup. */ + ctx = (wpse_req_ctx *) malloc(sizeof(*ctx)); + if (ctx == NULL) + return; + ctx->magic = WPSE_MAGIC; + ctx->value = 0xc0dec0de; + + *req_context_p = ctx; +} + +static void +client_req_cleanup(krb5_context kcontext, void *plugin_context, void *req_context) +{ + wpse_req_ctx *ctx = (wpse_req_ctx *)req_context; + + if (ctx) { +#ifdef DEBUG + fprintf(stderr, "client_req_cleanup: req_ctx at %p has magic %x and value %x\n", + ctx, ctx->magic, ctx->value); +#endif + if (ctx->magic != WPSE_MAGIC) { +#ifdef DEBUG + fprintf(stderr, "client_req_cleanup: req_context at %p has bad magic value %x\n", + ctx, ctx->magic); +#endif + return; + } + free(ctx); + } + return; +} + +/* Free state. */ +static krb5_error_code +server_free_pa_request_context(krb5_context kcontext, void *plugin_context, + void **request_context) +{ + if (*request_context != NULL) { + free(*request_context); + *request_context = NULL; + } + return 0; +} + +/* Obtain and return any preauthentication data (which is destined for the + * client) which matches type data->pa_type. */ +static krb5_error_code +server_get_edata(krb5_context kcontext, + krb5_kdc_req *request, + struct _krb5_db_entry_new *client, + struct _krb5_db_entry_new *server, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + krb5_pa_data *data) +{ + /* Return zero bytes of data. */ + data->length = 0; + data->contents = NULL; + return 0; +} + +/* Verify a request from a client. */ +static krb5_error_code +server_verify(krb5_context kcontext, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data) +{ + krb5_int32 nnonce; + krb5_data *test_edata; + + /* Verify the preauth data. */ + if (data->length != 4) + return KRB5KDC_ERR_PREAUTH_FAILED; + memcpy(&nnonce, data->contents, 4); + nnonce = ntohl(nnonce); + if (memcmp(&nnonce, &request->nonce, 4) != 0) + return KRB5KDC_ERR_PREAUTH_FAILED; + /* Note that preauthentication succeeded. */ + enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; + enc_tkt_reply->flags |= TKT_FLG_HW_AUTH; + /* Allocate a context. Useful for verifying that we do in fact do + * per-request cleanup. */ + if (*pa_request_context == NULL) + *pa_request_context = malloc(4); + + /* Return edata to exercise code that handles edata... */ + test_edata = malloc(sizeof(*test_edata)); + if (test_edata != NULL) { + test_edata->data = malloc(20); + if (test_edata->data == NULL) { + free(test_edata); + } else { + test_edata->length = 20; + memset(test_edata->data, '#', 20); /* fill it with junk */ + *e_data = test_edata; + } + } + return 0; +} + +/* Create the response for a client. */ +static krb5_error_code +server_return(krb5_context kcontext, + krb5_pa_data *padata, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + struct _krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + void **pa_request_context) +{ + /* This module does a couple of dumb things. It tags its reply with + * the same type as the initial challenge (expecting the client to sort + * out whether there's anything useful in there). Oh, and it replaces + * the AS reply key with one which is sent in the clear. */ + krb5_keyblock *kb; + krb5_int32 enctype; + int i; + + *send_pa = NULL; + + /* We'll want a key with the first supported enctype. */ + for (i = 0; i < request->nktypes; i++) { + kb = NULL; + if (krb5_init_keyblock(kcontext, request->ktype[i], 0, &kb) == 0) { + break; + } + } + if (i >= request->nktypes) { + /* No matching cipher type found. */ + return 0; + } + + /* Randomize a key and save it for the client. */ + if (krb5_c_make_random_key(kcontext, request->ktype[i], kb) != 0) { + krb5_free_keyblock(kcontext, kb); + return 0; + } +#ifdef DEBUG + fprintf(stderr, "Generated random key, type=%d, length=%d.\n", + kb->enctype, kb->length); +#endif + + *send_pa = malloc(sizeof(krb5_pa_data)); + if (*send_pa == NULL) { + krb5_free_keyblock(kcontext, kb); + return ENOMEM; + } + (*send_pa)->pa_type = KRB5_PADATA_WPSE_REQ; + (*send_pa)->length = 4 + kb->length; + (*send_pa)->contents = malloc(4 + kb->length); + if ((*send_pa)->contents == NULL) { + free(*send_pa); + *send_pa = NULL; + krb5_free_keyblock(kcontext, kb); + return ENOMEM; + } + + /* Store the preauth data. */ + enctype = htonl(kb->enctype); + memcpy((*send_pa)->contents, &enctype, 4); + memcpy((*send_pa)->contents + 4, kb->contents, kb->length); + krb5_free_keyblock_contents(kcontext, encrypting_key); + krb5_copy_keyblock_contents(kcontext, kb, encrypting_key); + + /* Clean up. */ + krb5_free_keyblock(kcontext, kb); + + return 0; +} + +static int +server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) +{ + return PA_HARDWARE | PA_REPLACES_KEY; +} + +static krb5_preauthtype supported_client_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0}; +static krb5_preauthtype supported_server_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0}; + +struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0_backport_1_6 = { + "wpse", /* name */ + &supported_client_pa_types[0], /* pa_type_list */ + NULL, /* enctype_list */ + client_init, /* plugin init function */ + client_fini, /* plugin fini function */ + client_get_flags, /* get flags function */ + client_req_init, /* request init function */ + client_req_cleanup, /* request fini function */ + client_process, /* process function */ + NULL, /* try_again function */ +}; + +struct krb5plugin_preauth_server_ftable_v0 preauthentication_server_0_backport_1_6 = { + "wpse", + &supported_server_pa_types[0], + NULL, + NULL, + server_get_flags, + server_get_edata, + server_verify, + server_return, + server_free_pa_request_context, +}; --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/wpse/wpse.exports @@ -0,0 +1,2 @@ +preauthentication_client_0_backport_1_6 +preauthentication_server_0_backport_1_6 --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/cksum_body/cksum_body.exports @@ -0,0 +1,2 @@ +preauthentication_client_0_backport_1_6 +preauthentication_server_0_backport_1_6 --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/cksum_body/configure.in @@ -0,0 +1,14 @@ +K5_AC_INIT(configure.in) +enable_shared=yes +build_dynobj=yes +CONFIG_RULES + +AC_CHECK_HEADERS(errno.h string.h) + +KRB5_RUN_FLAGS +dnl The following is for check... +KRB5_BUILD_PROGRAM +KRB5_BUILD_LIBOBJS +KRB5_BUILD_LIBRARY_WITH_DEPS +AC_CONFIG_HEADERS(config.h) +V5_AC_OUTPUT_MAKEFILE --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/cksum_body/Makefile.in @@ -0,0 +1,42 @@ +thisconfigdir=. +myfulldir=plugins/preauth/cksum_body +mydir=. +BUILDTOP=$(REL)..$(S)..$(S).. +KRB5_RUN_ENV = @KRB5_RUN_ENV@ +KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ; +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) +MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR) +DEFS=@DEFS@ + +LOCALINCLUDES = -I../../../include/krb5 -I. + +LIBBASE=cksum_body +LIBMAJOR=0 +LIBMINOR=0 +SO_EXT=.so +RELDIR=../plugins/preauth/cksum_body +# Depends on libk5crypto and libkrb5 +SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(TOPLIBD)/libkrb5$(SHLIBEXT) +SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto $(SUPPORT_LIB) $(LIBS) + +SHLIB_DIRS=-L$(TOPLIBD) +SHLIB_RDIRS=$(KRB5_LIBDIR) +STOBJLISTS=OBJS.ST +STLIBOBJS=cksum_body_main.o + +SRCS= $(srcdir)/cksum_body_main.c + +all-unix:: $(LIBBASE)$(SO_EXT) +install-unix:: install-libs +clean-unix:: clean-libs clean-libobjs + +clean:: + $(RM) lib$(LIBBASE)$(SO_EXT) + +@libnover_frag@ +@libobj_frag@ + +# +++ Dependency line eater +++ --- /dev/null 2007-01-10 09:59:42.964619257 -0500 +++ krb5/src/plugins/preauth/cksum_body/cksum_body_main.c @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2006 Red Hat, Inc. + * 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. + * * Neither the name of Red Hat, Inc., nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE 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 OWNER + * 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. + */ + +/* + * Checksum the request body with the user's long-term key. + * + * The e-data from the KDC is a list of network-byte-order 32-bit integers + * listing key types which the KDC has for the user. + * + * The client uses one of these key types to generate a checksum over the body + * of the request, and includes the checksum in the AS-REQ as preauthentication + * data. + * + * The AS-REP carries no preauthentication data for this scheme. + */ + +#ident "$Id$" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include +#include + +#include +#include + +/* This is not a standardized value. It's defined here only to make it easier + * to change in this module. */ +#define KRB5_PADATA_CKSUM_BODY_REQ 130 + +struct server_stats{ + int successes, failures; +}; + +static int +client_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) +{ + return PA_REAL; +} + +static krb5_error_code +client_process(krb5_context kcontext, + void *client_plugin_context, + void *client_request_context, + preauth_get_client_data_proc client_get_data_proc, + struct _krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data **out_pa_data) +{ + krb5_pa_data *send_pa; + krb5_checksum checksum; + krb5_enctype enctype; + krb5_cksumtype *cksumtypes; + krb5_error_code status = 0; + krb5_int32 cksumtype, *enctypes; + unsigned int i, n_enctypes, cksumtype_count; + + memset(&checksum, 0, sizeof(checksum)); + + /* Get the user's long-term key if we haven't asked for it yet. Try + * all of the encryption types which the server supports. */ + if (as_key->length == 0) { + if ((pa_data != NULL) && (pa_data->length >= 4)) { +#ifdef DEBUG + fprintf(stderr, "%d bytes of preauth data.\n", pa_data->length); +#endif + n_enctypes = pa_data->length / 4; + enctypes = (krb5_int32*) pa_data->contents; + } else { + n_enctypes = request->nktypes; + } + for (i = 0; i < n_enctypes; i++) { + if ((pa_data != NULL) && (pa_data->length >= 4)) { + memcpy(&enctype, pa_data->contents + 4 * i, 4); + enctype = ntohl(enctype); + } else { + enctype = request->ktype[i]; + } +#ifdef DEBUG + fprintf(stderr, "Asking for AS key (type = %d).\n", enctype); +#endif + status = (*gak_fct)(kcontext, request->client, enctype, + prompter, prompter_data, + salt, s2kparams, as_key, gak_data); + if (status == 0) + break; + } + if (status != 0) + return status; + } +#ifdef DEBUG + fprintf(stderr, "Got AS key (type = %d).\n", as_key->enctype); +#endif + + /* Determine an appropriate checksum type for this key. */ + cksumtype_count = 0; + cksumtypes = NULL; + status = krb5_c_keyed_checksum_types(kcontext, as_key->enctype, + &cksumtype_count, &cksumtypes); + if (status != 0) + return status; + + /* Generate the checksum. */ + for (i = 0; i < cksumtype_count; i++) { + status = krb5_c_make_checksum(kcontext, cksumtypes[i], as_key, + KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + encoded_request_body, + &checksum); + if (status == 0) { +#ifdef DEBUG + fprintf(stderr, "Made checksum (type = %d, %d bytes).\n", + checksum.checksum_type, encoded_request_body->length); +#endif + break; + } + } + cksumtype = htonl(cksumtypes[i]); + krb5_free_cksumtypes(kcontext, cksumtypes); + if (status != 0) { + if (checksum.length > 0) + krb5_free_checksum_contents(kcontext, &checksum); + return status; + } + + /* Allocate the preauth data structure. */ + send_pa = malloc(sizeof(krb5_pa_data)); + if (send_pa == NULL) { + krb5_free_checksum_contents(kcontext, &checksum); + return ENOMEM; + } + send_pa->pa_type = KRB5_PADATA_CKSUM_BODY_REQ; + send_pa->length = 4 + checksum.length; + send_pa->contents = malloc(4 + checksum.length); + if (send_pa->contents == NULL) { + krb5_free_checksum_contents(kcontext, &checksum); + free(send_pa); + return ENOMEM; + } + + /* Store the checksum. */ + memcpy(send_pa->contents, &cksumtype, 4); + memcpy(send_pa->contents + 4, checksum.contents, checksum.length); + *out_pa_data = send_pa; + + /* Clean up. */ + krb5_free_checksum_contents(kcontext, &checksum); + + return 0; +} + +/* Initialize and tear down the server-side module, and do stat tracking. */ +static krb5_error_code +server_init(krb5_context kcontext, void **module_context) +{ + struct server_stats *stats; + stats = malloc(sizeof(struct server_stats)); + if (stats == NULL) + return ENOMEM; + stats->successes = 0; + stats->failures = 0; + *module_context = stats; + return 0; +} +static void +server_fini(krb5_context kcontext, void *module_context) +{ + struct server_stats *stats; + stats = module_context; + if (stats != NULL) { +#ifdef DEBUG + fprintf(stderr, "Total: %d clients failed, %d succeeded.\n", + stats->failures, stats->successes); +#endif + free(stats); + } +} + +/* Obtain and return any preauthentication data (which is destined for the + * client) which matches type data->pa_type. */ +static krb5_error_code +server_get_edata(krb5_context kcontext, + krb5_kdc_req *request, + struct _krb5_db_entry_new *client, + struct _krb5_db_entry_new *server, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + krb5_pa_data *data) +{ + krb5_data *key_data; + krb5_keyblock *keys, *key; + krb5_int32 *enctypes, enctype; + int i; + + /* Retrieve the client's keys. */ + key_data = NULL; + if ((*server_get_entry_data)(kcontext, request, client, + krb5plugin_preauth_keys, &key_data) != 0) { +#ifdef DEBUG + fprintf(stderr, "Error retrieving client keys.\n"); +#endif + return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + } + + /* Count which types of keys we've got, freeing the contents, which we + * don't need at this point. */ + keys = (krb5_keyblock *) key_data->data; + key = NULL; + for (i = 0; keys[i].enctype != 0; i++) + krb5_free_keyblock_contents(kcontext, &keys[i]); + + /* Return the list of encryption types. */ + enctypes = malloc((unsigned)i * 4); + if (enctypes == NULL) { + krb5_free_data(kcontext, key_data); + return ENOMEM; + } +#ifdef DEBUG + fprintf(stderr, "Supported enctypes = {"); +#endif + for (i = 0; keys[i].enctype != 0; i++) { +#ifdef DEBUG + fprintf(stderr, "%s%d", (i > 0) ? ", " : "", keys[i].enctype); +#endif + enctype = htonl(keys[i].enctype); + memcpy(&enctypes[i], &enctype, 4); + } +#ifdef DEBUG + fprintf(stderr, "}.\n"); +#endif + data->pa_type = KRB5_PADATA_CKSUM_BODY_REQ; + data->length = (i * 4); + data->contents = (unsigned char *) enctypes; + krb5_free_data(kcontext, key_data); + return 0; +} + +/* Verify a request from a client. */ +static krb5_error_code +server_verify(krb5_context kcontext, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data) +{ + krb5_int32 cksumtype; + krb5_checksum checksum; + krb5_boolean valid; + krb5_data *key_data, *req_body; + krb5_keyblock *keys, *key; + size_t length; + int i; + unsigned int j, cksumtypes_count; + krb5_cksumtype *cksumtypes; + krb5_error_code status; + struct server_stats *stats; + krb5_data *test_edata; + + stats = pa_module_context; + + /* Verify the preauth data. Start with the checksum type. */ + if (data->length < 4) { + stats->failures++; + return KRB5KDC_ERR_PREAUTH_FAILED; + } + memcpy(&cksumtype, data->contents, 4); + memset(&checksum, 0, sizeof(checksum)); + checksum.checksum_type = ntohl(cksumtype); + + /* Verify that the amount of data we have left is what we expect. */ + if (krb5_c_checksum_length(kcontext, checksum.checksum_type, + &length) != 0) { +#ifdef DEBUG + fprintf(stderr, "Error determining checksum size (type = %d). " + "Is it supported?\n", checksum.checksum_type); +#endif + stats->failures++; + return KRB5KDC_ERR_SUMTYPE_NOSUPP; + } + if (data->length - 4 != length) { +#ifdef DEBUG + fprintf(stderr, "Checksum size doesn't match client packet size.\n"); +#endif + stats->failures++; + return KRB5KDC_ERR_PREAUTH_FAILED; + } + checksum.length = length; + + /* Pull up the client's keys. */ + key_data = NULL; + if ((*server_get_entry_data)(kcontext, request, client, + krb5plugin_preauth_keys, &key_data) != 0) { +#ifdef DEBUG + fprintf(stderr, "Error retrieving client keys.\n"); +#endif + stats->failures++; + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* Find the key which would have been used to generate the checksum. */ + keys = (krb5_keyblock *) key_data->data; + key = NULL; + for (i = 0; keys[i].enctype != 0; i++) { + key = &keys[i]; + cksumtypes_count = 0; + cksumtypes = NULL; + if (krb5_c_keyed_checksum_types(kcontext, key->enctype, + &cksumtypes_count, &cksumtypes) != 0) + continue; + for (j = 0; j < cksumtypes_count; j++) { + if (cksumtypes[j] == checksum.checksum_type) + break; + } + if (cksumtypes != NULL) + krb5_free_cksumtypes(kcontext, cksumtypes); + if (j < cksumtypes_count) { +#ifdef DEBUG + fprintf(stderr, "Found checksum key.\n"); +#endif + break; + } + } + if ((key == NULL) || (key->enctype == 0)) { + for (i = 0; keys[i].enctype != 0; i++) + krb5_free_keyblock_contents(kcontext, &keys[i]); + krb5_free_data(kcontext, key_data); + stats->failures++; + return KRB5KDC_ERR_SUMTYPE_NOSUPP; + } + + /* Save a copy of the key. */ + if (krb5_copy_keyblock(kcontext, &keys[i], &key) != 0) { + for (i = 0; keys[i].enctype != 0; i++) + krb5_free_keyblock_contents(kcontext, &keys[i]); + krb5_free_data(kcontext, key_data); + stats->failures++; + return KRB5KDC_ERR_SUMTYPE_NOSUPP; + } + for (i = 0; keys[i].enctype != 0; i++) + krb5_free_keyblock_contents(kcontext, &keys[i]); + krb5_free_data(kcontext, key_data); + + /* Rebuild a copy of the client's request-body. If we were serious + * about doing this with any chance of working interoperability, we'd + * extract the structure directly from the req_pkt structure. This + * will probably work if it's us on both ends, though. */ + req_body = NULL; + if ((*server_get_entry_data)(kcontext, request, client, + krb5plugin_preauth_request_body, + &req_body) != 0) { + krb5_free_keyblock(kcontext, key); + stats->failures++; + return KRB5KDC_ERR_PREAUTH_FAILED; + } + +#ifdef DEBUG + fprintf(stderr, "AS key type %d, checksum type %d, %d bytes.\n", + key->enctype, checksum.checksum_type, req_body->length); +#endif + + /* Verify the checksum itself. */ + checksum.contents = data->contents + 4; + valid = FALSE; + status = krb5_c_verify_checksum(kcontext, key, + KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + req_body, &checksum, &valid); + + /* Clean up. */ + krb5_free_data(kcontext, req_body); + krb5_free_keyblock(kcontext, key); + + /* Evaluate our results. */ + if ((status != 0) || (!valid)) { +#ifdef DEBUG + if (status != 0) { + fprintf(stderr, "Error in checksum verification.\n"); + } else { + fprintf(stderr, "Checksum mismatch.\n"); + } +#endif + /* Return edata to exercise code that handles edata... */ + test_edata = malloc(sizeof(*test_edata)); + if (test_edata != NULL) { + test_edata->data = malloc(20); + if (test_edata->data == NULL) { + free(test_edata); + } else { + test_edata->length = 20; + memset(test_edata->data, 'F', 20); /* fill it with junk */ + *e_data = test_edata; + } + } + stats->failures++; + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* Return edata to exercise code that handles edata... */ + test_edata = malloc(sizeof(*test_edata)); + if (test_edata != NULL) { + test_edata->data = malloc(20); + if (test_edata->data == NULL) { + free(test_edata); + } else { + test_edata->length = 20; + memset(test_edata->data, 'S', 20); /* fill it with junk */ + *e_data = test_edata; + } + } + + /* Note that preauthentication succeeded. */ + enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; + stats->successes++; + return 0; +} + +/* Create the response for a client. */ +static krb5_error_code +server_return(krb5_context kcontext, + krb5_pa_data *padata, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + struct _krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_module_context, + void **pa_request_context) +{ + /* We don't need to send data back on the return trip. */ + *send_pa = NULL; + return 0; +} + +static int +server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) +{ + return PA_SUFFICIENT; +} + +static krb5_preauthtype supported_client_pa_types[] = { + KRB5_PADATA_CKSUM_BODY_REQ, 0, +}; +static krb5_preauthtype supported_server_pa_types[] = { + KRB5_PADATA_CKSUM_BODY_REQ, 0, +}; + +struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0_backport_1_6 = { + "cksum_body", /* name */ + &supported_client_pa_types[0], /* pa_type_list */ + NULL, /* enctype_list */ + NULL, /* plugin init function */ + NULL, /* plugin fini function */ + client_get_flags, /* get flags function */ + NULL, /* request init function */ + NULL, /* request fini function */ + client_process, /* process function */ + NULL, /* try_again function */ +}; + +struct krb5plugin_preauth_server_ftable_v0 preauthentication_server_0_backport_1_6 = { + "cksum_body", + &supported_server_pa_types[0], + server_init, + server_fini, + server_get_flags, + server_get_edata, + server_verify, + server_return, + NULL +}; --- krb5/src/configure.in +++ krb5/src/configure.in @@ -900,7 +900,7 @@ fi if test -n "$KRB4_LIB"; then K5_GEN_MAKEFILE(lib/krb4) fi -AC_CONFIG_SUBDIRS(lib/apputils plugins/kdb/db2 appl tests) +AC_CONFIG_SUBDIRS(lib/apputils plugins/kdb/db2 plugins/preauth/wpse plugins/preauth/cksum_body appl tests) dnl if false; then AC_CHECK_HEADERS(Python.h python2.3/Python.h) --- krb5/src/Makefile.in +++ krb5/src/Makefile.in @@ -3,8 +3,8 @@ datadir=@datadir@ thisconfigdir=. myfulldir=. mydir=. -# Don't build sample by default: plugins/locate/python -SUBDIRS=util include lib @krb524@ kdc kadmin slave clients \ +# Don't build sample by default: plugins/locate/python plugins/preauth/wpse plugins/preauth/cksum_body +SUBDIRS=util include lib @krb524@ kdc kadmin slave clients \ plugins/kdb/db2 \ appl tests \ config-files gen-manpages --- krb5/src/config-files/krb5.conf.M +++ krb5/src/config-files/krb5.conf.M @@ -156,6 +156,12 @@ libraries, use a value of 3 to use the C instead. This field is ignored when its value is incompatible with the session key type. +.IP preferred_preauth_types +This allows you to set the preferred preauthentication types which the +client will attempt before others which may be advertised by a KDC. The +default value for this setting is "17, 16, 15, 14", which forces libkrb5 +to attempt to use PKINIT if it is supported. + .IP ccache_type User this parameter on systems which are DCE clients, to specify the type of cache to be created by kinit, or when forwarded tickets are @@ -169,7 +175,7 @@ Specifies the location of the Kerberos V "/etc/srvtab". .IP krb4_config -Specifies the location of hte Kerberos V4 configuration file. Default +Specifies the location of the Kerberos V4 configuration file. Default is "/etc/krb.conf". .IP krb4_realms