krb5 commit: Add responder support to preauth_otp
Greg Hudson
ghudson at MIT.EDU
Mon Oct 15 11:10:18 EDT 2012
https://github.com/krb5/krb5/commit/815da88a734c8a721e94fe0979ee5789b4576d10
commit 815da88a734c8a721e94fe0979ee5789b4576d10
Author: Nathaniel McCallum <npmccallum at redhat.com>
Date: Mon Oct 15 10:49:21 2012 -0400
Add responder support to preauth_otp
doc/rst_source/krb_appldev/refs/api/index.rst | 3 +
src/include/krb5/krb5.hin | 126 ++++++
src/lib/krb5/krb/preauth_otp.c | 590 ++++++++++++++++++++++++-
src/lib/krb5/libkrb5.exports | 3 +
src/lib/krb5_32.def | 3 +
5 files changed, 707 insertions(+), 18 deletions(-)
diff --git a/doc/rst_source/krb_appldev/refs/api/index.rst b/doc/rst_source/krb_appldev/refs/api/index.rst
index dc9c35a..26b7f86 100644
--- a/doc/rst_source/krb_appldev/refs/api/index.rst
+++ b/doc/rst_source/krb_appldev/refs/api/index.rst
@@ -82,6 +82,9 @@ Frequently used public interfaces
krb5_responder_get_challenge.rst
krb5_responder_list_questions.rst
krb5_responder_set_answer.rst
+ krb5_responder_otp_get_challenge.rst
+ krb5_responder_otp_set_answer.rst
+ krb5_responder_otp_challenge_free.rst
krb5_set_default_realm.rst
krb5_set_password.rst
krb5_set_password_using_ccache.rst
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index db71f96..f338689 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -6367,6 +6367,66 @@ krb5_prompter_posix(krb5_context context, void *data, const char *name,
*/
#define KRB5_RESPONDER_QUESTION_PASSWORD "password"
+/**
+ * OTP responder question
+ *
+ * The OTP responder question is asked when the KDC indicates that an OTP
+ * value is required in order to complete the authentication. The JSON format
+ * of the challenge is:
+ * {
+ * "service": <string (optional)>,
+ * "tokenInfo": [
+ * {
+ * "flags": <number>,
+ * "vendor": <string (optional)>,
+ * "challenge": <string (optional)>,
+ * "length": <number (optional)>,
+ * "format": <number (optional)>,
+ * "tokenID": <string (optional)>,
+ * "algID": <string (optional)>,
+ * },
+ * ...
+ * ]
+ * }
+ *
+ * The answer to the question MUST be JSON formatted:
+ * {
+ * "tokeninfo": <number>,
+ * "value": <string (optional)>,
+ * "pin": <string (optional)>,
+ * }
+ *
+ * For more detail, please see RFC 6560.
+ *
+ * @version First introduced in 1.11
+ */
+#define KRB5_RESPONDER_QUESTION_OTP "otp"
+
+/**
+ * These format constants identify the format of the token value.
+ */
+#define KRB5_RESPONDER_OTP_FORMAT_DECIMAL 0
+#define KRB5_RESPONDER_OTP_FORMAT_HEXADECIMAL 1
+#define KRB5_RESPONDER_OTP_FORMAT_ALPHANUMERIC 2
+#define KRB5_RESPONDER_OTP_FORMAT_BINARY 3
+
+/**
+ * This flag indicates that the token value MUST be collected.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN (1 << 0)
+
+/**
+ * This flag indicates that the PIN value MUST be collected.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN (1 << 1)
+
+/**
+ * This flag indicates that the token is now in re-synchronization mode with
+ * the server. The user is expected to reply with the next code displayed on
+ * the token.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_NEXTOTP (1 << 2)
+
typedef struct krb5_responder_context_st *krb5_responder_context;
/**
@@ -6431,6 +6491,72 @@ typedef krb5_error_code
(*krb5_responder_fn)(krb5_context ctx, krb5_responder_context rctx,
void *data);
+typedef struct _krb5_responder_otp_tokeninfo {
+ krb5_flags flags;
+ krb5_int32 format; /* -1 when not specified. */
+ krb5_int32 length; /* -1 when not specified. */
+ char *vendor;
+ char *challenge;
+ char *token_id;
+ char *alg_id;
+} krb5_responder_otp_tokeninfo;
+
+typedef struct _krb5_responder_otp_challenge {
+ char *service;
+ krb5_responder_otp_tokeninfo **tokeninfo;
+} krb5_responder_otp_challenge;
+
+/**
+ * Decode the KRB5_RESPONDER_QUESTION_OTP to a C struct.
+ *
+ * A convenience function which parses the KRB5_RESPONDER_QUESTION_OTP
+ * question challenge data, making it available in native C. The main feature
+ * of this function is the ability to interact with OTP tokens without parsing
+ * the JSON.
+ *
+ * The returned value must be passed to krb5_responder_otp_challenge_free() to
+ * be freed.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [out] chl Challenge structure
+ *
+ * @version First introduced in 1.11
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_responder_otp_get_challenge(krb5_context ctx,
+ krb5_responder_context rctx,
+ krb5_responder_otp_challenge **chl);
+
+/**
+ * Answer the KRB5_RESPONDER_QUESTION_OTP question.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [in] ti The index of the tokeninfo selected
+ * @param [in] value The value to set, or NULL for none
+ * @param [in] pin The pin to set, or NULL for none
+ *
+ * @version First introduced in 1.11
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx,
+ size_t ti, const char *value, const char *pin);
+
+/**
+ * Free the value returned by krb5_responder_otp_get_challenge().
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [in] chl The challenge to free
+ *
+ * @version First introduced in 1.11
+ */
+void KRB5_CALLCONV
+krb5_responder_otp_challenge_free(krb5_context ctx,
+ krb5_responder_context rctx,
+ krb5_responder_otp_challenge *chl);
+
/** Store options for @c _krb5_get_init_creds */
typedef struct _krb5_get_init_creds_opt {
krb5_flags flags;
diff --git a/src/lib/krb5/krb/preauth_otp.c b/src/lib/krb5/krb/preauth_otp.c
index c72f8b6..9a550e8 100644
--- a/src/lib/krb5/krb/preauth_otp.c
+++ b/src/lib/krb5/krb/preauth_otp.c
@@ -29,6 +29,7 @@
*/
#include "k5-int.h"
+#include "k5-json.h"
#include "int-proto.h"
#include <krb5/preauth_plugin.h>
@@ -37,6 +38,392 @@
static krb5_preauthtype otp_client_supported_pa_types[] =
{ KRB5_PADATA_OTP_CHALLENGE, 0 };
+/* Frees a tokeninfo. */
+static void
+free_tokeninfo(krb5_responder_otp_tokeninfo *ti)
+{
+ if (ti == NULL)
+ return;
+
+ free(ti->alg_id);
+ free(ti->challenge);
+ free(ti->token_id);
+ free(ti->vendor);
+ free(ti);
+}
+
+/* Converts a property of a json object into a char*. */
+static krb5_error_code
+codec_value_to_string(k5_json_object obj, const char *key, char **string)
+{
+ k5_json_value val;
+ char *str;
+
+ val = k5_json_object_get(obj, key);
+ if (val == NULL)
+ return ENOENT;
+
+ if (k5_json_get_tid(val) != K5_JSON_TID_STRING)
+ return EINVAL;
+
+ str = strdup(k5_json_string_utf8(val));
+ if (str == NULL)
+ return ENOMEM;
+
+ *string = str;
+ return 0;
+}
+
+/* Converts a property of a json object into a krb5_data struct. */
+static krb5_error_code
+codec_value_to_data(k5_json_object obj, const char *key, krb5_data *data)
+{
+ krb5_error_code retval;
+ char *tmp;
+
+ retval = codec_value_to_string(obj, key, &tmp);
+ if (retval != 0)
+ return retval;
+
+ *data = string2data(tmp);
+ return 0;
+}
+
+/* Converts a krb5_data struct into a property of a JSON object. */
+static krb5_error_code
+codec_data_to_value(krb5_data *data, k5_json_object obj, const char *key)
+{
+ krb5_error_code retval;
+ k5_json_string str;
+
+ if (data->data == NULL)
+ return 0;
+
+ str = k5_json_string_create_len(data->data, data->length);
+ if (str == NULL)
+ return ENOMEM;
+
+ retval = k5_json_object_set(obj, key, str);
+ k5_json_release(str);
+ return retval == 0 ? 0 : ENOMEM;
+}
+
+/* Converts a property of a json object into a krb5_int32. */
+static krb5_error_code
+codec_value_to_int32(k5_json_object obj, const char *key, krb5_int32 *int32)
+{
+ k5_json_value val;
+
+ val = k5_json_object_get(obj, key);
+ if (val == NULL)
+ return ENOENT;
+
+ if (k5_json_get_tid(val) != K5_JSON_TID_NUMBER)
+ return EINVAL;
+
+ *int32 = k5_json_number_value(val);
+ return 0;
+}
+
+/* Converts a krb5_int32 into a property of a JSON object. */
+static krb5_error_code
+codec_int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key)
+{
+ krb5_error_code retval;
+ k5_json_number num;
+
+ if (int32 == -1)
+ return 0;
+
+ num = k5_json_number_create(int32);
+ if (num == NULL)
+ return ENOMEM;
+
+ retval = k5_json_object_set(obj, key, num);
+ k5_json_release(num);
+ return retval == 0 ? 0 : ENOMEM;
+}
+
+/* Converts a krb5_otp_tokeninfo into a JSON object. */
+static krb5_error_code
+codec_encode_tokeninfo(krb5_otp_tokeninfo *ti, k5_json_object *out)
+{
+ krb5_error_code retval = 0;
+ k5_json_object obj;
+ krb5_flags flags;
+
+ obj = k5_json_object_create();
+ if (obj == NULL)
+ goto error;
+
+ flags = KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN;
+ if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN)
+ flags |= KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN;
+ if (ti->flags & KRB5_OTP_FLAG_NEXTOTP)
+ flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP;
+
+ retval = codec_int32_to_value(flags, obj, "flags");
+ if (retval != 0)
+ goto error;
+
+ retval = codec_data_to_value(&ti->vendor, obj, "vendor");
+ if (retval != 0)
+ goto error;
+
+ retval = codec_data_to_value(&ti->challenge, obj, "challenge");
+ if (retval != 0)
+ goto error;
+
+ retval = codec_int32_to_value(ti->length, obj, "length");
+ if (retval != 0)
+ goto error;
+
+ if (ti->format != KRB5_OTP_FORMAT_BASE64) {
+ retval = codec_int32_to_value(ti->format, obj, "format");
+ if (retval != 0)
+ goto error;
+ }
+
+ retval = codec_data_to_value(&ti->token_id, obj, "tokenID");
+ if (retval != 0)
+ goto error;
+
+ retval = codec_data_to_value(&ti->alg_id, obj, "algID");
+ if (retval != 0)
+ goto error;
+
+ *out = obj;
+ return 0;
+
+error:
+ k5_json_release(obj);
+ return retval;
+}
+
+/* Converts a krb5_pa_otp_challenge into a JSON object. */
+static krb5_error_code
+codec_encode_challenge(krb5_context ctx, krb5_pa_otp_challenge *chl,
+ char **json)
+{
+ k5_json_object obj = NULL, tmp = NULL;
+ k5_json_string str = NULL;
+ k5_json_array arr = NULL;
+ krb5_error_code retval = 0;
+ int i;
+
+ obj = k5_json_object_create();
+ if (obj == NULL)
+ goto error;
+
+ if (chl->service.data) {
+ str = k5_json_string_create_len(chl->service.data,
+ chl->service.length);
+ if (str == NULL)
+ goto error;
+ retval = k5_json_object_set(obj, "service", str);
+ k5_json_release(str);
+ if (retval != 0) {
+ retval = ENOMEM;
+ goto error;
+ }
+ }
+
+ arr = k5_json_array_create();
+ if (arr == NULL)
+ goto error;
+
+ for (i = 0; chl->tokeninfo[i] != NULL ; i++) {
+ retval = codec_encode_tokeninfo(chl->tokeninfo[i], &tmp);
+ if (retval != 0)
+ goto error;
+
+ retval = k5_json_array_add(arr, tmp);
+ k5_json_release(tmp);
+ if (retval != 0) {
+ retval = ENOMEM;
+ goto error;
+ }
+ }
+
+ if (k5_json_object_set(obj, "tokenInfo", arr) != 0) {
+ retval = ENOMEM;
+ goto error;
+ }
+
+ *json = k5_json_encode(obj);
+ if (*json == NULL)
+ goto error;
+
+ k5_json_release(arr);
+ k5_json_release(obj);
+ return 0;
+
+error:
+ k5_json_release(arr);
+ k5_json_release(obj);
+ return retval == 0 ? ENOMEM : retval;
+}
+
+/* Converts a JSON object into a krb5_responder_otp_tokeninfo. */
+static krb5_responder_otp_tokeninfo *
+codec_decode_tokeninfo(k5_json_object obj)
+{
+ krb5_responder_otp_tokeninfo *ti = NULL;
+ krb5_error_code retval;
+
+ ti = calloc(1, sizeof(krb5_responder_otp_tokeninfo));
+ if (ti == NULL)
+ goto error;
+
+ retval = codec_value_to_int32(obj, "flags", &ti->flags);
+ if (retval != 0)
+ goto error;
+
+ retval = codec_value_to_string(obj, "vendor", &ti->vendor);
+ if (retval != 0 && retval != ENOENT)
+ goto error;
+
+ retval = codec_value_to_string(obj, "challenge", &ti->challenge);
+ if (retval != 0 && retval != ENOENT)
+ goto error;
+
+ retval = codec_value_to_int32(obj, "length", &ti->length);
+ if (retval == ENOENT)
+ ti->length = -1;
+ else if (retval != 0)
+ goto error;
+
+ retval = codec_value_to_int32(obj, "format", &ti->format);
+ if (retval == ENOENT)
+ ti->format = -1;
+ else if (retval != 0)
+ goto error;
+
+ retval = codec_value_to_string(obj, "tokenID", &ti->token_id);
+ if (retval != 0 && retval != ENOENT)
+ goto error;
+
+ retval = codec_value_to_string(obj, "algID", &ti->alg_id);
+ if (retval != 0 && retval != ENOENT)
+ goto error;
+
+ return ti;
+
+error:
+ free_tokeninfo(ti);
+ return NULL;
+}
+
+/* Converts a JSON object into a krb5_responder_otp_challenge. */
+static krb5_responder_otp_challenge *
+codec_decode_challenge(krb5_context ctx, const char *json)
+{
+ krb5_responder_otp_challenge *chl = NULL;
+ k5_json_value obj = NULL, arr = NULL, tmp = NULL;
+ krb5_error_code retval;
+ size_t i;
+
+ obj = k5_json_decode(json);
+ if (obj == NULL)
+ goto error;
+
+ if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT)
+ goto error;
+
+ arr = k5_json_object_get(obj, "tokenInfo");
+ if (arr == NULL)
+ goto error;
+
+ if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY)
+ goto error;
+
+ chl = calloc(1, sizeof(krb5_responder_otp_challenge));
+ if (chl == NULL)
+ goto error;
+
+ chl->tokeninfo = calloc(k5_json_array_length(arr) + 1,
+ sizeof(krb5_responder_otp_tokeninfo*));
+ if (chl->tokeninfo == NULL)
+ goto error;
+
+ retval = codec_value_to_string(obj, "service", &chl->service);
+ if (retval != 0 && retval != ENOENT)
+ goto error;
+
+ for (i = 0; i < k5_json_array_length(arr); i++) {
+ tmp = k5_json_array_get(arr, i);
+ if (k5_json_get_tid(tmp) != K5_JSON_TID_OBJECT)
+ goto error;
+
+ chl->tokeninfo[i] = codec_decode_tokeninfo(tmp);
+ if (chl->tokeninfo[i] == NULL)
+ goto error;
+ }
+
+ k5_json_release(obj);
+ return chl;
+
+error:
+ if (chl != NULL) {
+ for (i = 0; chl->tokeninfo != NULL && chl->tokeninfo[i] != NULL; i++)
+ free_tokeninfo(chl->tokeninfo[i]);
+ free(chl->tokeninfo);
+ free(chl);
+ }
+ k5_json_release(obj);
+ return NULL;
+}
+
+/* Decode the responder answer into a tokeninfo, a value and a pin. */
+static krb5_error_code
+codec_decode_answer(krb5_context context, const char *answer,
+ krb5_otp_tokeninfo **tis, krb5_otp_tokeninfo **ti,
+ krb5_data *value, krb5_data *pin)
+{
+ krb5_error_code retval = EBADMSG;
+ k5_json_value val = NULL;
+ krb5_int32 indx, i;
+ krb5_data tmp;
+
+ if (answer == NULL)
+ return EBADMSG;
+
+ val = k5_json_decode(answer);
+ if (val == NULL)
+ goto cleanup;
+
+ if (k5_json_get_tid(val) != K5_JSON_TID_OBJECT)
+ goto cleanup;
+
+ retval = codec_value_to_int32(val, "tokeninfo", &indx);
+ if (retval != 0)
+ goto cleanup;
+
+ for (i = 0; tis[i] != NULL; i++) {
+ if (i == indx) {
+ retval = codec_value_to_data(val, "value", &tmp);
+ if (retval != 0 && retval != ENOENT)
+ goto cleanup;
+
+ retval = codec_value_to_data(val, "pin", pin);
+ if (retval != 0 && retval != ENOENT) {
+ krb5_free_data_contents(context, &tmp);
+ goto cleanup;
+ }
+
+ *value = tmp;
+ *ti = tis[i];
+ retval = 0;
+ goto cleanup;
+ }
+ }
+ retval = EINVAL;
+
+cleanup:
+ k5_json_release(val);
+ return retval;
+}
+
/* Takes the nonce from the challenge and encrypts it into the request. */
static krb5_error_code
encrypt_nonce(krb5_context ctx, krb5_keyblock *key,
@@ -541,6 +928,57 @@ otp_client_get_flags(krb5_context context, krb5_preauthtype pa_type)
return PA_REAL;
}
+static void
+otp_client_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq *modreq_out)
+{
+ *modreq_out = calloc(1, sizeof(krb5_pa_otp_challenge *));
+}
+
+static krb5_error_code
+otp_client_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock, krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data)
+{
+ krb5_pa_otp_challenge *chl;
+ krb5_error_code retval;
+ krb5_data tmp;
+ char *json;
+
+ if (modreq == NULL)
+ return ENOMEM;
+
+ /* Decode the challenge. */
+ tmp = make_data(pa_data->contents, pa_data->length);
+ retval = decode_krb5_pa_otp_challenge(&tmp,
+ (krb5_pa_otp_challenge **)modreq);
+ if (retval != 0)
+ return retval;
+ chl = *(krb5_pa_otp_challenge **)modreq;
+
+ /* Remove unsupported tokeninfos. */
+ retval = filter_supported_tokeninfos(context, chl->tokeninfo);
+ if (retval != 0)
+ return retval;
+
+ /* Make the JSON representation. */
+ retval = codec_encode_challenge(context, chl, &json);
+ if (retval != 0)
+ return retval;
+
+ /* Ask the question. */
+ retval = cb->ask_responder_question(context, rock,
+ KRB5_RESPONDER_QUESTION_OTP,
+ json);
+ free(json);
+ return retval;
+}
+
static krb5_error_code
otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
@@ -555,7 +993,12 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
krb5_keyblock *as_key = NULL;
krb5_pa_otp_req *req = NULL;
krb5_error_code retval = 0;
- krb5_data tmp, value, pin;
+ krb5_data value, pin;
+ const char *answer;
+
+ if (modreq == NULL)
+ return ENOMEM;
+ chl = *(krb5_pa_otp_challenge **)modreq;
*pa_data_out = NULL;
@@ -569,22 +1012,21 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
if (retval != 0)
return retval;
- /* Decode the challenge. */
- tmp = make_data(pa_data->contents, pa_data->length);
- retval = decode_krb5_pa_otp_challenge(&tmp, &chl);
- if (retval != 0)
- return retval;
-
- /* Remove unsupported tokeninfos. */
- retval = filter_supported_tokeninfos(context, chl->tokeninfo);
- if (retval != 0)
- goto error;
-
- /* Have the user select a tokeninfo and enter a password/pin. */
- retval = prompt_for_token(context, prompter, prompter_data,
- chl->tokeninfo, &ti, &value, &pin);
- if (retval != 0)
- goto error;
+ /* Attempt to get token selection from the responder. */
+ pin = empty_data();
+ value = empty_data();
+ answer = cb->get_responder_answer(context, rock,
+ KRB5_RESPONDER_QUESTION_OTP);
+ retval = codec_decode_answer(context, answer, chl->tokeninfo, &ti, &value,
+ &pin);
+ if (retval != 0) {
+ /* If the responder doesn't have a token selection,
+ * we need to select the token via prompting. */
+ retval = prompt_for_token(context, prompter, prompter_data,
+ chl->tokeninfo, &ti, &value, &pin);
+ if (retval != 0)
+ goto error;
+ }
/* Make the request. */
retval = make_request(context, ti, &value, &pin, &req);
@@ -601,11 +1043,21 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
error:
krb5_free_data_contents(context, &value);
krb5_free_data_contents(context, &pin);
- k5_free_pa_otp_challenge(context, chl);
k5_free_pa_otp_req(context, req);
return retval;
}
+static void
+otp_client_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq)
+{
+ if (modreq == NULL)
+ return;
+
+ k5_free_pa_otp_challenge(context, *(krb5_pa_otp_challenge **)modreq);
+ free(modreq);
+}
+
krb5_error_code
clpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
krb5_plugin_vtable vtable)
@@ -619,8 +1071,110 @@ clpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
vt->name = "otp";
vt->pa_type_list = otp_client_supported_pa_types;
vt->flags = otp_client_get_flags;
+ vt->request_init = otp_client_request_init;
+ vt->prep_questions = otp_client_prep_questions;
vt->process = otp_client_process;
+ vt->request_fini = otp_client_request_fini;
vt->gic_opts = NULL;
return 0;
}
+
+krb5_error_code
+krb5_responder_otp_get_challenge(krb5_context ctx,
+ krb5_responder_context rctx,
+ krb5_responder_otp_challenge **chl)
+{
+ const char *answer;
+ krb5_responder_otp_challenge *challenge;
+
+ answer = krb5_responder_get_challenge(ctx, rctx,
+ KRB5_RESPONDER_QUESTION_OTP);
+ if (answer == NULL) {
+ *chl = NULL;
+ return 0;
+ }
+
+ challenge = codec_decode_challenge(ctx, answer);
+ if (challenge == NULL)
+ return ENOMEM;
+
+ *chl = challenge;
+ return 0;
+}
+
+krb5_error_code
+krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx,
+ size_t ti, const char *value, const char *pin)
+{
+ krb5_error_code retval;
+ k5_json_object obj = NULL;
+ k5_json_value val = NULL;
+ char *tmp;
+
+ obj = k5_json_object_create();
+ if (obj == NULL)
+ goto error;
+
+ val = k5_json_number_create(ti);
+ if (val == NULL)
+ goto error;
+
+ retval = k5_json_object_set(obj, "tokeninfo", val);
+ k5_json_release(val);
+ if (retval != 0)
+ goto error;
+
+ if (value != NULL) {
+ val = k5_json_string_create(value);
+ if (val == NULL)
+ goto error;
+
+ retval = k5_json_object_set(obj, "value", val);
+ k5_json_release(val);
+ if (retval != 0)
+ goto error;
+ }
+
+ if (pin != NULL) {
+ val = k5_json_string_create(pin);
+ if (val == NULL)
+ goto error;
+
+ retval = k5_json_object_set(obj, "pin", val);
+ k5_json_release(val);
+ if (retval != 0)
+ goto error;
+ }
+
+ tmp = k5_json_encode(obj);
+ k5_json_release(obj);
+ if (tmp == NULL)
+ goto error;
+
+ retval = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_OTP,
+ tmp);
+ free(tmp);
+ return retval;
+
+error:
+ k5_json_release(obj);
+ return ENOMEM;
+}
+
+void
+krb5_responder_otp_challenge_free(krb5_context ctx,
+ krb5_responder_context rctx,
+ krb5_responder_otp_challenge *chl)
+{
+ size_t i;
+
+ if (chl == NULL)
+ return;
+
+ for (i = 0; chl->tokeninfo[i]; i++)
+ free_tokeninfo(chl->tokeninfo[i]);
+ free(chl->service);
+ free(chl->tokeninfo);
+ free(chl);
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 701aa39..d8c181d 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -528,6 +528,9 @@ krb5_register_serializer
krb5_responder_get_challenge
krb5_responder_list_questions
krb5_responder_set_answer
+krb5_responder_otp_get_challenge
+krb5_responder_otp_set_answer
+krb5_responder_otp_challenge_free
krb5_salttype_to_string
krb5_sendauth
krb5_sendto_kdc
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index 21419b7..6fe0784 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -439,3 +439,6 @@ EXPORTS
krb5_rc_resolve_full @410 ; PRIVATE GSSAPI
krb5_rc_get_name @411 ; PRIVATE GSSAPI
krb5_rc_get_type @412 ; PRIVATE GSSAPI
+ krb5_responder_otp_get_challenge @413
+ krb5_responder_otp_set_answer @414
+ krb5_responder_otp_challenge_free @415
More information about the cvs-krb5
mailing list