krb5 commit: Migrate to non-destructive tokeninfo selection
Greg Hudson
ghudson at MIT.EDU
Mon Oct 15 11:09:51 EDT 2012
https://github.com/krb5/krb5/commit/82a2526603e567eef08298f20e061d093c61e79c
commit 82a2526603e567eef08298f20e061d093c61e79c
Author: Nathaniel McCallum <npmccallum at redhat.com>
Date: Sun Oct 14 21:38:02 2012 -0400
Migrate to non-destructive tokeninfo selection
src/lib/krb5/krb/preauth_otp.c | 339 ++++++++++++++++++++++-----------------
1 files changed, 191 insertions(+), 148 deletions(-)
diff --git a/src/lib/krb5/krb/preauth_otp.c b/src/lib/krb5/krb/preauth_otp.c
index 2ac1705..c72f8b6 100644
--- a/src/lib/krb5/krb/preauth_otp.c
+++ b/src/lib/krb5/krb/preauth_otp.c
@@ -32,12 +32,7 @@
#include "int-proto.h"
#include <krb5/preauth_plugin.h>
-
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
#include <ctype.h>
-#include <string.h>
static krb5_preauthtype otp_client_supported_pa_types[] =
{ KRB5_PADATA_OTP_CHALLENGE, 0 };
@@ -63,9 +58,7 @@ encrypt_nonce(krb5_context ctx, krb5_keyblock *key,
if (retval != 0)
return retval;
- free(req->enc_data.ciphertext.data);
req->enc_data = encdata;
-
return 0;
}
@@ -92,23 +85,6 @@ otpvalue_matches_tokeninfo(const char *otpvalue, krb5_otp_tokeninfo *ti)
return 1;
}
-/* Removes the indexed tokeninfo from the array. */
-static void
-remove_tokeninfo(krb5_context ctx, krb5_otp_tokeninfo **tis, unsigned int i)
-{
- unsigned int j = 0;
-
- if (tis == NULL)
- return;
-
- for (j = 0; tis[j]; j++) {
- if (j == i)
- k5_free_otp_tokeninfo(ctx, tis[j]);
- if (j >= i)
- tis[j] = tis[j+1];
- }
-}
-
/* Performs a prompt and saves the response in the out parameter. */
static krb5_error_code
doprompt(krb5_context context, krb5_prompter_fct prompter, void *prompter_data,
@@ -135,11 +111,11 @@ doprompt(krb5_context context, krb5_prompter_fct prompter, void *prompter_data,
return 0;
}
-/* Forces the user to choose a single tokeninfo via prompting.
- * Removes all other tokeninfos from the array. */
+/* Forces the user to choose a single tokeninfo via prompting. */
static krb5_error_code
-choose_token(krb5_context context, krb5_prompter_fct prompter,
- void *prompter_data, krb5_otp_tokeninfo **tis)
+prompt_for_tokeninfo(krb5_context context, krb5_prompter_fct prompter,
+ void *prompter_data, krb5_otp_tokeninfo **tis,
+ krb5_otp_tokeninfo **out_ti)
{
char *banner = NULL, *tmp, response[1024];
krb5_otp_tokeninfo *ti = NULL;
@@ -178,47 +154,13 @@ choose_token(krb5_context context, krb5_prompter_fct prompter,
continue;
ti = tis[--j];
- for (i = 0; tis[i] != NULL; i++) {
- if (tis[i] != ti)
- remove_tokeninfo(context, tis, i--);
- }
} while (ti == NULL);
free(banner);
+ *out_ti = ti;
return 0;
}
-/* Like asprintf() but saves output into a krb5_data structure. */
-static krb5_error_code
-data_printf(krb5_data *data, const char *fmt, ...)
-{
- va_list ap;
- char *tmp = NULL;
-
- if (data == NULL)
- return EINVAL;
-
- va_start(ap, fmt);
- if (vasprintf(&tmp, fmt, ap) < 0) {
- va_end(ap);
- return errno;
- }
- va_end(ap);
-
- free(data->data);
- data->data = tmp;
- data->length = strlen(tmp);
-
- return 0;
-}
-
-/* Takes the otp value in the request and base64 encodes it. */
-static krb5_error_code
-base64_encode_request(krb5_pa_otp_req *req)
-{
- return ENOTSUP;
-}
-
/* Builds a challenge string from the given tokeninfo. */
static krb5_error_code
make_challenge(const krb5_otp_tokeninfo *ti, char **challenge)
@@ -240,26 +182,22 @@ make_challenge(const krb5_otp_tokeninfo *ti, char **challenge)
return 0;
}
-/* Sets the otp value into the request. Similarly, collects and sets
- * the pin if necessary. */
-static krb5_error_code
-set_value_and_collect_pin(krb5_context context, krb5_prompter_fct prompter,
- void *prompter_data, const krb5_otp_tokeninfo *ti,
- const char *otpvalue, krb5_pa_otp_req *req)
+/* Determines if a pin is required. If it is, it will be prompted for. */
+static inline krb5_error_code
+collect_pin(krb5_context context, krb5_prompter_fct prompter,
+ void *prompter_data, const krb5_otp_tokeninfo *ti,
+ krb5_data *out_pin)
{
krb5_error_code retval;
char otppin[1024];
- krb5_flags pin;
-
- pin = ti->flags & (KRB5_OTP_FLAG_COLLECT_PIN | KRB5_OTP_FLAG_SEPARATE_PIN);
-
- /* If no PIN will be collected, just set the otp value. */
- if (pin == 0) {
- retval = data_printf(&req->otp_value, "%s", otpvalue);
- if (retval != 0)
- return retval;
-
- req->pin = empty_data();
+ krb5_flags collect;
+ krb5_data pin;
+
+ /* If no PIN will be collected, don't prompt. */
+ collect = ti->flags & (KRB5_OTP_FLAG_COLLECT_PIN |
+ KRB5_OTP_FLAG_SEPARATE_PIN);
+ if (collect == 0) {
+ *out_pin = empty_data();
return 0;
}
@@ -269,44 +207,154 @@ set_value_and_collect_pin(krb5_context context, krb5_prompter_fct prompter,
if (retval != 0)
return retval;
- /* Set the separate PIN and Value fields. */
- if (pin & KRB5_OTP_FLAG_SEPARATE_PIN) {
- retval = data_printf(&req->otp_value, "%s", otpvalue);
- if (retval != 0)
- return retval;
+ /* Set the PIN. */
+ pin = make_data(strdup(otppin), strlen(otppin));
+ if (pin.data == NULL)
+ return ENOMEM;
- retval = data_printf(&req->pin, "%s", otppin);
- if (retval != 0)
- return retval;
+ *out_pin = pin;
+ return 0;
+}
- /* Prepend PIN to the Value field. */
- } else {
- retval = data_printf(&req->otp_value, "%s%s", otppin, otpvalue);
- if (retval != 0)
- return retval;
+/* Builds a request using the specified tokeninfo, value and pin. */
+static krb5_error_code
+make_request(krb5_context ctx, krb5_otp_tokeninfo *ti, const krb5_data *value,
+ const krb5_data *pin, krb5_pa_otp_req **out_req)
+{
+ krb5_pa_otp_req *req = NULL;
+ krb5_error_code retval = 0;
- req->pin = empty_data();
+ if (ti == NULL)
+ return 0;
+
+ if (ti->format == KRB5_OTP_FORMAT_BASE64)
+ return ENOTSUP;
+
+ req = calloc(1, sizeof(krb5_pa_otp_req));
+ if (req == NULL)
+ return ENOMEM;
+
+ req->flags = ti->flags & KRB5_OTP_FLAG_NEXTOTP;
+
+ retval = krb5int_copy_data_contents(ctx, &ti->vendor, &req->vendor);
+ if (retval != 0)
+ goto error;
+
+ req->format = ti->format;
+
+ retval = krb5int_copy_data_contents(ctx, &ti->token_id, &req->token_id);
+ if (retval != 0)
+ goto error;
+
+ retval = krb5int_copy_data_contents(ctx, &ti->alg_id, &req->alg_id);
+ if (retval != 0)
+ goto error;
+
+ retval = krb5int_copy_data_contents(ctx, value, &req->otp_value);
+ if (retval != 0)
+ goto error;
+
+ if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN) {
+ if (pin == NULL || pin->data == NULL) {
+ retval = EINVAL; /* No pin found! */
+ goto error;
+ }
+
+ if (ti->flags & KRB5_OTP_FLAG_SEPARATE_PIN) {
+ retval = krb5int_copy_data_contents(ctx, pin, &req->pin);
+ if (retval != 0)
+ goto error;
+ } else {
+ krb5_free_data_contents(ctx, &req->otp_value);
+ retval = asprintf(&req->otp_value.data, "%.*s%.*s",
+ pin->length, pin->data,
+ value->length, value->data);
+ if (retval < 0) {
+ retval = ENOMEM;
+ req->otp_value = empty_data();
+ goto error;
+ }
+ req->otp_value.length = req->pin.length + req->otp_value.length;
+ }
+ }
+
+ *out_req = req;
+ return 0;
+
+error:
+ k5_free_pa_otp_req(ctx, req);
+ return retval;
+}
+
+/*
+ * Filters a set of tokeninfos given an otp value. If the set is reduced to
+ * a single tokeninfo, it will be set in out_ti. Otherwise, a new shallow copy
+ * will be allocated in out_filtered.
+ */
+static inline krb5_error_code
+filter_tokeninfos(krb5_context context, const char *otpvalue,
+ krb5_otp_tokeninfo **tis,
+ krb5_otp_tokeninfo ***out_filtered,
+ krb5_otp_tokeninfo **out_ti)
+{
+ krb5_otp_tokeninfo **filtered;
+ size_t i = 0, j = 0;
+
+ while (tis[i] != NULL)
+ i++;
+
+ filtered = calloc(i + 1, sizeof(const krb5_otp_tokeninfo *));
+ if (filtered == NULL)
+ return ENOMEM;
+
+ /* Make a list of tokeninfos that match the value. */
+ for (i = 0, j = 0; tis[i] != NULL; i++) {
+ if (otpvalue_matches_tokeninfo(otpvalue, tis[i]))
+ filtered[j++] = tis[i];
+ }
+
+ /* It is an error if we have no matching tokeninfos. */
+ if (filtered[0] == NULL) {
+ free(filtered);
+ krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
+ _("OTP value doesn't match "
+ "any token formats"));
+ return KRB5_PREAUTH_FAILED; /* We have no supported tokeninfos. */
+ }
+
+ /* Otherwise, if we have just one tokeninfo, choose it. */
+ if (filtered[1] == NULL) {
+ *out_ti = filtered[0];
+ *out_filtered = NULL;
+ free(filtered);
+ return 0;
}
+ /* Otherwise, we'll return the remaining list. */
+ *out_ti = NULL;
+ *out_filtered = filtered;
return 0;
}
-/* Builds a request object to send to the KDC, prompting the user if
- * necessary. */
+/* Outputs the selected tokeninfo and possibly a value and pin.
+ * Prompting may occur. */
static krb5_error_code
-make_request(krb5_context context, krb5_prompter_fct prompter,
- void *prompter_data, krb5_otp_tokeninfo **tis,
- krb5_pa_otp_req **request)
+prompt_for_token(krb5_context context, krb5_prompter_fct prompter,
+ void *prompter_data, krb5_otp_tokeninfo **tis,
+ krb5_otp_tokeninfo **out_ti, krb5_data *out_value,
+ krb5_data *out_pin)
{
+ krb5_otp_tokeninfo **filtered = NULL;
+ krb5_otp_tokeninfo *ti = NULL;
krb5_error_code retval;
int i, challengers = 0;
char *challenge = NULL;
char otpvalue[1024];
- krb5_pa_otp_req *req;
+ krb5_data value, pin;
memset(otpvalue, 0, sizeof(otpvalue));
- if (request == NULL || tis == NULL || tis[0] == NULL)
+ if (tis == NULL || tis[0] == NULL || out_ti == NULL)
return EINVAL;
/* Count how many challenges we have. */
@@ -315,17 +363,22 @@ make_request(krb5_context context, krb5_prompter_fct prompter,
challengers++;
}
+ /* If we have only one tokeninfo as input, choose it. */
+ if (i == 1)
+ ti = tis[0];
+
/* Setup our challenge, if present. */
if (challengers > 0) {
/* If we have multiple tokeninfos still, choose now. */
- if (tis[1] != NULL) {
- retval = choose_token(context, prompter, prompter_data, tis);
+ if (ti == NULL) {
+ retval = prompt_for_tokeninfo(context, prompter, prompter_data,
+ tis, &ti);
if (retval != 0)
return retval;
}
/* Create the challenge prompt. */
- retval = make_challenge(tis[0], &challenge);
+ retval = make_challenge(ti, &challenge);
if (retval != 0)
return retval;
}
@@ -337,57 +390,39 @@ make_request(krb5_context context, krb5_prompter_fct prompter,
if (retval != 0)
return retval;
- /* Filter out tokeninfos that don't match our token value. */
- for (i = 0; tis[i] != NULL; i++) {
- if (!otpvalue_matches_tokeninfo(otpvalue, tis[i]))
- remove_tokeninfo(context, tis, i--);
- }
-
- /* If we still have multiple tokeninfos, choose now. */
- if (tis[0] != NULL && tis[1] != NULL) {
- retval = choose_token(context, prompter, prompter_data, tis);
+ if (ti == NULL) {
+ /* Filter out tokeninfos that don't match our token value. */
+ retval = filter_tokeninfos(context, otpvalue, tis, &filtered, &ti);
if (retval != 0)
return retval;
- }
- if (tis == NULL || tis[0] == NULL) {
- krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
- _("OTP value doesn't match any token formats"));
- return KRB5_PREAUTH_FAILED; /* We have no supported tokeninfos. */
+
+ /* If we still don't have a single tokeninfo, choose now. */
+ if (filtered != NULL) {
+ retval = prompt_for_tokeninfo(context, prompter, prompter_data,
+ filtered, &ti);
+ free(filtered);
+ if (retval != 0)
+ return retval;
+ }
}
- /* Create the request. */
- req = calloc(1, sizeof(krb5_pa_otp_req));
- if (req == NULL)
+ assert(ti != NULL);
+
+ /* Set the value. */
+ value = make_data(strdup(otpvalue), strlen(otpvalue));
+ if (value.data == NULL)
return ENOMEM;
/* Collect the PIN, if necessary. */
- retval = set_value_and_collect_pin(context, prompter, prompter_data,
- tis[0], otpvalue, req);
+ retval = collect_pin(context, prompter, prompter_data, ti, &pin);
if (retval != 0) {
- k5_free_pa_otp_req(context, req);
+ krb5_free_data_contents(context, &value);
return retval;
}
- /* Do Base64 encoding, if necessary. */
- if (tis[0]->format == KRB5_OTP_FORMAT_BASE64) {
- retval = base64_encode_request(req);
- if (retval != 0) {
- k5_free_pa_otp_req(context, req);
- return retval;
- }
- }
-
- /* Steal values from the tokeninfo. */
- req->flags = tis[0]->flags;
- req->alg_id = tis[0]->alg_id;
- req->format = tis[0]->format;
- req->token_id = tis[0]->token_id;
- req->vendor = tis[0]->vendor;
- tis[0]->alg_id = empty_data();
- tis[0]->token_id = empty_data();
- tis[0]->vendor = empty_data();
-
- *request = req;
+ *out_value = value;
+ *out_pin = pin;
+ *out_ti = ti;
return 0;
}
@@ -516,10 +551,11 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
krb5_pa_data ***pa_data_out)
{
krb5_pa_otp_challenge *chl = NULL;
+ krb5_otp_tokeninfo *ti = NULL;
krb5_keyblock *as_key = NULL;
krb5_pa_otp_req *req = NULL;
krb5_error_code retval = 0;
- krb5_data tmp;
+ krb5_data tmp, value, pin;
*pa_data_out = NULL;
@@ -544,9 +580,14 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
if (retval != 0)
goto error;
- /* Fill in the request info from the TokenInfo structs .*/
- retval = make_request(context, prompter, prompter_data,
- chl->tokeninfo, &req);
+ /* 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;
+
+ /* Make the request. */
+ retval = make_request(context, ti, &value, &pin, &req);
if (retval != 0)
goto error;
@@ -558,6 +599,8 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
/* Encode the request into the pa_data output. */
retval = set_pa_data(req, pa_data_out);
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;
More information about the cvs-krb5
mailing list