qemu already makes use of gnutls, so it is only natural to extend its use to the IRC char driver. Furthermore, though IRC is ubiquitous, there may be servers which allow only SSL connections for some sense of additional security, be it real or not. Adding that support to qemu therefore means greater compatibility which should be the goal of any software implementing an open standard such as IRC.
The Heartbleed crisis influences this patch in two ways: First, it does not use OpenSSL. As GnuTLS is not nearly used as commonly, this will give us an advantage regarding attackers which prefer to attack the most widespread platform. Second, certificates can be stolen any time and revoking them does not work. Therefore, authentication in SSL is broken by design and we do not need to care for it. This guideline is generally followed by IRC servers as well, for instance OFTC, which hosts the #qemu IRC channel, does offer a self-signed certificate. Thus, this policy seems widely accepted and to be current industry practice. Signed-off-by: Max Reitz <mre...@redhat.com> --- configure | 43 ++++++++++++++++------ qapi-schema.json | 3 +- qemu-char.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 12 deletions(-) diff --git a/configure b/configure index 09c9225..63ea221 100755 --- a/configure +++ b/configure @@ -246,6 +246,7 @@ vnc_sasl="" vnc_jpeg="" vnc_png="" vnc_ws="" +gnutls="" xen="" xen_ctrl_version="" xen_pci_passthrough="" @@ -889,6 +890,10 @@ for opt do ;; --enable-vnc-ws) vnc_ws="yes" ;; + --disable-gnutls) gnutls="no" + ;; + --enable-gnutls) gnutls="yes" + ;; --disable-slirp) slirp="no" ;; --disable-uuid) uuid="no" @@ -2283,32 +2288,43 @@ EOF fi ########################################## -# VNC TLS/WS detection -if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; then +# GnuTLS detection +if test "$gnutls" != "no" ; then cat > $TMPC <<EOF #include <gnutls/gnutls.h> int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; } EOF - vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null` - vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null` - if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then + gnutls_cflags=`$pkg_config --cflags gnutls 2> /dev/null` + gnutls_libs=`$pkg_config --libs gnutls 2> /dev/null` + if compile_prog "$gnutls_cflags" "$gnutls_libs" ; then + gnutls=yes + libs_softmmu="$gnutls_libs $libs_softmmu" + QEMU_CFLAGS="$QEMU_CFLAGS $gnutls_cflags" + else + if test "$gnutls" = "yes" ; then + feature_not_found "gnutls" "Install gnutls devel" + fi + gnutls=no + fi +fi + +########################################## +# VNC TLS/WS detection +if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; then + if test "$gnutls" = "yes" ; then if test "$vnc_tls" != "no" ; then vnc_tls=yes fi if test "$vnc_ws" != "no" ; then vnc_ws=yes fi - libs_softmmu="$vnc_tls_libs $libs_softmmu" - QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags" else if test "$vnc_tls" = "yes" ; then - feature_not_found "vnc-tls" "Install gnutls devel" + feature_not_found "vnc-tls" "Enable gnutls" fi if test "$vnc_ws" = "yes" ; then - feature_not_found "vnc-ws" "Install gnutls devel" + feature_not_found "vnc-ws" "Enable gnutls" fi - vnc_tls=no - vnc_ws=no fi fi @@ -4440,6 +4456,7 @@ echo "lzo support $lzo" echo "snappy support $snappy" echo "bzip2 support $bzip2" echo "NUMA host support $numa" +echo "GnuTLS support $gnutls" if test "$sdl_too_old" = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -4921,6 +4938,10 @@ if test "$tpm" = "yes"; then fi fi +if test "$gnutls" = "yes" ; then + echo "CONFIG_GNUTLS=y" >> $config_host_mak +fi + echo "TRACE_BACKENDS=$trace_backends" >> $config_host_mak if have_backend "nop"; then echo "CONFIG_TRACE_NOP=y" >> $config_host_mak diff --git a/qapi-schema.json b/qapi-schema.json index f427e04..e4f93fd 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -2888,7 +2888,8 @@ ## { 'type': 'ChardevIrc', 'data': { 'addr' : 'SocketAddress', 'nick' : 'str', - 'channel' : 'str' } } + 'channel' : 'str', + 'ssl' : 'bool' } } ## # @ChardevBackend: diff --git a/qemu-char.c b/qemu-char.c index 4507e5f..1030d5e 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -81,6 +81,9 @@ #endif #endif #endif +#ifdef CONFIG_GNUTLS +#include <gnutls/gnutls.h> +#endif #include "qemu/sockets.h" #include "ui/qemu-spice.h" @@ -3306,6 +3309,11 @@ typedef struct { GIOChannel *chan; +#ifdef CONFIG_GNUTLS + gnutls_session_t tls_session; + gnutls_certificate_credentials_t tls_cred; +#endif + uint8_t *sendbuf; int sendbuf_idx, sendbuf_prefixlen; bool send_line_skip; @@ -3315,6 +3323,38 @@ typedef struct { bool recv_line_skip; } IrcCharDriverState; +#ifdef CONFIG_GNUTLS +static ssize_t irc_send(IrcCharDriverState *irc, const void *buf, size_t len) +{ + if (irc->tls_session) { + return gnutls_record_send(irc->tls_session, buf, len); + } else { + return send_all(irc->fd, buf, len); + } +} + +static ssize_t irc_recv(IrcCharDriverState *irc, void *buf, size_t len) +{ + if (irc->tls_session) { + ssize_t ret = gnutls_record_recv(irc->tls_session, buf, len); + if (ret >= 0) { + return ret; + } else if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + return irc_recv(irc, buf, len); + } else if (ret == GNUTLS_E_REHANDSHAKE) { + ret = gnutls_handshake(irc->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + return -1; + } + return irc_recv(irc, buf, len); + } else { + return -1; + } + } else { + return recv_all(irc->fd, buf, len, true); + } +} +#else static ssize_t irc_send(IrcCharDriverState *irc, const void *buf, size_t len) { return send_all(irc->fd, buf, len); @@ -3324,6 +3364,7 @@ static ssize_t irc_recv(IrcCharDriverState *irc, void *buf, size_t len) { return recv_all(irc->fd, buf, len, true); } +#endif static int irc_read_line(IrcCharDriverState *irc, char **buf) { @@ -3569,6 +3610,13 @@ static void irc_chr_update_read_handler(CharDriverState *s) static void irc_destroy(IrcCharDriverState *irc) { irc_send(irc, "QUIT\r\n", strlen("QUIT\r\n")); +#ifdef CONFIG_GNUTLS + if (irc->tls_session) { + gnutls_bye(irc->tls_session, GNUTLS_SHUT_RDWR); + gnutls_certificate_free_credentials(irc->tls_cred); + gnutls_deinit(irc->tls_session); + } +#endif g_io_channel_unref(irc->chan); close(irc->fd); @@ -3938,6 +3986,7 @@ static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend, Error **errp) { const char *host, *port, *nick, *channel, *sockfd; + bool ssl; SocketAddress *addr; host = qemu_opt_get(opts, "host"); @@ -3945,6 +3994,11 @@ static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend, sockfd = qemu_opt_get(opts, "sockfd"); nick = qemu_opt_get(opts, "nick"); channel = qemu_opt_get(opts, "channel"); +#ifdef CONFIG_GNUTLS + ssl = qemu_opt_get_bool(opts, "ssl", false); +#else + ssl = false; +#endif if ((!host && !sockfd) || !nick || !channel) { error_setg(errp, "chardev: irc: Missing options"); @@ -3980,6 +4034,7 @@ static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend, backend->irc->nick = g_strdup(nick); backend->irc->channel = g_strdup(channel); + backend->irc->ssl = ssl; } typedef struct CharDriver { @@ -4349,6 +4404,11 @@ QemuOptsList qemu_chardev_opts = { },{ .name = "sockfd", .type = QEMU_OPT_STRING, +#ifdef CONFIG_GNUTLS + },{ + .name = "ssl", + .type = QEMU_OPT_BOOL, +#endif }, { /* end of list */ } }, @@ -4618,6 +4678,52 @@ static CharDriverState *qemu_chr_open_irc(ChardevIrc *irc, Error **errp) irc_cds->recvbuf = g_malloc(1024); irc_cds->recvbuf_idx = 0; +#ifdef CONFIG_GNUTLS + if (irc->ssl) { + ret = gnutls_init(&irc_cds->tls_session, GNUTLS_CLIENT); + if (ret != GNUTLS_E_SUCCESS) { + error_setg(errp, "Failed to initialize GnuTLS session: %s", + gnutls_strerror(ret)); + goto fail; + } + + gnutls_transport_set_ptr(irc_cds->tls_session, + (gnutls_transport_ptr_t)(uintptr_t)fd); + gnutls_transport_set_push_function(irc_cds->tls_session, &push_all); + gnutls_transport_set_pull_function(irc_cds->tls_session, &pull_all); + + ret = gnutls_priority_set_direct(irc_cds->tls_session, "NORMAL", NULL); + if (ret != GNUTLS_E_SUCCESS) { + error_setg(errp, "Setting TLS priorities failed: %s", + gnutls_strerror(ret)); + goto fail; + } + + ret = gnutls_certificate_allocate_credentials(&irc_cds->tls_cred); + if (ret != GNUTLS_E_SUCCESS) { + error_setg(errp, "TLS credential allocation failed: %s", + gnutls_strerror(ret)); + goto fail; + } + + gnutls_certificate_set_verify_function(irc_cds->tls_cred, + gnutls_cert_allow_all); + ret = gnutls_credentials_set(irc_cds->tls_session, + GNUTLS_CRD_CERTIFICATE, irc_cds->tls_cred); + if (ret != GNUTLS_E_SUCCESS) { + error_setg(errp, "TLS credential association failed: %s", + gnutls_strerror(ret)); + goto fail; + } + + ret = gnutls_handshake(irc_cds->tls_session); + if (ret != GNUTLS_E_SUCCESS) { + error_setg(errp, "TLS handshake failed: %s", gnutls_strerror(ret)); + goto fail; + } + } +#endif + sendstr = g_strdup_printf("USER qemu qemu qemu qemu\r\nNICK %s\r\n", irc->nick); ret = irc_send(irc_cds, sendstr, strlen(sendstr)); -- 2.3.4