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 <abokovoy@redhat.com>
Reviewed-By: David Kupka <dkupka@redhat.com>
This commit is contained in:
Sumit Bose 2017-02-02 12:32:13 +01:00 committed by David Kupka
parent da880decfe
commit c4156041fe
9 changed files with 450 additions and 1 deletions

1
.gitignore vendored
View File

@ -75,6 +75,7 @@ freeipa2-dev-doc
/daemons/dnssec/ipa-ods-exporter.socket
/daemons/ipa-kdb/ipa_kdb_tests
/daemons/ipa-kdb/tests/.dirstamp
/daemons/ipa-kdb/ipa-certauth
/daemons/ipa-otpd/ipa-otpd
/daemons/ipa-otpd/ipa-otpd.socket
/daemons/ipa-otpd/ipa-otpd@.service

View File

@ -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

View File

@ -0,0 +1,5 @@
[plugins]
certauth = {
module = ipakdb:@plugindir@/ipadb.so
enable_only = ipakdb
}

View File

@ -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;
}

View File

@ -3,6 +3,7 @@ EXPORTED {
# public symbols
global:
kdb_function_table;
certauth_ipakdb_initvt;
# everything else is local
local:

View File

@ -40,6 +40,7 @@
#include <arpa/inet.h>
#include <endian.h>
#include <unistd.h>
#include <krb5/certauth_plugin.h>
#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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <sbose@redhat.com>
*
* Copyright (C) 2017 Red Hat, Inc.
* All rights reserved.
* END COPYRIGHT BLOCK **/
#include <errno.h>
//#include <krb5/certauth_plugin.h>
#include <syslog.h>
#include <sss_certmap.h>
#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;
}

View File

@ -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

View File

@ -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 ---------------------------------------------------------------------------