Add a QIOChannel subclass that can run the TLS protocol over the top of another QIOChannel instance.
Signed-off-by: Daniel P. Berrange <berra...@redhat.com> --- include/io/channel-tls.h | 142 +++++++++++++++++ io/Makefile.objs | 1 + io/channel-tls.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+) create mode 100644 include/io/channel-tls.h create mode 100644 io/channel-tls.c diff --git a/include/io/channel-tls.h b/include/io/channel-tls.h new file mode 100644 index 0000000..7023b0a --- /dev/null +++ b/include/io/channel-tls.h @@ -0,0 +1,142 @@ +/* + * QEMU I/O channels TLS driver + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef QIO_CHANNEL_TLS_H__ +#define QIO_CHANNEL_TLS_H__ + +#include "io/channel.h" +#include "io/task.h" +#include "crypto/tlssession.h" + +#define TYPE_QIO_CHANNEL_TLS "qemu:io-channel-tls" +#define QIO_CHANNEL_TLS(obj) \ + OBJECT_CHECK(QIOChannelTLS, (obj), TYPE_QIO_CHANNEL_TLS) + +typedef struct QIOChannelTLS QIOChannelTLS; + +/** + * QIOChannelTLS + * + * The QIOChannelTLS class provides a channel wrapper which + * can transparently run the TLS encryption protocol. It is + * usually used over a TCP socket, but there is actually no + * technical restriction on which type of master channel is + * used as the transport. + * + * This channel object is capable of running as either a + * TLS server or TLS client. + */ + +struct QIOChannelTLS { + QIOChannel parent; + QIOChannel *master; + QCryptoTLSSession *session; +}; + +/** + * qio_channel_tls_new_server: + * @master: the underlying channel object + * @creds: the credentials to use for TLS handshake + * @aclname: the access control list for validating clients + * @errp: pointer to an uninitialized error object + * + * Create a new TLS channel that runs the server side of + * a TLS session. The TLS session handshake will use the + * credentials provided in @creds. If the @aclname parameter + * is non-NULL, then the client will have to provide + * credentials (ie a x509 client certificate) which will + * then be validated against the ACL. + * + * After creating the channel, it is mandatory to call + * the qio_channel_tls_handshake() method before attempting + * todo any I/O on the channel. + * + * Once the handshake has completed, all I/O should be done + * via the new TLS channel object and not the original + * master channel + * + * Returns: the new TLS channel object, or NULL + */ +QIOChannelTLS * +qio_channel_tls_new_server(QIOChannel *master, + QCryptoTLSCreds *creds, + const char *aclname, + Error **errp); + +/** + * qio_channel_tls_new_client: + * @master: the underlying channel object + * @creds: the credentials to use for TLS handshake + * @hostname: the user specified server hostname + * @errp: pointer to an uninitialized error object + * + * Create a new TLS channel that runs the client side of + * a TLS session. The TLS session handshake will use the + * credentials provided in @creds. The @hostname parameter + * should provide the user specified hostname of the server + * and will be validated against the server's credentials + * (ie CommonName of the x509 certificate) + * + * After creating the channel, it is mandatory to call + * the qio_channel_tls_handshake() method before attempting + * todo any I/O on the channel. + * + * Once the handshake has completed, all I/O should be done + * via the new TLS channel object and not the original + * master channel + * + * Returns: the new TLS channel object, or NULL + */ +QIOChannelTLS * +qio_channel_tls_new_client(QIOChannel *master, + QCryptoTLSCreds *creds, + const char *hostname, + Error **errp); + +/** + * qio_channel_tls_handshake: + * @ioc: the TLS channel object + * @func: the callback to invoke when completed + * @opaque: opaque data to pass to @func + * @destroy: optional callback to free @opaque + * + * Perform the TLS session handshake. This method + * will return immediately and the handshake will + * continue in the background, provided the main + * loop is running. When the handshake is complete, + * or fails, the @func callback will be invoked. + */ +void qio_channel_tls_handshake(QIOChannelTLS *ioc, + QIOTaskFunc func, + gpointer opaque, + GDestroyNotify destroy); + +/** + * qio_channel_tls_get_session: + * @ioc: the TLS channel object + * + * Get the TLS session used by the channel. + * + * Returns: the TLS session + */ +QCryptoTLSSession * +qio_channel_tls_get_session(QIOChannelTLS *ioc); + +#endif /* QIO_CHANNEL_TLS_H__ */ diff --git a/io/Makefile.objs b/io/Makefile.objs index b9973ac..3bf7656 100644 --- a/io/Makefile.objs +++ b/io/Makefile.objs @@ -3,3 +3,4 @@ util-obj-y += channel.o util-obj-y += channel-unix.o util-obj-y += channel-socket.o util-obj-y += channel-file.o +util-obj-y += channel-tls.o diff --git a/io/channel-tls.c b/io/channel-tls.c new file mode 100644 index 0000000..4f5f2e9 --- /dev/null +++ b/io/channel-tls.c @@ -0,0 +1,393 @@ +/* + * QEMU I/O channels TLS driver + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <glib/gi18n.h> + +#include "io/channel-tls.h" + +#define QIO_DEBUG + +#ifdef QIO_DEBUG +#define DPRINTF(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + + +static ssize_t qio_channel_tls_write_handler(const char *buf, + size_t len, + void *opaque) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque); + int ret; + + ret = qio_channel_write(tioc->master, buf, len, NULL); + if (ret == QIO_CHANNEL_ERR_BLOCK) { + errno = EAGAIN; + return -1; + } else if (ret < 0) { + errno = EIO; + return -1; + } + return ret; +} + +static ssize_t qio_channel_tls_read_handler(char *buf, + size_t len, + void *opaque) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque); + int ret; + + ret = qio_channel_read(tioc->master, buf, len, NULL); + if (ret == QIO_CHANNEL_ERR_BLOCK) { + errno = EAGAIN; + return -1; + } else if (ret < 0) { + errno = EIO; + return -1; + } + return ret; +} + + +QIOChannelTLS * +qio_channel_tls_new_server(QIOChannel *master, + QCryptoTLSCreds *creds, + const char *aclname, + Error **errp) +{ + QIOChannelTLS *ioc; + + ioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS)); + + ioc->master = master; + object_ref(OBJECT(master)); + + ioc->session = qcrypto_tls_session_new( + creds, + NULL, + aclname, + QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + errp); + if (!ioc->session) { + goto error; + } + + qcrypto_tls_session_set_callbacks( + ioc->session, + qio_channel_tls_write_handler, + qio_channel_tls_read_handler, + ioc); + + return ioc; + + error: + DPRINTF("Session setup failed %s\n", + error_get_pretty(*errp)); + object_unref(OBJECT(ioc)); + return NULL; +} + +QIOChannelTLS * +qio_channel_tls_new_client(QIOChannel *master, + QCryptoTLSCreds *creds, + const char *hostname, + Error **errp) +{ + QIOChannelTLS *ioc; + + ioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS)); + + ioc->master = master; + object_ref(OBJECT(master)); + + ioc->session = qcrypto_tls_session_new( + creds, + hostname, + NULL, + QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, + errp); + if (!ioc->session) { + goto error; + } + + qcrypto_tls_session_set_callbacks( + ioc->session, + qio_channel_tls_write_handler, + qio_channel_tls_read_handler, + ioc); + + return ioc; + + error: + DPRINTF("Session setup failed %s\n", + error_get_pretty(*errp)); + object_unref(OBJECT(ioc)); + return NULL; +} + + +static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, + GIOCondition condition, + gpointer user_data); + +static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + QIOTask *task) +{ + Error *err = NULL; + QCryptoTLSSessionHandshakeStatus status; + + if (qcrypto_tls_session_handshake(ioc->session, &err) < 0) { + qio_task_abort(task, err); + goto cleanup; + } + + status = qcrypto_tls_session_get_handshake_status(ioc->session); + if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { + if (qcrypto_tls_session_check_credentials(ioc->session, + &err) < 0) { + DPRINTF("Check creds failed session=%p err=%s\n", + ioc->session, error_get_pretty(err)); + qio_task_abort(task, err); + goto cleanup; + } + DPRINTF("Handshake compelte session=%p\n", + ioc->session); + qio_task_complete(task); + } else { + GIOCondition condition; + DPRINTF("Handshake still running %d\n", status); + if (status == QCRYPTO_TLS_HANDSHAKE_SENDING) { + condition = G_IO_OUT; + } else { + condition = G_IO_IN; + } + + object_ref(OBJECT(task)); + qio_channel_add_watch_full(ioc->master, + G_PRIORITY_DEFAULT, + condition, + qio_channel_tls_handshake_io, + task, + (GDestroyNotify)object_unref); + } + + cleanup: + error_free(err); +} + + +static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, + GIOCondition condition, + gpointer user_data) +{ + QIOTask *task = QIO_TASK(user_data); + + qio_channel_tls_handshake_task( + QIO_CHANNEL_TLS(task->source), task); + + return FALSE; +} + +void qio_channel_tls_handshake(QIOChannelTLS *ioc, + QIOTaskFunc func, + gpointer opaque, + GDestroyNotify destroy) +{ + QIOTask *task; + + task = qio_task_new(OBJECT(ioc), + func, opaque, destroy); + + qio_channel_tls_handshake_task(ioc, task); + + object_unref(OBJECT(task)); +} + + +static void qio_channel_tls_init(Object *obj G_GNUC_UNUSED) +{ +} + + +static void qio_channel_tls_finalize(Object *obj) +{ + QIOChannelTLS *ioc = QIO_CHANNEL_TLS(obj); + + object_unref(OBJECT(ioc->master)); + qcrypto_tls_session_free(ioc->session); +} + + +static ssize_t qio_channel_tls_readv(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int **fds, + size_t *nfds, + int flags, + Error **errp) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); + size_t i; + ssize_t got = 0; + + if (fds || nfds) { + error_setg(errp, "%s", + _("Cannot receive file descriptors over TLS channel")); + return -1; + } + if (flags) { + error_setg(errp, "Flags %x not supported in TLS channel", flags); + return -1; + } + + for (i = 0 ; i < niov ; i++) { + ssize_t ret = qcrypto_tls_session_read(tioc->session, + iov->iov_base, + iov->iov_len); + + if (ret < 0) { + if (errno == EAGAIN) { + if (got) { + return got; + } else { + return QIO_CHANNEL_ERR_BLOCK; + } + } + + error_setg_errno(errp, errno, "%s", + _("Cannot read from TLS channel")); + return -1; + } + got += ret; + if (ret < iov->iov_len) { + break; + } + } + return got; +} + + +static ssize_t qio_channel_tls_writev(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int *fds, + size_t nfds, + int flags, + Error **errp) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); + size_t i; + ssize_t done = 0; + + if (fds || nfds) { + error_setg(errp, "%s", + _("Cannot send file descriptors over TLS channel")); + return -1; + } + if (flags) { + error_setg(errp, "Flags %x not supported in TLS channel", flags); + return -1; + } + + for (i = 0 ; i < niov ; i++) { + ssize_t ret = qcrypto_tls_session_write(tioc->session, + iov->iov_base, + iov->iov_len); + + if (ret <= 0) { + if (errno == EAGAIN) { + if (done) { + return done; + } else { + return QIO_CHANNEL_ERR_BLOCK; + } + } + + error_setg_errno(errp, errno, "%s", + _("Cannot write to TLS channel")); + return -1; + } + done += ret; + if (ret < iov->iov_len) { + break; + } + } + return done; +} + +static void qio_channel_tls_set_blocking(QIOChannel *ioc, + bool enabled) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); + + qio_channel_set_blocking(tioc->master, enabled); +} + +static int qio_channel_tls_close(QIOChannel *ioc, + Error **errp) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); + + return qio_channel_close(tioc->master, errp); +} + +static GSource *qio_channel_tls_create_watch(QIOChannel *ioc, + GIOCondition condition) +{ + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); + + return qio_channel_create_watch(tioc->master, condition); +} + +QCryptoTLSSession * +qio_channel_tls_get_session(QIOChannelTLS *ioc) +{ + return ioc->session; +} + +static void qio_channel_tls_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass); + + ioc_klass->io_writev = qio_channel_tls_writev; + ioc_klass->io_readv = qio_channel_tls_readv; + ioc_klass->io_set_blocking = qio_channel_tls_set_blocking; + ioc_klass->io_close = qio_channel_tls_close; + ioc_klass->io_create_watch = qio_channel_tls_create_watch; +} + +static const TypeInfo qio_channel_tls_info = { + .parent = TYPE_QIO_CHANNEL, + .name = TYPE_QIO_CHANNEL_TLS, + .instance_size = sizeof(QIOChannelTLS), + .instance_init = qio_channel_tls_init, + .instance_finalize = qio_channel_tls_finalize, + .class_init = qio_channel_tls_class_init, +}; + +static void qio_channel_tls_register_types(void) +{ + type_register_static(&qio_channel_tls_info); +} + +type_init(qio_channel_tls_register_types); -- 2.1.0