From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- .../heimdal/kdc/altsecid_gss_preauth_authorizer.c | 541 +++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c (limited to 'third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c') diff --git a/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c b/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c new file mode 100644 index 0000000..d48ea58 --- /dev/null +++ b/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2021, PADL Software Pty Ltd. + * All rights reserved. + * + * Portions Copyright (c) 2004 Kungliga Tekniska Högskolan + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of PADL Software nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This plugin authorizes federated GSS-API pre-authentication clients by + * querying an AD DC in the KDC realm for the altSecurityIdentities + * attribute. + * + * For example, GSS-API initiator foo@AAA.H5L.SE using the eap-aes128 + * mechanism to authenticate in realm H5L.SE would require a user entry + * where altSecurityIdentities equals either: + * + * EAP:foo@AAA.H5L.SE + * EAP-AES128:foo@AAA.H5L.SE + * + * (Stripping the mechanism name after the hyphen is a convention + * intended to allow mechanism variants to be grouped together.) + * + * Once the user entry is found, the name is canonicalized by reading the + * sAMAccountName attribute and concatenating it with the KDC realm, + * specifically the canonicalized realm of the WELLKNOWN/FEDERATED HDB + * entry. + * + * The KDC will need to have access to a default credentials cache, or + * OpenLDAP will need to be linked against a version of Cyrus SASL and + * Heimdal that supports keytab credentials. + */ + +#include "kdc_locl.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gss_preauth_authorizer_plugin.h" + +#ifndef PAC_REQUESTOR_SID +#define PAC_REQUESTOR_SID 18 +#endif + +struct ad_server_tuple { + HEIM_TAILQ_ENTRY(ad_server_tuple) link; + char *realm; + LDAP *ld; +#ifdef LDAP_OPT_X_SASL_GSS_CREDS + gss_cred_id_t gss_cred; +#endif +}; + +struct altsecid_gss_preauth_authorizer_context { + HEIM_TAILQ_HEAD(ad_server_tuple_list, ad_server_tuple) servers; +}; + +static int +sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *interact) +{ + return LDAP_SUCCESS; +} + +#ifdef LDAP_OPT_X_SASL_GSS_CREDS +static krb5_error_code +ad_acquire_cred(krb5_context context, + krb5_const_realm realm, + struct ad_server_tuple *server) +{ + const char *keytab_name = NULL; + char *keytab_name_buf = NULL; + krb5_error_code ret; + + OM_uint32 major, minor; + gss_key_value_element_desc client_keytab; + gss_key_value_set_desc cred_store; + gss_OID_set_desc desired_mechs; + + desired_mechs.count = 1; + desired_mechs.elements = GSS_KRB5_MECHANISM; + + keytab_name = krb5_config_get_string(context, NULL, "kdc", realm, + "gss_altsecid_authorizer_keytab_name", NULL); + if (keytab_name == NULL) + keytab_name = krb5_config_get_string(context, NULL, "kdc", + "gss_altsecid_authorizer_keytab_name", NULL); + if (keytab_name == NULL) { + ret = _krb5_kt_client_default_name(context, &keytab_name_buf); + if (ret) + return ret; + + keytab_name = keytab_name_buf; + } + + client_keytab.key = "client_keytab"; + client_keytab.value = keytab_name; + + cred_store.count = 1; + cred_store.elements = &client_keytab; + + major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, + &desired_mechs, GSS_C_INITIATE, + &cred_store, &server->gss_cred, NULL, NULL); + if (GSS_ERROR(major)) + ret = minor ? minor : KRB5_KT_NOTFOUND; + else + ret = 0; + + krb5_xfree(keytab_name_buf); + + return ret; +} +#endif + +static krb5_boolean +is_recoverable_ldap_err_p(int lret) +{ + return + (lret == LDAP_SERVER_DOWN || + lret == LDAP_TIMEOUT || + lret == LDAP_UNAVAILABLE || + lret == LDAP_BUSY || + lret == LDAP_CONNECT_ERROR); +} + +static krb5_error_code +ad_connect(krb5_context context, + krb5_const_realm realm, + struct ad_server_tuple *server) +{ + krb5_error_code ret; + struct { + char *server; + int port; + } *s, *servers = NULL; + size_t i, num_servers = 0; + + { + struct rk_dns_reply *r; + struct rk_resource_record *rr; + char *domain; + + asprintf(&domain, "_ldap._tcp.%s", realm); + if (domain == NULL) { + ret = krb5_enomem(context); + goto out; + } + + r = rk_dns_lookup(domain, "SRV"); + free(domain); + if (r == NULL) { + krb5_set_error_message(context, KRB5KDC_ERR_SVC_UNAVAILABLE, + "Couldn't find AD DC in DNS"); + ret = KRB5KDC_ERR_SVC_UNAVAILABLE; + goto out; + } + + for (rr = r->head ; rr != NULL; rr = rr->next) { + if (rr->type != rk_ns_t_srv) + continue; + s = realloc(servers, sizeof(*servers) * (num_servers + 1)); + if (s == NULL) { + ret = krb5_enomem(context); + rk_dns_free_data(r); + goto out; + } + servers = s; + num_servers++; + servers[num_servers - 1].port = rr->u.srv->port; + servers[num_servers - 1].server = strdup(rr->u.srv->target); + } + rk_dns_free_data(r); + } + +#ifdef LDAP_OPT_X_SASL_GSS_CREDS + if (server->gss_cred == GSS_C_NO_CREDENTIAL) { + ret = ad_acquire_cred(context, realm, server); + if (ret) + goto out; + } +#endif + + for (i = 0; i < num_servers; i++) { + int lret, version = LDAP_VERSION3; + LDAP *ld; + char *url = NULL; + + asprintf(&url, "ldap://%s:%d", servers[i].server, servers[i].port); + if (url == NULL) { + ret = krb5_enomem(context); + goto out; + } + + lret = ldap_initialize(&ld, url); + free(url); + if (lret != LDAP_SUCCESS) + continue; + + ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); +#ifdef LDAP_OPT_X_SASL_GSS_CREDS + ldap_set_option(ld, LDAP_OPT_X_SASL_GSS_CREDS, server->gss_cred); +#endif + + lret = ldap_sasl_interactive_bind_s(ld, NULL, "GSS-SPNEGO", + NULL, NULL, LDAP_SASL_QUIET, + sasl_interact, NULL); + if (lret != LDAP_SUCCESS) { + krb5_set_error_message(context, 0, + "Couldn't bind to AD DC %s:%d: %s", + servers[i].server, servers[i].port, + ldap_err2string(lret)); + ldap_unbind_ext_s(ld, NULL, NULL); + continue; + } + + server->ld = ld; + break; + } + + ret = (server->ld != NULL) ? 0 : KRB5_KDC_UNREACH; + +out: + for (i = 0; i < num_servers; i++) + free(servers[i].server); + free(servers); + + if (ret && server->ld) { + ldap_unbind_ext_s(server->ld, NULL, NULL); + server->ld = NULL; + } + + return ret; +} + +static krb5_error_code +ad_lookup(krb5_context context, + krb5_const_realm realm, + struct ad_server_tuple *server, + gss_const_name_t initiator_name, + gss_const_OID mech_type, + krb5_principal *canon_principal, + kdc_data_t *requestor_sid) +{ + krb5_error_code ret; + OM_uint32 minor; + const char *mech_type_str, *p; + char *filter = NULL; + gss_buffer_desc initiator_name_buf = GSS_C_EMPTY_BUFFER; + LDAPMessage *m = NULL, *m0; + char *basedn = NULL; + int lret; + char *attrs[] = { "sAMAccountName", "objectSid", NULL }; + struct berval **values = NULL; + + *canon_principal = NULL; + if (requestor_sid) + *requestor_sid = NULL; + + mech_type_str = gss_oid_to_name(mech_type); + if (mech_type_str == NULL) { + ret = KRB5_PREAUTH_BAD_TYPE; /* should never happen */ + goto out; + } + + ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED; + + if (GSS_ERROR(gss_display_name(&minor, initiator_name, + &initiator_name_buf, NULL))) + goto out; + + if ((p = strrchr(mech_type_str, '-')) != NULL) { + asprintf(&filter, "(&(objectClass=user)" + "(|(altSecurityIdentities=%.*s:%.*s)(altSecurityIdentities=%s:%.*s)))", + (int)(p - mech_type_str), mech_type_str, + (int)initiator_name_buf.length, (char *)initiator_name_buf.value, + mech_type_str, + (int)initiator_name_buf.length, + (char *)initiator_name_buf.value); + } else { + asprintf(&filter, "(&(objectClass=user)(altSecurityIdentities=%s:%.*s))", + mech_type_str, + (int)initiator_name_buf.length, + (char *)initiator_name_buf.value); + } + if (filter == NULL) + goto enomem; + + lret = ldap_domain2dn(realm, &basedn); + if (lret != LDAP_SUCCESS) + goto out; + + lret = ldap_search_ext_s(server->ld, basedn, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, + NULL, NULL, NULL, 1, &m); + if (lret == LDAP_SIZELIMIT_EXCEEDED) + ret = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; + else if (is_recoverable_ldap_err_p(lret)) + ret = KRB5KDC_ERR_SVC_UNAVAILABLE; + if (lret != LDAP_SUCCESS) + goto out; + + m0 = ldap_first_entry(server->ld, m); + if (m0 == NULL) + goto out; + + values = ldap_get_values_len(server->ld, m0, "sAMAccountName"); + if (values == NULL || + ldap_count_values_len(values) == 0) + goto out; + + ret = krb5_make_principal(context, canon_principal, realm, + values[0]->bv_val, NULL); + if (ret) + goto out; + + if (requestor_sid) { + ldap_value_free_len(values); + + values = ldap_get_values_len(server->ld, m0, "objectSid"); + if (values == NULL || + ldap_count_values_len(values) == 0) + goto out; + + *requestor_sid = kdc_data_create(values[0]->bv_val, values[0]->bv_len); + if (*requestor_sid == NULL) + goto enomem; + } + + goto out; + +enomem: + ret = krb5_enomem(context); + goto out; + +out: + if (ret) { + krb5_free_principal(context, *canon_principal); + *canon_principal = NULL; + + if (requestor_sid) { + kdc_object_release(*requestor_sid); + *requestor_sid = NULL; + } + } + + ldap_value_free_len(values); + ldap_msgfree(m); + ldap_memfree(basedn); + free(filter); + gss_release_buffer(&minor, &initiator_name_buf); + + return ret; +} + +static KRB5_LIB_CALL krb5_error_code +authorize(void *ctx, + astgs_request_t r, + gss_const_name_t initiator_name, + gss_const_OID mech_type, + OM_uint32 ret_flags, + krb5_boolean *authorized, + krb5_principal *mapped_name) +{ + struct altsecid_gss_preauth_authorizer_context *c = ctx; + struct ad_server_tuple *server = NULL; + krb5_error_code ret; + krb5_context context = kdc_request_get_context((kdc_request_t)r); + const hdb_entry *client = kdc_request_get_client(r); + krb5_const_principal server_princ = kdc_request_get_server_princ(r); + krb5_const_realm realm = krb5_principal_get_realm(context, client->principal); + krb5_boolean reconnect_p = FALSE; + krb5_boolean is_tgs; + kdc_data_t requestor_sid = NULL; + + *authorized = FALSE; + *mapped_name = NULL; + + if (!krb5_principal_is_federated(context, client->principal) || + (ret_flags & GSS_C_ANON_FLAG)) + return KRB5_PLUGIN_NO_HANDLE; + + is_tgs = krb5_principal_is_krbtgt(context, server_princ); + + HEIM_TAILQ_FOREACH(server, &c->servers, link) { + if (strcmp(realm, server->realm) == 0) + break; + } + + if (server == NULL) { + server = calloc(1, sizeof(*server)); + if (server == NULL) + return krb5_enomem(context); + + server->realm = strdup(realm); + if (server->realm == NULL) { + free(server); + return krb5_enomem(context); + } + + HEIM_TAILQ_INSERT_HEAD(&c->servers, server, link); + } + + do { + if (server->ld == NULL) { + ret = ad_connect(context, realm, server); + if (ret) + return ret; + } + + ret = ad_lookup(context, realm, server, + initiator_name, mech_type, + mapped_name, is_tgs ? &requestor_sid : NULL); + if (ret == KRB5KDC_ERR_SVC_UNAVAILABLE) { + ldap_unbind_ext_s(server->ld, NULL, NULL); + server->ld = NULL; + + /* try to reconnect iff we haven't already tried */ + reconnect_p = !reconnect_p; + } + + *authorized = (ret == 0); + } while (reconnect_p); + + if (requestor_sid) { + kdc_request_set_attribute((kdc_request_t)r, + HSTR("org.h5l.gss-pa-requestor-sid"), requestor_sid); + kdc_object_release(requestor_sid); + } + + return ret; +} + +static KRB5_LIB_CALL krb5_error_code +finalize_pac(void *ctx, astgs_request_t r) +{ + kdc_data_t requestor_sid; + + requestor_sid = kdc_request_get_attribute((kdc_request_t)r, + HSTR("org.h5l.gss-pa-requestor-sid")); + if (requestor_sid == NULL) + return 0; + + kdc_audit_setkv_object((kdc_request_t)r, "gss_requestor_sid", requestor_sid); + + return kdc_request_add_pac_buffer(r, PAC_REQUESTOR_SID, + kdc_data_get_data(requestor_sid)); +} + +static KRB5_LIB_CALL krb5_error_code +init(krb5_context context, void **contextp) +{ + struct altsecid_gss_preauth_authorizer_context *c; + + c = calloc(1, sizeof(*c)); + if (c == NULL) + return krb5_enomem(context); + + HEIM_TAILQ_INIT(&c->servers); + + *contextp = c; + return 0; +} + +static KRB5_LIB_CALL void +fini(void *context) +{ + struct altsecid_gss_preauth_authorizer_context *c = context; + struct ad_server_tuple *server, *next; + OM_uint32 minor; + + HEIM_TAILQ_FOREACH_SAFE(server, &c->servers, link, next) { + free(server->realm); + ldap_unbind_ext_s(server->ld, NULL, NULL); +#ifdef LDAP_OPT_X_SASL_GSS_CREDS + gss_release_cred(&minor, &server->gss_cred); +#endif + free(server); + } +} + +static krb5plugin_gss_preauth_authorizer_ftable plug_desc = + { 1, init, fini, authorize, finalize_pac }; + +static krb5plugin_gss_preauth_authorizer_ftable *plugs[] = { &plug_desc }; + +static uintptr_t +altsecid_gss_preauth_authorizer_get_instance(const char *libname) +{ + if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + if (strcmp(libname, "kdc") == 0) + return kdc_get_instance(libname); + return 0; +} + +krb5_plugin_load_ft kdc_gss_preauth_authorizer_plugin_load; + +krb5_error_code KRB5_CALLCONV +kdc_gss_preauth_authorizer_plugin_load(heim_pcontext context, + krb5_get_instance_func_t *get_instance, + size_t *num_plugins, + krb5_plugin_common_ftable_cp **plugins) +{ + *get_instance = altsecid_gss_preauth_authorizer_get_instance; + *num_plugins = sizeof(plugs) / sizeof(plugs[0]); + *plugins = (krb5_plugin_common_ftable_cp *)plugs; + return 0; +} -- cgit v1.2.3