1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* tests/responder.c - Test harness for responder callbacks and the like. */
4 * Copyright 2013 Red Hat, Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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
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.
31 * A helper for testing PKINIT and responder callbacks.
33 * This test helper takes multiple options and one argument.
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
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
46 * If the responder callback isn't called, that's treated as an error.
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.
55 * Any missing data or mismatches are treated as errors.
58 #include <k5-platform.h>
60 #include <sys/types.h>
64 struct responder_data {
66 krb5_boolean print_pkinit_challenge;
67 const char *challenge;
69 const char *pkinit_answer;
70 const char *otp_answer;
73 static krb5_error_code
74 responder(krb5_context ctx, void *rawdata, krb5_responder_context rctx)
77 char *key, *value, *pin, *encoded1, *encoded2;
78 const char *challenge;
79 k5_json_value decoded1, decoded2;
82 krb5_int32 token_flags;
83 struct responder_data *data = rawdata;
84 krb5_responder_pkinit_challenge *chl;
85 krb5_responder_otp_challenge *ochl;
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);
96 value = key + strcspn(key, "=");
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);
110 fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", challenge, value);
114 /* Assume we're after a JSON compare - decode the actual value. */
115 err = k5_json_decode(challenge, &decoded2);
117 fprintf(stderr, "error decoding \"%s\"\n", challenge);
120 /* Re-encode the expected challenge and the actual challenge... */
121 err = k5_json_encode(decoded1, &encoded1);
123 fprintf(stderr, "error encoding json data\n");
126 err = k5_json_encode(decoded2, &encoded2);
128 fprintf(stderr, "error encoding json data\n");
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);
137 fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", encoded1,
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);
153 value = key + strcspn(key, "=");
156 /* ... and pass it in. */
157 err = krb5_responder_set_answer(ctx, rctx, key, value);
159 fprintf(stderr, "error setting response\n");
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);
169 fprintf(stderr, "error getting pkinit challenge\n");
173 for (n = 0; chl->identities[n] != NULL; n++)
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);
181 printf("identity %u/%u: %s\n", i + 1, n,
182 chl->identities[i]->identity);
186 krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
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);
194 fprintf(stderr, "error getting pkinit challenge\n");
198 * In case order matters, if the identity starts with "FILE:", exercise
199 * the set_answer function, with the real answer second.
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");
207 /* Provide the real answer. */
208 key = strdup(data->pkinit_answer);
211 value = strrchr(key, '=');
216 err = krb5_responder_pkinit_set_answer(ctx, rctx, key, value);
218 fprintf(stderr, "error setting response\n");
223 * In case order matters, if the identity starts with "PKCS12:",
224 * exercise the set_answer function, with the real answer first.
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");
232 krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
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.
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);
245 fprintf(stderr, "pkinit raw challenge set, "
246 "but structure is NULL\n");
249 if (k5_json_object_create(&ids) != 0) {
250 fprintf(stderr, "error creating json objects\n");
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");
259 if (k5_json_object_set(ids, chl->identities[i]->identity,
261 fprintf(stderr, "error adding json number to object\n");
264 k5_json_release(val);
266 /* Encode the structure... */
267 err = k5_json_encode(ids, &encoded1);
269 fprintf(stderr, "error encoding json data\n");
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);
278 krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
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, '=');
287 /* Make a copy of the answer that we can chop up. */
288 key = strdup(data->otp_answer);
291 /* Isolate the ti value. */
292 value = strchr(key, '=');
295 /* Break the value and PIN apart. */
296 pin = strchr(value, ':');
299 err = krb5_responder_otp_set_answer(ctx, rctx, n, value, pin);
301 fprintf(stderr, "error setting response\n");
306 krb5_responder_otp_challenge_free(ctx, rctx, ochl);
314 main(int argc, char **argv)
316 krb5_context context;
318 krb5_get_init_creds_opt *opts;
319 krb5_principal principal;
324 struct responder_data response;
327 err = krb5_init_context(&context);
329 fprintf(stderr, "error starting Kerberos: %s\n", error_message(err));
332 err = krb5_get_init_creds_opt_alloc(context, &opts);
334 fprintf(stderr, "error initializing options: %s\n",
338 err = krb5_cc_default(context, &ccache);
340 fprintf(stderr, "error resolving default ccache: %s\n",
344 err = krb5_get_init_creds_opt_set_out_ccache(context, opts, ccache);
346 fprintf(stderr, "error setting output ccache: %s\n",
351 memset(&response, 0, sizeof(response));
352 while ((c = getopt(argc, argv, "X:x:cr:p:")) != -1) {
355 /* Like kinit, set a generic preauth option. */
356 opt = strdup(optarg);
357 val = opt + strcspn(opt, "=");
361 err = krb5_get_init_creds_opt_set_pa(context, opts, opt, val);
363 fprintf(stderr, "error setting option \"%s\": %s\n", opt,
370 /* Check that a particular question has a specific challenge. */
371 response.challenge = optarg;
374 /* Note that we want a dump of the PKINIT challenge structure. */
375 response.print_pkinit_challenge = TRUE;
378 /* Set a verbatim response for a verbatim challenge. */
379 response.response = optarg;
382 /* Set a PKINIT answer for a specific PKINIT identity. */
383 response.pkinit_answer = optarg;
386 /* Set an OTP answer for a specific OTP tokeninfo. */
387 response.otp_answer = optarg;
393 err = krb5_parse_name(context, argv[optind], &principal);
395 fprintf(stderr, "error parsing name \"%s\": %s", argv[optind],
400 fprintf(stderr, "error: no principal name provided\n");
404 err = krb5_get_init_creds_opt_set_responder(context, opts,
405 responder, &response);
407 fprintf(stderr, "error setting responder: %s\n", error_message(err));
410 memset(&creds, 0, sizeof(creds));
411 err = krb5_get_init_creds_password(context, &creds, principal, NULL,
412 NULL, NULL, 0, NULL, opts);
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);
419 if (!response.called) {
420 fprintf(stderr, "error: responder callback wasn't called\n");
423 errmsg = krb5_get_error_message(context, err);
424 fprintf(stderr, "error: krb5_get_init_creds_password failed: %s\n",
426 krb5_free_error_message(context, errmsg);
429 krb5_free_context(context);