aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/preauth
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/preauth')
-rw-r--r--src/plugins/preauth/pkinit/pkinit_crypto.h4
-rw-r--r--src/plugins/preauth/pkinit/pkinit_crypto_openssl.c30
-rw-r--r--src/plugins/preauth/pkinit/pkinit_srv.c335
-rw-r--r--src/plugins/preauth/pkinit/pkinit_trace.h5
4 files changed, 334 insertions, 40 deletions
diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h
index b483aff..49b96b8 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 2ea2a49..c79edae 100644
--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
+++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
@@ -2133,6 +2133,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);
@@ -6165,3 +6166,32 @@ crypto_get_deferred_ids(krb5_context context,
ret = (const pkinit_deferred_id *)deferred;
return ret;
}
+
+/* Return the received certificate as DER-encoded data. */
+krb5_error_code
+crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx,
+ uint8_t **der_out, size_t *der_len)
+{
+ int len;
+ unsigned char *der, *p;
+
+ *der_out = NULL;
+ *der_len = 0;
+
+ if (reqctx->received_cert == NULL)
+ return EINVAL;
+ p = NULL;
+ len = i2d_X509(reqctx->received_cert, NULL);
+ if (len <= 0)
+ return EINVAL;
+ p = der = malloc(len);
+ if (p == NULL)
+ return ENOMEM;
+ if (i2d_X509(reqctx->received_cert, &p) <= 0) {
+ free(p);
+ return EINVAL;
+ }
+ *der_out = der;
+ *der_len = len;
+ return 0;
+}
diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c
index b5638a3..731d14e 100644
--- a/src/plugins/preauth/pkinit/pkinit_srv.c
+++ b/src/plugins/preauth/pkinit/pkinit_srv.c
@@ -31,6 +31,25 @@
#include <k5-int.h>
#include "pkinit.h"
+#include "krb5/certauth_plugin.h"
+
+/* Aliases used by the built-in certauth modules */
+struct certauth_req_opts {
+ krb5_kdcpreauth_callbacks cb;
+ krb5_kdcpreauth_rock rock;
+ pkinit_kdc_context plgctx;
+ pkinit_kdc_req_context reqctx;
+};
+
+typedef struct certauth_module_handle_st {
+ struct krb5_certauth_vtable_st vt;
+ krb5_certauth_moddata moddata;
+} *certauth_handle;
+
+struct krb5_kdcpreauth_moddata_st {
+ pkinit_kdc_context *realm_contexts;
+ certauth_handle *certauth_modules;
+};
static krb5_error_code
pkinit_init_kdc_req_context(krb5_context, pkinit_kdc_req_context *blob);
@@ -51,6 +70,34 @@ pkinit_find_realm_context(krb5_context context,
krb5_kdcpreauth_moddata moddata,
krb5_principal princ);
+static void
+free_realm_contexts(krb5_context context, pkinit_kdc_context *realm_contexts)
+{
+ int i;
+
+ if (realm_contexts == NULL)
+ return;
+ for (i = 0; realm_contexts[i] != NULL; i++)
+ pkinit_server_plugin_fini_realm(context, realm_contexts[i]);
+ pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts);
+ free(realm_contexts);
+}
+
+static void
+free_certauth_handles(krb5_context context, certauth_handle *list)
+{
+ int i;
+
+ if (list == NULL)
+ return;
+ for (i = 0; list[i] != NULL; i++) {
+ if (list[i]->vt.fini != NULL)
+ list[i]->vt.fini(context, list[i]->moddata);
+ free(list[i]);
+ }
+ free(list);
+}
+
static krb5_error_code
pkinit_create_edata(krb5_context context,
pkinit_plg_crypto_context plg_cryptoctx,
@@ -123,7 +170,7 @@ verify_client_san(krb5_context context,
pkinit_kdc_req_context reqctx,
krb5_kdcpreauth_callbacks cb,
krb5_kdcpreauth_rock rock,
- krb5_principal client,
+ krb5_const_principal client,
int *valid_san)
{
krb5_error_code retval;
@@ -134,12 +181,15 @@ verify_client_san(krb5_context context,
char *client_string = NULL, *san_string;
#endif
+ *valid_san = 0;
retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx,
reqctx->cryptoctx, plgctx->idctx,
&princs,
plgctx->opts->allow_upn ? &upns : NULL,
NULL);
- if (retval) {
+ if (retval == ENOENT) {
+ goto out;
+ } else if (retval) {
pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__);
retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
goto out;
@@ -273,6 +323,73 @@ out:
return retval;
}
+
+/* Run the received, verified certificate through certauth modules, to verify
+ * that it is authorized to authenticate as client. */
+static krb5_error_code
+authorize_cert(krb5_context context, certauth_handle *certauth_modules,
+ pkinit_kdc_context plgctx, pkinit_kdc_req_context reqctx,
+ krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
+ krb5_principal client)
+{
+ krb5_error_code ret;
+ certauth_handle h;
+ struct certauth_req_opts opts;
+ krb5_boolean accepted = FALSE;
+ uint8_t *cert;
+ size_t i, cert_len;
+ void *db_ent = NULL;
+ char **ais = NULL, **ai = NULL;
+
+ /* Re-encode the received certificate into DER, which is extra work, but
+ * avoids creating an X.509 library dependency in the interface. */
+ ret = crypto_encode_der_cert(context, reqctx->cryptoctx, &cert, &cert_len);
+ if (ret)
+ goto cleanup;
+
+ /* Set options for the builtin module. */
+ opts.plgctx = plgctx;
+ opts.reqctx = reqctx;
+ opts.cb = cb;
+ opts.rock = rock;
+
+ db_ent = cb->client_entry(context, rock);
+
+ /*
+ * Check the certificate against each certauth module. For the certificate
+ * to be authorized at least one module must return 0, and no module can an
+ * error code other than KRB5_PLUGIN_NO_HANDLE (pass). Add indicators from
+ * modules that return 0 or pass.
+ */
+ ret = KRB5_PLUGIN_NO_HANDLE;
+ for (i = 0; certauth_modules != NULL && certauth_modules[i] != NULL; i++) {
+ h = certauth_modules[i];
+ ret = h->vt.authorize(context, h->moddata, cert, cert_len, client,
+ &opts, db_ent, &ais);
+ if (ret == 0)
+ accepted = TRUE;
+ else if (ret != KRB5_PLUGIN_NO_HANDLE)
+ goto cleanup;
+
+ if (ais != NULL) {
+ /* Assert authentication indicators from the module. */
+ for (ai = ais; *ai != NULL; ai++) {
+ ret = cb->add_auth_indicator(context, rock, *ai);
+ if (ret)
+ goto cleanup;
+ }
+ h->vt.free_ind(context, h->moddata, ais);
+ ais = NULL;
+ }
+ }
+
+ ret = accepted ? 0 : KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
+
+cleanup:
+ free(cert);
+ return ret;
+}
+
static void
pkinit_server_verify_padata(krb5_context context,
krb5_data *req_pkt,
@@ -295,7 +412,6 @@ pkinit_server_verify_padata(krb5_context context,
pkinit_kdc_req_context reqctx = NULL;
krb5_checksum cksum = {0, 0, 0, NULL};
krb5_data *der_req = NULL;
- int valid_eku = 0, valid_san = 0;
krb5_data k5data;
int is_signed = 1;
krb5_pa_data **e_data = NULL;
@@ -388,27 +504,11 @@ pkinit_server_verify_padata(krb5_context context,
goto cleanup;
}
if (is_signed) {
-
- retval = verify_client_san(context, plgctx, reqctx, cb, rock,
- request->client, &valid_san);
- if (retval)
- goto cleanup;
- if (!valid_san) {
- pkiDebug("%s: did not find an acceptable SAN in user "
- "certificate\n", __FUNCTION__);
- retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
- goto cleanup;
- }
- retval = verify_client_eku(context, plgctx, reqctx, &valid_eku);
+ retval = authorize_cert(context, moddata->certauth_modules, plgctx,
+ reqctx, cb, rock, request->client);
if (retval)
goto cleanup;
- if (!valid_eku) {
- pkiDebug("%s: did not find an acceptable EKU in user "
- "certificate\n", __FUNCTION__);
- retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;
- goto cleanup;
- }
} else { /* !is_signed */
if (!krb5_principal_compare(context, request->client,
krb5_anonymous_principal())) {
@@ -1245,11 +1345,15 @@ pkinit_find_realm_context(krb5_context context,
krb5_principal princ)
{
int i;
- pkinit_kdc_context *realm_contexts = (pkinit_kdc_context *)moddata;
+ pkinit_kdc_context *realm_contexts;
if (moddata == NULL)
return NULL;
+ realm_contexts = moddata->realm_contexts;
+ if (realm_contexts == NULL)
+ return NULL;
+
for (i = 0; realm_contexts[i] != NULL; i++) {
pkinit_kdc_context p = realm_contexts[i];
@@ -1331,6 +1435,155 @@ errout:
return retval;
}
+static krb5_error_code
+pkinit_san_authorize(krb5_context context, krb5_certauth_moddata moddata,
+ const uint8_t *cert, size_t cert_len,
+ krb5_const_principal princ, const void *opts,
+ const krb5_db_entry *db_entry, char ***authinds_out)
+{
+ krb5_error_code ret;
+ int valid_san;
+ const struct certauth_req_opts *req_opts = opts;
+
+ *authinds_out = NULL;
+
+ ret = verify_client_san(context, req_opts->plgctx, req_opts->reqctx,
+ req_opts->cb, req_opts->rock, princ, &valid_san);
+ if (ret == ENOENT)
+ return KRB5_PLUGIN_NO_HANDLE;
+ else if (ret)
+ return ret;
+
+ if (!valid_san) {
+ pkiDebug("%s: did not find an acceptable SAN in user certificate\n",
+ __FUNCTION__);
+ return KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+pkinit_eku_authorize(krb5_context context, krb5_certauth_moddata moddata,
+ const uint8_t *cert, size_t cert_len,
+ krb5_const_principal princ, const void *opts,
+ const krb5_db_entry *db_entry, char ***authinds_out)
+{
+ krb5_error_code ret;
+ int valid_eku;
+ const struct certauth_req_opts *req_opts = opts;
+
+ *authinds_out = NULL;
+
+ /* Verify the client EKU. */
+ ret = verify_client_eku(context, req_opts->plgctx, req_opts->reqctx,
+ &valid_eku);
+ if (ret)
+ return ret;
+
+ if (!valid_eku) {
+ pkiDebug("%s: did not find an acceptable EKU in user certificate\n",
+ __FUNCTION__);
+ return KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+certauth_pkinit_san_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_certauth_vtable vt;
+
+ if (maj_ver != 1)
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ vt = (krb5_certauth_vtable)vtable;
+ vt->name = "pkinit_san";
+ vt->authorize = pkinit_san_authorize;
+ return 0;
+}
+
+static krb5_error_code
+certauth_pkinit_eku_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_certauth_vtable vt;
+
+ if (maj_ver != 1)
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ vt = (krb5_certauth_vtable)vtable;
+ vt->name = "pkinit_eku";
+ vt->authorize = pkinit_eku_authorize;
+ return 0;
+}
+
+static krb5_error_code
+load_certauth_plugins(krb5_context context, certauth_handle **handle_out)
+{
+ krb5_error_code ret;
+ krb5_plugin_initvt_fn *modules = NULL, *mod;
+ certauth_handle *list = NULL, h;
+ size_t count;
+
+ /* Register the builtin modules. */
+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH,
+ "pkinit_san", certauth_pkinit_san_initvt);
+ if (ret)
+ goto cleanup;
+
+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH,
+ "pkinit_eku", certauth_pkinit_eku_initvt);
+ if (ret)
+ goto cleanup;
+
+ ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CERTAUTH, &modules);
+ if (ret)
+ goto cleanup;
+
+ /* Allocate handle list. */
+ for (count = 0; modules[count]; count++);
+ list = k5calloc(count + 1, sizeof(*list), &ret);
+ if (list == NULL)
+ goto cleanup;
+
+ /* Initialize each module, ignoring ones that fail. */
+ count = 0;
+ for (mod = modules; *mod != NULL; mod++) {
+ h = k5calloc(1, sizeof(*h), &ret);
+ if (h == NULL)
+ goto cleanup;
+
+ ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);
+ if (ret) {
+ TRACE_CERTAUTH_VTINIT_FAIL(context, ret);
+ free(h);
+ continue;
+ }
+ h->moddata = NULL;
+ if (h->vt.init != NULL) {
+ ret = h->vt.init(context, &h->moddata);
+ if (ret) {
+ TRACE_CERTAUTH_INIT_FAIL(context, h->vt.name, ret);
+ free(h);
+ continue;
+ }
+ }
+ list[count++] = h;
+ list[count] = NULL;
+ }
+ list[count] = NULL;
+
+ ret = 0;
+ *handle_out = list;
+ list = NULL;
+
+cleanup:
+ k5_plugin_free_modules(context, modules);
+ free_certauth_handles(context, list);
+ return ret;
+}
+
static int
pkinit_server_plugin_init(krb5_context context,
krb5_kdcpreauth_moddata *moddata_out,
@@ -1338,6 +1591,8 @@ pkinit_server_plugin_init(krb5_context context,
{
krb5_error_code retval = ENOMEM;
pkinit_kdc_context plgctx, *realm_contexts = NULL;
+ certauth_handle *certauth_modules = NULL;
+ krb5_kdcpreauth_moddata moddata;
size_t i, j;
size_t numrealms;
@@ -1368,16 +1623,22 @@ pkinit_server_plugin_init(krb5_context context,
goto errout;
}
- *moddata_out = (krb5_kdcpreauth_moddata)realm_contexts;
- retval = 0;
- pkiDebug("%s: returning context at %p\n", __FUNCTION__, realm_contexts);
+ retval = load_certauth_plugins(context, &certauth_modules);
+ if (retval)
+ goto errout;
-errout:
- if (retval) {
- pkinit_server_plugin_fini(context,
- (krb5_kdcpreauth_moddata)realm_contexts);
- }
+ 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:
+ free_realm_contexts(context, realm_contexts);
+ free_certauth_handles(context, certauth_modules);
return retval;
}
@@ -1405,17 +1666,11 @@ static void
pkinit_server_plugin_fini(krb5_context context,
krb5_kdcpreauth_moddata moddata)
{
- pkinit_kdc_context *realm_contexts = (pkinit_kdc_context *)moddata;
- int i;
-
- if (realm_contexts == NULL)
+ if (moddata == NULL)
return;
-
- for (i = 0; realm_contexts[i] != NULL; i++) {
- pkinit_server_plugin_fini_realm(context, realm_contexts[i]);
- }
- pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts);
- free(realm_contexts);
+ free_realm_contexts(context, moddata->realm_contexts);
+ free_certauth_handles(context, moddata->certauth_modules);
+ free(moddata);
}
static krb5_error_code
diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h
index a3cc9cc..56821f1 100644
--- a/src/plugins/preauth/pkinit/pkinit_trace.h
+++ b/src/plugins/preauth/pkinit/pkinit_trace.h
@@ -93,4 +93,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 */