Path: blob/main/crypto/krb5/src/plugins/preauth/spake/spake_client.c
34889 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* plugins/preauth/spake/spake_client.c - SPAKE clpreauth module */2/*3* Copyright (C) 2015 by the Massachusetts Institute of Technology.4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9*10* * Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in15* the documentation and/or other materials provided with the16* distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS19* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT20* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS21* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE22* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,23* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES24* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR25* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)26* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,27* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)28* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED29* OF THE POSSIBILITY OF SUCH DAMAGE.30*/3132#include "k5-int.h"33#include "k5-spake.h"34#include "trace.h"35#include "util.h"36#include "iana.h"37#include "groups.h"38#include <krb5/clpreauth_plugin.h>3940typedef struct reqstate_st {41krb5_pa_spake *msg; /* set in prep_questions, used in process */42krb5_keyblock *initial_key;43krb5_data *support;44krb5_data thash;45krb5_data spakeresult;46} reqstate;4748/* Return true if SF-NONE is present in factors. */49static krb5_boolean50contains_sf_none(krb5_spake_factor **factors)51{52int i;5354for (i = 0; factors != NULL && factors[i] != NULL; i++) {55if (factors[i]->type == SPAKE_SF_NONE)56return TRUE;57}58return FALSE;59}6061static krb5_error_code62spake_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)63{64krb5_error_code ret;65groupstate *gstate;6667ret = group_init_state(context, FALSE, &gstate);68if (ret)69return ret;70*moddata_out = (krb5_clpreauth_moddata)gstate;71return 0;72}7374static void75spake_fini(krb5_context context, krb5_clpreauth_moddata moddata)76{77group_free_state((groupstate *)moddata);78}7980static void81spake_request_init(krb5_context context, krb5_clpreauth_moddata moddata,82krb5_clpreauth_modreq *modreq_out)83{84*modreq_out = calloc(1, sizeof(reqstate));85}8687static void88spake_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,89krb5_clpreauth_modreq modreq)90{91reqstate *st = (reqstate *)modreq;9293k5_free_pa_spake(context, st->msg);94krb5_free_keyblock(context, st->initial_key);95krb5_free_data(context, st->support);96krb5_free_data_contents(context, &st->thash);97zapfree(st->spakeresult.data, st->spakeresult.length);98free(st);99}100101static krb5_error_code102spake_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,103krb5_clpreauth_modreq modreq,104krb5_get_init_creds_opt *opt, krb5_clpreauth_callbacks cb,105krb5_clpreauth_rock rock, krb5_kdc_req *req,106krb5_data *enc_req, krb5_data *enc_prev_req,107krb5_pa_data *pa_data)108{109krb5_error_code ret;110groupstate *gstate = (groupstate *)moddata;111reqstate *st = (reqstate *)modreq;112krb5_data in_data;113krb5_spake_challenge *ch;114115if (st == NULL)116return ENOMEM;117118/* We don't need to ask any questions to send a support message. */119if (pa_data->length == 0)120return 0;121122/* Decode the incoming message, replacing any previous one in the request123* state. If we can't decode it, we have no questions to ask. */124k5_free_pa_spake(context, st->msg);125st->msg = NULL;126in_data = make_data(pa_data->contents, pa_data->length);127ret = decode_krb5_pa_spake(&in_data, &st->msg);128if (ret)129return (ret == ENOMEM) ? ENOMEM : 0;130131if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {132ch = &st->msg->u.challenge;133if (!group_is_permitted(gstate, ch->group))134return 0;135/* When second factor support is implemented, we should ask questions136* based on the factors in the challenge. */137if (!contains_sf_none(ch->factors))138return 0;139/* We will need the AS key to respond to the challenge. */140cb->need_as_key(context, rock);141} else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {142/* When second factor support is implemented, we should decrypt the143* encdata message and ask questions based on the factor data. */144}145return 0;146}147148/*149* Output a PA-SPAKE support message indicating which groups we support. This150* may be done for optimistic preauth, in response to an empty message, or in151* response to a challenge using a group we do not support. Save the support152* message in st->support.153*/154static krb5_error_code155send_support(krb5_context context, groupstate *gstate, reqstate *st,156krb5_pa_data ***pa_out)157{158krb5_error_code ret;159krb5_data *support;160krb5_pa_spake msg;161162msg.choice = SPAKE_MSGTYPE_SUPPORT;163group_get_permitted(gstate, &msg.u.support.groups, &msg.u.support.ngroups);164ret = encode_krb5_pa_spake(&msg, &support);165if (ret)166return ret;167168/* Save the support message for later use in the transcript hash. */169ret = krb5_copy_data(context, support, &st->support);170if (ret) {171krb5_free_data(context, support);172return ret;173}174175TRACE_SPAKE_SEND_SUPPORT(context);176return convert_to_padata(support, pa_out);177}178179static krb5_error_code180process_challenge(krb5_context context, groupstate *gstate, reqstate *st,181krb5_spake_challenge *ch, const krb5_data *der_msg,182krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,183krb5_prompter_fct prompter, void *prompter_data,184const krb5_data *der_req, krb5_pa_data ***pa_out)185{186krb5_error_code ret;187krb5_keyblock *k0 = NULL, *k1 = NULL, *as_key;188krb5_spake_factor factor;189krb5_pa_spake msg;190krb5_data *der_factor = NULL, *response;191krb5_data clpriv = empty_data(), clpub = empty_data();192krb5_data wbytes = empty_data();193krb5_enc_data enc_factor;194195enc_factor.ciphertext = empty_data();196197/* Not expected if we processed a challenge and didn't reject it. */198if (st->initial_key != NULL)199return KRB5KDC_ERR_PREAUTH_FAILED;200201if (!group_is_permitted(gstate, ch->group)) {202TRACE_SPAKE_REJECT_CHALLENGE(context, ch->group);203/* No point in sending a second support message. */204if (st->support != NULL)205return KRB5KDC_ERR_PREAUTH_FAILED;206return send_support(context, gstate, st, pa_out);207}208209/* Initialize and update the transcript with the concatenation of the210* support message (if we sent one) and the received challenge. */211ret = update_thash(context, gstate, ch->group, &st->thash, st->support,212der_msg);213if (ret)214return ret;215216TRACE_SPAKE_RECEIVE_CHALLENGE(context, ch->group, &ch->pubkey);217218/* When second factor support is implemented, we should check for a219* supported factor type instead of just checking for SF-NONE. */220if (!contains_sf_none(ch->factors))221return KRB5KDC_ERR_PREAUTH_FAILED;222223ret = cb->get_as_key(context, rock, &as_key);224if (ret)225goto cleanup;226ret = krb5_copy_keyblock(context, as_key, &st->initial_key);227if (ret)228goto cleanup;229ret = derive_wbytes(context, ch->group, st->initial_key, &wbytes);230if (ret)231goto cleanup;232ret = group_keygen(context, gstate, ch->group, &wbytes, &clpriv, &clpub);233if (ret)234goto cleanup;235ret = group_result(context, gstate, ch->group, &wbytes, &clpriv,236&ch->pubkey, &st->spakeresult);237if (ret)238goto cleanup;239240ret = update_thash(context, gstate, ch->group, &st->thash, &clpub, NULL);241if (ret)242goto cleanup;243TRACE_SPAKE_CLIENT_THASH(context, &st->thash);244245/* Replace the reply key with K'[0]. */246ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,247&st->spakeresult, &st->thash, der_req, 0, &k0);248if (ret)249goto cleanup;250ret = cb->set_as_key(context, rock, k0);251if (ret)252goto cleanup;253254/* Encrypt a SPAKESecondFactor message with K'[1]. */255ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,256&st->spakeresult, &st->thash, der_req, 1, &k1);257if (ret)258goto cleanup;259/* When second factor support is implemented, we should construct an260* appropriate factor here instead of hardcoding SF-NONE. */261factor.type = SPAKE_SF_NONE;262factor.data = NULL;263ret = encode_krb5_spake_factor(&factor, &der_factor);264if (ret)265goto cleanup;266ret = krb5_encrypt_helper(context, k1, KRB5_KEYUSAGE_SPAKE, der_factor,267&enc_factor);268if (ret)269goto cleanup;270271/* Encode and output a response message. */272msg.choice = SPAKE_MSGTYPE_RESPONSE;273msg.u.response.pubkey = clpub;274msg.u.response.factor = enc_factor;275ret = encode_krb5_pa_spake(&msg, &response);276if (ret)277goto cleanup;278TRACE_SPAKE_SEND_RESPONSE(context);279ret = convert_to_padata(response, pa_out);280if (ret)281goto cleanup;282283cb->disable_fallback(context, rock);284285cleanup:286krb5_free_keyblock(context, k0);287krb5_free_keyblock(context, k1);288krb5_free_data_contents(context, &enc_factor.ciphertext);289krb5_free_data_contents(context, &clpub);290zapfree(clpriv.data, clpriv.length);291zapfree(wbytes.data, wbytes.length);292if (der_factor != NULL) {293zapfree(der_factor->data, der_factor->length);294free(der_factor);295}296return ret;297}298299static krb5_error_code300process_encdata(krb5_context context, reqstate *st, krb5_enc_data *enc,301krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,302krb5_prompter_fct prompter, void *prompter_data,303const krb5_data *der_prev_req, const krb5_data *der_req,304krb5_pa_data ***pa_out)305{306/* Not expected if we haven't sent a response yet. */307if (st->initial_key == NULL || st->spakeresult.length == 0)308return KRB5KDC_ERR_PREAUTH_FAILED;309310/*311* When second factor support is implemented, we should process encdata312* messages according to the factor type. We should make sure to re-derive313* K'[0] and replace the reply key again, in case the request has changed.314* We should use der_prev_req to derive K'[n] to decrypt factor from the315* KDC. We should use der_req to derive K'[n+1] for the next message to316* send to the KDC.317*/318return KRB5_PLUGIN_OP_NOTSUPP;319}320321static krb5_error_code322spake_process(krb5_context context, krb5_clpreauth_moddata moddata,323krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,324krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,325krb5_kdc_req *req, krb5_data *der_req, krb5_data *der_prev_req,326krb5_pa_data *pa_in, krb5_prompter_fct prompter,327void *prompter_data, krb5_pa_data ***pa_out)328{329krb5_error_code ret;330groupstate *gstate = (groupstate *)moddata;331reqstate *st = (reqstate *)modreq;332krb5_data in_data;333334if (st == NULL)335return ENOMEM;336337if (pa_in->length == 0) {338/* Not expected if we already sent a support message. */339if (st->support != NULL)340return KRB5KDC_ERR_PREAUTH_FAILED;341return send_support(context, gstate, st, pa_out);342}343344if (st->msg == NULL) {345/* The message failed to decode in spake_prep_questions(). */346ret = KRB5KDC_ERR_PREAUTH_FAILED;347} else if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {348in_data = make_data(pa_in->contents, pa_in->length);349ret = process_challenge(context, gstate, st, &st->msg->u.challenge,350&in_data, cb, rock, prompter, prompter_data,351der_req, pa_out);352} else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {353ret = process_encdata(context, st, &st->msg->u.encdata, cb, rock,354prompter, prompter_data, der_prev_req, der_req,355pa_out);356} else {357/* Unexpected message type */358ret = KRB5KDC_ERR_PREAUTH_FAILED;359}360361return ret;362}363364krb5_error_code365clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,366krb5_plugin_vtable vtable);367368krb5_error_code369clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,370krb5_plugin_vtable vtable)371{372krb5_clpreauth_vtable vt;373static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 };374375if (maj_ver != 1)376return KRB5_PLUGIN_VER_NOTSUPP;377vt = (krb5_clpreauth_vtable)vtable;378vt->name = "spake";379vt->pa_type_list = pa_types;380vt->init = spake_init;381vt->fini = spake_fini;382vt->request_init = spake_request_init;383vt->request_fini = spake_request_fini;384vt->process = spake_process;385vt->prep_questions = spake_prep_questions;386return 0;387}388389390