From: "Daniel P. Berrange" <berrange@redhat.com>
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>
Subject: [Qemu-devel] [PATCH v4 4/4] char: introduce support for TLS encrypted TCP chardev backend
Date: Tue, 19 Jan 2016 11:14:31 +0000 [thread overview]
Message-ID: <1453202071-10289-5-git-send-email-berrange@redhat.com> (raw)
In-Reply-To: <1453202071-10289-1-git-send-email-berrange@redhat.com>
This integrates support for QIOChannelTLS object in the TCP
chardev backend. If the 'tls-creds=NAME' option is passed with
the '-chardev tcp' argument, then it will setup the chardev
such that the client is required to establish a TLS handshake
when connecting. There is no support for checking the client
certificate against ACLs in this initial patch. This is pending
work to QOM-ify the ACL object code.
A complete invocation to run QEMU as the server for a TLS
encrypted serial dev might be
$ qemu-system-x86_64 \
-nodefconfig -nodefaults -device sga -display none \
-chardev socket,id=s0,host=127.0.0.1,port=9000,tls-creds=tls0,server \
-device isa-serial,chardev=s0 \
-object tls-creds-x509,id=tls0,endpoint=server,verify-peer=off,\
dir=/home/berrange/security/qemutls
To test with the gnutls-cli tool as the client:
$ gnutls-cli --priority=NORMAL -p 9000 \
--x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
127.0.0.1
If QEMU was told to use 'anon' credential type, then use the
priority string 'NORMAL:+ANON-DH' with gnutls-cli
Alternatively, if setting up a chardev to operate as a client,
then the TLS credentials registered must be for the client
endpoint. First a TLS server must be setup, which can be done
with the gnutls-serv tool
$ gnutls-serv --priority=NORMAL -p 9000 --echo \
--x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
--x509certfile=/home/berrange/security/qemutls/server-cert.pem \
--x509keyfile=/home/berrange/security/qemutls/server-key.pem
Then QEMU can connect with
$ qemu-system-x86_64 \
-nodefconfig -nodefaults -device sga -display none \
-chardev socket,id=s0,host=127.0.0.1,port=9000,tls-creds=tls0 \
-device isa-serial,chardev=s0 \
-object tls-creds-x509,id=tls0,endpoint=client,\
dir=/home/berrange/security/qemutls
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
qapi-schema.json | 2 +
qemu-char.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
qemu-options.hx | 9 +++-
3 files changed, 134 insertions(+), 13 deletions(-)
diff --git a/qapi-schema.json b/qapi-schema.json
index b3038b2..8d04897 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3146,6 +3146,7 @@
#
# @addr: socket address to listen on (server=true)
# or connect to (server=false)
+# @tls-creds: #optional the ID of the TLS credentials object (since 2.6)
# @server: #optional create server socket (default: true)
# @wait: #optional wait for incoming connection on server
# sockets (default: false).
@@ -3160,6 +3161,7 @@
# Since: 1.4
##
{ 'struct': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress',
+ '*tls-creds' : 'str',
'*server' : 'bool',
'*wait' : 'bool',
'*nodelay' : 'bool',
diff --git a/qemu-char.c b/qemu-char.c
index f0cea8a..7ded3c2 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -35,6 +35,7 @@
#include "qemu/base64.h"
#include "io/channel-socket.h"
#include "io/channel-file.h"
+#include "io/channel-tls.h"
#include <unistd.h>
#include <fcntl.h>
@@ -2532,9 +2533,11 @@ static CharDriverState *qemu_chr_open_udp(QIOChannelSocket *sioc,
/* TCP Net console */
typedef struct {
- QIOChannel *ioc;
+ QIOChannel *ioc; /* Client I/O channel */
+ QIOChannelSocket *sioc; /* Client master channel */
QIOChannelSocket *listen_ioc;
guint listen_tag;
+ QCryptoTLSCreds *tls_creds;
int connected;
int max_size;
int do_telnetopt;
@@ -2776,6 +2779,8 @@ static void tcp_chr_disconnect(CharDriverState *chr)
QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr, NULL);
}
remove_fd_in_watch(chr);
+ object_unref(OBJECT(s->sioc));
+ s->sioc = NULL;
object_unref(OBJECT(s->ioc));
s->ioc = NULL;
g_free(chr->filename);
@@ -2849,12 +2854,12 @@ static void tcp_chr_connect(void *opaque)
{
CharDriverState *chr = opaque;
TCPCharDriver *s = chr->opaque;
- QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
g_free(chr->filename);
- chr->filename = sockaddr_to_str(&sioc->localAddr, sioc->localAddrLen,
- &sioc->remoteAddr, sioc->remoteAddrLen,
- s->is_listen, s->is_telnet);
+ chr->filename = sockaddr_to_str(
+ &s->sioc->localAddr, s->sioc->localAddrLen,
+ &s->sioc->remoteAddr, s->sioc->remoteAddrLen,
+ s->is_listen, s->is_telnet);
s->connected = 1;
if (s->ioc) {
@@ -2943,6 +2948,57 @@ static void tcp_chr_telnet_init(CharDriverState *chr)
init, NULL);
}
+
+static void tcp_chr_tls_handshake(Object *source,
+ Error *err,
+ gpointer user_data)
+{
+ CharDriverState *chr = user_data;
+ TCPCharDriver *s = chr->opaque;
+
+ if (err) {
+ tcp_chr_disconnect(chr);
+ } else {
+ if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+ }
+}
+
+
+static void tcp_chr_tls_init(CharDriverState *chr)
+{
+ TCPCharDriver *s = chr->opaque;
+ QIOChannelTLS *tioc;
+ Error *err = NULL;
+
+ if (s->is_listen) {
+ tioc = qio_channel_tls_new_server(
+ s->ioc, s->tls_creds,
+ NULL, /* XXX Use an ACL */
+ &err);
+ } else {
+ tioc = qio_channel_tls_new_client(
+ s->ioc, s->tls_creds,
+ s->addr->u.inet->host,
+ &err);
+ }
+ if (tioc == NULL) {
+ error_free(err);
+ tcp_chr_disconnect(chr);
+ }
+ object_unref(OBJECT(s->ioc));
+ s->ioc = QIO_CHANNEL(tioc);
+
+ qio_channel_tls_handshake(tioc,
+ tcp_chr_tls_handshake,
+ chr,
+ NULL);
+}
+
+
static int tcp_chr_new_client(CharDriverState *chr, QIOChannelSocket *sioc)
{
TCPCharDriver *s = chr->opaque;
@@ -2952,6 +3008,8 @@ static int tcp_chr_new_client(CharDriverState *chr, QIOChannelSocket *sioc)
s->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(sioc));
+ s->sioc = sioc;
+ object_ref(OBJECT(sioc));
if (s->do_nodelay) {
qio_channel_set_delay(s->ioc, false);
@@ -2961,10 +3019,14 @@ static int tcp_chr_new_client(CharDriverState *chr, QIOChannelSocket *sioc)
s->listen_tag = 0;
}
- if (s->do_telnetopt) {
- tcp_chr_telnet_init(chr);
+ if (s->tls_creds) {
+ tcp_chr_tls_init(chr);
} else {
- tcp_chr_connect(chr);
+ if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
}
return 0;
@@ -3033,6 +3095,9 @@ static void tcp_chr_close(CharDriverState *chr)
}
g_free(s->read_msgfds);
}
+ if (s->tls_creds) {
+ object_unref(OBJECT(s->tls_creds));
+ }
if (s->write_msgfds_num) {
g_free(s->write_msgfds);
}
@@ -3563,6 +3628,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
const char *path = qemu_opt_get(opts, "path");
const char *host = qemu_opt_get(opts, "host");
const char *port = qemu_opt_get(opts, "port");
+ const char *tls_creds = qemu_opt_get(opts, "tls-creds");
SocketAddress *addr;
if (!path) {
@@ -3574,6 +3640,11 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
error_setg(errp, "chardev: socket: no port given");
return;
}
+ } else {
+ if (tls_creds) {
+ error_setg(errp, "TLS can only be used over TCP socket");
+ return;
+ }
}
backend->u.socket = g_new0(ChardevSocket, 1);
@@ -3589,6 +3660,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
backend->u.socket->wait = is_waitconnect;
backend->u.socket->has_reconnect = true;
backend->u.socket->reconnect = reconnect;
+ backend->u.socket->tls_creds = g_strdup(tls_creds);
addr = g_new0(SocketAddress, 1);
if (path) {
@@ -4016,6 +4088,9 @@ QemuOptsList qemu_chardev_opts = {
.name = "telnet",
.type = QEMU_OPT_BOOL,
},{
+ .name = "tls-creds",
+ .type = QEMU_OPT_STRING,
+ },{
.name = "width",
.type = QEMU_OPT_NUMBER,
},{
@@ -4231,6 +4306,39 @@ static CharDriverState *qmp_chardev_open_socket(const char *id,
s->is_listen = is_listen;
s->is_telnet = is_telnet;
s->do_nodelay = do_nodelay;
+ if (sock->tls_creds) {
+ Object *creds;
+ creds = object_resolve_path_component(
+ object_get_objects_root(), sock->tls_creds);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ sock->tls_creds);
+ goto error;
+ }
+ s->tls_creds = (QCryptoTLSCreds *)
+ object_dynamic_cast(creds,
+ TYPE_QCRYPTO_TLS_CREDS);
+ if (!s->tls_creds) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ sock->tls_creds);
+ goto error;
+ }
+ object_ref(OBJECT(s->tls_creds));
+ if (is_listen) {
+ if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for server endpoint");
+ goto error;
+ }
+ } else {
+ if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for client endpoint");
+ goto error;
+ }
+ }
+ }
+
qapi_copy_SocketAddress(&s->addr, sock->addr);
chr->opaque = s;
@@ -4259,9 +4367,7 @@ static CharDriverState *qmp_chardev_open_socket(const char *id,
if (s->reconnect_time) {
socket_try_connect(chr);
} else if (!qemu_chr_open_socket_fd(chr, errp)) {
- g_free(s);
- qemu_chr_free_common(chr);
- return NULL;
+ goto error;
}
if (is_listen && is_waitconnect) {
@@ -4272,6 +4378,14 @@ static CharDriverState *qmp_chardev_open_socket(const char *id,
}
return chr;
+
+ error:
+ if (s->tls_creds) {
+ object_unref(OBJECT(s->tls_creds));
+ }
+ g_free(s);
+ qemu_chr_free_common(chr);
+ return NULL;
}
static CharDriverState *qmp_chardev_open_udp(const char *id,
diff --git a/qemu-options.hx b/qemu-options.hx
index b4763ba..f31a240 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2092,7 +2092,7 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
"-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
"-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
" [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off]\n"
- " [,logfile=PATH][,logappend=on|off] (tcp)\n"
+ " [,logfile=PATH][,logappend=on|off][,tls-creds=ID] (tcp)\n"
"-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds]\n"
" [,mux=on|off][,logfile=PATH][,logappend=on|off] (unix)\n"
"-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
@@ -2172,7 +2172,7 @@ Further options to each backend are described below.
A void device. This device will not emit any data, and will drop any data it
receives. The null backend does not take any options.
-@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
+@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}] [,tls-creds=@var{id}]
Create a two-way stream socket, which can be either a TCP or a unix socket. A
unix socket will be created if @option{path} is specified. Behaviour is
@@ -2190,6 +2190,11 @@ escape sequences.
the remote end goes away. qemu will delay this many seconds and then attempt
to reconnect. Zero disables reconnecting, and is the default.
+@option{tls-creds} requests enablement of the TLS protocol for encryption,
+and specifies the id of the TLS credentials to use for the handshake. The
+credentials must be previously created with the @option{-object tls-creds}
+argument.
+
TCP and unix socket options are given below:
@table @option
--
2.5.0
next prev parent reply other threads:[~2016-01-19 11:14 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-01-19 11:14 [Qemu-devel] [PATCH v4 0/4] Convert chardevs to QIOChannel & add TLS support Daniel P. Berrange
2016-01-19 11:14 ` [Qemu-devel] [PATCH v4 1/4] char: remove fixed length filename allocation Daniel P. Berrange
2016-01-19 11:14 ` [Qemu-devel] [PATCH v4 2/4] char: convert from GIOChannel to QIOChannel Daniel P. Berrange
2016-03-18 16:43 ` Laurent Vivier
2016-03-18 16:56 ` Daniel P. Berrange
2016-03-18 17:10 ` Laurent Vivier
2016-01-19 11:14 ` [Qemu-devel] [PATCH v4 3/4] char: don't assume telnet initialization will not block Daniel P. Berrange
2016-01-19 11:14 ` Daniel P. Berrange [this message]
2016-01-19 13:14 ` [Qemu-devel] [PATCH v4 0/4] Convert chardevs to QIOChannel & add TLS support Paolo Bonzini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1453202071-10289-5-git-send-email-berrange@redhat.com \
--to=berrange@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.