Blame src/plugins/preauth/spake/spake_client.c

Packit fd8b60
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
Packit fd8b60
/* plugins/preauth/spake/spake_client.c - SPAKE clpreauth module */
Packit fd8b60
/*
Packit fd8b60
 * Copyright (C) 2015 by the Massachusetts Institute of Technology.
Packit fd8b60
 * All rights reserved.
Packit fd8b60
 *
Packit fd8b60
 * Redistribution and use in source and binary forms, with or without
Packit fd8b60
 * modification, are permitted provided that the following conditions
Packit fd8b60
 * are met:
Packit fd8b60
 *
Packit fd8b60
 * * Redistributions of source code must retain the above copyright
Packit fd8b60
 *   notice, this list of conditions and the following disclaimer.
Packit fd8b60
 *
Packit fd8b60
 * * Redistributions in binary form must reproduce the above copyright
Packit fd8b60
 *   notice, this list of conditions and the following disclaimer in
Packit fd8b60
 *   the documentation and/or other materials provided with the
Packit fd8b60
 *   distribution.
Packit fd8b60
 *
Packit fd8b60
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
Packit fd8b60
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Packit fd8b60
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
Packit fd8b60
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
Packit fd8b60
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
Packit fd8b60
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
Packit fd8b60
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
Packit fd8b60
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
Packit fd8b60
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
Packit fd8b60
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
Packit fd8b60
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
Packit fd8b60
 * OF THE POSSIBILITY OF SUCH DAMAGE.
Packit fd8b60
 */
Packit fd8b60
Packit fd8b60
#include "k5-int.h"
Packit fd8b60
#include "k5-spake.h"
Packit fd8b60
#include "trace.h"
Packit fd8b60
#include "util.h"
Packit fd8b60
#include "iana.h"
Packit fd8b60
#include "groups.h"
Packit fd8b60
#include <krb5/clpreauth_plugin.h>
Packit fd8b60
Packit fd8b60
typedef struct reqstate_st {
Packit fd8b60
    krb5_pa_spake *msg;         /* set in prep_questions, used in process */
Packit fd8b60
    krb5_keyblock *initial_key;
Packit fd8b60
    krb5_data *support;
Packit fd8b60
    krb5_data thash;
Packit fd8b60
    krb5_data spakeresult;
Packit fd8b60
} reqstate;
Packit fd8b60
Packit fd8b60
/* Return true if SF-NONE is present in factors. */
Packit fd8b60
static krb5_boolean
Packit fd8b60
contains_sf_none(krb5_spake_factor **factors)
Packit fd8b60
{
Packit fd8b60
    int i;
Packit fd8b60
Packit fd8b60
    for (i = 0; factors != NULL && factors[i] != NULL; i++) {
Packit fd8b60
        if (factors[i]->type == SPAKE_SF_NONE)
Packit fd8b60
            return TRUE;
Packit fd8b60
    }
Packit fd8b60
    return FALSE;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static krb5_error_code
Packit fd8b60
spake_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)
Packit fd8b60
{
Packit fd8b60
    krb5_error_code ret;
Packit fd8b60
    groupstate *gstate;
Packit fd8b60
Packit fd8b60
    ret = group_init_state(context, FALSE, &gstate);
Packit fd8b60
    if (ret)
Packit fd8b60
        return ret;
Packit fd8b60
    *moddata_out = (krb5_clpreauth_moddata)gstate;
Packit fd8b60
    return 0;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static void
Packit fd8b60
spake_fini(krb5_context context, krb5_clpreauth_moddata moddata)
Packit fd8b60
{
Packit fd8b60
    group_free_state((groupstate *)moddata);
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static void
Packit fd8b60
spake_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
Packit fd8b60
                   krb5_clpreauth_modreq *modreq_out)
Packit fd8b60
{
Packit fd8b60
    *modreq_out = calloc(1, sizeof(reqstate));
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static void
Packit fd8b60
spake_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
Packit fd8b60
                   krb5_clpreauth_modreq modreq)
Packit fd8b60
{
Packit fd8b60
    reqstate *st = (reqstate *)modreq;
Packit fd8b60
Packit fd8b60
    k5_free_pa_spake(context, st->msg);
Packit fd8b60
    krb5_free_keyblock(context, st->initial_key);
Packit fd8b60
    krb5_free_data(context, st->support);
Packit fd8b60
    krb5_free_data_contents(context, &st->thash);
Packit fd8b60
    zapfree(st->spakeresult.data, st->spakeresult.length);
Packit fd8b60
    free(st);
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static krb5_error_code
Packit fd8b60
spake_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
Packit fd8b60
                     krb5_clpreauth_modreq modreq,
Packit fd8b60
                     krb5_get_init_creds_opt *opt, krb5_clpreauth_callbacks cb,
Packit fd8b60
                     krb5_clpreauth_rock rock, krb5_kdc_req *req,
Packit fd8b60
                     krb5_data *enc_req, krb5_data *enc_prev_req,
Packit fd8b60
                     krb5_pa_data *pa_data)
Packit fd8b60
{
Packit fd8b60
    krb5_error_code ret;
Packit fd8b60
    groupstate *gstate = (groupstate *)moddata;
Packit fd8b60
    reqstate *st = (reqstate *)modreq;
Packit fd8b60
    krb5_data in_data;
Packit fd8b60
    krb5_spake_challenge *ch;
Packit fd8b60
Packit fd8b60
    if (st == NULL)
Packit fd8b60
        return ENOMEM;
Packit fd8b60
Packit fd8b60
    /* We don't need to ask any questions to send a support message. */
Packit fd8b60
    if (pa_data->length == 0)
Packit fd8b60
        return 0;
Packit fd8b60
Packit fd8b60
    /* Decode the incoming message, replacing any previous one in the request
Packit fd8b60
     * state.  If we can't decode it, we have no questions to ask. */
Packit fd8b60
    k5_free_pa_spake(context, st->msg);
Packit fd8b60
    st->msg = NULL;
Packit fd8b60
    in_data = make_data(pa_data->contents, pa_data->length);
Packit fd8b60
    ret = decode_krb5_pa_spake(&in_data, &st->msg);
Packit fd8b60
    if (ret)
Packit fd8b60
        return (ret == ENOMEM) ? ENOMEM : 0;
Packit fd8b60
Packit fd8b60
    if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {
Packit fd8b60
        ch = &st->msg->u.challenge;
Packit fd8b60
        if (!group_is_permitted(gstate, ch->group))
Packit fd8b60
            return 0;
Packit fd8b60
        /* When second factor support is implemented, we should ask questions
Packit fd8b60
         * based on the factors in the challenge. */
Packit fd8b60
        if (!contains_sf_none(ch->factors))
Packit fd8b60
            return 0;
Packit fd8b60
        /* We will need the AS key to respond to the challenge. */
Packit fd8b60
        cb->need_as_key(context, rock);
Packit fd8b60
    } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {
Packit fd8b60
        /* When second factor support is implemented, we should decrypt the
Packit fd8b60
         * encdata message and ask questions based on the factor data. */
Packit fd8b60
    }
Packit fd8b60
    return 0;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
/*
Packit fd8b60
 * Output a PA-SPAKE support message indicating which groups we support.  This
Packit fd8b60
 * may be done for optimistic preauth, in response to an empty message, or in
Packit fd8b60
 * response to a challenge using a group we do not support.  Save the support
Packit fd8b60
 * message in st->support.
Packit fd8b60
 */
Packit fd8b60
static krb5_error_code
Packit fd8b60
send_support(krb5_context context, groupstate *gstate, reqstate *st,
Packit fd8b60
             krb5_pa_data ***pa_out)
Packit fd8b60
{
Packit fd8b60
    krb5_error_code ret;
Packit fd8b60
    krb5_data *support;
Packit fd8b60
    krb5_pa_spake msg;
Packit fd8b60
Packit fd8b60
    msg.choice = SPAKE_MSGTYPE_SUPPORT;
Packit fd8b60
    group_get_permitted(gstate, &msg.u.support.groups, &msg.u.support.ngroups);
Packit fd8b60
    ret = encode_krb5_pa_spake(&msg, &support);
Packit fd8b60
    if (ret)
Packit fd8b60
        return ret;
Packit fd8b60
Packit fd8b60
    /* Save the support message for later use in the transcript hash. */
Packit fd8b60
    ret = krb5_copy_data(context, support, &st->support);
Packit fd8b60
    if (ret) {
Packit fd8b60
        krb5_free_data(context, support);
Packit fd8b60
        return ret;
Packit fd8b60
    }
Packit fd8b60
Packit fd8b60
    TRACE_SPAKE_SEND_SUPPORT(context);
Packit fd8b60
    return convert_to_padata(support, pa_out);
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static krb5_error_code
Packit fd8b60
process_challenge(krb5_context context, groupstate *gstate, reqstate *st,
Packit fd8b60
                  krb5_spake_challenge *ch, const krb5_data *der_msg,
Packit fd8b60
                  krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
Packit fd8b60
                  krb5_prompter_fct prompter, void *prompter_data,
Packit fd8b60
                  const krb5_data *der_req, krb5_pa_data ***pa_out)
Packit fd8b60
{
Packit fd8b60
    krb5_error_code ret;
Packit fd8b60
    krb5_keyblock *k0 = NULL, *k1 = NULL, *as_key;
Packit fd8b60
    krb5_spake_factor factor;
Packit fd8b60
    krb5_pa_spake msg;
Packit fd8b60
    krb5_data *der_factor = NULL, *response;
Packit fd8b60
    krb5_data clpriv = empty_data(), clpub = empty_data();
Packit fd8b60
    krb5_data wbytes = empty_data();
Packit fd8b60
    krb5_enc_data enc_factor;
Packit fd8b60
Packit fd8b60
    enc_factor.ciphertext = empty_data();
Packit fd8b60
Packit fd8b60
    /* Not expected if we processed a challenge and didn't reject it. */
Packit fd8b60
    if (st->initial_key != NULL)
Packit fd8b60
        return KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
Packit fd8b60
    if (!group_is_permitted(gstate, ch->group)) {
Packit fd8b60
        TRACE_SPAKE_REJECT_CHALLENGE(context, ch->group);
Packit fd8b60
        /* No point in sending a second support message. */
Packit fd8b60
        if (st->support != NULL)
Packit fd8b60
            return KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
        return send_support(context, gstate, st, pa_out);
Packit fd8b60
    }
Packit fd8b60
Packit fd8b60
    /* Initialize and update the transcript with the concatenation of the
Packit fd8b60
     * support message (if we sent one) and the received challenge. */
Packit fd8b60
    ret = update_thash(context, gstate, ch->group, &st->thash, st->support,
Packit fd8b60
                       der_msg);
Packit fd8b60
    if (ret)
Packit fd8b60
        return ret;
Packit fd8b60
Packit fd8b60
    TRACE_SPAKE_RECEIVE_CHALLENGE(context, ch->group, &ch->pubkey);
Packit fd8b60
Packit fd8b60
    /* When second factor support is implemented, we should check for a
Packit fd8b60
     * supported factor type instead of just checking for SF-NONE. */
Packit fd8b60
    if (!contains_sf_none(ch->factors))
Packit fd8b60
        return KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
Packit fd8b60
    ret = cb->get_as_key(context, rock, &as_key);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = krb5_copy_keyblock(context, as_key, &st->initial_key);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = derive_wbytes(context, ch->group, st->initial_key, &wbytes);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = group_keygen(context, gstate, ch->group, &wbytes, &clpriv, &clpub);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = group_result(context, gstate, ch->group, &wbytes, &clpriv,
Packit fd8b60
                       &ch->pubkey, &st->spakeresult);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
Packit fd8b60
    ret = update_thash(context, gstate, ch->group, &st->thash, &clpub, NULL);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    TRACE_SPAKE_CLIENT_THASH(context, &st->thash);
Packit fd8b60
Packit fd8b60
    /* Replace the reply key with K'[0]. */
Packit fd8b60
    ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,
Packit fd8b60
                     &st->spakeresult, &st->thash, der_req, 0, &k0;;
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = cb->set_as_key(context, rock, k0);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
Packit fd8b60
    /* Encrypt a SPAKESecondFactor message with K'[1]. */
Packit fd8b60
    ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,
Packit fd8b60
                     &st->spakeresult, &st->thash, der_req, 1, &k1;;
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    /* When second factor support is implemented, we should construct an
Packit fd8b60
     * appropriate factor here instead of hardcoding SF-NONE. */
Packit fd8b60
    factor.type = SPAKE_SF_NONE;
Packit fd8b60
    factor.data = NULL;
Packit fd8b60
    ret = encode_krb5_spake_factor(&factor, &der_factor);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    ret = krb5_encrypt_helper(context, k1, KRB5_KEYUSAGE_SPAKE, der_factor,
Packit fd8b60
                              &enc_factor);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
Packit fd8b60
    /* Encode and output a response message. */
Packit fd8b60
    msg.choice = SPAKE_MSGTYPE_RESPONSE;
Packit fd8b60
    msg.u.response.pubkey = clpub;
Packit fd8b60
    msg.u.response.factor = enc_factor;
Packit fd8b60
    ret = encode_krb5_pa_spake(&msg, &response);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
    TRACE_SPAKE_SEND_RESPONSE(context);
Packit fd8b60
    ret = convert_to_padata(response, pa_out);
Packit fd8b60
    if (ret)
Packit fd8b60
        goto cleanup;
Packit fd8b60
Packit fd8b60
    cb->disable_fallback(context, rock);
Packit fd8b60
Packit fd8b60
cleanup:
Packit fd8b60
    krb5_free_keyblock(context, k0);
Packit fd8b60
    krb5_free_keyblock(context, k1);
Packit fd8b60
    krb5_free_data_contents(context, &enc_factor.ciphertext);
Packit fd8b60
    krb5_free_data_contents(context, &clpub);
Packit fd8b60
    zapfree(clpriv.data, clpriv.length);
Packit fd8b60
    zapfree(wbytes.data, wbytes.length);
Packit fd8b60
    if (der_factor != NULL) {
Packit fd8b60
        zapfree(der_factor->data, der_factor->length);
Packit fd8b60
        free(der_factor);
Packit fd8b60
    }
Packit fd8b60
    return ret;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static krb5_error_code
Packit fd8b60
process_encdata(krb5_context context, reqstate *st, krb5_enc_data *enc,
Packit fd8b60
                krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
Packit fd8b60
                krb5_prompter_fct prompter, void *prompter_data,
Packit fd8b60
                const krb5_data *der_prev_req, const krb5_data *der_req,
Packit fd8b60
                krb5_pa_data ***pa_out)
Packit fd8b60
{
Packit fd8b60
    /* Not expected if we haven't sent a response yet. */
Packit fd8b60
    if (st->initial_key == NULL || st->spakeresult.length == 0)
Packit fd8b60
        return KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
Packit fd8b60
    /*
Packit fd8b60
     * When second factor support is implemented, we should process encdata
Packit fd8b60
     * messages according to the factor type.  We should make sure to re-derive
Packit fd8b60
     * K'[0] and replace the reply key again, in case the request has changed.
Packit fd8b60
     * We should use der_prev_req to derive K'[n] to decrypt factor from the
Packit fd8b60
     * KDC.  We should use der_req to derive K'[n+1] for the next message to
Packit fd8b60
     * send to the KDC.
Packit fd8b60
     */
Packit fd8b60
    return KRB5_PLUGIN_OP_NOTSUPP;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
static krb5_error_code
Packit fd8b60
spake_process(krb5_context context, krb5_clpreauth_moddata moddata,
Packit fd8b60
              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
Packit fd8b60
              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
Packit fd8b60
              krb5_kdc_req *req, krb5_data *der_req, krb5_data *der_prev_req,
Packit fd8b60
              krb5_pa_data *pa_in, krb5_prompter_fct prompter,
Packit fd8b60
              void *prompter_data, krb5_pa_data ***pa_out)
Packit fd8b60
{
Packit fd8b60
    krb5_error_code ret;
Packit fd8b60
    groupstate *gstate = (groupstate *)moddata;
Packit fd8b60
    reqstate *st = (reqstate *)modreq;
Packit fd8b60
    krb5_data in_data;
Packit fd8b60
Packit fd8b60
    if (st == NULL)
Packit fd8b60
        return ENOMEM;
Packit fd8b60
Packit fd8b60
    if (pa_in->length == 0) {
Packit fd8b60
        /* Not expected if we already sent a support message. */
Packit fd8b60
        if (st->support != NULL)
Packit fd8b60
            return KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
        return send_support(context, gstate, st, pa_out);
Packit fd8b60
    }
Packit fd8b60
Packit fd8b60
    if (st->msg == NULL) {
Packit fd8b60
        /* The message failed to decode in spake_prep_questions(). */
Packit fd8b60
        ret = KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
    } else if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {
Packit fd8b60
        in_data = make_data(pa_in->contents, pa_in->length);
Packit fd8b60
        ret = process_challenge(context, gstate, st, &st->msg->u.challenge,
Packit fd8b60
                                &in_data, cb, rock, prompter, prompter_data,
Packit fd8b60
                                der_req, pa_out);
Packit fd8b60
    } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {
Packit fd8b60
        ret = process_encdata(context, st, &st->msg->u.encdata, cb, rock,
Packit fd8b60
                              prompter, prompter_data, der_prev_req, der_req,
Packit fd8b60
                              pa_out);
Packit fd8b60
    } else {
Packit fd8b60
        /* Unexpected message type */
Packit fd8b60
        ret = KRB5KDC_ERR_PREAUTH_FAILED;
Packit fd8b60
    }
Packit fd8b60
Packit fd8b60
    return ret;
Packit fd8b60
}
Packit fd8b60
Packit fd8b60
krb5_error_code
Packit fd8b60
clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
Packit fd8b60
                       krb5_plugin_vtable vtable);
Packit fd8b60
Packit fd8b60
krb5_error_code
Packit fd8b60
clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
Packit fd8b60
                       krb5_plugin_vtable vtable)
Packit fd8b60
{
Packit fd8b60
    krb5_clpreauth_vtable vt;
Packit fd8b60
    static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 };
Packit fd8b60
Packit fd8b60
    if (maj_ver != 1)
Packit fd8b60
        return KRB5_PLUGIN_VER_NOTSUPP;
Packit fd8b60
    vt = (krb5_clpreauth_vtable)vtable;
Packit fd8b60
    vt->name = "spake";
Packit fd8b60
    vt->pa_type_list = pa_types;
Packit fd8b60
    vt->init = spake_init;
Packit fd8b60
    vt->fini = spake_fini;
Packit fd8b60
    vt->request_init = spake_request_init;
Packit fd8b60
    vt->request_fini = spake_request_fini;
Packit fd8b60
    vt->process = spake_process;
Packit fd8b60
    vt->prep_questions = spake_prep_questions;
Packit fd8b60
    return 0;
Packit fd8b60
}