|
Packit |
fd8b60 |
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
Packit |
fd8b60 |
/* plugins/preauth/spake/spake_kdc.c - SPAKE kdcpreauth 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-input.h"
|
|
Packit |
fd8b60 |
#include "k5-spake.h"
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
#include "groups.h"
|
|
Packit |
fd8b60 |
#include "trace.h"
|
|
Packit |
fd8b60 |
#include "iana.h"
|
|
Packit |
fd8b60 |
#include "util.h"
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
#include <krb5/kdcpreauth_plugin.h>
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* The SPAKE kdcpreauth module uses a secure cookie containing the following
|
|
Packit |
fd8b60 |
* concatenated fields (all integer fields are big-endian):
|
|
Packit |
fd8b60 |
*
|
|
Packit |
fd8b60 |
* version (16-bit unsigned integer)
|
|
Packit |
fd8b60 |
* stage (16-bit unsigned integer)
|
|
Packit |
fd8b60 |
* group (32-bit signed integer)
|
|
Packit |
fd8b60 |
* SPAKE value (32-bit unsigned length, followed by data)
|
|
Packit |
fd8b60 |
* Transcript hash (32-bit unsigned length, followed by data)
|
|
Packit |
fd8b60 |
* Zero or more instances of:
|
|
Packit |
fd8b60 |
* second-factor number (32-bit signed integer)
|
|
Packit |
fd8b60 |
* second-factor data (32-bit unsigned length, followed by data)
|
|
Packit |
fd8b60 |
*
|
|
Packit |
fd8b60 |
* The only currently supported version is 1. stage is 0 if the cookie was
|
|
Packit |
fd8b60 |
* sent with a challenge message. stage is n>0 if the cookie was sent with an
|
|
Packit |
fd8b60 |
* encdata message encrypted in K'[2n]. group indicates the group number used
|
|
Packit |
fd8b60 |
* in the SPAKE challenge. The SPAKE value is the KDC private key for a
|
|
Packit |
fd8b60 |
* stage-0 cookie, represented in the scalar marshalling form of the group; for
|
|
Packit |
fd8b60 |
* other cookies, the SPAKE value is the SPAKE result K, represented in the
|
|
Packit |
fd8b60 |
* group element marshalling form. The transcript hash is the intermediate
|
|
Packit |
fd8b60 |
* hash after updating with the support and challenge messages for a stage-0
|
|
Packit |
fd8b60 |
* cookie, or the final hash for other cookies. For a stage 0 cookie, there
|
|
Packit |
fd8b60 |
* may be any number of second-factor records, including none (no record is
|
|
Packit |
fd8b60 |
* generated for SF-NONE); for other cookies, there must be exactly one
|
|
Packit |
fd8b60 |
* second-factor record corresponding to the factor type chosen by the client.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* From a k5input structure representing the remainder of a secure cookie
|
|
Packit |
fd8b60 |
* plaintext, parse a four-byte length and data. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
parse_data(struct k5input *in, krb5_data *out)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
out->length = k5_input_get_uint32_be(in);
|
|
Packit |
fd8b60 |
out->data = (char *)k5_input_get_bytes(in, out->length);
|
|
Packit |
fd8b60 |
out->magic = KV5M_DATA;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Parse a received cookie into its components. The pointers stored in the
|
|
Packit |
fd8b60 |
* krb5_data outputs are aliases into cookie and should not be freed. */
|
|
Packit |
fd8b60 |
static krb5_error_code
|
|
Packit |
fd8b60 |
parse_cookie(const krb5_data *cookie, int *stage_out, int32_t *group_out,
|
|
Packit |
fd8b60 |
krb5_data *spake_out, krb5_data *thash_out,
|
|
Packit |
fd8b60 |
krb5_data *factors_out)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
struct k5input in;
|
|
Packit |
fd8b60 |
int version, stage;
|
|
Packit |
fd8b60 |
int32_t group;
|
|
Packit |
fd8b60 |
krb5_data thash, spake, factors;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
*spake_out = *thash_out = *factors_out = empty_data();
|
|
Packit |
fd8b60 |
k5_input_init(&in, cookie->data, cookie->length);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Parse and check the version, and read the other integer fields. */
|
|
Packit |
fd8b60 |
version = k5_input_get_uint16_be(&in);
|
|
Packit |
fd8b60 |
if (version != 1)
|
|
Packit |
fd8b60 |
return KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
stage = k5_input_get_uint16_be(&in);
|
|
Packit |
fd8b60 |
group = k5_input_get_uint32_be(&in);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Parse the data fields. The factor data is anything remaining after the
|
|
Packit |
fd8b60 |
* transcript hash. */
|
|
Packit |
fd8b60 |
parse_data(&in, &spake);
|
|
Packit |
fd8b60 |
parse_data(&in, &thash);
|
|
Packit |
fd8b60 |
if (in.status)
|
|
Packit |
fd8b60 |
return in.status;
|
|
Packit |
fd8b60 |
factors = make_data((char *)in.ptr, in.len);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
*stage_out = stage;
|
|
Packit |
fd8b60 |
*group_out = group;
|
|
Packit |
fd8b60 |
*spake_out = spake;
|
|
Packit |
fd8b60 |
*thash_out = thash;
|
|
Packit |
fd8b60 |
*factors_out = factors;
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Marshal data into buf as a four-byte length followed by the contents. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
marshal_data(struct k5buf *buf, const krb5_data *data)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
k5_buf_add_uint32_be(buf, data->length);
|
|
Packit |
fd8b60 |
k5_buf_add_len(buf, data->data, data->length);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Marshal components into a cookie. */
|
|
Packit |
fd8b60 |
static krb5_error_code
|
|
Packit |
fd8b60 |
make_cookie(int stage, int32_t group, const krb5_data *spake,
|
|
Packit |
fd8b60 |
const krb5_data *thash, krb5_data *cookie_out)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
struct k5buf buf;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
*cookie_out = empty_data();
|
|
Packit |
fd8b60 |
k5_buf_init_dynamic_zap(&buf;;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Marshal the version, stage, and group. */
|
|
Packit |
fd8b60 |
k5_buf_add_uint16_be(&buf, 1);
|
|
Packit |
fd8b60 |
k5_buf_add_uint16_be(&buf, stage);
|
|
Packit |
fd8b60 |
k5_buf_add_uint32_be(&buf, group);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Marshal the data fields. */
|
|
Packit |
fd8b60 |
marshal_data(&buf, spake);
|
|
Packit |
fd8b60 |
marshal_data(&buf, thash);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* When second factor support is implemented, we should add factor data
|
|
Packit |
fd8b60 |
* here. */
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
if (buf.data == NULL)
|
|
Packit |
fd8b60 |
return ENOMEM;
|
|
Packit |
fd8b60 |
*cookie_out = make_data(buf.data, buf.len);
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Add authentication indicators if any are configured for SPAKE. */
|
|
Packit |
fd8b60 |
static krb5_error_code
|
|
Packit |
fd8b60 |
add_indicators(krb5_context context, const krb5_data *realm,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
const char *keys[4];
|
|
Packit |
fd8b60 |
char *realmstr, **indicators, **ind;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
realmstr = k5memdup0(realm->data, realm->length, &ret;;
|
|
Packit |
fd8b60 |
if (realmstr == NULL)
|
|
Packit |
fd8b60 |
return ret;
|
|
Packit |
fd8b60 |
keys[0] = KRB5_CONF_REALMS;
|
|
Packit |
fd8b60 |
keys[1] = realmstr;
|
|
Packit |
fd8b60 |
keys[2] = KRB5_CONF_SPAKE_PREAUTH_INDICATOR;
|
|
Packit |
fd8b60 |
keys[3] = NULL;
|
|
Packit |
fd8b60 |
ret = profile_get_values(context->profile, keys, &indicators);
|
|
Packit |
fd8b60 |
free(realmstr);
|
|
Packit |
fd8b60 |
if (ret == PROF_NO_RELATION)
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
return ret;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
for (ind = indicators; *ind != NULL && !ret; ind++)
|
|
Packit |
fd8b60 |
ret = cb->add_auth_indicator(context, rock, *ind);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
profile_free_list(indicators);
|
|
Packit |
fd8b60 |
return ret;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Initialize a SPAKE module data object. */
|
|
Packit |
fd8b60 |
static krb5_error_code
|
|
Packit |
fd8b60 |
spake_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,
|
|
Packit |
fd8b60 |
const char **realmnames)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
groupstate *gstate;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = group_init_state(context, TRUE, &gstate);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
return ret;
|
|
Packit |
fd8b60 |
*moddata_out = (krb5_kdcpreauth_moddata)gstate;
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Release a SPAKE module data object. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
spake_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
group_free_state((groupstate *)moddata);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* Generate a SPAKE challenge message for the specified group. Use cb and rock
|
|
Packit |
fd8b60 |
* to retrieve the initial reply key and to set a stage-0 cookie. Invoke
|
|
Packit |
fd8b60 |
* either erespond or vrespond with the result.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
send_challenge(krb5_context context, groupstate *gstate, int32_t group,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
const krb5_data *support,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_edata_respond_fn erespond,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_verify_respond_fn vrespond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
const krb5_keyblock *ikey;
|
|
Packit |
fd8b60 |
krb5_pa_data **padata = NULL, *pa;
|
|
Packit |
fd8b60 |
krb5_data kdcpriv = empty_data(), kdcpub = empty_data(), *der_msg = NULL;
|
|
Packit |
fd8b60 |
krb5_data thash = empty_data(), cookie = empty_data();
|
|
Packit |
fd8b60 |
krb5_data wbytes = empty_data();
|
|
Packit |
fd8b60 |
krb5_spake_factor f, *flist[2];
|
|
Packit |
fd8b60 |
krb5_pa_spake msg;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ikey = cb->client_keyblock(context, rock);
|
|
Packit |
fd8b60 |
if (ikey == NULL) {
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = derive_wbytes(context, group, ikey, &wbytes);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = group_keygen(context, gstate, group, &wbytes, &kdcpriv, &kdcpub);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Encode the challenge. When second factor support is implemented, we
|
|
Packit |
fd8b60 |
* should construct a factor list instead of hardcoding SF-NONE. */
|
|
Packit |
fd8b60 |
f.type = SPAKE_SF_NONE;
|
|
Packit |
fd8b60 |
f.data = NULL;
|
|
Packit |
fd8b60 |
flist[0] = &f;
|
|
Packit |
fd8b60 |
flist[1] = NULL;
|
|
Packit |
fd8b60 |
msg.choice = SPAKE_MSGTYPE_CHALLENGE;
|
|
Packit |
fd8b60 |
msg.u.challenge.group = group;
|
|
Packit |
fd8b60 |
msg.u.challenge.pubkey = kdcpub;
|
|
Packit |
fd8b60 |
msg.u.challenge.factors = flist;
|
|
Packit |
fd8b60 |
ret = encode_krb5_pa_spake(&msg, &der_msg);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Initialize and update the transcript hash with the support message (if
|
|
Packit |
fd8b60 |
* we received one) and challenge message. */
|
|
Packit |
fd8b60 |
ret = update_thash(context, gstate, group, &thash, support, der_msg);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Save the group, transcript hash, and private key in a stage-0 cookie.
|
|
Packit |
fd8b60 |
* When second factor support is implemented, also save factor state. */
|
|
Packit |
fd8b60 |
ret = make_cookie(0, group, &kdcpriv, &thash, &cookie);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = cb->set_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = convert_to_padata(der_msg, &padata);
|
|
Packit |
fd8b60 |
der_msg = NULL;
|
|
Packit |
fd8b60 |
TRACE_SPAKE_SEND_CHALLENGE(context, group);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
cleanup:
|
|
Packit |
fd8b60 |
zapfree(wbytes.data, wbytes.length);
|
|
Packit |
fd8b60 |
zapfree(kdcpriv.data, kdcpriv.length);
|
|
Packit |
fd8b60 |
zapfree(cookie.data, cookie.length);
|
|
Packit |
fd8b60 |
krb5_free_data_contents(context, &kdcpub);
|
|
Packit |
fd8b60 |
krb5_free_data_contents(context, &thash);
|
|
Packit |
fd8b60 |
krb5_free_data(context, der_msg);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
if (erespond != NULL) {
|
|
Packit |
fd8b60 |
assert(vrespond == NULL);
|
|
Packit |
fd8b60 |
/* Grab the first pa-data element from the list, if we made one. */
|
|
Packit |
fd8b60 |
pa = (padata == NULL) ? NULL : padata[0];
|
|
Packit |
fd8b60 |
free(padata);
|
|
Packit |
fd8b60 |
(*erespond)(arg, ret, pa);
|
|
Packit |
fd8b60 |
} else {
|
|
Packit |
fd8b60 |
assert(vrespond != NULL);
|
|
Packit |
fd8b60 |
if (!ret)
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
|
|
Packit |
fd8b60 |
(*vrespond)(arg, ret, NULL, padata, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Generate the METHOD-DATA entry indicating support for SPAKE. Include an
|
|
Packit |
fd8b60 |
* optimistic challenge if configured to do so. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
spake_edata(krb5_context context, krb5_kdc_req *req,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_edata_respond_fn respond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
const krb5_keyblock *ikey;
|
|
Packit |
fd8b60 |
groupstate *gstate = (groupstate *)moddata;
|
|
Packit |
fd8b60 |
krb5_data empty = empty_data();
|
|
Packit |
fd8b60 |
int32_t group;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* SPAKE requires a client key, which cannot be a single-DES key. */
|
|
Packit |
fd8b60 |
ikey = cb->client_keyblock(context, rock);
|
|
Packit |
fd8b60 |
if (ikey == NULL) {
|
|
Packit |
fd8b60 |
(*respond)(arg, KRB5KDC_ERR_ETYPE_NOSUPP, NULL);
|
|
Packit |
fd8b60 |
return;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
group = group_optimistic_challenge(gstate);
|
|
Packit |
fd8b60 |
if (group) {
|
|
Packit |
fd8b60 |
send_challenge(context, gstate, group, cb, rock, &empty, respond, NULL,
|
|
Packit |
fd8b60 |
arg);
|
|
Packit |
fd8b60 |
} else {
|
|
Packit |
fd8b60 |
/* No optimistic challenge configured; send an empty pa-data value. */
|
|
Packit |
fd8b60 |
(*respond)(arg, 0, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Choose a group from the client's support message and generate a
|
|
Packit |
fd8b60 |
* challenge. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
verify_support(krb5_context context, groupstate *gstate,
|
|
Packit |
fd8b60 |
krb5_spake_support *support, const krb5_data *der_msg,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
int32_t i, group;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
for (i = 0; i < support->ngroups; i++) {
|
|
Packit |
fd8b60 |
if (group_is_permitted(gstate, support->groups[i]))
|
|
Packit |
fd8b60 |
break;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
if (i == support->ngroups) {
|
|
Packit |
fd8b60 |
TRACE_SPAKE_REJECT_SUPPORT(context);
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
goto error;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
group = support->groups[i];
|
|
Packit |
fd8b60 |
TRACE_SPAKE_RECEIVE_SUPPORT(context, group);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
send_challenge(context, gstate, group, cb, rock, der_msg, NULL, respond,
|
|
Packit |
fd8b60 |
arg);
|
|
Packit |
fd8b60 |
return;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
error:
|
|
Packit |
fd8b60 |
(*respond)(arg, ret, NULL, NULL, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* From the client's response message, compute the SPAKE result and decrypt the
|
|
Packit |
fd8b60 |
* factor reply. On success, either mark the reply as pre-authenticated and
|
|
Packit |
fd8b60 |
* set a reply key in the pre-request module data, or generate an additional
|
|
Packit |
fd8b60 |
* factor challenge and ask for another round of pre-authentication.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
verify_response(krb5_context context, groupstate *gstate,
|
|
Packit |
fd8b60 |
krb5_spake_response *resp, const krb5_data *realm,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_enc_tkt_part *enc_tkt_reply,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
const krb5_keyblock *ikey;
|
|
Packit |
fd8b60 |
krb5_keyblock *k1 = NULL, *reply_key = NULL;
|
|
Packit |
fd8b60 |
krb5_data cookie, thash_in, kdcpriv, factors, *der_req;
|
|
Packit |
fd8b60 |
krb5_data thash = empty_data(), der_factor = empty_data();
|
|
Packit |
fd8b60 |
krb5_data wbytes = empty_data(), spakeresult = empty_data();
|
|
Packit |
fd8b60 |
krb5_spake_factor *factor = NULL;
|
|
Packit |
fd8b60 |
int stage;
|
|
Packit |
fd8b60 |
int32_t group;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ikey = cb->client_keyblock(context, rock);
|
|
Packit |
fd8b60 |
if (ikey == NULL) {
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Fetch the stage-0 cookie and parse it. (All of the krb5_data results
|
|
Packit |
fd8b60 |
* are aliases into memory owned by rock). */
|
|
Packit |
fd8b60 |
if (!cb->get_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie)) {
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
ret = parse_cookie(&cookie, &stage, &group, &kdcpriv, &thash_in, &factors);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
if (stage != 0) {
|
|
Packit |
fd8b60 |
/* The received cookie wasn't sent with a challenge. */
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
TRACE_SPAKE_RECEIVE_RESPONSE(context, &resp->pubkey);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Update the transcript hash with the client public key. */
|
|
Packit |
fd8b60 |
ret = krb5int_copy_data_contents(context, &thash_in, &thash);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = update_thash(context, gstate, group, &thash, &resp->pubkey, NULL);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
TRACE_SPAKE_KDC_THASH(context, &thash);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = derive_wbytes(context, group, ikey, &wbytes);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = group_result(context, gstate, group, &wbytes, &kdcpriv,
|
|
Packit |
fd8b60 |
&resp->pubkey, &spakeresult);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Decrypt the response factor field using K'[1]. If the decryption
|
|
Packit |
fd8b60 |
* integrity check fails, the client probably used the wrong password. */
|
|
Packit |
fd8b60 |
der_req = cb->request_body(context, rock);
|
|
Packit |
fd8b60 |
ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
|
|
Packit |
fd8b60 |
&thash, der_req, 1, &k1;;
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = alloc_data(&der_factor, resp->factor.ciphertext.length);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = krb5_c_decrypt(context, k1, KRB5_KEYUSAGE_SPAKE, NULL, &resp->factor,
|
|
Packit |
fd8b60 |
&der_factor);
|
|
Packit |
fd8b60 |
if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY)
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
ret = decode_krb5_spake_factor(&der_factor, &factor);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* When second factor support is implemented, we should verify the factor
|
|
Packit |
fd8b60 |
* data here, and possibly generate an encdata message for another hop.
|
|
Packit |
fd8b60 |
* This function may need to be split at this point to allow for
|
|
Packit |
fd8b60 |
* asynchronous verification of the second-factor value. We might also
|
|
Packit |
fd8b60 |
* need to collect authentication indicators from the second-factor module;
|
|
Packit |
fd8b60 |
* alternatively the module could have access to cb and rock so that it can
|
|
Packit |
fd8b60 |
* add indicators itself.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
if (factor->type != SPAKE_SF_NONE) {
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = add_indicators(context, realm, cb, rock);
|
|
Packit |
fd8b60 |
if (ret)
|
|
Packit |
fd8b60 |
goto cleanup;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
|
|
Packit |
fd8b60 |
&thash, der_req, 0, &reply_key);
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
cleanup:
|
|
Packit |
fd8b60 |
zapfree(wbytes.data, wbytes.length);
|
|
Packit |
fd8b60 |
zapfree(der_factor.data, der_factor.length);
|
|
Packit |
fd8b60 |
zapfree(spakeresult.data, spakeresult.length);
|
|
Packit |
fd8b60 |
krb5_free_data_contents(context, &thash);
|
|
Packit |
fd8b60 |
krb5_free_keyblock(context, k1);
|
|
Packit |
fd8b60 |
k5_free_spake_factor(context, factor);
|
|
Packit |
fd8b60 |
(*respond)(arg, ret, (krb5_kdcpreauth_modreq)reply_key, NULL, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* Decrypt and validate an additional second-factor reply. On success, either
|
|
Packit |
fd8b60 |
* mark the reply as pre-authenticated and set a reply key in the pre-request
|
|
Packit |
fd8b60 |
* module data, or generate an additional factor challenge and ask for another
|
|
Packit |
fd8b60 |
* round of pre-authentication.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
verify_encdata(krb5_context context, krb5_enc_data *enc,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_enc_tkt_part *enc_tkt_reply,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* When second factor support is implemented, we should process encdata
|
|
Packit |
fd8b60 |
* message according to the factor type recorded in the cookie. If the
|
|
Packit |
fd8b60 |
* second factor exchange finishes successfully, we should set
|
|
Packit |
fd8b60 |
* TKT_FLG_PRE_AUTH, set the reply key to K'[0], and add any auth
|
|
Packit |
fd8b60 |
* indicators from configuration (with a call to add_indicators()) or the
|
|
Packit |
fd8b60 |
* second factor module (unless the module has access to cb and rock and
|
|
Packit |
fd8b60 |
* can add indicators itself).
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
(*respond)(arg, KRB5KDC_ERR_PREAUTH_FAILED, NULL, NULL, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/*
|
|
Packit |
fd8b60 |
* Respond to a client padata message, either by generating a SPAKE challenge,
|
|
Packit |
fd8b60 |
* generating an additional second-factor challenge, or marking the reply as
|
|
Packit |
fd8b60 |
* pre-authenticated and setting an additional reply key in the pre-request
|
|
Packit |
fd8b60 |
* module data.
|
|
Packit |
fd8b60 |
*/
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
spake_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
|
|
Packit |
fd8b60 |
krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *data,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_moddata moddata,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_verify_respond_fn respond, void *arg)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_error_code ret;
|
|
Packit |
fd8b60 |
krb5_pa_spake *pa_spake = NULL;
|
|
Packit |
fd8b60 |
krb5_data in_data = make_data(data->contents, data->length);
|
|
Packit |
fd8b60 |
groupstate *gstate = (groupstate *)moddata;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
ret = decode_krb5_pa_spake(&in_data, &pa_spake);
|
|
Packit |
fd8b60 |
if (ret) {
|
|
Packit |
fd8b60 |
(*respond)(arg, ret, NULL, NULL, NULL);
|
|
Packit |
fd8b60 |
} else if (pa_spake->choice == SPAKE_MSGTYPE_SUPPORT) {
|
|
Packit |
fd8b60 |
verify_support(context, gstate, &pa_spake->u.support, &in_data, cb,
|
|
Packit |
fd8b60 |
rock, respond, arg);
|
|
Packit |
fd8b60 |
} else if (pa_spake->choice == SPAKE_MSGTYPE_RESPONSE) {
|
|
Packit |
fd8b60 |
verify_response(context, gstate, &pa_spake->u.response,
|
|
Packit |
fd8b60 |
&request->server->realm, cb, rock, enc_tkt_reply,
|
|
Packit |
fd8b60 |
respond, arg);
|
|
Packit |
fd8b60 |
} else if (pa_spake->choice == SPAKE_MSGTYPE_ENCDATA) {
|
|
Packit |
fd8b60 |
verify_encdata(context, &pa_spake->u.encdata, cb, rock, enc_tkt_reply,
|
|
Packit |
fd8b60 |
respond, arg);
|
|
Packit |
fd8b60 |
} else {
|
|
Packit |
fd8b60 |
ret = KRB5KDC_ERR_PREAUTH_FAILED;
|
|
Packit |
fd8b60 |
k5_setmsg(context, ret, _("Unknown SPAKE request type"));
|
|
Packit |
fd8b60 |
(*respond)(arg, ret, NULL, NULL, NULL);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
k5_free_pa_spake(context, pa_spake);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* If a key was set in the per-request module data, replace the reply key. Do
|
|
Packit |
fd8b60 |
* not generate any pa-data to include with the KDC reply. */
|
|
Packit |
fd8b60 |
static krb5_error_code
|
|
Packit |
fd8b60 |
spake_return(krb5_context context, krb5_pa_data *padata, krb5_data *req_pkt,
|
|
Packit |
fd8b60 |
krb5_kdc_req *request, krb5_kdc_rep *reply,
|
|
Packit |
fd8b60 |
krb5_keyblock *encrypting_key, krb5_pa_data **send_pa_out,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_keyblock *reply_key = (krb5_keyblock *)modreq;
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
if (reply_key == NULL)
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
krb5_free_keyblock_contents(context, encrypting_key);
|
|
Packit |
fd8b60 |
return krb5_copy_keyblock_contents(context, reply_key, encrypting_key);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
/* Release a per-request module data object. */
|
|
Packit |
fd8b60 |
static void
|
|
Packit |
fd8b60 |
spake_free_modreq(krb5_context context, krb5_kdcpreauth_moddata moddata,
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_modreq modreq)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_free_keyblock(context, (krb5_keyblock *)modreq);
|
|
Packit |
fd8b60 |
}
|
|
Packit |
fd8b60 |
|
|
Packit |
fd8b60 |
krb5_error_code
|
|
Packit |
fd8b60 |
kdcpreauth_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 |
kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
|
|
Packit |
fd8b60 |
krb5_plugin_vtable vtable)
|
|
Packit |
fd8b60 |
{
|
|
Packit |
fd8b60 |
krb5_kdcpreauth_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_kdcpreauth_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->edata = spake_edata;
|
|
Packit |
fd8b60 |
vt->verify = spake_verify;
|
|
Packit |
fd8b60 |
vt->return_padata = spake_return;
|
|
Packit |
fd8b60 |
vt->free_modreq = spake_free_modreq;
|
|
Packit |
fd8b60 |
return 0;
|
|
Packit |
fd8b60 |
}
|