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