Hi, I posted a patch to bugzilla (http://bugs.freeradius.org/show_bug.cgi?id=296) which adds kerberos hardware preauthentication support to rlm_krb5 when using MIT krb5 libs. Also pasted below. Any feedback from people involved with rlm_krb5 or other modules which use challenge-response would be appreciated. This patch takes advantage of the krb5 libs (and hence rlm_krb5) being thread-unsafe already, but could be changed if that bothers people. --ben --- src/modules/rlm_krb5/rlm_krb5.c.dist 2005-11-18 18:06:03.000000000 -0500 +++ src/modules/rlm_krb5/rlm_krb5.c 2005-11-18 21:44:49.000000000 -0500 @@ -20,6 +20,7 @@ * Copyright 2000 The FreeRADIUS server project * Copyright 2000 Nathan Neulinger <nneul@umr.edu> * Copyright 2000 Alan DeKok <aland@ox.org> + * Copyright 2005 Benjamin Bennett <ben@psc.edu> */ @@ -39,6 +40,8 @@ #include <krb5.h> #include <com_err.h> +REQUEST *global_request; + typedef struct rlm_krb5_t { const char *keytab; const char *service_princ; @@ -205,12 +208,83 @@ return 0; } +#ifndef HEIMDAL_KRB5 +static krb5_error_code KRB5_CALLCONV +krb5_radius_prompter(krb5_context context, void *data, const char *name, + const char *banner, int num_prompts, + krb5_prompt prompts[]) +{ + VALUE_PAIR *state; + VALUE_PAIR *reply; + char *challenge; + char *newstate; + int clen; + int r; + + /* Squelch unused param warnings */ + context = context; + data = data; + + if(num_prompts != 1) { + radlog(L_DBG, "rlm_krb5: [%s] krb5_radius_prompter failed: num_prompts (%d) != 1", global_request->username->strvalue, num_prompts); + global_request->reply->code = PW_AUTHENTICATION_REJECT; + return KRB5_LIBOS_CANTREADPWD; + } + + state = pairfind(global_request->packet->vps, PW_STATE); + + if(!state) { /* NEW Request */ + radlog(L_DBG, "rlm_krb5: [%s] krb5_radius_prompter: additional token required", global_request->username->strvalue); + + /* Setup challenge prompt */ + clen = strlen(name)+strlen(banner)+strlen(prompts[0].prompt)+5; + challenge = rad_malloc(clen); + snprintf(challenge, clen, "%s\n%s\n%s: ", name, banner, + prompts[0].prompt); + + /* Re-encode User-Password to send back */ + rad_pwencode((char *)global_request->password->strvalue, + &global_request->password->length, + global_request->secret, + (char *)global_request->packet->vector); + + /* Setup State with current Authenticator and User-Password */ + newstate = rad_malloc(AUTH_VECTOR_LEN+ + global_request->password->length+1); + memcpy(newstate, global_request->packet->vector, AUTH_VECTOR_LEN); + memcpy(newstate+AUTH_VECTOR_LEN, global_request->password->strvalue, + global_request->password->length+1); + + /* Put State into a challenge reply */ + reply = pairmake("Reply-Message", challenge, T_OP_EQ); + pairadd(&global_request->reply->vps, reply); + state = pairmake("State", newstate, T_OP_EQ); + pairadd(&global_request->reply->vps, state); + global_request->reply->code = PW_ACCESS_CHALLENGE; + free(challenge); + free(newstate); + r = KRB5_LIBOS_CANTREADPWD; + } else { /* Challenge Response */ + radlog(L_DBG, "rlm_krb5: [%s] krb5_radius_prompter: using additional token", global_request->username->strvalue); + + strncpy(prompts[0].reply->data, + (char *)global_request->password->strvalue, + prompts[0].reply->length); + prompts[0].reply->data[(prompts[0].reply->length)-1] = '\0'; + prompts[0].reply->length = strlen(prompts[0].reply->data); + r = 0; + } + return r; +} +#endif + /* validate userid/passwd */ /* MIT case */ #ifndef HEIMDAL_KRB5 static int krb5_auth(void *instance, REQUEST *request) { int r; + VALUE_PAIR *state; krb5_data tgtname = { 0, @@ -218,11 +292,13 @@ KRB5_TGS_NAME }; krb5_creds kcreds; + krb5_get_init_creds_opt opts; krb5_ccache ccache; char cache_name[L_tmpnam + 8]; krb5_context context = *((rlm_krb5_t *)instance)->context; /* copy data */ - const char *user, *pass; + const char *user; + char *pass; /* * We can only authenticate user requests which HAVE @@ -271,6 +347,8 @@ /* * Actually perform the authentication */ + krb5_get_init_creds_opt_init(&opts); + memset((char *)&kcreds, 0, sizeof(kcreds)); if ( (r = krb5_parse_name(context, user, &kcreds.client)) ) { @@ -302,22 +380,40 @@ return RLM_MODULE_REJECT; } - if ( (r = krb5_get_in_tkt_with_password(context, - 0, NULL, NULL, NULL, pass, ccache, &kcreds, 0)) ) { - radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s", - user, error_message(r)); - krb5_free_cred_contents(context, &kcreds); - krb5_cc_destroy(context, ccache); - return RLM_MODULE_REJECT; - } else { + global_request = request; + state = pairfind(request->packet->vps, PW_STATE); + if(state) { + radlog(L_DBG, "rlm_krb5: [%s] challenge response", user); + if(state->length <= AUTH_VECTOR_LEN) { + radlog(L_AUTH, "rlm_krb5: [%s] BAD State data", user); + return RLM_MODULE_REJECT; + } + pass = (char *)state->strvalue+AUTH_VECTOR_LEN; + rad_pwdecode(pass, state->length-AUTH_VECTOR_LEN, + request->secret, (char *)state->strvalue); + } + + if(!(r = krb5_get_init_creds_password(context, &kcreds, + kcreds.client, pass, krb5_radius_prompter, NULL, + (krb5_deltat)0, NULL, &opts))) { + if( (r = krb5_cc_store_cred(context, ccache, &kcreds)) ) { + radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_store_cred failed: %s", + user, error_message(r)); + return RLM_MODULE_REJECT; + } /* Now verify the KDC's identity. */ r = verify_krb5_tgt(context, (rlm_krb5_t *)instance, user, ccache); - krb5_free_cred_contents(context, &kcreds); - krb5_cc_destroy(context, ccache); - return r; + } else if(r == KRB5_LIBOS_CANTREADPWD) { + r = RLM_MODULE_HANDLED; + } else { + radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_c_p failed: %s", user, + error_message(r)); + r = RLM_MODULE_REJECT; } - return RLM_MODULE_REJECT; + krb5_free_cred_contents(context, &kcreds); + krb5_cc_destroy(context, ccache); + return r; } #else /* HEIMDAL_KRB5 */