1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/preauth_otp.c - OTP clpreauth module */
4 * Copyright 2011 NORDUnet A/S. All rights reserved.
5 * Copyright 2011 Red Hat, Inc. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
22 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #include "int-proto.h"
36 #include <krb5/clpreauth_plugin.h>
39 static krb5_preauthtype otp_client_supported_pa_types[] =
40 { KRB5_PADATA_OTP_CHALLENGE, 0 };
42 /* Frees a tokeninfo. */
44 free_tokeninfo(krb5_responder_otp_tokeninfo *ti)
56 /* Converts a property of a json object into a char*. */
57 static krb5_error_code
58 codec_value_to_string(k5_json_object obj, const char *key, char **string)
63 val = k5_json_object_get(obj, key);
67 if (k5_json_get_tid(val) != K5_JSON_TID_STRING)
70 str = strdup(k5_json_string_utf8(val));
78 /* Converts a property of a json object into a krb5_data struct. */
79 static krb5_error_code
80 codec_value_to_data(k5_json_object obj, const char *key, krb5_data *data)
82 krb5_error_code retval;
85 retval = codec_value_to_string(obj, key, &tmp);
89 *data = string2data(tmp);
93 /* Converts a krb5_data struct into a property of a JSON object. */
94 static krb5_error_code
95 codec_data_to_value(krb5_data *data, k5_json_object obj, const char *key)
97 krb5_error_code retval;
100 if (data->data == NULL)
103 retval = k5_json_string_create_len(data->data, data->length, &str);
107 retval = k5_json_object_set(obj, key, str);
108 k5_json_release(str);
112 /* Converts a property of a json object into a krb5_int32. */
113 static krb5_error_code
114 codec_value_to_int32(k5_json_object obj, const char *key, krb5_int32 *int32)
118 val = k5_json_object_get(obj, key);
122 if (k5_json_get_tid(val) != K5_JSON_TID_NUMBER)
125 *int32 = k5_json_number_value(val);
129 /* Converts a krb5_int32 into a property of a JSON object. */
130 static krb5_error_code
131 codec_int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key)
133 krb5_error_code retval;
139 retval = k5_json_number_create(int32, &num);
143 retval = k5_json_object_set(obj, key, num);
144 k5_json_release(num);
148 /* Converts a krb5_otp_tokeninfo into a JSON object. */
149 static krb5_error_code
150 codec_encode_tokeninfo(krb5_otp_tokeninfo *ti, k5_json_object *out)
152 krb5_error_code retval;
156 retval = k5_json_object_create(&obj);
160 flags = KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN;
161 if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN) {
162 flags |= KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN;
163 if (ti->flags & KRB5_OTP_FLAG_SEPARATE_PIN)
164 flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP;
166 if (ti->flags & KRB5_OTP_FLAG_NEXTOTP)
167 flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP;
169 retval = codec_int32_to_value(flags, obj, "flags");
173 retval = codec_data_to_value(&ti->vendor, obj, "vendor");
177 retval = codec_data_to_value(&ti->challenge, obj, "challenge");
181 retval = codec_int32_to_value(ti->length, obj, "length");
185 if (ti->format != KRB5_OTP_FORMAT_BASE64 &&
186 ti->format != KRB5_OTP_FORMAT_BINARY) {
187 retval = codec_int32_to_value(ti->format, obj, "format");
192 retval = codec_data_to_value(&ti->token_id, obj, "tokenID");
196 retval = codec_data_to_value(&ti->alg_id, obj, "algID");
204 k5_json_release(obj);
208 /* Converts a krb5_pa_otp_challenge into a JSON object. */
209 static krb5_error_code
210 codec_encode_challenge(krb5_context ctx, krb5_pa_otp_challenge *chl,
213 k5_json_object obj = NULL, tmp = NULL;
214 k5_json_string str = NULL;
215 k5_json_array arr = NULL;
216 krb5_error_code retval;
219 retval = k5_json_object_create(&obj);
223 if (chl->service.data) {
224 retval = k5_json_string_create_len(chl->service.data,
225 chl->service.length, &str);
228 retval = k5_json_object_set(obj, "service", str);
229 k5_json_release(str);
234 retval = k5_json_array_create(&arr);
238 for (i = 0; chl->tokeninfo[i] != NULL ; i++) {
239 retval = codec_encode_tokeninfo(chl->tokeninfo[i], &tmp);
243 retval = k5_json_array_add(arr, tmp);
244 k5_json_release(tmp);
249 retval = k5_json_object_set(obj, "tokenInfo", arr);
253 retval = k5_json_encode(obj, json);
258 k5_json_release(arr);
259 k5_json_release(obj);
263 /* Converts a JSON object into a krb5_responder_otp_tokeninfo. */
264 static krb5_responder_otp_tokeninfo *
265 codec_decode_tokeninfo(k5_json_object obj)
267 krb5_responder_otp_tokeninfo *ti = NULL;
268 krb5_error_code retval;
270 ti = calloc(1, sizeof(krb5_responder_otp_tokeninfo));
274 retval = codec_value_to_int32(obj, "flags", &ti->flags);
278 retval = codec_value_to_string(obj, "vendor", &ti->vendor);
279 if (retval != 0 && retval != ENOENT)
282 retval = codec_value_to_string(obj, "challenge", &ti->challenge);
283 if (retval != 0 && retval != ENOENT)
286 retval = codec_value_to_int32(obj, "length", &ti->length);
287 if (retval == ENOENT)
289 else if (retval != 0)
292 retval = codec_value_to_int32(obj, "format", &ti->format);
293 if (retval == ENOENT)
295 else if (retval != 0)
298 retval = codec_value_to_string(obj, "tokenID", &ti->token_id);
299 if (retval != 0 && retval != ENOENT)
302 retval = codec_value_to_string(obj, "algID", &ti->alg_id);
303 if (retval != 0 && retval != ENOENT)
313 /* Converts a JSON object into a krb5_responder_otp_challenge. */
314 static krb5_responder_otp_challenge *
315 codec_decode_challenge(krb5_context ctx, const char *json)
317 krb5_responder_otp_challenge *chl = NULL;
318 k5_json_value obj = NULL, arr = NULL, tmp = NULL;
319 krb5_error_code retval;
322 retval = k5_json_decode(json, &obj);
326 if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT)
329 arr = k5_json_object_get(obj, "tokenInfo");
333 if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY)
336 chl = calloc(1, sizeof(krb5_responder_otp_challenge));
340 chl->tokeninfo = calloc(k5_json_array_length(arr) + 1,
341 sizeof(krb5_responder_otp_tokeninfo*));
342 if (chl->tokeninfo == NULL)
345 retval = codec_value_to_string(obj, "service", &chl->service);
346 if (retval != 0 && retval != ENOENT)
349 for (i = 0; i < k5_json_array_length(arr); i++) {
350 tmp = k5_json_array_get(arr, i);
351 if (k5_json_get_tid(tmp) != K5_JSON_TID_OBJECT)
354 chl->tokeninfo[i] = codec_decode_tokeninfo(tmp);
355 if (chl->tokeninfo[i] == NULL)
359 k5_json_release(obj);
364 for (i = 0; chl->tokeninfo != NULL && chl->tokeninfo[i] != NULL; i++)
365 free_tokeninfo(chl->tokeninfo[i]);
366 free(chl->tokeninfo);
369 k5_json_release(obj);
373 /* Decode the responder answer into a tokeninfo, a value and a pin. */
374 static krb5_error_code
375 codec_decode_answer(krb5_context context, const char *answer,
376 krb5_otp_tokeninfo **tis, krb5_otp_tokeninfo **ti,
377 krb5_data *value, krb5_data *pin)
379 krb5_error_code retval;
380 k5_json_value val = NULL;
387 retval = k5_json_decode(answer, &val);
391 if (k5_json_get_tid(val) != K5_JSON_TID_OBJECT)
394 retval = codec_value_to_int32(val, "tokeninfo", &indx);
398 for (i = 0; tis[i] != NULL; i++) {
400 retval = codec_value_to_data(val, "value", &tmp);
401 if (retval != 0 && retval != ENOENT)
404 retval = codec_value_to_data(val, "pin", pin);
405 if (retval != 0 && retval != ENOENT) {
406 krb5_free_data_contents(context, &tmp);
419 k5_json_release(val);
423 /* Takes the nonce from the challenge and encrypts it into the request. */
424 static krb5_error_code
425 encrypt_nonce(krb5_context ctx, krb5_keyblock *key,
426 const krb5_pa_otp_challenge *chl, krb5_pa_otp_req *req)
428 krb5_error_code retval;
429 krb5_enc_data encdata;
432 /* Encode the nonce. */
433 retval = encode_krb5_pa_otp_enc_req(&chl->nonce, &er);
437 /* Do the encryption. */
438 retval = krb5_encrypt_helper(ctx, key, KRB5_KEYUSAGE_PA_OTP_REQUEST,
440 krb5_free_data(ctx, er);
444 req->enc_data = encdata;
448 /* Checks to see if the user-supplied otp value matches the length and format
449 * of the supplied tokeninfo. */
451 otpvalue_matches_tokeninfo(const char *otpvalue, krb5_otp_tokeninfo *ti)
453 int (*table[])(int c) = { isdigit, isxdigit, isalnum };
455 if (otpvalue == NULL || ti == NULL)
458 if (ti->length >= 0 && strlen(otpvalue) != (size_t)ti->length)
461 if (ti->format >= 0 && ti->format < 3) {
463 if (!(*table[ti->format])((unsigned char)*otpvalue++))
471 /* Performs a prompt and saves the response in the out parameter. */
472 static krb5_error_code
473 doprompt(krb5_context context, krb5_prompter_fct prompter, void *prompter_data,
474 const char *banner, const char *prompttxt, char *out, size_t len)
477 krb5_data prompt_reply;
478 krb5_error_code retval;
479 krb5_prompt_type prompt_type = KRB5_PROMPT_TYPE_PREAUTH;
481 if (prompttxt == NULL || out == NULL)
486 prompt_reply = make_data(out, len);
487 prompt.reply = &prompt_reply;
488 prompt.prompt = (char *)prompttxt;
491 /* PROMPTER_INVOCATION */
492 k5_set_prompt_types(context, &prompt_type);
493 retval = (*prompter)(context, prompter_data, NULL, banner, 1, &prompt);
494 k5_set_prompt_types(context, NULL);
501 /* Forces the user to choose a single tokeninfo via prompting. */
502 static krb5_error_code
503 prompt_for_tokeninfo(krb5_context context, krb5_prompter_fct prompter,
504 void *prompter_data, krb5_otp_tokeninfo **tis,
505 krb5_otp_tokeninfo **out_ti)
507 char *banner = NULL, *tmp, response[1024];
508 krb5_otp_tokeninfo *ti = NULL;
509 krb5_error_code retval = 0;
512 for (i = 0; tis[i] != NULL; i++) {
513 if (asprintf(&tmp, "%s\t%d. %s %.*s\n",
515 _("Please choose from the following:\n"),
516 i + 1, _("Vendor:"), tis[i]->vendor.length,
517 tis[i]->vendor.data) < 0) {
527 retval = doprompt(context, prompter, prompter_data,
528 banner, _("Enter #"), response, sizeof(response));
535 j = strtol(response, NULL, 0);
544 } while (ti == NULL);
551 /* Builds a challenge string from the given tokeninfo. */
552 static krb5_error_code
553 make_challenge(const krb5_otp_tokeninfo *ti, char **challenge)
555 if (challenge == NULL)
560 if (ti == NULL || ti->challenge.data == NULL)
563 if (asprintf(challenge, "%s %.*s\n",
565 ti->challenge.length,
566 ti->challenge.data) < 0)
572 /* Determines if a pin is required. If it is, it will be prompted for. */
573 static inline krb5_error_code
574 collect_pin(krb5_context context, krb5_prompter_fct prompter,
575 void *prompter_data, const krb5_otp_tokeninfo *ti,
578 krb5_error_code retval;
583 /* If no PIN will be collected, don't prompt. */
584 collect = ti->flags & (KRB5_OTP_FLAG_COLLECT_PIN |
585 KRB5_OTP_FLAG_SEPARATE_PIN);
587 *out_pin = empty_data();
591 /* Collect the PIN. */
592 retval = doprompt(context, prompter, prompter_data, NULL,
593 _("OTP Token PIN"), otppin, sizeof(otppin));
598 pin = make_data(strdup(otppin), strlen(otppin));
599 if (pin.data == NULL)
606 /* Builds a request using the specified tokeninfo, value and pin. */
607 static krb5_error_code
608 make_request(krb5_context ctx, krb5_otp_tokeninfo *ti, const krb5_data *value,
609 const krb5_data *pin, krb5_pa_otp_req **out_req)
611 krb5_pa_otp_req *req = NULL;
612 krb5_error_code retval = 0;
617 if (ti->format == KRB5_OTP_FORMAT_BASE64)
620 req = calloc(1, sizeof(krb5_pa_otp_req));
624 req->flags = ti->flags & KRB5_OTP_FLAG_NEXTOTP;
626 retval = krb5int_copy_data_contents(ctx, &ti->vendor, &req->vendor);
630 req->format = ti->format;
632 retval = krb5int_copy_data_contents(ctx, &ti->token_id, &req->token_id);
636 retval = krb5int_copy_data_contents(ctx, &ti->alg_id, &req->alg_id);
640 retval = krb5int_copy_data_contents(ctx, value, &req->otp_value);
644 if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN) {
645 if (ti->flags & KRB5_OTP_FLAG_SEPARATE_PIN) {
646 if (pin == NULL || pin->data == NULL) {
647 retval = EINVAL; /* No pin found! */
651 retval = krb5int_copy_data_contents(ctx, pin, &req->pin);
654 } else if (pin != NULL && pin->data != NULL) {
655 krb5_free_data_contents(ctx, &req->otp_value);
656 retval = asprintf(&req->otp_value.data, "%.*s%.*s",
657 pin->length, pin->data,
658 value->length, value->data);
661 req->otp_value = empty_data();
664 req->otp_value.length = req->pin.length + req->otp_value.length;
665 } /* Otherwise, the responder has already combined them. */
672 k5_free_pa_otp_req(ctx, req);
677 * Filters a set of tokeninfos given an otp value. If the set is reduced to
678 * a single tokeninfo, it will be set in out_ti. Otherwise, a new shallow copy
679 * will be allocated in out_filtered.
681 static inline krb5_error_code
682 filter_tokeninfos(krb5_context context, const char *otpvalue,
683 krb5_otp_tokeninfo **tis,
684 krb5_otp_tokeninfo ***out_filtered,
685 krb5_otp_tokeninfo **out_ti)
687 krb5_otp_tokeninfo **filtered;
690 while (tis[i] != NULL)
693 filtered = calloc(i + 1, sizeof(const krb5_otp_tokeninfo *));
694 if (filtered == NULL)
697 /* Make a list of tokeninfos that match the value. */
698 for (i = 0, j = 0; tis[i] != NULL; i++) {
699 if (otpvalue_matches_tokeninfo(otpvalue, tis[i]))
700 filtered[j++] = tis[i];
703 /* It is an error if we have no matching tokeninfos. */
704 if (filtered[0] == NULL) {
706 k5_setmsg(context, KRB5_PREAUTH_FAILED,
707 _("OTP value doesn't match any token formats"));
708 return KRB5_PREAUTH_FAILED; /* We have no supported tokeninfos. */
711 /* Otherwise, if we have just one tokeninfo, choose it. */
712 if (filtered[1] == NULL) {
713 *out_ti = filtered[0];
714 *out_filtered = NULL;
719 /* Otherwise, we'll return the remaining list. */
721 *out_filtered = filtered;
725 /* Outputs the selected tokeninfo and possibly a value and pin.
726 * Prompting may occur. */
727 static krb5_error_code
728 prompt_for_token(krb5_context context, krb5_prompter_fct prompter,
729 void *prompter_data, krb5_otp_tokeninfo **tis,
730 krb5_otp_tokeninfo **out_ti, krb5_data *out_value,
733 krb5_otp_tokeninfo **filtered = NULL;
734 krb5_otp_tokeninfo *ti = NULL;
735 krb5_error_code retval;
736 int i, challengers = 0;
737 char *challenge = NULL;
739 krb5_data value, pin;
741 memset(otpvalue, 0, sizeof(otpvalue));
743 if (tis == NULL || tis[0] == NULL || out_ti == NULL)
746 /* Count how many challenges we have. */
747 for (i = 0; tis[i] != NULL; i++) {
748 if (tis[i]->challenge.data != NULL)
752 /* If we have only one tokeninfo as input, choose it. */
756 /* Setup our challenge, if present. */
757 if (challengers > 0) {
758 /* If we have multiple tokeninfos still, choose now. */
760 retval = prompt_for_tokeninfo(context, prompter, prompter_data,
766 /* Create the challenge prompt. */
767 retval = make_challenge(ti, &challenge);
772 /* Prompt for token value. */
773 retval = doprompt(context, prompter, prompter_data, challenge,
774 _("Enter OTP Token Value"), otpvalue, sizeof(otpvalue));
780 /* Filter out tokeninfos that don't match our token value. */
781 retval = filter_tokeninfos(context, otpvalue, tis, &filtered, &ti);
785 /* If we still don't have a single tokeninfo, choose now. */
786 if (filtered != NULL) {
787 retval = prompt_for_tokeninfo(context, prompter, prompter_data,
798 value = make_data(strdup(otpvalue), strlen(otpvalue));
799 if (value.data == NULL)
802 /* Collect the PIN, if necessary. */
803 retval = collect_pin(context, prompter, prompter_data, ti, &pin);
805 krb5_free_data_contents(context, &value);
815 /* Encode the OTP request into a krb5_pa_data buffer. */
816 static krb5_error_code
817 set_pa_data(const krb5_pa_otp_req *req, krb5_pa_data ***pa_data_out)
819 krb5_pa_data **out = NULL;
822 /* Allocate the preauth data array and one item. */
823 out = calloc(2, sizeof(krb5_pa_data *));
826 out[0] = calloc(1, sizeof(krb5_pa_data));
831 /* Encode our request into the preauth data item. */
832 memset(out[0], 0, sizeof(krb5_pa_data));
833 out[0]->pa_type = KRB5_PADATA_OTP_REQUEST;
834 if (encode_krb5_pa_otp_req(req, &tmp) != 0)
836 out[0]->contents = (krb5_octet *)tmp->data;
837 out[0]->length = tmp->length;
851 /* Tests krb5_data to see if it is printable. */
853 is_printable_string(const krb5_data *data)
860 for (i = 0; i < data->length; i++) {
861 if (!isprint((unsigned char)data->data[i]))
868 /* Returns TRUE when the given tokeninfo contains the subset of features we
871 is_tokeninfo_supported(krb5_otp_tokeninfo *ti)
873 krb5_flags supported_flags = KRB5_OTP_FLAG_COLLECT_PIN |
874 KRB5_OTP_FLAG_NO_COLLECT_PIN |
875 KRB5_OTP_FLAG_SEPARATE_PIN;
877 /* Flags we don't support... */
878 if (ti->flags & ~supported_flags)
881 /* We don't currently support hashing. */
882 if (ti->supported_hash_alg != NULL || ti->iteration_count >= 0)
885 /* Remove tokeninfos with invalid vendor strings. */
886 if (!is_printable_string(&ti->vendor))
889 /* Remove tokeninfos with non-printable challenges. */
890 if (!is_printable_string(&ti->challenge))
893 /* We don't currently support base64. */
894 if (ti->format == KRB5_OTP_FORMAT_BASE64)
900 /* Removes unsupported tokeninfos. Returns an error if no tokeninfos remain. */
901 static krb5_error_code
902 filter_supported_tokeninfos(krb5_context context, krb5_otp_tokeninfo **tis)
906 /* Filter out any tokeninfos we don't support. */
907 for (i = 0, j = 0; tis[i] != NULL; i++) {
908 if (!is_tokeninfo_supported(tis[i]))
909 k5_free_otp_tokeninfo(context, tis[i]);
914 /* Terminate the array. */
920 k5_setmsg(context, KRB5_PREAUTH_FAILED, _("No supported tokens"));
921 return KRB5_PREAUTH_FAILED; /* We have no supported tokeninfos. */
925 * Try to find tokeninfos which match configuration data recorded in the input
926 * ccache, and if exactly one is found, drop the rest.
928 static krb5_error_code
929 filter_config_tokeninfos(krb5_context context,
930 krb5_clpreauth_callbacks cb,
931 krb5_clpreauth_rock rock,
932 krb5_otp_tokeninfo **tis)
934 krb5_otp_tokeninfo *match = NULL;
936 const char *vendor, *alg_id, *token_id;
938 /* Pull up what we know about the token we want to use. */
939 vendor = cb->get_cc_config(context, rock, "vendor");
940 alg_id = cb->get_cc_config(context, rock, "algID");
941 token_id = cb->get_cc_config(context, rock, "tokenID");
943 /* Look for a single matching entry. */
944 for (i = 0; tis[i] != NULL; i++) {
945 if (vendor != NULL && tis[i]->vendor.length > 0 &&
946 !data_eq_string(tis[i]->vendor, vendor))
948 if (alg_id != NULL && tis[i]->alg_id.length > 0 &&
949 !data_eq_string(tis[i]->alg_id, alg_id))
951 if (token_id != NULL && tis[i]->token_id.length > 0 &&
952 !data_eq_string(tis[i]->token_id, token_id))
954 /* Oh, we already had a matching entry. More than one -> no change. */
960 /* No matching entry -> no change. */
964 /* Prune out everything except the best match. */
965 for (i = 0, j = 0; tis[i] != NULL; i++) {
967 k5_free_otp_tokeninfo(context, tis[i]);
977 otp_client_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
978 krb5_clpreauth_modreq *modreq_out)
980 *modreq_out = calloc(1, sizeof(krb5_pa_otp_challenge *));
983 static krb5_error_code
984 otp_client_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
985 krb5_clpreauth_modreq modreq,
986 krb5_get_init_creds_opt *opt,
987 krb5_clpreauth_callbacks cb,
988 krb5_clpreauth_rock rock, krb5_kdc_req *request,
989 krb5_data *encoded_request_body,
990 krb5_data *encoded_previous_request,
991 krb5_pa_data *pa_data)
993 krb5_pa_otp_challenge *chl;
994 krb5_error_code retval;
1001 /* Decode the challenge. */
1002 tmp = make_data(pa_data->contents, pa_data->length);
1003 retval = decode_krb5_pa_otp_challenge(&tmp,
1004 (krb5_pa_otp_challenge **)modreq);
1007 chl = *(krb5_pa_otp_challenge **)modreq;
1009 /* Remove unsupported tokeninfos. */
1010 retval = filter_supported_tokeninfos(context, chl->tokeninfo);
1014 /* Remove tokeninfos that don't match the recorded description, if that
1015 * results in there being only one that does. */
1016 retval = filter_config_tokeninfos(context, cb, rock, chl->tokeninfo);
1020 /* Make the JSON representation. */
1021 retval = codec_encode_challenge(context, chl, &json);
1025 /* Ask the question. */
1026 retval = cb->ask_responder_question(context, rock,
1027 KRB5_RESPONDER_QUESTION_OTP,
1034 * Save the vendor, algID, and tokenID values for the selected token to the
1035 * out_ccache, so that later we can try to use them to select the right one
1036 * without having ot ask the user.
1039 save_config_tokeninfo(krb5_context context,
1040 krb5_clpreauth_callbacks cb,
1041 krb5_clpreauth_rock rock,
1042 krb5_otp_tokeninfo *ti)
1045 if (ti->vendor.length > 0 &&
1046 asprintf(&tmp, "%.*s", ti->vendor.length, ti->vendor.data) >= 0) {
1047 cb->set_cc_config(context, rock, "vendor", tmp);
1050 if (ti->alg_id.length > 0 &&
1051 asprintf(&tmp, "%.*s", ti->alg_id.length, ti->alg_id.data) >= 0) {
1052 cb->set_cc_config(context, rock, "algID", tmp);
1055 if (ti->token_id.length > 0 &&
1056 asprintf(&tmp, "%.*s", ti->token_id.length, ti->token_id.data) >= 0) {
1057 cb->set_cc_config(context, rock, "tokenID", tmp);
1062 static krb5_error_code
1063 otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
1064 krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
1065 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
1066 krb5_kdc_req *request, krb5_data *encoded_request_body,
1067 krb5_data *encoded_previous_request, krb5_pa_data *pa_data,
1068 krb5_prompter_fct prompter, void *prompter_data,
1069 krb5_pa_data ***pa_data_out)
1071 krb5_pa_otp_challenge *chl = NULL;
1072 krb5_otp_tokeninfo *ti = NULL;
1073 krb5_keyblock *as_key = NULL;
1074 krb5_pa_otp_req *req = NULL;
1075 krb5_error_code retval = 0;
1076 krb5_data value, pin;
1081 chl = *(krb5_pa_otp_challenge **)modreq;
1083 *pa_data_out = NULL;
1085 /* Get FAST armor key. */
1086 as_key = cb->fast_armor(context, rock);
1090 /* Attempt to get token selection from the responder. */
1092 value = empty_data();
1093 answer = cb->get_responder_answer(context, rock,
1094 KRB5_RESPONDER_QUESTION_OTP);
1095 retval = codec_decode_answer(context, answer, chl->tokeninfo, &ti, &value,
1098 /* If the responder doesn't have a token selection,
1099 * we need to select the token via prompting. */
1100 retval = prompt_for_token(context, prompter, prompter_data,
1101 chl->tokeninfo, &ti, &value, &pin);
1106 /* Make the request. */
1107 retval = make_request(context, ti, &value, &pin, &req);
1111 /* Save information about the token which was used. */
1112 save_config_tokeninfo(context, cb, rock, ti);
1114 /* Encrypt the challenge's nonce and set it in the request. */
1115 retval = encrypt_nonce(context, as_key, chl, req);
1119 /* Use FAST armor key as response key. */
1120 retval = cb->set_as_key(context, rock, as_key);
1124 /* Encode the request into the pa_data output. */
1125 retval = set_pa_data(req, pa_data_out);
1127 krb5_free_data_contents(context, &value);
1128 krb5_free_data_contents(context, &pin);
1129 k5_free_pa_otp_req(context, req);
1134 otp_client_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
1135 krb5_clpreauth_modreq modreq)
1140 k5_free_pa_otp_challenge(context, *(krb5_pa_otp_challenge **)modreq);
1145 clpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
1146 krb5_plugin_vtable vtable)
1148 krb5_clpreauth_vtable vt;
1151 return KRB5_PLUGIN_VER_NOTSUPP;
1153 vt = (krb5_clpreauth_vtable)vtable;
1155 vt->pa_type_list = otp_client_supported_pa_types;
1156 vt->request_init = otp_client_request_init;
1157 vt->prep_questions = otp_client_prep_questions;
1158 vt->process = otp_client_process;
1159 vt->request_fini = otp_client_request_fini;
1160 vt->gic_opts = NULL;
1165 krb5_error_code KRB5_CALLCONV
1166 krb5_responder_otp_get_challenge(krb5_context ctx,
1167 krb5_responder_context rctx,
1168 krb5_responder_otp_challenge **chl)
1171 krb5_responder_otp_challenge *challenge;
1173 answer = krb5_responder_get_challenge(ctx, rctx,
1174 KRB5_RESPONDER_QUESTION_OTP);
1175 if (answer == NULL) {
1180 challenge = codec_decode_challenge(ctx, answer);
1181 if (challenge == NULL)
1188 krb5_error_code KRB5_CALLCONV
1189 krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx,
1190 size_t ti, const char *value, const char *pin)
1192 krb5_error_code retval;
1193 k5_json_object obj = NULL;
1198 retval = k5_json_object_create(&obj);
1202 retval = k5_json_number_create(ti, &num);
1206 retval = k5_json_object_set(obj, "tokeninfo", num);
1207 k5_json_release(num);
1211 if (value != NULL) {
1212 retval = k5_json_string_create(value, &str);
1216 retval = k5_json_object_set(obj, "value", str);
1217 k5_json_release(str);
1223 retval = k5_json_string_create(pin, &str);
1227 retval = k5_json_object_set(obj, "pin", str);
1228 k5_json_release(str);
1233 retval = k5_json_encode(obj, &tmp);
1236 k5_json_release(obj);
1238 retval = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_OTP,
1244 k5_json_release(obj);
1249 krb5_responder_otp_challenge_free(krb5_context ctx,
1250 krb5_responder_context rctx,
1251 krb5_responder_otp_challenge *chl)
1258 for (i = 0; chl->tokeninfo[i]; i++)
1259 free_tokeninfo(chl->tokeninfo[i]);
1261 free(chl->tokeninfo);