Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/plugins/preauth/spake/spake_client.c
34889 views
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
}
389
390