summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/io/channel-tls.h142
-rw-r--r--io/Makefile.objs1
-rw-r--r--io/channel-tls.c393
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile4
-rw-r--r--tests/test-io-channel-tls.c342
-rw-r--r--trace-events10
7 files changed, 893 insertions, 0 deletions
diff --git a/include/io/channel-tls.h b/include/io/channel-tls.h
new file mode 100644
index 0000000000..0298b1770e
--- /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 "qio-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 3d2f232480..a48011b726 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1,5 +1,6 @@
io-obj-y = channel.o
io-obj-y += channel-file.o
io-obj-y += channel-socket.o
+io-obj-y += channel-tls.o
io-obj-y += channel-watch.o
io-obj-y += task.o
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);
diff --git a/tests/.gitignore b/tests/.gitignore
index 6160003e75..810b4f0bee 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -27,6 +27,7 @@ test-iov
test-io-channel-file
test-io-channel-file.txt
test-io-channel-socket
+test-io-channel-tls
test-io-task
test-mul64
test-opts-visitor
diff --git a/tests/Makefile b/tests/Makefile
index b7c9989090..9d95350dcd 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -87,6 +87,7 @@ check-unit-y += tests/test-timed-average$(EXESUF)
check-unit-y += tests/test-io-task$(EXESUF)
check-unit-y += tests/test-io-channel-socket$(EXESUF)
check-unit-y += tests/test-io-channel-file$(EXESUF)
+check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -478,6 +479,9 @@ tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
tests/io-channel-helpers.o $(test-io-obj-y)
tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \
+ tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
new file mode 100644
index 0000000000..3c11a5097c
--- /dev/null
+++ b/tests/test-io-channel-tls.c
@@ -0,0 +1,342 @@
+/*
+ * QEMU I/O channel TLS test
+ *
+ * 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.1 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/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "config-host.h"
+#include "crypto-tls-x509-helpers.h"
+#include "io/channel-tls.h"
+#include "io/channel-socket.h"
+#include "io-channel-helpers.h"
+#include "crypto/tlscredsx509.h"
+#include "qemu/acl.h"
+#include "qom/object_interfaces.h"
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+
+#define WORKDIR "tests/test-io-channel-tls-work/"
+#define KEYFILE WORKDIR "key-ctx.pem"
+
+struct QIOChannelTLSTestData {
+ const char *servercacrt;
+ const char *clientcacrt;
+ const char *servercrt;
+ const char *clientcrt;
+ bool expectServerFail;
+ bool expectClientFail;
+ const char *hostname;
+ const char *const *wildcards;
+};
+
+struct QIOChannelTLSHandshakeData {
+ bool finished;
+ bool failed;
+};
+
+static void test_tls_handshake_done(Object *source,
+ Error *err,
+ gpointer opaque)
+{
+ struct QIOChannelTLSHandshakeData *data = opaque;
+
+ data->finished = true;
+ data->failed = err != NULL;
+}
+
+
+static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
+ const char *certdir,
+ Error **errp)
+{
+ Object *parent = object_get_objects_root();
+ Object *creds = object_new_with_props(
+ TYPE_QCRYPTO_TLS_CREDS_X509,
+ parent,
+ (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+ "testtlscredsserver" : "testtlscredsclient"),
+ errp,
+ "endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+ "server" : "client"),
+ "dir", certdir,
+ "verify-peer", "yes",
+ /* We skip initial sanity checks here because we
+ * want to make sure that problems are being
+ * detected at the TLS session validation stage,
+ * and the test-crypto-tlscreds test already
+ * validate the sanity check code.
+ */
+ "sanity-check", "no",
+ NULL
+ );
+
+ if (*errp) {
+ return NULL;
+ }
+ return QCRYPTO_TLS_CREDS(creds);
+}
+
+
+/*
+ * This tests validation checking of peer certificates
+ *
+ * This is replicating the checks that are done for an
+ * active TLS session after handshake completes. To
+ * simulate that we create our TLS contexts, skipping
+ * sanity checks. When then get a socketpair, and
+ * initiate a TLS session across them. Finally do
+ * do actual cert validation tests
+ */
+static void test_io_channel_tls(const void *opaque)
+{
+ struct QIOChannelTLSTestData *data =
+ (struct QIOChannelTLSTestData *)opaque;
+ QCryptoTLSCreds *clientCreds;
+ QCryptoTLSCreds *serverCreds;
+ QIOChannelTLS *clientChanTLS;
+ QIOChannelTLS *serverChanTLS;
+ QIOChannelSocket *clientChanSock;
+ QIOChannelSocket *serverChanSock;
+ qemu_acl *acl;
+ const char * const *wildcards;
+ int channel[2];
+ struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
+ struct QIOChannelTLSHandshakeData serverHandshake = { false, false };
+ Error *err = NULL;
+ QIOChannelTest *test;
+ GMainContext *mainloop;
+
+ /* We'll use this for our fake client-server connection */
+ g_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
+
+#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
+#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
+ mkdir(CLIENT_CERT_DIR, 0700);
+ mkdir(SERVER_CERT_DIR, 0700);
+
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+ g_assert(link(data->servercacrt,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+ g_assert(link(data->servercrt,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
+ g_assert(link(KEYFILE,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
+
+ g_assert(link(data->clientcacrt,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+ g_assert(link(data->clientcrt,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
+ g_assert(link(KEYFILE,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
+
+ clientCreds = test_tls_creds_create(
+ QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ CLIENT_CERT_DIR,
+ &err);
+ g_assert(clientCreds != NULL);
+
+ serverCreds = test_tls_creds_create(
+ QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+ SERVER_CERT_DIR,
+ &err);
+ g_assert(serverCreds != NULL);
+
+ acl = qemu_acl_init("channeltlsacl");
+ qemu_acl_reset(acl);
+ wildcards = data->wildcards;
+ while (wildcards && *wildcards) {
+ qemu_acl_append(acl, 0, *wildcards);
+ wildcards++;
+ }
+
+ clientChanSock = qio_channel_socket_new_fd(
+ channel[0], &err);
+ g_assert(clientChanSock != NULL);
+ serverChanSock = qio_channel_socket_new_fd(
+ channel[1], &err);
+ g_assert(serverChanSock != NULL);
+
+ /*
+ * We have an evil loop to do the handshake in a single
+ * thread, so we need these non-blocking to avoid deadlock
+ * of ourselves
+ */
+ qio_channel_set_blocking(QIO_CHANNEL(clientChanSock), false, NULL);
+ qio_channel_set_blocking(QIO_CHANNEL(serverChanSock), false, NULL);
+
+ /* Now the real part of the test, setup the sessions */
+ clientChanTLS = qio_channel_tls_new_client(
+ QIO_CHANNEL(clientChanSock), clientCreds,
+ data->hostname, &err);
+ g_assert(clientChanTLS != NULL);
+
+ serverChanTLS = qio_channel_tls_new_server(
+ QIO_CHANNEL(serverChanSock), serverCreds,
+ "channeltlsacl", &err);
+ g_assert(serverChanTLS != NULL);
+
+ qio_channel_tls_handshake(clientChanTLS,
+ test_tls_handshake_done,
+ &clientHandshake,
+ NULL);
+ qio_channel_tls_handshake(serverChanTLS,
+ test_tls_handshake_done,
+ &serverHandshake,
+ NULL);
+
+ /*
+ * Finally we loop around & around doing handshake on each
+ * session until we get an error, or the handshake completes.
+ * This relies on the socketpair being nonblocking to avoid
+ * deadlocking ourselves upon handshake
+ */
+ mainloop = g_main_context_default();
+ do {
+ g_main_context_iteration(mainloop, TRUE);
+ } while (!clientHandshake.finished &&
+ !serverHandshake.finished);
+
+ g_assert(clientHandshake.failed == data->expectClientFail);
+ g_assert(serverHandshake.failed == data->expectServerFail);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, false,
+ QIO_CHANNEL(clientChanTLS),
+ QIO_CHANNEL(serverChanTLS));
+ qio_channel_test_validate(test);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, true,
+ QIO_CHANNEL(clientChanTLS),
+ QIO_CHANNEL(serverChanTLS));
+ qio_channel_test_validate(test);
+
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+ rmdir(CLIENT_CERT_DIR);
+ rmdir(SERVER_CERT_DIR);
+
+ object_unparent(OBJECT(serverCreds));
+ object_unparent(OBJECT(clientCreds));
+
+ object_unref(OBJECT(serverChanTLS));
+ object_unref(OBJECT(clientChanTLS));
+
+ object_unref(OBJECT(serverChanSock));
+ object_unref(OBJECT(clientChanSock));
+
+ close(channel[0]);
+ close(channel[1]);
+}
+
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+ setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
+
+ mkdir(WORKDIR, 0700);
+
+ test_tls_init(KEYFILE);
+
+# define TEST_CHANNEL(name, caCrt, \
+ serverCrt, clientCrt, \
+ expectServerFail, expectClientFail, \
+ hostname, wildcards) \
+ struct QIOChannelTLSTestData name = { \
+ caCrt, caCrt, serverCrt, clientCrt, \
+ expectServerFail, expectClientFail, \
+ hostname, wildcards \
+ }; \
+ g_test_add_data_func("/qio/channel/tls/" # name, \
+ &name, test_io_channel_tls);
+
+ /* A perfect CA, perfect client & perfect server */
+
+ /* Basic:CA:critical */
+ TLS_ROOT_REQ(cacertreq,
+ "UK", "qemu CA", NULL, NULL, NULL, NULL,
+ true, true, true,
+ true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+ false, false, NULL, NULL,
+ 0, 0);
+ TLS_CERT_REQ(servercertreq, cacertreq,
+ "UK", "qemu.org", NULL, NULL, NULL, NULL,
+ true, true, false,
+ true, true,
+ GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+ true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+ 0, 0);
+ TLS_CERT_REQ(clientcertreq, cacertreq,
+ "UK", "qemu", NULL, NULL, NULL, NULL,
+ true, true, false,
+ true, true,
+ GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+ true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+ 0, 0);
+
+ const char *const wildcards[] = {
+ "C=UK,CN=qemu*",
+ NULL,
+ };
+ TEST_CHANNEL(basic, cacertreq.filename, servercertreq.filename,
+ clientcertreq.filename, false, false,
+ "qemu.org", wildcards);
+
+ ret = g_test_run();
+
+ test_tls_discard_cert(&clientcertreq);
+ test_tls_discard_cert(&servercertreq);
+ test_tls_discard_cert(&cacertreq);
+
+ test_tls_cleanup(KEYFILE);
+ rmdir(WORKDIR);
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
+
+int
+main(void)
+{
+ return EXIT_SUCCESS;
+}
+
+#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
diff --git a/trace-events b/trace-events
index 88c83f4431..a317955338 100644
--- a/trace-events
+++ b/trace-events
@@ -1840,3 +1840,13 @@ qio_channel_socket_accept_complete(void *ioc, void *cioc, int fd) "Socket accept
# io/channel-file.c
qio_channel_file_new_fd(void *ioc, int fd) "File new fd ioc=%p fd=%d"
qio_channel_file_new_path(void *ioc, const char *path, int flags, int mode, int fd) "File new fd ioc=%p path=%s flags=%d mode=%d fd=%d"
+
+# io/channel-tls.c
+qio_channel_tls_new_client(void *ioc, void *master, void *creds, const char *hostname) "TLS new client ioc=%p master=%p creds=%p hostname=%s"
+qio_channel_tls_new_server(void *ioc, void *master, void *creds, const char *aclname) "TLS new client ioc=%p master=%p creds=%p acltname=%s"
+qio_channel_tls_handshake_start(void *ioc) "TLS handshake start ioc=%p"
+qio_channel_tls_handshake_pending(void *ioc, int status) "TLS handshake pending ioc=%p status=%d"
+qio_channel_tls_handshake_fail(void *ioc) "TLS handshake fail ioc=%p"
+qio_channel_tls_handshake_complete(void *ioc) "TLS handshake complete ioc=%p"
+qio_channel_tls_credentials_allow(void *ioc) "TLS credentials allow ioc=%p"
+qio_channel_tls_credentials_deny(void *ioc) "TLS credentials deny ioc=%p"