diff --git a/configure b/configure index 1806966b0..8fd21244b 100755 --- a/configure +++ b/configure @@ -744,6 +744,7 @@ LOCALEDIR_SUFFIX SUDO_NLS LIBPTHREAD LIBMD +OPENSSL_LIBS LIBINTL LIBRT LIBDL @@ -3152,6 +3153,7 @@ PSMAN=0 SEMAN=0 LIBINTL= LIBMD= +OPENSSL_LIBS= ZLIB= ZLIB_SRC= AUTH_OBJS= @@ -6428,6 +6430,7 @@ if test "${enable_openssl+set}" = set; then : enableval=$enable_openssl; case $enableval in no) ;; *) LIBMD="-lcrypto" + OPENSSL_LIBS="-lcrypto -lssl" DIGEST=digest_openssl.lo $as_echo "#define HAVE_OPENSSL 1" >>confdefs.h diff --git a/examples/sudo_logsrvd.conf b/examples/sudo_logsrvd.conf index a8c5e3f07..55ae7238c 100644 --- a/examples/sudo_logsrvd.conf +++ b/examples/sudo_logsrvd.conf @@ -17,6 +17,51 @@ # The default is to listen on all addresses. #listen_address = *:30344 +# Sets timeout for the socket. If this parameter is not set, +# the value will be 0 (no timeout) +#timeout = 30 + +# Sets audit server's communication over TLS on/off. +# Minimum negotiable TLS version is 1.2 +#tls = true + +# Path to the certificate authority bundle file +# It must be in PEM format! +#tls_cacert = /etc/sudo_logsrvd_ca.pem + +# Path to the audit server's private key file +# It must be in PEM format! +#tls_key = /etc/sudo_logsrvd_key.pem + +# Path to the audit server's certificate file +# It must be in PEM format! +#tls_cert = /etc/sudo_logsrvd_cert.pem + +# Path to the Diffie-Hellman parameter file +# If this parameter is not set, the audit server +# will use the OpenSSL defaults for Diffie-Hellman key generation +# It must be in PEM format! +#tls_dhparams = /etc/sudo_logsrvd_dhparams.pem + +# TLS cipher list (see "CIPHER LIST FORMAT" in the OpenSSL manual) +# NOTE that this setting is only effective if the negotiated protocol +# is TLSver1.2. +#tls_ciphers_v12 = HIGH:!aNULL + +# TLS cipher list if negotiated protocol is TLSver1.3 +# available ciphersuites are: +# TLS_AES_128_GCM_SHA256 +# TLS_AES_256_GCM_SHA384 +# TLS_CHACHA20_POLY1305_SHA256 +# TLS_AES_128_CCM_SHA256 +# TLS_AES_128_CCM_8_SHA256 +# multiple ciphersuites can be set separated by ':' +# by default, all ciphersuites are enabled +#tls_ciphers_v13 = TLS_AES_256_GCM_SHA384 + +# Validate client certificates +#tls_checkpeer = false + [iolog] # The top-level directory to use when constructing the path name for the # I/O log directory. The session sequence number, if any, is stored here. diff --git a/logsrvd/Makefile.in b/logsrvd/Makefile.in index ab7dbc5aa..955ed646f 100644 --- a/logsrvd/Makefile.in +++ b/logsrvd/Makefile.in @@ -72,6 +72,8 @@ PIE_LDFLAGS = @PIE_LDFLAGS@ SSP_CFLAGS = @SSP_CFLAGS@ SSP_LDFLAGS = @SSP_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ + # cppcheck options, usually set in the top-level Makefile CPPCHECK_OPTS = -q --force --enable=warning,performance,portability --suppress=constStatement --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64 @@ -145,10 +147,10 @@ Makefile: $(srcdir)/Makefile.in ifile=$<; rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $${ifile%i}c --i-file $< --output-file $@ sudo_logsrvd: $(LOGSRVD_OBJS) $(LT_LIBS) - $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LOGSRVD_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LOGSRVD_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) $(OPENSSL_LIBS) sudo_sendlog: $(SENDLOG_OBJS) $(LT_LIBS) - $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SENDLOG_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SENDLOG_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) $(OPENSSL_LIBS) GENERATED = log_server.pb-c.h log_server.pb-c.c diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index 006911c18..0578fb9d9 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -40,6 +40,11 @@ #include #include +#if defined(HAVE_OPENSSL) +# include +# include +#endif + #include "log_server.pb-c.h" #include "sudo_gettext.h" /* must be included before sudo_compat.h */ #include "sudo_compat.h" @@ -60,6 +65,11 @@ # include "compat/getopt.h" #endif /* HAVE_GETOPT_LONG */ +#if defined(HAVE_OPENSSL) +# define LOGSRVD_DEFAULT_CIPHER_LST12 "HIGH:!aNULL" +# define LOGSRVD_DEFAULT_CIPHER_LST13 "TLS_AES_256_GCM_SHA384" +#endif + /* * Sudo I/O audit server. */ @@ -81,6 +91,13 @@ connection_closure_free(struct connection_closure *closure) if (closure != NULL) { bool shutting_down = closure->state == SHUTDOWN; +#if defined(HAVE_OPENSSL) + /* deallocate the connection's ssl object */ + if (logsrvd_conf_get_tls_opt() == true) { + if (closure->ssl) + SSL_free(closure->ssl); + } +#endif TAILQ_REMOVE(&connections, closure, entries); close(closure->sock); iolog_close_all(closure); @@ -556,6 +573,12 @@ shutdown_cb(int unused, int what, void *v) struct sudo_event_base *base = v; debug_decl(shutdown_cb, SUDO_DEBUG_UTIL) +#if defined(HAVE_OPENSSL) + /* deallocate server's SSL context object */ + if (logsrvd_conf_get_tls_opt() == true) { + SSL_CTX_free(logsrvd_get_tls_runtime()->ssl_ctx); + } +#endif sudo_ev_loopbreak(base); debug_return; @@ -614,7 +637,16 @@ server_msg_cb(int fd, int what, void *v) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to client", __func__, buf->len - buf->off); +#if defined(HAVE_OPENSSL) + if (logsrvd_conf_get_tls_opt() == true) { + nwritten = SSL_write(closure->ssl, buf->data + buf->off, buf->len - buf->off); + } else { + nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); + } +#else nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); +#endif + if (nwritten == -1) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, "unable to send %u bytes", buf->len - buf->off); @@ -652,7 +684,16 @@ client_msg_cb(int fd, int what, void *v) ssize_t nread; debug_decl(client_msg_cb, SUDO_DEBUG_UTIL) - nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); +#if defined(HAVE_OPENSSL) + if (logsrvd_conf_get_tls_opt() == true) { + nread = SSL_read(closure->ssl, buf->data + buf->len, buf->size); + } else { + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); + } +#else + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); +#endif + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from client", __func__, nread); switch (nread) { @@ -784,6 +825,306 @@ signal_cb(int signo, int what, void *v) debug_return; } +#if defined(HAVE_OPENSSL) +static X509 * +load_cert(const char *file) +{ + X509 *x509 = NULL; + BIO *cert = NULL; + + debug_decl(load_cert, SUDO_DEBUG_UTIL) + + if ((cert = BIO_new(BIO_s_file())) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate new BIO object for certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + if (BIO_read_filename(cert, file) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to read certificate file: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + x509 = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL); + +exit: + if (cert) + BIO_free(cert); + + debug_return_ptr(x509); +} + +static bool +check_cert(X509_STORE *ca_store_ctx, SSL_CTX *ctx, const char *cert_file) +{ + bool ret = false; + X509_STORE_CTX *store_ctx = NULL; + X509 *x509 = NULL; + + debug_decl(check_cert, SUDO_DEBUG_UTIL) + + if ((x509 = load_cert(cert_file)) == NULL) + goto exit; + + if ((store_ctx = X509_STORE_CTX_new()) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate X509_STORE_CTX object: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + X509_STORE_set_flags(ca_store_ctx, X509_V_FLAG_X509_STRICT); + + if (!X509_STORE_CTX_init(store_ctx, ca_store_ctx, x509, 0)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to initialize X509_STORE_CTX object: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + if (X509_verify_cert(store_ctx) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to verify cert: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + /* everything is good, use this server certificate during TLS handshakes */ + if (!SSL_CTX_use_certificate(ctx, x509)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load cert to the ssl context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + + ret = true; +exit: + X509_STORE_CTX_free(store_ctx); + X509_free(x509); + + debug_return_bool(ret); +} + +static bool +verify_server_cert(SSL_CTX *ctx, const struct logsrvd_tls_config *tls_config) +{ + bool ret = false; + X509_STORE *ca_store_ctx = NULL; + X509_LOOKUP *x509_lookup = NULL; + + debug_decl(verify_server_cert, SUDO_DEBUG_UTIL) + + if ((ca_store_ctx = X509_STORE_new()) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate X509_STORE object for CA bundle: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + if ((x509_lookup = X509_STORE_add_lookup(ca_store_ctx, X509_LOOKUP_file())) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to set lookup method: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + if(!X509_LOOKUP_load_file(x509_lookup, tls_config->cacert_path, X509_FILETYPE_PEM)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load CA bundle file: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto exit; + } + + ret = check_cert(ca_store_ctx, ctx, tls_config->cert_path); + +exit: + X509_STORE_free(ca_store_ctx); + + debug_return_bool(ret); +} + +static bool +init_tls_ciphersuites(SSL_CTX *ctx, const struct logsrvd_tls_config *tls_config) +{ + debug_decl(init_tls_ciphersuites, SUDO_DEBUG_UTIL) + + /* try to set TLS v1.2 ciphersuite list from config if given */ + if (tls_config->ciphers_v12) { + if (SSL_CTX_set_cipher_list(ctx, tls_config->ciphers_v12)) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "TLS v1.2 ciphersuite list is set from config"); + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to set configured TLS v1.2 ciphersuite list (%s). Falling back to default...", + ERR_error_string(ERR_get_error(), NULL)); + debug_return_bool(false); + } + /* fallback to default ciphersuites for TLS v1.2 */ + } else { + if (SSL_CTX_set_cipher_list(ctx, LOGSRVD_DEFAULT_CIPHER_LST12) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load default TLS v1.2 ciphersuite list: %s", + ERR_error_string(ERR_get_error(), NULL)); + debug_return_bool(false); + } else { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "TLS v1.2 ciphersuite list is set to default (%s)", + LOGSRVD_DEFAULT_CIPHER_LST12); + } + } + + /* try to set TLSv1.3 ciphersuite list from config */ + if (tls_config->ciphers_v13) { + if (SSL_CTX_set_ciphersuites(ctx, tls_config->ciphers_v13)) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "TLS v1.3 ciphersuite list is set from config"); + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load configured TLS v1.3 ciphersuite list (%s). Falling back to default...", + ERR_error_string(ERR_get_error(), NULL)); + debug_return_bool(false); + } + /* fallback to default ciphersuites for TLS v1.3 */ + } else { + if (SSL_CTX_set_ciphersuites(ctx, LOGSRVD_DEFAULT_CIPHER_LST13) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load default TLS v1.3 ciphersuite list: %s", + ERR_error_string(ERR_get_error(), NULL)); + debug_return_bool(false); + } else { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "TLS v1.3 ciphersuite list is set to default (%s)", + LOGSRVD_DEFAULT_CIPHER_LST13); + } + } + + debug_return_bool(true); +} + +/* + * Calls series of openssl initialization functions in order to + * be able to establish configured network connections over TLS + */ +static SSL_CTX * +init_tls_server_context(void) +{ + const SSL_METHOD *method; + SSL_CTX *ctx = NULL; + const struct logsrvd_tls_config *tls_config = logsrvd_get_tls_config(); + + debug_decl(init_tls_server_context, SUDO_DEBUG_UTIL) + + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + + if ((method = TLS_server_method()) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "creation of SSL_METHOD failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if ((ctx = SSL_CTX_new(method)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "creation of new SSL_CTX object failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + /* verify server certification against the CA bundle file */ + if (!verify_server_cert(ctx, tls_config)) { + goto bad; + } + + /* if peer authentication is enabled, verify client cert during TLS handshake */ + if (tls_config->check_peer) { + X509 *cacert = load_cert(tls_config->cacert_path); + + /* server will send the name of the CA to the client during the handshake */ + if (SSL_CTX_add_client_CA(ctx, cacert) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "calling SSL_CTX_add_client_CA() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* sets the location of the CA bundle file for verification purposes */ + if (SSL_CTX_load_verify_locations(ctx, tls_config->cacert_path, NULL) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "calling SSL_CTX_load_verify_locations() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* server will send a client certificate request to the peer during the handshake */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + + if (!SSL_CTX_use_PrivateKey_file(ctx, tls_config->pkey_path, SSL_FILETYPE_PEM)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load key file: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if (!SSL_CTX_check_private_key(ctx)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to verify key file: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + /* initialize TLSv1.2 and TLSv1.3 ciphersuites */ + if (!init_tls_ciphersuites(ctx, tls_config)) { + goto bad; + } + + /* try to load and set diffie-hellman parameters */ + FILE *dhparam_file = fopen(tls_config->dhparams_path, "r"); + if (dhparam_file != NULL) { + DH* dhparams; + if ((dhparams = PEM_read_DHparams(dhparam_file, NULL, NULL, NULL)) != NULL) { + if (!SSL_CTX_set_tmp_dh(ctx, dhparams)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to set dh parameters: %s", + ERR_error_string(ERR_get_error(), NULL)); + } else { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "diffie-hellman parameters are loaded"); + } + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "dhparam file can't be loaded: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + fclose(dhparam_file); + } else { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "dhparam file not found, will use default parameters"); + } + + /* audit server supports TLS ver1.2 or higher */ + if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to restrict min. protocol version: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + goto good; + +bad: + if (ctx) { + SSL_CTX_free(ctx); + ctx = NULL; + } + +good: + debug_return_ptr(ctx); +} +#endif + /* * Allocate a new connection closure. */ @@ -796,6 +1137,33 @@ connection_closure_alloc(int sock) if ((closure = calloc(1, sizeof(*closure))) == NULL) debug_return_ptr(NULL); + TAILQ_INSERT_TAIL(&connections, closure, entries); + +#if defined(HAVE_OPENSSL) + if (logsrvd_conf_get_tls_opt() == true) { + if ((closure->ssl = SSL_new(logsrvd_get_tls_runtime()->ssl_ctx)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to create new ssl object: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + if (SSL_set_fd(closure->ssl, sock) != 1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to set fd for TLS: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + if (SSL_accept(closure->ssl) != 1 ) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "TLS handshake was unsuccessful: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + } +#endif + closure->iolog_dir_fd = -1; closure->sock = sock; @@ -819,7 +1187,6 @@ connection_closure_alloc(int sock) if (closure->write_ev == NULL) goto bad; - TAILQ_INSERT_TAIL(&connections, closure, entries); debug_return_ptr(closure); bad: connection_closure_free(closure); @@ -859,6 +1226,7 @@ static int create_listener(struct listen_address *addr) { int flags, i, sock; + struct timeval timeout; debug_decl(create_listener, SUDO_DEBUG_UTIL) if ((sock = socket(addr->sa_un.sa.sa_family, SOCK_STREAM, 0)) == -1) { @@ -868,6 +1236,12 @@ create_listener(struct listen_address *addr) i = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) == -1) sudo_warn("SO_REUSEADDR"); + timeout.tv_sec = logsrvd_conf_get_sock_timeout(); + timeout.tv_usec = 0; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) + sudo_warn("SO_RCVTIMEO"); + if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1) + sudo_warn("SO_SNDTIMEO"); if (bind(sock, &addr->sa_un.sa, addr->sa_len) == -1) { sudo_warn("bind"); goto bad; @@ -923,12 +1297,13 @@ register_listener(struct listen_address *addr, struct sudo_event_base *base) debug_decl(register_listener, SUDO_DEBUG_UTIL) sock = create_listener(addr); + if (sock != -1) { - ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, listener_cb, base); - if (ev == NULL) - sudo_fatal(NULL); - if (sudo_ev_add(base, ev, NULL, false) == -1) - sudo_fatal(U_("unable to add event to queue")); + ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, listener_cb, base); + if (ev == NULL) + sudo_fatal(NULL); + if (sudo_ev_add(base, ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); } debug_return; @@ -938,7 +1313,7 @@ static void register_signal(int signo, struct sudo_event_base *base) { struct sudo_event *ev; - debug_decl(register_listener, SUDO_DEBUG_UTIL) + debug_decl(register_signal, SUDO_DEBUG_UTIL) ev = sudo_ev_alloc(signo, SUDO_EV_SIGNAL, signal_cb, base); if (ev == NULL) @@ -1104,6 +1479,14 @@ main(int argc, char *argv[]) TAILQ_FOREACH(addr, logsrvd_conf_listen_address(), entries) register_listener(addr, evbase); +#if defined(HAVE_OPENSSL) + if (logsrvd_conf_get_tls_opt() == true) { + struct logsrvd_tls_runtime *tls_runtime = logsrvd_get_tls_runtime(); + if ((tls_runtime->ssl_ctx = init_tls_server_context()) == NULL) + sudo_fatal(NULL); + } +#endif + register_signal(SIGHUP, evbase); register_signal(SIGINT, evbase); register_signal(SIGTERM, evbase); diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 1f59ee3fb..5945bde21 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -21,11 +21,20 @@ # error protobuf-c version 1.30 or higher required #endif +#include "config.h" + +#if defined(HAVE_OPENSSL) +# include +#endif + #include "logsrv_util.h" /* Default listen address (port 30344 on all interfaces). */ #define DEFAULT_LISTEN_ADDR "*:" DEFAULT_PORT_STR +/* Default timeout value for server socket */ +#define DEFAULT_SOCKET_TIMEOUT_SEC 30 + /* How often to send an ACK to the client (commit point) in seconds */ #define ACK_FREQUENCY 10 @@ -81,6 +90,9 @@ struct connection_closure { struct sudo_event *commit_ev; struct sudo_event *read_ev; struct sudo_event *write_ev; +#if defined(HAVE_OPENSSL) + SSL *ssl; +#endif const char *errstr; struct iolog_file iolog_files[IOFD_MAX]; int iolog_dir_fd; @@ -106,6 +118,23 @@ struct listen_address { }; TAILQ_HEAD(listen_address_list, listen_address); +#if defined(HAVE_OPENSSL) +/* parameters to configure tls */ +struct logsrvd_tls_config { + char *pkey_path; + char *cert_path; + char *cacert_path; + char *dhparams_path; + char *ciphers_v12; + char *ciphers_v13; + bool check_peer; +}; + +struct logsrvd_tls_runtime { + SSL_CTX *ssl_ctx; +}; +#endif + /* Supported eventlog types */ enum logsrvd_eventlog_type { EVLOG_NONE, @@ -138,6 +167,12 @@ bool logsrvd_conf_read(const char *path); const char *logsrvd_conf_iolog_dir(void); const char *logsrvd_conf_iolog_file(void); struct listen_address_list *logsrvd_conf_listen_address(void); +int logsrvd_conf_get_sock_timeout(void); +#if defined(HAVE_OPENSSL) +bool logsrvd_conf_get_tls_opt(void); +const struct logsrvd_tls_config *logsrvd_get_tls_config(void); +struct logsrvd_tls_runtime *logsrvd_get_tls_runtime(void); +#endif enum logsrvd_eventlog_type logsrvd_conf_eventlog_type(void); enum logsrvd_eventlog_format logsrvd_conf_eventlog_format(void); unsigned int logsrvd_conf_syslog_maxlen(void); diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c index b37371000..1fd60d3ef 100644 --- a/logsrvd/logsrvd_conf.c +++ b/logsrvd/logsrvd_conf.c @@ -67,7 +67,13 @@ struct logsrvd_config_section { static struct logsrvd_config { struct logsrvd_config_server { - struct listen_address_list addresses; + struct listen_address_list addresses; + int timeout; +#if defined(HAVE_OPENSSL) + bool tls; + struct logsrvd_tls_config tls_config; + struct logsrvd_tls_runtime tls_runtime; +#endif } server; struct logsrvd_config_iolog { bool compress; @@ -123,6 +129,32 @@ logsrvd_conf_listen_address(void) return &logsrvd_config->server.addresses; } +int +logsrvd_conf_get_sock_timeout(void) +{ + return logsrvd_config->server.timeout; +} + +#if defined(HAVE_OPENSSL) +bool +logsrvd_conf_get_tls_opt(void) +{ + return logsrvd_config->server.tls; +} + +const struct logsrvd_tls_config * +logsrvd_get_tls_config(void) +{ + return &logsrvd_config->server.tls_config; +} + +struct logsrvd_tls_runtime * +logsrvd_get_tls_runtime(void) +{ + return &logsrvd_config->server.tls_runtime; +} +#endif + /* eventlog getters */ enum logsrvd_eventlog_type logsrvd_conf_eventlog_type(void) @@ -358,6 +390,128 @@ done: debug_return_bool(ret); } +#if defined(HAVE_OPENSSL) +static bool +cb_tls_opt(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_tls_opt, SUDO_DEBUG_UTIL) + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->server.tls = val; + debug_return_bool(true); +} + +static bool +cb_timeout(struct logsrvd_config *config, const char *str) +{ + int timeout; + const char* errstr; + debug_decl(cb_timeout, SUDO_DEBUG_UTIL) + + timeout = sudo_strtonum(str, 0, UINT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + + config->server.timeout = timeout; + + debug_return_bool(true); +} + +static bool +cb_tls_key(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_key, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.pkey_path); + if ((config->server.tls_config.pkey_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_cacert(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_cacert, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.cacert_path); + if ((config->server.tls_config.cacert_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_cert(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_cert, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.cert_path); + if ((config->server.tls_config.cert_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_dhparam(struct logsrvd_config *config, const char *path) +{ + debug_decl(cb_tls_dhparam, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.dhparams_path); + if ((config->server.tls_config.dhparams_path = strdup(path)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_ciphers12(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_tls_ciphers12, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.ciphers_v12); + if ((config->server.tls_config.ciphers_v12 = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_ciphers13(struct logsrvd_config *config, const char *str) +{ + debug_decl(cb_tls_ciphers13, SUDO_DEBUG_UTIL) + + free(config->server.tls_config.ciphers_v13); + if ((config->server.tls_config.ciphers_v13 = strdup(str)) == NULL) { + sudo_warn(NULL); + debug_return_bool(false); + } + debug_return_bool(true); +} + +static bool +cb_tls_checkpeer(struct logsrvd_config *config, const char *str) +{ + int val; + debug_decl(cb_tls_checkpeer, SUDO_DEBUG_UTIL) + + if ((val = sudo_strtobool(str)) == -1) + debug_return_bool(false); + + config->server.tls_config.check_peer = val; + debug_return_bool(true); +} +#endif + /* eventlog callbacks */ static bool cb_eventlog_type(struct logsrvd_config *config, const char *str) @@ -513,6 +667,17 @@ cb_logfile_time_format(struct logsrvd_config *config, const char *str) static struct logsrvd_config_entry server_conf_entries[] = { { "listen_address", cb_listen_address }, + { "timeout", cb_timeout }, +#if defined(HAVE_OPENSSL) + { "tls", cb_tls_opt }, + { "tls_key", cb_tls_key }, + { "tls_cacert", cb_tls_cacert }, + { "tls_cert", cb_tls_cert }, + { "tls_dhparams", cb_tls_dhparam }, + { "tls_ciphers_v12", cb_tls_ciphers12 }, + { "tls_ciphers_v13", cb_tls_ciphers13 }, + { "tls_checkpeer", cb_tls_checkpeer }, +#endif { NULL } }; @@ -682,6 +847,7 @@ logsrvd_conf_alloc(void) /* Server defaults */ TAILQ_INIT(&config->server.addresses); + config->server.timeout = DEFAULT_SOCKET_TIMEOUT_SEC; /* I/O log defaults */ config->iolog.compress = false; diff --git a/logsrvd/sendlog.c b/logsrvd/sendlog.c index 8d173117b..8074cb820 100644 --- a/logsrvd/sendlog.c +++ b/logsrvd/sendlog.c @@ -53,6 +53,11 @@ #include "sudo_iolog.h" #include "sendlog.h" +#if defined(HAVE_OPENSSL) +# include +# include +#endif + #ifndef HAVE_GETADDRINFO # include "compat/getaddrinfo.h" #endif @@ -65,10 +70,20 @@ static struct iolog_file iolog_files[IOFD_MAX]; static char *iolog_dir; +#if defined(HAVE_OPENSSL) +static bool tls = false; +static SSL_CTX *ssl_ctx = NULL; +static SSL *ssl = NULL; +#endif + static void usage(bool fatal) { +#if defined(HAVE_OPENSSL) + fprintf(stderr, "usage: %s [-h host] [-i iolog-id] [-p port] [-t -b ca_bundle] [-c cert_file [-k key_file]] " +#else fprintf(stderr, "usage: %s [-h host] [-i iolog-id] [-p port] " +#endif "[-r restart-point] /path/to/iolog\n", getprogname()); exit(1); } @@ -85,10 +100,78 @@ help(void) " -i, --iolog_id remote ID of I/O log to be resumed\n" " -p, --port port to use when connecting to host\n" " -r, --restart restart previous I/O log transfer\n" +#if defined(HAVE_OPENSSL) + " -t, --tls set the communication over TLS with the audit server\n" + " -b, --ca-bundle certificate bundle file to verify server's cert against\n" + " -c, --cert certificate file for TLS handshake\n" + " -k, --key private key file\n" +#endif " -V, --version display version information and exit\n")); exit(0); } +#if defined(HAVE_OPENSSL) +static SSL_CTX * +init_tls_client_context(const char *ca_bundle_file, const char *cert_file, const char *key_file) +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + + debug_decl(init_tls_client_context, SUDO_DEBUG_UTIL) + + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + + if ((method = TLS_client_method()) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "creation of SSL_METHOD failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if ((ctx = SSL_CTX_new(method)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "creation of new SSL_CTX object failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + if (cert_file) { + if (!SSL_CTX_use_certificate_chain_file(ctx, cert_file)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load cert to the ssl context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if (!SSL_CTX_use_PrivateKey_file(ctx, key_file, X509_FILETYPE_PEM)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to load key to the ssl context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + } + + /* sets the location of the CA bundle file for verification purposes */ + if (SSL_CTX_load_verify_locations(ctx, ca_bundle_file, NULL) <= 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "calling SSL_CTX_load_verify_locations() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* set verify server cert during the handshake */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + goto exit; + +bad: + if (ctx) + SSL_CTX_free(ctx); + +exit: + return ctx; +} +#endif + /* * Connect to specified host:port * If host has multiple addresses, the first one that connects is used. @@ -129,16 +212,6 @@ connect_server(const char *host, const char *port) } break; /* success */ } - if (sock != -1) { - int flags = fcntl(sock, F_GETFL, 0); - if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { - cause = "fcntl(O_NONBLOCK)"; - save_errno = errno; - close(sock); - errno = save_errno; - sock = -1; - } - } if (sock == -1) sudo_warn("%s", cause); @@ -877,7 +950,15 @@ server_msg_cb(int fd, int what, void *v) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: reading ServerMessage", __func__); /* XXX - make common */ +#if defined(HAVE_OPENSSL) + if (tls) { + nread = SSL_read(ssl, buf->data + buf->len, buf->size - buf->len); + } else { + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); + } +#else nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); +#endif sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from server", __func__, nread); switch (nread) { @@ -942,7 +1023,15 @@ client_msg_cb(int fd, int what, void *v) sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to server", __func__, buf->len - buf->off); +#if defined(HAVE_OPENSSL) + if (tls) { + nwritten = SSL_write(ssl, buf->data + buf->off, buf->len - buf->off); + } else { + nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); + } +#else nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); +#endif if (nwritten == -1) { sudo_warn("send"); goto bad; @@ -1049,13 +1138,23 @@ parse_timespec(struct timespec *ts, const char *strval) debug_return_bool(true); } +#if defined(HAVE_OPENSSL) +static const char short_opts[] = "h:i:p:r:tb:c:k:V"; +#else static const char short_opts[] = "h:i:p:r:V"; +#endif static struct option long_opts[] = { { "help", no_argument, NULL, 1 }, { "host", required_argument, NULL, 'h' }, { "iolog-id", required_argument, NULL, 'i' }, { "port", required_argument, NULL, 'p' }, { "restart", required_argument, NULL, 'r' }, +#if defined(HAVE_OPENSSL) + { "tls", no_argument, NULL, 't' }, + { "ca-bundle", required_argument, NULL, 'b' }, + { "cert", required_argument, NULL, 'c' }, + { "key", required_argument, NULL, 'k' }, +#endif { "version", no_argument, NULL, 'V' }, { NULL, no_argument, NULL, 0 }, }; @@ -1068,6 +1167,11 @@ main(int argc, char *argv[]) struct client_closure closure; struct sudo_event_base *evbase; struct iolog_info *log_info; +#if defined(HAVE_OPENSSL) + const char *ca_bundle = NULL; + const char *cert = NULL; + const char *key = NULL; +#endif const char *host = "localhost"; const char *port = DEFAULT_PORT_STR; struct timespec restart = { 0, 0 }; @@ -1120,6 +1224,20 @@ main(int argc, char *argv[]) case 1: help(); break; +#if defined(HAVE_OPENSSL) + case 'b': + ca_bundle = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + case 't': + tls = true; + break; +#endif case 'V': (void)printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION); @@ -1131,6 +1249,21 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; +#if defined(HAVE_OPENSSL) + /* if the protocol is tls, the CA bundle file is a required argument + * to be able to verify the server's certificate + */ + if (tls && !ca_bundle) { + sudo_warnx("%s", U_("with the tls protocol, the CA bundle file must be specified")); + usage(true); + } + + /* if no key file is given explicitly, try to load the key from the cert */ + if (cert && !key) { + key = cert; + } +#endif + if (sudo_timespecisset(&restart) != (iolog_id != NULL)) { sudo_warnx("%s", U_("both restart point and iolog ID must be specified")); usage(true); @@ -1167,7 +1300,39 @@ main(int argc, char *argv[]) sock = connect_server(host, port); if (sock == -1) goto bad; - printf("connected to %s:%s\n", host, port); +#if defined(HAVE_OPENSSL) + if (tls) { + int ret; + if ((ssl_ctx = init_tls_client_context(ca_bundle, cert, key)) == NULL) { + sudo_warnx(U_("Unable to initialize ssl context: %s\n"), + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if ((ssl = SSL_new(ssl_ctx)) == NULL) { + sudo_warnx(U_("Unable to allocate ssl object: %s\n"), + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + if (SSL_set_fd(ssl, sock) <= 0) { + sudo_warnx(U_("Unable to attach socket to the ssl object: %s\n"), + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + ret = SSL_ERROR_NONE; + + if ((ret = SSL_connect(ssl)) != 1) { + sudo_warnx(U_("SSL_connect failed: ret=%d ssl_error=%d, stack=%s\n"), + ret, + SSL_get_error(ssl, ret), + ERR_error_string(ERR_get_error(), NULL)); + goto bad; + } + + printf("Protocol version: %s\n", SSL_get_version(ssl)); + printf("Negotiated ciphersuite: %s\n", SSL_get_cipher(ssl)); + } +#endif + printf("Connected to %s:%s\n", host, port); if ((evbase = sudo_ev_base_alloc()) == NULL) sudo_fatal(NULL);