diff options
author | Robbie Harwood <rharwood@redhat.com> | 2017-10-05 15:10:47 -0400 |
---|---|---|
committer | Robbie Harwood <rharwood@redhat.com> | 2017-10-05 20:29:13 +0000 |
commit | 533a73fdd1bf9988853f3eb1a23c3f28a87454b8 (patch) | |
tree | 99b7add9fe5875dd24bd0e924e2b1da95dfad595 /Add-certauth-pluggable-interface.patch | |
parent | 0c7302b5bc5da01f88ea5ad6873a48a011c5fb54 (diff) | |
download | krb5-533a73fdd1bf9988853f3eb1a23c3f28a87454b8.tar.gz krb5-533a73fdd1bf9988853f3eb1a23c3f28a87454b8.tar.xz krb5-533a73fdd1bf9988853f3eb1a23c3f28a87454b8.zip |
New upstream prerelease (1.16-beta1)
Diffstat (limited to 'Add-certauth-pluggable-interface.patch')
-rw-r--r-- | Add-certauth-pluggable-interface.patch | 1146 |
1 files changed, 0 insertions, 1146 deletions
diff --git a/Add-certauth-pluggable-interface.patch b/Add-certauth-pluggable-interface.patch deleted file mode 100644 index a9adc3e..0000000 --- a/Add-certauth-pluggable-interface.patch +++ /dev/null @@ -1,1146 +0,0 @@ -From 43418f21de72060932661242126fe611b6b17d84 Mon Sep 17 00:00:00 2001 -From: Matt Rogers <mrogers@redhat.com> -Date: Tue, 28 Feb 2017 15:55:24 -0500 -Subject: [PATCH] Add certauth pluggable interface - -Add the header include/krb5/certauth_plugin.h, defining a pluggable -interface to control authorization of PKINIT client certificates. - -Add the "pkinit_san" and "pkinit_eku" builtin certauth modules and -related PKINIT crypto X.509 helper functions. Add authorize_cert() as -the entry function for certauth plugin module checks called in -pkinit_server_verify_padata(). Modify kdcpreauth_moddata to hold the -list of certauth module handles, and load the modules when the PKINIT -kdcpreauth server plugin is initialized. Change -crypto_retrieve_X509_sans() to return ENOENT when no SAN is found. - -Add test modules in plugins/certauth/test. Create t_certauth.py with -basic certauth tests. Add plugin interface documentation in -doc/plugindev/certauth.rst and doc/admin/krb5_conf.rst. - -[ghudson@mit.edu: simplified code, edited docs] - -ticket: 8561 (new) -(cherry picked from commit b619ce84470519bea65470be3263cd85fba94f57) ---- - doc/admin/conf_files/krb5_conf.rst | 21 ++ - doc/plugindev/certauth.rst | 27 ++ - doc/plugindev/index.rst | 1 + - src/Makefile.in | 1 + - src/configure.in | 1 + - src/include/Makefile.in | 1 + - src/include/k5-int.h | 3 +- - src/include/krb5/certauth_plugin.h | 103 +++++++ - src/lib/krb5/krb/plugin.c | 3 +- - src/plugins/certauth/test/Makefile.in | 20 ++ - src/plugins/certauth/test/certauth_test.exports | 2 + - src/plugins/certauth/test/deps | 14 + - src/plugins/certauth/test/main.c | 209 +++++++++++++ - src/plugins/preauth/pkinit/pkinit_crypto.h | 4 + - src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 30 ++ - src/plugins/preauth/pkinit/pkinit_srv.c | 335 ++++++++++++++++++--- - src/plugins/preauth/pkinit/pkinit_trace.h | 5 + - src/tests/Makefile.in | 1 + - src/tests/t_certauth.py | 47 +++ - 19 files changed, 786 insertions(+), 42 deletions(-) - create mode 100644 doc/plugindev/certauth.rst - create mode 100644 src/include/krb5/certauth_plugin.h - create mode 100644 src/plugins/certauth/test/Makefile.in - create mode 100644 src/plugins/certauth/test/certauth_test.exports - create mode 100644 src/plugins/certauth/test/deps - create mode 100644 src/plugins/certauth/test/main.c - create mode 100644 src/tests/t_certauth.py - -diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst -index 02a935961..1d9bc9e34 100644 ---- a/doc/admin/conf_files/krb5_conf.rst -+++ b/doc/admin/conf_files/krb5_conf.rst -@@ -859,6 +859,27 @@ built-in modules exist for this interface: - This module authorizes a principal to a local account if the - principal name maps to the local account name. - -+.. _certauth: -+ -+certauth interface -+################## -+ -+The certauth section (introduced in release 1.16) controls modules for -+the certificate authorization interface, which determines whether a -+certificate is allowed to preauthenticate a user via PKINIT. The -+following built-in modules exist for this interface: -+ -+**pkinit_san** -+ This module authorizes the certificate if it contains a PKINIT -+ Subject Alternative Name for the requested client principal, or a -+ Microsoft UPN SAN matching the principal if **pkinit_allow_upn** -+ is set to true for the realm. -+ -+**pkinit_eku** -+ This module rejects the certificate if it does not contain an -+ Extended Key Usage attribute consistent with the -+ **pkinit_eku_checking** value for the realm. -+ - - PKINIT options - -------------- -diff --git a/doc/plugindev/certauth.rst b/doc/plugindev/certauth.rst -new file mode 100644 -index 000000000..8a7f7c5eb ---- /dev/null -+++ b/doc/plugindev/certauth.rst -@@ -0,0 +1,27 @@ -+.. _certauth_plugin: -+ -+PKINIT certificate authorization interface (certauth) -+===================================================== -+ -+The certauth interface was first introduced in release 1.16. It -+allows customization of the X.509 certificate attribute requirements -+placed on certificates used by PKINIT enabled clients. For a detailed -+description of the certauth interface, see the header file -+``<krb5/certauth_plugin.h>`` -+ -+A certauth module implements the **authorize** method to determine -+whether a client's certificate is authorized to authenticate a client -+principal. **authorize** receives the DER-encoded certificate, the -+requested client principal, and a pointer to the client's -+krb5_db_entry (for modules that link against libkdb5). It returns the -+authorization status and optionally outputs a list of authentication -+indicator strings to be added to the ticket. A module must use its -+own internal or library-provided ASN.1 certificate decoder. -+ -+A module can optionally create and destroy module data with the -+**init** and **fini** methods. Module data objects last for the -+lifetime of the KDC process. -+ -+If a module allocates and returns a list of authentication indicators -+from **authorize**, it must also implement the **free_ind** method -+to free the list. -diff --git a/doc/plugindev/index.rst b/doc/plugindev/index.rst -index 3fb921778..67dbc2790 100644 ---- a/doc/plugindev/index.rst -+++ b/doc/plugindev/index.rst -@@ -31,5 +31,6 @@ Contents - profile.rst - gssapi.rst - internal.rst -+ certauth.rst - - .. TODO: GSSAPI mechanism plugins -diff --git a/src/Makefile.in b/src/Makefile.in -index 2ebf2fb4d..b0249778c 100644 ---- a/src/Makefile.in -+++ b/src/Makefile.in -@@ -17,6 +17,7 @@ SUBDIRS=util include lib \ - plugins/pwqual/test \ - plugins/authdata/greet_server \ - plugins/authdata/greet_client \ -+ plugins/certauth/test \ - plugins/kdb/db2 \ - @ldap_plugin_dir@ \ - plugins/kdb/test \ -diff --git a/src/configure.in b/src/configure.in -index acf3a458b..24f653f0d 100644 ---- a/src/configure.in -+++ b/src/configure.in -@@ -1451,6 +1451,7 @@ dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test - - kdc slave config-files build-tools man doc include - -+ plugins/certauth/test - plugins/hostrealm/test - plugins/localauth/test - plugins/kadm5_hook/test -diff --git a/src/include/Makefile.in b/src/include/Makefile.in -index f5b921833..0239338a1 100644 ---- a/src/include/Makefile.in -+++ b/src/include/Makefile.in -@@ -140,6 +140,7 @@ install-headers-unix install: krb5/krb5.h profile.h - $(INSTALL_DATA) $(srcdir)/krb5.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5.h - $(INSTALL_DATA) $(srcdir)/kdb.h $(DESTDIR)$(KRB5_INCDIR)$(S)kdb.h - $(INSTALL_DATA) krb5/krb5.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)krb5.h -+ $(INSTALL_DATA) $(srcdir)/krb5/certauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)certauth_plugin.h - $(INSTALL_DATA) $(srcdir)/krb5/ccselect_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)ccselect_plugin.h - $(INSTALL_DATA) $(srcdir)/krb5/clpreauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)clpreauth_plugin.h - $(INSTALL_DATA) $(srcdir)/krb5/hostrealm_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)hostrealm_plugin.h -diff --git a/src/include/k5-int.h b/src/include/k5-int.h -index 173cb0264..cea644d0a 100644 ---- a/src/include/k5-int.h -+++ b/src/include/k5-int.h -@@ -1156,7 +1156,8 @@ struct plugin_interface { - #define PLUGIN_INTERFACE_AUDIT 7 - #define PLUGIN_INTERFACE_TLS 8 - #define PLUGIN_INTERFACE_KDCAUTHDATA 9 --#define PLUGIN_NUM_INTERFACES 10 -+#define PLUGIN_INTERFACE_CERTAUTH 10 -+#define PLUGIN_NUM_INTERFACES 11 - - /* Retrieve the plugin module of type interface_id and name modname, - * storing the result into module. */ -diff --git a/src/include/krb5/certauth_plugin.h b/src/include/krb5/certauth_plugin.h -new file mode 100644 -index 000000000..f22fc1e84 ---- /dev/null -+++ b/src/include/krb5/certauth_plugin.h -@@ -0,0 +1,103 @@ -+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -+/* include/krb5/certauth_plugin.h - certauth plugin header. */ -+/* -+ * Copyright (C) 2017 by Red Hat, Inc. -+ * All rights reserved. -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions -+ * are met: -+ * -+ * * Redistributions of source code must retain the above copyright -+ * notice, this list of conditions and the following disclaimer. -+ * -+ * * 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. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE -+ * COPYRIGHT HOLDER 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. -+ */ -+ -+/* -+ * Certificate authorization plugin interface. The PKINIT server module uses -+ * this interface to check client certificate attributes after the certificate -+ * signature has been verified. -+ */ -+#ifndef KRB5_CERTAUTH_PLUGIN_H -+#define KRB5_CERTAUTH_PLUGIN_H -+ -+#include <krb5/krb5.h> -+#include <krb5/plugin.h> -+ -+/* Abstract module data type. */ -+typedef struct krb5_certauth_moddata_st *krb5_certauth_moddata; -+ -+typedef struct _krb5_db_entry_new krb5_db_entry; -+ -+/* -+ * Optional: Initialize module data. -+ */ -+typedef krb5_error_code -+(*krb5_certauth_init_fn)(krb5_context context, -+ krb5_certauth_moddata *moddata_out); -+ -+/* -+ * Optional: Clean up the module data. -+ */ -+typedef void -+(*krb5_certauth_fini_fn)(krb5_context context, krb5_certauth_moddata moddata); -+ -+/* -+ * Mandatory: -+ * Return 0 if the DER-encoded cert is authorized for PKINIT authentication by -+ * princ; otherwise return one of the following error codes: -+ * - KRB5KDC_ERR_CLIENT_NAME_MISMATCH - incorrect SAN value -+ * - KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE - incorrect EKU -+ * - KRB5KDC_ERR_CERTIFICATE_MISMATCH - other extension error -+ * - KRB5_PLUGIN_NO_HANDLE - the module has no opinion about cert -+ * -+ * - opts is used by built-in modules to receive internal data, and must be -+ * ignored by other modules. -+ * - db_entry receives the client principal database entry, and can be ignored -+ * by modules that do not link with libkdb5. -+ * - *authinds_out optionally returns a null-terminated list of authentication -+ * indicator strings upon KRB5_PLUGIN_NO_HANDLE or accepted authorization. -+ */ -+typedef krb5_error_code -+(*krb5_certauth_authorize_fn)(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); -+ -+/* -+ * Free indicators allocated by a module. Mandatory if authorize returns -+ * authentication indicators. -+ */ -+typedef void -+(*krb5_certauth_free_indicator_fn)(krb5_context context, -+ krb5_certauth_moddata moddata, -+ char **authinds); -+ -+typedef struct krb5_certauth_vtable_st { -+ char *name; -+ krb5_certauth_init_fn init; -+ krb5_certauth_fini_fn fini; -+ krb5_certauth_authorize_fn authorize; -+ krb5_certauth_free_indicator_fn free_ind; -+} *krb5_certauth_vtable; -+ -+#endif /* KRB5_CERTAUTH_PLUGIN_H */ -diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c -index 7d64b7c7e..17dd6bd30 100644 ---- a/src/lib/krb5/krb/plugin.c -+++ b/src/lib/krb5/krb/plugin.c -@@ -57,7 +57,8 @@ const char *interface_names[] = { - "hostrealm", - "audit", - "tls", -- "kdcauthdata" -+ "kdcauthdata", -+ "certauth" - }; - - /* Return the context's interface structure for id, or NULL if invalid. */ -diff --git a/src/plugins/certauth/test/Makefile.in b/src/plugins/certauth/test/Makefile.in -new file mode 100644 -index 000000000..d3524084c ---- /dev/null -+++ b/src/plugins/certauth/test/Makefile.in -@@ -0,0 +1,20 @@ -+mydir=plugins$(S)certauth$(S)test -+BUILDTOP=$(REL)..$(S)..$(S).. -+ -+LIBBASE=certauth_test -+LIBMAJOR=0 -+LIBMINOR=0 -+RELDIR=../plugins/certauth/test -+SHLIB_EXPDEPS=$(KRB5_BASE_DEPLIBS) -+SHLIB_EXPLIBS=$(KRB5_BASE_LIBS) -+ -+STLIBOBJS=main.o -+ -+SRCS=$(srcdir)/main.c -+ -+all-unix: all-libs -+install-unix: -+clean-unix:: clean-libs clean-libobjs -+ -+@libnover_frag@ -+@libobj_frag@ -diff --git a/src/plugins/certauth/test/certauth_test.exports b/src/plugins/certauth/test/certauth_test.exports -new file mode 100644 -index 000000000..1c8cd24e2 ---- /dev/null -+++ b/src/plugins/certauth/test/certauth_test.exports -@@ -0,0 +1,2 @@ -+certauth_test1_initvt -+certauth_test2_initvt -diff --git a/src/plugins/certauth/test/deps b/src/plugins/certauth/test/deps -new file mode 100644 -index 000000000..2974b3b57 ---- /dev/null -+++ b/src/plugins/certauth/test/deps -@@ -0,0 +1,14 @@ -+# -+# Generated makefile dependencies follow. -+# -+main.so main.po $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ -+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ -+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ -+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ -+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ -+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ -+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ -+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ -+ $(top_srcdir)/include/krb5/certauth_plugin.h $(top_srcdir)/include/krb5/plugin.h \ -+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ -+ main.c -diff --git a/src/plugins/certauth/test/main.c b/src/plugins/certauth/test/main.c -new file mode 100644 -index 000000000..7ef7377fb ---- /dev/null -+++ b/src/plugins/certauth/test/main.c -@@ -0,0 +1,209 @@ -+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -+/* plugins/certauth/main.c - certauth plugin test modules. */ -+/* -+ * Copyright (C) 2017 by Red Hat, Inc. -+ * All rights reserved. -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions -+ * are met: -+ * -+ * * Redistributions of source code must retain the above copyright -+ * notice, this list of conditions and the following disclaimer. -+ * -+ * * 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. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE -+ * COPYRIGHT HOLDER 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. -+ */ -+ -+#include <k5-int.h> -+#include "krb5/certauth_plugin.h" -+ -+struct krb5_certauth_moddata_st { -+ int initialized; -+}; -+ -+/* Test module 1 returns OK with an indicator. */ -+static krb5_error_code -+test1_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 **ais = NULL; -+ -+ ais = calloc(2, sizeof(*ais)); -+ assert(ais != NULL); -+ ais[0] = strdup("test1"); -+ assert(ais[0] != NULL); -+ *authinds_out = ais; -+ return KRB5_PLUGIN_NO_HANDLE; -+} -+ -+static void -+test_free_ind(krb5_context context, krb5_certauth_moddata moddata, -+ char **authinds) -+{ -+ size_t i; -+ -+ if (authinds == NULL) -+ return; -+ for (i = 0; authinds[i] != NULL; i++) -+ free(authinds[i]); -+ free(authinds); -+} -+ -+/* A basic moddata test. */ -+static krb5_error_code -+test2_init(krb5_context context, krb5_certauth_moddata *moddata_out) -+{ -+ krb5_certauth_moddata mod; -+ -+ mod = calloc(1, sizeof(*mod)); -+ assert(mod != NULL); -+ mod->initialized = 1; -+ *moddata_out = mod; -+ return 0; -+} -+ -+static void -+test2_fini(krb5_context context, krb5_certauth_moddata moddata) -+{ -+ free(moddata); -+} -+ -+/* Return true if cert appears to contain the CN name, based on a search of the -+ * DER encoding. */ -+static krb5_boolean -+has_cn(krb5_context context, const uint8_t *cert, size_t cert_len, -+ const char *name) -+{ -+ krb5_boolean match = FALSE; -+ uint8_t name_len, cntag[5] = "\x06\x03\x55\x04\x03"; -+ const uint8_t *c; -+ struct k5buf buf; -+ size_t c_left; -+ -+ /* Construct a DER search string of the CN AttributeType encoding followed -+ * by a UTF8String encoding containing name as the AttributeValue. */ -+ k5_buf_init_dynamic(&buf); -+ k5_buf_add_len(&buf, cntag, sizeof(cntag)); -+ k5_buf_add(&buf, "\x0C"); -+ assert(strlen(name) < 128); -+ name_len = strlen(name); -+ k5_buf_add_len(&buf, &name_len, 1); -+ k5_buf_add_len(&buf, name, name_len); -+ assert(k5_buf_status(&buf) == 0); -+ -+ /* Check for the CN needle in the certificate haystack. */ -+ c_left = cert_len; -+ c = memchr(cert, *cntag, c_left); -+ while (c != NULL) { -+ c_left = cert_len - (c - cert); -+ if (buf.len > c_left) -+ break; -+ if (memcmp(c, buf.data, buf.len) == 0) { -+ match = TRUE; -+ break; -+ } -+ assert(c_left >= 1); -+ c = memchr(c + 1, *cntag, c_left - 1); -+ } -+ -+ k5_buf_free(&buf); -+ return match; -+} -+ -+/* -+ * Test module 2 returns OK if princ matches the CN part of the subject name, -+ * and returns indicators of the module name and princ. -+ */ -+static krb5_error_code -+test2_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) -+{ -+ krb5_error_code ret; -+ char *name = NULL, **ais = NULL; -+ -+ *authinds_out = NULL; -+ -+ assert(moddata != NULL && moddata->initialized); -+ -+ ret = krb5_unparse_name_flags(context, princ, -+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name); -+ if (ret) -+ goto cleanup; -+ -+ if (!has_cn(context, cert, cert_len, name)) { -+ ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; -+ goto cleanup; -+ } -+ -+ /* Create an indicator list with the module name and CN. */ -+ ais = calloc(3, sizeof(*ais)); -+ assert(ais != NULL); -+ ais[0] = strdup("test2"); -+ ais[1] = strdup(name); -+ assert(ais[0] != NULL && ais[1] != NULL); -+ *authinds_out = ais; -+ -+ ais = NULL; -+ -+cleanup: -+ krb5_free_unparsed_name(context, name); -+ return ret; -+} -+ -+krb5_error_code -+certauth_test1_initvt(krb5_context context, int maj_ver, int min_ver, -+ krb5_plugin_vtable vtable); -+krb5_error_code -+certauth_test1_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 = "test1"; -+ vt->authorize = test1_authorize; -+ vt->free_ind = test_free_ind; -+ return 0; -+} -+ -+krb5_error_code -+certauth_test2_initvt(krb5_context context, int maj_ver, int min_ver, -+ krb5_plugin_vtable vtable); -+krb5_error_code -+certauth_test2_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 = "test2"; -+ vt->authorize = test2_authorize; -+ vt->init = test2_init; -+ vt->fini = test2_fini; -+ vt->free_ind = test_free_ind; -+ return 0; -+} -diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h -index b483affed..49b96b8ee 100644 ---- a/src/plugins/preauth/pkinit/pkinit_crypto.h -+++ b/src/plugins/preauth/pkinit/pkinit_crypto.h -@@ -664,4 +664,8 @@ extern const size_t krb5_pkinit_sha512_oid_len; - */ - extern krb5_data const * const supported_kdf_alg_ids[]; - -+krb5_error_code -+crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx, -+ uint8_t **der_out, size_t *der_len); -+ - #endif /* _PKINIT_CRYPTO_H */ -diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c -index 8def8c542..a5b010b26 100644 ---- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c -+++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c -@@ -2137,6 +2137,7 @@ crypto_retrieve_X509_sans(krb5_context context, - - if (!(ext = X509_get_ext(cert, l)) || !(ialt = X509V3_EXT_d2i(ext))) { - pkiDebug("%s: found no subject alt name extensions\n", __FUNCTION__); -+ retval = ENOENT; - goto cleanup; - } - num_sans = sk_GENERAL_NAME_num(ialt); -@@ -6176,3 +6177,32 @@ crypto_get_deferred_ids(krb5_context context, - ret = (const pkinit_deferred_id *)deferred; - return ret; - } -+ -+/* Return the received certificate as DER-encoded data. */ -+krb5_error_code -+crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx, -+ uint8_t **der_out, size_t *der_len) -+{ -+ int len; -+ unsigned char *der, *p; -+ -+ *der_out = NULL; -+ *der_len = 0; -+ -+ if (reqctx->received_cert == NULL) -+ return EINVAL; -+ p = NULL; -+ len = i2d_X509(reqctx->received_cert, NULL); -+ if (len <= 0) -+ return EINVAL; -+ p = der = malloc(len); -+ if (p == NULL) -+ return ENOMEM; -+ if (i2d_X509(reqctx->received_cert, &p) <= 0) { -+ free(p); -+ return EINVAL; -+ } -+ *der_out = der; -+ *der_len = len; -+ return 0; -+} -diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c -index b5638a367..731d14eb8 100644 ---- a/src/plugins/preauth/pkinit/pkinit_srv.c -+++ b/src/plugins/preauth/pkinit/pkinit_srv.c -@@ -31,6 +31,25 @@ - - #include <k5-int.h> - #include "pkinit.h" -+#include "krb5/certauth_plugin.h" -+ -+/* Aliases used by the built-in certauth modules */ -+struct certauth_req_opts { -+ krb5_kdcpreauth_callbacks cb; -+ krb5_kdcpreauth_rock rock; -+ pkinit_kdc_context plgctx; -+ pkinit_kdc_req_context reqctx; -+}; -+ -+typedef struct certauth_module_handle_st { -+ struct krb5_certauth_vtable_st vt; -+ krb5_certauth_moddata moddata; -+} *certauth_handle; -+ -+struct krb5_kdcpreauth_moddata_st { -+ pkinit_kdc_context *realm_contexts; -+ certauth_handle *certauth_modules; -+}; - - static krb5_error_code - pkinit_init_kdc_req_context(krb5_context, pkinit_kdc_req_context *blob); -@@ -51,6 +70,34 @@ pkinit_find_realm_context(krb5_context context, - krb5_kdcpreauth_moddata moddata, - krb5_principal princ); - -+static void -+free_realm_contexts(krb5_context context, pkinit_kdc_context *realm_contexts) -+{ -+ int i; -+ -+ if (realm_contexts == NULL) -+ return; -+ for (i = 0; realm_contexts[i] != NULL; i++) -+ pkinit_server_plugin_fini_realm(context, realm_contexts[i]); -+ pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts); -+ free(realm_contexts); -+} -+ -+static void -+free_certauth_handles(krb5_context context, certauth_handle *list) -+{ -+ int i; -+ -+ if (list == NULL) -+ return; -+ for (i = 0; list[i] != NULL; i++) { -+ if (list[i]->vt.fini != NULL) -+ list[i]->vt.fini(context, list[i]->moddata); -+ free(list[i]); -+ } -+ free(list); -+} -+ - static krb5_error_code - pkinit_create_edata(krb5_context context, - pkinit_plg_crypto_context plg_cryptoctx, -@@ -123,7 +170,7 @@ verify_client_san(krb5_context context, - pkinit_kdc_req_context reqctx, - krb5_kdcpreauth_callbacks cb, - krb5_kdcpreauth_rock rock, -- krb5_principal client, -+ krb5_const_principal client, - int *valid_san) - { - krb5_error_code retval; -@@ -134,12 +181,15 @@ verify_client_san(krb5_context context, - char *client_string = NULL, *san_string; - #endif - -+ *valid_san = 0; - retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx, - reqctx->cryptoctx, plgctx->idctx, - &princs, - plgctx->opts->allow_upn ? &upns : NULL, - NULL); -- if (retval) { -+ if (retval == ENOENT) { -+ goto out; -+ } else if (retval) { - pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__); - retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; - goto out; -@@ -273,6 +323,73 @@ out: - return retval; - } - -+ -+/* Run the received, verified certificate through certauth modules, to verify -+ * that it is authorized to authenticate as client. */ -+static krb5_error_code -+authorize_cert(krb5_context context, certauth_handle *certauth_modules, -+ pkinit_kdc_context plgctx, pkinit_kdc_req_context reqctx, -+ krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, -+ krb5_principal client) -+{ -+ krb5_error_code ret; -+ certauth_handle h; -+ struct certauth_req_opts opts; -+ krb5_boolean accepted = FALSE; -+ uint8_t *cert; -+ size_t i, cert_len; -+ void *db_ent = NULL; -+ char **ais = NULL, **ai = NULL; -+ -+ /* Re-encode the received certificate into DER, which is extra work, but -+ * avoids creating an X.509 library dependency in the interface. */ -+ ret = crypto_encode_der_cert(context, reqctx->cryptoctx, &cert, &cert_len); -+ if (ret) -+ goto cleanup; -+ -+ /* Set options for the builtin module. */ -+ opts.plgctx = plgctx; -+ opts.reqctx = reqctx; -+ opts.cb = cb; -+ opts.rock = rock; -+ -+ db_ent = cb->client_entry(context, rock); -+ -+ /* -+ * Check the certificate against each certauth module. For the certificate -+ * to be authorized at least one module must return 0, and no module can an -+ * error code other than KRB5_PLUGIN_NO_HANDLE (pass). Add indicators from -+ * modules that return 0 or pass. -+ */ -+ ret = KRB5_PLUGIN_NO_HANDLE; -+ for (i = 0; certauth_modules != NULL && certauth_modules[i] != NULL; i++) { -+ h = certauth_modules[i]; -+ ret = h->vt.authorize(context, h->moddata, cert, cert_len, client, -+ &opts, db_ent, &ais); -+ if (ret == 0) -+ accepted = TRUE; -+ else if (ret != KRB5_PLUGIN_NO_HANDLE) -+ goto cleanup; -+ -+ if (ais != NULL) { -+ /* Assert authentication indicators from the module. */ -+ for (ai = ais; *ai != NULL; ai++) { -+ ret = cb->add_auth_indicator(context, rock, *ai); -+ if (ret) -+ goto cleanup; -+ } -+ h->vt.free_ind(context, h->moddata, ais); -+ ais = NULL; -+ } -+ } -+ -+ ret = accepted ? 0 : KRB5KDC_ERR_CLIENT_NAME_MISMATCH; -+ -+cleanup: -+ free(cert); -+ return ret; -+} -+ - static void - pkinit_server_verify_padata(krb5_context context, - krb5_data *req_pkt, -@@ -295,7 +412,6 @@ pkinit_server_verify_padata(krb5_context context, - pkinit_kdc_req_context reqctx = NULL; - krb5_checksum cksum = {0, 0, 0, NULL}; - krb5_data *der_req = NULL; -- int valid_eku = 0, valid_san = 0; - krb5_data k5data; - int is_signed = 1; - krb5_pa_data **e_data = NULL; -@@ -388,27 +504,11 @@ pkinit_server_verify_padata(krb5_context context, - goto cleanup; - } - if (is_signed) { -- -- retval = verify_client_san(context, plgctx, reqctx, cb, rock, -- request->client, &valid_san); -- if (retval) -- goto cleanup; -- if (!valid_san) { -- pkiDebug("%s: did not find an acceptable SAN in user " -- "certificate\n", __FUNCTION__); -- retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; -- goto cleanup; -- } -- retval = verify_client_eku(context, plgctx, reqctx, &valid_eku); -+ retval = authorize_cert(context, moddata->certauth_modules, plgctx, -+ reqctx, cb, rock, request->client); - if (retval) - goto cleanup; - -- if (!valid_eku) { -- pkiDebug("%s: did not find an acceptable EKU in user " -- "certificate\n", __FUNCTION__); -- retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; -- goto cleanup; -- } - } else { /* !is_signed */ - if (!krb5_principal_compare(context, request->client, - krb5_anonymous_principal())) { -@@ -1245,11 +1345,15 @@ pkinit_find_realm_context(krb5_context context, - krb5_principal princ) - { - int i; -- pkinit_kdc_context *realm_contexts = (pkinit_kdc_context *)moddata; -+ pkinit_kdc_context *realm_contexts; - - if (moddata == NULL) - return NULL; - -+ realm_contexts = moddata->realm_contexts; -+ if (realm_contexts == NULL) -+ return NULL; -+ - for (i = 0; realm_contexts[i] != NULL; i++) { - pkinit_kdc_context p = realm_contexts[i]; - -@@ -1331,6 +1435,155 @@ errout: - return retval; - } - -+static krb5_error_code -+pkinit_san_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) -+{ -+ krb5_error_code ret; -+ int valid_san; -+ const struct certauth_req_opts *req_opts = opts; -+ -+ *authinds_out = NULL; -+ -+ ret = verify_client_san(context, req_opts->plgctx, req_opts->reqctx, -+ req_opts->cb, req_opts->rock, princ, &valid_san); -+ if (ret == ENOENT) -+ return KRB5_PLUGIN_NO_HANDLE; -+ else if (ret) -+ return ret; -+ -+ if (!valid_san) { -+ pkiDebug("%s: did not find an acceptable SAN in user certificate\n", -+ __FUNCTION__); -+ return KRB5KDC_ERR_CLIENT_NAME_MISMATCH; -+ } -+ -+ return 0; -+} -+ -+static krb5_error_code -+pkinit_eku_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) -+{ -+ krb5_error_code ret; -+ int valid_eku; -+ const struct certauth_req_opts *req_opts = opts; -+ -+ *authinds_out = NULL; -+ -+ /* Verify the client EKU. */ -+ ret = verify_client_eku(context, req_opts->plgctx, req_opts->reqctx, -+ &valid_eku); -+ if (ret) -+ return ret; -+ -+ if (!valid_eku) { -+ pkiDebug("%s: did not find an acceptable EKU in user certificate\n", -+ __FUNCTION__); -+ return KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; -+ } -+ -+ return 0; -+} -+ -+static krb5_error_code -+certauth_pkinit_san_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 = "pkinit_san"; -+ vt->authorize = pkinit_san_authorize; -+ return 0; -+} -+ -+static krb5_error_code -+certauth_pkinit_eku_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 = "pkinit_eku"; -+ vt->authorize = pkinit_eku_authorize; -+ return 0; -+} -+ -+static krb5_error_code -+load_certauth_plugins(krb5_context context, certauth_handle **handle_out) -+{ -+ krb5_error_code ret; -+ krb5_plugin_initvt_fn *modules = NULL, *mod; -+ certauth_handle *list = NULL, h; -+ size_t count; -+ -+ /* Register the builtin modules. */ -+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH, -+ "pkinit_san", certauth_pkinit_san_initvt); -+ if (ret) -+ goto cleanup; -+ -+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH, -+ "pkinit_eku", certauth_pkinit_eku_initvt); -+ if (ret) -+ goto cleanup; -+ -+ ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CERTAUTH, &modules); -+ if (ret) -+ goto cleanup; -+ -+ /* Allocate handle list. */ -+ for (count = 0; modules[count]; count++); -+ list = k5calloc(count + 1, sizeof(*list), &ret); -+ if (list == NULL) -+ goto cleanup; -+ -+ /* Initialize each module, ignoring ones that fail. */ -+ count = 0; -+ for (mod = modules; *mod != NULL; mod++) { -+ h = k5calloc(1, sizeof(*h), &ret); -+ if (h == NULL) -+ goto cleanup; -+ -+ ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt); -+ if (ret) { -+ TRACE_CERTAUTH_VTINIT_FAIL(context, ret); -+ free(h); -+ continue; -+ } -+ h->moddata = NULL; -+ if (h->vt.init != NULL) { -+ ret = h->vt.init(context, &h->moddata); -+ if (ret) { -+ TRACE_CERTAUTH_INIT_FAIL(context, h->vt.name, ret); -+ free(h); -+ continue; -+ } -+ } -+ list[count++] = h; -+ list[count] = NULL; -+ } -+ list[count] = NULL; -+ -+ ret = 0; -+ *handle_out = list; -+ list = NULL; -+ -+cleanup: -+ k5_plugin_free_modules(context, modules); -+ free_certauth_handles(context, list); -+ return ret; -+} -+ - static int - pkinit_server_plugin_init(krb5_context context, - krb5_kdcpreauth_moddata *moddata_out, -@@ -1338,6 +1591,8 @@ pkinit_server_plugin_init(krb5_context context, - { - krb5_error_code retval = ENOMEM; - pkinit_kdc_context plgctx, *realm_contexts = NULL; -+ certauth_handle *certauth_modules = NULL; -+ krb5_kdcpreauth_moddata moddata; - size_t i, j; - size_t numrealms; - -@@ -1368,16 +1623,22 @@ pkinit_server_plugin_init(krb5_context context, - goto errout; - } - -- *moddata_out = (krb5_kdcpreauth_moddata)realm_contexts; -- retval = 0; -- pkiDebug("%s: returning context at %p\n", __FUNCTION__, realm_contexts); -+ retval = load_certauth_plugins(context, &certauth_modules); -+ if (retval) -+ goto errout; -+ -+ moddata = k5calloc(1, sizeof(*moddata), &retval); -+ if (moddata == NULL) -+ goto errout; -+ moddata->realm_contexts = realm_contexts; -+ moddata->certauth_modules = certauth_modules; -+ *moddata_out = moddata; -+ pkiDebug("%s: returning context at %p\n", __FUNCTION__, moddata); -+ return 0; - - errout: -- if (retval) { -- pkinit_server_plugin_fini(context, -- (krb5_kdcpreauth_moddata)realm_contexts); -- } -- -+ free_realm_contexts(context, realm_contexts); -+ free_certauth_handles(context, certauth_modules); - return retval; - } - -@@ -1405,17 +1666,11 @@ static void - pkinit_server_plugin_fini(krb5_context context, - krb5_kdcpreauth_moddata moddata) - { -- pkinit_kdc_context *realm_contexts = (pkinit_kdc_context *)moddata; -- int i; -- -- if (realm_contexts == NULL) -+ if (moddata == NULL) - return; -- -- for (i = 0; realm_contexts[i] != NULL; i++) { -- pkinit_server_plugin_fini_realm(context, realm_contexts[i]); -- } -- pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts); -- free(realm_contexts); -+ free_realm_contexts(context, moddata->realm_contexts); -+ free_certauth_handles(context, moddata->certauth_modules); -+ free(moddata); - } - - static krb5_error_code -diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h -index b3f5cbb20..458d0961e 100644 ---- a/src/plugins/preauth/pkinit/pkinit_trace.h -+++ b/src/plugins/preauth/pkinit/pkinit_trace.h -@@ -91,4 +91,9 @@ - #define TRACE_PKINIT_OPENSSL_ERROR(c, msg) \ - TRACE(c, "PKINIT OpenSSL error: {str}", msg) - -+#define TRACE_CERTAUTH_VTINIT_FAIL(c, ret) \ -+ TRACE(c, "certauth module failed to init vtable: {kerr}", ret) -+#define TRACE_CERTAUTH_INIT_FAIL(c, name, ret) \ -+ TRACE(c, "certauth module {str} failed to init: {kerr}", name, ret) -+ - #endif /* PKINIT_TRACE_H */ -diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in -index b55469146..0e93d6b59 100644 ---- a/src/tests/Makefile.in -+++ b/src/tests/Makefile.in -@@ -167,6 +167,7 @@ check-pytests: localauth plugorder rdreq responder s2p s4u2proxy unlockiter - $(RUNPYTEST) $(srcdir)/t_preauth.py $(PYTESTFLAGS) - $(RUNPYTEST) $(srcdir)/t_princflags.py $(PYTESTFLAGS) - $(RUNPYTEST) $(srcdir)/t_tabdump.py $(PYTESTFLAGS) -+ $(RUNPYTEST) $(srcdir)/t_certauth.py $(PYTESTFLAGS) - - clean: - $(RM) adata etinfo forward gcred hist hooks hrealm icred kdbtest -diff --git a/src/tests/t_certauth.py b/src/tests/t_certauth.py -new file mode 100644 -index 000000000..e64a57b0d ---- /dev/null -+++ b/src/tests/t_certauth.py -@@ -0,0 +1,47 @@ -+#!/usr/bin/python -+from k5test import * -+ -+# Skip this test if pkinit wasn't built. -+if not os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so')): -+ skip_rest('certauth tests', 'PKINIT module not built') -+ -+certs = os.path.join(srctop, 'tests', 'dejagnu', 'pkinit-certs') -+ca_pem = os.path.join(certs, 'ca.pem') -+kdc_pem = os.path.join(certs, 'kdc.pem') -+privkey_pem = os.path.join(certs, 'privkey.pem') -+user_pem = os.path.join(certs, 'user.pem') -+ -+modpath = os.path.join(buildtop, 'plugins', 'certauth', 'test', -+ 'certauth_test.so') -+pkinit_krb5_conf = {'realms': {'$realm': { -+ 'pkinit_anchors': 'FILE:%s' % ca_pem}}, -+ 'plugins': {'certauth': {'module': ['test1:' + modpath, -+ 'test2:' + modpath], -+ 'enable_only': ['test1', 'test2']}}} -+pkinit_kdc_conf = {'realms': {'$realm': { -+ 'default_principal_flags': '+preauth', -+ 'pkinit_eku_checking': 'none', -+ 'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem), -+ 'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}} -+ -+file_identity = 'FILE:%s,%s' % (user_pem, privkey_pem) -+ -+realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=pkinit_kdc_conf, -+ get_creds=False) -+ -+# Let the test module match user to CN=user, with indicators. -+realm.kinit(realm.user_princ, -+ flags=['-X', 'X509_user_identity=%s' % file_identity]) -+realm.klist(realm.user_princ) -+realm.run([kvno, realm.host_princ]) -+realm.run(['./adata', realm.host_princ], -+ expected_msg='+97: [test1, test2, user, indpkinit1, indpkinit2]') -+ -+# Let the test module mismatch with user2 to CN=user. -+realm.addprinc("user2@KRBTEST.COM") -+out = realm.kinit("user2@KRBTEST.COM", -+ flags=['-X', 'X509_user_identity=%s' % file_identity], -+ expected_code=1, -+ expected_msg='kinit: Certificate mismatch') -+ -+success("certauth tests") |