From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:35911) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZXWcu-0004nF-Ew for qemu-devel@nongnu.org; Thu, 03 Sep 2015 11:40:41 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZXWcs-0007qO-F4 for qemu-devel@nongnu.org; Thu, 03 Sep 2015 11:40:36 -0400 Received: from mx1.redhat.com ([209.132.183.28]:39617) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZXWcs-0007q5-6D for qemu-devel@nongnu.org; Thu, 03 Sep 2015 11:40:34 -0400 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) by mx1.redhat.com (Postfix) with ESMTPS id DF7E62BCD9D for ; Thu, 3 Sep 2015 15:40:33 +0000 (UTC) From: "Daniel P. Berrange" Date: Thu, 3 Sep 2015 16:39:04 +0100 Message-Id: <1441294768-8712-23-git-send-email-berrange@redhat.com> In-Reply-To: <1441294768-8712-1-git-send-email-berrange@redhat.com> References: <1441294768-8712-1-git-send-email-berrange@redhat.com> Subject: [Qemu-devel] [PATCH FYI 22/46] char: introduce support for TLS encrypted TCP chardev backend List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Juan Quintela , "Dr. David Alan Gilbert" , Gerd Hoffmann , Amit Shah , Paolo Bonzini 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,id=tls0,credtype=x509,\ endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off 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 \ --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,id=tls0,credtype=x509,\ endpoint=client,dir=/home/berrange/security/qemutls Signed-off-by: Daniel P. Berrange --- qapi-schema.json | 2 + qemu-char.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++----- qemu-options.hx | 9 +++- 3 files changed, 138 insertions(+), 15 deletions(-) diff --git a/qapi-schema.json b/qapi-schema.json index ae4858c..b6ec943 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -2910,6 +2910,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.4) # @server: #optional create server socket (default: true) # @wait: #optional wait for incoming connection on server # sockets (default: false). @@ -2924,6 +2925,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 fe75f09..69ee044 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -34,6 +34,7 @@ #include "qapi-visit.h" #include "io/channel-socket.h" #include "io/channel-file.h" +#include "io/channel-tls.h" #include #include @@ -2435,9 +2436,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; @@ -2668,6 +2671,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); @@ -2741,12 +2746,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) { @@ -2833,6 +2838,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->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; @@ -2842,6 +2898,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)); qio_channel_set_blocking(s->ioc, false); if (s->do_nodelay) { @@ -2852,10 +2910,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; @@ -2923,6 +2985,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); } @@ -3415,6 +3480,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) { @@ -3426,6 +3492,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->socket = g_new0(ChardevSocket, 1); @@ -3440,6 +3511,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, backend->socket->wait = is_waitconnect; backend->socket->has_reconnect = true; backend->socket->reconnect = reconnect; + backend->socket->tls_creds = g_strdup(tls_creds); addr = g_new0(SocketAddress, 1); if (path) { @@ -3459,6 +3531,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, addr->inet->ipv6 = qemu_opt_get_bool(opts, "ipv6", 0); } backend->socket->addr = addr; + return; } static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend, @@ -3842,6 +3915,9 @@ QemuOptsList qemu_chardev_opts = { .name = "telnet", .type = QEMU_OPT_BOOL, },{ + .name = "tls-creds", + .type = QEMU_OPT_STRING, + },{ .name = "width", .type = QEMU_OPT_NUMBER, },{ @@ -4008,6 +4084,7 @@ static gboolean socket_reconnect_timeout(gpointer opaque) } static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock, + const char *id, Error **errp) { CharDriverState *chr; @@ -4026,6 +4103,39 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock, 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; @@ -4054,10 +4164,7 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock, if (s->reconnect_time) { socket_try_connect(chr); } else if (!qemu_chr_open_socket_fd(chr, errp)) { - g_free(s); - g_free(chr->filename); - g_free(chr); - return NULL; + goto error; } if (is_listen && is_waitconnect) { @@ -4068,6 +4175,15 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock, } return chr; + + error: + if (s->tls_creds) { + object_unref(OBJECT(s->tls_creds)); + } + g_free(s); + g_free(chr->filename); + g_free(chr); + return NULL; } static CharDriverState *qmp_chardev_open_udp(ChardevUdp *udp, @@ -4111,7 +4227,7 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, chr = qemu_chr_open_pipe(backend->pipe); break; case CHARDEV_BACKEND_KIND_SOCKET: - chr = qmp_chardev_open_socket(backend->socket, errp); + chr = qmp_chardev_open_socket(backend->socket, id, errp); break; case CHARDEV_BACKEND_KIND_UDP: chr = qmp_chardev_open_udp(backend->udp, errp); diff --git a/qemu-options.hx b/qemu-options.hx index 9918874..6acd400 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2030,7 +2030,7 @@ ETEXI DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, "-chardev null,id=id[,mux=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] (tcp)\n" + " [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off][,tls-creds=ID] (tcp)\n" "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n" "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n" " [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n" @@ -2103,7 +2103,7 @@ 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 @@ -2121,6 +2121,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.4.3