Fix transit path validation
Also implement KDC hierarchical transit policy checks. The "hier_capaths" parameter defaults to "yes" in [libdefaults] or can be set explicitly in [realms] per-realm.
This commit is contained in:
		
				
					committed by
					
						
						Nicolas Williams
					
				
			
			
				
	
			
			
			
						parent
						
							0561396c0a
						
					
				
				
					commit
					1501740952
				
			@@ -797,4 +797,5 @@ EXPORTS
 | 
				
			|||||||
	_krb5_plugin_find;
 | 
						_krb5_plugin_find;
 | 
				
			||||||
	_krb5_plugin_free;
 | 
						_krb5_plugin_free;
 | 
				
			||||||
	_krb5_expand_path_tokensv;
 | 
						_krb5_expand_path_tokensv;
 | 
				
			||||||
 | 
					        _krb5_find_capath;
 | 
				
			||||||
 | 
					        _krb5_free_capath;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -398,6 +398,204 @@ krb5_domain_x500_encode(char **realms, unsigned int num_realms,
 | 
				
			|||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					KRB5_LIB_FUNCTION void KRB5_LIB_CALL
 | 
				
			||||||
 | 
					_krb5_free_capath(krb5_context context, char **capath)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    char **s;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (s = capath; s && *s; ++s)
 | 
				
			||||||
 | 
					        free(*s);
 | 
				
			||||||
 | 
					    free(capath);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct hier_iter {
 | 
				
			||||||
 | 
					    const char *local_realm;
 | 
				
			||||||
 | 
					    const char *server_realm;
 | 
				
			||||||
 | 
					    const char *lr;
 | 
				
			||||||
 | 
					    const char *sr;
 | 
				
			||||||
 | 
					    size_t llen;
 | 
				
			||||||
 | 
					    size_t slen;
 | 
				
			||||||
 | 
					    size_t len;
 | 
				
			||||||
 | 
					    size_t num;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *
 | 
				
			||||||
 | 
					hier_next(struct hier_iter *state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const char *lr = state->lr;
 | 
				
			||||||
 | 
					    const char *sr = state->sr;
 | 
				
			||||||
 | 
					    const char *lsuffix = state->local_realm + state->llen - state->len;
 | 
				
			||||||
 | 
					    const char *server_realm = state->server_realm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (lr != NULL) {
 | 
				
			||||||
 | 
					        while (lr < lsuffix)
 | 
				
			||||||
 | 
					            if (*lr++ == '.')
 | 
				
			||||||
 | 
					                return state->lr = lr;
 | 
				
			||||||
 | 
					        state->lr = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (sr != NULL) {
 | 
				
			||||||
 | 
					        while (--sr >= server_realm)
 | 
				
			||||||
 | 
					            if (sr == server_realm || sr[-1] == '.')
 | 
				
			||||||
 | 
					                return state->sr = sr;
 | 
				
			||||||
 | 
					        state->sr = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					hier_init(struct hier_iter *state, const char *local_realm, const char *server_realm)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    size_t llen;
 | 
				
			||||||
 | 
					    size_t slen;
 | 
				
			||||||
 | 
					    size_t len;
 | 
				
			||||||
 | 
					    const char *lr;
 | 
				
			||||||
 | 
					    const char *sr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state->local_realm = local_realm;
 | 
				
			||||||
 | 
					    state->server_realm = server_realm;
 | 
				
			||||||
 | 
					    state->llen = llen = strlen(local_realm);
 | 
				
			||||||
 | 
					    state->slen = slen = strlen(server_realm);
 | 
				
			||||||
 | 
					    state->len = 0;
 | 
				
			||||||
 | 
					    state->num = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (slen == 0 || llen == 0)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Find first difference from the back */
 | 
				
			||||||
 | 
					    for (lr = local_realm + llen, sr = server_realm + slen;
 | 
				
			||||||
 | 
					         lr != local_realm && sr != server_realm;
 | 
				
			||||||
 | 
					         --lr, --sr) {
 | 
				
			||||||
 | 
					        if (lr[-1] != sr[-1])
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        if (lr[-1] == '.')
 | 
				
			||||||
 | 
					            len = llen - (lr - local_realm);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Nothing in common? */
 | 
				
			||||||
 | 
					    if (*lr == '\0')
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Everything in common? */
 | 
				
			||||||
 | 
					    if (llen == slen && lr == local_realm)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Is one realm is a suffix of the other? */
 | 
				
			||||||
 | 
					    if ((llen < slen && lr == local_realm && sr[-1] == '.') ||
 | 
				
			||||||
 | 
					        (llen > slen && sr == server_realm && lr[-1] == '.'))
 | 
				
			||||||
 | 
					        len = llen - (lr - local_realm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state->len = len;
 | 
				
			||||||
 | 
					    /* `lr` starts at local realm and walks up the tree to common suffix */
 | 
				
			||||||
 | 
					    state->lr = local_realm;
 | 
				
			||||||
 | 
					    /* `sr` starts at common suffix in server realm and walks down the tree */
 | 
				
			||||||
 | 
					    state->sr = server_realm + slen - len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Count elements and reset */
 | 
				
			||||||
 | 
					    while (hier_next(state) != NULL)
 | 
				
			||||||
 | 
					        ++state->num;
 | 
				
			||||||
 | 
					    state->lr = local_realm;
 | 
				
			||||||
 | 
					    state->sr = server_realm + slen - len;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Find a referral path from crealm to srealm via our_realm.
 | 
				
			||||||
 | 
					 * Either via [capaths] or hierarchicaly.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | 
				
			||||||
 | 
					_krb5_find_capath(krb5_context context,
 | 
				
			||||||
 | 
					                  const char *client_realm,
 | 
				
			||||||
 | 
					                  const char *local_realm,
 | 
				
			||||||
 | 
					                  const char *server_realm,
 | 
				
			||||||
 | 
					                  krb5_boolean use_hierachical,
 | 
				
			||||||
 | 
					                  char ***rpath,
 | 
				
			||||||
 | 
					                  size_t *npath)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    krb5_boolean maybe = (TRUE == 1) ? 2 : 1;   /* Neither TRUE nor FALSE */
 | 
				
			||||||
 | 
					    krb5_boolean b;
 | 
				
			||||||
 | 
					    char **confpath;
 | 
				
			||||||
 | 
					    char **capath;
 | 
				
			||||||
 | 
					    struct hier_iter hier_state;
 | 
				
			||||||
 | 
					    char **rp;
 | 
				
			||||||
 | 
					    const char *r;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    *rpath = NULL;
 | 
				
			||||||
 | 
					    *npath = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confpath = krb5_config_get_strings(context, NULL, "capaths",
 | 
				
			||||||
 | 
					                                       client_realm, server_realm, NULL);
 | 
				
			||||||
 | 
					    if (confpath == NULL)
 | 
				
			||||||
 | 
					        confpath = krb5_config_get_strings(context, NULL, "capaths",
 | 
				
			||||||
 | 
					                                           local_realm, server_realm, NULL);
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * With a [capaths] setting from the client to the server we look for our
 | 
				
			||||||
 | 
					     * own realm in the list.  If our own realm is not present, we return the
 | 
				
			||||||
 | 
					     * first element.  Otherwise, we return the next element, or possibly NULL.
 | 
				
			||||||
 | 
					     * Ignoring a [capaths] settings risks loops plus would violate explicit
 | 
				
			||||||
 | 
					     * policy and the principle of least surpise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    if (confpath != NULL) {
 | 
				
			||||||
 | 
					        char **start = confpath;
 | 
				
			||||||
 | 
					        size_t i;
 | 
				
			||||||
 | 
					        size_t n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (rp = start; *rp; rp++)
 | 
				
			||||||
 | 
					            if (strcmp(*rp, local_realm) == 0)
 | 
				
			||||||
 | 
					                start = rp+1;
 | 
				
			||||||
 | 
					        n = rp - start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (n == 0)
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        capath = calloc(n + 1, sizeof(*capath));
 | 
				
			||||||
 | 
					        if (capath == NULL)
 | 
				
			||||||
 | 
					            return krb5_enomem(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0, rp = start; *rp; rp++) {
 | 
				
			||||||
 | 
					            if ((capath[i++] = strdup(*rp)) == NULL) {
 | 
				
			||||||
 | 
					                _krb5_free_capath(context, capath);
 | 
				
			||||||
 | 
					                krb5_config_free_strings(confpath);
 | 
				
			||||||
 | 
					                return krb5_enomem(context);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        krb5_config_free_strings(confpath);
 | 
				
			||||||
 | 
					        capath[i] = NULL;
 | 
				
			||||||
 | 
					        *rpath = capath;
 | 
				
			||||||
 | 
					        *npath = n;
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * With hierarchical referrals, we ignore the client realm, and build a
 | 
				
			||||||
 | 
					     * path forward from our own realm!
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    b = krb5_config_get_bool_default(context, NULL, maybe, "realms",
 | 
				
			||||||
 | 
					                                     local_realm, "hier_capaths", NULL);
 | 
				
			||||||
 | 
					    if (b == maybe)
 | 
				
			||||||
 | 
					        b = krb5_config_get_bool_default(context, NULL, TRUE, "libdefaults",
 | 
				
			||||||
 | 
					                                         "hier_capaths", NULL);
 | 
				
			||||||
 | 
					    if (b == FALSE)
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hier_init(&hier_state, local_realm, server_realm);
 | 
				
			||||||
 | 
					    if (hier_state.num == 0)
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rp = capath = calloc(hier_state.num + 1, sizeof(*capath));
 | 
				
			||||||
 | 
					    if (capath == NULL)
 | 
				
			||||||
 | 
					        return krb5_enomem(context);
 | 
				
			||||||
 | 
					    while ((r = hier_next(&hier_state)) != NULL) {
 | 
				
			||||||
 | 
					        if ((*rp++ = strdup(r)) == NULL) {
 | 
				
			||||||
 | 
					            _krb5_free_capath(context, capath);
 | 
				
			||||||
 | 
					            return krb5_enomem(context);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    *rp = NULL;
 | 
				
			||||||
 | 
					    *rpath = capath;
 | 
				
			||||||
 | 
					    *npath = hier_state.num;
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | 
					KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | 
				
			||||||
krb5_check_transited(krb5_context context,
 | 
					krb5_check_transited(krb5_context context,
 | 
				
			||||||
		     krb5_const_realm client_realm,
 | 
							     krb5_const_realm client_realm,
 | 
				
			||||||
@@ -406,25 +604,22 @@ krb5_check_transited(krb5_context context,
 | 
				
			|||||||
		     unsigned int num_realms,
 | 
							     unsigned int num_realms,
 | 
				
			||||||
		     int *bad_realm)
 | 
							     int *bad_realm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    char **tr_realms;
 | 
					    krb5_error_code ret = 0;
 | 
				
			||||||
    char **p;
 | 
					    char **capath = NULL;
 | 
				
			||||||
    size_t i;
 | 
					    size_t num_capath = 0;
 | 
				
			||||||
 | 
					    size_t i = 0;
 | 
				
			||||||
 | 
					    size_t j = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(num_realms == 0)
 | 
					    ret = _krb5_find_capath(context, client_realm, client_realm, server_realm,
 | 
				
			||||||
	return 0;
 | 
					                            TRUE, &capath, &num_capath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tr_realms = krb5_config_get_strings(context, NULL,
 | 
					 | 
				
			||||||
					"capaths",
 | 
					 | 
				
			||||||
					client_realm,
 | 
					 | 
				
			||||||
					server_realm,
 | 
					 | 
				
			||||||
					NULL);
 | 
					 | 
				
			||||||
    for (i = 0; i < num_realms; i++) {
 | 
					    for (i = 0; i < num_realms; i++) {
 | 
				
			||||||
	for(p = tr_realms; p && *p; p++) {
 | 
						for (j = 0; j < num_capath; ++j) {
 | 
				
			||||||
	    if(strcmp(*p, realms[i]) == 0)
 | 
						    if (strcmp(realms[i], capath[j]) == 0)
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(p == NULL || *p == NULL) {
 | 
						if (j == num_capath) {
 | 
				
			||||||
	    krb5_config_free_strings(tr_realms);
 | 
					            _krb5_free_capath(context, capath);
 | 
				
			||||||
	    krb5_set_error_message (context, KRB5KRB_AP_ERR_ILL_CR_TKT,
 | 
						    krb5_set_error_message (context, KRB5KRB_AP_ERR_ILL_CR_TKT,
 | 
				
			||||||
				    N_("no transit allowed "
 | 
									    N_("no transit allowed "
 | 
				
			||||||
				       "through realm %s from %s to %s", ""),
 | 
									       "through realm %s from %s to %s", ""),
 | 
				
			||||||
@@ -434,7 +629,8 @@ krb5_check_transited(krb5_context context,
 | 
				
			|||||||
	    return KRB5KRB_AP_ERR_ILL_CR_TKT;
 | 
						    return KRB5KRB_AP_ERR_ILL_CR_TKT;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    krb5_config_free_strings(tr_realms);
 | 
					
 | 
				
			||||||
 | 
					    _krb5_free_capath(context, capath);
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -487,4 +683,3 @@ main(int argc, char **argv)
 | 
				
			|||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -410,6 +410,7 @@ struct entry libdefaults_entries[] = {
 | 
				
			|||||||
    { "fcc-mit-ticketflags", krb5_config_string, check_boolean, 0 },
 | 
					    { "fcc-mit-ticketflags", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
    { "forward", krb5_config_string, check_boolean, 0 },
 | 
					    { "forward", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
    { "forwardable", krb5_config_string, check_boolean, 0 },
 | 
					    { "forwardable", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
 | 
					    { "hier_capaths", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
    { "host_timeout", krb5_config_string, check_time, 0 },
 | 
					    { "host_timeout", krb5_config_string, check_time, 0 },
 | 
				
			||||||
    { "http_proxy", krb5_config_string, check_host /* XXX */, 0 },
 | 
					    { "http_proxy", krb5_config_string, check_host /* XXX */, 0 },
 | 
				
			||||||
    { "ignore_addresses", krb5_config_string, NULL, 0 },
 | 
					    { "ignore_addresses", krb5_config_string, NULL, 0 },
 | 
				
			||||||
@@ -480,6 +481,7 @@ struct entry realms_entries[] = {
 | 
				
			|||||||
    { "auth_to_local_names", krb5_config_string, NULL, 0 },
 | 
					    { "auth_to_local_names", krb5_config_string, NULL, 0 },
 | 
				
			||||||
    { "default_domain", krb5_config_string, NULL, 0 },
 | 
					    { "default_domain", krb5_config_string, NULL, 0 },
 | 
				
			||||||
    { "forwardable", krb5_config_string, check_boolean, 0 },
 | 
					    { "forwardable", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
 | 
					    { "hier_capaths", krb5_config_string, check_boolean, 0 },
 | 
				
			||||||
    { "kdc", krb5_config_string, check_host, 0 },
 | 
					    { "kdc", krb5_config_string, check_host, 0 },
 | 
				
			||||||
    { "kpasswd_server", krb5_config_string, check_host, 0 },
 | 
					    { "kpasswd_server", krb5_config_string, check_host, 0 },
 | 
				
			||||||
    { "krb524_server", krb5_config_string, check_host, 0 },
 | 
					    { "krb524_server", krb5_config_string, check_host, 0 },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -784,6 +784,10 @@ HEIMDAL_KRB5_2.0 {
 | 
				
			|||||||
		_krb5_fast_cf2;
 | 
							_krb5_fast_cf2;
 | 
				
			||||||
		_krb5_fast_armor_key;
 | 
							_krb5_fast_armor_key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # TGS
 | 
				
			||||||
 | 
					                _krb5_find_capath;
 | 
				
			||||||
 | 
					                _krb5_free_capath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local:
 | 
						local:
 | 
				
			||||||
		*;
 | 
							*;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,4 +129,5 @@
 | 
				
			|||||||
		TEST4.H5L.SE = TEST3.H5L.SE
 | 
							TEST4.H5L.SE = TEST3.H5L.SE
 | 
				
			||||||
		SOME-REALM6.US = SOME-REALM5.FR
 | 
							SOME-REALM6.US = SOME-REALM5.FR
 | 
				
			||||||
		SOME-REALM7.UK = SOME-REALM6.US
 | 
							SOME-REALM7.UK = SOME-REALM6.US
 | 
				
			||||||
 | 
							SOME-REALM7.UK = SOME-REALM5.FR
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user