diff options
-rw-r--r-- | src/include/k5-int.h | 11 | ||||
-rw-r--r-- | src/include/krb5/preauth_plugin.h | 18 | ||||
-rw-r--r-- | src/lib/krb5/krb/get_in_tkt.c | 19 | ||||
-rw-r--r-- | src/lib/krb5/krb/preauth2.c | 23 | ||||
-rw-r--r-- | src/lib/krb5/krb/preauth_ec.c | 5 | ||||
-rw-r--r-- | src/lib/krb5/krb/preauth_encts.c | 11 | ||||
-rw-r--r-- | src/lib/krb5/os/ustime.c | 54 | ||||
-rw-r--r-- | 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') |