names to local user names. The tag is the mapping name, and the
value is the corresponding local user name.
+**auto_fast_armor**
+ If this flag is true, initial credential acquisition will acquire
+ an anonymous PKINIT ticket to use as FAST armor before making the
+ real ticket request, unless an armor ccache is already configured
+ (as with kinit's **-T** option). This flag allows
+ preauthentication mechanisms that require FAST, such as OTP, to
+ work without a pre-existing ticket. This flag should only be set
+ for realms known to support anonymous PKINIT. This flag has no
+ effect if the client principal is itself the anonymous principal.
+ The default value is false. New in release 1.23.
+
**default_domain**
This tag specifies the domain used to expand hostnames when
translating Kerberos 4 service principals to Kerberos 5 principals
#define KRB5_CONF_ALLOW_WEAK_CRYPTO "allow_weak_crypto"
#define KRB5_CONF_AUTH_TO_LOCAL "auth_to_local"
#define KRB5_CONF_AUTH_TO_LOCAL_NAMES "auth_to_local_names"
+#define KRB5_CONF_AUTO_FAST_ARMOR "auto_fast_armor"
#define KRB5_CONF_CANONICALIZE "canonicalize"
#define KRB5_CONF_CCACHE_TYPE "ccache_type"
#define KRB5_CONF_CLOCKSKEW "clockskew"
#define TRACE_DNS_URI_SEND(c, domain) \
TRACE(c, "Sending DNS URI query for {str}", domain)
-#define TRACE_FAST_ARMOR_CCACHE(c, ccache_name) \
- TRACE(c, "FAST armor ccache: {str}", ccache_name)
+#define TRACE_FAST_ARMOR_CCACHE(c, ccache) \
+ TRACE(c, "FAST armor ccache: {ccache}", ccache)
#define TRACE_FAST_ARMOR_CCACHE_KEY(c, keyblock) \
TRACE(c, "Armor ccache session key: {keyblock}", keyblock)
#define TRACE_FAST_ARMOR_KEY(c, keyblock) \
#define TRACE_INIT_CREDS(c, princ) \
TRACE(c, "Getting initial credentials for {princ}", princ)
+#define TRACE_INIT_CREDS_AUTO_FAST_ARMOR(c) \
+ TRACE(c, "Acquiring anonymous PKINIT armor ticket for FAST")
#define TRACE_INIT_CREDS_AS_KEY_GAK(c, keyblock) \
TRACE(c, "AS key obtained from gak_fct: {keyblock}", keyblock)
#define TRACE_INIT_CREDS_AS_KEY_PREAUTH(c, keyblock) \
krb5_error_code
krb5int_fast_as_armor(krb5_context context,
struct krb5int_fast_request_state *state,
- krb5_get_init_creds_opt *opt, krb5_kdc_req *request)
+ krb5_get_init_creds_opt *opt,
+ krb5_ccache armor_ccache, krb5_kdc_req *request)
{
krb5_error_code retval = 0;
krb5_ccache ccache = NULL;
krb5_principal target_principal = NULL;
- krb5_data *target_realm;
+ krb5_data *target_realm, config_data = empty_data();
const char *ccname = k5_gic_opt_get_fast_ccache_name(opt);
krb5_flags fast_flags;
krb5_clear_error_message(context);
target_realm = &request->server->realm;
- if (ccname != NULL) {
- TRACE_FAST_ARMOR_CCACHE(context, ccname);
- state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
+
+ if (armor_ccache == NULL) {
+ /* Stop if no armor ccache was provided by the direct caller or the GIC
+ * options. */
+ if (ccname == NULL)
+ return 0;
+ /* Resolve the armor ccache name provided in the GIC options. */
retval = krb5_cc_resolve(context, ccname, &ccache);
- if (retval == 0) {
- retval = krb5int_tgtname(context, target_realm, target_realm,
- &target_principal);
- }
- if (retval == 0) {
- krb5_data config_data;
- config_data.data = NULL;
- retval = krb5_cc_get_config(context, ccache, target_principal,
- KRB5_CC_CONF_FAST_AVAIL, &config_data);
- if ((retval == 0) && config_data.data) {
- TRACE_FAST_CCACHE_CONFIG(context);
- state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
- }
- krb5_free_data_contents(context, &config_data);
- retval = 0;
- }
- fast_flags = k5_gic_opt_get_fast_flags(opt);
- if (fast_flags & KRB5_FAST_REQUIRED) {
- TRACE_FAST_REQUIRED(context);
- state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
- }
- if (retval == 0 && (state->fast_state_flags & KRB5INT_FAST_DO_FAST)) {
- retval = fast_armor_ap_request(context, state, ccache,
- target_principal);
- }
- if (retval != 0) {
- k5_prependmsg(context, retval,
- _("Error constructing AP-REQ armor"));
- }
+ if (retval)
+ goto cleanup;
+ armor_ccache = ccache;
+ }
+
+ TRACE_FAST_ARMOR_CCACHE(context, armor_ccache);
+
+ state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
+ retval = krb5int_tgtname(context, target_realm, target_realm,
+ &target_principal);
+ if (retval)
+ goto cleanup;
+
+ retval = krb5_cc_get_config(context, armor_ccache, target_principal,
+ KRB5_CC_CONF_FAST_AVAIL, &config_data);
+ if (!retval && config_data.data != NULL) {
+ TRACE_FAST_CCACHE_CONFIG(context);
+ state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
}
+
+ fast_flags = k5_gic_opt_get_fast_flags(opt);
+ if (fast_flags & KRB5_FAST_REQUIRED) {
+ TRACE_FAST_REQUIRED(context);
+ state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
+ }
+
+ retval = fast_armor_ap_request(context, state, armor_ccache,
+ target_principal);
+ if (retval)
+ k5_prependmsg(context, retval, _("Error constructing AP-REQ armor"));
+
+cleanup:
if (ccache)
krb5_cc_close(context, ccache);
- if (target_principal)
- krb5_free_principal(context, target_principal);
+ krb5_free_principal(context, target_principal);
+ krb5_free_data_contents(context, &config_data);
return retval;
}
krb5_error_code
krb5int_fast_as_armor(krb5_context context,
struct krb5int_fast_request_state *state,
- krb5_get_init_creds_opt *opt, krb5_kdc_req *request);
+ krb5_get_init_creds_opt *opt,
+ krb5_ccache auto_armor_ccache, krb5_kdc_req *request);
krb5_error_code
krb5int_fast_reply_key(krb5_context context,
krb5_free_data_contents(context, &ctx->salt);
krb5_free_data_contents(context, &ctx->s2kparams);
krb5_free_keyblock_contents(context, &ctx->as_key);
+ krb5_init_creds_free(context, ctx->auto_armor_ctx);
+ krb5_get_init_creds_opt_free(context, ctx->auto_armor_opt);
+ if (ctx->auto_armor_ccache != NULL)
+ krb5_cc_destroy(context, ctx->auto_armor_ccache);
k5_json_release(ctx->cc_config_in);
k5_json_release(ctx->cc_config_out);
free(ctx);
return (ret == 0) ? bval : FALSE;
}
+/* Return true if auto_fast_armor is enabled for realm. */
+static krb5_boolean
+auto_fast_armor_enabled(profile_t profile, const krb5_data *realm)
+{
+ krb5_error_code ret;
+ char *realmstr;
+ int bval;
+
+ realmstr = k5memdup0(realm->data, realm->length, &ret);
+ if (realmstr == NULL)
+ return FALSE;
+ ret = profile_get_boolean(profile, KRB5_CONF_REALMS, realmstr,
+ KRB5_CONF_AUTO_FAST_ARMOR, FALSE, &bval);
+ free(realmstr);
+ return (ret == 0) ? bval : FALSE;
+}
+
+/*
+ * Return true if ctx should first acquire FAST armor using anonymous PKINIT.
+ * This decision is primarily dependent on the auto_fast_armor config option,
+ * but we don't acquire armor if the caller passed in an armor ccache or if the
+ * state machine is already performing an anonymous PKINIT request.
+ */
+static krb5_boolean
+want_auto_armor(krb5_context context, krb5_init_creds_context ctx)
+{
+ if (k5_gic_opt_get_fast_ccache_name(ctx->opt) != NULL)
+ return FALSE;
+ if (krb5_principal_compare_any_realm(context, ctx->request->client,
+ krb5_anonymous_principal()))
+ return FALSE;
+ return auto_fast_armor_enabled(context->profile,
+ &ctx->request->client->realm);
+}
+
+/* Create a memory ccache and nested init_creds context for acquiring FAST amor
+ * via anonymous PKINIT. */
+static krb5_error_code
+begin_auto_armor(krb5_context context, krb5_init_creds_context ctx)
+{
+ krb5_error_code ret;
+ krb5_principal anon_princ = NULL;
+ const krb5_data *realm = &ctx->request->client->realm;
+
+ TRACE_INIT_CREDS_AUTO_FAST_ARMOR(context);
+
+ ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ctx->auto_armor_ccache);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_build_principal_ext(context, &anon_princ,
+ realm->length, realm->data,
+ strlen(KRB5_WELLKNOWN_NAMESTR),
+ KRB5_WELLKNOWN_NAMESTR,
+ strlen(KRB5_ANONYMOUS_PRINCSTR),
+ KRB5_ANONYMOUS_PRINCSTR, 0);
+ if (ret)
+ goto cleanup;
+ anon_princ->type = KRB5_NT_WELLKNOWN;
+
+ ret = krb5_get_init_creds_opt_alloc(context, &ctx->auto_armor_opt);
+ if (ret)
+ goto cleanup;
+ krb5_get_init_creds_opt_set_anonymous(ctx->auto_armor_opt, 1);
+ krb5_get_init_creds_opt_set_tkt_life(ctx->auto_armor_opt, 60 * 60);
+ ret = krb5_get_init_creds_opt_set_out_ccache(context, ctx->auto_armor_opt,
+ ctx->auto_armor_ccache);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_init_creds_init(context, anon_princ, NULL, NULL,
+ ctx->start_time, ctx->auto_armor_opt,
+ &ctx->auto_armor_ctx);
+ if (ret)
+ goto cleanup;
+
+cleanup:
+ krb5_free_principal(context, anon_princ);
+ return ret;
+}
+
/**
* Throw away any pre-authentication realm state and begin with a
* unauthenticated or optimistically authenticated request. If fast_upgrade is
goto cleanup;
code = krb5int_fast_as_armor(context, ctx->fast_state, ctx->opt,
- ctx->request);
+ ctx->auto_armor_ccache, ctx->request);
if (code != 0)
goto cleanup;
/* give the preauth plugins a chance to prep the request body */
ctx->request->client->type = KRB5_NT_WELLKNOWN;
}
+ if (want_auto_armor(context, ctx)) {
+ code = begin_auto_armor(context, ctx);
+ if (code)
+ goto cleanup;
+ }
+
*pctx = ctx;
ctx = NULL;
if (code)
return code;
- if (in->length != 0) {
+ if (ctx->auto_armor_ctx != NULL) {
+ /* Drive the nested context to acquire an anonymous TGT. */
+ code = krb5_init_creds_step(context, ctx->auto_armor_ctx, in, out,
+ realm, flags);
+ if (code || (*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE))
+ return code;
+
+ /* The nested context is complete. Discard it to signal that the outer
+ * state machine should proceed using auto_armor_ccache. */
+ krb5_init_creds_free(context, ctx->auto_armor_ctx);
+ ctx->auto_armor_ctx = NULL;
+
+ /* Begin the actual AS request, asserting that FAST is available. */
+ code = restart_init_creds_loop(context, ctx, TRUE);
+ if (code)
+ return code;
+ } else if (in->length != 0) {
code = init_creds_step_reply(context, ctx, in);
if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
code2 = krb5int_copy_data_contents(context,
krb5_boolean info_pa_permitted;
krb5_boolean restarted;
krb5_boolean encts_disabled;
+ /* Automatic FAST armor state machine and memory cache */
+ krb5_init_creds_context auto_armor_ctx;
+ krb5_get_init_creds_opt *auto_armor_opt;
+ krb5_ccache auto_armor_ccache;
struct krb5_responder_context_st rctx;
krb5_preauthtype current_preauth_type;
krb5_preauthtype selected_preauth_type;
check_lifetime('actx gss_inquire_context', ln[8], 8000 * 86400 + 300)
check_lifetime('actx gss_context_time', ln[9], 8000 * 86400 + 300)
+realm.stop()
+
+# Test auto_fast_armor with IAKERB driving the state machine.
+if pkinit_enabled:
+ mark('IAKERB with auto_fast_armor')
+ afa_conf = {'realms': {'$realm': {'auto_fast_armor': 'true'}}}
+ realm = K5Realm(krb5_conf=afa_conf, get_creds=False, pkinit=True)
+ realm.run([kadminl, 'modprinc', '+preauth', realm.user_princ])
+ realm.addprinc('WELLKNOWN/ANONYMOUS')
+ msgs = ('Acquiring anonymous PKINIT armor ticket for FAST',
+ 'Getting initial credentials for WELLKNOWN/ANONYMOUS',
+ 'Using FAST due to armor ccache negotiation result',
+ 'Preauth module encrypted_challenge (138) (real) returned: 0')
+ realm.run(['./t_iakerb', 'p:' + realm.user_princ, password('user'),
+ 'h:host@' + hostname, 'h:host'], expected_trace=msgs)
+else:
+ print('Skipping IAKERB auto_fast_armor test: PKINIT not built')
+
success('GSSAPI tests')
realm.klist(realm.user_princ)
realm.run([kvno, realm.host_princ])
-# Having tested password preauth, remove the keys for better error
-# reporting.
-realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
-
# Test anonymous PKINIT.
mark('anonymous')
realm.kinit('@%s' % realm.realm, flags=['-n'], expected_code=1,
realm.run([klist, '-C'], expected_msg='start_realm = KRBTEST.COM')
realm.run([kvno, '-S', 'host', hostname])
+# Test auto_fast_armor.
+mark('auto_fast_armor')
+afa_conf = {'realms': {'$realm': {'auto_fast_armor': 'true'}}}
+afa_env = realm.special_env('auto_fast', False, krb5_conf=afa_conf)
+msgs = ('Acquiring anonymous PKINIT armor ticket for FAST',
+ 'Getting initial credentials for WELLKNOWN/ANONYMOUS@%s' % realm.realm,
+ 'Using FAST due to armor ccache negotiation result',
+ 'Preauth module encrypted_challenge (138) (real) returned: 0/Success')
+realm.kinit(realm.user_princ, password=password('user'), env=afa_env,
+ expected_trace=msgs)
+realm.klist(realm.user_princ)
+
+# auto_fast_armor shouldn't trigger for direct use of anonymous PKINIT.
+mark('anonymous (auto_fast_armor=true)')
+out, trace = realm.kinit('@%s' % realm.realm, flags=['-n'], env=afa_env,
+ return_trace=True)
+if 'Acquiring anonymous PKINIT armor ticket for FAST' in trace:
+ fail('auto_fast_armor improperly triggered for anonymous kinit')
+
+# For the remaining tests in this realm, remove the keys on user for
+# better error reporting (by preventing encrypted timestamp fallback).
+realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
+
# Test anonymous kadmin.
mark('anonymous kadmin')
f = open(os.path.join(realm.testdir, 'acl'), 'a')