Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / plugins / preauth / spake / spake_client.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/spake/spake_client.c - SPAKE clpreauth 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-spake.h"
35 #include "trace.h"
36 #include "util.h"
37 #include "iana.h"
38 #include "groups.h"
39 #include <krb5/clpreauth_plugin.h>
40
41 typedef struct reqstate_st {
42     krb5_pa_spake *msg;         /* set in prep_questions, used in process */
43     krb5_keyblock *initial_key;
44     krb5_data *support;
45     krb5_data thash;
46     krb5_data spakeresult;
47 } reqstate;
48
49 /* Return true if SF-NONE is present in factors. */
50 static krb5_boolean
51 contains_sf_none(krb5_spake_factor **factors)
52 {
53     int i;
54
55     for (i = 0; factors != NULL && factors[i] != NULL; i++) {
56         if (factors[i]->type == SPAKE_SF_NONE)
57             return TRUE;
58     }
59     return FALSE;
60 }
61
62 static krb5_error_code
63 spake_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)
64 {
65     krb5_error_code ret;
66     groupstate *gstate;
67
68     ret = group_init_state(context, FALSE, &gstate);
69     if (ret)
70         return ret;
71     *moddata_out = (krb5_clpreauth_moddata)gstate;
72     return 0;
73 }
74
75 static void
76 spake_fini(krb5_context context, krb5_clpreauth_moddata moddata)
77 {
78     group_free_state((groupstate *)moddata);
79 }
80
81 static void
82 spake_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
83                    krb5_clpreauth_modreq *modreq_out)
84 {
85     *modreq_out = calloc(1, sizeof(reqstate));
86 }
87
88 static void
89 spake_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
90                    krb5_clpreauth_modreq modreq)
91 {
92     reqstate *st = (reqstate *)modreq;
93
94     k5_free_pa_spake(context, st->msg);
95     krb5_free_keyblock(context, st->initial_key);
96     krb5_free_data(context, st->support);
97     krb5_free_data_contents(context, &st->thash);
98     zapfree(st->spakeresult.data, st->spakeresult.length);
99     free(st);
100 }
101
102 static krb5_error_code
103 spake_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
104                      krb5_clpreauth_modreq modreq,
105                      krb5_get_init_creds_opt *opt, krb5_clpreauth_callbacks cb,
106                      krb5_clpreauth_rock rock, krb5_kdc_req *req,
107                      krb5_data *enc_req, krb5_data *enc_prev_req,
108                      krb5_pa_data *pa_data)
109 {
110     krb5_error_code ret;
111     groupstate *gstate = (groupstate *)moddata;
112     reqstate *st = (reqstate *)modreq;
113     krb5_data in_data;
114     krb5_spake_challenge *ch;
115
116     if (st == NULL)
117         return ENOMEM;
118
119     /* We don't need to ask any questions to send a support message. */
120     if (pa_data->length == 0)
121         return 0;
122
123     /* Decode the incoming message, replacing any previous one in the request
124      * state.  If we can't decode it, we have no questions to ask. */
125     k5_free_pa_spake(context, st->msg);
126     st->msg = NULL;
127     in_data = make_data(pa_data->contents, pa_data->length);
128     ret = decode_krb5_pa_spake(&in_data, &st->msg);
129     if (ret)
130         return (ret == ENOMEM) ? ENOMEM : 0;
131
132     if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {
133         ch = &st->msg->u.challenge;
134         if (!group_is_permitted(gstate, ch->group))
135             return 0;
136         /* When second factor support is implemented, we should ask questions
137          * based on the factors in the challenge. */
138         if (!contains_sf_none(ch->factors))
139             return 0;
140         /* We will need the AS key to respond to the challenge. */
141         cb->need_as_key(context, rock);
142     } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {
143         /* When second factor support is implemented, we should decrypt the
144          * encdata message and ask questions based on the factor data. */
145     }
146     return 0;
147 }
148
149 /*
150  * Output a PA-SPAKE support message indicating which groups we support.  This
151  * may be done for optimistic preauth, in response to an empty message, or in
152  * response to a challenge using a group we do not support.  Save the support
153  * message in st->support.
154  */
155 static krb5_error_code
156 send_support(krb5_context context, groupstate *gstate, reqstate *st,
157              krb5_pa_data ***pa_out)
158 {
159     krb5_error_code ret;
160     krb5_data *support;
161     krb5_pa_spake msg;
162
163     msg.choice = SPAKE_MSGTYPE_SUPPORT;
164     group_get_permitted(gstate, &msg.u.support.groups, &msg.u.support.ngroups);
165     ret = encode_krb5_pa_spake(&msg, &support);
166     if (ret)
167         return ret;
168
169     /* Save the support message for later use in the transcript hash. */
170     ret = krb5_copy_data(context, support, &st->support);
171     if (ret) {
172         krb5_free_data(context, support);
173         return ret;
174     }
175
176     TRACE_SPAKE_SEND_SUPPORT(context);
177     return convert_to_padata(support, pa_out);
178 }
179
180 static krb5_error_code
181 process_challenge(krb5_context context, groupstate *gstate, reqstate *st,
182                   krb5_spake_challenge *ch, const krb5_data *der_msg,
183                   krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
184                   krb5_prompter_fct prompter, void *prompter_data,
185                   const krb5_data *der_req, krb5_pa_data ***pa_out)
186 {
187     krb5_error_code ret;
188     krb5_keyblock *k0 = NULL, *k1 = NULL, *as_key;
189     krb5_spake_factor factor;
190     krb5_pa_spake msg;
191     krb5_data *der_factor = NULL, *response;
192     krb5_data clpriv = empty_data(), clpub = empty_data();
193     krb5_data wbytes = empty_data();
194     krb5_enc_data enc_factor;
195
196     enc_factor.ciphertext = empty_data();
197
198     /* Not expected if we processed a challenge and didn't reject it. */
199     if (st->initial_key != NULL)
200         return KRB5KDC_ERR_PREAUTH_FAILED;
201
202     if (!group_is_permitted(gstate, ch->group)) {
203         TRACE_SPAKE_REJECT_CHALLENGE(context, ch->group);
204         /* No point in sending a second support message. */
205         if (st->support != NULL)
206             return KRB5KDC_ERR_PREAUTH_FAILED;
207         return send_support(context, gstate, st, pa_out);
208     }
209
210     /* Initialize and update the transcript with the concatenation of the
211      * support message (if we sent one) and the received challenge. */
212     ret = update_thash(context, gstate, ch->group, &st->thash, st->support,
213                        der_msg);
214     if (ret)
215         return ret;
216
217     TRACE_SPAKE_RECEIVE_CHALLENGE(context, ch->group, &ch->pubkey);
218
219     /* When second factor support is implemented, we should check for a
220      * supported factor type instead of just checking for SF-NONE. */
221     if (!contains_sf_none(ch->factors))
222         return KRB5KDC_ERR_PREAUTH_FAILED;
223
224     ret = cb->get_as_key(context, rock, &as_key);
225     if (ret)
226         goto cleanup;
227     ret = krb5_copy_keyblock(context, as_key, &st->initial_key);
228     if (ret)
229         goto cleanup;
230     ret = derive_wbytes(context, ch->group, st->initial_key, &wbytes);
231     if (ret)
232         goto cleanup;
233     ret = group_keygen(context, gstate, ch->group, &wbytes, &clpriv, &clpub);
234     if (ret)
235         goto cleanup;
236     ret = group_result(context, gstate, ch->group, &wbytes, &clpriv,
237                        &ch->pubkey, &st->spakeresult);
238     if (ret)
239         goto cleanup;
240
241     ret = update_thash(context, gstate, ch->group, &st->thash, &clpub, NULL);
242     if (ret)
243         goto cleanup;
244     TRACE_SPAKE_CLIENT_THASH(context, &st->thash);
245
246     /* Replace the reply key with K'[0]. */
247     ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,
248                      &st->spakeresult, &st->thash, der_req, 0, &k0);
249     if (ret)
250         goto cleanup;
251     ret = cb->set_as_key(context, rock, k0);
252     if (ret)
253         goto cleanup;
254
255     /* Encrypt a SPAKESecondFactor message with K'[1]. */
256     ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes,
257                      &st->spakeresult, &st->thash, der_req, 1, &k1);
258     if (ret)
259         goto cleanup;
260     /* When second factor support is implemented, we should construct an
261      * appropriate factor here instead of hardcoding SF-NONE. */
262     factor.type = SPAKE_SF_NONE;
263     factor.data = NULL;
264     ret = encode_krb5_spake_factor(&factor, &der_factor);
265     if (ret)
266         goto cleanup;
267     ret = krb5_encrypt_helper(context, k1, KRB5_KEYUSAGE_SPAKE, der_factor,
268                               &enc_factor);
269     if (ret)
270         goto cleanup;
271
272     /* Encode and output a response message. */
273     msg.choice = SPAKE_MSGTYPE_RESPONSE;
274     msg.u.response.pubkey = clpub;
275     msg.u.response.factor = enc_factor;
276     ret = encode_krb5_pa_spake(&msg, &response);
277     if (ret)
278         goto cleanup;
279     TRACE_SPAKE_SEND_RESPONSE(context);
280     ret = convert_to_padata(response, pa_out);
281     if (ret)
282         goto cleanup;
283
284     cb->disable_fallback(context, rock);
285
286 cleanup:
287     krb5_free_keyblock(context, k0);
288     krb5_free_keyblock(context, k1);
289     krb5_free_data_contents(context, &enc_factor.ciphertext);
290     krb5_free_data_contents(context, &clpub);
291     zapfree(clpriv.data, clpriv.length);
292     zapfree(wbytes.data, wbytes.length);
293     if (der_factor != NULL) {
294         zapfree(der_factor->data, der_factor->length);
295         free(der_factor);
296     }
297     return ret;
298 }
299
300 static krb5_error_code
301 process_encdata(krb5_context context, reqstate *st, krb5_enc_data *enc,
302                 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
303                 krb5_prompter_fct prompter, void *prompter_data,
304                 const krb5_data *der_prev_req, const krb5_data *der_req,
305                 krb5_pa_data ***pa_out)
306 {
307     /* Not expected if we haven't sent a response yet. */
308     if (st->initial_key == NULL || st->spakeresult.length == 0)
309         return KRB5KDC_ERR_PREAUTH_FAILED;
310
311     /*
312      * When second factor support is implemented, we should process encdata
313      * messages according to the factor type.  We should make sure to re-derive
314      * K'[0] and replace the reply key again, in case the request has changed.
315      * We should use der_prev_req to derive K'[n] to decrypt factor from the
316      * KDC.  We should use der_req to derive K'[n+1] for the next message to
317      * send to the KDC.
318      */
319     return KRB5_PLUGIN_OP_NOTSUPP;
320 }
321
322 static krb5_error_code
323 spake_process(krb5_context context, krb5_clpreauth_moddata moddata,
324               krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
325               krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
326               krb5_kdc_req *req, krb5_data *der_req, krb5_data *der_prev_req,
327               krb5_pa_data *pa_in, krb5_prompter_fct prompter,
328               void *prompter_data, krb5_pa_data ***pa_out)
329 {
330     krb5_error_code ret;
331     groupstate *gstate = (groupstate *)moddata;
332     reqstate *st = (reqstate *)modreq;
333     krb5_data in_data;
334
335     if (st == NULL)
336         return ENOMEM;
337
338     if (pa_in->length == 0) {
339         /* Not expected if we already sent a support message. */
340         if (st->support != NULL)
341             return KRB5KDC_ERR_PREAUTH_FAILED;
342         return send_support(context, gstate, st, pa_out);
343     }
344
345     if (st->msg == NULL) {
346         /* The message failed to decode in spake_prep_questions(). */
347         ret = KRB5KDC_ERR_PREAUTH_FAILED;
348     } else if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) {
349         in_data = make_data(pa_in->contents, pa_in->length);
350         ret = process_challenge(context, gstate, st, &st->msg->u.challenge,
351                                 &in_data, cb, rock, prompter, prompter_data,
352                                 der_req, pa_out);
353     } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) {
354         ret = process_encdata(context, st, &st->msg->u.encdata, cb, rock,
355                               prompter, prompter_data, der_prev_req, der_req,
356                               pa_out);
357     } else {
358         /* Unexpected message type */
359         ret = KRB5KDC_ERR_PREAUTH_FAILED;
360     }
361
362     return ret;
363 }
364
365 krb5_error_code
366 clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
367                        krb5_plugin_vtable vtable);
368
369 krb5_error_code
370 clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
371                        krb5_plugin_vtable vtable)
372 {
373     krb5_clpreauth_vtable vt;
374     static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 };
375
376     if (maj_ver != 1)
377         return KRB5_PLUGIN_VER_NOTSUPP;
378     vt = (krb5_clpreauth_vtable)vtable;
379     vt->name = "spake";
380     vt->pa_type_list = pa_types;
381     vt->init = spake_init;
382     vt->fini = spake_fini;
383     vt->request_init = spake_request_init;
384     vt->request_fini = spake_request_fini;
385     vt->process = spake_process;
386     vt->prep_questions = spake_prep_questions;
387     return 0;
388 }