From f220067c2969aab107bd1300ad1cb8d4855389a7 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Thu, 17 Apr 2014 17:17:13 -0400 Subject: [PATCH 08/13] Load custom anchors when using KKDCP Add an http_anchors per-realm setting which we'll apply when using an HTTPS proxy, more or less mimicking the syntax of its similarly-named PKINIT counterpart. We only check the [realms] section, though. ticket: 7929 --- doc/admin/conf_files/krb5_conf.rst | 26 ++++++ src/include/k5-int.h | 1 + src/include/k5-trace.h | 7 ++ src/lib/krb5/os/sendto_kdc.c | 169 ++++++++++++++++++++++++++++++++++++- 4 files changed, 201 insertions(+), 2 deletions(-) diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst index 19ea9c9..c069327 100644 --- a/doc/admin/conf_files/krb5_conf.rst +++ b/doc/admin/conf_files/krb5_conf.rst @@ -428,6 +428,32 @@ following tags may be specified in the realm's subsection: (for example, when converting ``rcmd.hostname`` to ``host/hostname.domain``). +**http_anchors** + When KDCs and kpasswd servers are accessed through HTTPS proxies, this tag + can be used to specify the location of the CA certificate which should be + trusted to issue the certificate for a proxy server. If left unspecified, + the system-wide default set of CA certificates is used. + + The syntax for values is similar to that of values for the + **pkinit_anchors** tag: + + **FILE:** *filename* + + *filename* is assumed to be the name of an OpenSSL-style ca-bundle file. + + **DIR:** *dirname* + + *dirname* is assumed to be an directory which contains CA certificates. + All files in the directory will be examined; if they contain certificates + (in PEM format), they will be used. + + **ENV:** *envvar* + + *envvar* specifies the name of an environment variable which has been set + to a value conforming to one of the previous values. For example, + ``ENV:X509_PROXY_CA``, where environment variable ``X509_PROXY_CA`` has + been set to ``FILE:/tmp/my_proxy.pem``. + **kdc** The name or address of a host running a KDC for that realm. An optional port number, separated from the hostname by a colon, may diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 8f039ee..187d16d 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -212,6 +212,7 @@ typedef unsigned char u_char; #define KRB5_CONF_EXTRA_ADDRESSES "extra_addresses" #define KRB5_CONF_FORWARDABLE "forwardable" #define KRB5_CONF_HOST_BASED_SERVICES "host_based_services" +#define KRB5_CONF_HTTP_ANCHORS "http_anchors" #define KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME "ignore_acceptor_hostname" #define KRB5_CONF_IPROP_ENABLE "iprop_enable" #define KRB5_CONF_IPROP_MASTER_ULOGSIZE "iprop_master_ulogsize" diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h index f0d79f1..046bc95 100644 --- a/src/include/k5-trace.h +++ b/src/include/k5-trace.h @@ -324,6 +324,13 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); TRACE(c, "Resolving hostname {str}", hostname) #define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \ TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr) +#define TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(c) \ + TRACE(c, "HTTPS server certificate not received") +#define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \ + namelen, name, \ + err, errs) \ + TRACE(c, "HTTPS certificate error at {int} ({lenstr}): " \ + "{int} ({str})", depth, namelen, name, err, errs) #define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \ TRACE(c, "HTTPS error connecting to {raddr}", raddr) #define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr, err) \ diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index a4727c4..4bd8698 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -77,6 +77,9 @@ #ifdef PROXY_TLS_IMPL_OPENSSL #include #include +#include +#include +#include #endif #define MAX_PASS 3 @@ -152,6 +155,11 @@ struct conn_state { } http; }; +#ifdef PROXY_TLS_IMPL_OPENSSL +/* Extra-data identifier, used to pass context into the verify callback. */ +static int ssl_ex_context_id = -1; +#endif + void k5_sendto_kdc_initialize(void) { @@ -159,6 +167,8 @@ k5_sendto_kdc_initialize(void) SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); + + ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); #endif } @@ -1147,6 +1157,152 @@ flush_ssl_errors(krb5_context context) } } +static krb5_error_code +load_http_anchor_file(X509_STORE *store, const char *path) +{ + FILE *fp; + STACK_OF(X509_INFO) *sk = NULL; + X509_INFO *xi; + int i; + + fp = fopen(path, "r"); + if (fp == NULL) + return errno; + sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL); + fclose(fp); + if (sk == NULL) + return ENOENT; + for (i = 0; i < sk_X509_INFO_num(sk); i++) { + xi = sk_X509_INFO_value(sk, i); + if (xi->x509 != NULL) + X509_STORE_add_cert(store, xi->x509); + } + sk_X509_INFO_pop_free(sk, X509_INFO_free); + return 0; +} + +static krb5_error_code +load_http_anchor_dir(X509_STORE *store, const char *path) +{ + DIR *d = NULL; + struct dirent *dentry = NULL; + char filename[1024]; + krb5_boolean found_any = FALSE; + + d = opendir(path); + if (d == NULL) + return ENOENT; + while ((dentry = readdir(d)) != NULL) { + if (dentry->d_name[0] != '.') { + snprintf(filename, sizeof(filename), "%s/%s", + path, dentry->d_name); + if (load_http_anchor_file(store, filename) == 0) + found_any = TRUE; + } + } + closedir(d); + return found_any ? 0 : ENOENT; +} + +static krb5_error_code +load_http_anchor(SSL_CTX *ctx, const char *location) +{ + X509_STORE *store; + const char *envloc; + + store = SSL_CTX_get_cert_store(ctx); + if (strncmp(location, "FILE:", 5) == 0) { + return load_http_anchor_file(store, location + 5); + } else if (strncmp(location, "DIR:", 4) == 0) { + return load_http_anchor_dir(store, location + 4); + } else if (strncmp(location, "ENV:", 4) == 0) { + envloc = getenv(location + 4); + if (envloc == NULL) + return ENOENT; + return load_http_anchor(ctx, envloc); + } + return EINVAL; +} + +static krb5_error_code +load_http_verify_anchors(krb5_context context, const krb5_data *realm, + SSL_CTX *sctx) +{ + const char *anchors[4]; + char **values = NULL, *realmz; + unsigned int i; + krb5_error_code err; + + realmz = k5memdup0(realm->data, realm->length, &err); + if (realmz == NULL) + goto cleanup; + + /* Load the configured anchors. */ + anchors[0] = KRB5_CONF_REALMS; + anchors[1] = realmz; + anchors[2] = KRB5_CONF_HTTP_ANCHORS; + anchors[3] = NULL; + if (profile_get_values(context->profile, anchors, &values) == 0) { + for (i = 0; values[i] != NULL; i++) { + err = load_http_anchor(sctx, values[i]); + if (err != 0) + break; + } + profile_free_list(values); + } else { + /* Use the library defaults. */ + if (SSL_CTX_set_default_verify_paths(sctx) != 1) + err = ENOENT; + } + +cleanup: + free(realmz); + return err; +} + +static int +ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) +{ + X509 *x; + SSL *ssl; + BIO *bio; + krb5_context context; + int err, depth; + const char *cert = NULL, *errstr; + size_t count; + + ssl = X509_STORE_CTX_get_ex_data(store_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + context = SSL_get_ex_data(ssl, ssl_ex_context_id); + /* We do have the peer's cert, right? */ + x = X509_STORE_CTX_get_current_cert(store_ctx); + if (x == NULL) { + TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(context); + return 0; + } + /* Figure out where we are. */ + depth = X509_STORE_CTX_get_error_depth(store_ctx); + if (depth < 0) + return 0; + /* If there's an error at this level that we're not ignoring, fail. */ + err = X509_STORE_CTX_get_error(store_ctx); + if (err != X509_V_OK) { + bio = BIO_new(BIO_s_mem()); + if (bio != NULL) { + X509_NAME_print_ex(bio, x->cert_info->subject, 0, 0); + count = BIO_get_mem_data(bio, &cert); + errstr = X509_verify_cert_error_string(err); + TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(context, depth, + count, cert, err, + errstr); + BIO_free(bio); + } + return 0; + } + /* All done. */ + return 1; +} + /* * Set up structures that we use to manage the SSL handling for this connection * and apply any non-default settings. Kill the connection and return false if @@ -1156,10 +1312,14 @@ static krb5_boolean setup_ssl(krb5_context context, const krb5_data *realm, struct conn_state *conn, struct select_state *selstate) { + int e; long options; SSL_CTX *ctx = NULL; SSL *ssl = NULL; + if (ssl_ex_context_id == -1) + goto kill_conn; + /* Do general SSL library setup. */ ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx == NULL) @@ -1167,14 +1327,19 @@ setup_ssl(krb5_context context, const krb5_data *realm, options = SSL_CTX_get_options(ctx); SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - if (!SSL_CTX_set_default_verify_paths(ctx)) + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ssl_verify_callback); + X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0); + e = load_http_verify_anchors(context, realm, ctx); + if (e != 0) goto kill_conn; ssl = SSL_new(ctx); if (ssl == NULL) goto kill_conn; + if (!SSL_set_ex_data(ssl, ssl_ex_context_id, context)) + goto kill_conn; + /* Tell the SSL library about the socket. */ if (!SSL_set_fd(ssl, conn->fd)) goto kill_conn; -- 2.1.0