Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/tests/responder.c
34878 views
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* tests/responder.c - Test harness for responder callbacks and the like. */
3
/*
4
* Copyright 2013 Red Hat, Inc. All rights reserved.
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions are met:
8
*
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
*
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in
14
* the documentation and/or other materials provided with the
15
* distribution.
16
*
17
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
*/
29
30
/*
31
* A helper for testing PKINIT and responder callbacks.
32
*
33
* This test helper takes multiple options and one argument.
34
*
35
* responder [options] principal
36
* -X preauth_option -> preauth options, as for kinit
37
* -x challenge -> expected responder challenge, of the form
38
* "question=challenge"
39
* -r response -> provide a reponder answer, in the form
40
* "question=answer"
41
* -c -> print the pkinit challenge
42
* -p identity=pin -> provide a pkinit answer, in the form "identity=pin"
43
* -o index=value:pin -> provide an OTP answer, in the form "index=value:pin"
44
* principal -> client principal name
45
*
46
* If the responder callback isn't called, that's treated as an error.
47
*
48
* If an expected responder challenge is specified, when the responder
49
* callback is called, the challenge associated with the specified question is
50
* compared against the specified value. If the value provided to the
51
* callback doesn't parse as JSON, a literal string compare is performed,
52
* otherwise both values are parsed as JSON and then re-encoded before
53
* comparison. In either case, the comparison must succeed.
54
*
55
* Any missing data or mismatches are treated as errors.
56
*/
57
58
#include <k5-platform.h>
59
#include <k5-json.h>
60
#include <sys/types.h>
61
#include <unistd.h>
62
#include <krb5.h>
63
64
struct responder_data {
65
krb5_boolean called;
66
krb5_boolean print_pkinit_challenge;
67
const char *challenge;
68
const char *response;
69
const char *pkinit_answer;
70
const char *otp_answer;
71
};
72
73
static krb5_error_code
74
responder(krb5_context ctx, void *rawdata, krb5_responder_context rctx)
75
{
76
krb5_error_code err;
77
char *key, *value, *pin, *encoded1, *encoded2;
78
const char *challenge;
79
k5_json_value decoded1, decoded2;
80
k5_json_object ids;
81
k5_json_number val;
82
krb5_int32 token_flags;
83
struct responder_data *data = rawdata;
84
krb5_responder_pkinit_challenge *chl;
85
krb5_responder_otp_challenge *ochl;
86
unsigned int i, n;
87
88
data->called = TRUE;
89
90
/* Check that a particular challenge has the specified expected value. */
91
if (data->challenge != NULL) {
92
/* Separate the challenge name and its expected value. */
93
key = strdup(data->challenge);
94
if (key == NULL)
95
exit(ENOMEM);
96
value = key + strcspn(key, "=");
97
if (*value != '\0')
98
*value++ = '\0';
99
/* Read the challenge. */
100
challenge = krb5_responder_get_challenge(ctx, rctx, key);
101
err = k5_json_decode(value, &decoded1);
102
/* Check for "no challenge". */
103
if (challenge == NULL && *value == '\0') {
104
fprintf(stderr, "OK: (no challenge) == (no challenge)\n");
105
} else if (err != 0) {
106
/* It's not JSON, so assume we're just after a string compare. */
107
if (strcmp(challenge, value) == 0) {
108
fprintf(stderr, "OK: \"%s\" == \"%s\"\n", challenge, value);
109
} else {
110
fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", challenge, value);
111
exit(1);
112
}
113
} else {
114
/* Assume we're after a JSON compare - decode the actual value. */
115
err = k5_json_decode(challenge, &decoded2);
116
if (err != 0) {
117
fprintf(stderr, "error decoding \"%s\"\n", challenge);
118
exit(1);
119
}
120
/* Re-encode the expected challenge and the actual challenge... */
121
err = k5_json_encode(decoded1, &encoded1);
122
if (err != 0) {
123
fprintf(stderr, "error encoding json data\n");
124
exit(1);
125
}
126
err = k5_json_encode(decoded2, &encoded2);
127
if (err != 0) {
128
fprintf(stderr, "error encoding json data\n");
129
exit(1);
130
}
131
k5_json_release(decoded1);
132
k5_json_release(decoded2);
133
/* ... and see if they look the same. */
134
if (strcmp(encoded1, encoded2) == 0) {
135
fprintf(stderr, "OK: \"%s\" == \"%s\"\n", encoded1, encoded2);
136
} else {
137
fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", encoded1,
138
encoded2);
139
exit(1);
140
}
141
free(encoded1);
142
free(encoded2);
143
}
144
free(key);
145
}
146
147
/* Provide a particular response for a challenge. */
148
if (data->response != NULL) {
149
/* Separate the challenge and its data content... */
150
key = strdup(data->response);
151
if (key == NULL)
152
exit(ENOMEM);
153
value = key + strcspn(key, "=");
154
if (*value != '\0')
155
*value++ = '\0';
156
/* ... and pass it in. */
157
err = krb5_responder_set_answer(ctx, rctx, key, value);
158
if (err != 0) {
159
fprintf(stderr, "error setting response\n");
160
exit(1);
161
}
162
free(key);
163
}
164
165
if (data->print_pkinit_challenge) {
166
/* Read the PKINIT challenge, formatted as a structure. */
167
err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
168
if (err != 0) {
169
fprintf(stderr, "error getting pkinit challenge\n");
170
exit(1);
171
}
172
if (chl != NULL) {
173
for (n = 0; chl->identities[n] != NULL; n++)
174
continue;
175
for (i = 0; chl->identities[i] != NULL; i++) {
176
if (chl->identities[i]->token_flags != -1) {
177
printf("identity %u/%u: %s (flags=0x%lx)\n", i + 1, n,
178
chl->identities[i]->identity,
179
(long)chl->identities[i]->token_flags);
180
} else {
181
printf("identity %u/%u: %s\n", i + 1, n,
182
chl->identities[i]->identity);
183
}
184
}
185
}
186
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
187
}
188
189
/* Provide a particular response for the PKINIT challenge. */
190
if (data->pkinit_answer != NULL) {
191
/* Read the PKINIT challenge, formatted as a structure. */
192
err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
193
if (err != 0) {
194
fprintf(stderr, "error getting pkinit challenge\n");
195
exit(1);
196
}
197
/*
198
* In case order matters, if the identity starts with "FILE:", exercise
199
* the set_answer function, with the real answer second.
200
*/
201
if (chl != NULL &&
202
chl->identities != NULL &&
203
chl->identities[0] != NULL) {
204
if (strncmp(chl->identities[0]->identity, "FILE:", 5) == 0)
205
krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar");
206
}
207
/* Provide the real answer. */
208
key = strdup(data->pkinit_answer);
209
if (key == NULL)
210
exit(ENOMEM);
211
value = strrchr(key, '=');
212
if (value != NULL)
213
*value++ = '\0';
214
else
215
value = "";
216
err = krb5_responder_pkinit_set_answer(ctx, rctx, key, value);
217
if (err != 0) {
218
fprintf(stderr, "error setting response\n");
219
exit(1);
220
}
221
free(key);
222
/*
223
* In case order matters, if the identity starts with "PKCS12:",
224
* exercise the set_answer function, with the real answer first.
225
*/
226
if (chl != NULL &&
227
chl->identities != NULL &&
228
chl->identities[0] != NULL) {
229
if (strncmp(chl->identities[0]->identity, "PKCS12:", 7) == 0)
230
krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar");
231
}
232
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
233
}
234
235
/*
236
* Something we always check: read the PKINIT challenge, both as a
237
* structure and in JSON form, reconstruct the JSON form from the
238
* structure's contents, and check that they're the same.
239
*/
240
challenge = krb5_responder_get_challenge(ctx, rctx,
241
KRB5_RESPONDER_QUESTION_PKINIT);
242
if (challenge != NULL) {
243
krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
244
if (chl == NULL) {
245
fprintf(stderr, "pkinit raw challenge set, "
246
"but structure is NULL\n");
247
exit(1);
248
}
249
if (k5_json_object_create(&ids) != 0) {
250
fprintf(stderr, "error creating json objects\n");
251
exit(1);
252
}
253
for (i = 0; chl->identities[i] != NULL; i++) {
254
token_flags = chl->identities[i]->token_flags;
255
if (k5_json_number_create(token_flags, &val) != 0) {
256
fprintf(stderr, "error creating json number\n");
257
exit(1);
258
}
259
if (k5_json_object_set(ids, chl->identities[i]->identity,
260
val) != 0) {
261
fprintf(stderr, "error adding json number to object\n");
262
exit(1);
263
}
264
k5_json_release(val);
265
}
266
/* Encode the structure... */
267
err = k5_json_encode(ids, &encoded1);
268
if (err != 0) {
269
fprintf(stderr, "error encoding json data\n");
270
exit(1);
271
}
272
k5_json_release(ids);
273
/* ... and see if they look the same. */
274
if (strcmp(encoded1, challenge) != 0) {
275
fprintf(stderr, "\"%s\" != \"%s\"\n", encoded1, challenge);
276
exit(1);
277
}
278
krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
279
free(encoded1);
280
}
281
282
/* Provide a particular response for an OTP challenge. */
283
if (data->otp_answer != NULL) {
284
if (krb5_responder_otp_get_challenge(ctx, rctx, &ochl) == 0) {
285
key = strchr(data->otp_answer, '=');
286
if (key != NULL) {
287
/* Make a copy of the answer that we can chop up. */
288
key = strdup(data->otp_answer);
289
if (key == NULL)
290
return ENOMEM;
291
/* Isolate the ti value. */
292
value = strchr(key, '=');
293
*value++ = '\0';
294
n = atoi(key);
295
/* Break the value and PIN apart. */
296
pin = strchr(value, ':');
297
if (pin != NULL)
298
*pin++ = '\0';
299
err = krb5_responder_otp_set_answer(ctx, rctx, n, value, pin);
300
if (err != 0) {
301
fprintf(stderr, "error setting response\n");
302
exit(1);
303
}
304
free(key);
305
}
306
krb5_responder_otp_challenge_free(ctx, rctx, ochl);
307
}
308
}
309
310
return 0;
311
}
312
313
int
314
main(int argc, char **argv)
315
{
316
krb5_context context;
317
krb5_ccache ccache;
318
krb5_get_init_creds_opt *opts;
319
krb5_principal principal;
320
krb5_creds creds;
321
krb5_error_code err;
322
const char *errmsg;
323
char *opt, *val;
324
struct responder_data response;
325
int c;
326
327
err = krb5_init_context(&context);
328
if (err != 0) {
329
fprintf(stderr, "error starting Kerberos: %s\n", error_message(err));
330
return err;
331
}
332
err = krb5_get_init_creds_opt_alloc(context, &opts);
333
if (err != 0) {
334
fprintf(stderr, "error initializing options: %s\n",
335
error_message(err));
336
return err;
337
}
338
err = krb5_cc_default(context, &ccache);
339
if (err != 0) {
340
fprintf(stderr, "error resolving default ccache: %s\n",
341
error_message(err));
342
return err;
343
}
344
err = krb5_get_init_creds_opt_set_out_ccache(context, opts, ccache);
345
if (err != 0) {
346
fprintf(stderr, "error setting output ccache: %s\n",
347
error_message(err));
348
return err;
349
}
350
351
memset(&response, 0, sizeof(response));
352
while ((c = getopt(argc, argv, "X:x:cr:p:")) != -1) {
353
switch (c) {
354
case 'X':
355
/* Like kinit, set a generic preauth option. */
356
opt = strdup(optarg);
357
val = opt + strcspn(opt, "=");
358
if (*val != '\0') {
359
*val++ = '\0';
360
}
361
err = krb5_get_init_creds_opt_set_pa(context, opts, opt, val);
362
if (err != 0) {
363
fprintf(stderr, "error setting option \"%s\": %s\n", opt,
364
error_message(err));
365
return err;
366
}
367
free(opt);
368
break;
369
case 'x':
370
/* Check that a particular question has a specific challenge. */
371
response.challenge = optarg;
372
break;
373
case 'c':
374
/* Note that we want a dump of the PKINIT challenge structure. */
375
response.print_pkinit_challenge = TRUE;
376
break;
377
case 'r':
378
/* Set a verbatim response for a verbatim challenge. */
379
response.response = optarg;
380
break;
381
case 'p':
382
/* Set a PKINIT answer for a specific PKINIT identity. */
383
response.pkinit_answer = optarg;
384
break;
385
case 'o':
386
/* Set an OTP answer for a specific OTP tokeninfo. */
387
response.otp_answer = optarg;
388
break;
389
}
390
}
391
392
if (argc > optind) {
393
err = krb5_parse_name(context, argv[optind], &principal);
394
if (err != 0) {
395
fprintf(stderr, "error parsing name \"%s\": %s", argv[optind],
396
error_message(err));
397
return err;
398
}
399
} else {
400
fprintf(stderr, "error: no principal name provided\n");
401
return -1;
402
}
403
404
err = krb5_get_init_creds_opt_set_responder(context, opts,
405
responder, &response);
406
if (err != 0) {
407
fprintf(stderr, "error setting responder: %s\n", error_message(err));
408
return err;
409
}
410
memset(&creds, 0, sizeof(creds));
411
err = krb5_get_init_creds_password(context, &creds, principal, NULL,
412
NULL, NULL, 0, NULL, opts);
413
if (err == 0)
414
krb5_free_cred_contents(context, &creds);
415
krb5_free_principal(context, principal);
416
krb5_get_init_creds_opt_free(context, opts);
417
krb5_cc_close(context, ccache);
418
419
if (!response.called) {
420
fprintf(stderr, "error: responder callback wasn't called\n");
421
err = 1;
422
} else if (err) {
423
errmsg = krb5_get_error_message(context, err);
424
fprintf(stderr, "error: krb5_get_init_creds_password failed: %s\n",
425
errmsg);
426
krb5_free_error_message(context, errmsg);
427
err = 2;
428
}
429
krb5_free_context(context);
430
return err;
431
}
432
433