diff options
Diffstat (limited to 'source4/heimdal/kdc/simple_csr_authorizer.c')
-rw-r--r-- | source4/heimdal/kdc/simple_csr_authorizer.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/source4/heimdal/kdc/simple_csr_authorizer.c b/source4/heimdal/kdc/simple_csr_authorizer.c new file mode 100644 index 00000000000..1ae9efd676c --- /dev/null +++ b/source4/heimdal/kdc/simple_csr_authorizer.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2019 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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. + */ + +/* + * This plugin authorizes requested certificate SANs and EKUs by checking for + * existence of files of the form: + * + * + * /<path>/<princ>/<ext>-<value> + * + * where <path> is the value of: + * + * [kdc] simple_csr_authorizer_directory = PATH + * + * <princ> is a requesting client principal name with all characters other than + * alphanumeric, '-', '_', and non-leading '.' URL-encoded. + * + * <ext> is one of: + * + * - pkinit (SAN) + * - xmpt (SAN) + * - emailt (SAN) + * - ms-upt (SAN) + * - dnsnamt (SAN) + * - eku (EKU OID) + * + * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded + * just like principal names (see above). + * + * OIDs are of the form "1.2.3.4.5". + * + * Only digitalSignature and nonRepudiation key usage values are permitted. + */ +#define _GNU_SOURCE 1 + +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <roken.h> +#include <krb5.h> +#include <hx509.h> +#include <kdc.h> +#include <common_plugin.h> +#include <csr_authorizer_plugin.h> + +/* + * string_encode_sz() and string_encode() encode a string to be safe for use as + * a file name. They function very much like URL encoders, but '~' also gets + * encoded, and '@', '-', '_', and non-leading '.' do not. + * + * A corresponding decoder is not needed. + */ +static size_t +string_encode_sz(const char *in) +{ + size_t sz = strlen(in); + int first = 1; + + while (*in) { + char c = *(in++); + + switch (c) { + case '@': + case '-': + case '_': + break; + case '.': + if (first) + sz += 2; + break; + default: + if (!isalnum(c)) + sz += 2; + } + first = 0; + } + return sz; +} + +static char * +string_encode(const char *in) +{ + size_t len = strlen(in); + size_t sz = string_encode_sz(in); + size_t i, k; + char *s; + int first = 1; + + if ((s = malloc(sz + 1)) == NULL) + return NULL; + s[sz] = '\0'; + + for (i = k = 0; i < len; i++, first = 0) { + unsigned char c = ((const unsigned char *)in)[i]; + + switch (c) { + case '@': + case '-': + case '_': + s[k++] = c; + break; + case '.': + if (first) { + s[k++] = '%'; + s[k++] = "0123456789abcdef"[(c&0xff)>>4]; + s[k++] = "0123456789abcdef"[(c&0x0f)]; + } else { + s[k++] = c; + } + break; + default: + if (isalnum(c)) { + s[k++] = c; + } else { + s[k++] = '%'; + s[k++] = "0123456789abcdef"[(c&0xff)>>4]; + s[k++] = "0123456789abcdef"[(c&0x0f)]; + } + } + } + return s; +} + +static KRB5_LIB_CALL krb5_error_code +authorize(void *ctx, + krb5_context context, + const char *app, + hx509_request csr, + krb5_const_principal client, + krb5_boolean *result) +{ + krb5_error_code ret; + hx509_context hx509ctx = NULL; + KeyUsage ku; + const char *d; + size_t i; + char *princ = NULL; + char *s = NULL; + + if ((d = krb5_config_get_string(context, NULL, app ? app : "kdc", + "simple_csr_authorizer_directory", + NULL)) == NULL) + return KRB5_PLUGIN_NO_HANDLE; + + if ((ret = hx509_context_init(&hx509ctx))) + return ret; + + if ((ret = krb5_unparse_name(context, client, &princ))) + goto out; + + s = string_encode(princ); + free(princ); + princ = NULL; + if (s == NULL) + goto enomem; + + princ = s; + s = NULL; + + for (i = 0; ret == 0; i++) { + hx509_san_type san_type; + struct stat st; + const char *prefix; + char *san; + char *p; + + ret = hx509_request_get_san(csr, i, &san_type, &s); + if (ret) + break; + switch (san_type) { + case HX509_SAN_TYPE_EMAIL: + prefix = "email"; + break; + case HX509_SAN_TYPE_DNSNAME: + prefix = "dnsname"; + break; + case HX509_SAN_TYPE_XMPP: + prefix = "xmpp"; + break; + case HX509_SAN_TYPE_PKINIT: + prefix = "pkinit"; + break; + case HX509_SAN_TYPE_MS_UPN: + prefix = "ms-upn"; + break; + default: + ret = ENOTSUP; + break; + } + if (ret) + break; + + if ((san = string_encode(s)) == NULL || + asprintf(&p, "%s/%s/%s-%s", d, princ, prefix, san) == -1 || + p == NULL) + goto enomem; + ret = stat(p, &st) == -1 ? errno : 0; + free(san); + free(p); + free(s); + s = NULL; + if (ret) + goto skip; + ret = hx509_request_authorize_san(csr, i); + } + if (ret == HX509_NO_ITEM) + ret = 0; + if (ret) + goto out; + + for (i = 0; ret == 0; i++) { + struct stat st; + char *p; + + ret = hx509_request_get_eku(csr, i, &s); + if (ret) + break; + if (asprintf(&p, "%s/%s/eku-%s", d, princ, s) == -1 || p == NULL) { + free(princ); + free(s); + } + ret = stat(p, &st) == -1 ? errno : 0; + free(p); + free(s); + s = NULL; + if (ret) + goto skip; + ret = hx509_request_authorize_eku(csr, i); + } + if (ret == HX509_NO_ITEM) + ret = 0; + if (ret) + goto out; + + ku = int2KeyUsage(0); + ku.digitalSignature = 1; + ku.nonRepudiation = 1; + hx509_request_authorize_ku(csr, ku); + + *result = TRUE; + ret = 0; + goto out; + +skip: + /* Allow another plugin to get a crack at this */ + ret = KRB5_PLUGIN_NO_HANDLE; + goto out; + +enomem: + ret = krb5_enomem(context); + goto out; + +out: + hx509_context_free(&hx509ctx); + free(princ); + free(s); + return ret; +} + +static KRB5_LIB_CALL krb5_error_code +simple_csr_authorizer_init(krb5_context context, void **c) +{ + *c = NULL; + return 0; +} + +static KRB5_LIB_CALL void +simple_csr_authorizer_fini(void *c) +{ +} + +static krb5plugin_csr_authorizer_ftable plug_desc = + { 1, simple_csr_authorizer_init, simple_csr_authorizer_fini, authorize }; + +static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc }; + +static uintptr_t +simple_csr_authorizer_get_instance(const char *libname) +{ + if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + if (strcmp(libname, "kdc") == 0) + return kdc_get_instance(libname); + if (strcmp(libname, "hx509") == 0) + return hx509_get_instance(libname); + return 0; +} + +krb5_plugin_load_ft kdc_csr_authorizer_plugin_load; + +krb5_error_code KRB5_CALLCONV +kdc_csr_authorizer_plugin_load(heim_pcontext context, + krb5_get_instance_func_t *get_instance, + size_t *num_plugins, + krb5_plugin_common_ftable_cp **plugins) +{ + *get_instance = simple_csr_authorizer_get_instance; + *num_plugins = sizeof(plugs) / sizeof(plugs[0]); + *plugins = (krb5_plugin_common_ftable_cp *)plugs; + return 0; +} |