From 0956c8149f11921ed427d67b10bb9b6c4b97df48 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Thu, 2 Feb 2017 12:32:13 +0100 Subject: [PATCH] IPA certauth plugin This patch add a certauth plugin which allows the IPA server to support PKINIT for certificates which do not include a special SAN extension which contains a Kerberos principal but allow other mappings with the help of SSSD's certmap library. Related to https://pagure.io/freeipa/issue/4905 Reviewed-By: Alexander Bokovoy Reviewed-By: David Kupka --- daemons/ipa-kdb/Makefile.am | 24 ++- daemons/ipa-kdb/ipa-certauth.in | 5 + daemons/ipa-kdb/ipa_kdb.c | 2 + daemons/ipa-kdb/ipa_kdb.exports | 1 + daemons/ipa-kdb/ipa_kdb.h | 5 + daemons/ipa-kdb/ipa_kdb_certauth.c | 398 +++++++++++++++++++++++++++++++++++++ freeipa.spec.in | 2 + server.m4 | 13 ++ 8 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 daemons/ipa-kdb/ipa-certauth.in create mode 100644 daemons/ipa-kdb/ipa_kdb_certauth.c diff --git a/daemons/ipa-kdb/Makefile.am b/daemons/ipa-kdb/Makefile.am index 6a2caa0637bf076c796b50efc92412062524f35f..715666e779a4fa64c2c0f71767f09efb19b5f908 100644 --- a/daemons/ipa-kdb/Makefile.am +++ b/daemons/ipa-kdb/Makefile.am @@ -18,6 +18,7 @@ AM_CPPFLAGS = \ $(WARN_CFLAGS) \ $(NDRPAC_CFLAGS) \ $(NSS_CFLAGS) \ + $(SSSCERTMAP_CFLAGS) \ $(NULL) plugindir = $(libdir)/krb5/plugins/kdb @@ -39,6 +40,20 @@ ipadb_la_SOURCES = \ ipa_kdb_audit_as.c \ $(NULL) +if BUILD_IPA_CERTAUTH_PLUGIN +ipadb_la_SOURCES += ipa_kdb_certauth.c + + +%: %.in + sed \ + -e 's|@plugindir@|$(plugindir)|g' \ + '$(srcdir)/$@.in' >$@ + +krb5confdir = $(sysconfdir)/krb5.conf.d +krb5conf_DATA = ipa-certauth +CLEANFILES = $(krb5conf_DATA) +endif + ipadb_la_LDFLAGS = \ -avoid-version \ -module \ @@ -50,6 +65,7 @@ ipadb_la_LIBADD = \ $(NDRPAC_LIBS) \ $(UNISTRING_LIBS) \ $(NSS_LIBS) \ + $(SSSCERTMAP_LIBS) \ $(top_builddir)/util/libutil.la \ $(NULL) @@ -70,6 +86,11 @@ ipa_kdb_tests_SOURCES = \ ipa_kdb_delegation.c \ ipa_kdb_audit_as.c \ $(NULL) + +if BUILD_IPA_CERTAUTH_PLUGIN +ipa_kdb_tests_SOURCES += ipa_kdb_certauth.c +endif + ipa_kdb_tests_CFLAGS = $(CMOCKA_CFLAGS) ipa_kdb_tests_LDADD = \ $(CMOCKA_LIBS) \ @@ -78,12 +99,13 @@ ipa_kdb_tests_LDADD = \ $(NDRPAC_LIBS) \ $(UNISTRING_LIBS) \ $(NSS_LIBS) \ + $(SSSCERTMAP_LIBS) \ $(top_builddir)/util/libutil.la \ -lkdb5 \ -lsss_idmap \ $(NULL) -dist_noinst_DATA = ipa_kdb.exports +dist_noinst_DATA = ipa_kdb.exports ipa-certauth.in clean-local: rm -f tests/.dirstamp diff --git a/daemons/ipa-kdb/ipa-certauth.in b/daemons/ipa-kdb/ipa-certauth.in new file mode 100644 index 0000000000000000000000000000000000000000..eda89a26f02fbea449eb754b232b8115904acd21 --- /dev/null +++ b/daemons/ipa-kdb/ipa-certauth.in @@ -0,0 +1,5 @@ +[plugins] + certauth = { + module = ipakdb:@plugindir@/ipadb.so + enable_only = ipakdb + } diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c index c19b7c40e2e88173ab8367a3ef1d7f46245fd174..a961e4e57cf5379eb237551d56e3bc8dc82d952d 100644 --- a/daemons/ipa-kdb/ipa_kdb.c +++ b/daemons/ipa-kdb/ipa_kdb.c @@ -67,6 +67,8 @@ static void ipadb_context_free(krb5_context kcontext, } free(cfg->authz_data); + ipa_certauth_free_moddata(&((*ctx)->certauth_moddata)); + free(*ctx); *ctx = NULL; } diff --git a/daemons/ipa-kdb/ipa_kdb.exports b/daemons/ipa-kdb/ipa_kdb.exports index d2c3f30246cc7ebcba02a9ec5d134e604fa0dbb9..27ce92d2edd741245061a5f4ee9275169440c932 100644 --- a/daemons/ipa-kdb/ipa_kdb.exports +++ b/daemons/ipa-kdb/ipa_kdb.exports @@ -3,6 +3,7 @@ EXPORTED { # public symbols global: kdb_function_table; + certauth_ipakdb_initvt; # everything else is local local: diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 72f2675809a3267cce30bc06c77335697c7287ad..632c1979d15e88aec86d5e408ed6c7017d8362b8 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -40,6 +40,7 @@ #include #include #include +#include #include "ipa_krb5.h" #include "ipa_pwd.h" @@ -111,6 +112,7 @@ struct ipadb_context { krb5_key_salt_tuple *def_encs; int n_def_encs; struct ipadb_mspac *mspac; + krb5_certauth_moddata certauth_moddata; /* Don't access this directly, use ipadb_get_global_config(). */ struct ipadb_global_config config; @@ -331,3 +333,6 @@ ipadb_get_global_config(struct ipadb_context *ipactx); int ipadb_get_enc_salt_types(struct ipadb_context *ipactx, LDAPMessage *entry, char *attr, krb5_key_salt_tuple **enc_salt_types, int *n_enc_salt_types); + +/* CERTAUTH PLUGIN */ +void ipa_certauth_free_moddata(krb5_certauth_moddata *moddata); diff --git a/daemons/ipa-kdb/ipa_kdb_certauth.c b/daemons/ipa-kdb/ipa_kdb_certauth.c new file mode 100644 index 0000000000000000000000000000000000000000..a53a2ce4e7ceb06ec8de117cdbca2666fdb5a97a --- /dev/null +++ b/daemons/ipa-kdb/ipa_kdb_certauth.c @@ -0,0 +1,398 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Sumit Bose + * + * Copyright (C) 2017 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include +//#include +#include +#include + +#include "util/ipa_krb5.h" +#include "ipa_kdb.h" + +#define IPA_OC_CERTMAP_RULE "ipaCertMapRule" +#define IPA_CERTMAP_MAPRULE "ipaCertMapMapRule" +#define IPA_CERTMAP_MATCHRULE "ipaCertMapMatchRule" +#define IPA_CERTMAP_PRIORITY "ipaCertMapPriority" +#define IPA_ENABLED_FLAG "ipaEnabledFlag" +#define IPA_TRUE_VALUE "TRUE" +#define IPA_ASSOCIATED_DOMAIN "associatedDomain" + +#define OBJECTCLASS "objectClass" + +#define CERTMAP_FILTER "(&("OBJECTCLASS"="IPA_OC_CERTMAP_RULE")" \ + "("IPA_ENABLED_FLAG"="IPA_TRUE_VALUE"))" + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + + +struct krb5_certauth_moddata_st { + char *local_domain; + struct sss_certmap_ctx *sss_certmap_ctx; + struct ipadb_context *ipactx; +}; + +void ipa_certmap_debug(void *private, + const char *file, long line, + const char *function, + const char *format, ...) +{ + va_list ap; + char str[255] = { 0 }; + + va_start(ap, format); + vsnprintf(str, sizeof(str)-1, format, ap); + va_end(ap); + krb5_klog_syslog(LOG_INFO, str); +} + +void ipa_certauth_free_moddata(krb5_certauth_moddata *moddata) +{ + if (moddata == NULL || *moddata == NULL) { + return; + } + + free((*moddata)->local_domain); + (*moddata)->local_domain = NULL; + sss_certmap_free_ctx((*moddata)->sss_certmap_ctx); + (*moddata)->sss_certmap_ctx = NULL; + + free(*moddata); + + return; +} + +static krb5_error_code ipa_get_init_data(krb5_context kcontext, + krb5_certauth_moddata moddata_out) +{ + int ret; + struct sss_certmap_ctx *ctx = NULL; + struct ipadb_context *ipactx; + krb5_error_code kerr; + char *basedn = NULL; + LDAPMessage *result = NULL; + LDAPMessage *le; + LDAP *lc; + size_t c; + uint32_t prio; + char *map_rule = NULL; + char *match_rule = NULL; + char **domains = NULL; + + const char *certmap_attrs[] = { OBJECTCLASS, + IPA_CERTMAP_PRIORITY, + IPA_CERTMAP_MATCHRULE, + IPA_CERTMAP_MAPRULE, + IPA_ASSOCIATED_DOMAIN, + IPA_ENABLED_FLAG, + NULL}; + + + krb5_klog_syslog(LOG_INFO, "Initializing IPA certauth plugin."); + + ipactx = ipadb_get_context(kcontext); + if (ipactx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + if (ipactx->certauth_moddata == NULL) { + ret = asprintf(&basedn, "cn=certmap,%s", ipactx->base); + if (ret == -1) { + return ENOMEM; + } + + kerr = ipadb_simple_search(ipactx,basedn, LDAP_SCOPE_SUBTREE, + CERTMAP_FILTER, discard_const(certmap_attrs), + &result); + if (kerr != 0 && kerr != KRB5_KDB_NOENTRY) { + goto done; + } + + ret = sss_certmap_init(NULL, ipa_certmap_debug, NULL, &ctx); + if (ret != 0) { + return ret; + } + + if (kerr == KRB5_KDB_NOENTRY) { + ret = sss_certmap_add_rule(ctx, SSS_CERTMAP_MIN_PRIO, + NULL, NULL, NULL); + if (ret != 0) { + goto done; + } + } else { + lc = ipactx->lcontext; + + for (le = ldap_first_entry(lc, result); le; + le = ldap_next_entry(lc, le)) { + prio = SSS_CERTMAP_MIN_PRIO; + ret = ipadb_ldap_attr_to_uint32(lc, le, IPA_CERTMAP_PRIORITY, + &prio); + if (ret != 0 && ret != ENOENT) { + goto done; + } + + free(map_rule); + map_rule = NULL; + ret = ipadb_ldap_attr_to_str(lc, le, IPA_CERTMAP_MAPRULE, + &map_rule); + if (ret != 0 && ret != ENOENT) { + goto done; + } + + free(match_rule); + match_rule = NULL; + ret = ipadb_ldap_attr_to_str(lc, le, IPA_CERTMAP_MATCHRULE, + &match_rule); + if (ret != 0 && ret != ENOENT) { + goto done; + } + + if (domains != NULL) { + for (c = 0; domains[c] != NULL; c++) { + free(domains[c]); + } + free(domains); + domains = NULL; + } + ret = ipadb_ldap_attr_to_strlist(lc, le, IPA_ASSOCIATED_DOMAIN, + &domains); + if (ret != 0 && ret != ENOENT) { + goto done; + } + + ret = sss_certmap_add_rule(ctx, prio, match_rule, map_rule, + (const char **) domains); + if (ret != 0) { + goto done; + } + } + } + + ipactx->certauth_moddata = moddata_out; + + if (ipactx->realm != NULL) { + ipactx->certauth_moddata->local_domain = strdup(ipactx->realm); + if (ipactx->certauth_moddata->local_domain == NULL) { + free(ipactx->certauth_moddata); + ipactx->certauth_moddata = NULL; + ret = ENOMEM; + goto done; + } + } + + ipactx->certauth_moddata->sss_certmap_ctx = ctx; + ipactx->certauth_moddata->ipactx = ipactx; + + } + + ret = 0; + +done: + ldap_msgfree(result); + free(basedn); + free(map_rule); + free(match_rule); + if (domains != NULL) { + for (c = 0; domains[c] != NULL; c++) { + free(domains[c]); + } + free(domains); + domains = NULL; + } + + if (ret != 0) { + sss_certmap_free_ctx(ctx); + } + + return ret; +} + +static krb5_error_code ipa_certauth_authorize(krb5_context context, + krb5_certauth_moddata moddata, + const uint8_t *cert, + size_t cert_len, + krb5_const_principal princ, + const void *opts, + const krb5_db_entry *db_entry, + char ***authinds_out) +{ + char *cert_filter = NULL; + char **domains = NULL; + int ret; + size_t c; + char *principal = NULL; + LDAPMessage *res = NULL; + krb5_error_code kerr; + LDAPMessage *lentry; + + if (moddata == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + if (moddata->sss_certmap_ctx == NULL) { + kerr = ipa_get_init_data(context, moddata); + if (kerr != 0) { + krb5_klog_syslog(LOG_ERR, "Failed to init certmapping data"); + return KRB5_PLUGIN_NO_HANDLE; + } + } + + ret = krb5_unparse_name(context, princ, &principal); + if (ret != 0) { + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } + krb5_klog_syslog(LOG_INFO, "Doing certauth authorize for [%s]", principal); + + ret = sss_certmap_get_search_filter(moddata->sss_certmap_ctx, + cert, cert_len, + &cert_filter, &domains); + if (ret != 0) { + if (ret == ENOENT) { + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + } + goto done; + } + krb5_klog_syslog(LOG_INFO, "Got cert filter [%s]", cert_filter); + + /* If there are no domains assigned the rule will apply to the local + * domain only. */ + if (domains != NULL) { + + if (moddata->local_domain == NULL) { + /* We don't know our own domain name, in general this should not + * happen. But to be fault tolerant we allow matching rule which + * do not have a domain assigned. */ + + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } + + for (c = 0; domains[c] != NULL; c++) { + if (strcasecmp(domains[c], moddata->local_domain) == 0) { + break; + } + } + + /* Our domain was not in the list */ + if (domains[c] == NULL) { + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } + } + + kerr = ipadb_fetch_principals_with_extra_filter(moddata->ipactx, + KRB5_KDB_FLAG_ALIAS_OK, + principal, + cert_filter, + &res); + if (kerr != 0) { + krb5_klog_syslog(LOG_ERR, "Search failed [%d]", kerr); + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } + + kerr = ipadb_find_principal(context, KRB5_KDB_FLAG_ALIAS_OK, res, + &principal, &lentry); + if (kerr == KRB5_KDB_NOENTRY) { + krb5_klog_syslog(LOG_INFO, "No matching entry found"); + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } else if (kerr != 0) { + krb5_klog_syslog(LOG_ERR, "ipadb_find_principal failed [%d]", kerr); + ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; + goto done; + } + + /* TODO: add more tests ? */ + + ret = 0; + +done: + sss_certmap_free_filter_and_domains(cert_filter, domains); + krb5_free_unparsed_name(context, principal); + ldap_msgfree(res); + + return ret; +} + +static krb5_error_code ipa_certauth_init(krb5_context kcontext, + krb5_certauth_moddata *moddata_out) +{ + struct krb5_certauth_moddata_st *certauth_moddata; + + certauth_moddata = calloc(1, sizeof(struct krb5_certauth_moddata_st)); + if (certauth_moddata == NULL) { + return ENOMEM; + } + + *moddata_out = certauth_moddata; + + return 0; +} + +static void ipa_certauth_fini(krb5_context context, + krb5_certauth_moddata moddata_out) +{ + krb5_klog_syslog(LOG_INFO, "IPA certauth plugin un-loaded."); + return; +} + + +krb5_error_code certauth_ipakdb_initvt(krb5_context context, + int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + krb5_certauth_vtable vt; + + if (maj_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + vt = (krb5_certauth_vtable) vtable; + + vt->name = "ipakdb"; + vt->authorize = ipa_certauth_authorize; + vt->init = ipa_certauth_init; + vt->fini = ipa_certauth_fini; + /* currently we do not return authentication indicators */ + vt->free_ind = NULL; + return 0; +} diff --git a/freeipa.spec.in b/freeipa.spec.in index f776b34af88cc8ccd02da0713cb6eaca161c99f5..18291a5793a6b69dcd719f42e80e1652169e5e1d 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -120,6 +120,7 @@ BuildRequires: libtalloc-devel BuildRequires: libtevent-devel BuildRequires: libuuid-devel BuildRequires: libsss_idmap-devel +BuildRequires: libsss_certmap-devel # 1.14.0: sss_nss_getnamebycert (https://fedorahosted.org/sssd/ticket/2897) BuildRequires: libsss_nss_idmap-devel >= 1.14.0 BuildRequires: rhino @@ -1164,6 +1165,7 @@ fi %attr(0755,root,root) %{_libexecdir}/ipa/oddjob/org.freeipa.server.conncheck %config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freeipa.server.conf %config(noreplace) %{_sysconfdir}/oddjobd.conf.d/ipa-server.conf +%config(noreplace) %{_sysconfdir}/krb5.conf.d/ipa-certauth %dir %{_libexecdir}/ipa/certmonger %attr(755,root,root) %{_libexecdir}/ipa/certmonger/* # NOTE: systemd specific section diff --git a/server.m4 b/server.m4 index 92b5cdd3a6ff90b70a9002360ff3d3aec5053392..7b2e94df91a4803849e496142788a4ed87ef487d 100644 --- a/server.m4 +++ b/server.m4 @@ -30,6 +30,19 @@ dnl -- sss_idmap is needed by the extdom exop -- PKG_CHECK_MODULES([SSSIDMAP], [sss_idmap]) PKG_CHECK_MODULES([SSSNSSIDMAP], [sss_nss_idmap >= 1.13.90]) +dnl -- sss_certmap and certauth.h are needed by the IPA KDB certauth plugin -- +PKG_CHECK_EXISTS([sss_certmap], + [PKG_CHECK_MODULES([SSSCERTMAP], [sss_certmap])], + [AC_MSG_NOTICE([sss_certmap not found])]) +AC_CHECK_HEADER([krb5/certauth_plugin.h], + [have_certauth_plugin=yes], + [have_certauth_plugin=no]) +AM_CONDITIONAL([BUILD_IPA_CERTAUTH_PLUGIN], + [test x$have_certauth_plugin = xyes -a x"$SSSCERTMAP_LIBS" != x]) +AM_COND_IF([BUILD_IPA_CERTAUTH_PLUGIN], + [AC_MSG_NOTICE([Build IPA KDB certauth plugin])], + [AC_MSG_WARN([Cannot build IPA KDB certauth plugin])]) + dnl --------------------------------------------------------------------------- dnl - Check for KRB5 krad dnl --------------------------------------------------------------------------- -- 2.12.1