From bb5552ece2a351dc3ccab52cceea1eaffeacd768 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Mon, 14 Dec 2020 13:16:17 -0500 Subject: [PATCH] Add support for start_realm cache config When making TGS requests, if start_realm is set in the cache, use the named realm to look up the initial TGT for referral or cross-realm requests. (Also correct a comment in struct _tkt_creds_context: the ccache field is an owner pointer, not an alias.) Add an internal API k5_cc_store_primary_cred(), which sets start_realm if the cred being stored is a TGT for a realm other than the client realm. Use this API when acquiring initial tickets with a caller-specified output ccache, when renewing or validating tickets with kinit, when accepting a delegated credential in a GSS context, and when storing a single cred with kvno --out-cache. ticket: 8332 tags: pullup target_version: 1.19 (cherry picked from commit 0d56740ab9fcc40dc7f46c6fbebdf8f1214f9d96) [rharwood@redhat.com: backport around spelling and canonicalization fallback] --- doc/formats/ccache_file_format.rst | 6 +++++ src/clients/kinit/kinit.c | 2 +- src/clients/kvno/kvno.c | 5 ++++- src/include/k5-int.h | 4 ++++ src/lib/gssapi/krb5/accept_sec_context.c | 2 +- src/lib/krb5/ccache/ccfns.c | 20 +++++++++++++++++ src/lib/krb5/krb/get_creds.c | 28 ++++++++++++++++++------ src/lib/krb5/krb/get_in_tkt.c | 2 +- src/lib/krb5/libkrb5.exports | 1 + src/lib/krb5_32.def | 3 +++ src/tests/t_crossrealm.py | 8 +++++++ src/tests/t_pkinit.py | 3 +++ 12 files changed, 73 insertions(+), 11 deletions(-) diff --git a/doc/formats/ccache_file_format.rst b/doc/formats/ccache_file_format.rst index 6349e0d29..6138c1b58 100644 --- a/doc/formats/ccache_file_format.rst +++ b/doc/formats/ccache_file_format.rst @@ -174,3 +174,9 @@ refresh_time decimal representation of a timestamp at which the GSS mechanism should attempt to refresh the credential cache from the client keytab. + +start_realm + This key indicates the realm of the ticket-granting ticket to be + used for TGS requests, when making a referrals request or + beginning a cross-realm request. If it is not present, the client + realm is used. diff --git a/src/clients/kinit/kinit.c b/src/clients/kinit/kinit.c index 3fdae2878..e5ebeb895 100644 --- a/src/clients/kinit/kinit.c +++ b/src/clients/kinit/kinit.c @@ -828,7 +828,7 @@ k5_kinit(struct k_opts *opts, struct k5_data *k5) if (opts->verbose) fprintf(stderr, _("Initialized cache\n")); - ret = krb5_cc_store_cred(k5->ctx, k5->out_cc, &my_creds); + ret = k5_cc_store_primary_cred(k5->ctx, k5->out_cc, &my_creds); if (ret) { com_err(progname, ret, _("while storing credentials")); goto cleanup; diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c index c5f6bf700..f83c68a99 100644 --- a/src/clients/kvno/kvno.c +++ b/src/clients/kvno/kvno.c @@ -561,7 +561,10 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, } initialized = 1; } - ret = krb5_cc_store_cred(context, out_ccache, creds); + if (count == 1) + ret = k5_cc_store_primary_cred(context, out_ccache, creds); + else + ret = krb5_cc_store_cred(context, out_ccache, creds); if (ret) { com_err(prog, ret, _("while storing creds in output ccache")); exit(1); diff --git a/src/include/k5-int.h b/src/include/k5-int.h index eb18a4cd6..912aaedac 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -307,6 +307,7 @@ typedef unsigned char u_char; #define KRB5_CC_CONF_PA_TYPE "pa_type" #define KRB5_CC_CONF_PROXY_IMPERSONATOR "proxy_impersonator" #define KRB5_CC_CONF_REFRESH_TIME "refresh_time" +#define KRB5_CC_CONF_START_REALM "start_realm" /* Error codes used in KRB_ERROR protocol messages. Return values of library routines are based on a different error table @@ -1910,6 +1911,9 @@ krb5_ser_unpack_bytes(krb5_octet *, size_t, krb5_octet **, size_t *); krb5_error_code KRB5_CALLCONV krb5int_cc_default(krb5_context, krb5_ccache *); +krb5_error_code +k5_cc_store_primary_cred(krb5_context, krb5_ccache, krb5_creds *); + /* Fill in the buffer with random alpha-numeric data. */ krb5_error_code krb5int_random_string(krb5_context, char *string, unsigned int length); diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c index 3d5b84b15..abccb5d11 100644 --- a/src/lib/gssapi/krb5/accept_sec_context.c +++ b/src/lib/gssapi/krb5/accept_sec_context.c @@ -216,7 +216,7 @@ rd_and_store_for_creds(context, auth_context, inbuf, out_cred) if ((retval = krb5_cc_initialize(context, ccache, creds[0]->client))) goto cleanup; - if ((retval = krb5_cc_store_cred(context, ccache, creds[0]))) + if ((retval = k5_cc_store_primary_cred(context, ccache, creds[0]))) goto cleanup; /* generate a delegated credential handle */ diff --git a/src/lib/krb5/ccache/ccfns.c b/src/lib/krb5/ccache/ccfns.c index 62a6983d8..23edc2578 100644 --- a/src/lib/krb5/ccache/ccfns.c +++ b/src/lib/krb5/ccache/ccfns.c @@ -297,3 +297,23 @@ krb5_cc_switch(krb5_context context, krb5_ccache cache) return 0; return cache->ops->switch_to(context, cache); } + +krb5_error_code +k5_cc_store_primary_cred(krb5_context context, krb5_ccache cache, + krb5_creds *creds) +{ + krb5_error_code ret; + + /* Write a start realm if we're writing a TGT and the client realm isn't + * the same as the TGS realm. */ + if (IS_TGS_PRINC(creds->server) && + !data_eq(creds->client->realm, creds->server->data[1])) { + ret = krb5_cc_set_config(context, cache, NULL, + KRB5_CC_CONF_START_REALM, + &creds->server->data[1]); + if (ret) + return ret; + } + + return krb5_cc_store_cred(context, cache, creds); +} diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c index e0a3b5cd8..b40f705fc 100644 --- a/src/lib/krb5/krb/get_creds.c +++ b/src/lib/krb5/krb/get_creds.c @@ -149,7 +149,8 @@ struct _krb5_tkt_creds_context { krb5_principal client; /* Caller-requested client principal (alias) */ krb5_principal server; /* Server principal (alias) */ krb5_principal req_server; /* Caller-requested server principal */ - krb5_ccache ccache; /* Caller-provided ccache (alias) */ + krb5_ccache ccache; /* Caller-provided ccache */ + krb5_data start_realm; /* Realm of starting TGT in ccache */ krb5_flags req_options; /* Caller-requested KRB5_GC_* options */ krb5_flags req_kdcopt; /* Caller-requested options as KDC options */ krb5_authdata **authdata; /* Caller-requested authdata */ @@ -783,7 +784,7 @@ get_cached_local_tgt(krb5_context context, krb5_tkt_creds_context ctx, return code; /* Construct the principal name. */ - code = krb5int_tgtname(context, &ctx->client->realm, &ctx->client->realm, + code = krb5int_tgtname(context, &ctx->start_realm, &ctx->start_realm, &tgtname); if (code != 0) return code; @@ -821,7 +822,7 @@ init_realm_path(krb5_context context, krb5_tkt_creds_context ctx) size_t nrealms; /* Get the client realm path and count its length. */ - code = k5_client_realm_path(context, &ctx->client->realm, + code = k5_client_realm_path(context, &ctx->start_realm, &ctx->server->realm, &realm_path); if (code != 0) return code; @@ -933,7 +934,7 @@ step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx) ctx->cur_realm = path_realm; ctx->next_realm = ctx->last_realm; } - } else if (data_eq(*tgt_realm, ctx->client->realm)) { + } else if (data_eq(*tgt_realm, ctx->start_realm)) { /* We were referred back to the local realm, which is bad. */ return KRB5_KDCREP_MODIFIED; } else { @@ -963,7 +964,7 @@ begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx) ctx->state = STATE_GET_TGT; - is_local_service = data_eq(ctx->client->realm, ctx->server->realm); + is_local_service = data_eq(ctx->start_realm, ctx->server->realm); if (!is_local_service) { /* See if we have a cached TGT for the server realm. */ code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt); @@ -1048,10 +1049,10 @@ begin(krb5_context context, krb5_tkt_creds_context ctx) if (code != 0 || ctx->state == STATE_COMPLETE) return code; - /* If the server realm is unspecified, start with the client realm. */ + /* If the server realm is unspecified, start with the TGT realm. */ if (krb5_is_referral_realm(&ctx->server->realm)) { krb5_free_data_contents(context, &ctx->server->realm); - code = krb5int_copy_data_contents(context, &ctx->client->realm, + code = krb5int_copy_data_contents(context, &ctx->start_realm, &ctx->server->realm); TRACE_TKT_CREDS_REFERRAL_REALM(context, ctx->server); if (code != 0) @@ -1100,6 +1101,18 @@ krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache, code = krb5_cc_dup(context, ccache, &ctx->ccache); if (code != 0) goto cleanup; + + /* Get the start realm from the cache config, defaulting to the client + * realm. */ + code = krb5_cc_get_config(context, ccache, NULL, "start_realm", + &ctx->start_realm); + if (code != 0) { + code = krb5int_copy_data_contents(context, &ctx->client->realm, + &ctx->start_realm); + if (code != 0) + goto cleanup; + } + code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata); if (code != 0) goto cleanup; @@ -1139,6 +1152,7 @@ krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx) krb5int_fast_free_state(context, ctx->fast_state); krb5_free_creds(context, ctx->in_creds); krb5_cc_close(context, ctx->ccache); + krb5_free_data_contents(context, &ctx->start_realm); krb5_free_principal(context, ctx->req_server); krb5_free_authdata(context, ctx->authdata); krb5_free_creds(context, ctx->cur_tgt); diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index cc0f70e83..f5dd7518b 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -1779,7 +1779,7 @@ init_creds_step_reply(krb5_context context, code = krb5_cc_initialize(context, out_ccache, ctx->cred.client); if (code != 0) goto cc_cleanup; - code = krb5_cc_store_cred(context, out_ccache, &ctx->cred); + code = k5_cc_store_primary_cred(context, out_ccache, &ctx->cred); if (code != 0) goto cc_cleanup; if (fast_avail) { diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 5aba29ee4..cab5b3b17 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -125,6 +125,7 @@ k5_add_pa_data_from_data k5_alloc_pa_data k5_authind_decode k5_build_conf_principals +k5_cc_store_primary_cred k5_ccselect_free_context k5_change_error_message_code k5_etypes_contains diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index a0734c729..de5823c17 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -499,3 +499,6 @@ EXPORTS k5_size_context @467 ; PRIVATE GSSAPI k5_size_keyblock @468 ; PRIVATE GSSAPI k5_size_principal @469 ; PRIVATE GSSAPI + +; new in 1.19 + k5_cc_store_primary_cred @470 ; PRIVATE diff --git a/src/tests/t_crossrealm.py b/src/tests/t_crossrealm.py index fa7fd2604..28b397cfb 100755 --- a/src/tests/t_crossrealm.py +++ b/src/tests/t_crossrealm.py @@ -77,6 +77,14 @@ r1, r2, r3 = cross_realms(3, xtgts=((0,1), (1,2)), {'realm': 'B.X'})) test_kvno(r1, r3.host_princ, 'KDC domain walk') check_klist(r1, (tgt(r1, r1), r3.host_princ)) + +# Test start_realm in this setup. +r1.run([kvno, '--out-cache', r1.ccache, r2.krbtgt_princ]) +r1.run([klist, '-C'], expected_msg='config: start_realm = X') +msgs = ('Requesting TGT krbtgt/B.X@X using TGT krbtgt/X@X', + 'Received TGT for service realm: krbtgt/B.X@X') +r1.run([kvno, r3.host_princ], expected_trace=msgs) + stop(r1, r2, r3) # Test client capaths. The client in A will ask for a cross TGT to D, diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py index ecd450e8a..f224383c8 100755 --- a/src/tests/t_pkinit.py +++ b/src/tests/t_pkinit.py @@ -130,6 +130,9 @@ realm.run([kvno, realm.host_princ]) out = realm.run(['./adata', realm.host_princ]) if '97:' in out: fail('auth indicators seen in anonymous PKINIT ticket') +# Verify start_realm setting and test referrals TGS request. +realm.run([klist, '-C'], expected_msg='start_realm = KRBTEST.COM') +realm.run([kvno, '-S', 'host', hostname]) # Test anonymous kadmin. mark('anonymous kadmin')