diff options
author | Robbie Harwood <rharwood@redhat.com> | 2017-03-22 18:09:04 +0000 |
---|---|---|
committer | Robbie Harwood <rharwood@redhat.com> | 2017-03-22 18:09:06 +0000 |
commit | 0dc40d929f05d1372bdb642edfee725e1cc40853 (patch) | |
tree | ec08df79f3aa96e39d4920a74e89e80bc0351558 /Add-certauth-pluggable-interface.patch | |
parent | fd8a9e22c43f7564f210aa473e40aa94b1d07476 (diff) | |
download | krb5-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.patch | 1143 |
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") |