aboutsummaryrefslogtreecommitdiff
path: root/io/channel-tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'io/channel-tls.c')
-rw-r--r--io/channel-tls.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/io/channel-tls.c b/io/channel-tls.c
new file mode 100644
index 0000000000..8ac4f76f78
--- /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 "io/channel-tls.h"
+#include "trace.h"
+
+
+static ssize_t qio_channel_tls_write_handler(const char *buf,
+ size_t len,
+ void *opaque)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
+ ssize_t 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);
+ ssize_t 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);
+
+ trace_qio_channel_tls_new_server(ioc, master, creds, aclname);
+ return ioc;
+
+ error:
+ object_unref(OBJECT(ioc));
+ return NULL;
+}
+
+QIOChannelTLS *
+qio_channel_tls_new_client(QIOChannel *master,
+ QCryptoTLSCreds *creds,
+ const char *hostname,
+ Error **errp)
+{
+ QIOChannelTLS *tioc;
+ QIOChannel *ioc;
+
+ tioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS));
+ ioc = QIO_CHANNEL(tioc);
+
+ tioc->master = master;
+ if (master->features & (1 << QIO_CHANNEL_FEATURE_SHUTDOWN)) {
+ ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
+ }
+ object_ref(OBJECT(master));
+
+ tioc->session = qcrypto_tls_session_new(
+ creds,
+ hostname,
+ NULL,
+ QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ errp);
+ if (!tioc->session) {
+ goto error;
+ }
+
+ qcrypto_tls_session_set_callbacks(
+ tioc->session,
+ qio_channel_tls_write_handler,
+ qio_channel_tls_read_handler,
+ tioc);
+
+ trace_qio_channel_tls_new_client(tioc, master, creds, hostname);
+ return tioc;
+
+ error:
+ object_unref(OBJECT(tioc));
+ 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) {
+ trace_qio_channel_tls_handshake_fail(ioc);
+ qio_task_abort(task, err);
+ goto cleanup;
+ }
+
+ status = qcrypto_tls_session_get_handshake_status(ioc->session);
+ if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
+ trace_qio_channel_tls_handshake_complete(ioc);
+ if (qcrypto_tls_session_check_credentials(ioc->session,
+ &err) < 0) {
+ trace_qio_channel_tls_credentials_deny(ioc);
+ qio_task_abort(task, err);
+ goto cleanup;
+ }
+ trace_qio_channel_tls_credentials_allow(ioc);
+ qio_task_complete(task);
+ } else {
+ GIOCondition condition;
+ if (status == QCRYPTO_TLS_HANDSHAKE_SENDING) {
+ condition = G_IO_OUT;
+ } else {
+ condition = G_IO_IN;
+ }
+
+ trace_qio_channel_tls_handshake_pending(ioc, status);
+ qio_channel_add_watch(ioc->master,
+ condition,
+ qio_channel_tls_handshake_io,
+ task,
+ NULL);
+ }
+
+ cleanup:
+ error_free(err);
+}
+
+
+static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ QIOTask *task = user_data;
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(
+ qio_task_get_source(task));
+
+ qio_channel_tls_handshake_task(
+ tioc, task);
+
+ object_unref(OBJECT(tioc));
+
+ 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);
+
+ trace_qio_channel_tls_handshake_start(ioc);
+ qio_channel_tls_handshake_task(ioc, 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,
+ Error **errp)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+ size_t i;
+ ssize_t got = 0;
+
+ for (i = 0 ; i < niov ; i++) {
+ ssize_t ret = qcrypto_tls_session_read(tioc->session,
+ iov[i].iov_base,
+ iov[i].iov_len);
+ if (ret < 0) {
+ if (errno == EAGAIN) {
+ if (got) {
+ return got;
+ } else {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ }
+
+ error_setg_errno(errp, errno,
+ "Cannot read from TLS channel");
+ return -1;
+ }
+ got += ret;
+ if (ret < iov[i].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,
+ Error **errp)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+ size_t i;
+ ssize_t done = 0;
+
+ for (i = 0 ; i < niov ; i++) {
+ ssize_t ret = qcrypto_tls_session_write(tioc->session,
+ iov[i].iov_base,
+ iov[i].iov_len);
+ if (ret <= 0) {
+ if (errno == EAGAIN) {
+ if (done) {
+ return done;
+ } else {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ }
+
+ error_setg_errno(errp, errno,
+ "Cannot write to TLS channel");
+ return -1;
+ }
+ done += ret;
+ if (ret < iov[i].iov_len) {
+ break;
+ }
+ }
+ return done;
+}
+
+static int qio_channel_tls_set_blocking(QIOChannel *ioc,
+ bool enabled,
+ Error **errp)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+ return qio_channel_set_blocking(tioc->master, enabled, errp);
+}
+
+static void qio_channel_tls_set_delay(QIOChannel *ioc,
+ bool enabled)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+ qio_channel_set_delay(tioc->master, enabled);
+}
+
+static void qio_channel_tls_set_cork(QIOChannel *ioc,
+ bool enabled)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+ qio_channel_set_cork(tioc->master, enabled);
+}
+
+static int qio_channel_tls_shutdown(QIOChannel *ioc,
+ QIOChannelShutdown how,
+ Error **errp)
+{
+ QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+ return qio_channel_shutdown(tioc->master, how, errp);
+}
+
+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_set_delay = qio_channel_tls_set_delay;
+ ioc_klass->io_set_cork = qio_channel_tls_set_cork;
+ ioc_klass->io_close = qio_channel_tls_close;
+ ioc_klass->io_shutdown = qio_channel_tls_shutdown;
+ 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);