summaryrefslogtreecommitdiffstats
path: root/Add-certauth-pluggable-interface.patch
diff options
context:
space:
mode:
authorRobbie Harwood <rharwood@redhat.com>2017-03-22 18:09:04 +0000
committerRobbie Harwood <rharwood@redhat.com>2017-03-22 18:09:06 +0000
commit0dc40d929f05d1372bdb642edfee725e1cc40853 (patch)
treeec08df79f3aa96e39d4920a74e89e80bc0351558 /Add-certauth-pluggable-interface.patch
parentfd8a9e22c43f7564f210aa473e40aa94b1d07476 (diff)
downloadkrb5-0dc40d929f05d1372bdb642edfee725e1cc40853.tar.gz
krb5-0dc40d929f05d1372bdb642edfee725e1cc40853.tar.xz
krb5-0dc40d929f05d1372bdb642edfee725e1cc40853.zip
Backport certauth plugin and related pkinit changes
Diffstat (limited to 'Add-certauth-pluggable-interface.patch')
-rw-r--r--Add-certauth-pluggable-interface.patch1143
1 files changed, 1143 insertions, 0 deletions
diff --git a/Add-certauth-pluggable-interface.patch b/Add-certauth-pluggable-interface.patch
new file mode 100644
index 0000000..d946c2f
--- /dev/null
+++ b/Add-certauth-pluggable-interface.patch
@@ -0,0 +1,1143 @@
+From ee26c1e3f7e98ed656b154c212bd5a335e87f312 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 6a48b95e3ad65605a657020385b34875677e8b75)
+---
+ 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 | 26 ++
+ src/plugins/preauth/pkinit/pkinit_srv.c | 340 ++++++++++++++++++---
+ src/plugins/preauth/pkinit/pkinit_trace.h | 5 +
+ src/tests/Makefile.in | 1 +
+ src/tests/t_certauth.py | 43 +++
+ 19 files changed, 783 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 653aad613..ac89e3b52 100644
+--- a/doc/admin/conf_files/krb5_conf.rst
++++ b/doc/admin/conf_files/krb5_conf.rst
+@@ -858,6 +858,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 the
++ PKINIT 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..8b0360327
+--- /dev/null
++++ b/doc/plugindev/certauth.rst
+@@ -0,0 +1,27 @@
++.. _certauth:
++
++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..c1276521b 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,28 @@ 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 *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 = malloc(len);
++ if (p == NULL)
++ return ENOMEM;
++ *der_out = p;
++ *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..23826c5e8 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,36 @@ 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;
++ certauth_handle h;
++
++ if (list == NULL)
++ return;
++ for (i = 0; list[i] != NULL; i++) {
++ h = list[i];
++ if (h->vt.fini != NULL)
++ h->vt.fini(context, h->moddata);
++ free(list[i]);
++ }
++ free(list);
++}
++
+ static krb5_error_code
+ pkinit_create_edata(krb5_context context,
+ pkinit_plg_crypto_context plg_cryptoctx,
+@@ -123,7 +172,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 +183,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 +325,76 @@ 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 hd;
++ 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 a crypto dependency on 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++) {
++ hd = certauth_modules[i];
++ if (hd->vt.authorize == NULL)
++ continue;
++
++ ret = hd->vt.authorize(context, hd->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;
++ }
++ hd->vt.free_ind(context, hd->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 +417,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 +509,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 +1350,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 +1440,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 +1596,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 +1628,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 +1671,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..ca7df2b42
+--- /dev/null
++++ b/src/tests/t_certauth.py
+@@ -0,0 +1,43 @@
++#!/usr/bin/python
++from k5test import *
++
++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")