Crypto patches
* Drop unused 'detached-header' QAPI field from LUKS create options * Improve tracing of TLS sockets and TLS chardevs * Improve error messages from TLS I/O failures * Add docs about use of LUKS detached header options * Allow building without libtasn1, but with GNUTLS * Fix detection of libgcrypt when libgcrypt-config is absent -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE2vOm/bJrYpEtDo4/vobrtBUQT98FAmagzXUACgkQvobrtBUQ T9++chAAhCFgo5A/UjQGdl9UAOW/sdgOoHGE3E8Y6sSTQyv+EfHf1DO89JtAh4ft d8Hz7Taul4k1wRm6Dxv2aCqH5iS1tgDE2ghGDNwn/zDtHNnjFx3+HcxBaAEcpt3O FqvGeG6KdFO1t2UR2DMh1XbhfwygrHiIcSB2y8jrgi46ncS6JvLrFavjLTe7JBn9 J3y/iYgQiVPN6UlIwUs1EquGdoTI/0SpHVirqHN/2yyrdRsGBsXZq5WI6Oli8zFL VqJNmc5Dzo7ushoYG5Rpk83mmC26VuXO/JmXyJ/c7FeADLWUfc/SPPyAMxPGuwFr DKg84ovRtq3yZIw8LPoUJOtbcu4Y7BSGwlolQjWegvsVTU6Bdk+teZVR9X64QbM2 YBXzMkRHUKzR3rb0LewAKehP3n93aBypLln9ZMgg7wj92Rj8Dl/sylaBhDEkH/HQ 2pMdSdAWqMnGHfnKPxyjflNO2PIsOenZUkDZwf9i7Ow6fU5n3fqvudVDTWjXpWPn V7v9JGNPHocScJFRUqHSVqd2ZWaZX4F1TsvG6SGOmzDGR0IjBRlqos7OEdbAAH1x IglizbTxD6M9ZWJrGt1sl6LSAwEp3oXgsWNdejq2+7I6H4BeUm4ACDbdrEjqG9aG Ya/HpNT0PEzbGXm6qsuHY5z0agGtaPwdXLcSGnsv+a0rP/9nthY= =ccYf -----END PGP SIGNATURE----- Merge tag 'misc-fixes-pull-request' of https://gitlab.com/berrange/qemu into staging Crypto patches * Drop unused 'detached-header' QAPI field from LUKS create options * Improve tracing of TLS sockets and TLS chardevs * Improve error messages from TLS I/O failures * Add docs about use of LUKS detached header options * Allow building without libtasn1, but with GNUTLS * Fix detection of libgcrypt when libgcrypt-config is absent # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCAAdFiEE2vOm/bJrYpEtDo4/vobrtBUQT98FAmagzXUACgkQvobrtBUQ # T9++chAAhCFgo5A/UjQGdl9UAOW/sdgOoHGE3E8Y6sSTQyv+EfHf1DO89JtAh4ft # d8Hz7Taul4k1wRm6Dxv2aCqH5iS1tgDE2ghGDNwn/zDtHNnjFx3+HcxBaAEcpt3O # FqvGeG6KdFO1t2UR2DMh1XbhfwygrHiIcSB2y8jrgi46ncS6JvLrFavjLTe7JBn9 # J3y/iYgQiVPN6UlIwUs1EquGdoTI/0SpHVirqHN/2yyrdRsGBsXZq5WI6Oli8zFL # VqJNmc5Dzo7ushoYG5Rpk83mmC26VuXO/JmXyJ/c7FeADLWUfc/SPPyAMxPGuwFr # DKg84ovRtq3yZIw8LPoUJOtbcu4Y7BSGwlolQjWegvsVTU6Bdk+teZVR9X64QbM2 # YBXzMkRHUKzR3rb0LewAKehP3n93aBypLln9ZMgg7wj92Rj8Dl/sylaBhDEkH/HQ # 2pMdSdAWqMnGHfnKPxyjflNO2PIsOenZUkDZwf9i7Ow6fU5n3fqvudVDTWjXpWPn # V7v9JGNPHocScJFRUqHSVqd2ZWaZX4F1TsvG6SGOmzDGR0IjBRlqos7OEdbAAH1x # IglizbTxD6M9ZWJrGt1sl6LSAwEp3oXgsWNdejq2+7I6H4BeUm4ACDbdrEjqG9aG # Ya/HpNT0PEzbGXm6qsuHY5z0agGtaPwdXLcSGnsv+a0rP/9nthY= # =ccYf # -----END PGP SIGNATURE----- # gpg: Signature made Wed 24 Jul 2024 07:46:29 PM AEST # gpg: using RSA key DAF3A6FDB26B62912D0E8E3FBE86EBB415104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" [full] # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" [full] * tag 'misc-fixes-pull-request' of https://gitlab.com/berrange/qemu: crypto: propagate errors from TLS session I/O callbacks crypto: push error reporting into TLS session I/O APIs crypto: drop gnutls debug logging support chardev: add tracing of socket error conditions meson: build chardev trace files when have_block qapi: drop unused QCryptoBlockCreateOptionsLUKS.detached-header meson.build: fix libgcrypt detection on system without libgcrypt-config docs/devel: Add introduction to LUKS volume with detached header crypto: Allow building with GnuTLS but without Libtasn1 crypto: Restrict pkix_asn1_tab[] to crypto-tls-x509-helpers.c crypto: Remove 'crypto-tls-x509-helpers.h' from crypto-tls-psk-helpers.c Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
9726687f2f
@ -3451,6 +3451,7 @@ Detached LUKS header
|
||||
M: Hyman Huang <yong.huang@smartx.com>
|
||||
S: Maintained
|
||||
F: tests/qemu-iotests/tests/luks-detached-header
|
||||
F: docs/devel/luks-detached-header.rst
|
||||
|
||||
D-Bus
|
||||
M: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
@ -3484,7 +3485,7 @@ F: qapi/crypto.json
|
||||
F: tests/unit/test-crypto-*
|
||||
F: tests/bench/benchmark-crypto-*
|
||||
F: tests/unit/crypto-tls-*
|
||||
F: tests/unit/pkix_asn1_tab.c
|
||||
F: tests/unit/pkix_asn1_tab.c.inc
|
||||
F: qemu.sasl
|
||||
|
||||
Coroutines
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "qapi/clone-visitor.h"
|
||||
#include "qapi/qapi-visit-sockets.h"
|
||||
#include "qemu/yank.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "chardev/char-io.h"
|
||||
#include "chardev/char-socket.h"
|
||||
@ -126,6 +127,7 @@ static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
if (ret < 0 && errno != EAGAIN) {
|
||||
if (tcp_chr_read_poll(chr) <= 0) {
|
||||
/* Perform disconnect and return error. */
|
||||
trace_chr_socket_poll_err(chr, chr->label);
|
||||
tcp_chr_disconnect_locked(chr);
|
||||
} /* else let the read handler finish it properly */
|
||||
}
|
||||
@ -279,15 +281,16 @@ static ssize_t tcp_chr_recv(Chardev *chr, char *buf, size_t len)
|
||||
size_t i;
|
||||
int *msgfds = NULL;
|
||||
size_t msgfds_num = 0;
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_channel_has_feature(s->ioc, QIO_CHANNEL_FEATURE_FD_PASS)) {
|
||||
ret = qio_channel_readv_full(s->ioc, &iov, 1,
|
||||
&msgfds, &msgfds_num,
|
||||
0, NULL);
|
||||
0, &err);
|
||||
} else {
|
||||
ret = qio_channel_readv_full(s->ioc, &iov, 1,
|
||||
NULL, NULL,
|
||||
0, NULL);
|
||||
0, &err);
|
||||
}
|
||||
|
||||
if (msgfds_num) {
|
||||
@ -322,7 +325,11 @@ static ssize_t tcp_chr_recv(Chardev *chr, char *buf, size_t len)
|
||||
errno = EAGAIN;
|
||||
ret = -1;
|
||||
} else if (ret == -1) {
|
||||
trace_chr_socket_recv_err(chr, chr->label, error_get_pretty(err));
|
||||
error_free(err);
|
||||
errno = EIO;
|
||||
} else if (ret == 0) {
|
||||
trace_chr_socket_recv_eof(chr, chr->label);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -463,6 +470,7 @@ static void tcp_chr_disconnect_locked(Chardev *chr)
|
||||
SocketChardev *s = SOCKET_CHARDEV(chr);
|
||||
bool emit_close = s->state == TCP_CHARDEV_STATE_CONNECTED;
|
||||
|
||||
trace_chr_socket_disconnect(chr, chr->label);
|
||||
tcp_chr_free_connection(chr);
|
||||
|
||||
if (s->listener) {
|
||||
@ -521,6 +529,7 @@ static gboolean tcp_chr_hup(QIOChannel *channel,
|
||||
void *opaque)
|
||||
{
|
||||
Chardev *chr = CHARDEV(opaque);
|
||||
trace_chr_socket_hangup(chr, chr->label);
|
||||
tcp_chr_disconnect(chr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
@ -672,15 +681,18 @@ static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc,
|
||||
SocketChardev *s = user_data;
|
||||
Chardev *chr = CHARDEV(s);
|
||||
TCPChardevTelnetInit *init = s->telnet_init;
|
||||
Error *err = NULL;
|
||||
ssize_t ret;
|
||||
|
||||
assert(init);
|
||||
|
||||
ret = qio_channel_write(ioc, init->buf, init->buflen, NULL);
|
||||
ret = qio_channel_write(ioc, init->buf, init->buflen, &err);
|
||||
if (ret < 0) {
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
ret = 0;
|
||||
} else {
|
||||
trace_chr_socket_write_err(chr, chr->label, error_get_pretty(err));
|
||||
error_free(err);
|
||||
tcp_chr_disconnect(chr);
|
||||
goto end;
|
||||
}
|
||||
@ -765,9 +777,9 @@ static void tcp_chr_websock_handshake(QIOTask *task, gpointer user_data)
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_task_propagate_error(task, &err)) {
|
||||
error_reportf_err(err,
|
||||
"websock handshake of character device %s failed: ",
|
||||
chr->label);
|
||||
trace_chr_socket_ws_handshake_err(chr, chr->label,
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
tcp_chr_disconnect(chr);
|
||||
} else {
|
||||
if (s->do_telnetopt) {
|
||||
@ -805,9 +817,9 @@ static void tcp_chr_tls_handshake(QIOTask *task,
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_task_propagate_error(task, &err)) {
|
||||
error_reportf_err(err,
|
||||
"TLS handshake of character device %s failed: ",
|
||||
chr->label);
|
||||
trace_chr_socket_tls_handshake_err(chr, chr->label,
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
tcp_chr_disconnect(chr);
|
||||
} else {
|
||||
if (s->is_websock) {
|
||||
@ -826,19 +838,22 @@ static void tcp_chr_tls_init(Chardev *chr)
|
||||
SocketChardev *s = SOCKET_CHARDEV(chr);
|
||||
QIOChannelTLS *tioc;
|
||||
gchar *name;
|
||||
Error *err = NULL;
|
||||
|
||||
if (s->is_listen) {
|
||||
tioc = qio_channel_tls_new_server(
|
||||
s->ioc, s->tls_creds,
|
||||
s->tls_authz,
|
||||
NULL);
|
||||
&err);
|
||||
} else {
|
||||
tioc = qio_channel_tls_new_client(
|
||||
s->ioc, s->tls_creds,
|
||||
s->addr->u.inet.host,
|
||||
NULL);
|
||||
&err);
|
||||
}
|
||||
if (tioc == NULL) {
|
||||
trace_chr_socket_tls_init_err(chr, chr->label, error_get_pretty(err));
|
||||
error_free(err);
|
||||
tcp_chr_disconnect(chr);
|
||||
return;
|
||||
}
|
||||
|
@ -17,3 +17,13 @@ spice_vmc_register_interface(void *scd) "spice vmc registered interface %p"
|
||||
spice_vmc_unregister_interface(void *scd) "spice vmc unregistered interface %p"
|
||||
spice_vmc_event(int event) "spice vmc event %d"
|
||||
|
||||
# char-socket.c
|
||||
chr_socket_poll_err(void *chrdev, const char *label) "chardev socket poll error %p (%s)"
|
||||
chr_socket_recv_err(void *chrdev, const char *label, const char *err) "chardev socket recv error %p (%s): %s"
|
||||
chr_socket_recv_eof(void *chrdev, const char *label) "chardev socket recv end-of-file %p (%s)"
|
||||
chr_socket_write_err(void *chrdev, const char *label, const char *err) "chardev socket write error %p (%s): %s"
|
||||
chr_socket_disconnect(void *chrdev, const char *label) "chardev socket disconnect %p (%s)"
|
||||
chr_socket_hangup(void *chrdev, const char *label) "chardev socket hangup %p (%s)"
|
||||
chr_socket_ws_handshake_err(void *chrdev, const char *label, const char *err) "chardev socket websock handshake error %p (%s): %s"
|
||||
chr_socket_tls_handshake_err(void *chrdev, const char *label, const char *err) "chardev socket TLS handshake error %p (%s): %s"
|
||||
chr_socket_tls_init_err(void *chrdev, const char *label, const char *err) "chardev socket TLS init error %p (%s): %s"
|
||||
|
@ -34,14 +34,11 @@
|
||||
|
||||
#include "crypto/random.h"
|
||||
|
||||
/* #define DEBUG_GNUTLS */
|
||||
#ifdef DEBUG_GNUTLS
|
||||
static void qcrypto_gnutls_log(int level, const char *str)
|
||||
{
|
||||
fprintf(stderr, "%d: %s", level, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* To debug GNUTLS see env vars listed in
|
||||
* https://gnutls.org/manual/html_node/Debugging-and-auditing.html
|
||||
*/
|
||||
int qcrypto_init(Error **errp)
|
||||
{
|
||||
#ifdef CONFIG_GNUTLS
|
||||
@ -53,10 +50,6 @@ int qcrypto_init(Error **errp)
|
||||
gnutls_strerror(ret));
|
||||
return -1;
|
||||
}
|
||||
#ifdef DEBUG_GNUTLS
|
||||
gnutls_global_set_log_level(10);
|
||||
gnutls_global_set_log_function(qcrypto_gnutls_log);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GCRYPT
|
||||
|
@ -44,6 +44,13 @@ struct QCryptoTLSSession {
|
||||
QCryptoTLSSessionReadFunc readFunc;
|
||||
void *opaque;
|
||||
char *peername;
|
||||
|
||||
/*
|
||||
* Allow concurrent reads and writes, so track
|
||||
* errors separately
|
||||
*/
|
||||
Error *rerr;
|
||||
Error *werr;
|
||||
};
|
||||
|
||||
|
||||
@ -54,6 +61,9 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
|
||||
return;
|
||||
}
|
||||
|
||||
error_free(session->rerr);
|
||||
error_free(session->werr);
|
||||
|
||||
gnutls_deinit(session->handle);
|
||||
g_free(session->hostname);
|
||||
g_free(session->peername);
|
||||
@ -67,13 +77,26 @@ static ssize_t
|
||||
qcrypto_tls_session_push(void *opaque, const void *buf, size_t len)
|
||||
{
|
||||
QCryptoTLSSession *session = opaque;
|
||||
ssize_t ret;
|
||||
|
||||
if (!session->writeFunc) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
};
|
||||
|
||||
return session->writeFunc(buf, len, session->opaque);
|
||||
error_free(session->werr);
|
||||
session->werr = NULL;
|
||||
|
||||
ret = session->writeFunc(buf, len, session->opaque, &session->werr);
|
||||
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -81,13 +104,26 @@ static ssize_t
|
||||
qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
|
||||
{
|
||||
QCryptoTLSSession *session = opaque;
|
||||
ssize_t ret;
|
||||
|
||||
if (!session->readFunc) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
};
|
||||
|
||||
return session->readFunc(buf, len, session->opaque);
|
||||
error_free(session->rerr);
|
||||
session->rerr = NULL;
|
||||
|
||||
ret = session->readFunc(buf, len, session->opaque, &session->rerr);
|
||||
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#define TLS_PRIORITY_ADDITIONAL_ANON "+ANON-DH"
|
||||
@ -441,23 +477,25 @@ qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session,
|
||||
ssize_t
|
||||
qcrypto_tls_session_write(QCryptoTLSSession *session,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
size_t len,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t ret = gnutls_record_send(session->handle, buf, len);
|
||||
|
||||
if (ret < 0) {
|
||||
switch (ret) {
|
||||
case GNUTLS_E_AGAIN:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
errno = EINTR;
|
||||
break;
|
||||
default:
|
||||
errno = EIO;
|
||||
break;
|
||||
if (ret == GNUTLS_E_AGAIN) {
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else {
|
||||
if (session->werr) {
|
||||
error_propagate(errp, session->werr);
|
||||
session->werr = NULL;
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Cannot write to TLS channel: %s",
|
||||
gnutls_strerror(ret));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -467,26 +505,29 @@ qcrypto_tls_session_write(QCryptoTLSSession *session,
|
||||
ssize_t
|
||||
qcrypto_tls_session_read(QCryptoTLSSession *session,
|
||||
char *buf,
|
||||
size_t len)
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t ret = gnutls_record_recv(session->handle, buf, len);
|
||||
|
||||
if (ret < 0) {
|
||||
switch (ret) {
|
||||
case GNUTLS_E_AGAIN:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
errno = EINTR;
|
||||
break;
|
||||
case GNUTLS_E_PREMATURE_TERMINATION:
|
||||
errno = ECONNABORTED;
|
||||
break;
|
||||
default:
|
||||
errno = EIO;
|
||||
break;
|
||||
if (ret == GNUTLS_E_AGAIN) {
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else if ((ret == GNUTLS_E_PREMATURE_TERMINATION) &&
|
||||
gracefulTermination){
|
||||
return 0;
|
||||
} else {
|
||||
if (session->rerr) {
|
||||
error_propagate(errp, session->rerr);
|
||||
session->rerr = NULL;
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Cannot read from TLS channel: %s",
|
||||
gnutls_strerror(ret));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -512,11 +553,21 @@ qcrypto_tls_session_handshake(QCryptoTLSSession *session,
|
||||
ret == GNUTLS_E_AGAIN) {
|
||||
ret = 1;
|
||||
} else {
|
||||
error_setg(errp, "TLS handshake failed: %s",
|
||||
gnutls_strerror(ret));
|
||||
if (session->rerr || session->werr) {
|
||||
error_setg(errp, "TLS handshake failed: %s: %s",
|
||||
gnutls_strerror(ret),
|
||||
error_get_pretty(session->rerr ?
|
||||
session->rerr : session->werr));
|
||||
} else {
|
||||
error_setg(errp, "TLS handshake failed: %s",
|
||||
gnutls_strerror(ret));
|
||||
}
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
error_free(session->rerr);
|
||||
error_free(session->werr);
|
||||
session->rerr = session->werr = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -605,9 +656,10 @@ qcrypto_tls_session_set_callbacks(
|
||||
ssize_t
|
||||
qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
size_t len,
|
||||
Error **errp)
|
||||
{
|
||||
errno = -EIO;
|
||||
error_setg(errp, "TLS requires GNUTLS support");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -615,9 +667,11 @@ qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
||||
ssize_t
|
||||
qcrypto_tls_session_read(QCryptoTLSSession *sess,
|
||||
char *buf,
|
||||
size_t len)
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp)
|
||||
{
|
||||
errno = -EIO;
|
||||
error_setg(errp, "TLS requires GNUTLS support");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
10
docs/devel/crypto.rst
Normal file
10
docs/devel/crypto.rst
Normal file
@ -0,0 +1,10 @@
|
||||
.. _crypto-ref:
|
||||
|
||||
====================
|
||||
Cryptography in QEMU
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
luks-detached-header
|
@ -20,3 +20,4 @@ Details about QEMU's various subsystems including how to add features to them.
|
||||
vfio-iommufd
|
||||
writing-monitor-commands
|
||||
virtio-backends
|
||||
crypto
|
||||
|
182
docs/devel/luks-detached-header.rst
Normal file
182
docs/devel/luks-detached-header.rst
Normal file
@ -0,0 +1,182 @@
|
||||
================================
|
||||
LUKS volume with detached header
|
||||
================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This document gives an overview of the design of LUKS volume with detached
|
||||
header and how to use it.
|
||||
|
||||
Background
|
||||
==========
|
||||
|
||||
The LUKS format has ability to store the header in a separate volume from
|
||||
the payload. We could extend the LUKS driver in QEMU to support this use
|
||||
case.
|
||||
|
||||
Normally a LUKS volume has a layout:
|
||||
|
||||
::
|
||||
|
||||
+-----------------------------------------------+
|
||||
| | | |
|
||||
disk | header | key material | disk payload data |
|
||||
| | | |
|
||||
+-----------------------------------------------+
|
||||
|
||||
With a detached LUKS header, you need 2 disks so getting:
|
||||
|
||||
::
|
||||
|
||||
+--------------------------+
|
||||
disk1 | header | key material |
|
||||
+--------------------------+
|
||||
+---------------------+
|
||||
disk2 | disk payload data |
|
||||
+---------------------+
|
||||
|
||||
There are a variety of benefits to doing this:
|
||||
|
||||
* Secrecy - the disk2 cannot be identified as containing LUKS
|
||||
volume since there's no header
|
||||
* Control - if access to the disk1 is restricted, then even
|
||||
if someone has access to disk2 they can't unlock
|
||||
it. Might be useful if you have disks on NFS but
|
||||
want to restrict which host can launch a VM
|
||||
instance from it, by dynamically providing access
|
||||
to the header to a designated host
|
||||
* Flexibility - your application data volume may be a given
|
||||
size and it is inconvenient to resize it to
|
||||
add encryption.You can store the LUKS header
|
||||
separately and use the existing storage
|
||||
volume for payload
|
||||
* Recovery - corruption of a bit in the header may make the
|
||||
entire payload inaccessible. It might be
|
||||
convenient to take backups of the header. If
|
||||
your primary disk header becomes corrupt, you
|
||||
can unlock the data still by pointing to the
|
||||
backup detached header
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
Take the qcow2 encryption, for example. The architecture of the
|
||||
LUKS volume with detached header is shown in the diagram below.
|
||||
|
||||
There are two children of the root node: a file and a header.
|
||||
Data from the disk payload is stored in the file node. The
|
||||
LUKS header and key material are located in the header node,
|
||||
as previously mentioned.
|
||||
|
||||
::
|
||||
|
||||
+-----------------------------+
|
||||
Root node | foo[luks] |
|
||||
+-----------------------------+
|
||||
| |
|
||||
file | header |
|
||||
| |
|
||||
+---------------------+ +------------------+
|
||||
Child node |payload-format[qcow2]| |header-format[raw]|
|
||||
+---------------------+ +------------------+
|
||||
| |
|
||||
file | file |
|
||||
| |
|
||||
+----------------------+ +---------------------+
|
||||
Child node |payload-protocol[file]| |header-protocol[file]|
|
||||
+----------------------+ +---------------------+
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
Host storage Host storage
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Create a LUKS disk with a detached header using qemu-img
|
||||
--------------------------------------------------------
|
||||
|
||||
Shell commandline::
|
||||
|
||||
# qemu-img create --object secret,id=sec0,data=abc123 -f luks \
|
||||
-o cipher-alg=aes-256,cipher-mode=xts -o key-secret=sec0 \
|
||||
-o detached-header=true test-header.img
|
||||
# qemu-img create -f qcow2 test-payload.qcow2 200G
|
||||
# qemu-img info 'json:{"driver":"luks","file":{"filename": \
|
||||
"test-payload.img"},"header":{"filename":"test-header.img"}}'
|
||||
|
||||
Set up a VM's LUKS volume with a detached header
|
||||
------------------------------------------------
|
||||
|
||||
Qemu commandline::
|
||||
|
||||
# qemu-system-x86_64 ... \
|
||||
-object '{"qom-type":"secret","id":"libvirt-3-format-secret", \
|
||||
"data":"abc123"}' \
|
||||
-blockdev '{"driver":"file","filename":"/path/to/test-header.img", \
|
||||
"node-name":"libvirt-1-storage"}' \
|
||||
-blockdev '{"node-name":"libvirt-1-format","read-only":false, \
|
||||
"driver":"raw","file":"libvirt-1-storage"}' \
|
||||
-blockdev '{"driver":"file","filename":"/path/to/test-payload.qcow2", \
|
||||
"node-name":"libvirt-2-storage"}' \
|
||||
-blockdev '{"node-name":"libvirt-2-format","read-only":false, \
|
||||
"driver":"qcow2","file":"libvirt-2-storage"}' \
|
||||
-blockdev '{"node-name":"libvirt-3-format","driver":"luks", \
|
||||
"file":"libvirt-2-format","header":"libvirt-1-format","key-secret": \
|
||||
"libvirt-3-format-secret"}' \
|
||||
-device '{"driver":"virtio-blk-pci","bus":XXX,"addr":YYY,"drive": \
|
||||
"libvirt-3-format","id":"virtio-disk1"}'
|
||||
|
||||
Add LUKS volume to a VM with a detached header
|
||||
----------------------------------------------
|
||||
|
||||
1. object-add the secret for decrypting the cipher stored in
|
||||
LUKS header above::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"object-add", \
|
||||
"arguments":{"qom-type":"secret", "id": \
|
||||
"libvirt-4-format-secret", "data":"abc123"}}'
|
||||
|
||||
2. block-add the protocol node for LUKS header::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \
|
||||
"arguments":{"node-name":"libvirt-1-storage", "driver":"file", \
|
||||
"filename": "/path/to/test-header.img" }}'
|
||||
|
||||
3. block-add the raw-drived node for LUKS header::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \
|
||||
"arguments":{"node-name":"libvirt-1-format", "driver":"raw", \
|
||||
"file":"libvirt-1-storage"}}'
|
||||
|
||||
4. block-add the protocol node for disk payload image::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \
|
||||
"arguments":{"node-name":"libvirt-2-storage", "driver":"file", \
|
||||
"filename":"/path/to/test-payload.qcow2"}}'
|
||||
|
||||
5. block-add the qcow2-drived format node for disk payload data::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \
|
||||
"arguments":{"node-name":"libvirt-2-format", "driver":"qcow2", \
|
||||
"file":"libvirt-2-storage"}}'
|
||||
|
||||
6. block-add the luks-drived format node to link the qcow2 disk
|
||||
with the LUKS header by specifying the field "header"::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \
|
||||
"arguments":{"node-name":"libvirt-3-format", "driver":"luks", \
|
||||
"file":"libvirt-2-format", "header":"libvirt-1-format", \
|
||||
"key-secret":"libvirt-2-format-secret"}}'
|
||||
|
||||
7. hot-plug the virtio-blk device finally::
|
||||
|
||||
# virsh qemu-monitor-command vm '{"execute":"device_add", \
|
||||
"arguments": {"driver":"virtio-blk-pci", \
|
||||
"drive": "libvirt-3-format", "id":"virtio-disk2"}}
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
1. Support the shared detached LUKS header within the VM.
|
@ -107,6 +107,7 @@
|
||||
|
||||
typedef struct QCryptoTLSSession QCryptoTLSSession;
|
||||
|
||||
#define QCRYPTO_TLS_SESSION_ERR_BLOCK -2
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_new:
|
||||
@ -177,12 +178,18 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(QCryptoTLSSession, qcrypto_tls_session_free)
|
||||
int qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess,
|
||||
Error **errp);
|
||||
|
||||
/*
|
||||
* These must return QCRYPTO_TLS_SESSION_ERR_BLOCK if the I/O
|
||||
* would block, but on other errors, must fill 'errp'
|
||||
*/
|
||||
typedef ssize_t (*QCryptoTLSSessionWriteFunc)(const char *buf,
|
||||
size_t len,
|
||||
void *opaque);
|
||||
void *opaque,
|
||||
Error **errp);
|
||||
typedef ssize_t (*QCryptoTLSSessionReadFunc)(char *buf,
|
||||
size_t len,
|
||||
void *opaque);
|
||||
void *opaque,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_set_callbacks:
|
||||
@ -212,6 +219,7 @@ void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess,
|
||||
* @sess: the TLS session object
|
||||
* @buf: the plain text to send
|
||||
* @len: the length of @buf
|
||||
* @errp: pointer to hold returned error object
|
||||
*
|
||||
* Encrypt @len bytes of the data in @buf and send
|
||||
* it to the remote peer using the callback previously
|
||||
@ -221,32 +229,45 @@ void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess,
|
||||
* qcrypto_tls_session_get_handshake_status() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns: the number of bytes sent, or -1 on error
|
||||
* Returns: the number of bytes sent,
|
||||
* or QCRYPTO_TLS_SESSION_ERR_BLOCK if the write would block,
|
||||
* or -1 on error.
|
||||
*/
|
||||
ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
||||
const char *buf,
|
||||
size_t len);
|
||||
size_t len,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_read:
|
||||
* @sess: the TLS session object
|
||||
* @buf: to fill with plain text received
|
||||
* @len: the length of @buf
|
||||
* @gracefulTermination: treat premature termination as graceful EOF
|
||||
* @errp: pointer to hold returned error object
|
||||
*
|
||||
* Receive up to @len bytes of data from the remote peer
|
||||
* using the callback previously registered with
|
||||
* qcrypto_tls_session_set_callbacks(), decrypt it and
|
||||
* store it in @buf.
|
||||
*
|
||||
* If @gracefulTermination is true, then a premature termination
|
||||
* of the TLS session will be treated as indicating EOF, as
|
||||
* opposed to an error.
|
||||
*
|
||||
* It is an error to call this before
|
||||
* qcrypto_tls_session_get_handshake_status() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns: the number of bytes received, or -1 on error
|
||||
* Returns: the number of bytes received,
|
||||
* or QCRYPTO_TLS_SESSION_ERR_BLOCK if the receive would block,
|
||||
* or -1 on error.
|
||||
*/
|
||||
ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
|
||||
char *buf,
|
||||
size_t len);
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_check_pending:
|
||||
|
@ -28,17 +28,16 @@
|
||||
|
||||
static ssize_t qio_channel_tls_write_handler(const char *buf,
|
||||
size_t len,
|
||||
void *opaque)
|
||||
void *opaque,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_write(tioc->master, buf, len, NULL);
|
||||
ret = qio_channel_write(tioc->master, buf, len, errp);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
@ -46,17 +45,16 @@ static ssize_t qio_channel_tls_write_handler(const char *buf,
|
||||
|
||||
static ssize_t qio_channel_tls_read_handler(char *buf,
|
||||
size_t len,
|
||||
void *opaque)
|
||||
void *opaque,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_read(tioc->master, buf, len, NULL);
|
||||
ret = qio_channel_read(tioc->master, buf, len, errp);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
@ -277,24 +275,19 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
|
||||
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;
|
||||
}
|
||||
} else if (errno == ECONNABORTED &&
|
||||
(qatomic_load_acquire(&tioc->shutdown) &
|
||||
QIO_CHANNEL_SHUTDOWN_READ)) {
|
||||
return 0;
|
||||
ssize_t ret = qcrypto_tls_session_read(
|
||||
tioc->session,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len,
|
||||
qatomic_load_acquire(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ,
|
||||
errp);
|
||||
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
|
||||
if (got) {
|
||||
return got;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Cannot read from TLS channel");
|
||||
} else if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
got += ret;
|
||||
@ -321,18 +314,15 @@ static ssize_t qio_channel_tls_writev(QIOChannel *ioc,
|
||||
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;
|
||||
}
|
||||
iov[i].iov_len,
|
||||
errp);
|
||||
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
|
||||
if (done) {
|
||||
return done;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Cannot write to TLS channel");
|
||||
} else if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
done += ret;
|
||||
|
@ -1696,7 +1696,6 @@ endif
|
||||
if not gnutls_crypto.found()
|
||||
if (not get_option('gcrypt').auto() or have_system) and not get_option('nettle').enabled()
|
||||
gcrypt = dependency('libgcrypt', version: '>=1.8',
|
||||
method: 'config-tool',
|
||||
required: get_option('gcrypt'))
|
||||
# Debian has removed -lgpg-error from libgcrypt-config
|
||||
# as it "spreads unnecessary dependencies" which in
|
||||
@ -1979,6 +1978,7 @@ endif
|
||||
tasn1 = not_found
|
||||
if gnutls.found()
|
||||
tasn1 = dependency('libtasn1',
|
||||
required: false,
|
||||
method: 'pkg-config')
|
||||
endif
|
||||
keyutils = not_found
|
||||
@ -3343,6 +3343,7 @@ if have_block
|
||||
trace_events_subdirs += [
|
||||
'authz',
|
||||
'block',
|
||||
'chardev',
|
||||
'io',
|
||||
'nbd',
|
||||
'scsi',
|
||||
@ -3354,7 +3355,6 @@ if have_system
|
||||
'audio',
|
||||
'backends',
|
||||
'backends/tpm',
|
||||
'chardev',
|
||||
'ebpf',
|
||||
'hw/9pfs',
|
||||
'hw/acpi',
|
||||
|
@ -226,8 +226,6 @@
|
||||
# @iter-time: number of milliseconds to spend in PBKDF passphrase
|
||||
# processing. Currently defaults to 2000. (since 2.8)
|
||||
#
|
||||
# @detached-header: create a detached LUKS header. (since 9.0)
|
||||
#
|
||||
# Since: 2.6
|
||||
##
|
||||
{ 'struct': 'QCryptoBlockCreateOptionsLUKS',
|
||||
@ -237,8 +235,7 @@
|
||||
'*ivgen-alg': 'QCryptoIVGenAlgorithm',
|
||||
'*ivgen-hash-alg': 'QCryptoHashAlgorithm',
|
||||
'*hash-alg': 'QCryptoHashAlgorithm',
|
||||
'*iter-time': 'int',
|
||||
'*detached-header': 'bool'}}
|
||||
'*iter-time': 'int' }}
|
||||
|
||||
##
|
||||
# @QCryptoBlockOpenOptions:
|
||||
|
@ -322,8 +322,7 @@ if gnutls.found()
|
||||
migration_files += [files('../unit/crypto-tls-psk-helpers.c'), gnutls]
|
||||
|
||||
if tasn1.found()
|
||||
migration_files += [files('../unit/crypto-tls-x509-helpers.c',
|
||||
'../unit/pkix_asn1_tab.c'), tasn1]
|
||||
migration_files += [files('../unit/crypto-tls-x509-helpers.c'), tasn1]
|
||||
endif
|
||||
endif
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "crypto-tls-psk-helpers.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
|
@ -20,15 +20,19 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include <libtasn1.h>
|
||||
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "crypto/init.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
#include "pkix_asn1_tab.c.inc"
|
||||
|
||||
/*
|
||||
* This stores some static data that is needed when
|
||||
* encoding extensions in the x509 certs
|
||||
*/
|
||||
asn1_node pkix_asn1;
|
||||
static asn1_node pkix_asn1;
|
||||
|
||||
/*
|
||||
* To avoid consuming random entropy to generate keys,
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
#include <libtasn1.h>
|
||||
|
||||
|
||||
#define QCRYPTO_TLS_TEST_CLIENT_NAME "ACME QEMU Client"
|
||||
@ -171,6 +170,4 @@ void test_tls_cleanup(const char *keyfile);
|
||||
}; \
|
||||
test_tls_generate_cert(&varname, cavarname.crt)
|
||||
|
||||
extern const asn1_static_node pkix_asn1_tab[];
|
||||
|
||||
#endif
|
||||
|
@ -99,11 +99,11 @@ if have_block
|
||||
tasn1.found() and \
|
||||
host_os != 'windows'
|
||||
tests += {
|
||||
'test-crypto-tlscredsx509': ['crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c',
|
||||
'test-crypto-tlscredsx509': ['crypto-tls-x509-helpers.c',
|
||||
tasn1, crypto, gnutls],
|
||||
'test-crypto-tlssession': ['crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c', 'crypto-tls-psk-helpers.c',
|
||||
'test-crypto-tlssession': ['crypto-tls-x509-helpers.c', 'crypto-tls-psk-helpers.c',
|
||||
tasn1, crypto, gnutls],
|
||||
'test-io-channel-tls': ['io-channel-helpers.c', 'crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c',
|
||||
'test-io-channel-tls': ['io-channel-helpers.c', 'crypto-tls-x509-helpers.c',
|
||||
tasn1, io, crypto, gnutls]}
|
||||
endif
|
||||
if pam.found()
|
||||
|
@ -3,10 +3,7 @@
|
||||
* and is under copyright of various GNUTLS contributors.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
|
||||
const asn1_static_node pkix_asn1_tab[] = {
|
||||
static const asn1_static_node pkix_asn1_tab[] = {
|
||||
{"PKIX1", 536875024, 0},
|
||||
{0, 1073741836, 0},
|
||||
{"id-ce", 1879048204, 0},
|
@ -35,18 +35,40 @@
|
||||
#define PSKFILE WORKDIR "keys.psk"
|
||||
#define KEYFILE WORKDIR "key-ctx.pem"
|
||||
|
||||
static ssize_t testWrite(const char *buf, size_t len, void *opaque)
|
||||
static ssize_t
|
||||
testWrite(const char *buf, size_t len, void *opaque, Error **errp)
|
||||
{
|
||||
int *fd = opaque;
|
||||
int ret;
|
||||
|
||||
return write(*fd, buf, len);
|
||||
ret = write(*fd, buf, len);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else {
|
||||
error_setg_errno(errp, errno, "unable to write");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t testRead(char *buf, size_t len, void *opaque)
|
||||
static ssize_t
|
||||
testRead(char *buf, size_t len, void *opaque, Error **errp)
|
||||
{
|
||||
int *fd = opaque;
|
||||
int ret;
|
||||
|
||||
return read(*fd, buf, len);
|
||||
ret = read(*fd, buf, len);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else {
|
||||
error_setg_errno(errp, errno, "unable to read");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static QCryptoTLSCreds *test_tls_creds_psk_create(
|
||||
|
Loading…
x
Reference in New Issue
Block a user