1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/spake/spake_kdc.c - SPAKE kdcpreauth module */
3 /*
4  * Copyright (C) 2015 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "k5-int.h"
34 #include "k5-input.h"
35 #include "k5-spake.h"
36 
37 #include "groups.h"
38 #include "trace.h"
39 #include "iana.h"
40 #include "util.h"
41 
42 #include <krb5/kdcpreauth_plugin.h>
43 
44 /*
45  * The SPAKE kdcpreauth module uses a secure cookie containing the following
46  * concatenated fields (all integer fields are big-endian):
47  *
48  *     version (16-bit unsigned integer)
49  *     stage (16-bit unsigned integer)
50  *     group (32-bit signed integer)
51  *     SPAKE value (32-bit unsigned length, followed by data)
52  *     Transcript hash (32-bit unsigned length, followed by data)
53  *     Zero or more instances of:
54  *         second-factor number (32-bit signed integer)
55  *         second-factor data (32-bit unsigned length, followed by data)
56  *
57  * The only currently supported version is 1.  stage is 0 if the cookie was
58  * sent with a challenge message.  stage is n>0 if the cookie was sent with an
59  * encdata message encrypted in K'[2n].  group indicates the group number used
60  * in the SPAKE challenge.  The SPAKE value is the KDC private key for a
61  * stage-0 cookie, represented in the scalar marshalling form of the group; for
62  * other cookies, the SPAKE value is the SPAKE result K, represented in the
63  * group element marshalling form.  The transcript hash is the intermediate
64  * hash after updating with the support and challenge messages for a stage-0
65  * cookie, or the final hash for other cookies.  For a stage 0 cookie, there
66  * may be any number of second-factor records, including none (no record is
67  * generated for SF-NONE); for other cookies, there must be exactly one
68  * second-factor record corresponding to the factor type chosen by the client.
69  */
70 
71 /* From a k5input structure representing the remainder of a secure cookie
72  * plaintext, parse a four-byte length and data. */
73 static void
parse_data(struct k5input * in,krb5_data * out)74 parse_data(struct k5input *in, krb5_data *out)
75 {
76     out->length = k5_input_get_uint32_be(in);
77     out->data = (char *)k5_input_get_bytes(in, out->length);
78     out->magic = KV5M_DATA;
79 }
80 
81 /* Parse a received cookie into its components.  The pointers stored in the
82  * krb5_data outputs are aliases into cookie and should not be freed. */
83 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)84 parse_cookie(const krb5_data *cookie, int *stage_out, int32_t *group_out,
85              krb5_data *spake_out, krb5_data *thash_out,
86              krb5_data *factors_out)
87 {
88     struct k5input in;
89     int version, stage;
90     int32_t group;
91     krb5_data thash, spake, factors;
92 
93     *spake_out = *thash_out = *factors_out = empty_data();
94     k5_input_init(&in, cookie->data, cookie->length);
95 
96     /* Parse and check the version, and read the other integer fields. */
97     version = k5_input_get_uint16_be(&in);
98     if (version != 1)
99         return KRB5KDC_ERR_PREAUTH_FAILED;
100     stage = k5_input_get_uint16_be(&in);
101     group = k5_input_get_uint32_be(&in);
102 
103     /* Parse the data fields.  The factor data is anything remaining after the
104      * transcript hash. */
105     parse_data(&in, &spake);
106     parse_data(&in, &thash);
107     if (in.status)
108         return in.status;
109     factors = make_data((char *)in.ptr, in.len);
110 
111     *stage_out = stage;
112     *group_out = group;
113     *spake_out = spake;
114     *thash_out = thash;
115     *factors_out = factors;
116     return 0;
117 }
118 
119 /* Marshal data into buf as a four-byte length followed by the contents. */
120 static void
marshal_data(struct k5buf * buf,const krb5_data * data)121 marshal_data(struct k5buf *buf, const krb5_data *data)
122 {
123     k5_buf_add_uint32_be(buf, data->length);
124     k5_buf_add_len(buf, data->data, data->length);
125 }
126 
127 /* Marshal components into a cookie. */
128 static krb5_error_code
make_cookie(int stage,int32_t group,const krb5_data * spake,const krb5_data * thash,krb5_data * cookie_out)129 make_cookie(int stage, int32_t group, const krb5_data *spake,
130             const krb5_data *thash, krb5_data *cookie_out)
131 {
132     struct k5buf buf;
133 
134     *cookie_out = empty_data();
135     k5_buf_init_dynamic_zap(&buf);
136 
137     /* Marshal the version, stage, and group. */
138     k5_buf_add_uint16_be(&buf, 1);
139     k5_buf_add_uint16_be(&buf, stage);
140     k5_buf_add_uint32_be(&buf, group);
141 
142     /* Marshal the data fields. */
143     marshal_data(&buf, spake);
144     marshal_data(&buf, thash);
145 
146     /* When second factor support is implemented, we should add factor data
147      * here. */
148 
149     if (buf.data == NULL)
150         return ENOMEM;
151     *cookie_out = make_data(buf.data, buf.len);
152     return 0;
153 }
154 
155 /* Add authentication indicators if any are configured for SPAKE. */
156 static krb5_error_code
add_indicators(krb5_context context,const krb5_data * realm,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock)157 add_indicators(krb5_context context, const krb5_data *realm,
158                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock)
159 {
160     krb5_error_code ret;
161     const char *keys[4];
162     char *realmstr, **indicators, **ind;
163 
164     realmstr = k5memdup0(realm->data, realm->length, &ret);
165     if (realmstr == NULL)
166         return ret;
167     keys[0] = KRB5_CONF_REALMS;
168     keys[1] = realmstr;
169     keys[2] = KRB5_CONF_SPAKE_PREAUTH_INDICATOR;
170     keys[3] = NULL;
171     ret = profile_get_values(context->profile, keys, &indicators);
172     free(realmstr);
173     if (ret == PROF_NO_RELATION)
174         return 0;
175     if (ret)
176         return ret;
177 
178     for (ind = indicators; *ind != NULL && !ret; ind++)
179         ret = cb->add_auth_indicator(context, rock, *ind);
180 
181     profile_free_list(indicators);
182     return ret;
183 }
184 
185 /* Initialize a SPAKE module data object. */
186 static krb5_error_code
spake_init(krb5_context context,krb5_kdcpreauth_moddata * moddata_out,const char ** realmnames)187 spake_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,
188            const char **realmnames)
189 {
190     krb5_error_code ret;
191     groupstate *gstate;
192 
193     ret = group_init_state(context, TRUE, &gstate);
194     if (ret)
195         return ret;
196     *moddata_out = (krb5_kdcpreauth_moddata)gstate;
197     return 0;
198 }
199 
200 /* Release a SPAKE module data object. */
201 static void
spake_fini(krb5_context context,krb5_kdcpreauth_moddata moddata)202 spake_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
203 {
204     group_free_state((groupstate *)moddata);
205 }
206 
207 /*
208  * Generate a SPAKE challenge message for the specified group.  Use cb and rock
209  * to retrieve the initial reply key and to set a stage-0 cookie.  Invoke
210  * either erespond or vrespond with the result.
211  */
212 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)213 send_challenge(krb5_context context, groupstate *gstate, int32_t group,
214                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
215                const krb5_data *support,
216                krb5_kdcpreauth_edata_respond_fn erespond,
217                krb5_kdcpreauth_verify_respond_fn vrespond, void *arg)
218 {
219     krb5_error_code ret;
220     const krb5_keyblock *ikey;
221     krb5_pa_data **padata = NULL, *pa;
222     krb5_data kdcpriv = empty_data(), kdcpub = empty_data(), *der_msg = NULL;
223     krb5_data thash = empty_data(), cookie = empty_data();
224     krb5_data wbytes = empty_data();
225     krb5_spake_factor f, *flist[2];
226     krb5_pa_spake msg;
227 
228     ikey = cb->client_keyblock(context, rock);
229     if (ikey == NULL) {
230         ret = KRB5KDC_ERR_ETYPE_NOSUPP;
231         goto cleanup;
232     }
233 
234     ret = derive_wbytes(context, group, ikey, &wbytes);
235     if (ret)
236         goto cleanup;
237     ret = group_keygen(context, gstate, group, &wbytes, &kdcpriv, &kdcpub);
238     if (ret)
239         goto cleanup;
240 
241     /* Encode the challenge.  When second factor support is implemented, we
242      * should construct a factor list instead of hardcoding SF-NONE. */
243     f.type = SPAKE_SF_NONE;
244     f.data = NULL;
245     flist[0] = &f;
246     flist[1] = NULL;
247     msg.choice = SPAKE_MSGTYPE_CHALLENGE;
248     msg.u.challenge.group = group;
249     msg.u.challenge.pubkey = kdcpub;
250     msg.u.challenge.factors = flist;
251     ret = encode_krb5_pa_spake(&msg, &der_msg);
252     if (ret)
253         goto cleanup;
254 
255     /* Initialize and update the transcript hash with the support message (if
256      * we received one) and challenge message. */
257     ret = update_thash(context, gstate, group, &thash, support, der_msg);
258     if (ret)
259         goto cleanup;
260 
261     /* Save the group, transcript hash, and private key in a stage-0 cookie.
262      * When second factor support is implemented, also save factor state. */
263     ret = make_cookie(0, group, &kdcpriv, &thash, &cookie);
264     if (ret)
265         goto cleanup;
266     ret = cb->set_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie);
267     if (ret)
268         goto cleanup;
269 
270     ret = convert_to_padata(der_msg, &padata);
271     der_msg = NULL;
272     TRACE_SPAKE_SEND_CHALLENGE(context, group);
273 
274 cleanup:
275     zapfree(wbytes.data, wbytes.length);
276     zapfree(kdcpriv.data, kdcpriv.length);
277     zapfree(cookie.data, cookie.length);
278     krb5_free_data_contents(context, &kdcpub);
279     krb5_free_data_contents(context, &thash);
280     krb5_free_data(context, der_msg);
281 
282     if (erespond != NULL) {
283         assert(vrespond == NULL);
284         /* Grab the first pa-data element from the list, if we made one. */
285         pa = (padata == NULL) ? NULL : padata[0];
286         free(padata);
287         (*erespond)(arg, ret, pa);
288     } else {
289         assert(vrespond != NULL);
290         if (!ret)
291             ret = KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
292         (*vrespond)(arg, ret, NULL, padata, NULL);
293     }
294 }
295 
296 /* Generate the METHOD-DATA entry indicating support for SPAKE.  Include an
297  * optimistic challenge if configured to do so. */
298 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)299 spake_edata(krb5_context context, krb5_kdc_req *req,
300             krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
301             krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
302             krb5_kdcpreauth_edata_respond_fn respond, void *arg)
303 {
304     const krb5_keyblock *ikey;
305     groupstate *gstate = (groupstate *)moddata;
306     krb5_data empty = empty_data();
307     int32_t group;
308 
309     /* SPAKE requires a client key, which cannot be a single-DES key. */
310     ikey = cb->client_keyblock(context, rock);
311     if (ikey == NULL) {
312         (*respond)(arg, KRB5KDC_ERR_ETYPE_NOSUPP, NULL);
313         return;
314     }
315 
316     group = group_optimistic_challenge(gstate);
317     if (group) {
318         send_challenge(context, gstate, group, cb, rock, &empty, respond, NULL,
319                        arg);
320     } else {
321         /* No optimistic challenge configured; send an empty pa-data value. */
322         (*respond)(arg, 0, NULL);
323     }
324 }
325 
326 /* Choose a group from the client's support message and generate a
327  * challenge. */
328 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)329 verify_support(krb5_context context, groupstate *gstate,
330                krb5_spake_support *support, const krb5_data *der_msg,
331                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
332                krb5_kdcpreauth_verify_respond_fn respond, void *arg)
333 {
334     krb5_error_code ret;
335     int32_t i, group;
336 
337     for (i = 0; i < support->ngroups; i++) {
338         if (group_is_permitted(gstate, support->groups[i]))
339             break;
340     }
341     if (i == support->ngroups) {
342         TRACE_SPAKE_REJECT_SUPPORT(context);
343         ret = KRB5KDC_ERR_PREAUTH_FAILED;
344         goto error;
345     }
346     group = support->groups[i];
347     TRACE_SPAKE_RECEIVE_SUPPORT(context, group);
348 
349     send_challenge(context, gstate, group, cb, rock, der_msg, NULL, respond,
350                    arg);
351     return;
352 
353 error:
354     (*respond)(arg, ret, NULL, NULL, NULL);
355 }
356 
357 /*
358  * From the client's response message, compute the SPAKE result and decrypt the
359  * factor reply.  On success, either mark the reply as pre-authenticated and
360  * set a reply key in the pre-request module data, or generate an additional
361  * factor challenge and ask for another round of pre-authentication.
362  */
363 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)364 verify_response(krb5_context context, groupstate *gstate,
365                 krb5_spake_response *resp, const krb5_data *realm,
366                 krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
367                 krb5_enc_tkt_part *enc_tkt_reply,
368                 krb5_kdcpreauth_verify_respond_fn respond, void *arg)
369 {
370     krb5_error_code ret;
371     const krb5_keyblock *ikey;
372     krb5_keyblock *k1 = NULL, *reply_key = NULL;
373     krb5_data cookie, thash_in, kdcpriv, factors, *der_req;
374     krb5_data thash = empty_data(), der_factor = empty_data();
375     krb5_data wbytes = empty_data(), spakeresult = empty_data();
376     krb5_spake_factor *factor = NULL;
377     int stage;
378     int32_t group;
379 
380     ikey = cb->client_keyblock(context, rock);
381     if (ikey == NULL) {
382         ret = KRB5KDC_ERR_ETYPE_NOSUPP;
383         goto cleanup;
384     }
385 
386     /* Fetch the stage-0 cookie and parse it.  (All of the krb5_data results
387      * are aliases into memory owned by rock). */
388     if (!cb->get_cookie(context, rock, KRB5_PADATA_SPAKE, &cookie)) {
389         ret = KRB5KDC_ERR_PREAUTH_FAILED;
390         goto cleanup;
391     }
392     ret = parse_cookie(&cookie, &stage, &group, &kdcpriv, &thash_in, &factors);
393     if (ret)
394         goto cleanup;
395     if (stage != 0) {
396         /* The received cookie wasn't sent with a challenge. */
397         ret = KRB5KDC_ERR_PREAUTH_FAILED;
398         goto cleanup;
399     }
400     TRACE_SPAKE_RECEIVE_RESPONSE(context, &resp->pubkey);
401 
402     /* Update the transcript hash with the client public key. */
403     ret = krb5int_copy_data_contents(context, &thash_in, &thash);
404     if (ret)
405         goto cleanup;
406     ret = update_thash(context, gstate, group, &thash, &resp->pubkey, NULL);
407     if (ret)
408         goto cleanup;
409     TRACE_SPAKE_KDC_THASH(context, &thash);
410 
411     ret = derive_wbytes(context, group, ikey, &wbytes);
412     if (ret)
413         goto cleanup;
414     ret = group_result(context, gstate, group, &wbytes, &kdcpriv,
415                        &resp->pubkey, &spakeresult);
416     if (ret)
417         goto cleanup;
418 
419     /* Decrypt the response factor field using K'[1].  If the decryption
420      * integrity check fails, the client probably used the wrong password. */
421     der_req = cb->request_body(context, rock);
422     ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
423                      &thash, der_req, 1, &k1);
424     if (ret)
425         goto cleanup;
426     ret = alloc_data(&der_factor, resp->factor.ciphertext.length);
427     if (ret)
428         goto cleanup;
429     ret = krb5_c_decrypt(context, k1, KRB5_KEYUSAGE_SPAKE, NULL, &resp->factor,
430                          &der_factor);
431     if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY)
432         ret = KRB5KDC_ERR_PREAUTH_FAILED;
433     if (ret)
434         goto cleanup;
435     ret = decode_krb5_spake_factor(&der_factor, &factor);
436     if (ret)
437         goto cleanup;
438 
439     /*
440      * When second factor support is implemented, we should verify the factor
441      * data here, and possibly generate an encdata message for another hop.
442      * This function may need to be split at this point to allow for
443      * asynchronous verification of the second-factor value.  We might also
444      * need to collect authentication indicators from the second-factor module;
445      * alternatively the module could have access to cb and rock so that it can
446      * add indicators itself.
447      */
448     if (factor->type != SPAKE_SF_NONE) {
449         ret = KRB5KDC_ERR_PREAUTH_FAILED;
450         goto cleanup;
451     }
452 
453     ret = add_indicators(context, realm, cb, rock);
454     if (ret)
455         goto cleanup;
456 
457     enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
458 
459     ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
460                      &thash, der_req, 0, &reply_key);
461 
462 cleanup:
463     zapfree(wbytes.data, wbytes.length);
464     zapfree(der_factor.data, der_factor.length);
465     zapfree(spakeresult.data, spakeresult.length);
466     krb5_free_data_contents(context, &thash);
467     krb5_free_keyblock(context, k1);
468     k5_free_spake_factor(context, factor);
469     (*respond)(arg, ret, (krb5_kdcpreauth_modreq)reply_key, NULL, NULL);
470 }
471 
472 /*
473  * Decrypt and validate an additional second-factor reply.  On success, either
474  * mark the reply as pre-authenticated and set a reply key in the pre-request
475  * module data, or generate an additional factor challenge and ask for another
476  * round of pre-authentication.
477  */
478 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)479 verify_encdata(krb5_context context, krb5_enc_data *enc,
480                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
481                krb5_enc_tkt_part *enc_tkt_reply,
482                krb5_kdcpreauth_verify_respond_fn respond, void *arg)
483 {
484     /*
485      * When second factor support is implemented, we should process encdata
486      * message according to the factor type recorded in the cookie.  If the
487      * second factor exchange finishes successfully, we should set
488      * TKT_FLG_PRE_AUTH, set the reply key to K'[0], and add any auth
489      * indicators from configuration (with a call to add_indicators()) or the
490      * second factor module (unless the module has access to cb and rock and
491      * can add indicators itself).
492      */
493     (*respond)(arg, KRB5KDC_ERR_PREAUTH_FAILED, NULL, NULL, NULL);
494 }
495 
496 /*
497  * Respond to a client padata message, either by generating a SPAKE challenge,
498  * generating an additional second-factor challenge, or marking the reply as
499  * pre-authenticated and setting an additional reply key in the pre-request
500  * module data.
501  */
502 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)503 spake_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
504              krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *data,
505              krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
506              krb5_kdcpreauth_moddata moddata,
507              krb5_kdcpreauth_verify_respond_fn respond, void *arg)
508 {
509     krb5_error_code ret;
510     krb5_pa_spake *pa_spake = NULL;
511     krb5_data in_data = make_data(data->contents, data->length);
512     groupstate *gstate = (groupstate *)moddata;
513 
514     ret = decode_krb5_pa_spake(&in_data, &pa_spake);
515     if (ret) {
516         (*respond)(arg, ret, NULL, NULL, NULL);
517     } else if (pa_spake->choice == SPAKE_MSGTYPE_SUPPORT) {
518         verify_support(context, gstate, &pa_spake->u.support, &in_data, cb,
519                        rock, respond, arg);
520     } else if (pa_spake->choice == SPAKE_MSGTYPE_RESPONSE) {
521         verify_response(context, gstate, &pa_spake->u.response,
522                         &request->server->realm, cb, rock, enc_tkt_reply,
523                         respond, arg);
524     } else if (pa_spake->choice == SPAKE_MSGTYPE_ENCDATA) {
525         verify_encdata(context, &pa_spake->u.encdata, cb, rock, enc_tkt_reply,
526                        respond, arg);
527     } else {
528         ret = KRB5KDC_ERR_PREAUTH_FAILED;
529         k5_setmsg(context, ret, _("Unknown SPAKE request type"));
530         (*respond)(arg, ret, NULL, NULL, NULL);
531     }
532 
533     k5_free_pa_spake(context, pa_spake);
534 }
535 
536 /* If a key was set in the per-request module data, replace the reply key.  Do
537  * not generate any pa-data to include with the KDC reply. */
538 static krb5_error_code
spake_return(krb5_context context,krb5_pa_data * padata,krb5_data * req_pkt,krb5_kdc_req * request,krb5_kdc_rep * reply,krb5_keyblock * encrypting_key,krb5_pa_data ** send_pa_out,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_modreq modreq)539 spake_return(krb5_context context, krb5_pa_data *padata, krb5_data *req_pkt,
540              krb5_kdc_req *request, krb5_kdc_rep *reply,
541              krb5_keyblock *encrypting_key, krb5_pa_data **send_pa_out,
542              krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
543              krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq)
544 {
545     krb5_keyblock *reply_key = (krb5_keyblock *)modreq;
546 
547     if (reply_key == NULL)
548         return 0;
549     krb5_free_keyblock_contents(context, encrypting_key);
550     return krb5_copy_keyblock_contents(context, reply_key, encrypting_key);
551 }
552 
553 /* Release a per-request module data object. */
554 static void
spake_free_modreq(krb5_context context,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_modreq modreq)555 spake_free_modreq(krb5_context context, krb5_kdcpreauth_moddata moddata,
556                   krb5_kdcpreauth_modreq modreq)
557 {
558     krb5_free_keyblock(context, (krb5_keyblock *)modreq);
559 }
560 
561 krb5_error_code
562 kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
563                         krb5_plugin_vtable vtable);
564 
565 krb5_error_code
kdcpreauth_spake_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)566 kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
567                         krb5_plugin_vtable vtable)
568 {
569     krb5_kdcpreauth_vtable vt;
570     static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 };
571 
572     if (maj_ver != 1)
573         return KRB5_PLUGIN_VER_NOTSUPP;
574     vt = (krb5_kdcpreauth_vtable)vtable;
575     vt->name = "spake";
576     vt->pa_type_list = pa_types;
577     vt->init = spake_init;
578     vt->fini = spake_fini;
579     vt->edata = spake_edata;
580     vt->verify = spake_verify;
581     vt->return_padata = spake_return;
582     vt->free_modreq = spake_free_modreq;
583     return 0;
584 }
585