From 5f39a4438eafd693a3eb8366bbc3901efe62e538 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Tue, 17 Apr 2012 04:07:34 +0000 Subject: [PATCH] Allow preauth mechs to work with clock skew Add a clpreauth callback which gets the time of day using an offset determined by the preauth-required error, and use it in encrypted timestamp and encrypted challenge. This timestamp is not necessarily authenticated, but the security consequences for those preauth mechs are minor (and can be mitigated by turning off kdc_timesync on clients). Based on a patch from Stef Walter. ticket: 7114 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25808 dc483132-0cff-0310-8789-dd5450dbe970 --- src/include/k5-int.h | 11 +++++++ src/include/krb5/preauth_plugin.h | 18 +++++++++++ src/lib/krb5/krb/get_in_tkt.c | 19 +++++++++++ src/lib/krb5/krb/preauth2.c | 23 +++++++++++-- src/lib/krb5/krb/preauth_ec.c | 5 ++- src/lib/krb5/krb/preauth_encts.c | 11 +++++-- src/lib/krb5/os/ustime.c | 54 +++++++++++++++++++------------ src/tests/t_skew.py | 12 +++++-- 8 files changed, 124 insertions(+), 29 deletions(-) diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 7ef421d2d..752b40efc 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -665,6 +665,12 @@ typedef struct _krb5_os_context { char * default_ccname; } *krb5_os_context; +/* Get the current time of day plus a specified offset. */ +krb5_error_code k5_time_with_offset(krb5_timestamp offset, + krb5_int32 offset_usec, + krb5_timestamp *time_out, + krb5_int32 *usec_out); + /* * Flags for the os_flags field * @@ -753,6 +759,11 @@ struct krb5_clpreauth_rock_st { krb5_principal client; krb5_prompter_fct prompter; void *prompter_data; + + /* Discovered offset of server time during preauth */ + krb5_timestamp pa_offset; + krb5_int32 pa_offset_usec; + enum { NO_OFFSET = 0, UNAUTH_OFFSET, AUTH_OFFSET } pa_offset_state; }; typedef struct _krb5_pa_enc_ts { diff --git a/src/include/krb5/preauth_plugin.h b/src/include/krb5/preauth_plugin.h index f732b947d..72fd92d26 100644 --- a/src/include/krb5/preauth_plugin.h +++ b/src/include/krb5/preauth_plugin.h @@ -176,6 +176,24 @@ typedef struct krb5_clpreauth_callbacks_st { const krb5_keyblock *keyblock); /* End of version 1 clpreauth callbacks. */ + + /* + * Get the current time for use in a preauth response. If + * allow_unauth_time is true and the library has been configured to allow + * it, the current time will be offset using unauthenticated timestamp + * information received from the KDC in the preauth-required error, if one + * has been received. Otherwise, the timestamp in a preauth-required error + * will only be used if it is protected by a FAST channel. Only set + * allow_unauth_time if using an unauthenticated time offset would not + * create a security issue. + */ + krb5_error_code (*get_preauth_time)(krb5_context context, + krb5_clpreauth_rock rock, + krb5_boolean allow_unauth_time, + krb5_timestamp *time_out, + krb5_int32 *usec_out); + + /* End of version 2 clpreauth callbacks (added in 1.11). */ } *krb5_clpreauth_callbacks; /* diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index fc8df83df..aaabc4e21 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -1281,6 +1281,23 @@ check_reply_enctype(krb5_init_creds_context ctx) return KRB5_CONFIG_ETYPE_NOSUPP; } +/* Note the difference between the KDC's time, as reported to us in a + * preauth-required error, and the current time. */ +static void +note_req_timestamp(krb5_context kcontext, krb5_clpreauth_rock rock, + krb5_timestamp kdc_time, krb5_int32 kdc_usec) +{ + krb5_timestamp now; + krb5_int32 usec; + + if (k5_time_with_offset(0, 0, &now, &usec) != 0) + return; + rock->pa_offset = kdc_time - now; + rock->pa_offset_usec = kdc_usec - usec; + rock->pa_offset_state = (rock->fast_state->armor_key != NULL) ? + AUTH_OFFSET : UNAUTH_OFFSET; +} + static krb5_error_code init_creds_step_reply(krb5_context context, krb5_init_creds_context ctx, @@ -1328,6 +1345,8 @@ init_creds_step_reply(krb5_context context, krb5_free_pa_data(context, ctx->preauth_to_use); ctx->preauth_to_use = ctx->err_padata; ctx->err_padata = NULL; + note_req_timestamp(context, &ctx->preauth_rock, + ctx->err_reply->stime, ctx->err_reply->susec); /* this will trigger a new call to krb5_do_preauth() */ krb5_free_error(context, ctx->err_reply); ctx->err_reply = NULL; diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index 0c8ead5fe..06a135a36 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -412,12 +412,31 @@ set_as_key(krb5_context context, krb5_clpreauth_rock rock, return krb5_copy_keyblock_contents(context, keyblock, rock->as_key); } +static krb5_error_code +get_preauth_time(krb5_context context, krb5_clpreauth_rock rock, + krb5_boolean allow_unauth_time, krb5_timestamp *time_out, + krb5_int32 *usec_out) +{ + if (rock->pa_offset_state != NO_OFFSET && + (allow_unauth_time || rock->pa_offset_state == AUTH_OFFSET) && + (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME)) { + /* Use the offset we got from the preauth-required error. */ + return k5_time_with_offset(rock->pa_offset, rock->pa_offset_usec, + time_out, usec_out); + + } else { + /* Use the time offset from the context, or no offset. */ + return krb5_us_timeofday(context, time_out, usec_out); + } +} + static struct krb5_clpreauth_callbacks_st callbacks = { - 1, + 2, get_etype, fast_armor, get_as_key, - set_as_key + set_as_key, + get_preauth_time }; /* 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 7e7565b6f..48a4a17bf 100644 --- a/src/lib/krb5/krb/preauth_ec.c +++ b/src/lib/krb5/krb/preauth_ec.c @@ -92,7 +92,10 @@ ec_process(krb5_context context, krb5_clpreauth_moddata moddata, krb5_data *encoded_ts = NULL; krb5_pa_enc_ts ts; enc.ciphertext.data = NULL; - retval = krb5_us_timeofday(context, &ts.patimestamp, &ts.pausec); + /* Use the timestamp from the preauth-required error if possible. + * This time should always be secured by the FAST channel. */ + retval = cb->get_preauth_time(context, rock, FALSE, &ts.patimestamp, + &ts.pausec); if (retval == 0) retval = encode_krb5_pa_enc_ts(&ts, &encoded_ts); if (retval == 0) diff --git a/src/lib/krb5/krb/preauth_encts.c b/src/lib/krb5/krb/preauth_encts.c index 63e4259eb..559c6700f 100644 --- a/src/lib/krb5/krb/preauth_encts.c +++ b/src/lib/krb5/krb/preauth_encts.c @@ -58,8 +58,15 @@ encts_process(krb5_context context, krb5_clpreauth_moddata moddata, goto cleanup; TRACE_PREAUTH_ENC_TS_KEY_GAK(context, as_key); - /* now get the time of day, and encrypt it accordingly */ - ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec); + /* + * Try and use the timestamp of the preauth request, even if it's + * unauthenticated. We could be fooled into making a preauth response for + * a future time, but that has no security consequences other than the + * KDC's audit logs. If kdc_timesync is not configured, then this will + * just use local time. + */ + ret = cb->get_preauth_time(context, rock, TRUE, &pa_enc.patimestamp, + &pa_enc.pausec); if (ret) goto cleanup; diff --git a/src/lib/krb5/os/ustime.c b/src/lib/krb5/os/ustime.c index be94a8218..90fa4a6e2 100644 --- a/src/lib/krb5/os/ustime.c +++ b/src/lib/krb5/os/ustime.c @@ -35,34 +35,46 @@ #include "k5-int.h" -krb5_error_code KRB5_CALLCONV -krb5_us_timeofday(krb5_context context, krb5_timestamp *seconds, krb5_int32 *microseconds) +krb5_error_code +k5_time_with_offset(krb5_timestamp offset, krb5_int32 offset_usec, + krb5_timestamp *time_out, krb5_int32 *usec_out) { - krb5_os_context os_ctx = &context->os_context; krb5_int32 sec, usec; krb5_error_code retval; - if (os_ctx->os_flags & KRB5_OS_TOFFSET_TIME) { - *seconds = os_ctx->time_offset; - *microseconds = os_ctx->usec_offset; - return 0; - } retval = krb5_crypto_us_timeofday(&sec, &usec); if (retval) return retval; - if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) { - usec += os_ctx->usec_offset; - if (usec > 1000000) { - usec -= 1000000; - sec++; - } - if (usec < 0) { - usec += 1000000; - sec--; - } - sec += os_ctx->time_offset; + usec += offset_usec; + if (usec > 1000000) { + usec -= 1000000; + sec++; + } + if (usec < 0) { + usec += 1000000; + sec--; } - *seconds = sec; - *microseconds = usec; + sec += offset; + + *time_out = sec; + *usec_out = usec; return 0; } + +krb5_error_code KRB5_CALLCONV +krb5_us_timeofday(krb5_context context, krb5_timestamp *seconds, + krb5_int32 *microseconds) +{ + krb5_os_context os_ctx = &context->os_context; + + if (os_ctx->os_flags & KRB5_OS_TOFFSET_TIME) { + *seconds = os_ctx->time_offset; + *microseconds = os_ctx->usec_offset; + return 0; + } else if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) { + return k5_time_with_offset(os_ctx->time_offset, os_ctx->usec_offset, + seconds, microseconds); + } else { + return krb5_crypto_us_timeofday(seconds, microseconds); + } +} diff --git a/src/tests/t_skew.py b/src/tests/t_skew.py index f00c2f920..f831035ac 100644 --- a/src/tests/t_skew.py +++ b/src/tests/t_skew.py @@ -13,9 +13,13 @@ realm.kinit(realm.user_princ, password('user'), flags=['-T', realm.ccache]) realm.run_as_client([kvno, realm.host_princ]) realm.run_as_client([kdestroy]) -# kinit (with preauth) should fail. +# kinit (with preauth) should work, with or without FAST. realm.run_kadminl('modprinc +requires_preauth user') -realm.kinit(realm.user_princ, password('user'), expected_code=1) +realm.kinit(realm.user_princ, password('user')) +realm.run_as_client([kvno, realm.host_princ]) +realm.kinit(realm.user_princ, password('user'), flags=['-T', realm.ccache]) +realm.run_as_client([kvno, realm.host_princ]) +realm.run_as_client([kdestroy]) realm.stop() @@ -31,8 +35,10 @@ realm.run_as_client([kvno, realm.host_princ], expected_code=1) realm.kinit(realm.user_princ, password('user'), flags=['-T', realm.ccache], expected_code=1) -# kinit (with preauth) should fail. +# kinit (with preauth) should fail, with or without FAST. realm.run_kadminl('modprinc +requires_preauth user') realm.kinit(realm.user_princ, password('user'), expected_code=1) +realm.kinit(realm.user_princ, password('user'), flags=['-T', realm.ccache], + expected_code=1) success('Clock skew tests') -- 2.26.2