From a684e001ba77132cd5197602118da64245c4e77e Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Sun, 21 Jun 2020 16:34:11 -0500 Subject: [PATCH] gsskrb5: Check dst-TGT pokicy at store time Our initiator supports configuration-driven delegation of destination TGTs. This commit adds acceptor-side handling of destination TGT policy to reject storing of non-destination TGTs when destination TGTs are desired. Currently we use the same appdefault for this. Background: A root TGT is one of the form krbtgt/REALM@SAME-REALM. A destination TGT is a root TGT for the same realm as the acceptor service's realm. Normally clients delegate a root TGT for the client's realm. In some deployments clients may want to delegate destination TGTs as a form of constrained delegation: so that the destination service cannot use the delegated credential to impersonate the client principal to services in its home realm (due to KDC lineage/transit checks). In those deployments there may not even be a route back to the KDCs of the client's realm, and attempting to use a non-destination TGT might even lead to timeouts. --- lib/gssapi/krb5/accept_sec_context.c | 44 ++++++++++++++++++++---- lib/gssapi/krb5/acquire_cred.c | 1 + lib/gssapi/krb5/add_cred.c | 1 + lib/gssapi/krb5/copy_ccache.c | 1 + lib/gssapi/krb5/creds.c | 1 + lib/gssapi/krb5/duplicate_cred.c | 10 +++++- lib/gssapi/krb5/gsskrb5_locl.h | 1 + lib/gssapi/krb5/release_cred.c | 1 + lib/gssapi/krb5/store_cred.c | 51 ++++++++++++++++++++++++++++ 9 files changed, 104 insertions(+), 7 deletions(-) diff --git a/lib/gssapi/krb5/accept_sec_context.c b/lib/gssapi/krb5/accept_sec_context.c index b5aa5f1f8..6cb199e87 100644 --- a/lib/gssapi/krb5/accept_sec_context.c +++ b/lib/gssapi/krb5/accept_sec_context.c @@ -149,12 +149,10 @@ _gsskrb5i_is_cfx(krb5_context context, gsskrb5_ctx ctx, int acceptor) static OM_uint32 -gsskrb5_accept_delegated_token -(OM_uint32 * minor_status, - gsskrb5_ctx ctx, - krb5_context context, - gss_cred_id_t * delegated_cred_handle - ) +gsskrb5_accept_delegated_token(OM_uint32 *minor_status, + gsskrb5_ctx ctx, + krb5_context context, + gss_cred_id_t *delegated_cred_handle) { krb5_ccache ccache = NULL; krb5_error_code kret; @@ -212,6 +210,40 @@ gsskrb5_accept_delegated_token handle = (gsskrb5_cred) *delegated_cred_handle; handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; + + /* + * A root TGT is one of the form krbtgt/REALM@SAME-REALM. + * + * A destination TGT is a root TGT for the same realm as the acceptor + * service's realm. + * + * Normally clients delegate a root TGT for the client's realm. + * + * In some deployments clients may want to delegate destination TGTs as + * a form of constrained delegation: so that the destination service + * cannot use the delegated credential to impersonate the client + * principal to services in its home realm (due to KDC lineage/transit + * checks). In those deployments there may not even be a route back to + * the KDCs of the client's realm, and attempting to use a + * non-destination TGT might even lead to timeouts. + * + * We could simply pretend not to have obtained a credential, except + * that a) we don't (yet) have an app name here for the appdefault we + * need to check, b) the application really wants to be able to log a + * message about the delegated credential being no good. + * + * Thus we leave it to _gsskrb5_store_cred_into2() to decide what to do + * with non-destination TGTs. To do that, it needs the realm of the + * acceptor service, which we record here. + */ + handle->destination_realm = + strdup(krb5_principal_get_realm(context, ctx->target)); + if (handle->destination_realm == NULL) { + _gsskrb5_release_cred(minor_status, delegated_cred_handle); + *minor_status = krb5_enomem(context); + ret = GSS_S_FAILURE; + goto out; + } } out: diff --git a/lib/gssapi/krb5/acquire_cred.c b/lib/gssapi/krb5/acquire_cred.c index ba5d48fad..688f7f2ef 100644 --- a/lib/gssapi/krb5/acquire_cred.c +++ b/lib/gssapi/krb5/acquire_cred.c @@ -603,6 +603,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from return GSS_S_FAILURE; } + handle->destination_realm = NULL; HEIMDAL_MUTEX_init(&handle->cred_id_mutex); if (desired_name != GSS_C_NO_NAME) { diff --git a/lib/gssapi/krb5/add_cred.c b/lib/gssapi/krb5/add_cred.c index 8ec316948..0bc61a7be 100644 --- a/lib/gssapi/krb5/add_cred.c +++ b/lib/gssapi/krb5/add_cred.c @@ -138,6 +138,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_add_cred_from ( handle->usage = cred_usage; handle->endtime = cred->endtime; handle->principal = NULL; + handle->destination_realm = NULL; handle->keytab = NULL; handle->ccache = NULL; handle->mechanisms = NULL; diff --git a/lib/gssapi/krb5/copy_ccache.c b/lib/gssapi/krb5/copy_ccache.c index f32eec701..182421581 100644 --- a/lib/gssapi/krb5/copy_ccache.c +++ b/lib/gssapi/krb5/copy_ccache.c @@ -96,6 +96,7 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status, HEIMDAL_MUTEX_init(&handle->cred_id_mutex); handle->usage = 0; + handle->destination_realm = NULL; if (*id) { time_t now; diff --git a/lib/gssapi/krb5/creds.c b/lib/gssapi/krb5/creds.c index 1cc3ac848..c0d29828f 100644 --- a/lib/gssapi/krb5/creds.c +++ b/lib/gssapi/krb5/creds.c @@ -268,6 +268,7 @@ _gsskrb5_import_cred(OM_uint32 * minor_status, } handle->usage = GSS_C_INITIATE; + handle->destination_realm = NULL; krb5_cc_get_principal(context, id, &handle->principal); handle->ccache = id; handle->cred_flags = flags; diff --git a/lib/gssapi/krb5/duplicate_cred.c b/lib/gssapi/krb5/duplicate_cred.c index 8d07c51b8..f5b34c514 100644 --- a/lib/gssapi/krb5/duplicate_cred.c +++ b/lib/gssapi/krb5/duplicate_cred.c @@ -66,7 +66,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred ( dup = calloc(1, sizeof(*dup)); if (dup == NULL) { - *minor_status = ENOMEM; + *minor_status = krb5_enomem(context); return (GSS_S_FAILURE); } @@ -74,6 +74,14 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_duplicate_cred ( cred = (gsskrb5_cred)input_cred_handle; HEIMDAL_MUTEX_lock(&cred->cred_id_mutex); + + dup->destination_realm = NULL; + if (cred->destination_realm && + (dup->destination_realm = strdup(cred->destination_realm)) == NULL) { + *minor_status = krb5_enomem(context); + free(dup); + return (GSS_S_FAILURE); + } dup->usage = cred->usage; dup->endtime = cred->endtime; dup->principal = NULL; diff --git a/lib/gssapi/krb5/gsskrb5_locl.h b/lib/gssapi/krb5/gsskrb5_locl.h index a4a737f7f..2d53f055f 100644 --- a/lib/gssapi/krb5/gsskrb5_locl.h +++ b/lib/gssapi/krb5/gsskrb5_locl.h @@ -92,6 +92,7 @@ typedef struct gsskrb5_ctx { typedef struct { krb5_principal principal; + char *destination_realm; /* Realm of acceptor service, if delegated */ int cred_flags; #define GSS_CF_DESTROY_CRED_ON_RELEASE 1 #define GSS_CF_NO_CI_FLAGS 2 diff --git a/lib/gssapi/krb5/release_cred.c b/lib/gssapi/krb5/release_cred.c index 105a7a6eb..e1bb87013 100644 --- a/lib/gssapi/krb5/release_cred.c +++ b/lib/gssapi/krb5/release_cred.c @@ -54,6 +54,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_release_cred HEIMDAL_MUTEX_lock(&cred->cred_id_mutex); + free(cred->destination_realm); if (cred->principal != NULL) krb5_free_principal(context, cred->principal); if (cred->keytab != NULL) diff --git a/lib/gssapi/krb5/store_cred.c b/lib/gssapi/krb5/store_cred.c index b2666c74e..311686dc1 100644 --- a/lib/gssapi/krb5/store_cred.c +++ b/lib/gssapi/krb5/store_cred.c @@ -119,6 +119,49 @@ principal_is_best_for_user(krb5_context context, return ret; } +static krb5_error_code +check_destination_tgt_policy(krb5_context context, + const char *appname, + gsskrb5_cred input_cred) +{ + krb5_error_code ret; + krb5_boolean want_dst_tgt = 0; + krb5_data v; + + if (input_cred->destination_realm == NULL) + /* + * Not a delegated credential, so we can't check the destination TGT + * policy for the realm of the service -- we don't know the realm of + * the service. + */ + return 0; + + krb5_appdefault_boolean(context, appname, input_cred->destination_realm, + "require_delegate_destination_tgt", FALSE, + &want_dst_tgt); + if (!want_dst_tgt) + return 0; + + krb5_data_zero(&v); + ret = krb5_cc_get_config(context, input_cred->ccache, NULL, + "start_realm", &v); + if (ret == 0 && + v.length != strlen(input_cred->destination_realm)) + ret = KRB5_CC_NOTFOUND; + if (ret == 0 && + strncmp(input_cred->destination_realm, v.data, v.length) != 0) + ret = KRB5_CC_NOTFOUND; + if (ret) + krb5_set_error_message(context, ret, + "Delegated TGT is not a destination TGT for " + "realm \"%s\" but for \"%.*s\"", + input_cred->destination_realm, + (int)(v.length ? v.length : sizeof("") - 1), + v.data ? (const char *)v.data : ""); + krb5_data_free(&v); + return ret; +} + OM_uint32 GSSAPI_CALLCONV _gsskrb5_store_cred_into2(OM_uint32 *minor_status, gss_const_cred_id_t input_cred_handle, @@ -215,6 +258,14 @@ _gsskrb5_store_cred_into2(OM_uint32 *minor_status, return GSS_S_NO_CRED; } + ret = check_destination_tgt_policy(context, cs_app_name, input_cred); + if (ret) { + HEIMDAL_MUTEX_unlock(&input_cred->cred_id_mutex); + *minor_status = ret; + free(ccache_name); + return GSS_S_NO_CRED; + } + /* * Find an appropriate ccache, which will be one of: *