char: introduce support for TLS encrypted TCP chardev backend
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> Message-Id: <1453202071-10289-5-git-send-email-berrange@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
f2001a7e05
commit
a8fb542705
@ -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',
|
||||
|
136
qemu-char.c
136
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) {
|
||||
@ -4015,6 +4087,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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user