diff options
Diffstat (limited to 'src/plugins/preauth')
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_crypto.h | 4 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 30 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_srv.c | 335 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_trace.h | 5 |
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 */ |