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
*
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 {
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;
/*
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,
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;
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
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)
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;
#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);
+ }
+}
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()
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')