Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/plugins/preauth/spake/groups.c
34907 views
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* plugins/preauth/spake/groups.c - SPAKE group interfaces */
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
/*
34
* The SPAKE2 algorithm works as follows:
35
*
36
* 1. The parties agree on a group, a base element G, and constant elements M
37
* and N. In this mechanism, these parameters are determined by the
38
* registered group number.
39
* 2. Both parties derive a scalar value w from the initial key.
40
* 3. The first party (the KDC, in this mechanism) chooses a random secret
41
* scalar x and sends T=xG+wM.
42
* 4. The second party (the client, in this mechanism) chooses a random
43
* secret scalar y and sends S=yG+wN.
44
* 5. The first party computes K=x(S-wN).
45
* 6. The second party computes the same value as K=y(T-wM).
46
* 7. Both parties derive a key from a random oracle whose input incorporates
47
* the party identities, w, T, S, and K.
48
*
49
* We implement the algorithm using a vtable for each group, where the primary
50
* vtable methods are "keygen" (corresponding to step 3 or 4) and "result"
51
* (corresponding to step 5 or 6). We use the term "private scalar" to refer
52
* to x or y, and "public element" to refer to S or T.
53
*/
54
55
#include "iana.h"
56
#include "trace.h"
57
#include "groups.h"
58
59
#define DEFAULT_GROUPS_CLIENT "edwards25519"
60
#define DEFAULT_GROUPS_KDC ""
61
62
typedef struct groupent_st {
63
const groupdef *gdef;
64
groupdata *gdata;
65
} groupent;
66
67
struct groupstate_st {
68
krb5_boolean is_kdc;
69
70
/* Permitted and groups, from configuration */
71
int32_t *permitted;
72
size_t npermitted;
73
74
/* Optimistic challenge group, from configuration */
75
int32_t challenge_group;
76
77
/* Lazily-initialized list of gdata objects. */
78
groupent *data;
79
size_t ndata;
80
};
81
82
extern groupdef builtin_edwards25519;
83
#ifdef SPAKE_OPENSSL
84
extern groupdef ossl_P256;
85
extern groupdef ossl_P384;
86
extern groupdef ossl_P521;
87
#endif
88
89
static const groupdef *groupdefs[] = {
90
&builtin_edwards25519,
91
#ifdef SPAKE_OPENSSL
92
&ossl_P256,
93
&ossl_P384,
94
&ossl_P521,
95
#endif
96
NULL
97
};
98
99
/* Find a groupdef structure by group number. Return NULL on failure. */
100
static const groupdef *
101
find_gdef(int32_t group)
102
{
103
size_t i;
104
105
for (i = 0; groupdefs[i] != NULL; i++) {
106
if (groupdefs[i]->reg->id == group)
107
return groupdefs[i];
108
}
109
110
return NULL;
111
}
112
113
/* Find a group number by name. Return 0 on failure. */
114
static int32_t
115
find_gnum(const char *name)
116
{
117
size_t i;
118
119
for (i = 0; groupdefs[i] != NULL; i++) {
120
if (strcasecmp(name, groupdefs[i]->reg->name) == 0)
121
return groupdefs[i]->reg->id;
122
}
123
return 0;
124
}
125
126
static krb5_boolean
127
in_grouplist(const int32_t *list, size_t count, int32_t group)
128
{
129
size_t i;
130
131
for (i = 0; i < count; i++) {
132
if (list[i] == group)
133
return TRUE;
134
}
135
136
return FALSE;
137
}
138
139
/* Retrieve a group data object for group within gstate, lazily initializing it
140
* if necessary. */
141
static krb5_error_code
142
get_gdata(krb5_context context, groupstate *gstate, const groupdef *gdef,
143
groupdata **gdata_out)
144
{
145
krb5_error_code ret;
146
groupent *ent, *newptr;
147
148
*gdata_out = NULL;
149
150
/* Look for an existing entry. */
151
for (ent = gstate->data; ent < gstate->data + gstate->ndata; ent++) {
152
if (ent->gdef == gdef) {
153
*gdata_out = ent->gdata;
154
return 0;
155
}
156
}
157
158
/* Make a new entry. */
159
newptr = realloc(gstate->data, (gstate->ndata + 1) * sizeof(groupent));
160
if (newptr == NULL)
161
return ENOMEM;
162
gstate->data = newptr;
163
ent = &gstate->data[gstate->ndata];
164
ent->gdef = gdef;
165
ent->gdata = NULL;
166
if (gdef->init != NULL) {
167
ret = gdef->init(context, gdef, &ent->gdata);
168
if (ret)
169
return ret;
170
}
171
gstate->ndata++;
172
*gdata_out = ent->gdata;
173
return 0;
174
}
175
176
/* Destructively parse str into a list of group numbers. */
177
static krb5_error_code
178
parse_groups(krb5_context context, char *str, int32_t **list_out,
179
size_t *count_out)
180
{
181
const char *const delim = " \t\r\n,";
182
char *token, *save = NULL;
183
int32_t group, *newptr, *list = NULL;
184
size_t count = 0;
185
186
*list_out = NULL;
187
*count_out = 0;
188
189
/* Walk through the words in profstr. */
190
for (token = strtok_r(str, delim, &save); token != NULL;
191
token = strtok_r(NULL, delim, &save)) {
192
group = find_gnum(token);
193
if (!group) {
194
TRACE_SPAKE_UNKNOWN_GROUP(context, token);
195
continue;
196
}
197
if (in_grouplist(list, count, group))
198
continue;
199
newptr = realloc(list, (count + 1) * sizeof(*list));
200
if (newptr == NULL) {
201
free(list);
202
return ENOMEM;
203
}
204
list = newptr;
205
list[count++] = group;
206
}
207
208
*list_out = list;
209
*count_out = count;
210
return 0;
211
}
212
213
krb5_error_code
214
group_init_state(krb5_context context, krb5_boolean is_kdc,
215
groupstate **gstate_out)
216
{
217
krb5_error_code ret;
218
groupstate *gstate;
219
const char *defgroups;
220
char *profstr1 = NULL, *profstr2 = NULL;
221
int32_t *permitted = NULL, challenge_group = 0;
222
size_t npermitted;
223
224
*gstate_out = NULL;
225
226
defgroups = is_kdc ? DEFAULT_GROUPS_KDC : DEFAULT_GROUPS_CLIENT;
227
ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
228
KRB5_CONF_SPAKE_PREAUTH_GROUPS, NULL, defgroups,
229
&profstr1);
230
if (ret)
231
goto cleanup;
232
ret = parse_groups(context, profstr1, &permitted, &npermitted);
233
if (ret)
234
goto cleanup;
235
if (npermitted == 0) {
236
ret = KRB5_PLUGIN_OP_NOTSUPP;
237
k5_setmsg(context, ret, _("No SPAKE preauth groups configured"));
238
goto cleanup;
239
}
240
241
if (is_kdc) {
242
/*
243
* Check for a configured optimistic challenge group. If one is set,
244
* the KDC will send a challenge in the PREAUTH_REQUIRED method data,
245
* before receiving the list of supported groups.
246
*/
247
ret = profile_get_string(context->profile, KRB5_CONF_KDCDEFAULTS,
248
KRB5_CONF_SPAKE_PREAUTH_KDC_CHALLENGE, NULL,
249
NULL, &profstr2);
250
if (ret)
251
goto cleanup;
252
if (profstr2 != NULL) {
253
challenge_group = find_gnum(profstr2);
254
if (!in_grouplist(permitted, npermitted, challenge_group)) {
255
ret = KRB5_PLUGIN_OP_NOTSUPP;
256
k5_setmsg(context, ret,
257
_("SPAKE challenge group not a permitted group: %s"),
258
profstr2);
259
goto cleanup;
260
}
261
}
262
}
263
264
gstate = k5alloc(sizeof(*gstate), &ret);
265
if (gstate == NULL)
266
goto cleanup;
267
gstate->is_kdc = is_kdc;
268
gstate->permitted = permitted;
269
gstate->npermitted = npermitted;
270
gstate->challenge_group = challenge_group;
271
permitted = NULL;
272
gstate->data = NULL;
273
gstate->ndata = 0;
274
*gstate_out = gstate;
275
276
cleanup:
277
profile_release_string(profstr1);
278
profile_release_string(profstr2);
279
free(permitted);
280
return ret;
281
}
282
283
284
void
285
group_free_state(groupstate *gstate)
286
{
287
groupent *ent;
288
289
for (ent = gstate->data; ent < gstate->data + gstate->ndata; ent++) {
290
if (ent->gdata != NULL && ent->gdef->fini != NULL)
291
ent->gdef->fini(ent->gdata);
292
}
293
294
free(gstate->permitted);
295
free(gstate->data);
296
free(gstate);
297
}
298
299
krb5_boolean
300
group_is_permitted(groupstate *gstate, int32_t group)
301
{
302
return in_grouplist(gstate->permitted, gstate->npermitted, group);
303
}
304
305
void
306
group_get_permitted(groupstate *gstate, int32_t **list_out, int32_t *count_out)
307
{
308
*list_out = gstate->permitted;
309
*count_out = gstate->npermitted;
310
}
311
312
krb5_int32
313
group_optimistic_challenge(groupstate *gstate)
314
{
315
assert(gstate->is_kdc);
316
return gstate->challenge_group;
317
}
318
319
krb5_error_code
320
group_mult_len(int32_t group, size_t *len_out)
321
{
322
const groupdef *gdef;
323
324
*len_out = 0;
325
gdef = find_gdef(group);
326
if (gdef == NULL)
327
return EINVAL;
328
*len_out = gdef->reg->mult_len;
329
return 0;
330
}
331
332
krb5_error_code
333
group_keygen(krb5_context context, groupstate *gstate, int32_t group,
334
const krb5_data *wbytes, krb5_data *priv_out, krb5_data *pub_out)
335
{
336
krb5_error_code ret;
337
const groupdef *gdef;
338
groupdata *gdata;
339
uint8_t *priv = NULL, *pub = NULL;
340
341
*priv_out = empty_data();
342
*pub_out = empty_data();
343
gdef = find_gdef(group);
344
if (gdef == NULL || wbytes->length != gdef->reg->mult_len)
345
return EINVAL;
346
ret = get_gdata(context, gstate, gdef, &gdata);
347
if (ret)
348
return ret;
349
350
priv = k5alloc(gdef->reg->mult_len, &ret);
351
if (priv == NULL)
352
goto cleanup;
353
pub = k5alloc(gdef->reg->elem_len, &ret);
354
if (pub == NULL)
355
goto cleanup;
356
357
ret = gdef->keygen(context, gdata, (uint8_t *)wbytes->data, gstate->is_kdc,
358
priv, pub);
359
if (ret)
360
goto cleanup;
361
362
*priv_out = make_data(priv, gdef->reg->mult_len);
363
*pub_out = make_data(pub, gdef->reg->elem_len);
364
priv = pub = NULL;
365
TRACE_SPAKE_KEYGEN(context, pub_out);
366
367
cleanup:
368
zapfree(priv, gdef->reg->mult_len);
369
free(pub);
370
return ret;
371
}
372
373
krb5_error_code
374
group_result(krb5_context context, groupstate *gstate, int32_t group,
375
const krb5_data *wbytes, const krb5_data *ourpriv,
376
const krb5_data *theirpub, krb5_data *spakeresult_out)
377
{
378
krb5_error_code ret;
379
const groupdef *gdef;
380
groupdata *gdata;
381
uint8_t *spakeresult = NULL;
382
383
*spakeresult_out = empty_data();
384
gdef = find_gdef(group);
385
if (gdef == NULL || wbytes->length != gdef->reg->mult_len)
386
return EINVAL;
387
if (ourpriv->length != gdef->reg->mult_len ||
388
theirpub->length != gdef->reg->elem_len)
389
return EINVAL;
390
ret = get_gdata(context, gstate, gdef, &gdata);
391
if (ret)
392
return ret;
393
394
spakeresult = k5alloc(gdef->reg->elem_len, &ret);
395
if (spakeresult == NULL)
396
goto cleanup;
397
398
/* Invert is_kdc here to use the other party's constant. */
399
ret = gdef->result(context, gdata, (uint8_t *)wbytes->data,
400
(uint8_t *)ourpriv->data, (uint8_t *)theirpub->data,
401
!gstate->is_kdc, spakeresult);
402
if (ret)
403
goto cleanup;
404
405
*spakeresult_out = make_data(spakeresult, gdef->reg->elem_len);
406
spakeresult = NULL;
407
TRACE_SPAKE_RESULT(context, spakeresult_out);
408
409
cleanup:
410
zapfree(spakeresult, gdef->reg->elem_len);
411
return ret;
412
}
413
414
krb5_error_code
415
group_hash_len(int32_t group, size_t *len_out)
416
{
417
const groupdef *gdef;
418
419
*len_out = 0;
420
gdef = find_gdef(group);
421
if (gdef == NULL)
422
return EINVAL;
423
*len_out = gdef->reg->hash_len;
424
return 0;
425
}
426
427
krb5_error_code
428
group_hash(krb5_context context, groupstate *gstate, int32_t group,
429
const krb5_data *dlist, size_t ndata, uint8_t *result_out)
430
{
431
krb5_error_code ret;
432
const groupdef *gdef;
433
groupdata *gdata;
434
435
gdef = find_gdef(group);
436
if (gdef == NULL)
437
return EINVAL;
438
ret = get_gdata(context, gstate, gdef, &gdata);
439
if (ret)
440
return ret;
441
return gdef->hash(context, gdata, dlist, ndata, result_out);
442
}
443
444