#include "k5-int.h"
#include "k5-input.h"
#include "k5-spake.h"
#include "groups.h"
#include "trace.h"
#include "iana.h"
#include "util.h"
#include <krb5/kdcpreauth_plugin.h>
static void
parse_data(struct k5input *in, krb5_data *out)
{
out->length = k5_input_get_uint32_be(in);
out->data = (char *)k5_input_get_bytes(in, out->length);
out->magic = KV5M_DATA;
}
static krb5_error_code
parse_cookie(const krb5_data *cookie, int *stage_out, int32_t *group_out,
krb5_data *spake_out, krb5_data *thash_out,
krb5_data *factors_out)
{
struct k5input in;
int version, stage;
int32_t group;
krb5_data thash, spake, factors;
*spake_out = *thash_out = *factors_out = empty_data();
k5_input_init(&in, cookie->data, cookie->length);
version = k5_input_get_uint16_be(&in);
if (version != 1)
return KRB5KDC_ERR_PREAUTH_FAILED;
stage = k5_input_get_uint16_be(&in);
group = k5_input_get_uint32_be(&in);
parse_data(&in, &spake);
parse_data(&in, &thash);
if (in.status)
return in.status;
factors = make_data((char *)in.ptr, in.len);
*stage_out = stage;
*group_out = group;
*spake_out = spake;
*thash_out = thash;
*factors_out = factors;
return 0;
}
static void
marshal_data(struct k5buf *buf, const krb5_data *data)
{
k5_buf_add_uint32_be(buf, data->length);
k5_buf_add_len(buf, data->data, data->length);
}
static krb5_error_code
make_cookie(int stage, int32_t group, const krb5_data *spake,
const krb5_data *thash, krb5_data *cookie_out)
{
struct k5buf buf;
*cookie_out = empty_data();
k5_buf_init_dynamic_zap(&buf);
k5_buf_add_uint16_be(&buf, 1);
k5_buf_add_uint16_be(&buf, stage);
k5_buf_add_uint32_be(&buf, group);
marshal_data(&buf, spake);
marshal_data(&buf, thash);
if (buf.data == NULL)
return ENOMEM;
*cookie_out = make_data(buf.data, buf.len);
return 0;
}
static krb5_error_code
add_indicators(krb5_context context, const krb5_data *realm,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock)
{
krb5_error_code ret;
const char *keys[4];
char *realmstr, **indicators, **ind;
realmstr = k5memdup0(realm->data, realm->length, &ret);
if (realmstr == NULL)
return ret;
keys[0] = KRB5_CONF_REALMS;
keys[1] = realmstr;
keys[2] = KRB5_CONF_SPAKE_PREAUTH_INDICATOR;
keys[3] = NULL;
ret = profile_get_values(context->profile, keys, &indicators);
free(realmstr);
if (ret == PROF_NO_RELATION)
return 0;
if (ret)
return ret;
for (ind = indicators; *ind != NULL && !ret; ind++)
ret = cb->add_auth_indicator(context, rock, *ind);
profile_free_list(indicators);
return ret;
}
static krb5_error_code
spake_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,
const char **realmnames)
{
krb5_error_code ret;
groupstate *gstate;
ret = group_init_state(context, TRUE, &gstate);
if (ret)
return ret;
*moddata_out = (krb5_kdcpreauth_moddata)gstate;
return 0;
}
static void
spake_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
{
group_free_state((groupstate *)moddata);
}
static void
send_challenge(krb5_context context, groupstate *gstate, int32_t group,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
const krb5_data *support,
krb5_kdcpreauth_edata_respond_fn erespond,
krb5_kdcpreauth_verify_respond_fn vrespond, void *arg)
{
krb5_error_code ret;
const krb5_keyblock *ikey;
krb5_pa_data **padata = NULL, *pa;
krb5_data kdcpriv = empty_data(), kdcpub = empty_data(), *der_msg = NULL;
krb5_data thash = empty_data(), cookie = empty_data();
krb5_data wbytes = empty_data();
krb5_spake_factor f, *flist[2];
krb5_pa_spake msg;
ikey = cb->client_keyblock(context, rock);
if (ikey == NULL) {
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
goto cleanup;
}
ret = derive_wbytes(context, group, ikey, &wbytes);
if (ret)
goto cleanup;
ret = group_keygen(context, gstate, group, &wbytes, &kdcpriv, &kdcpub);
if (ret)
goto cleanup;
f.type = SPAKE_SF_NONE;
f.data = NULL;
flist[0] = &f;
flist[1] = NULL;
msg.choice = SPAKE_MSGTYPE_CHALLENGE;
msg.u.challenge.group = group;
msg.u.challenge.pubkey = kdcpub;
msg.u.challenge.factors = flist;
ret = encode_krb5_pa_spake(&msg, &der_msg);
if (ret)
goto cleanup;
ret = update_thash(context, gstate, group, &thash, support, der_msg);
if (ret)
goto cleanup;
ret = make_cookie(0, group, &kdcpriv, &thash, &cookie);
if (ret)
goto cleanup;
ret = cb->set_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie);
if (ret)
goto cleanup;
ret = convert_to_padata(der_msg, &padata);
der_msg = NULL;
TRACE_SPAKE_SEND_CHALLENGE(context, group);
cleanup:
zapfree(wbytes.data, wbytes.length);
zapfree(kdcpriv.data, kdcpriv.length);
zapfree(cookie.data, cookie.length);
krb5_free_data_contents(context, &kdcpub);
krb5_free_data_contents(context, &thash);
krb5_free_data(context, der_msg);
if (erespond != NULL) {
assert(vrespond == NULL);
pa = (padata == NULL) ? NULL : padata[0];
free(padata);
(*erespond)(arg, ret, pa);
} else {
assert(vrespond != NULL);
if (!ret)
ret = KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
(*vrespond)(arg, ret, NULL, padata, NULL);
}
}
static void
spake_edata(krb5_context context, krb5_kdc_req *req,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
krb5_kdcpreauth_edata_respond_fn respond, void *arg)
{
const krb5_keyblock *ikey;
groupstate *gstate = (groupstate *)moddata;
krb5_data empty = empty_data();
int32_t group;
ikey = cb->client_keyblock(context, rock);
if (ikey == NULL) {
(*respond)(arg, KRB5KDC_ERR_ETYPE_NOSUPP, NULL);
return;
}
group = group_optimistic_challenge(gstate);
if (group) {
send_challenge(context, gstate, group, cb, rock, &empty, respond, NULL,
arg);
} else {
(*respond)(arg, 0, NULL);
}
}
static void
verify_support(krb5_context context, groupstate *gstate,
krb5_spake_support *support, const krb5_data *der_msg,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
{
krb5_error_code ret;
int32_t i, group;
for (i = 0; i < support->ngroups; i++) {
if (group_is_permitted(gstate, support->groups[i]))
break;
}
if (i == support->ngroups) {
TRACE_SPAKE_REJECT_SUPPORT(context);
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto error;
}
group = support->groups[i];
TRACE_SPAKE_RECEIVE_SUPPORT(context, group);
send_challenge(context, gstate, group, cb, rock, der_msg, NULL, respond,
arg);
return;
error:
(*respond)(arg, ret, NULL, NULL, NULL);
}
static void
verify_response(krb5_context context, groupstate *gstate,
krb5_spake_response *resp, const krb5_data *realm,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
krb5_enc_tkt_part *enc_tkt_reply,
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
{
krb5_error_code ret;
const krb5_keyblock *ikey;
krb5_keyblock *k1 = NULL, *reply_key = NULL;
krb5_data cookie, thash_in, kdcpriv, factors, *der_req;
krb5_data thash = empty_data(), der_factor = empty_data();
krb5_data wbytes = empty_data(), spakeresult = empty_data();
krb5_spake_factor *factor = NULL;
int stage;
int32_t group;
ikey = cb->client_keyblock(context, rock);
if (ikey == NULL) {
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
goto cleanup;
}
if (!cb->get_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie)) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto cleanup;
}
ret = parse_cookie(&cookie, &stage, &group, &kdcpriv, &thash_in, &factors);
if (ret)
goto cleanup;
if (stage != 0) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto cleanup;
}
TRACE_SPAKE_RECEIVE_RESPONSE(context, &resp->pubkey);
ret = krb5int_copy_data_contents(context, &thash_in, &thash);
if (ret)
goto cleanup;
ret = update_thash(context, gstate, group, &thash, &resp->pubkey, NULL);
if (ret)
goto cleanup;
TRACE_SPAKE_KDC_THASH(context, &thash);
ret = derive_wbytes(context, group, ikey, &wbytes);
if (ret)
goto cleanup;
ret = group_result(context, gstate, group, &wbytes, &kdcpriv,
&resp->pubkey, &spakeresult);
if (ret)
goto cleanup;
der_req = cb->request_body(context, rock);
ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
&thash, der_req, 1, &k1);
if (ret)
goto cleanup;
ret = alloc_data(&der_factor, resp->factor.ciphertext.length);
if (ret)
goto cleanup;
ret = krb5_c_decrypt(context, k1, KRB5_KEYUSAGE_SPAKE, NULL, &resp->factor,
&der_factor);
if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY)
ret = KRB5KDC_ERR_PREAUTH_FAILED;
if (ret)
goto cleanup;
ret = decode_krb5_spake_factor(&der_factor, &factor);
if (ret)
goto cleanup;
if (factor->type != SPAKE_SF_NONE) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto cleanup;
}
ret = add_indicators(context, realm, cb, rock);
if (ret)
goto cleanup;
enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
&thash, der_req, 0, &reply_key);
if (ret)
goto cleanup;
ret = cb->replace_reply_key(context, rock, reply_key, TRUE);
cleanup:
zapfree(wbytes.data, wbytes.length);
zapfree(der_factor.data, der_factor.length);
zapfree(spakeresult.data, spakeresult.length);
krb5_free_data_contents(context, &thash);
krb5_free_keyblock(context, k1);
krb5_free_keyblock(context, reply_key);
k5_free_spake_factor(context, factor);
(*respond)(arg, ret, NULL, NULL, NULL);
}
static void
verify_encdata(krb5_context context, krb5_enc_data *enc,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
krb5_enc_tkt_part *enc_tkt_reply,
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
{
(*respond)(arg, KRB5KDC_ERR_PREAUTH_FAILED, NULL, NULL, NULL);
}
static void
spake_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *data,
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
krb5_kdcpreauth_moddata moddata,
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
{
krb5_error_code ret;
krb5_pa_spake *pa_spake = NULL;
krb5_data in_data = make_data(data->contents, data->length);
groupstate *gstate = (groupstate *)moddata;
ret = decode_krb5_pa_spake(&in_data, &pa_spake);
if (ret) {
(*respond)(arg, ret, NULL, NULL, NULL);
} else if (pa_spake->choice == SPAKE_MSGTYPE_SUPPORT) {
verify_support(context, gstate, &pa_spake->u.support, &in_data, cb,
rock, respond, arg);
} else if (pa_spake->choice == SPAKE_MSGTYPE_RESPONSE) {
verify_response(context, gstate, &pa_spake->u.response,
&request->server->realm, cb, rock, enc_tkt_reply,
respond, arg);
} else if (pa_spake->choice == SPAKE_MSGTYPE_ENCDATA) {
verify_encdata(context, &pa_spake->u.encdata, cb, rock, enc_tkt_reply,
respond, arg);
} else {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
k5_setmsg(context, ret, _("Unknown SPAKE request type"));
(*respond)(arg, ret, NULL, NULL, NULL);
}
k5_free_pa_spake(context, pa_spake);
}
krb5_error_code
kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
krb5_plugin_vtable vtable);
krb5_error_code
kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
krb5_plugin_vtable vtable)
{
krb5_kdcpreauth_vtable vt;
static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 };
if (maj_ver != 1)
return KRB5_PLUGIN_VER_NOTSUPP;
vt = (krb5_kdcpreauth_vtable)vtable;
vt->name = "spake";
vt->pa_type_list = pa_types;
vt->init = spake_init;
vt->fini = spake_fini;
vt->edata = spake_edata;
vt->verify = spake_verify;
return 0;
}