Improving QEMU security part 2: generic TLS support
This blog is part 2 of a series I am writing about work I’ve completed over the past few releases to improve QEMU security related features.
After the initial consolidation of cryptographic APIs into one area of the QEMU codebase, it was time to move onto step two, which is where things start to directly benefit the users. With the original patches to support TLS in the VNC server, configuration of the TLS credentials was done as part of the -vnc
command line argument. The downside of such an approach is that as we add support for TLS to other arguments like -chardev, the user would have to supply the same TLS information in multiple places. So it was necessary to isolate the TLS credential configuration from the TLS session handling code, enabling a single set of TLS credentials to be associated with multiple network servers (they can of course each have a unique set of credentials if desired). To achieve this, the new code made use of QEMU’s object model framework (QOM) to define a general TLS credentials interface and then provide implementations for anonymous credentials (totally insecure but needed for back compat with existing QEMU features) and for x509 certificates (the preferred & secure option). There are now two QOM types tls-creds-anon
and tls-creds-x509
that can be created on the command line via QEMU’s -object argument, or in the monitor using the ‘object_add’ command. The VNC server was converted to use the new TLS credential objects for its configuration, so whereas in QEMU 2.4 VNC with TLS would be configured using
-vnc 0.0.0.0:0,tls,x509verify=/path/to/certificates/directory
As of QEMU 2.5 the preferred approach is to use the new credential objects
-object tls-creds-x509,id=tls0.endpoint=server,dir=/path/to/certificates/directory -vnc 0.0.0.0:0,tls-creds=tls0
The old CLI syntax is still supported, but gets translated internally to create the right TLS credential objects. By default the x509 credentials will require that the client provide a certificate, which is equivalent to the traditional ‘x509verify
‘ option for VNC. To remove the requirement for client certs, the ‘verify-peer=no
‘ option can be given when creating the x509 credentials object.
Generating correct x509 certificates is something that users often struggle with and when getting it wrong the failures are pretty hard to debug – usually just resulting in an unhelpful “handshake failed” type error message. To help troubleshoot problems, the new x509 credentials code in QEMU will sanity check all certificates it loads prior to using them. For example, it will check that the CA certificate has basic constraints set to indicate usage as a CA, catching problems where people give a server/client cert instead of a CA cert. Likewise it will check that the server certificate has basic constraints set to indicate usage in a server. It’ll check that the server certificate is actually signed by the CA that is provided and that none of the certs have expired already. These are all things that the client would check when it connects, so we’re not adding / removing security here, just helping administrators to detect misconfiguration of their TLS certificates as early as possible. These same checks have been done in libvirt for several years now and have been very beneficial in reducing the bugs reports we get related to misconfiguration of TLS.
With the generic TLS credential objects created, the second step was to create a general purpose API for handling the TLS protocol inside QEMU, especially the simplifying the handshake which requires a non-negligible amount of code. The TLS session APIs were designed such that they are independent of the underling data transport since while the VNC server always runs over TCP/UNIX sockets, other QEMU backends may wish to run TLS over non-socket based transports. Overall the API for dealing with TLS session establishment in QEMU can be used as follows
static ssize_t mysock_send(const char *buf, size_t len, void *opaque) { int fd = GPOINTER_TO_INT(opaque); return write(*fd, buf, len); } static ssize_t mysock_recv(const char *buf, size_t len, void *opaque) { int fd = GPOINTER_TO_INT(opaque); return read(*fd, buf, len); } static int mysock_run_tls(int sockfd, QCryptoTLSCreds *creds, Error *erp) { QCryptoTLSSession *sess; sess = qcrypto_tls_session_new(creds, "vnc.example.com", NULL, QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, errp); if (sess == NULL) { return -1; } qcrypto_tls_session_set_callbacks(sess, mysock_send, mysock_recv, GINT_TO_POINTER(fd)); while (1) { if (qcrypto_tls_session_handshake(sess, errp) < 0) { qcrypto_tls_session_free(sess); return -1; } switch(qcrypto_tls_session_get_handshake_status(sess)) { case QCRYPTO_TLS_HANDSHAKE_COMPLETE: if (qcrypto_tls_session_check_credentials(sess, errp) < )) { qcrypto_tls_session_free(sess); return -1; } goto done; case QCRYPTO_TLS_HANDSHAKE_RECVING: ...wait for GIO_IN event on fd... break; case QCRYPTO_TLS_HANDSHAKE_SENDING: ...wait for GIO_OUT event on fd... break; } } done: ....send/recv payload data on sess... qcrypto_tls_session_free(sess): }
The particularly important thing to note with this example is how the network service (eg VNC, NBD, chardev) that is enabling TLS no longer has to have any knowledge of x509 certificates. They are loaded automatically when the user provides the ‘-object tls-creds-x509
‘ argument to QEMU, and they are validated automatically by the call to qcrypto_tls_session_handshake()
. This makes it easy to add TLS support to other network backends in QEMU, with minimal overhead significantly lowering the risk of screwing up the security. Since it already had TLS support, the VNC server was converted to use this new TLS session API instead of using the gnutls APIs directly. Once again this had a very positive impact on maintainability of the VNC code, since it allowed countless #ifdef CONFIG_GNUTLS conditionals to be removed which clarified the code flow significantly. This work on TLS and the VNC server all merged for the 2.5 release of QEMU, so is already available to benefit users. There is corresponding libvirt work to be done still to convert over to use the new command line syntax for configuring TLS with QEMU.
In this blog series: