krb5/SOURCES/Restrict-pre-authentication...

492 lines
21 KiB
Diff

From 70f41a8dafaadfb43aba4918564c22460f812dca Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Thu, 5 Apr 2018 16:23:34 -0400
Subject: [PATCH] Restrict pre-authentication fallback cases
Add a new callback disable_fallback() and call it from each clpreauth
module when it generates a client message using credentials to
authenticate. (For SPAKE, this is the message responding to a
challenge; for all other current mechanisms, it is the first and only
client message.) If disable_fallback() is called, do not try another
mechanism after a KDC error.
Remove k5_reset_preauth_types_tried() and its call sites, so that
preauth mechanisms which are tried optimistically will no longer be
retried after a failure.
ticket: 8654
(cherry picked from commit 7a24a088c16d326127dd2b29084d4ca085c70d10)
---
src/include/krb5/clpreauth_plugin.h | 14 ++++
src/lib/krb5/krb/get_in_tkt.c | 21 +++---
src/lib/krb5/krb/init_creds_ctx.h | 1 +
src/lib/krb5/krb/int-proto.h | 3 -
src/lib/krb5/krb/preauth2.c | 23 +++----
src/lib/krb5/krb/preauth_ec.c | 1 +
src/lib/krb5/krb/preauth_encts.c | 2 +
src/lib/krb5/krb/preauth_otp.c | 4 ++
src/lib/krb5/krb/preauth_sam2.c | 1 +
src/plugins/preauth/pkinit/pkinit_clnt.c | 1 +
src/plugins/preauth/spake/spake_client.c | 4 ++
src/plugins/preauth/test/cltest.c | 11 +++
src/tests/t_preauth.py | 88 +++++++++++++++++++++---
src/tests/t_spake.py | 9 +--
14 files changed, 134 insertions(+), 49 deletions(-)
diff --git a/src/include/krb5/clpreauth_plugin.h b/src/include/krb5/clpreauth_plugin.h
index 0106734ad..5317669b7 100644
--- a/src/include/krb5/clpreauth_plugin.h
+++ b/src/include/krb5/clpreauth_plugin.h
@@ -160,7 +160,21 @@ typedef struct krb5_clpreauth_callbacks_st {
krb5_error_code (*set_cc_config)(krb5_context context,
krb5_clpreauth_rock rock,
const char *key, const char *data);
+
/* End of version 2 clpreauth callbacks (added in 1.11). */
+
+ /*
+ * Prevent further fallbacks to other preauth mechanisms if the KDC replies
+ * with an error. (The module itself can still respond to errors with its
+ * tryagain method, or continue after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED
+ * errors with its process method.) A module should invoke this callback
+ * from the process method when it generates an authenticated request using
+ * credentials; often this will be the first or only client message
+ * generated by the mechanism.
+ */
+ void (*disable_fallback)(krb5_context context, krb5_clpreauth_rock rock);
+
+ /* End of version 3 clpreauth callbacks (added in 1.17). */
} *krb5_clpreauth_callbacks;
/*
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
index 1d96ff163..c026bbc6d 100644
--- a/src/lib/krb5/krb/get_in_tkt.c
+++ b/src/lib/krb5/krb/get_in_tkt.c
@@ -1331,9 +1331,7 @@ init_creds_step_request(krb5_context context,
krb5_free_pa_data(context, ctx->optimistic_padata);
ctx->optimistic_padata = NULL;
if (code) {
- /* Make an unauthenticated request, and possibly try again using
- * the same mechanisms as we tried optimistically. */
- k5_reset_preauth_types_tried(ctx);
+ /* Make an unauthenticated request. */
krb5_clear_error_message(context);
code = 0;
}
@@ -1361,6 +1359,9 @@ init_creds_step_request(krb5_context context,
/* Don't continue after a keyboard interrupt. */
if (code == KRB5_LIBOS_PWDINTR)
goto cleanup;
+ /* Don't continue if fallback is disabled. */
+ if (code && ctx->fallback_disabled)
+ goto cleanup;
if (code) {
/* See if we can try a different preauth mech before giving up. */
k5_save_ctx_error(context, code, &save);
@@ -1549,16 +1550,10 @@ init_creds_step_reply(krb5_context context,
} else if (reply_code == KDC_ERR_PREAUTH_FAILED && retry) {
note_req_timestamp(context, ctx, ctx->err_reply->stime,
ctx->err_reply->susec);
- if (ctx->method_padata == NULL) {
- /* Optimistic preauth failed on the KDC. Allow all mechanisms
- * to be tried again using method data. */
- k5_reset_preauth_types_tried(ctx);
- } else {
- /* Don't try again with the mechanism that failed. */
- code = k5_preauth_note_failed(ctx, ctx->selected_preauth_type);
- if (code)
- goto cleanup;
- }
+ /* Don't try again with the mechanism that failed. */
+ code = k5_preauth_note_failed(ctx, ctx->selected_preauth_type);
+ if (code)
+ goto cleanup;
ctx->selected_preauth_type = KRB5_PADATA_NONE;
/* Accept or update method data if the KDC sent it. */
if (ctx->err_padata != NULL)
diff --git a/src/lib/krb5/krb/init_creds_ctx.h b/src/lib/krb5/krb/init_creds_ctx.h
index b19410a13..7ba61e17c 100644
--- a/src/lib/krb5/krb/init_creds_ctx.h
+++ b/src/lib/krb5/krb/init_creds_ctx.h
@@ -60,6 +60,7 @@ struct _krb5_init_creds_context {
krb5_enctype etype;
krb5_boolean info_pa_permitted;
krb5_boolean restarted;
+ krb5_boolean fallback_disabled;
struct krb5_responder_context_st rctx;
krb5_preauthtype selected_preauth_type;
krb5_preauthtype allowed_preauth_type;
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index cda9010e3..d20133885 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -197,9 +197,6 @@ k5_init_preauth_context(krb5_context context);
void
k5_free_preauth_context(krb5_context context);
-void
-k5_reset_preauth_types_tried(krb5_init_creds_context ctx);
-
krb5_error_code
k5_preauth_note_failed(krb5_init_creds_context ctx, krb5_preauthtype pa_type);
diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c
index 451e0b7a8..1f17ec2b0 100644
--- a/src/lib/krb5/krb/preauth2.c
+++ b/src/lib/krb5/krb/preauth2.c
@@ -203,18 +203,6 @@ cleanup:
free_handles(context, list);
}
-/* Reset the memory of which preauth types we have already tried. */
-void
-k5_reset_preauth_types_tried(krb5_init_creds_context ctx)
-{
- krb5_preauth_req_context reqctx = ctx->preauth_reqctx;
-
- if (reqctx == NULL)
- return;
- free(reqctx->failed);
- reqctx->failed = NULL;
-}
-
/* Add pa_type to the list of types which has previously failed. */
krb5_error_code
k5_preauth_note_failed(krb5_init_creds_context ctx, krb5_preauthtype pa_type)
@@ -553,8 +541,14 @@ set_cc_config(krb5_context context, krb5_clpreauth_rock rock,
return ret;
}
+static void
+disable_fallback(krb5_context context, krb5_clpreauth_rock rock)
+{
+ ((krb5_init_creds_context)rock)->fallback_disabled = TRUE;
+}
+
static struct krb5_clpreauth_callbacks_st callbacks = {
- 2,
+ 3,
get_etype,
fast_armor,
get_as_key,
@@ -564,7 +558,8 @@ static struct krb5_clpreauth_callbacks_st callbacks = {
responder_get_answer,
need_as_key,
get_cc_config,
- set_cc_config
+ set_cc_config,
+ disable_fallback
};
/* Tweak the request body, for now adding any enctypes which the module claims
diff --git a/src/lib/krb5/krb/preauth_ec.c b/src/lib/krb5/krb/preauth_ec.c
index c1aa9090f..75aab770e 100644
--- a/src/lib/krb5/krb/preauth_ec.c
+++ b/src/lib/krb5/krb/preauth_ec.c
@@ -138,6 +138,7 @@ ec_process(krb5_context context, krb5_clpreauth_moddata moddata,
encoded_ts->data = NULL;
*out_padata = pa;
pa = NULL;
+ cb->disable_fallback(context, rock);
}
free(pa);
krb5_free_data(context, encoded_ts);
diff --git a/src/lib/krb5/krb/preauth_encts.c b/src/lib/krb5/krb/preauth_encts.c
index cec384227..45bf9da92 100644
--- a/src/lib/krb5/krb/preauth_encts.c
+++ b/src/lib/krb5/krb/preauth_encts.c
@@ -109,6 +109,8 @@ encts_process(krb5_context context, krb5_clpreauth_moddata moddata,
*out_padata = pa;
pa = NULL;
+ cb->disable_fallback(context, rock);
+
cleanup:
krb5_free_data(context, ts);
krb5_free_data(context, enc_ts);
diff --git a/src/lib/krb5/krb/preauth_otp.c b/src/lib/krb5/krb/preauth_otp.c
index 48fcbb5d5..13e584657 100644
--- a/src/lib/krb5/krb/preauth_otp.c
+++ b/src/lib/krb5/krb/preauth_otp.c
@@ -1123,6 +1123,10 @@ 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);
+ if (retval != 0)
+ goto error;
+ cb->disable_fallback(context, rock);
+
error:
krb5_free_data_contents(context, &value);
krb5_free_data_contents(context, &pin);
diff --git a/src/lib/krb5/krb/preauth_sam2.c b/src/lib/krb5/krb/preauth_sam2.c
index c8a330655..4c70021a9 100644
--- a/src/lib/krb5/krb/preauth_sam2.c
+++ b/src/lib/krb5/krb/preauth_sam2.c
@@ -410,6 +410,7 @@ sam2_process(krb5_context context, krb5_clpreauth_moddata moddata,
sam_padata[1] = NULL;
*out_padata = sam_padata;
+ cb->disable_fallback(context, rock);
return(0);
}
diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c
index 9483d69e5..77e9e5308 100644
--- a/src/plugins/preauth/pkinit/pkinit_clnt.c
+++ b/src/plugins/preauth/pkinit/pkinit_clnt.c
@@ -179,6 +179,7 @@ pa_pkinit_gen_req(krb5_context context,
*out_padata = return_pa_data;
return_pa_data = NULL;
+ cb->disable_fallback(context, rock);
cleanup:
krb5_free_data(context, der_req);
diff --git a/src/plugins/preauth/spake/spake_client.c b/src/plugins/preauth/spake/spake_client.c
index 47a6ba26c..00734a13b 100644
--- a/src/plugins/preauth/spake/spake_client.c
+++ b/src/plugins/preauth/spake/spake_client.c
@@ -278,6 +278,10 @@ process_challenge(krb5_context context, groupstate *gstate, reqstate *st,
goto cleanup;
TRACE_SPAKE_SEND_RESPONSE(context);
ret = convert_to_padata(response, pa_out);
+ if (ret)
+ goto cleanup;
+
+ cb->disable_fallback(context, rock);
cleanup:
krb5_free_keyblock(context, k0);
diff --git a/src/plugins/preauth/test/cltest.c b/src/plugins/preauth/test/cltest.c
index f5f7c5aba..51b848481 100644
--- a/src/plugins/preauth/test/cltest.c
+++ b/src/plugins/preauth/test/cltest.c
@@ -53,6 +53,9 @@
* - If the "fail_optimistic", "fail_2rt", or "fail_tryagain" gic options are
* set, it fails with a recognizable error string at the requested point in
* processing.
+ *
+ * - If the "disable_fallback" gic option is set, fallback is disabled when a
+ * client message is generated.
*/
#include "k5-int.h"
@@ -66,6 +69,7 @@ struct client_state {
krb5_boolean fail_optimistic;
krb5_boolean fail_2rt;
krb5_boolean fail_tryagain;
+ krb5_boolean disable_fallback;
};
struct client_request_state {
@@ -81,6 +85,7 @@ test_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)
assert(st != NULL);
st->indicators = NULL;
st->fail_optimistic = st->fail_2rt = st->fail_tryagain = FALSE;
+ st->disable_fallback = FALSE;
*moddata_out = (krb5_clpreauth_moddata)st;
return 0;
}
@@ -138,6 +143,8 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata,
return KRB5_PREAUTH_FAILED;
}
*out_pa_data = make_pa_list("optimistic", 10);
+ if (st->disable_fallback)
+ cb->disable_fallback(context, rock);
return 0;
} else if (reqst->second_round_trip) {
printf("2rt: %.*s\n", pa_data->length, pa_data->contents);
@@ -166,6 +173,8 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata,
indstr = (st->indicators != NULL) ? st->indicators : "";
*out_pa_data = make_pa_list(indstr, strlen(indstr));
+ if (st->disable_fallback)
+ cb->disable_fallback(context, rock);
return 0;
}
@@ -212,6 +221,8 @@ test_gic_opt(krb5_context kcontext, krb5_clpreauth_moddata moddata,
st->fail_2rt = TRUE;
} else if (strcmp(attr, "fail_tryagain") == 0) {
st->fail_tryagain = TRUE;
+ } else if (strcmp(attr, "disable_fallback") == 0) {
+ st->disable_fallback = TRUE;
}
return 0;
}
diff --git a/src/tests/t_preauth.py b/src/tests/t_preauth.py
index efb3ea20d..32e35b08b 100644
--- a/src/tests/t_preauth.py
+++ b/src/tests/t_preauth.py
@@ -37,8 +37,8 @@ expected_trace = ('Attempting optimistic preauth',
realm.run(['./icred', '-o', '-123', realm.user_princ, password('user')],
expected_trace=expected_trace)
-# Test optimistic preauth failing on client, followed by successful
-# preauth using the same module.
+# Test optimistic preauth failing on client, falling back to encrypted
+# timestamp.
msgs = ('Attempting optimistic preauth',
'Processing preauth types: -123',
'/induced optimistic fail',
@@ -46,15 +46,15 @@ msgs = ('Attempting optimistic preauth',
'/Additional pre-authentication required',
'Preauthenticating using KDC method data',
'Processing preauth types:',
- 'Preauth module test (-123) (real) returned: 0/Success',
- 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ 'Encrypted timestamp (for ',
+ 'module encrypted_timestamp (2) (real) returned: 0/Success',
+ 'preauth for next request: PA-FX-COOKIE (133), PA-ENC-TIMESTAMP (2)',
'Decrypted AS reply')
realm.run(['./icred', '-o', '-123', '-X', 'fail_optimistic', realm.user_princ,
- password('user')], expected_msg='testval',
- expected_trace=msgs)
+ password('user')], expected_trace=msgs)
-# Test optimistic preauth failing on KDC, followed by successful preauth
-# using the same module.
+# Test optimistic preauth failing on KDC, falling back to encrypted
+# timestamp.
realm.run([kadminl, 'setstr', realm.user_princ, 'failopt', 'yes'])
msgs = ('Attempting optimistic preauth',
'Processing preauth types: -123',
@@ -63,11 +63,24 @@ msgs = ('Attempting optimistic preauth',
'/Preauthentication failed',
'Preauthenticating using KDC method data',
'Processing preauth types:',
- 'Preauth module test (-123) (real) returned: 0/Success',
- 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ 'Encrypted timestamp (for ',
+ 'module encrypted_timestamp (2) (real) returned: 0/Success',
+ 'preauth for next request: PA-FX-COOKIE (133), PA-ENC-TIMESTAMP (2)',
'Decrypted AS reply')
realm.run(['./icred', '-o', '-123', realm.user_princ, password('user')],
- expected_msg='testval', expected_trace=msgs)
+ expected_trace=msgs)
+# Leave failopt set for the next test.
+
+# Test optimistic preauth failing on KDC, stopping because the test
+# module disabled fallback.
+msgs = ('Attempting optimistic preauth',
+ 'Processing preauth types: -123',
+ 'Preauth module test (-123) (real) returned: 0/Success',
+ 'Produced preauth for next request: -123',
+ '/Preauthentication failed')
+realm.run(['./icred', '-X', 'disable_fallback', '-o', '-123', realm.user_princ,
+ password('user')], expected_code=1,
+ expected_msg='Preauthentication failed', expected_trace=msgs)
realm.run([kadminl, 'delstr', realm.user_princ, 'failopt'])
# Test KDC_ERR_MORE_PREAUTH_DATA_REQUIRED and secure cookies.
@@ -107,6 +120,23 @@ msgs = ('Sending unauthenticated request',
realm.run(['./icred', '-X', 'fail_2rt', realm.user_princ, password('user')],
expected_msg='2rt: secondtrip', expected_trace=msgs)
+# Test client-side failure after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED,
+# stopping because the test module disabled fallback.
+msgs = ('Sending unauthenticated request',
+ '/Additional pre-authentication required',
+ 'Preauthenticating using KDC method data',
+ 'Processing preauth types:',
+ 'Preauth module test (-123) (real) returned: 0/Success',
+ 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ '/More preauthentication data is required',
+ 'Continuing preauth mech -123',
+ 'Processing preauth types: -123, PA-FX-COOKIE (133)',
+ '/induced 2rt fail')
+realm.run(['./icred', '-X', 'fail_2rt', '-X', 'disable_fallback',
+ realm.user_princ, password('user')], expected_code=1,
+ expected_msg='Pre-authentication failed: induced 2rt fail',
+ expected_trace=msgs)
+
# Test KDC-side failure after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED,
# falling back to encrypted timestamp.
realm.run([kadminl, 'setstr', realm.user_princ, 'fail2rt', 'yes'])
@@ -130,6 +160,25 @@ msgs = ('Sending unauthenticated request',
'Decrypted AS reply')
realm.run(['./icred', realm.user_princ, password('user')],
expected_msg='2rt: secondtrip', expected_trace=msgs)
+# Leave fail2rt set for the next test.
+
+# Test KDC-side failure after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED,
+# stopping because the test module disabled fallback.
+msgs = ('Sending unauthenticated request',
+ '/Additional pre-authentication required',
+ 'Preauthenticating using KDC method data',
+ 'Processing preauth types:',
+ 'Preauth module test (-123) (real) returned: 0/Success',
+ 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ '/More preauthentication data is required',
+ 'Continuing preauth mech -123',
+ 'Processing preauth types: -123, PA-FX-COOKIE (133)',
+ 'Preauth module test (-123) (real) returned: 0/Success',
+ 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ '/Preauthentication failed')
+realm.run(['./icred', '-X', 'disable_fallback',
+ realm.user_princ, password('user')], expected_code=1,
+ expected_msg='Preauthentication failed', expected_trace=msgs)
realm.run([kadminl, 'delstr', realm.user_princ, 'fail2rt'])
# Test tryagain flow by inducing a KDC_ERR_ENCTYPE_NOSUPP error on the KDC.
@@ -170,6 +219,23 @@ msgs = ('Sending unauthenticated request',
realm.run(['./icred', '-X', 'fail_tryagain', realm.user_princ,
password('user')], expected_trace=msgs)
+# Test a client-side tryagain failure, stopping because the test
+# module disabled fallback.
+msgs = ('Sending unauthenticated request',
+ '/Additional pre-authentication required',
+ 'Preauthenticating using KDC method data',
+ 'Processing preauth types:',
+ 'Preauth module test (-123) (real) returned: 0/Success',
+ 'Produced preauth for next request: PA-FX-COOKIE (133), -123',
+ '/KDC has no support for encryption type',
+ 'Recovering from KDC error 14 using preauth mech -123',
+ 'Preauth tryagain input types (-123): -123, PA-FX-COOKIE (133)',
+ '/induced tryagain fail')
+realm.run(['./icred', '-X', 'fail_tryagain', '-X', 'disable_fallback',
+ realm.user_princ, password('user')], expected_code=1,
+ expected_msg='KDC has no support for encryption type',
+ expected_trace=msgs)
+
# Test that multiple stepwise initial creds operations can be
# performed with the same krb5_context, with proper tracking of
# clpreauth module request handles.
diff --git a/src/tests/t_spake.py b/src/tests/t_spake.py
index a81a238b4..5b47e62d3 100644
--- a/src/tests/t_spake.py
+++ b/src/tests/t_spake.py
@@ -31,9 +31,7 @@ for gnum, gname in groups:
'Decrypted AS reply')
realm.kinit('user', 'pw', expected_trace=msgs)
- # Test an unsuccessful authentication. (The client will try
- # again with encrypted timestamp, which isn't really desired,
- # but check for that as long as it is expected.)
+ # Test an unsuccessful authentication.
msgs = ('/Additional pre-authentication required',
'Selected etype info:',
'Sending SPAKE support message',
@@ -42,9 +40,6 @@ for gnum, gname in groups:
'Continuing preauth mech PA-SPAKE (151)',
'SPAKE challenge received with group ' + str(gnum),
'Sending SPAKE response',
- '/Preauthentication failed',
- 'Encrypted timestamp ',
- 'for next request: PA-FX-COOKIE (133), PA-ENC-TIMESTAMP (2)',
'/Preauthentication failed')
realm.kinit('user', 'wrongpw', expected_code=1, expected_trace=msgs)
@@ -114,8 +109,6 @@ msgs = ('Attempting optimistic preauth',
'for next request: PA-SPAKE (151)',
'/Preauthentication failed',
'Selected etype info:',
- 'SPAKE challenge with group 1 rejected',
- 'spake (151) (real) returned: -1765328360/Preauthentication failed',
'Encrypted timestamp ',
'for next request: PA-FX-COOKIE (133), PA-ENC-TIMESTAMP (2)',
'AS key determined by preauth:',