Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / tests / responder.c
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:", 5) == 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 }