Adapt to headers being included in a different order in sendto_kdc.c. Drop portions which drop checkhost.c and checkhost.h. commit 472349d2a47fbc7db82e46ba46411b95c312fc1f Author: Greg Hudson Date: Sun Jun 22 10:42:14 2014 -0400 Move KKDCP OpenSSL code to an internal plugin Create an internal pluggable interface "tls" with one in-tree dynamic plugin module named "k5tls". Move all of the OpenSSL calls to the plugin module, and make the libkrb5 code load and invoke the plugin. This way we do not load or initialize libssl unless an HTTP proxy is used. ticket: 7929 diff --git a/src/Makefile.in b/src/Makefile.in index 5e2cf4e..92bb60a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -20,6 +20,7 @@ SUBDIRS=util include lib \ @ldap_plugin_dir@ \ plugins/preauth/otp \ plugins/preauth/pkinit \ + plugins/tls/k5tls \ kdc kadmin slave clients appl tests \ config-files build-tools man doc @po@ WINSUBDIRS=include util lib ccapi windows clients appl @@ -62,7 +63,7 @@ INSTALLMKDIRS = $(KRB5ROOT) $(KRB5MANROOT) $(KRB5OTHERMKDIRS) \ $(KRB5_LIBDIR) $(KRB5_INCDIR) \ $(KRB5_DB_MODULE_DIR) $(KRB5_PA_MODULE_DIR) \ $(KRB5_AD_MODULE_DIR) \ - $(KRB5_LIBKRB5_MODULE_DIR) \ + $(KRB5_LIBKRB5_MODULE_DIR) $(KRB5_TLS_MODULE_DIR) \ @localstatedir@ @localstatedir@/krb5kdc \ $(KRB5_INCSUBDIRS) $(datadir) $(EXAMPLEDIR) \ $(PKGCONFIG_DIR) diff --git a/src/config/pre.in b/src/config/pre.in index e1d7e4b..fd8ee56 100644 --- a/src/config/pre.in +++ b/src/config/pre.in @@ -213,6 +213,7 @@ KRB5_DB_MODULE_DIR = $(MODULE_DIR)/kdb KRB5_PA_MODULE_DIR = $(MODULE_DIR)/preauth KRB5_AD_MODULE_DIR = $(MODULE_DIR)/authdata KRB5_LIBKRB5_MODULE_DIR = $(MODULE_DIR)/libkrb5 +KRB5_TLS_MODULE_DIR = $(MODULE_DIR)/tls KRB5_LOCALEDIR = @localedir@ GSS_MODULE_DIR = @libdir@/gss KRB5_INCSUBDIRS = \ diff --git a/src/configure.in b/src/configure.in index 8aa513e..43509ab 100644 --- a/src/configure.in +++ b/src/configure.in @@ -308,6 +308,11 @@ no) ;; esac +if test "$PROXY_TLS_IMPL" = no; then + AC_DEFINE(PROXY_TLS_IMPL_NONE,1, + [Define if no HTTP TLS implementation is selected]) +fi + AC_SUBST(PROXY_TLS_IMPL) AC_SUBST(PROXY_TLS_IMPL_CFLAGS) AC_SUBST(PROXY_TLS_IMPL_LIBS) @@ -1386,6 +1391,7 @@ dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test plugins/authdata/greet plugins/authdata/greet_client plugins/authdata/greet_server + plugins/tls/k5tls clients clients/klist clients/kinit clients/kvno clients/kdestroy clients/kpasswd clients/ksu clients/kswitch diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 9f14ee0..38846eb 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -1083,7 +1083,8 @@ struct plugin_interface { #define PLUGIN_INTERFACE_LOCALAUTH 5 #define PLUGIN_INTERFACE_HOSTREALM 6 #define PLUGIN_INTERFACE_AUDIT 7 -#define PLUGIN_NUM_INTERFACES 8 +#define PLUGIN_INTERFACE_TLS 8 +#define PLUGIN_NUM_INTERFACES 9 /* Retrieve the plugin module of type interface_id and name modname, * storing the result into module. */ @@ -1126,6 +1127,7 @@ typedef struct krb5_preauth_context_st krb5_preauth_context; struct ccselect_module_handle; struct localauth_module_handle; struct hostrealm_module_handle; +struct k5_tls_vtable_st; struct _krb5_context { krb5_magic magic; krb5_enctype *in_tkt_etypes; @@ -1169,6 +1171,9 @@ struct _krb5_context { /* hostrealm module stuff */ struct hostrealm_module_handle **hostrealm_handles; + /* TLS module vtable (if loaded) */ + struct k5_tls_vtable_st *tls; + /* error detail info */ struct errinfo err; diff --git a/src/include/k5-tls.h b/src/include/k5-tls.h new file mode 100644 index 0000000..0661c05 --- /dev/null +++ b/src/include/k5-tls.h @@ -0,0 +1,104 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* include/k5-tls.h - internal pluggable interface for TLS */ +/* + * Copyright (C) 2014 by the Massachusetts Institute of Technology. + * 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. + */ + +/* + * This internal pluggable interface allows libkrb5 to load an in-tree module + * providing TLS support at runtime. It is currently tailored for the needs of + * the OpenSSL module as used for HTTP proxy support. As an internal + * interface, it can be changed to fit different implementations and consumers + * without regard for backward compatibility. + */ + +#ifndef K5_TLS_H +#define K5_TLS_H + +#include "k5-int.h" + +/* An abstract type for localauth module data. */ +typedef struct k5_tls_handle_st *k5_tls_handle; + +typedef enum { + DATA_READ, DONE, WANT_READ, WANT_WRITE, ERROR_TLS +} k5_tls_status; + +/* + * Create a handle for fd, where the server certificate must match servername + * and be trusted according to anchors. anchors is a null-terminated list + * using the DIR:/FILE:/ENV: syntax borrowed from PKINIT. If anchors is null, + * use the system default trust anchors. + */ +typedef krb5_error_code +(*k5_tls_setup_fn)(krb5_context context, SOCKET fd, const char *servername, + char **anchors, k5_tls_handle *handle_out); + +/* + * Write len bytes of data using TLS. Return DONE if writing is complete, + * WANT_READ or WANT_WRITE if the underlying socket must be readable or + * writable to continue, and ERROR_TLS if the TLS channel or underlying socket + * experienced an error. After WANT_READ or WANT_WRITE, the operation will be + * retried with the same arguments even if some data has already been written. + * (OpenSSL makes this contract easy to fulfill. For other implementations we + * might want to change it.) + */ +typedef k5_tls_status +(*k5_tls_write_fn)(krb5_context context, k5_tls_handle handle, + const void *data, size_t len); + +/* + * Read up to data_size bytes of data using TLS. Return DATA_READ and set + * *len_out if any data is read. Return DONE if there is no more data to be + * read on the connection, WANT_READ or WANT_WRITE if the underlying socket + * must be readable or writable to continue, and ERROR_TLS if the TLS channel + * or underlying socket experienced an error. + * + * After DATA_READ, there may still be pending buffered data to read. The + * caller must call this method again with additional buffer space before + * selecting for reading on the underlying socket. + */ +typedef k5_tls_status +(*k5_tls_read_fn)(krb5_context context, k5_tls_handle handle, void *data, + size_t data_size, size_t *len_out); + +/* Release a handle. Do not pass a null pointer. */ +typedef void +(*k5_tls_free_handle_fn)(krb5_context context, k5_tls_handle handle); + +/* All functions are mandatory unless they are all null, in which case the + * caller should assume that TLS is unsupported. */ +typedef struct k5_tls_vtable_st { + k5_tls_setup_fn setup; + k5_tls_write_fn write; + k5_tls_read_fn read; + k5_tls_free_handle_fn free_handle; +} *k5_tls_vtable; + +#endif /* K5_TLS_H */ diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h index 9e75b29..a0aa85a 100644 --- a/src/include/k5-trace.h +++ b/src/include/k5-trace.h @@ -324,23 +324,11 @@ 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_SERVER_NAME_MISMATCH(c, hostname) \ - TRACE(c, "HTTPS certificate name mismatch: server certificate is " \ - "not for \"{str}\"", hostname) -#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(c, hostname) \ - TRACE(c, "HTTPS certificate name matched \"{str}\"", hostname) -#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) \ +#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) \ - TRACE(c, "HTTPS error receiving from {raddr}: {errno}", raddr, err) -#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \ +#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr) \ + TRACE(c, "HTTPS error receiving from {raddr}", raddr) +#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \ TRACE(c, "HTTPS error sending to {raddr}", raddr) #define TRACE_SENDTO_KDC_HTTPS_SEND(c, raddr) \ TRACE(c, "Sending HTTPS request to {raddr}", raddr) @@ -383,6 +371,19 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); TRACE(c, "TGS reply didn't decode with subkey; trying session key " \ "({keyblock)}", keyblock) +#define TRACE_TLS_ERROR(c, errs) \ + TRACE(c, "TLS error: {str}", errs) +#define TRACE_TLS_NO_REMOTE_CERTIFICATE(c) \ + TRACE(c, "TLS server certificate not received") +#define TRACE_TLS_CERT_ERROR(c, depth, namelen, name, err, errs) \ + TRACE(c, "TLS certificate error at {int} ({lenstr}): {int} ({str})", \ + depth, namelen, name, err, errs) +#define TRACE_TLS_SERVER_NAME_MISMATCH(c, hostname) \ + TRACE(c, "TLS certificate name mismatch: server certificate is " \ + "not for \"{str}\"", hostname) +#define TRACE_TLS_SERVER_NAME_MATCH(c, hostname) \ + TRACE(c, "TLS certificate name matched \"{str}\"", hostname) + #define TRACE_TKT_CREDS(c, creds, cache) \ TRACE(c, "Getting credentials {creds} using ccache {ccache}", \ creds, cache) diff --git a/src/lib/krb5/Makefile.in b/src/lib/krb5/Makefile.in index 472c008..d9cddc1 100644 --- a/src/lib/krb5/Makefile.in +++ b/src/lib/krb5/Makefile.in @@ -56,8 +56,7 @@ RELDIR=krb5 SHLIB_EXPDEPS = \ $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ $(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB) -SHLIB_EXPLIBS=-lk5crypto -lcom_err $(PROXY_TLS_IMPL_LIBS) $(SUPPORT_LIB) \ - @GEN_LIB@ $(LIBS) +SHLIB_EXPLIBS=-lk5crypto -lcom_err $(SUPPORT_LIB) @GEN_LIB@ $(LIBS) all-unix:: all-liblinks diff --git a/src/lib/krb5/krb/copy_ctx.c b/src/lib/krb5/krb/copy_ctx.c index 4237023..322c288 100644 --- a/src/lib/krb5/krb/copy_ctx.c +++ b/src/lib/krb5/krb/copy_ctx.c @@ -81,6 +81,7 @@ krb5_copy_context(krb5_context ctx, krb5_context *nctx_out) nctx->ccselect_handles = NULL; nctx->localauth_handles = NULL; nctx->hostrealm_handles = NULL; + nctx->tls = NULL; nctx->kdblog_context = NULL; nctx->trace_callback = NULL; nctx->trace_callback_data = NULL; diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index 6801bb1..6548f36 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -319,6 +319,7 @@ krb5_free_context(krb5_context ctx) k5_localauth_free_context(ctx); k5_plugin_free_context(ctx); free(ctx->plugin_base_dir); + free(ctx->tls); ctx->magic = 0; free(ctx); diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c index 8b62c7b..7375f51 100644 --- a/src/lib/krb5/krb/plugin.c +++ b/src/lib/krb5/krb/plugin.c @@ -55,7 +55,8 @@ const char *interface_names[] = { "ccselect", "localauth", "hostrealm", - "audit" + "audit", + "tls" }; /* Return the context's interface structure for id, or NULL if invalid. */ diff --git a/src/lib/krb5/krb5_libinit.c b/src/lib/krb5/krb5_libinit.c index b72bc58..eb40124 100644 --- a/src/lib/krb5/krb5_libinit.c +++ b/src/lib/krb5/krb5_libinit.c @@ -55,8 +55,6 @@ int krb5int_lib_init(void) if (err) return err; - k5_sendto_kdc_initialize(); - return 0; } diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in index fa8a093..ea68990 100644 --- a/src/lib/krb5/os/Makefile.in +++ b/src/lib/krb5/os/Makefile.in @@ -2,7 +2,7 @@ mydir=lib$(S)krb5$(S)os BUILDTOP=$(REL)..$(S)..$(S).. DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \ -DSBINDIR=\"$(ADMIN_BINDIR)\" -LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) -I$(top_srcdir)/util/profile +LOCALINCLUDES= -I$(top_srcdir)/util/profile ##DOS##BUILDTOP = ..\..\.. ##DOS##PREFIXDIR=os @@ -13,7 +13,6 @@ STLIBOBJS= \ c_ustime.o \ ccdefname.o \ changepw.o \ - checkhost.o \ dnsglue.o \ dnssrv.o \ expand_path.o \ diff --git a/src/lib/krb5/os/deps b/src/lib/krb5/os/deps index d56ff30..211354c 100644 --- a/src/lib/krb5/os/deps +++ b/src/lib/krb5/os/deps @@ -49,17 +49,6 @@ changepw.so changepw.po $(OUTPRE)changepw.$(OBJEXT): \ $(top_srcdir)/include/krb5/locate_plugin.h $(top_srcdir)/include/krb5/plugin.h \ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ changepw.c os-proto.h -checkhost.so checkhost.po $(OUTPRE)checkhost.$(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/k5-utf8.h \ - $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ - $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ - $(top_srcdir)/include/socket-utils.h checkhost.c checkhost.h dnsglue.so dnsglue.po $(OUTPRE)dnsglue.$(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 \ @@ -429,7 +418,7 @@ sendto_kdc.so sendto_kdc.po $(OUTPRE)sendto_kdc.$(OBJEXT): \ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ - $(top_srcdir)/include/socket-utils.h checkhost.h os-proto.h \ + $(top_srcdir)/include/socket-utils.h os-proto.h \ sendto_kdc.c sn2princ.so sn2princ.po $(OUTPRE)sn2princ.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c index 1f2039c..160a2d0 100644 --- a/src/lib/krb5/os/locate_kdc.c +++ b/src/lib/krb5/os/locate_kdc.c @@ -177,7 +177,6 @@ oom: return ENOMEM; } -#ifdef PROXY_TLS_IMPL_OPENSSL static void parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, char **uri_path) @@ -195,13 +194,6 @@ parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, } } } -#else -static void -parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, - char **uri) -{ -} -#endif /* Return true if server is identical to an entry in list. */ static krb5_boolean diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h index 34bf028..69ee376 100644 --- a/src/lib/krb5/os/os-proto.h +++ b/src/lib/krb5/os/os-proto.h @@ -187,6 +187,5 @@ krb5_error_code localauth_k5login_initvt(krb5_context context, int maj_ver, krb5_plugin_vtable vtable); krb5_error_code localauth_an2ln_initvt(krb5_context context, int maj_ver, int min_ver, krb5_plugin_vtable vtable); -void k5_sendto_kdc_initialize(void); #endif /* KRB5_LIBOS_INT_PROTO__ */ diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index a572831..2242240 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -54,6 +54,7 @@ * as necessary. */ #include "fake-addrinfo.h" +#include "k5-tls.h" #include "k5-int.h" #include "os-proto.h" @@ -74,15 +75,6 @@ #endif #endif -#ifdef PROXY_TLS_IMPL_OPENSSL -#include -#include -#include -#include -#include -#include "checkhost.h" -#endif - #define MAX_PASS 3 #define DEFAULT_UDP_PREF_LIMIT 1465 #define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */ @@ -147,29 +139,30 @@ struct conn_state { const char *uri_path; const char *servername; char *https_request; -#ifdef PROXY_TLS_IMPL_OPENSSL - SSL *ssl; -#endif + k5_tls_handle tls; } http; }; -#ifdef PROXY_TLS_IMPL_OPENSSL -/* Extra-data identifier, used to pass context into the verify callback. */ -static int ssl_ex_context_id = -1; -static int ssl_ex_conn_id = -1; -#endif - -void -k5_sendto_kdc_initialize(void) +/* Set up context->tls. On allocation failure, return ENOMEM. On plugin load + * failure, set context->tls to point to a nulled vtable and return 0. */ +static krb5_error_code +init_tls_vtable(krb5_context context) { -#ifdef PROXY_TLS_IMPL_OPENSSL - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); + krb5_plugin_initvt_fn initfn; - ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - ssl_ex_conn_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); -#endif + if (context->tls != NULL) + return 0; + + context->tls = calloc(1, sizeof(*context->tls)); + if (context->tls == NULL) + return ENOMEM; + + /* Attempt to load the module; just let it stay nulled out on failure. */ + k5_plugin_register_dyn(context, PLUGIN_INTERFACE_TLS, "k5tls", "tls"); + if (k5_plugin_load(context, PLUGIN_INTERFACE_TLS, "k5tls", &initfn) == 0) + (*initfn)(context, 0, 0, (krb5_plugin_vtable)context->tls); + + return 0; } /* Get current time in milliseconds. */ @@ -184,21 +177,15 @@ get_curtime_ms(time_ms *time_out) return 0; } -#ifdef PROXY_TLS_IMPL_OPENSSL static void -free_http_ssl_data(struct conn_state *state) +free_http_tls_data(krb5_context context, struct conn_state *state) { - SSL_free(state->http.ssl); - state->http.ssl = NULL; + if (state->http.tls != NULL) + context->tls->free_handle(context, state->http.tls); + state->http.tls = NULL; free(state->http.https_request); state->http.https_request = NULL; } -#else -static void -free_http_ssl_data(struct conn_state *state) -{ -} -#endif #ifdef USE_POLL @@ -532,7 +519,6 @@ static fd_handler_fn service_udp_read; static fd_handler_fn service_https_write; static fd_handler_fn service_https_read; -#ifdef PROXY_TLS_IMPL_OPENSSL static krb5_error_code make_proxy_request(struct conn_state *state, const krb5_data *realm, const krb5_data *message, char **req_out, size_t *len_out) @@ -585,14 +571,6 @@ cleanup: krb5_free_data(NULL, encoded_pm); return ret; } -#else -static krb5_error_code -make_proxy_request(struct conn_state *state, const krb5_data *realm, - const krb5_data *message, char **req_out, size_t *len_out) -{ - abort(); -} -#endif /* Set up the actual message we will send across the underlying transport to * communicate the payload message, using one or both of state->out.sgbuf. */ @@ -963,7 +941,7 @@ static void kill_conn(krb5_context context, struct conn_state *conn, struct select_state *selstate) { - free_http_ssl_data(conn); + free_http_tls_data(context, conn); if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); @@ -1145,249 +1123,44 @@ service_udp_read(krb5_context context, const krb5_data *realm, return TRUE; } -#ifdef PROXY_TLS_IMPL_OPENSSL -/* Output any error strings that OpenSSL's accumulated as tracing messages. */ -static void -flush_ssl_errors(krb5_context context) -{ - unsigned long err; - char buf[128]; - - while ((err = ERR_get_error()) != 0) { - ERR_error_string_n(err, buf, sizeof(buf)); - TRACE_SENDTO_KDC_HTTPS_ERROR(context, buf); - } -} - -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) +/* Set up conn->http.tls. Return true on success. */ +static krb5_boolean +setup_tls(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) { - 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; -} + krb5_error_code ret; + krb5_boolean ok = FALSE; + char **anchors = NULL, *realmstr = NULL; + const char *names[4]; -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; + if (init_tls_vtable(context) != 0 || context->tls->setup == NULL) + return FALSE; - realmz = k5memdup0(realm->data, realm->length, &err); - if (realmz == NULL) + realmstr = k5memdup0(realm->data, realm->length, &ret); + if (realmstr == 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 krb5_boolean -ssl_check_name_or_ip(X509 *x, const char *expected_name) -{ - struct in_addr in; - struct in6_addr in6; - - if (inet_aton(expected_name, &in) != 0 || - inet_pton(AF_INET6, expected_name, &in6) != 0) { - return k5_check_cert_address(x, expected_name); - } else { - return k5_check_cert_servername(x, expected_name); - } -} + names[0] = KRB5_CONF_REALMS; + names[1] = realmstr; + names[2] = KRB5_CONF_HTTP_ANCHORS; + names[3] = NULL; + ret = profile_get_values(context->profile, names, &anchors); + if (ret != 0 && ret != PROF_NO_RELATION) + goto cleanup; -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; - struct conn_state *conn = NULL; - const char *cert = NULL, *errstr, *expected_name; - 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); - conn = SSL_get_ex_data(ssl, ssl_ex_conn_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; - } - /* If we're not looking at the peer, we're done and everything's ok. */ - if (depth != 0) - return 1; - /* Check if the name we expect to find is in the certificate. */ - expected_name = conn->http.servername; - if (ssl_check_name_or_ip(x, expected_name)) { - TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(context, expected_name); - return 1; - } else { - TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(context, expected_name); + if (context->tls->setup(context, conn->fd, conn->http.servername, anchors, + &conn->http.tls) != 0) { + TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); + goto cleanup; } - /* The name didn't match. */ - return 0; -} -/* - * 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 - * anything goes wrong while we're doing that; return true otherwise. - */ -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; + ok = TRUE; - if (ssl_ex_context_id == -1 || ssl_ex_conn_id == -1) - goto kill_conn; - - /* Do general SSL library setup. */ - ctx = SSL_CTX_new(SSLv23_client_method()); - if (ctx == NULL) - goto kill_conn; - options = SSL_CTX_get_options(ctx); - SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); - - 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; - if (!SSL_set_ex_data(ssl, ssl_ex_conn_id, conn)) - goto kill_conn; - - /* Tell the SSL library about the socket. */ - if (!SSL_set_fd(ssl, conn->fd)) - goto kill_conn; - SSL_set_connect_state(ssl); - - SSL_CTX_free(ctx); - conn->http.ssl = ssl; - - return TRUE; - -kill_conn: - TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); - flush_ssl_errors(context); - SSL_free(ssl); - SSL_CTX_free(ctx); - kill_conn(context, conn, selstate); - return FALSE; +cleanup: + free(realmstr); + profile_free_list(anchors); + return ok; } /* Set conn->state to READING when done; otherwise, call a cm_set_. */ @@ -1395,50 +1168,41 @@ static krb5_boolean service_https_write(krb5_context context, const krb5_data *realm, struct conn_state *conn, struct select_state *selstate) { - ssize_t nwritten; - int e; + k5_tls_status st; /* If this is our first time in here, set up the SSL context. */ - if (conn->http.ssl == NULL && !setup_ssl(context, realm, conn, selstate)) + if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) { + kill_conn(context, conn, selstate); return FALSE; + } /* Try to transmit our request to the server. */ - nwritten = SSL_write(conn->http.ssl, SG_BUF(conn->out.sgp), - SG_LEN(conn->out.sgbuf)); - if (nwritten <= 0) { - e = SSL_get_error(conn->http.ssl, nwritten); - if (e == SSL_ERROR_WANT_READ) { - cm_read(selstate, conn->fd); - return FALSE; - } else if (e == SSL_ERROR_WANT_WRITE) { - cm_write(selstate, conn->fd); - return FALSE; - } + st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp), + SG_LEN(conn->out.sgbuf)); + if (st == DONE) { + TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); + cm_read(selstate, conn->fd); + conn->state = READING; + } else if (st == WANT_READ) { + cm_read(selstate, conn->fd); + } else if (st == WANT_WRITE) { + cm_write(selstate, conn->fd); + } else if (st == ERROR_TLS) { TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr); - flush_ssl_errors(context); kill_conn(context, conn, selstate); - return FALSE; } - /* Done writing, switch to reading. */ - TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); - cm_read(selstate, conn->fd); - conn->state = READING; return FALSE; } -/* - * Return true on readable data, call a cm_read/write function and return - * false if the SSL layer needs it, kill the connection otherwise. - */ +/* Return true on finished data. Call a cm_read/write function and return + * false if the TLS layer needs it. Kill the connection on error. */ static krb5_boolean https_read_bytes(krb5_context context, struct conn_state *conn, struct select_state *selstate) { - size_t bufsize; - ssize_t nread; - krb5_boolean readbytes = FALSE; - int e = 0; + size_t bufsize, nread; + k5_tls_status st; char *tmp; struct incoming_message *in = &conn->in; @@ -1458,31 +1222,26 @@ https_read_bytes(krb5_context context, struct conn_state *conn, in->bufsize = bufsize; } - nread = SSL_read(conn->http.ssl, &in->buf[in->pos], - in->bufsize - in->pos - 1); - if (nread <= 0) + st = context->tls->read(context, conn->http.tls, &in->buf[in->pos], + in->bufsize - in->pos - 1, &nread); + if (st != DATA_READ) break; + in->pos += nread; in->buf[in->pos] = '\0'; - readbytes = TRUE; } - e = SSL_get_error(conn->http.ssl, nread); - if (e == SSL_ERROR_WANT_READ) { + if (st == DONE) + return TRUE; + + if (st == WANT_READ) { cm_read(selstate, conn->fd); - return FALSE; - } else if (e == SSL_ERROR_WANT_WRITE) { + } else if (st == WANT_WRITE) { cm_write(selstate, conn->fd); - return FALSE; - } else if ((e == SSL_ERROR_ZERO_RETURN) || - (e == SSL_ERROR_SYSCALL && nread == 0 && readbytes)) { - return TRUE; + } else if (st == ERROR_TLS) { + TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr); + kill_conn(context, conn, selstate); } - - e = readbytes ? SOCKET_ERRNO : ECONNRESET; - TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr, e); - flush_ssl_errors(context); - kill_conn(context, conn, selstate); return FALSE; } @@ -1531,20 +1290,6 @@ kill_conn: kill_conn(context, conn, selstate); return FALSE; } -#else -static krb5_boolean -service_https_write(krb5_context context, const krb5_data *realm, - struct conn_state *conn, struct select_state *selstate) -{ - abort(); -} -static krb5_boolean -service_https_read(krb5_context context, const krb5_data *realm, - struct conn_state *conn, struct select_state *selstate) -{ - abort(); -} -#endif /* Return the maximum of endtime and the endtime fields of all currently active * TCP connections. */ @@ -1765,7 +1510,7 @@ cleanup: if (socktype_for_transport(state->addr.transport) == SOCK_STREAM) TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr); closesocket(state->fd); - free_http_ssl_data(state); + free_http_tls_data(context, state); } if (state->state == READING && state->in.buf != udpbuf) free(state->in.buf); diff --git a/src/plugins/tls/k5tls/Makefile.in b/src/plugins/tls/k5tls/Makefile.in new file mode 100644 index 0000000..4d58df0 --- /dev/null +++ b/src/plugins/tls/k5tls/Makefile.in @@ -0,0 +1,22 @@ +mydir=plugins$(S)tls$(S)k5tls +BUILDTOP=$(REL)..$(S)..$(S).. +MODULE_INSTALL_DIR = $(KRB5_TLS_MODULE_DIR) +LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) + +LIBBASE=k5tls +LIBMAJOR=0 +LIBMINOR=0 +RELDIR=../plugins/tls/k5tls +SHLIB_EXPDEPS= $(KRB5_DEPLIB) $(SUPPORT_DEPLIB) +SHLIB_EXPLIBS= $(KRB5_LIB) $(SUPPORT_LIB) $(PROXY_TLS_IMPL_LIBS) + +STLIBOBJS=openssl.o notls.o + +SRCS=$(srcdir)/openssl.c $(srcdir)/notls.c + +all-unix:: all-liblinks +install-unix:: install-libs +clean-unix:: clean-libs clean-libobjs + +@libnover_frag@ +@libobj_frag@ diff --git a/src/plugins/tls/k5tls/deps b/src/plugins/tls/k5tls/deps new file mode 100644 index 0000000..a6088a7 --- /dev/null +++ b/src/plugins/tls/k5tls/deps @@ -0,0 +1,25 @@ +# +# Generated makefile dependencies follow. +# +openssl.so openssl.po $(OUTPRE)openssl.$(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-tls.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h openssl.c +notls.so notls.po $(OUTPRE)notls.$(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-tls.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h notls.c diff --git a/src/plugins/tls/k5tls/k5tls.exports b/src/plugins/tls/k5tls/k5tls.exports new file mode 100644 index 0000000..d67d928 --- /dev/null +++ b/src/plugins/tls/k5tls/k5tls.exports @@ -0,0 +1 @@ +tls_k5tls_initvt diff --git a/src/plugins/tls/k5tls/notls.c b/src/plugins/tls/k5tls/notls.c new file mode 100644 index 0000000..7be0a4a --- /dev/null +++ b/src/plugins/tls/k5tls/notls.c @@ -0,0 +1,53 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plus/tls/k5tls/none.c - Stub TLS module implementation */ +/* + * Copyright (C) 2014 by the Massachusetts Institute of Technology. + * 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. + */ + +/* This dummy module is used if no TLS implemented is selected. */ + +#include "k5-int.h" +#include "k5-utf8.h" +#include "k5-tls.h" + +#ifdef PROXY_TLS_IMPL_NONE + +krb5_error_code +tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + /* Leave all vtable functions nulled. */ + return 0; +} + +#endif /* PROXY_TLS_IMPL_NONE */ diff --git a/src/plugins/tls/k5tls/openssl.c b/src/plugins/tls/k5tls/openssl.c new file mode 100644 index 0000000..0691a34 --- /dev/null +++ b/src/plugins/tls/k5tls/openssl.c @@ -0,0 +1,570 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/tls/k5tls/openssl.c - OpenSSL TLS module implementation */ +/* + * Copyright 2013,2014 Red Hat, Inc. + * + * 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. + * + * 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 OWNER + * 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 "k5-utf8.h" +#include "k5-tls.h" + +#ifdef PROXY_TLS_IMPL_OPENSSL +#include +#include +#include +#include +#include + +struct k5_tls_handle_st { + SSL *ssl; + char *servername; +}; + +static int ex_context_id = -1; +static int ex_handle_id = -1; + +MAKE_INIT_FUNCTION(init_openssl); + +int +init_openssl() +{ + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + ex_handle_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + return 0; +} + +static void +flush_errors(krb5_context context) +{ + unsigned long err; + char buf[128]; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, buf, sizeof(buf)); + TRACE_TLS_ERROR(context, buf); + } +} + +/* Return the passed-in character, lower-cased if it's an ASCII character. */ +static inline char +ascii_tolower(char p) +{ + if (KRB5_UPPER(p)) + return p + ('a' - 'A'); + return p; +} + +/* + * Check a single label. If allow_wildcard is true, and the presented name + * includes a wildcard, return true and note that we matched a wildcard. + * Otherwise, for both the presented and expected values, do a case-insensitive + * comparison of ASCII characters, and a case-sensitive comparison of + * everything else. + */ +static krb5_boolean +label_match(const char *presented, size_t plen, const char *expected, + size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard) +{ + unsigned int i; + + if (allow_wildcard && plen == 1 && presented[0] == '*') { + *wildcard = TRUE; + return TRUE; + } + + if (plen != elen) + return FALSE; + + for (i = 0; i < elen; i++) { + if (ascii_tolower(presented[i]) != ascii_tolower(expected[i])) + return FALSE; + } + return TRUE; +} + +/* Break up the two names and check them, label by label. */ +static krb5_boolean +domain_match(const char *presented, size_t plen, const char *expected) +{ + const char *p, *q, *r, *s; + int n_label; + krb5_boolean used_wildcard = FALSE; + + n_label = 0; + p = presented; + r = expected; + while (p < presented + plen && *r != '\0') { + q = memchr(p, '.', plen - (p - presented)); + if (q == NULL) + q = presented + plen; + s = r + strcspn(r, "."); + if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard)) + return FALSE; + p = q < presented + plen ? q + 1 : q; + r = *s ? s + 1 : s; + n_label++; + } + if (used_wildcard && n_label <= 2) + return FALSE; + if (p == presented + plen && *r == '\0') + return TRUE; + return FALSE; +} + +/* Fetch the list of subjectAltNames from a certificate. */ +static GENERAL_NAMES * +get_cert_sans(X509 *x) +{ + int ext; + X509_EXTENSION *san_ext; + + ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); + if (ext < 0) + return NULL; + san_ext = X509_get_ext(x, ext); + if (san_ext == NULL) + return NULL; + return X509V3_EXT_d2i(san_ext); +} + +/* Fetch a CN value from the subjct name field, returning its length, or -1 if + * there is no subject name or it contains no CN value. */ +static int +get_cert_cn(X509 *x, char *buf, size_t bufsize) +{ + X509_NAME *name; + + name = X509_get_subject_name(x); + if (name == NULL) + return -1; + return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize); +} + +/* Return true if text matches any of the addresses we can recover from x. */ +static krb5_boolean +check_cert_address(X509 *x, const char *text) +{ + char buf[1024]; + GENERAL_NAMES *sans; + GENERAL_NAME *san = NULL; + ASN1_OCTET_STRING *ip; + krb5_boolean found_ip_san = FALSE, matched = FALSE; + int n_sans, i; + int name_length; + struct in_addr sin; + struct in6_addr sin6; + + /* Parse the IP address into an octet string. */ + ip = M_ASN1_OCTET_STRING_new(); + if (ip == NULL) + return FALSE; + if (inet_pton(AF_INET, text, &sin)) { + M_ASN1_OCTET_STRING_set(ip, &sin, sizeof(sin)); + } else if (inet_pton(AF_INET6, text, &sin6)) { + M_ASN1_OCTET_STRING_set(ip, &sin6, sizeof(sin6)); + } else { + ASN1_OCTET_STRING_free(ip); + return FALSE; + } + + /* Check for matches in ipaddress subjectAltName values. */ + sans = get_cert_sans(x); + if (sans != NULL) { + n_sans = sk_GENERAL_NAME_num(sans); + for (i = 0; i < n_sans; i++) { + san = sk_GENERAL_NAME_value(sans, i); + if (san->type != GEN_IPADD) + continue; + found_ip_san = TRUE; + matched = (ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0); + if (matched) + break; + } + sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); + } + ASN1_OCTET_STRING_free(ip); + + if (found_ip_san) + return matched; + + /* Check for a match against the CN value in the peer's subject name. */ + name_length = get_cert_cn(x, buf, sizeof(buf)); + if (name_length >= 0) { + /* Do a string compare to check if it's an acceptable value. */ + return strlen(text) == (size_t)name_length && + strncmp(text, buf, name_length) == 0; + } + + /* We didn't find a match. */ + return FALSE; +} + +/* Return true if expected matches any of the names we can recover from x. */ +static krb5_boolean +check_cert_servername(X509 *x, const char *expected) +{ + char buf[1024]; + GENERAL_NAMES *sans; + GENERAL_NAME *san = NULL; + unsigned char *dnsname; + krb5_boolean found_dns_san = FALSE, matched = FALSE; + int name_length, n_sans, i; + + /* Check for matches in dnsname subjectAltName values. */ + sans = get_cert_sans(x); + if (sans != NULL) { + n_sans = sk_GENERAL_NAME_num(sans); + for (i = 0; i < n_sans; i++) { + san = sk_GENERAL_NAME_value(sans, i); + if (san->type != GEN_DNS) + continue; + found_dns_san = TRUE; + dnsname = NULL; + name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName); + if (dnsname == NULL) + continue; + matched = domain_match((char *)dnsname, name_length, expected); + OPENSSL_free(dnsname); + if (matched) + break; + } + sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); + } + + if (matched) + return TRUE; + if (found_dns_san) + return matched; + + /* Check for a match against the CN value in the peer's subject name. */ + name_length = get_cert_cn(x, buf, sizeof(buf)); + if (name_length >= 0) + return domain_match(buf, name_length, expected); + + /* We didn't find a match. */ + return FALSE; +} + +static krb5_boolean +check_cert_name_or_ip(X509 *x, const char *expected_name) +{ + struct in_addr in; + struct in6_addr in6; + + if (inet_pton(AF_INET, expected_name, &in) != 0 || + inet_pton(AF_INET6, expected_name, &in6) != 0) { + return check_cert_address(x, expected_name); + } else { + return check_cert_servername(x, expected_name); + } +} + +static int +verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) +{ + X509 *x; + SSL *ssl; + BIO *bio; + krb5_context context; + int err, depth; + k5_tls_handle handle; + const char *cert = NULL, *errstr, *expected_name; + 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, ex_context_id); + handle = SSL_get_ex_data(ssl, ex_handle_id); + assert(context != NULL && handle != NULL); + /* We do have the peer's cert, right? */ + x = X509_STORE_CTX_get_current_cert(store_ctx); + if (x == NULL) { + TRACE_TLS_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_TLS_CERT_ERROR(context, depth, count, cert, err, errstr); + BIO_free(bio); + } + return 0; + } + /* If we're not looking at the peer, we're done and everything's ok. */ + if (depth != 0) + return 1; + /* Check if the name we expect to find is in the certificate. */ + expected_name = handle->servername; + if (check_cert_name_or_ip(x, expected_name)) { + TRACE_TLS_SERVER_NAME_MATCH(context, expected_name); + return 1; + } else { + TRACE_TLS_SERVER_NAME_MISMATCH(context, expected_name); + } + /* The name didn't match. */ + return 0; +} + +static krb5_error_code +load_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_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_anchor_file(store, filename) == 0) + found_any = TRUE; + } + } + closedir(d); + return found_any ? 0 : ENOENT; +} + +static krb5_error_code +load_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_anchor_file(store, location + 5); + } else if (strncmp(location, "DIR:", 4) == 0) { + return load_anchor_dir(store, location + 4); + } else if (strncmp(location, "ENV:", 4) == 0) { + envloc = getenv(location + 4); + if (envloc == NULL) + return ENOENT; + return load_anchor(ctx, envloc); + } + return EINVAL; +} + +static krb5_error_code +load_anchors(krb5_context context, char **anchors, SSL_CTX *sctx) +{ + unsigned int i; + krb5_error_code ret; + + if (anchors != NULL) { + for (i = 0; anchors[i] != NULL; i++) { + ret = load_anchor(sctx, anchors[i]); + if (ret) + return ret; + } + } else { + /* Use the library defaults. */ + if (SSL_CTX_set_default_verify_paths(sctx) != 1) + return ENOENT; + } + + return 0; +} + +static krb5_error_code +setup(krb5_context context, SOCKET fd, const char *servername, + char **anchors, k5_tls_handle *handle_out) +{ + int e; + long options; + SSL_CTX *ctx = NULL; + SSL *ssl = NULL; + k5_tls_handle handle = NULL; + + *handle_out = NULL; + + (void)CALL_INIT_FUNCTION(init_openssl); + if (ex_context_id == -1 || ex_handle_id == -1) + return KRB5_PLUGIN_OP_NOTSUPP; + + /* Do general SSL library setup. */ + ctx = SSL_CTX_new(SSLv23_client_method()); + if (ctx == NULL) + goto error; + options = SSL_CTX_get_options(ctx); + SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); + X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0); + e = load_anchors(context, anchors, ctx); + if (e != 0) + goto error; + + ssl = SSL_new(ctx); + if (ssl == NULL) + goto error; + + if (!SSL_set_fd(ssl, fd)) + goto error; + SSL_set_connect_state(ssl); + + /* Create a handle and allow verify_callback to access it. */ + handle = malloc(sizeof(*handle)); + if (handle == NULL || !SSL_set_ex_data(ssl, ex_handle_id, handle)) + goto error; + + handle->ssl = ssl; + handle->servername = strdup(servername); + if (handle->servername == NULL) + goto error; + *handle_out = handle; + SSL_CTX_free(ctx); + return 0; + +error: + flush_errors(context); + free(handle); + SSL_free(ssl); + SSL_CTX_free(ctx); + return KRB5_PLUGIN_OP_NOTSUPP; +} + +static k5_tls_status +write_tls(krb5_context context, k5_tls_handle handle, const void *data, + size_t len) +{ + int nwritten, e; + + /* Try to transmit our request; allow verify_callback to access context. */ + if (!SSL_set_ex_data(handle->ssl, ex_context_id, context)) + return ERROR_TLS; + nwritten = SSL_write(handle->ssl, data, len); + (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL); + if (nwritten > 0) + return DONE; + + e = SSL_get_error(handle->ssl, nwritten); + if (e == SSL_ERROR_WANT_READ) + return WANT_READ; + else if (e == SSL_ERROR_WANT_WRITE) + return WANT_WRITE; + flush_errors(context); + return ERROR_TLS; +} + +static k5_tls_status +read_tls(krb5_context context, k5_tls_handle handle, void *data, + size_t data_size, size_t *len_out) +{ + ssize_t nread; + int e; + + *len_out = 0; + + /* Try to read response data; allow verify_callback to access context. */ + if (!SSL_set_ex_data(handle->ssl, ex_context_id, context)) + return ERROR_TLS; + nread = SSL_read(handle->ssl, data, data_size); + (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL); + if (nread > 0) { + *len_out = nread; + return DATA_READ; + } + + e = SSL_get_error(handle->ssl, nread); + if (e == SSL_ERROR_WANT_READ) + return WANT_READ; + else if (e == SSL_ERROR_WANT_WRITE) + return WANT_WRITE; + + if (e == SSL_ERROR_ZERO_RETURN || (e == SSL_ERROR_SYSCALL && nread == 0)) + return DONE; + + flush_errors(context); + return ERROR_TLS; +} + +static void +free_handle(krb5_context context, k5_tls_handle handle) +{ + SSL_free(handle->ssl); + free(handle->servername); + free(handle); +} + +krb5_error_code +tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + k5_tls_vtable vt; + + vt = (k5_tls_vtable)vtable; + vt->setup = setup; + vt->write = write_tls; + vt->read = read_tls; + vt->free_handle = free_handle; + return 0; +} + +#endif /* PROXY_TLS_IMPL_OPENSSL */