Merge tag 'next-pr-pull-request' of https://gitlab.com/berrange/qemu into staging

Merge crypto and other misc fixes / features

 * Increase minimum gnutls to 3.7.5
 * Increase minimum libgcrypt to 1.9.4
 * Increase minimum nettle to 3.7.3
 * Drop obsolete in-tree XTS impl
 * Fix memory leak when loading certificates
 * Remove/reduce duplication when loading certifcates
 * Fix possible crash when certificates are unloaded
   while an active TLS connection is using when in a
   TLS handshake operation
 * Deprecate use of dh-params.pem file
 * Document how to create certificates with Post-Quantum
   Cryptography compliant algorithms.
 * Support loading multiple certificate identities to
   allow support for Post-Quantum crypto in parallel
   with traditional RSA/ECC
 * Add "-run-with exit-with-parent=on" parameter
 * Flush pending errors when seeing ENOBUFS with
   a zero-copy send attempt
 * Fix data buffer parameters in hash & IO channel APIs
   to use 'void *'

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE2vOm/bJrYpEtDo4/vobrtBUQT98FAmkIr/8ACgkQvobrtBUQ
# T9+2RhAAhEak/krdlTJw8OlJonUop7G5mlLU2TEoX0duRORcFhScsdSwb2pyc/wM
# tnwfWXsnsKFItJx1y3STkOICtdNqizGoU3+c7wl4anQBurydu+XTs4ESBtVJtMYr
# 1lTYvp0HFyKvaXwDWKE+ztltlJiog51tHPDLUIBCnyJysLVqxCHMHmkbG46IPBZo
# A2XXxp3j/VBPmhls0JHpbAD4iVE3PChdK7zhyeGe/rld9+0JA12EPCvZ5Uokdj41
# aYP/okvnVH1atucoygPdDE3P5GYBKaSXZUWqzfkKhU7FgaF2863Td7ff1ip+WyWN
# FFPNEU1hVg+T5hfsZVQmmIFDdSJWqoZaZM/WJVYdrRY4dKUCPnJ9OINbbnhuWz5E
# JFmZOPibRZKQ44XcHX49JRfJEBvoq1z9OT1r7HkEP4D9/O7V/riIunbAESMk0sgi
# 0/fatvdhNKMN6YBQM3mtN3yNOcfRSWFtSy9XS9zDjdpEKT7ui2t9FC0ZNSP0FRkS
# aTY31FyacjHwU3zaoh6NoqqpxV9wwHrgsJwNbA/IztjmX/jvGG0Gb/sXVEqM59tR
# e3VWTmlmZ1T8OLImh1hG4t+nY+XzI64QpVX8H9RCGm21o28DyTcOnTFK4OyIfWe5
# ttnNfEJN8WCVCsA8tcM8yAbZ/0qXrYfiZSO7hq79wE7LvyholAQ=
# =9ESG
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 03 Nov 2025 02:37:03 PM CET
# gpg:                using RSA key DAF3A6FDB26B62912D0E8E3FBE86EBB415104FDF
# gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" [unknown]
# gpg:                 aka "Daniel P. Berrange <berrange@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: DAF3 A6FD B26B 6291 2D0E  8E3F BE86 EBB4 1510 4FDF

* tag 'next-pr-pull-request' of https://gitlab.com/berrange/qemu: (32 commits)
  docs: creation of x509 certs compliant with post-quantum crypto
  crypto: support upto 5 parallel certificate identities
  crypto: expand logic to cope with multiple certificate identities
  crypto: avoid loading the identity certs twice
  crypto: avoid loading the CA certs twice
  crypto: deprecate use of external dh-params.pem file
  crypto: make TLS credentials structs private
  crypto: fix lifecycle handling of gnutls credentials objects
  crypto: introduce a wrapper around gnutls credentials
  crypto: introduce method for reloading TLS creds
  crypto: reduce duplication in handling TLS priority strings
  crypto: remove duplication loading x509 CA cert
  crypto: shorten the endpoint == server check in TLS creds
  crypto: move release of DH parameters into TLS creds parent
  crypto: remove needless indirection via parent_obj field
  crypto: use g_autofree when loading x509 credentials
  crypto: move check for TLS creds 'dir' property
  crypto: remove redundant access() checks before loading certs
  crypto: replace stat() with access() for credential checks
  crypto: add missing free of certs array
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
diff --git a/crypto/cipher-gnutls.c.inc b/crypto/cipher-gnutls.c.inc
index b9450d4..a8263ff 100644
--- a/crypto/cipher-gnutls.c.inc
+++ b/crypto/cipher-gnutls.c.inc
@@ -23,10 +23,6 @@
 
 #include <gnutls/crypto.h>
 
-#if GNUTLS_VERSION_NUMBER >= 0x030608
-#define QEMU_GNUTLS_XTS
-#endif
-
 bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
                              QCryptoCipherMode mode)
 {
@@ -44,7 +40,6 @@
         default:
             return false;
         }
-#ifdef QEMU_GNUTLS_XTS
     case QCRYPTO_CIPHER_MODE_XTS:
         switch (alg) {
         case QCRYPTO_CIPHER_ALGO_AES_128:
@@ -53,7 +48,6 @@
         default:
             return false;
         }
-#endif
     default:
         return false;
     }
@@ -241,7 +235,6 @@
     int err;
 
     switch (mode) {
-#ifdef QEMU_GNUTLS_XTS
     case QCRYPTO_CIPHER_MODE_XTS:
         switch (alg) {
         case QCRYPTO_CIPHER_ALGO_AES_128:
@@ -254,7 +247,6 @@
             break;
         }
         break;
-#endif
 
     case QCRYPTO_CIPHER_MODE_ECB:
     case QCRYPTO_CIPHER_MODE_CBC:
diff --git a/crypto/cipher-nettle.c.inc b/crypto/cipher-nettle.c.inc
index ae91363..1afdc39 100644
--- a/crypto/cipher-nettle.c.inc
+++ b/crypto/cipher-nettle.c.inc
@@ -18,10 +18,6 @@
  *
  */
 
-#ifdef CONFIG_QEMU_PRIVATE_XTS
-#include "crypto/xts.h"
-#endif
-
 #include <nettle/nettle-types.h>
 #include <nettle/aes.h>
 #include <nettle/des.h>
@@ -30,9 +26,7 @@
 #include <nettle/serpent.h>
 #include <nettle/twofish.h>
 #include <nettle/ctr.h>
-#ifndef CONFIG_QEMU_PRIVATE_XTS
 #include <nettle/xts.h>
-#endif
 #ifdef CONFIG_CRYPTO_SM4
 #include <nettle/sm4.h>
 #endif
@@ -154,43 +148,6 @@
 };
 
 
-#ifdef CONFIG_QEMU_PRIVATE_XTS
-#define DEFINE__XTS(NAME, TYPE, BLEN, ENCRYPT, DECRYPT)                 \
-static void NAME##_xts_wrape(const void *ctx, size_t length,            \
-                             uint8_t *dst, const uint8_t *src)          \
-{                                                                       \
-    ENCRYPT((const void *)ctx, length, dst, src);                       \
-}                                                                       \
-static void NAME##_xts_wrapd(const void *ctx, size_t length,            \
-                             uint8_t *dst, const uint8_t *src)          \
-{                                                                       \
-    DECRYPT((const void *)ctx, length, dst, src);                       \
-}                                                                       \
-static int NAME##_encrypt_xts(QCryptoCipher *cipher, const void *in,    \
-                              void *out, size_t len, Error **errp)      \
-{                                                                       \
-    TYPE *ctx = container_of(cipher, TYPE, base);                       \
-    if (!qcrypto_length_check(len, BLEN, errp)) {                       \
-        return -1;                                                      \
-    }                                                                   \
-    xts_encrypt(&ctx->key, &ctx->key_xts,                               \
-                NAME##_xts_wrape, NAME##_xts_wrapd,                     \
-                ctx->iv, len, out, in);                                 \
-    return 0;                                                           \
-}                                                                       \
-static int NAME##_decrypt_xts(QCryptoCipher *cipher, const void *in,    \
-                              void *out, size_t len, Error **errp)      \
-{                                                                       \
-    TYPE *ctx = container_of(cipher, TYPE, base);                       \
-    if (!qcrypto_length_check(len, BLEN, errp)) {                       \
-        return -1;                                                      \
-    }                                                                   \
-    xts_decrypt(&ctx->key, &ctx->key_xts,                               \
-                NAME##_xts_wrape, NAME##_xts_wrapd,                     \
-                ctx->iv, len, out, in);                                 \
-    return 0;                                                           \
-}
-#else
 #define DEFINE__XTS(NAME, TYPE, BLEN, ENCRYPT, DECRYPT)                 \
 static int NAME##_encrypt_xts(QCryptoCipher *cipher, const void *in,    \
                               void *out, size_t len, Error **errp)      \
@@ -214,7 +171,6 @@
                         ctx->iv, len, out, in);                         \
     return 0;                                                           \
 }
-#endif
 
 #define DEFINE_XTS(NAME, TYPE, BLEN, ENCRYPT, DECRYPT)          \
     QEMU_BUILD_BUG_ON(BLEN != XTS_BLOCK_SIZE);                  \
diff --git a/crypto/cipher.c b/crypto/cipher.c
index 229710f..515165e 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -142,7 +142,7 @@
 #include "cipher-gcrypt.c.inc"
 #elif defined CONFIG_NETTLE
 #include "cipher-nettle.c.inc"
-#elif defined CONFIG_GNUTLS_CRYPTO
+#elif defined CONFIG_GNUTLS
 #include "cipher-gnutls.c.inc"
 #else
 #include "cipher-stub.c.inc"
diff --git a/crypto/hash.c b/crypto/hash.c
index 7513769..6ffb88b 100644
--- a/crypto/hash.c
+++ b/crypto/hash.c
@@ -67,13 +67,13 @@
 
 
 int qcrypto_hash_bytes(QCryptoHashAlgo alg,
-                       const char *buf,
+                       const void *buf,
                        size_t len,
                        uint8_t **result,
                        size_t *resultlen,
                        Error **errp)
 {
-    struct iovec iov = { .iov_base = (char *)buf,
+    struct iovec iov = { .iov_base = (void *)buf,
                          .iov_len = len };
     return qcrypto_hash_bytesv(alg, &iov, 1, result, resultlen, errp);
 }
@@ -89,11 +89,11 @@
 }
 
 int qcrypto_hash_update(QCryptoHash *hash,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         Error **errp)
 {
-    struct iovec iov = { .iov_base = (char *)buf, .iov_len = len };
+    struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
 
     return qcrypto_hash_updatev(hash, &iov, 1, errp);
 }
@@ -206,12 +206,12 @@
 }
 
 int qcrypto_hash_digest(QCryptoHashAlgo alg,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **digest,
                         Error **errp)
 {
-    struct iovec iov = { .iov_base = (char *)buf, .iov_len = len };
+    struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
 
     return qcrypto_hash_digestv(alg, &iov, 1, digest, errp);
 }
@@ -237,12 +237,12 @@
 }
 
 int qcrypto_hash_base64(QCryptoHashAlgo alg,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **base64,
                         Error **errp)
 {
-    struct iovec iov = { .iov_base = (char *)buf, .iov_len = len };
+    struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
 
     return qcrypto_hash_base64v(alg, &iov, 1, base64, errp);
 }
diff --git a/crypto/hmac.c b/crypto/hmac.c
index 422e005..2f0d044 100644
--- a/crypto/hmac.c
+++ b/crypto/hmac.c
@@ -28,14 +28,14 @@
 }
 
 int qcrypto_hmac_bytes(QCryptoHmac *hmac,
-                       const char *buf,
+                       const void *buf,
                        size_t len,
                        uint8_t **result,
                        size_t *resultlen,
                        Error **errp)
 {
     struct iovec iov = {
-            .iov_base = (char *)buf,
+            .iov_base = (void *)buf,
             .iov_len = len
     };
 
@@ -70,13 +70,13 @@
 }
 
 int qcrypto_hmac_digest(QCryptoHmac *hmac,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **digest,
                         Error **errp)
 {
     struct iovec iov = {
-            .iov_base = (char *)buf,
+            .iov_base = (void *)buf,
             .iov_len = len
     };
 
diff --git a/crypto/meson.build b/crypto/meson.build
index 735635d..b51597a 100644
--- a/crypto/meson.build
+++ b/crypto/meson.build
@@ -25,7 +25,10 @@
 ))
 
 if gnutls.found()
-  crypto_ss.add(files('x509-utils.c'))
+  crypto_ss.add(files(
+    'tlscredsbox.c',
+    'x509-utils.c',
+  ))
 endif
 
 if nettle.found()
@@ -33,12 +36,9 @@
   if hogweed.found()
     crypto_ss.add(gmp, hogweed)
   endif
-  if xts == 'private'
-    crypto_ss.add(files('xts.c'))
-  endif
 elif gcrypt.found()
   crypto_ss.add(gcrypt, files('hash-gcrypt.c', 'hmac-gcrypt.c', 'pbkdf-gcrypt.c'))
-elif gnutls_crypto.found()
+elif gnutls.found()
   crypto_ss.add(gnutls, files('hash-gnutls.c', 'hmac-gnutls.c', 'pbkdf-gnutls.c'))
 else
   crypto_ss.add(files('hash-glib.c', 'hmac-glib.c', 'pbkdf-stub.c'))
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c
index 9e59594..fb09e29 100644
--- a/crypto/tlscreds.c
+++ b/crypto/tlscreds.c
@@ -22,6 +22,7 @@
 #include "qapi/error.h"
 #include "qapi-types-crypto.h"
 #include "qemu/module.h"
+#include "qemu/error-report.h"
 #include "tlscredspriv.h"
 #include "trace.h"
 
@@ -38,22 +39,7 @@
 
     trace_qcrypto_tls_creds_load_dh(creds, filename ? filename : "<generated>");
 
-    if (filename == NULL) {
-        ret = gnutls_dh_params_init(dh_params);
-        if (ret < 0) {
-            error_setg(errp, "Unable to initialize DH parameters: %s",
-                       gnutls_strerror(ret));
-            return -1;
-        }
-        ret = gnutls_dh_params_generate2(*dh_params, DH_BITS);
-        if (ret < 0) {
-            gnutls_dh_params_deinit(*dh_params);
-            *dh_params = NULL;
-            error_setg(errp, "Unable to generate DH parameters: %s",
-                       gnutls_strerror(ret));
-            return -1;
-        }
-    } else {
+    if (filename != NULL) {
         GError *gerr = NULL;
         gchar *contents;
         gsize len;
@@ -67,6 +53,10 @@
             g_error_free(gerr);
             return -1;
         }
+        warn_report_once("Use of an external DH parameters file '%s' is "
+                         "deprecated and will be removed in a future release",
+                         filename);
+
         data.data = (unsigned char *)contents;
         data.size = len;
         ret = gnutls_dh_params_init(dh_params);
@@ -87,12 +77,22 @@
                        filename, gnutls_strerror(ret));
             return -1;
         }
+    } else {
+        *dh_params = NULL;
     }
 
     return 0;
 }
 
 
+char *
+qcrypto_tls_creds_build_path(QCryptoTLSCreds *creds,
+                             const char *filename)
+{
+    return g_strdup_printf("%s/%s", creds->dir, filename);
+}
+
+
 int
 qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds,
                            const char *filename,
@@ -100,21 +100,11 @@
                            char **cred,
                            Error **errp)
 {
-    struct stat sb;
     int ret = -1;
 
-    if (!creds->dir) {
-        if (required) {
-            error_setg(errp, "Missing 'dir' property value");
-            return -1;
-        } else {
-            return 0;
-        }
-    }
+    *cred = qcrypto_tls_creds_build_path(creds, filename);
 
-    *cred = g_strdup_printf("%s/%s", creds->dir, filename);
-
-    if (stat(*cred, &sb) < 0) {
+    if (access(*cred, R_OK) < 0) {
         if (errno == ENOENT && !required) {
             ret = 0;
         } else {
@@ -256,6 +246,9 @@
 {
     QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
 
+#ifdef CONFIG_GNUTLS
+    qcrypto_tls_creds_box_unref(creds->box);
+#endif
     g_free(creds->dir);
     g_free(creds->priority);
 }
@@ -272,6 +265,36 @@
     return true;
 }
 
+
+char *qcrypto_tls_creds_get_priority(QCryptoTLSCreds *creds)
+{
+    QCryptoTLSCredsClass *tcc = QCRYPTO_TLS_CREDS_GET_CLASS(creds);
+    const char *priorityBase =
+        creds->priority ? creds->priority : CONFIG_TLS_PRIORITY;
+
+    if (tcc->prioritySuffix) {
+        return g_strdup_printf("%s:%s", priorityBase, tcc->prioritySuffix);
+    } else {
+        return g_strdup(priorityBase);
+    }
+}
+
+
+bool qcrypto_tls_creds_reload(QCryptoTLSCreds *creds,
+                              Error **errp)
+{
+    QCryptoTLSCredsClass *credscls = QCRYPTO_TLS_CREDS_GET_CLASS(creds);
+
+    if (credscls->reload) {
+        return credscls->reload(creds, errp);
+    }
+
+    error_setg(errp, "%s does not support reloading credentials",
+               object_get_typename(OBJECT(creds)));
+    return false;
+}
+
+
 static const TypeInfo qcrypto_tls_creds_info = {
     .parent = TYPE_OBJECT,
     .name = TYPE_QCRYPTO_TLS_CREDS,
diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c
index 44af9e6..1551382 100644
--- a/crypto/tlscredsanon.c
+++ b/crypto/tlscredsanon.c
@@ -27,15 +27,19 @@
 #include "trace.h"
 
 
+struct QCryptoTLSCredsAnon {
+    QCryptoTLSCreds parent_obj;
+};
+
 #ifdef CONFIG_GNUTLS
 
 #include <gnutls/gnutls.h>
 
-
 static int
 qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds,
                             Error **errp)
 {
+    g_autoptr(QCryptoTLSCredsBox) box = NULL;
     g_autofree char *dhparams = NULL;
     int ret;
 
@@ -43,13 +47,16 @@
             creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>");
 
     if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
-        if (qcrypto_tls_creds_get_path(&creds->parent_obj,
+        box = qcrypto_tls_creds_box_new_server(GNUTLS_CRD_ANON);
+
+        if (creds->parent_obj.dir &&
+            qcrypto_tls_creds_get_path(&creds->parent_obj,
                                        QCRYPTO_TLS_CREDS_DH_PARAMS,
                                        false, &dhparams, errp) < 0) {
             return -1;
         }
 
-        ret = gnutls_anon_allocate_server_credentials(&creds->data.server);
+        ret = gnutls_anon_allocate_server_credentials(&box->data.anonserver);
         if (ret < 0) {
             error_setg(errp, "Cannot allocate credentials: %s",
                        gnutls_strerror(ret));
@@ -57,46 +64,28 @@
         }
 
         if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams,
-                                                 &creds->parent_obj.dh_params,
-                                                 errp) < 0) {
+                                                 &box->dh_params, errp) < 0) {
             return -1;
         }
 
-        gnutls_anon_set_server_dh_params(creds->data.server,
-                                         creds->parent_obj.dh_params);
+        if (box->dh_params) {
+            gnutls_anon_set_server_dh_params(box->data.anonserver,
+                                             box->dh_params);
+        }
     } else {
-        ret = gnutls_anon_allocate_client_credentials(&creds->data.client);
+        ret = gnutls_anon_allocate_client_credentials(&box->data.anonclient);
         if (ret < 0) {
             error_setg(errp, "Cannot allocate credentials: %s",
                        gnutls_strerror(ret));
             return -1;
         }
     }
+    creds->parent_obj.box = g_steal_pointer(&box);
 
     return 0;
 }
 
 
-static void
-qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds)
-{
-    if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
-        if (creds->data.client) {
-            gnutls_anon_free_client_credentials(creds->data.client);
-            creds->data.client = NULL;
-        }
-    } else {
-        if (creds->data.server) {
-            gnutls_anon_free_server_credentials(creds->data.server);
-            creds->data.server = NULL;
-        }
-    }
-    if (creds->parent_obj.dh_params) {
-        gnutls_dh_params_deinit(creds->parent_obj.dh_params);
-        creds->parent_obj.dh_params = NULL;
-    }
-}
-
 #else /* ! CONFIG_GNUTLS */
 
 
@@ -108,13 +97,6 @@
 }
 
 
-static void
-qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED)
-{
-    /* nada */
-}
-
-
 #endif /* ! CONFIG_GNUTLS */
 
 
@@ -128,20 +110,13 @@
 
 
 static void
-qcrypto_tls_creds_anon_finalize(Object *obj)
-{
-    QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj);
-
-    qcrypto_tls_creds_anon_unload(creds);
-}
-
-
-static void
 qcrypto_tls_creds_anon_class_init(ObjectClass *oc, const void *data)
 {
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    QCryptoTLSCredsClass *tcc = QCRYPTO_TLS_CREDS_CLASS(oc);
 
     ucc->complete = qcrypto_tls_creds_anon_complete;
+    tcc->prioritySuffix = "+ANON-DH";
 }
 
 
@@ -149,7 +124,6 @@
     .parent = TYPE_QCRYPTO_TLS_CREDS,
     .name = TYPE_QCRYPTO_TLS_CREDS_ANON,
     .instance_size = sizeof(QCryptoTLSCredsAnon),
-    .instance_finalize = qcrypto_tls_creds_anon_finalize,
     .class_size = sizeof(QCryptoTLSCredsAnonClass),
     .class_init = qcrypto_tls_creds_anon_class_init,
     .interfaces = (const InterfaceInfo[]) {
diff --git a/crypto/tlscredsbox.c b/crypto/tlscredsbox.c
new file mode 100644
index 0000000..b8d9846
--- /dev/null
+++ b/crypto/tlscredsbox.c
@@ -0,0 +1,101 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QEMU crypto TLS credential support
+ *
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#include "qemu/osdep.h"
+#include "crypto/tlscredsbox.h"
+#include "qemu/atomic.h"
+
+
+static QCryptoTLSCredsBox *
+qcrypto_tls_creds_box_new_impl(int type, bool server)
+{
+    QCryptoTLSCredsBox *credsbox = g_new0(QCryptoTLSCredsBox, 1);
+    credsbox->ref = 1;
+    credsbox->server = server;
+    credsbox->type = type;
+    return credsbox;
+}
+
+
+QCryptoTLSCredsBox *
+qcrypto_tls_creds_box_new_server(int type)
+{
+    return qcrypto_tls_creds_box_new_impl(type, true);
+}
+
+
+QCryptoTLSCredsBox *
+qcrypto_tls_creds_box_new_client(int type)
+{
+    return qcrypto_tls_creds_box_new_impl(type, false);
+}
+
+static void qcrypto_tls_creds_box_free(QCryptoTLSCredsBox *credsbox)
+{
+    switch (credsbox->type) {
+    case GNUTLS_CRD_CERTIFICATE:
+        if (credsbox->data.cert) {
+            gnutls_certificate_free_credentials(credsbox->data.cert);
+        }
+        break;
+    case GNUTLS_CRD_PSK:
+        if (credsbox->server) {
+            if (credsbox->data.pskserver) {
+                gnutls_psk_free_server_credentials(credsbox->data.pskserver);
+            }
+        } else {
+            if (credsbox->data.pskclient) {
+                gnutls_psk_free_client_credentials(credsbox->data.pskclient);
+            }
+        }
+        break;
+    case GNUTLS_CRD_ANON:
+        if (credsbox->server) {
+            if (credsbox->data.anonserver) {
+                gnutls_anon_free_server_credentials(credsbox->data.anonserver);
+            }
+        } else {
+            if (credsbox->data.anonclient) {
+                gnutls_anon_free_client_credentials(credsbox->data.anonclient);
+            }
+        }
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    if (credsbox->dh_params) {
+        gnutls_dh_params_deinit(credsbox->dh_params);
+    }
+
+    g_free(credsbox);
+}
+
+
+void qcrypto_tls_creds_box_ref(QCryptoTLSCredsBox *credsbox)
+{
+    uint32_t ref = qatomic_fetch_inc(&credsbox->ref);
+    /* Assert waaay before the integer overflows */
+    g_assert(ref < INT_MAX);
+}
+
+
+void qcrypto_tls_creds_box_unref(QCryptoTLSCredsBox *credsbox)
+{
+    if (!credsbox) {
+        return;
+    }
+
+    g_assert(credsbox->ref > 0);
+
+    if (qatomic_fetch_dec(&credsbox->ref) == 1) {
+        qcrypto_tls_creds_box_free(credsbox);
+    }
+
+}
+
diff --git a/crypto/tlscredsbox.h b/crypto/tlscredsbox.h
new file mode 100644
index 0000000..eeb54d1
--- /dev/null
+++ b/crypto/tlscredsbox.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QEMU crypto TLS credential support
+ *
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#ifndef QCRYPTO_TLSCREDS_BOX_H
+#define QCRYPTO_TLSCREDS_BOX_H
+
+#include "qom/object.h"
+
+#ifdef CONFIG_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
+typedef struct QCryptoTLSCredsBox QCryptoTLSCredsBox;
+
+struct QCryptoTLSCredsBox {
+    uint32_t ref;
+    bool server;
+    int type;
+    union {
+        void *any;
+#ifdef CONFIG_GNUTLS
+        /*
+         * All of these gnutls_XXXX_credentials_t types are
+         * pointers, hence matching the 'any' field above
+         */
+        gnutls_anon_server_credentials_t anonserver;
+        gnutls_anon_client_credentials_t anonclient;
+        gnutls_psk_server_credentials_t pskserver;
+        gnutls_psk_client_credentials_t pskclient;
+        gnutls_certificate_credentials_t cert;
+#endif
+    } data;
+#ifdef CONFIG_GNUTLS
+    gnutls_dh_params_t dh_params;
+#endif
+};
+
+QCryptoTLSCredsBox *qcrypto_tls_creds_box_new_server(int type);
+QCryptoTLSCredsBox *qcrypto_tls_creds_box_new_client(int type);
+void qcrypto_tls_creds_box_ref(QCryptoTLSCredsBox *credsbox);
+void qcrypto_tls_creds_box_unref(QCryptoTLSCredsBox *credsbox);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QCryptoTLSCredsBox, qcrypto_tls_creds_box_unref);
+
+#endif /* QCRYPTO_TLSCREDS_BOX_H */
diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h
index df9815a..8f2d096 100644
--- a/crypto/tlscredspriv.h
+++ b/crypto/tlscredspriv.h
@@ -22,6 +22,7 @@
 #define QCRYPTO_TLSCREDSPRIV_H
 
 #include "crypto/tlscreds.h"
+#include "crypto/tlscredsbox.h"
 
 #ifdef CONFIG_GNUTLS
 #include <gnutls/gnutls.h>
@@ -31,45 +32,16 @@
     Object parent_obj;
     char *dir;
     QCryptoTLSCredsEndpoint endpoint;
-#ifdef CONFIG_GNUTLS
-    gnutls_dh_params_t dh_params;
-#endif
     bool verifyPeer;
     char *priority;
-};
-
-struct QCryptoTLSCredsAnon {
-    QCryptoTLSCreds parent_obj;
-#ifdef CONFIG_GNUTLS
-    union {
-        gnutls_anon_server_credentials_t server;
-        gnutls_anon_client_credentials_t client;
-    } data;
-#endif
-};
-
-struct QCryptoTLSCredsPSK {
-    QCryptoTLSCreds parent_obj;
-    char *username;
-#ifdef CONFIG_GNUTLS
-    union {
-        gnutls_psk_server_credentials_t server;
-        gnutls_psk_client_credentials_t client;
-    } data;
-#endif
-};
-
-struct QCryptoTLSCredsX509 {
-    QCryptoTLSCreds parent_obj;
-#ifdef CONFIG_GNUTLS
-    gnutls_certificate_credentials_t data;
-#endif
-    bool sanityCheck;
-    char *passwordid;
+    QCryptoTLSCredsBox *box;
 };
 
 #ifdef CONFIG_GNUTLS
 
+char *qcrypto_tls_creds_build_path(QCryptoTLSCreds *creds,
+                                   const char *filename);
+
 int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds,
                                const char *filename,
                                bool required,
diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c
index 5b68a6b..e1b1e1a 100644
--- a/crypto/tlscredspsk.c
+++ b/crypto/tlscredspsk.c
@@ -27,6 +27,11 @@
 #include "trace.h"
 
 
+struct QCryptoTLSCredsPSK {
+    QCryptoTLSCreds parent_obj;
+    char *username;
+};
+
 #ifdef CONFIG_GNUTLS
 
 #include <gnutls/gnutls.h>
@@ -71,6 +76,7 @@
 qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds,
                            Error **errp)
 {
+    g_autoptr(QCryptoTLSCredsBox) box = NULL;
     g_autofree char *pskfile = NULL;
     g_autofree char *dhparams = NULL;
     const char *username;
@@ -81,7 +87,14 @@
     trace_qcrypto_tls_creds_psk_load(creds,
             creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>");
 
+    if (!creds->parent_obj.dir) {
+        error_setg(errp, "Missing 'dir' property value");
+        goto cleanup;
+    }
+
     if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        box = qcrypto_tls_creds_box_new_server(GNUTLS_CRD_PSK);
+
         if (creds->username) {
             error_setg(errp, "username should not be set when endpoint=server");
             goto cleanup;
@@ -96,7 +109,7 @@
             goto cleanup;
         }
 
-        ret = gnutls_psk_allocate_server_credentials(&creds->data.server);
+        ret = gnutls_psk_allocate_server_credentials(&box->data.pskserver);
         if (ret < 0) {
             error_setg(errp, "Cannot allocate credentials: %s",
                        gnutls_strerror(ret));
@@ -104,20 +117,25 @@
         }
 
         if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams,
-                                                 &creds->parent_obj.dh_params,
+                                                 &box->dh_params,
                                                  errp) < 0) {
             goto cleanup;
         }
 
-        ret = gnutls_psk_set_server_credentials_file(creds->data.server, pskfile);
+        ret = gnutls_psk_set_server_credentials_file(box->data.pskserver,
+                                                     pskfile);
         if (ret < 0) {
             error_setg(errp, "Cannot set PSK server credentials: %s",
                        gnutls_strerror(ret));
             goto cleanup;
         }
-        gnutls_psk_set_server_dh_params(creds->data.server,
-                                        creds->parent_obj.dh_params);
+        if (box->dh_params) {
+            gnutls_psk_set_server_dh_params(box->data.pskserver,
+                                            box->dh_params);
+        }
     } else {
+        box = qcrypto_tls_creds_box_new_client(GNUTLS_CRD_PSK);
+
         if (qcrypto_tls_creds_get_path(&creds->parent_obj,
                                        QCRYPTO_TLS_CREDS_PSKFILE,
                                        true, &pskfile, errp) < 0) {
@@ -133,14 +151,14 @@
             goto cleanup;
         }
 
-        ret = gnutls_psk_allocate_client_credentials(&creds->data.client);
+        ret = gnutls_psk_allocate_client_credentials(&box->data.pskclient);
         if (ret < 0) {
             error_setg(errp, "Cannot allocate credentials: %s",
                        gnutls_strerror(ret));
             goto cleanup;
         }
 
-        ret = gnutls_psk_set_client_credentials(creds->data.client,
+        ret = gnutls_psk_set_client_credentials(box->data.pskclient,
                                                 username, &key, GNUTLS_PSK_KEY_HEX);
         if (ret < 0) {
             error_setg(errp, "Cannot set PSK client credentials: %s",
@@ -148,6 +166,7 @@
             goto cleanup;
         }
     }
+    creds->parent_obj.box = g_steal_pointer(&box);
 
     rv = 0;
  cleanup:
@@ -155,27 +174,6 @@
     return rv;
 }
 
-
-static void
-qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds)
-{
-    if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
-        if (creds->data.client) {
-            gnutls_psk_free_client_credentials(creds->data.client);
-            creds->data.client = NULL;
-        }
-    } else {
-        if (creds->data.server) {
-            gnutls_psk_free_server_credentials(creds->data.server);
-            creds->data.server = NULL;
-        }
-    }
-    if (creds->parent_obj.dh_params) {
-        gnutls_dh_params_deinit(creds->parent_obj.dh_params);
-        creds->parent_obj.dh_params = NULL;
-    }
-}
-
 #else /* ! CONFIG_GNUTLS */
 
 
@@ -187,13 +185,6 @@
 }
 
 
-static void
-qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED)
-{
-    /* nada */
-}
-
-
 #endif /* ! CONFIG_GNUTLS */
 
 
@@ -211,7 +202,6 @@
 {
     QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj);
 
-    qcrypto_tls_creds_psk_unload(creds);
     g_free(creds->username);
 }
 
@@ -239,8 +229,10 @@
 qcrypto_tls_creds_psk_class_init(ObjectClass *oc, const void *data)
 {
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    QCryptoTLSCredsClass *tcc = QCRYPTO_TLS_CREDS_CLASS(oc);
 
     ucc->complete = qcrypto_tls_creds_psk_complete;
+    tcc->prioritySuffix = "+ECDHE-PSK:+DHE-PSK:+PSK";
 
     object_class_property_add_str(oc, "username",
                                   qcrypto_tls_creds_psk_prop_get_username,
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c
index db2b74b..b8d0cd2 100644
--- a/crypto/tlscredsx509.c
+++ b/crypto/tlscredsx509.c
@@ -28,12 +28,81 @@
 #include "trace.h"
 
 
+struct QCryptoTLSCredsX509 {
+    QCryptoTLSCreds parent_obj;
+    bool sanityCheck;
+    char *passwordid;
+};
+
 #ifdef CONFIG_GNUTLS
 
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
 
+typedef struct QCryptoTLSCredsX509IdentFiles QCryptoTLSCredsX509IdentFiles;
+struct QCryptoTLSCredsX509IdentFiles {
+    char *certpath;
+    char *keypath;
+    gnutls_x509_crt_t *certs;
+    unsigned int ncerts;
+    gnutls_x509_privkey_t key;
+};
+
+typedef struct QCryptoTLSCredsX509Files QCryptoTLSCredsX509Files;
+struct QCryptoTLSCredsX509Files {
+    char *cacertpath;
+    gnutls_x509_crt_t *cacerts;
+    unsigned int ncacerts;
+
+    QCryptoTLSCredsX509IdentFiles **identities;
+    size_t nidentities;
+};
+
+static QCryptoTLSCredsX509Files *
+qcrypto_tls_creds_x509_files_new(void)
+{
+    return g_new0(QCryptoTLSCredsX509Files, 1);
+}
+
+
+static void
+qcrypto_tls_creds_x509_ident_files_free(QCryptoTLSCredsX509IdentFiles *files)
+{
+    size_t i;
+    for (i = 0; i < files->ncerts; i++) {
+        gnutls_x509_crt_deinit(files->certs[i]);
+    }
+    gnutls_x509_privkey_deinit(files->key);
+    g_free(files->certs);
+    g_free(files->certpath);
+    g_free(files->keypath);
+    g_free(files);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QCryptoTLSCredsX509IdentFiles,
+                              qcrypto_tls_creds_x509_ident_files_free);
+
+
+static void
+qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files)
+{
+    size_t i;
+    for (i = 0; i < files->ncacerts; i++) {
+        gnutls_x509_crt_deinit(files->cacerts[i]);
+    }
+    g_free(files->cacerts);
+    g_free(files->cacertpath);
+    for (i = 0; i < files->nidentities; i++) {
+        qcrypto_tls_creds_x509_ident_files_free(files->identities[i]);
+    }
+    g_free(files->identities);
+    g_free(files);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QCryptoTLSCredsX509Files,
+                              qcrypto_tls_creds_x509_files_free);
+
 static int
 qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert,
                                    const char *certFile,
@@ -309,13 +378,10 @@
 
 static int
 qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds,
+                                        QCryptoTLSCredsX509Files *files,
                                         gnutls_x509_crt_t *certs,
                                         unsigned int ncerts,
-                                        gnutls_x509_crt_t *cacerts,
-                                        unsigned int ncacerts,
-                                        const char *cacertFile,
                                         bool isServer,
-                                        bool isCA,
                                         Error **errp)
 {
     gnutls_x509_crt_t cert_to_check = certs[ncerts - 1];
@@ -355,13 +421,13 @@
              * reached the root of trust.
              */
             return qcrypto_tls_creds_check_cert(
-                creds, cert_to_check, cacertFile,
-                isServer, isCA, errp);
+                creds, cert_to_check, files->cacertpath,
+                isServer, true, errp);
         }
-        for (int i = 0; i < ncacerts; i++) {
+        for (int i = 0; i < files->ncacerts; i++) {
             if (gnutls_x509_crt_check_issuer(cert_to_check,
-                                             cacerts[i])) {
-                cert_issuer = cacerts[i];
+                                             files->cacerts[i])) {
+                cert_issuer = files->cacerts[i];
                 break;
             }
         }
@@ -369,8 +435,8 @@
             break;
         }
 
-        if (qcrypto_tls_creds_check_cert(creds, cert_issuer, cacertFile,
-                                         isServer, isCA, errp) < 0) {
+        if (qcrypto_tls_creds_check_cert(creds, cert_issuer, files->cacertpath,
+                                         isServer, true, errp) < 0) {
             return -1;
         }
 
@@ -389,19 +455,17 @@
 }
 
 static int
-qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t *certs,
+qcrypto_tls_creds_check_cert_pair(QCryptoTLSCredsX509Files *files,
+                                  gnutls_x509_crt_t *certs,
                                   size_t ncerts,
                                   const char *certFile,
-                                  gnutls_x509_crt_t *cacerts,
-                                  size_t ncacerts,
-                                  const char *cacertFile,
                                   bool isServer,
                                   Error **errp)
 {
     unsigned int status;
 
     if (gnutls_x509_crt_list_verify(certs, ncerts,
-                                    cacerts, ncacerts,
+                                    files->cacerts, files->ncacerts,
                                     NULL, 0,
                                     0, &status) < 0) {
         error_setg(errp, isServer ?
@@ -409,7 +473,7 @@
                    "CA certificate %s" :
                    "Unable to verify client certificate %s against "
                    "CA certificate %s",
-                   certFile, cacertFile);
+                   certFile, files->cacertpath);
         return -1;
     }
 
@@ -434,7 +498,7 @@
 
         error_setg(errp,
                    "Our own certificate %s failed validation against %s: %s",
-                   certFile, cacertFile, reason);
+                   certFile, files->cacertpath, reason);
         return -1;
     }
 
@@ -447,14 +511,13 @@
                                  const char *certFile,
                                  gnutls_x509_crt_t **certs,
                                  unsigned int *ncerts,
-                                 bool isServer,
-                                 bool isCA,
                                  Error **errp)
 {
     gnutls_datum_t data;
     g_autofree char *buf = NULL;
     gsize buflen;
     GError *gerr = NULL;
+    int ret;
 
     *ncerts = 0;
     trace_qcrypto_tls_creds_x509_load_cert_list(creds, certFile);
@@ -469,13 +532,94 @@
     data.data = (unsigned char *)buf;
     data.size = strlen(buf);
 
-    if (gnutls_x509_crt_list_import2(certs, ncerts, &data,
-                                     GNUTLS_X509_FMT_PEM, 0) < 0) {
-        error_setg(errp,
-                   isCA ? "Unable to import CA certificate list %s" :
-                   (isServer ? "Unable to import server certificate %s" :
-                    "Unable to import client certificate %s"),
-                   certFile);
+    ret = gnutls_x509_crt_list_import2(certs, ncerts, &data,
+                                       GNUTLS_X509_FMT_PEM, 0);
+    if (ret < 0) {
+        error_setg(errp, "Unable to import certificate %s: %s",
+                   certFile, gnutls_strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+qcrypto_tls_creds_load_privkey(QCryptoTLSCredsX509 *creds,
+                               const char *keyFile,
+                               gnutls_x509_privkey_t *key,
+                               Error **errp)
+{
+    gnutls_datum_t data;
+    g_autofree char *buf = NULL;
+    g_autofree char *password = NULL;
+    gsize buflen;
+    GError *gerr = NULL;
+    int ret;
+
+    ret = gnutls_x509_privkey_init(key);
+    if (ret < 0) {
+        error_setg(errp, "Unable to initialize private key: %s",
+                   gnutls_strerror(ret));
+        return -1;
+    }
+
+    if (!g_file_get_contents(keyFile, &buf, &buflen, &gerr)) {
+        error_setg(errp, "Cannot load private key %s: %s",
+                   keyFile, gerr->message);
+        g_error_free(gerr);
+        return -1;
+    }
+
+    data.data = (unsigned char *)buf;
+    data.size = strlen(buf);
+
+    if (creds->passwordid) {
+        password = qcrypto_secret_lookup_as_utf8(creds->passwordid,
+                                                 errp);
+        if (!password) {
+            return -1;
+        }
+    }
+
+    if (gnutls_x509_privkey_import2(*key, &data,
+                                    GNUTLS_X509_FMT_PEM,
+                                    password, 0) < 0) {
+        error_setg(errp, "Unable to import private key %s", keyFile);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+qcrypto_tls_creds_x509_sanity_check_identity(QCryptoTLSCredsX509 *creds,
+                                             QCryptoTLSCredsX509Files *files,
+                                             QCryptoTLSCredsX509IdentFiles *ifiles,
+                                             bool isServer,
+                                             Error **errp)
+{
+    size_t i;
+
+    for (i = 0; i < ifiles->ncerts; i++) {
+        if (qcrypto_tls_creds_check_cert(creds,
+                                         ifiles->certs[i], ifiles->certpath,
+                                         isServer, i != 0, errp) < 0) {
+            return -1;
+        }
+    }
+
+    if (ifiles->ncerts &&
+        qcrypto_tls_creds_check_authority_chain(creds, files,
+                                                ifiles->certs, ifiles->ncerts,
+                                                isServer, errp) < 0) {
+        return -1;
+    }
+
+    if (ifiles->ncerts &&
+        qcrypto_tls_creds_check_cert_pair(files, ifiles->certs, ifiles->ncerts,
+                                          ifiles->certpath, isServer, errp) < 0) {
         return -1;
     }
 
@@ -485,78 +629,191 @@
 
 static int
 qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds,
+                                    QCryptoTLSCredsX509Files *files,
                                     bool isServer,
-                                    const char *cacertFile,
-                                    const char *certFile,
                                     Error **errp)
 {
-    gnutls_x509_crt_t *certs = NULL;
-    unsigned int ncerts = 0;
-    gnutls_x509_crt_t *cacerts = NULL;
-    unsigned int ncacerts = 0;
     size_t i;
-    int ret = -1;
-
-    if (certFile &&
-        access(certFile, R_OK) == 0) {
-        if (qcrypto_tls_creds_load_cert_list(creds,
-                                             certFile,
-                                             &certs,
-                                             &ncerts,
-                                             isServer,
-                                             false,
-                                             errp) < 0) {
-            goto cleanup;
+    for (i = 0; i < files->nidentities; i++) {
+        if (qcrypto_tls_creds_x509_sanity_check_identity(creds,
+                                                         files,
+                                                         files->identities[i],
+                                                         isServer,
+                                                         errp) < 0) {
+            return -1;
         }
     }
-    if (access(cacertFile, R_OK) == 0) {
-        if (qcrypto_tls_creds_load_cert_list(creds,
-                                             cacertFile,
-                                             &cacerts,
-                                             &ncacerts,
-                                             isServer,
-                                             true,
-                                             errp) < 0) {
-            goto cleanup;
+    return 0;
+}
+
+
+static int
+qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds,
+                               QCryptoTLSCredsBox *box,
+                               QCryptoTLSCredsX509Files *files,
+                               bool isServer,
+                               Error **errp)
+{
+    int ret;
+
+    if (qcrypto_tls_creds_get_path(&creds->parent_obj,
+                                   QCRYPTO_TLS_CREDS_X509_CA_CERT,
+                                   true, &files->cacertpath, errp) < 0) {
+        return -1;
+    }
+
+    if (qcrypto_tls_creds_load_cert_list(creds,
+                                         files->cacertpath,
+                                         &files->cacerts,
+                                         &files->ncacerts,
+                                         errp) < 0) {
+        return -1;
+    }
+
+    ret = gnutls_certificate_set_x509_trust(box->data.cert,
+                                            files->cacerts, files->ncacerts);
+    if (ret < 0) {
+        error_setg(errp, "Cannot set CA certificate '%s': %s",
+                   files->cacertpath, gnutls_strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static QCryptoTLSCredsX509IdentFiles *
+qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds,
+                                     QCryptoTLSCredsBox *box,
+                                     const char *certbase,
+                                     const char *keybase,
+                                     Error **errp)
+{
+    g_autoptr(QCryptoTLSCredsX509IdentFiles) files =
+        g_new0(QCryptoTLSCredsX509IdentFiles, 1);
+    int ret;
+
+    if (qcrypto_tls_creds_get_path(&creds->parent_obj, certbase,
+                                   false, &files->certpath, errp) < 0 ||
+        qcrypto_tls_creds_get_path(&creds->parent_obj, keybase,
+                                   false, &files->keypath, errp) < 0) {
+        return NULL;
+    }
+
+    if (!files->certpath &&
+        !files->keypath) {
+        return NULL;
+    }
+    if (files->certpath && !files->keypath) {
+        g_autofree char *keypath =
+            qcrypto_tls_creds_build_path(&creds->parent_obj, keybase);
+        error_setg(errp, "Cert '%s' without corresponding key '%s'",
+                   files->certpath, keypath);
+        return NULL;
+    }
+    if (!files->certpath && files->keypath) {
+        g_autofree char *certpath =
+            qcrypto_tls_creds_build_path(&creds->parent_obj, certbase);
+        error_setg(errp, "Key '%s' without corresponding cert '%s'",
+                   files->keypath, certpath);
+        return NULL;
+    }
+
+    if (qcrypto_tls_creds_load_cert_list(creds,
+                                         files->certpath,
+                                         &files->certs,
+                                         &files->ncerts,
+                                         errp) < 0) {
+        return NULL;
+    }
+
+    if (qcrypto_tls_creds_load_privkey(creds,
+                                       files->keypath,
+                                       &files->key,
+                                       errp) < 0) {
+        return NULL;
+    }
+
+    ret = gnutls_certificate_set_x509_key(box->data.cert,
+                                          files->certs,
+                                          files->ncerts,
+                                          files->key);
+    if (ret < 0) {
+        error_setg(errp, "Cannot set certificate '%s' & key '%s': %s",
+                   files->certpath, files->keypath, gnutls_strerror(ret));
+        return NULL;
+    }
+    return g_steal_pointer(&files);
+}
+
+
+static int
+qcrypto_tls_creds_x509_load_identities(QCryptoTLSCredsX509 *creds,
+                                       QCryptoTLSCredsBox *box,
+                                       QCryptoTLSCredsX509Files *files,
+                                       bool isServer,
+                                       Error **errp)
+{
+    ERRP_GUARD();
+    QCryptoTLSCredsX509IdentFiles *ifiles;
+    size_t i;
+
+    ifiles = qcrypto_tls_creds_x509_load_identity(
+        creds, box,
+        isServer ?
+        QCRYPTO_TLS_CREDS_X509_SERVER_CERT :
+        QCRYPTO_TLS_CREDS_X509_CLIENT_CERT,
+        isServer ?
+        QCRYPTO_TLS_CREDS_X509_SERVER_KEY :
+        QCRYPTO_TLS_CREDS_X509_CLIENT_KEY,
+        errp);
+    if (!ifiles && *errp) {
+        return -1;
+    }
+
+    if (ifiles) {
+        files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *,
+                                    files->identities,
+                                    files->nidentities + 1);
+        files->identities[files->nidentities++] = ifiles;
+    }
+
+    for (i = 0; i < QCRYPTO_TLS_CREDS_X509_IDENTITY_MAX; i++) {
+        g_autofree char *cert = g_strdup_printf(
+            isServer ?
+            QCRYPTO_TLS_CREDS_X509_SERVER_CERT_N :
+            QCRYPTO_TLS_CREDS_X509_CLIENT_CERT_N, i);
+        g_autofree char *key = g_strdup_printf(
+            isServer ?
+            QCRYPTO_TLS_CREDS_X509_SERVER_KEY_N :
+            QCRYPTO_TLS_CREDS_X509_CLIENT_KEY_N, i);
+
+        ifiles = qcrypto_tls_creds_x509_load_identity(creds, box,
+                                                      cert, key, errp);
+        if (!ifiles && *errp) {
+            return -1;
         }
-    }
-
-    for (i = 0; i < ncerts; i++) {
-        if (qcrypto_tls_creds_check_cert(creds,
-                                         certs[i], certFile,
-                                         isServer, i != 0, errp) < 0) {
-            goto cleanup;
+        if (!ifiles) {
+            break;
         }
+
+        files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *,
+                                    files->identities,
+                                    files->nidentities + 1);
+        files->identities[files->nidentities++] = ifiles;
     }
 
-    if (ncerts &&
-        qcrypto_tls_creds_check_authority_chain(creds,
-                                                certs, ncerts,
-                                                cacerts, ncacerts,
-                                                cacertFile, isServer,
-                                                true, errp) < 0) {
-        goto cleanup;
+    if (files->nidentities == 0 && isServer) {
+        g_autofree char *certpath = qcrypto_tls_creds_build_path(
+            &creds->parent_obj, QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+        g_autofree char *keypath = qcrypto_tls_creds_build_path(
+            &creds->parent_obj, QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+        error_setg(errp, "Missing server cert '%s' & key '%s'",
+                   certpath, keypath);
+        return -1;
     }
 
-    if (ncerts && ncacerts &&
-        qcrypto_tls_creds_check_cert_pair(certs, ncerts, certFile,
-                                          cacerts, ncacerts, cacertFile,
-                                          isServer, errp) < 0) {
-        goto cleanup;
-    }
-
-    ret = 0;
-
- cleanup:
-    for (i = 0; i < ncerts; i++) {
-        gnutls_x509_crt_deinit(certs[i]);
-    }
-    for (i = 0; i < ncacerts; i++) {
-        gnutls_x509_crt_deinit(cacerts[i]);
-    }
-    g_free(cacerts);
-
-    return ret;
+    return 0;
 }
 
 
@@ -564,134 +821,85 @@
 qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
                             Error **errp)
 {
-    char *cacert = NULL, *cacrl = NULL, *cert = NULL,
-        *key = NULL, *dhparams = NULL;
+    g_autoptr(QCryptoTLSCredsBox) box = NULL;
+    g_autoptr(QCryptoTLSCredsX509Files) files = NULL;
+    g_autofree char *cacrl = NULL;
+    g_autofree char *dhparams = NULL;
+    bool isServer = (creds->parent_obj.endpoint ==
+                     QCRYPTO_TLS_CREDS_ENDPOINT_SERVER);
     int ret;
-    int rv = -1;
 
-    trace_qcrypto_tls_creds_x509_load(creds,
-            creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>");
+    if (!creds->parent_obj.dir) {
+        error_setg(errp, "Missing 'dir' property value");
+        return -1;
+    }
+
+    trace_qcrypto_tls_creds_x509_load(creds, creds->parent_obj.dir);
 
     if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        box = qcrypto_tls_creds_box_new_server(GNUTLS_CRD_CERTIFICATE);
+    } else {
+        box = qcrypto_tls_creds_box_new_client(GNUTLS_CRD_CERTIFICATE);
+    }
+
+    ret = gnutls_certificate_allocate_credentials(&box->data.cert);
+    if (ret < 0) {
+        error_setg(errp, "Cannot allocate credentials: '%s'",
+                   gnutls_strerror(ret));
+        return -1;
+    }
+
+    files = qcrypto_tls_creds_x509_files_new();
+
+    if (qcrypto_tls_creds_x509_load_ca(creds, box, files, isServer, errp) < 0) {
+        return -1;
+    }
+
+    if (qcrypto_tls_creds_x509_load_identities(creds, box, files,
+                                               isServer, errp) < 0) {
+        return -1;
+    }
+
+    if (isServer) {
         if (qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_CA_CERT,
-                                       true, &cacert, errp) < 0 ||
-            qcrypto_tls_creds_get_path(&creds->parent_obj,
                                        QCRYPTO_TLS_CREDS_X509_CA_CRL,
                                        false, &cacrl, errp) < 0 ||
             qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_SERVER_CERT,
-                                       true, &cert, errp) < 0 ||
-            qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_SERVER_KEY,
-                                       true, &key, errp) < 0 ||
-            qcrypto_tls_creds_get_path(&creds->parent_obj,
                                        QCRYPTO_TLS_CREDS_DH_PARAMS,
                                        false, &dhparams, errp) < 0) {
-            goto cleanup;
-        }
-    } else {
-        if (qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_CA_CERT,
-                                       true, &cacert, errp) < 0 ||
-            qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_CLIENT_CERT,
-                                       false, &cert, errp) < 0 ||
-            qcrypto_tls_creds_get_path(&creds->parent_obj,
-                                       QCRYPTO_TLS_CREDS_X509_CLIENT_KEY,
-                                       false, &key, errp) < 0) {
-            goto cleanup;
+            return -1;
         }
     }
 
     if (creds->sanityCheck &&
-        qcrypto_tls_creds_x509_sanity_check(creds,
-            creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
-            cacert, cert, errp) < 0) {
-        goto cleanup;
-    }
-
-    ret = gnutls_certificate_allocate_credentials(&creds->data);
-    if (ret < 0) {
-        error_setg(errp, "Cannot allocate credentials: '%s'",
-                   gnutls_strerror(ret));
-        goto cleanup;
-    }
-
-    ret = gnutls_certificate_set_x509_trust_file(creds->data,
-                                                 cacert,
-                                                 GNUTLS_X509_FMT_PEM);
-    if (ret < 0) {
-        error_setg(errp, "Cannot load CA certificate '%s': %s",
-                   cacert, gnutls_strerror(ret));
-        goto cleanup;
-    }
-
-    if (cert != NULL && key != NULL) {
-        char *password = NULL;
-        if (creds->passwordid) {
-            password = qcrypto_secret_lookup_as_utf8(creds->passwordid,
-                                                     errp);
-            if (!password) {
-                goto cleanup;
-            }
-        }
-        ret = gnutls_certificate_set_x509_key_file2(creds->data,
-                                                    cert, key,
-                                                    GNUTLS_X509_FMT_PEM,
-                                                    password,
-                                                    0);
-        g_free(password);
-        if (ret < 0) {
-            error_setg(errp, "Cannot load certificate '%s' & key '%s': %s",
-                       cert, key, gnutls_strerror(ret));
-            goto cleanup;
-        }
+        qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, errp) < 0) {
+        return -1;
     }
 
     if (cacrl != NULL) {
-        ret = gnutls_certificate_set_x509_crl_file(creds->data,
+        ret = gnutls_certificate_set_x509_crl_file(box->data.cert,
                                                    cacrl,
                                                    GNUTLS_X509_FMT_PEM);
         if (ret < 0) {
             error_setg(errp, "Cannot load CRL '%s': %s",
                        cacrl, gnutls_strerror(ret));
-            goto cleanup;
+            return -1;
         }
     }
 
-    if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+    if (isServer) {
         if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams,
-                                                 &creds->parent_obj.dh_params,
+                                                 &box->dh_params,
                                                  errp) < 0) {
-            goto cleanup;
+            return -1;
         }
-        gnutls_certificate_set_dh_params(creds->data,
-                                         creds->parent_obj.dh_params);
+        if (box->dh_params) {
+            gnutls_certificate_set_dh_params(box->data.cert, box->dh_params);
+        }
     }
+    creds->parent_obj.box = g_steal_pointer(&box);
 
-    rv = 0;
- cleanup:
-    g_free(cacert);
-    g_free(cacrl);
-    g_free(cert);
-    g_free(key);
-    g_free(dhparams);
-    return rv;
-}
-
-
-static void
-qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds)
-{
-    if (creds->data) {
-        gnutls_certificate_free_credentials(creds->data);
-        creds->data = NULL;
-    }
-    if (creds->parent_obj.dh_params) {
-        gnutls_dh_params_deinit(creds->parent_obj.dh_params);
-        creds->parent_obj.dh_params = NULL;
-    }
+    return 0;
 }
 
 
@@ -706,13 +914,6 @@
 }
 
 
-static void
-qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED)
-{
-    /* nada */
-}
-
-
 #endif /* ! CONFIG_GNUTLS */
 
 
@@ -775,26 +976,17 @@
 {
     QCryptoTLSCredsX509 *x509_creds = QCRYPTO_TLS_CREDS_X509(creds);
     Error *local_err = NULL;
-    gnutls_certificate_credentials_t creds_data = x509_creds->data;
-    gnutls_dh_params_t creds_dh_params = x509_creds->parent_obj.dh_params;
+    QCryptoTLSCredsBox *creds_box = creds->box;
 
-    x509_creds->data = NULL;
-    x509_creds->parent_obj.dh_params = NULL;
+    creds->box = NULL;
     qcrypto_tls_creds_x509_load(x509_creds, &local_err);
     if (local_err) {
-        qcrypto_tls_creds_x509_unload(x509_creds);
-        x509_creds->data = creds_data;
-        x509_creds->parent_obj.dh_params = creds_dh_params;
+        creds->box = creds_box;
         error_propagate(errp, local_err);
         return false;
     }
 
-    if (creds_data) {
-        gnutls_certificate_free_credentials(creds_data);
-    }
-    if (creds_dh_params) {
-        gnutls_dh_params_deinit(creds_dh_params);
-    }
+    qcrypto_tls_creds_box_unref(creds_box);
     return true;
 }
 
@@ -827,7 +1019,6 @@
     QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
 
     g_free(creds->passwordid);
-    qcrypto_tls_creds_x509_unload(creds);
 }
 
 
diff --git a/crypto/tlssession.c b/crypto/tlssession.c
index 92fe4f0..314e3e9 100644
--- a/crypto/tlssession.c
+++ b/crypto/tlssession.c
@@ -38,6 +38,7 @@
 
 struct QCryptoTLSSession {
     QCryptoTLSCreds *creds;
+    QCryptoTLSCredsBox *credsbox;
     gnutls_session_t handle;
     char *hostname;
     char *authzid;
@@ -78,6 +79,7 @@
     g_free(session->hostname);
     g_free(session->peername);
     g_free(session->authzid);
+    qcrypto_tls_creds_box_unref(session->credsbox);
     object_unref(OBJECT(session->creds));
     qemu_mutex_destroy(&session->lock);
     g_free(session);
@@ -155,9 +157,6 @@
     }
 }
 
-#define TLS_PRIORITY_ADDITIONAL_ANON "+ANON-DH"
-#define TLS_PRIORITY_ADDITIONAL_PSK "+ECDHE-PSK:+DHE-PSK:+PSK"
-
 QCryptoTLSSession *
 qcrypto_tls_session_new(QCryptoTLSCreds *creds,
                         const char *hostname,
@@ -167,6 +166,7 @@
 {
     QCryptoTLSSession *session;
     int ret;
+    g_autofree char *prio = NULL;
 
     session = g_new0(QCryptoTLSSession, 1);
     trace_qcrypto_tls_session_new(
@@ -200,113 +200,41 @@
         goto error;
     }
 
-    if (object_dynamic_cast(OBJECT(creds),
-                            TYPE_QCRYPTO_TLS_CREDS_ANON)) {
-        QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds);
-        char *prio;
-
-        if (creds->priority != NULL) {
-            prio = g_strdup_printf("%s:%s",
-                                   creds->priority,
-                                   TLS_PRIORITY_ADDITIONAL_ANON);
-        } else {
-            prio = g_strdup(CONFIG_TLS_PRIORITY ":"
-                            TLS_PRIORITY_ADDITIONAL_ANON);
-        }
-
-        ret = gnutls_priority_set_direct(session->handle, prio, NULL);
-        if (ret < 0) {
-            error_setg(errp, "Unable to set TLS session priority %s: %s",
-                       prio, gnutls_strerror(ret));
-            g_free(prio);
-            goto error;
-        }
-        g_free(prio);
-        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
-            ret = gnutls_credentials_set(session->handle,
-                                         GNUTLS_CRD_ANON,
-                                         acreds->data.server);
-        } else {
-            ret = gnutls_credentials_set(session->handle,
-                                         GNUTLS_CRD_ANON,
-                                         acreds->data.client);
-        }
-        if (ret < 0) {
-            error_setg(errp, "Cannot set session credentials: %s",
-                       gnutls_strerror(ret));
-            goto error;
-        }
-    } else if (object_dynamic_cast(OBJECT(creds),
-                                   TYPE_QCRYPTO_TLS_CREDS_PSK)) {
-        QCryptoTLSCredsPSK *pcreds = QCRYPTO_TLS_CREDS_PSK(creds);
-        char *prio;
-
-        if (creds->priority != NULL) {
-            prio = g_strdup_printf("%s:%s",
-                                   creds->priority,
-                                   TLS_PRIORITY_ADDITIONAL_PSK);
-        } else {
-            prio = g_strdup(CONFIG_TLS_PRIORITY ":"
-                            TLS_PRIORITY_ADDITIONAL_PSK);
-        }
-
-        ret = gnutls_priority_set_direct(session->handle, prio, NULL);
-        if (ret < 0) {
-            error_setg(errp, "Unable to set TLS session priority %s: %s",
-                       prio, gnutls_strerror(ret));
-            g_free(prio);
-            goto error;
-        }
-        g_free(prio);
-        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
-            ret = gnutls_credentials_set(session->handle,
-                                         GNUTLS_CRD_PSK,
-                                         pcreds->data.server);
-        } else {
-            ret = gnutls_credentials_set(session->handle,
-                                         GNUTLS_CRD_PSK,
-                                         pcreds->data.client);
-        }
-        if (ret < 0) {
-            error_setg(errp, "Cannot set session credentials: %s",
-                       gnutls_strerror(ret));
-            goto error;
-        }
-    } else if (object_dynamic_cast(OBJECT(creds),
-                                   TYPE_QCRYPTO_TLS_CREDS_X509)) {
-        QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds);
-        const char *prio = creds->priority;
-        if (!prio) {
-            prio = CONFIG_TLS_PRIORITY;
-        }
-
-        ret = gnutls_priority_set_direct(session->handle, prio, NULL);
-        if (ret < 0) {
-            error_setg(errp, "Cannot set default TLS session priority %s: %s",
-                       prio, gnutls_strerror(ret));
-            goto error;
-        }
-        ret = gnutls_credentials_set(session->handle,
-                                     GNUTLS_CRD_CERTIFICATE,
-                                     tcreds->data);
-        if (ret < 0) {
-            error_setg(errp, "Cannot set session credentials: %s",
-                       gnutls_strerror(ret));
-            goto error;
-        }
-
-        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
-            /* This requests, but does not enforce a client cert.
-             * The cert checking code later does enforcement */
-            gnutls_certificate_server_set_request(session->handle,
-                                                  GNUTLS_CERT_REQUEST);
-        }
-    } else {
-        error_setg(errp, "Unsupported TLS credentials type %s",
-                   object_get_typename(OBJECT(creds)));
+    prio = qcrypto_tls_creds_get_priority(creds);
+    ret = gnutls_priority_set_direct(session->handle, prio, NULL);
+    if (ret < 0) {
+        error_setg(errp, "Unable to set TLS session priority %s: %s",
+                   prio, gnutls_strerror(ret));
         goto error;
     }
 
+    ret = gnutls_credentials_set(session->handle,
+                                 creds->box->type,
+                                 creds->box->data.any);
+    if (ret < 0) {
+        error_setg(errp, "Cannot set session credentials: %s",
+                   gnutls_strerror(ret));
+        goto error;
+    }
+
+    /*
+     * creds->box->data.any must be kept alive for as long
+     * as the gnutls_session_t is alive, so acquire a ref
+     */
+    qcrypto_tls_creds_box_ref(creds->box);
+    session->credsbox = creds->box;
+
+    if (object_dynamic_cast(OBJECT(creds),
+                            TYPE_QCRYPTO_TLS_CREDS_X509) &&
+        creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        /*
+         * This requests, but does not enforce a client cert.
+         * The cert checking code later does enforcement
+         */
+        gnutls_certificate_server_set_request(session->handle,
+                                              GNUTLS_CERT_REQUEST);
+    }
+
     gnutls_transport_set_ptr(session->handle, session);
     gnutls_transport_set_push_function(session->handle,
                                        qcrypto_tls_session_push);
@@ -417,6 +345,7 @@
                 goto error;
             }
             session->peername = (char *)g_steal_pointer(&dname.data);
+            trace_qcrypto_tls_session_check_x509_dn(session, session->peername);
             if (session->authzid) {
                 bool allow;
 
diff --git a/crypto/trace-events b/crypto/trace-events
index d0e3342..771f9b8 100644
--- a/crypto/trace-events
+++ b/crypto/trace-events
@@ -21,6 +21,7 @@
 # tlssession.c
 qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *authzid, int endpoint) "TLS session new session=%p creds=%p hostname=%s authzid=%s endpoint=%d"
 qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
+qcrypto_tls_session_check_x509_dn(void *session, const char *dname) "TLS session check x509 distinguished name session=%p dname=%s"
 qcrypto_tls_session_parameters(void *session, int threadSafety, int protocol, int cipher) "TLS session parameters session=%p threadSafety=%d protocol=%d cipher=%d"
 qcrypto_tls_session_bug1717_workaround(void *session) "TLS session bug1717 workaround session=%p"
 
diff --git a/crypto/xts.c b/crypto/xts.c
deleted file mode 100644
index d4a49fd..0000000
--- a/crypto/xts.c
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * QEMU Crypto XTS cipher mode
- *
- * Copyright (c) 2015-2016 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- *
- * This code is originally derived from public domain / WTFPL code in
- * LibTomCrypt crytographic library http://libtom.org. The XTS code
- * was donated by Elliptic Semiconductor Inc (www.ellipticsemi.com)
- * to the LibTom Projects
- *
- */
-
-#include "qemu/osdep.h"
-#include "qemu/bswap.h"
-#include "crypto/xts.h"
-
-typedef union {
-    uint8_t b[XTS_BLOCK_SIZE];
-    uint64_t u[2];
-} xts_uint128;
-
-static inline void xts_uint128_xor(xts_uint128 *D,
-                                   const xts_uint128 *S1,
-                                   const xts_uint128 *S2)
-{
-    D->u[0] = S1->u[0] ^ S2->u[0];
-    D->u[1] = S1->u[1] ^ S2->u[1];
-}
-
-static inline void xts_uint128_cpu_to_les(xts_uint128 *v)
-{
-    cpu_to_le64s(&v->u[0]);
-    cpu_to_le64s(&v->u[1]);
-}
-
-static inline void xts_uint128_le_to_cpus(xts_uint128 *v)
-{
-    le64_to_cpus(&v->u[0]);
-    le64_to_cpus(&v->u[1]);
-}
-
-static void xts_mult_x(xts_uint128 *I)
-{
-    uint64_t tt;
-
-    xts_uint128_le_to_cpus(I);
-
-    tt = I->u[0] >> 63;
-    I->u[0] <<= 1;
-
-    if (I->u[1] >> 63) {
-        I->u[0] ^= 0x87;
-    }
-    I->u[1] <<= 1;
-    I->u[1] |= tt;
-
-    xts_uint128_cpu_to_les(I);
-}
-
-
-/**
- * xts_tweak_encdec:
- * @param ctxt: the cipher context
- * @param func: the cipher function
- * @src: buffer providing the input text of XTS_BLOCK_SIZE bytes
- * @dst: buffer to output the output text of XTS_BLOCK_SIZE bytes
- * @iv: the initialization vector tweak of XTS_BLOCK_SIZE bytes
- *
- * Encrypt/decrypt data with a tweak
- */
-static inline void xts_tweak_encdec(const void *ctx,
-                                    xts_cipher_func *func,
-                                    const xts_uint128 *src,
-                                    xts_uint128 *dst,
-                                    xts_uint128 *iv)
-{
-    /* tweak encrypt block i */
-    xts_uint128_xor(dst, src, iv);
-
-    func(ctx, XTS_BLOCK_SIZE, dst->b, dst->b);
-
-    xts_uint128_xor(dst, dst, iv);
-
-    /* LFSR the tweak */
-    xts_mult_x(iv);
-}
-
-
-void xts_decrypt(const void *datactx,
-                 const void *tweakctx,
-                 xts_cipher_func *encfunc,
-                 xts_cipher_func *decfunc,
-                 uint8_t *iv,
-                 size_t length,
-                 uint8_t *dst,
-                 const uint8_t *src)
-{
-    xts_uint128 PP, CC, T;
-    unsigned long i, m, mo, lim;
-
-    /* get number of blocks */
-    m = length >> 4;
-    mo = length & 15;
-
-    /* must have at least one full block */
-    g_assert(m != 0);
-
-    if (mo == 0) {
-        lim = m;
-    } else {
-        lim = m - 1;
-    }
-
-    /* encrypt the iv */
-    encfunc(tweakctx, XTS_BLOCK_SIZE, T.b, iv);
-
-    if (QEMU_PTR_IS_ALIGNED(src, sizeof(uint64_t)) &&
-        QEMU_PTR_IS_ALIGNED(dst, sizeof(uint64_t))) {
-        xts_uint128 *S = (xts_uint128 *)src;
-        xts_uint128 *D = (xts_uint128 *)dst;
-        for (i = 0; i < lim; i++, S++, D++) {
-            xts_tweak_encdec(datactx, decfunc, S, D, &T);
-        }
-    } else {
-        xts_uint128 D;
-
-        for (i = 0; i < lim; i++) {
-            memcpy(&D, src, XTS_BLOCK_SIZE);
-            xts_tweak_encdec(datactx, decfunc, &D, &D, &T);
-            memcpy(dst, &D, XTS_BLOCK_SIZE);
-            src += XTS_BLOCK_SIZE;
-            dst += XTS_BLOCK_SIZE;
-        }
-    }
-
-    /* if length is not a multiple of XTS_BLOCK_SIZE then */
-    if (mo > 0) {
-        xts_uint128 S, D;
-        memcpy(&CC, &T, XTS_BLOCK_SIZE);
-        xts_mult_x(&CC);
-
-        /* PP = tweak decrypt block m-1 */
-        memcpy(&S, src, XTS_BLOCK_SIZE);
-        xts_tweak_encdec(datactx, decfunc, &S, &PP, &CC);
-
-        /* Pm = first length % XTS_BLOCK_SIZE bytes of PP */
-        for (i = 0; i < mo; i++) {
-            CC.b[i] = src[XTS_BLOCK_SIZE + i];
-            dst[XTS_BLOCK_SIZE + i] = PP.b[i];
-        }
-        for (; i < XTS_BLOCK_SIZE; i++) {
-            CC.b[i] = PP.b[i];
-        }
-
-        /* Pm-1 = Tweak uncrypt CC */
-        xts_tweak_encdec(datactx, decfunc, &CC, &D, &T);
-        memcpy(dst, &D, XTS_BLOCK_SIZE);
-    }
-
-    /* Decrypt the iv back */
-    decfunc(tweakctx, XTS_BLOCK_SIZE, iv, T.b);
-}
-
-
-void xts_encrypt(const void *datactx,
-                 const void *tweakctx,
-                 xts_cipher_func *encfunc,
-                 xts_cipher_func *decfunc,
-                 uint8_t *iv,
-                 size_t length,
-                 uint8_t *dst,
-                 const uint8_t *src)
-{
-    xts_uint128 PP, CC, T;
-    unsigned long i, m, mo, lim;
-
-    /* get number of blocks */
-    m = length >> 4;
-    mo = length & 15;
-
-    /* must have at least one full block */
-    g_assert(m != 0);
-
-    if (mo == 0) {
-        lim = m;
-    } else {
-        lim = m - 1;
-    }
-
-    /* encrypt the iv */
-    encfunc(tweakctx, XTS_BLOCK_SIZE, T.b, iv);
-
-    if (QEMU_PTR_IS_ALIGNED(src, sizeof(uint64_t)) &&
-        QEMU_PTR_IS_ALIGNED(dst, sizeof(uint64_t))) {
-        xts_uint128 *S = (xts_uint128 *)src;
-        xts_uint128 *D = (xts_uint128 *)dst;
-        for (i = 0; i < lim; i++, S++, D++) {
-            xts_tweak_encdec(datactx, encfunc, S, D, &T);
-        }
-    } else {
-        xts_uint128 D;
-
-        for (i = 0; i < lim; i++) {
-            memcpy(&D, src, XTS_BLOCK_SIZE);
-            xts_tweak_encdec(datactx, encfunc, &D, &D, &T);
-            memcpy(dst, &D, XTS_BLOCK_SIZE);
-
-            dst += XTS_BLOCK_SIZE;
-            src += XTS_BLOCK_SIZE;
-        }
-    }
-
-    /* if length is not a multiple of XTS_BLOCK_SIZE then */
-    if (mo > 0) {
-        xts_uint128 S, D;
-        /* CC = tweak encrypt block m-1 */
-        memcpy(&S, src, XTS_BLOCK_SIZE);
-        xts_tweak_encdec(datactx, encfunc, &S, &CC, &T);
-
-        /* Cm = first length % XTS_BLOCK_SIZE bytes of CC */
-        for (i = 0; i < mo; i++) {
-            PP.b[i] = src[XTS_BLOCK_SIZE + i];
-            dst[XTS_BLOCK_SIZE + i] = CC.b[i];
-        }
-
-        for (; i < XTS_BLOCK_SIZE; i++) {
-            PP.b[i] = CC.b[i];
-        }
-
-        /* Cm-1 = Tweak encrypt PP */
-        xts_tweak_encdec(datactx, encfunc, &PP, &D, &T);
-        memcpy(dst, &D, XTS_BLOCK_SIZE);
-    }
-
-    /* Decrypt the iv back */
-    decfunc(tweakctx, XTS_BLOCK_SIZE, iv, T.b);
-}
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index 4ee98d6..03e2991 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -385,6 +385,15 @@
     - move backing file to NVDIMM storage and keep ``pmem=on``
       (to have NVDIMM with persistence guaranties).
 
+Using an external DH (Diffie-Hellman) parameters file (since 10.2)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Loading of external Diffie-Hellman parameters from a 'dh-params.pem'
+file is deprecated and will be removed with no replacement in a
+future release. Where no 'dh-params.pem' file is provided, the DH
+parameters will be automatically negotiated in accordance with
+RFC7919.
+
 Device options
 --------------
 
diff --git a/docs/system/tls.rst b/docs/system/tls.rst
index a4f6781..03fa1d8 100644
--- a/docs/system/tls.rst
+++ b/docs/system/tls.rst
@@ -36,8 +36,58 @@
 case it might be useful to use a commercial CA to avoid needing to
 install custom CA certs in the web browsers.
 
-The recommendation is for the server to keep its certificates in either
-``/etc/pki/qemu`` or for unprivileged users in ``$HOME/.pki/qemu``.
+.. _tls_cert_file_naming:
+
+Certificate file naming
+~~~~~~~~~~~~~~~~~~~~~~~
+
+In a simple setup, where all QEMU instances on a machine share the
+same TLS configuration, it is suggested that QEMU certificates be
+kept in either ``/etc/pki/qemu`` or, for unprivileged users, in
+``$HOME/.pki/qemu``. Where different QEMU subsystems require
+different certificate configurations, sub-dirs of these locations
+may be chosen.
+
+The default file names that QEMU will traditionally load are:
+
+* ``ca-cert.pem`` - mandatory; for both client and server configurations
+* ``ca-crl.pem`` - optional; for server configurations only
+* ``server-cert.pem`` - mandatory; for server configurations only
+* ``server-key.pem`` - mandatory; for server configurations only
+* ``client-cert.pem`` - optional; for client configurations only
+* ``client-key.pem`` - optional; for client configurations only
+* ``dh-params.pem`` - optional; for server configurations only
+
+Since QEMU 10.2.0, there is support for loading upto four additional
+identities:
+
+* ``server-cert-[IDX].pem`` - optional; for server configurations only
+* ``server-key-[IDX].pem`` - optional; for server configurations only
+* ``client-cert-[IDX].pem`` - optional; for client configurations only
+* ``client-key-[IDX].pem`` - optional; for client configurations only
+
+where ``-[IDX]`` is one of the digits 0-3. Loading will terminate at
+the first absent index. The index based certificate files may be used
+as a replacement for, or in addition to, the traditional non-index
+based certificate files. The traditional certificate files will be
+loaded first, if present, then the index based certificates. Where
+multiple certificates are compatible with a TLS session, the first
+loaded certificate will preferred. IOW file naming can influence
+which certificates are used for a session.
+
+The use of multiple sets of certificates is intended to allow an
+incremental transition to certificates using different crytographic
+algorithms. This allows a newly deployed QEMU to introduce use of
+stronger cryptographic algorithms that will be preferred when talking
+to other newly deployed QEMU instances, while retaining compatbility
+with certificates issued to a historically deployed QEMU. This is
+notably useful to support live migration from an old QEMU deployed
+on older operating system releases, which may support fewer crypto
+algorithm choices than the current OS.
+
+The certificate creation commands below will be illustrated using
+the traditional naming scheme, but their args can be substituted
+to use the indexed naming in the obvious manner.
 
 .. _tls_005fgenerate_005fca:
 
@@ -251,11 +301,13 @@
 directory contains the credential files. This directory is expected to
 contain files with the names mentioned previously, ``ca-cert.pem``,
 ``server-key.pem``, ``server-cert.pem``, ``client-key.pem`` and
-``client-cert.pem`` as appropriate. It is also possible to include a set
-of pre-generated Diffie-Hellman (DH) parameters in a file
-``dh-params.pem``, which can be created using the
-``certtool --generate-dh-params`` command. If omitted, QEMU will
-dynamically generate DH parameters when loading the credentials.
+``client-cert.pem`` as appropriate.
+
+While it is possible to include a set of pre-generated Diffie-Hellman
+(DH) parameters in a file ``dh-params.pem``, this facility is now
+deprecated and will be removed in a future release. When omitted the
+DH parameters will be automatically negotiated in accordance with
+RFC7919.
 
 The ``endpoint`` parameter indicates whether the credentials will be
 used for a network client or server, and determines which PEM files are
@@ -293,6 +345,74 @@
 
 .. _tls_005fpsk:
 
+TLS certificates for Post-Quantum Cryptography
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Given a new enough gnutls release, suitably integrated & configured with the
+operating system crypto policies, QEMU is able to support post-quantum
+crytography on TLS enabled services, either exclusively or in a hybrid mode.
+
+In exclusive mode, only a single set of certificates need to be configured
+for QEMU, with PQC compliant algorithms. Such a QEMU configuration will only
+be able to interoperate with other services (including other QEMU's) that
+also have PQC enabled. This can result in compatibility concerns during the
+period of transition over to PQC compliant algorithms.
+
+In hybrid mode, multiple sets of certificates need to be configured for QEMU,
+at least one set with traditional (non-PQC compliant) algorithms, and at least
+one other set with modern (PQC compliant) algorithms. At time of the TLS
+handshake, the GNUTLS algorithm priorities should ensure that PQC compliant
+algorithms are negotiated if both sides of the connection support PQC. If one
+side lacks PQC, the TLS handshake should fallback to the non-PQC algorithms.
+This can assist with interoperability during the transition to PQC, but has a
+potential weakness wrt downgrade attacks forcing use of non-PQC algorithms.
+Exclusive PQC mode should be preferred where both peers in the TLS connections
+are known to support PQC.
+
+Key generation parameters
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To create certificates with PQC compliant algorithms, the ``--key-type``
+argument must be passed to ``certtool`` when creating private keys. No
+extra arguments are required for the other ``certtool`` commands, as
+their behaviour will be determined by the private key type.
+
+The typical PQC compliant algorithms to use are ``ML-DSA-44``, ``ML-DSA-65``
+and ``ML-DSA-87``, with ``ML-DSA-65`` being a suitable default choice in
+the absence of explicit requirements.
+
+Taking the example earlier, for creating a key for a client certificate,
+to use ``ML-DSA-65`` the command line would be modified to look like::
+
+   # certtool --generate-privkey --key-type=mldsa65 > client-hostNNN-key.pem
+
+The equivalent modification applies to the creation of the private keys
+used for server certs, or root/intermediate CA certs.
+
+For hybrid mode, the additional indexed certificate naming must be used.
+If multiple configured certificates are compatible with the mutually
+supported crypto algorithms between the client and server, then the
+first matching certificate will be used.
+
+IOW, to ensure that PQC certificates are preferred, they must use a
+non-index based filename, or use an index that is smaller than any
+non-PQC certificates. ie, ``server-cert.pem`` for PQC and ``server-cert-0.pem``
+for non-PQC, or ``server-cert-0.pem`` for PQC and ``server-cert-1.pem`` for
+non-PQC.
+
+Force disabling PQC via crypto priority
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the OS configuration for system crypto algorithm priorities has
+enabled PQC, this can (optionally) be overriden in QEMU configuration
+disable use of PQC using the ``priority`` parameter to the ``tls-creds-x509``
+object::
+
+  NO_MLDSA="-SIGN-ML-DSA-65:-SIGN-ML-DSA-44:-SIGN-ML-DSA-87"
+  NO_MLKEM="-GROUP-X25519-MLKEM768:-GROUP-SECP256R1-MLKEM768:-GROUP-SECP384R1-MLKEM1024"
+  # qemu-nbd --object tls-creds-x509,id=tls0,endpoint=server,dir=....,priority=@SYSTEM:$NO_MLDSA:$NO_MLKEM
+
+
 TLS Pre-Shared Keys (PSK)
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/include/crypto/hash.h b/include/crypto/hash.h
index 1868d4a..4352509 100644
--- a/include/crypto/hash.h
+++ b/include/crypto/hash.h
@@ -122,7 +122,7 @@
  * Returns: 0 on success, -1 on error
  */
 int qcrypto_hash_bytes(QCryptoHashAlgo alg,
-                       const char *buf,
+                       const void *buf,
                        size_t len,
                        uint8_t **result,
                        size_t *resultlen,
@@ -180,7 +180,7 @@
  * Returns: 0 on success, -1 on error
  */
 int qcrypto_hash_update(QCryptoHash *hash,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         Error **errp);
 
@@ -289,7 +289,7 @@
  * Returns: 0 on success, -1 on error
  */
 int qcrypto_hash_digest(QCryptoHashAlgo alg,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **digest,
                         Error **errp);
@@ -335,7 +335,7 @@
  * Returns: 0 on success, -1 on error
  */
 int qcrypto_hash_base64(QCryptoHashAlgo alg,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **base64,
                         Error **errp);
diff --git a/include/crypto/hmac.h b/include/crypto/hmac.h
index af3d5f8..0885ae2 100644
--- a/include/crypto/hmac.h
+++ b/include/crypto/hmac.h
@@ -139,7 +139,7 @@
  *  0 on success, -1 on error
  */
 int qcrypto_hmac_bytes(QCryptoHmac *hmac,
-                       const char *buf,
+                       const void *buf,
                        size_t len,
                        uint8_t **result,
                        size_t *resultlen,
@@ -187,7 +187,7 @@
  * Returns: 0 on success, -1 on error
  */
 int qcrypto_hmac_digest(QCryptoHmac *hmac,
-                        const char *buf,
+                        const void *buf,
                         size_t len,
                         char **digest,
                         Error **errp);
diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h
index 2a8a857..bb9280e 100644
--- a/include/crypto/tlscreds.h
+++ b/include/crypto/tlscreds.h
@@ -47,6 +47,7 @@
 struct QCryptoTLSCredsClass {
     ObjectClass parent_class;
     CryptoTLSCredsReload reload;
+    const char *prioritySuffix;
 };
 
 /**
@@ -64,4 +65,29 @@
                                       QCryptoTLSCredsEndpoint endpoint,
                                       Error **errp);
 
+
+/**
+ * qcrypto_tls_creds_get_priority:
+ * @creds: pointer to a TLS credentials object
+ *
+ * Get the TLS credentials priority string. The caller
+ * must free the returned string when no longer required.
+ *
+ * Returns: a non-NULL priority string
+ */
+char *qcrypto_tls_creds_get_priority(QCryptoTLSCreds *creds);
+
+
+/**
+ * qcrypto_tls_creds_reload:
+ * @creds: pointer to a TLS credentials object
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Request a reload of the TLS credentials, if supported
+ *
+ * Returns: true on success, false on error or if not supported
+ */
+bool qcrypto_tls_creds_reload(QCryptoTLSCreds *creds,
+                              Error **errp);
+
 #endif /* QCRYPTO_TLSCREDS_H */
diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h
index c4daba2..61b7f73 100644
--- a/include/crypto/tlscredsx509.h
+++ b/include/crypto/tlscredsx509.h
@@ -37,7 +37,13 @@
 #define QCRYPTO_TLS_CREDS_X509_SERVER_CERT "server-cert.pem"
 #define QCRYPTO_TLS_CREDS_X509_CLIENT_KEY "client-key.pem"
 #define QCRYPTO_TLS_CREDS_X509_CLIENT_CERT "client-cert.pem"
+#define QCRYPTO_TLS_CREDS_X509_SERVER_KEY_N "server-key-%zu.pem"
+#define QCRYPTO_TLS_CREDS_X509_SERVER_CERT_N "server-cert-%zu.pem"
+#define QCRYPTO_TLS_CREDS_X509_CLIENT_KEY_N "client-key-%zu.pem"
+#define QCRYPTO_TLS_CREDS_X509_CLIENT_CERT_N "client-cert-%zu.pem"
 
+/* Max number of additional cert/key pairs (ie _N constants) */
+#define QCRYPTO_TLS_CREDS_X509_IDENTITY_MAX 4
 
 /**
  * QCryptoTLSCredsX509:
diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h
index 2e9fe11..28e4196 100644
--- a/include/crypto/tlssession.h
+++ b/include/crypto/tlssession.h
@@ -199,11 +199,11 @@
  * 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,
+typedef ssize_t (*QCryptoTLSSessionWriteFunc)(const void *buf,
                                               size_t len,
                                               void *opaque,
                                               Error **errp);
-typedef ssize_t (*QCryptoTLSSessionReadFunc)(char *buf,
+typedef ssize_t (*QCryptoTLSSessionReadFunc)(void *buf,
                                              size_t len,
                                              void *opaque,
                                              Error **errp);
diff --git a/include/crypto/xts.h b/include/crypto/xts.h
deleted file mode 100644
index f267b78..0000000
--- a/include/crypto/xts.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * QEMU Crypto XTS cipher mode
- *
- * Copyright (c) 2015-2016 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- *
- * This code is originally derived from public domain / WTFPL code in
- * LibTomCrypt crytographic library http://libtom.org. The XTS code
- * was donated by Elliptic Semiconductor Inc (www.ellipticsemi.com)
- * to the LibTom Projects
- *
- */
-
-#ifndef QCRYPTO_XTS_H
-#define QCRYPTO_XTS_H
-
-
-#define XTS_BLOCK_SIZE 16
-
-typedef void xts_cipher_func(const void *ctx,
-                             size_t length,
-                             uint8_t *dst,
-                             const uint8_t *src);
-
-/**
- * xts_decrypt:
- * @datactx: the cipher context for data decryption
- * @tweakctx: the cipher context for tweak decryption
- * @encfunc: the cipher function for encryption
- * @decfunc: the cipher function for decryption
- * @iv: the initialization vector tweak of XTS_BLOCK_SIZE bytes
- * @length: the length of @dst and @src
- * @dst: buffer to hold the decrypted plaintext
- * @src: buffer providing the ciphertext
- *
- * Decrypts @src into @dst
- */
-void xts_decrypt(const void *datactx,
-                 const void *tweakctx,
-                 xts_cipher_func *encfunc,
-                 xts_cipher_func *decfunc,
-                 uint8_t *iv,
-                 size_t length,
-                 uint8_t *dst,
-                 const uint8_t *src);
-
-/**
- * xts_decrypt:
- * @datactx: the cipher context for data encryption
- * @tweakctx: the cipher context for tweak encryption
- * @encfunc: the cipher function for encryption
- * @decfunc: the cipher function for decryption
- * @iv: the initialization vector tweak of XTS_BLOCK_SIZE bytes
- * @length: the length of @dst and @src
- * @dst: buffer to hold the encrypted ciphertext
- * @src: buffer providing the plaintext
- *
- * Decrypts @src into @dst
- */
-void xts_encrypt(const void *datactx,
-                 const void *tweakctx,
-                 xts_cipher_func *encfunc,
-                 xts_cipher_func *decfunc,
-                 uint8_t *iv,
-                 size_t length,
-                 uint8_t *dst,
-                 const uint8_t *src);
-
-
-#endif /* QCRYPTO_XTS_H */
diff --git a/include/io/channel-socket.h b/include/io/channel-socket.h
index a88cf8b..fcfd489 100644
--- a/include/io/channel-socket.h
+++ b/include/io/channel-socket.h
@@ -49,6 +49,12 @@
     socklen_t remoteAddrLen;
     ssize_t zero_copy_queued;
     ssize_t zero_copy_sent;
+    bool blocking;
+    /**
+     * This flag indicates whether any new data was successfully sent with
+     * zerocopy since the last qio_channel_socket_flush() call.
+     */
+    bool new_zero_copy_sent_success;
 };
 
 
diff --git a/include/io/channel.h b/include/io/channel.h
index 0f25ae0..db893a3 100644
--- a/include/io/channel.h
+++ b/include/io/channel.h
@@ -437,7 +437,7 @@
  * a single memory region.
  */
 ssize_t qio_channel_read(QIOChannel *ioc,
-                         char *buf,
+                         void *buf,
                          size_t buflen,
                          Error **errp);
 
@@ -453,7 +453,7 @@
  * single memory region.
  */
 ssize_t qio_channel_write(QIOChannel *ioc,
-                          const char *buf,
+                          const void *buf,
                           size_t buflen,
                           Error **errp);
 
@@ -475,7 +475,7 @@
  *          without data, or -1 on error
  */
 int coroutine_mixed_fn qio_channel_read_all_eof(QIOChannel *ioc,
-                                                char *buf,
+                                                void *buf,
                                                 size_t buflen,
                                                 Error **errp);
 
@@ -495,7 +495,7 @@
  * Returns: 0 if all bytes were read, or -1 on error
  */
 int coroutine_mixed_fn qio_channel_read_all(QIOChannel *ioc,
-                                            char *buf,
+                                            void *buf,
                                             size_t buflen,
                                             Error **errp);
 
@@ -514,7 +514,7 @@
  * Returns: 0 if all bytes were written, or -1 on error
  */
 int coroutine_mixed_fn qio_channel_write_all(QIOChannel *ioc,
-                                             const char *buf,
+                                             const void *buf,
                                              size_t buflen,
                                              Error **errp);
 
@@ -595,7 +595,7 @@
  * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
  *
  */
-ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
+ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
                            off_t offset, Error **errp);
 
 /**
@@ -631,7 +631,7 @@
  * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
  *
  */
-ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
+ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
                           off_t offset, Error **errp);
 
 /**
diff --git a/include/qemu/exit-with-parent.h b/include/qemu/exit-with-parent.h
new file mode 100644
index 0000000..c00b863
--- /dev/null
+++ b/include/qemu/exit-with-parent.h
@@ -0,0 +1,57 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Originally derived from nbdkit common/utils/exit-with-parent.h
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NBDKIT_EXIT_WITH_PARENT_H
+#define NBDKIT_EXIT_WITH_PARENT_H
+
+/* Test if the feature is available on the platform. */
+static inline bool can_exit_with_parent(void)
+{
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+    return true;
+#else
+    return false;
+#endif
+}
+
+/*
+ * --exit-with-parent: kill the current process if the parent exits.
+ * This may return -1 on error.
+ *
+ * Note this will abort on platforms where can_exit_with_parent()
+ * returned false.
+ */
+extern int set_exit_with_parent(void);
+
+#endif /* NBDKIT_EXIT_WITH_PARENT_H */
diff --git a/io/channel-socket.c b/io/channel-socket.c
index 712b793..3053b35 100644
--- a/io/channel-socket.c
+++ b/io/channel-socket.c
@@ -37,6 +37,12 @@
 
 #define SOCKET_MAX_FDS 16
 
+#ifdef QEMU_MSG_ZEROCOPY
+static int qio_channel_socket_flush_internal(QIOChannel *ioc,
+                                             bool block,
+                                             Error **errp);
+#endif
+
 SocketAddress *
 qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
                                      Error **errp)
@@ -65,6 +71,8 @@
     sioc->fd = -1;
     sioc->zero_copy_queued = 0;
     sioc->zero_copy_sent = 0;
+    sioc->blocking = false;
+    sioc->new_zero_copy_sent_success = false;
 
     ioc = QIO_CHANNEL(sioc);
     qio_channel_set_feature(ioc, QIO_CHANNEL_FEATURE_SHUTDOWN);
@@ -617,6 +625,10 @@
     size_t fdsize = sizeof(int) * nfds;
     struct cmsghdr *cmsg;
     int sflags = 0;
+#ifdef QEMU_MSG_ZEROCOPY
+    bool blocking = sioc->blocking;
+    bool zerocopy_flushed_once = false;
+#endif
 
     memset(control, 0, CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS));
 
@@ -661,13 +673,30 @@
             return QIO_CHANNEL_ERR_BLOCK;
         case EINTR:
             goto retry;
+#ifdef QEMU_MSG_ZEROCOPY
         case ENOBUFS:
             if (flags & QIO_CHANNEL_WRITE_FLAG_ZERO_COPY) {
-                error_setg_errno(errp, errno,
-                                 "Process can't lock enough memory for using MSG_ZEROCOPY");
-                return -1;
+                /**
+                 * Socket error queueing may exhaust the OPTMEM limit. Try
+                 * flushing the error queue once.
+                 */
+                if (!zerocopy_flushed_once) {
+                    ret = qio_channel_socket_flush_internal(ioc, blocking,
+                                                            errp);
+                    if (ret < 0) {
+                        return -1;
+                    }
+                    zerocopy_flushed_once = true;
+                    goto retry;
+                } else {
+                    error_setg_errno(errp, errno,
+                                     "Process can't lock enough memory for "
+                                     "using MSG_ZEROCOPY");
+                    return -1;
+                }
             }
             break;
+#endif
         }
 
         error_setg_errno(errp, errno,
@@ -776,8 +805,9 @@
 
 
 #ifdef QEMU_MSG_ZEROCOPY
-static int qio_channel_socket_flush(QIOChannel *ioc,
-                                    Error **errp)
+static int qio_channel_socket_flush_internal(QIOChannel *ioc,
+                                             bool block,
+                                             Error **errp)
 {
     QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
     struct msghdr msg = {};
@@ -785,7 +815,6 @@
     struct cmsghdr *cm;
     char control[CMSG_SPACE(sizeof(*serr))];
     int received;
-    int ret;
 
     if (sioc->zero_copy_queued == sioc->zero_copy_sent) {
         return 0;
@@ -795,16 +824,25 @@
     msg.msg_controllen = sizeof(control);
     memset(control, 0, sizeof(control));
 
-    ret = 1;
-
     while (sioc->zero_copy_sent < sioc->zero_copy_queued) {
         received = recvmsg(sioc->fd, &msg, MSG_ERRQUEUE);
         if (received < 0) {
             switch (errno) {
             case EAGAIN:
-                /* Nothing on errqueue, wait until something is available */
-                qio_channel_wait(ioc, G_IO_ERR);
-                continue;
+                if (block) {
+                    /*
+                     * Nothing on errqueue, wait until something is
+                     * available.
+                     *
+                     * Use G_IO_ERR instead of G_IO_IN since MSG_ERRQUEUE reads
+                     * are signaled via POLLERR, not POLLIN, as the kernel
+                     * sets POLLERR when zero-copy notificatons appear on the
+                     * socket error queue.
+                     */
+                    qio_channel_wait(ioc, G_IO_ERR);
+                    continue;
+                }
+                return 0;
             case EINTR:
                 continue;
             default:
@@ -842,13 +880,32 @@
         /* No errors, count successfully finished sendmsg()*/
         sioc->zero_copy_sent += serr->ee_data - serr->ee_info + 1;
 
-        /* If any sendmsg() succeeded using zero copy, return 0 at the end */
+        /* If any sendmsg() succeeded using zero copy, mark zerocopy success */
         if (serr->ee_code != SO_EE_CODE_ZEROCOPY_COPIED) {
-            ret = 0;
+            sioc->new_zero_copy_sent_success = true;
         }
     }
 
-    return ret;
+    return 0;
+}
+
+static int qio_channel_socket_flush(QIOChannel *ioc,
+                                    Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    int ret;
+
+    ret = qio_channel_socket_flush_internal(ioc, true, errp);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (sioc->new_zero_copy_sent_success) {
+        sioc->new_zero_copy_sent_success = false;
+        return 0;
+    }
+
+    return 1;
 }
 
 #endif /* QEMU_MSG_ZEROCOPY */
@@ -859,6 +916,7 @@
                                 Error **errp)
 {
     QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    sioc->blocking = enabled;
 
     if (!qemu_set_blocking(sioc->fd, enabled, errp)) {
         return -1;
diff --git a/io/channel-tls.c b/io/channel-tls.c
index ce04179..b0cec27 100644
--- a/io/channel-tls.c
+++ b/io/channel-tls.c
@@ -26,7 +26,7 @@
 #include "qemu/atomic.h"
 
 
-static ssize_t qio_channel_tls_write_handler(const char *buf,
+static ssize_t qio_channel_tls_write_handler(const void *buf,
                                              size_t len,
                                              void *opaque,
                                              Error **errp)
@@ -43,7 +43,7 @@
     return ret;
 }
 
-static ssize_t qio_channel_tls_read_handler(char *buf,
+static ssize_t qio_channel_tls_read_handler(void *buf,
                                             size_t len,
                                             void *opaque,
                                             Error **errp)
diff --git a/io/channel.c b/io/channel.c
index 852e684..8e8bd2e 100644
--- a/io/channel.c
+++ b/io/channel.c
@@ -310,7 +310,7 @@
 
 
 ssize_t qio_channel_read(QIOChannel *ioc,
-                         char *buf,
+                         void *buf,
                          size_t buflen,
                          Error **errp)
 {
@@ -320,7 +320,7 @@
 
 
 ssize_t qio_channel_write(QIOChannel *ioc,
-                          const char *buf,
+                          const void *buf,
                           size_t buflen,
                           Error **errp)
 {
@@ -330,7 +330,7 @@
 
 
 int coroutine_mixed_fn qio_channel_read_all_eof(QIOChannel *ioc,
-                                                char *buf,
+                                                void *buf,
                                                 size_t buflen,
                                                 Error **errp)
 {
@@ -340,7 +340,7 @@
 
 
 int coroutine_mixed_fn qio_channel_read_all(QIOChannel *ioc,
-                                            char *buf,
+                                            void *buf,
                                             size_t buflen,
                                             Error **errp)
 {
@@ -350,7 +350,7 @@
 
 
 int coroutine_mixed_fn qio_channel_write_all(QIOChannel *ioc,
-                                             const char *buf,
+                                             const void *buf,
                                              size_t buflen,
                                              Error **errp)
 {
@@ -475,7 +475,7 @@
     return klass->io_pwritev(ioc, iov, niov, offset, errp);
 }
 
-ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
+ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
                            off_t offset, Error **errp)
 {
     struct iovec iov = {
@@ -504,7 +504,7 @@
     return klass->io_preadv(ioc, iov, niov, offset, errp);
 }
 
-ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
+ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
                           off_t offset, Error **errp)
 {
     struct iovec iov = {
diff --git a/meson.build b/meson.build
index df876c7..b8c1296 100644
--- a/meson.build
+++ b/meson.build
@@ -1823,33 +1823,11 @@
 endif
 
 gnutls = not_found
-gnutls_crypto = not_found
 gnutls_bug1717_workaround = false
 if get_option('gnutls').enabled() or (get_option('gnutls').auto() and have_system)
-  # For general TLS support our min gnutls matches
-  # that implied by our platform support matrix
-  #
-  # For the crypto backends, we look for a newer
-  # gnutls:
-  #
-  #   Version 3.6.8  is needed to get XTS
-  #   Version 3.6.13 is needed to get PBKDF
-  #   Version 3.6.14 is needed to get HW accelerated XTS
-  #
-  # If newer enough gnutls isn't available, we can
-  # still use a different crypto backend to satisfy
-  # the platform support requirements
-  gnutls_crypto = dependency('gnutls', version: '>=3.6.14',
-                             method: 'pkg-config',
-                             required: false)
-  if gnutls_crypto.found()
-    gnutls = gnutls_crypto
-  else
-    # Our min version if all we need is TLS
-    gnutls = dependency('gnutls', version: '>=3.5.18',
-                        method: 'pkg-config',
-                        required: get_option('gnutls'))
-  endif
+  gnutls = dependency('gnutls', version: '>=3.7.5',
+                      method: 'pkg-config',
+                      required: get_option('gnutls'))
 
   #if gnutls.found() and not get_option('gnutls-bug1717-workaround').disabled()
     # XXX: when bug 1717 is resolved, add logic to probe for
@@ -1868,20 +1846,14 @@
 hogweed = not_found
 crypto_sm4 = not_found
 crypto_sm3 = not_found
-xts = 'none'
 
 if get_option('nettle').enabled() and get_option('gcrypt').enabled()
   error('Only one of gcrypt & nettle can be enabled')
 endif
 
-# Explicit nettle/gcrypt request, so ignore gnutls for crypto
-if get_option('nettle').enabled() or get_option('gcrypt').enabled()
-  gnutls_crypto = not_found
-endif
-
-if not gnutls_crypto.found()
+if not gnutls.found()
   if (not get_option('gcrypt').auto() or have_system) and not get_option('nettle').enabled()
-    gcrypt = dependency('libgcrypt', version: '>=1.8',
+    gcrypt = dependency('libgcrypt', version: '>=1.9.4',
                         required: get_option('gcrypt'))
     # Debian has removed -lgpg-error from libgcrypt-config
     # as it "spreads unnecessary dependencies" which in
@@ -1893,35 +1865,12 @@
         version: gcrypt.version())
     endif
     crypto_sm4 = gcrypt
-    # SM4 ALG is available in libgcrypt >= 1.9
-    if gcrypt.found() and not cc.links('''
-      #include <gcrypt.h>
-      int main(void) {
-        gcry_cipher_hd_t handler;
-        gcry_cipher_open(&handler, GCRY_CIPHER_SM4, GCRY_CIPHER_MODE_ECB, 0);
-        return 0;
-      }''', dependencies: gcrypt)
-      crypto_sm4 = not_found
-    endif
     crypto_sm3 = gcrypt
-    # SM3 ALG is available in libgcrypt >= 1.9
-    if gcrypt.found() and not cc.links('''
-      #include <gcrypt.h>
-      int main(void) {
-        gcry_md_hd_t handler;
-        gcry_md_open(&handler, GCRY_MD_SM3, 0);
-        return 0;
-      }''', dependencies: gcrypt)
-      crypto_sm3 = not_found
-    endif
   endif
   if (not get_option('nettle').auto() or have_system) and not gcrypt.found()
-    nettle = dependency('nettle', version: '>=3.4',
+    nettle = dependency('nettle', version: '>=3.7.3',
                         method: 'pkg-config',
                         required: get_option('nettle'))
-    if nettle.found() and not cc.has_header('nettle/xts.h', dependencies: nettle)
-      xts = 'private'
-    endif
     crypto_sm4 = nettle
     # SM4 ALG is available in nettle >= 3.9
     if nettle.found() and not cc.links('''
@@ -2606,7 +2555,6 @@
 config_host_data.set('CONFIG_KEYUTILS', keyutils.found())
 config_host_data.set('CONFIG_GETTID', has_gettid)
 config_host_data.set('CONFIG_GNUTLS', gnutls.found())
-config_host_data.set('CONFIG_GNUTLS_CRYPTO', gnutls_crypto.found())
 config_host_data.set('CONFIG_GNUTLS_BUG1717_WORKAROUND', gnutls_bug1717_workaround)
 config_host_data.set('CONFIG_TASN1', tasn1.found())
 config_host_data.set('CONFIG_GCRYPT', gcrypt.found())
@@ -2614,7 +2562,6 @@
 config_host_data.set('CONFIG_CRYPTO_SM4', crypto_sm4.found())
 config_host_data.set('CONFIG_CRYPTO_SM3', crypto_sm3.found())
 config_host_data.set('CONFIG_HOGWEED', hogweed.found())
-config_host_data.set('CONFIG_QEMU_PRIVATE_XTS', xts == 'private')
 config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim)
 config_host_data.set('CONFIG_ZSTD', zstd.found())
 config_host_data.set('CONFIG_QPL', qpl.found())
@@ -4906,14 +4853,10 @@
 summary_info += {'TLS priority':      get_option('tls_priority')}
 summary_info += {'GNUTLS support':    gnutls}
 if gnutls.found()
-  summary_info += {'  GNUTLS crypto':   gnutls_crypto.found()}
   summary_info += {'  GNUTLS bug 1717 workaround': gnutls_bug1717_workaround }
 endif
 summary_info += {'libgcrypt':         gcrypt}
 summary_info += {'nettle':            nettle}
-if nettle.found()
-   summary_info += {'  XTS':             xts != 'private'}
-endif
 summary_info += {'SM4 ALG support':   crypto_sm4}
 summary_info += {'SM3 ALG support':   crypto_sm3}
 summary_info += {'AF_ALG support':    have_afalg}
diff --git a/qemu-options.hx b/qemu-options.hx
index 0223cef..fca2b7b 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -5467,15 +5467,18 @@
 
 #if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN)
 DEF("run-with", HAS_ARG, QEMU_OPTION_run_with,
-    "-run-with [async-teardown=on|off][,chroot=dir][user=username|uid:gid]\n"
+    "-run-with [async-teardown=on|off][,chroot=dir]\n" \
+    "          [,exit-with-parent=on|off][,user=username|uid:gid]\n"
     "                Set miscellaneous QEMU process lifecycle options:\n"
     "                async-teardown=on enables asynchronous teardown (Linux only)\n"
+    "                exit-with-parent=on causes QEMU to exit if the parent\n"
+    "                  process of QEMU exits (Linux, FreeBSD, macOS only)\n"
     "                chroot=dir chroot to dir just before starting the VM\n"
     "                user=username switch to the specified user before starting the VM\n"
     "                user=uid:gid ditto, but use specified user-ID and group-ID instead\n",
     QEMU_ARCH_ALL)
 SRST
-``-run-with [async-teardown=on|off][,chroot=dir][user=username|uid:gid]``
+``-run-with [async-teardown=on|off][,chroot=dir][,exit-with-parent=on|off][,user=username|uid:gid]``
     Set QEMU process lifecycle options.
 
     ``async-teardown=on`` enables asynchronous teardown. A new process called
@@ -5493,6 +5496,12 @@
     immediately before starting the guest execution. This is especially useful
     in combination with ``user=...``.
 
+    ``exit-with-parent=on`` causes QEMU to exit if the parent process of
+    QEMU exits.  This can be used when QEMU runs a captive appliance,
+    where the lifetime of the appliance is scoped to the parent process.
+    In case the parent process crashes, QEMU is still cleaned up.
+    This only works on Linux, FreeBSD and macOS platforms.
+
     ``user=username`` or ``user=uid:gid`` can be used to drop root privileges
     before starting guest execution. QEMU will use the ``setuid`` and ``setgid``
     system calls to switch to the specified identity.  Note that the
diff --git a/system/exit-with-parent.c b/system/exit-with-parent.c
new file mode 100644
index 0000000..df65d22
--- /dev/null
+++ b/system/exit-with-parent.c
@@ -0,0 +1,140 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Originally derived from nbdkit common/utils/exit-with-parent.c
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Implement the --exit-with-parent feature on operating systems which
+ * support it.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/exit-with-parent.h"
+
+#if defined(__linux__)
+
+#include <sys/prctl.h>
+
+/*
+ * Send SIGTERM to self when the parent exits.  This will cause
+ * qemu_system_killed() to be called.
+ *
+ * PR_SET_PDEATHSIG has been defined since Linux 2.1.57.
+ */
+int
+set_exit_with_parent(void)
+{
+    return prctl(PR_SET_PDEATHSIG, SIGTERM);
+}
+
+#elif defined(__FreeBSD__)
+
+#include <sys/procctl.h>
+
+/*
+ * Send SIGTERM to self when the parent exits.  This will cause
+ * qemu_system_killed() to be called.
+ *
+ * PROC_PDEATHSIG_CTL has been defined since FreeBSD 11.2.
+ */
+int
+set_exit_with_parent(void)
+{
+    const int sig = SIGTERM;
+    return procctl(P_PID, 0, PROC_PDEATHSIG_CTL, (void *) &sig);
+}
+
+#elif defined(__APPLE__)
+
+/* For macOS. */
+
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+#include "system/runstate.h"
+#include <sys/event.h>
+
+static void *
+exit_with_parent_loop(void *vp)
+{
+    const pid_t ppid = getppid();
+    int fd;
+    struct kevent kev, res[1];
+    int r;
+
+    /* Register the kevent to wait for ppid to exit. */
+    fd = kqueue();
+    if (fd == -1) {
+        error_report("exit_with_parent_loop: kqueue: %m");
+        return NULL;
+    }
+    EV_SET(&kev, ppid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL);
+    if (kevent(fd, &kev, 1, NULL, 0, NULL) == -1) {
+        error_report("exit_with_parent_loop: kevent: %m");
+        close(fd);
+        return NULL;
+    }
+
+    /* Wait for the kevent to happen. */
+    r = kevent(fd, 0, 0, res, 1, NULL);
+    if (r == 1 && res[0].ident == ppid) {
+        /* Behave like Linux and FreeBSD above, as if SIGTERM was sent */
+        qemu_system_killed(SIGTERM, ppid);
+    }
+
+    return NULL;
+}
+
+int
+set_exit_with_parent(void)
+{
+    QemuThread exit_with_parent_thread;
+
+    /*
+     * We have to block waiting for kevent, so that requires that we
+     * start a background thread.
+     */
+    qemu_thread_create(&exit_with_parent_thread,
+                       "exit-parent",
+                       exit_with_parent_loop, NULL,
+                       QEMU_THREAD_DETACHED);
+    return 0;
+}
+
+#else /* any platform that doesn't support this function */
+
+int
+set_exit_with_parent(void)
+{
+    g_assert_not_reached();
+}
+
+#endif
diff --git a/system/meson.build b/system/meson.build
index 6d21ff9..4b69ef0 100644
--- a/system/meson.build
+++ b/system/meson.build
@@ -15,6 +15,7 @@
   'datadir.c',
   'dirtylimit.c',
   'dma-helpers.c',
+  'exit-with-parent.c',
   'globals.c',
   'ioport.c',
   'ram-block-attributes.c',
diff --git a/system/vl.c b/system/vl.c
index 29f5389..5091fe5 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -53,6 +53,7 @@
 #include "qemu/sockets.h"
 #include "qemu/accel.h"
 #include "qemu/async-teardown.h"
+#include "qemu/exit-with-parent.h"
 #include "hw/usb.h"
 #include "hw/isa/isa.h"
 #include "hw/scsi/scsi.h"
@@ -784,6 +785,10 @@
             .type = QEMU_OPT_STRING,
         },
         {
+            .name = "exit-with-parent",
+            .type = QEMU_OPT_BOOL,
+        },
+        {
             .name = "user",
             .type = QEMU_OPT_STRING,
         },
@@ -3691,6 +3696,14 @@
                 if (str) {
                     os_set_chroot(str);
                 }
+                if (qemu_opt_get_bool(opts, "exit-with-parent", false)) {
+                    if (!can_exit_with_parent()) {
+                        error_report("exit-with-parent is not available"
+                                     " on this platform");
+                        exit(1);
+                    }
+                    set_exit_with_parent();
+                }
                 str = qemu_opt_get(opts, "user");
                 if (str) {
                     if (!os_set_runas(str)) {
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 933d085..622464e 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -33,6 +33,7 @@
 #include "qemu/accel.h"
 #include "qemu/ctype.h"
 #include "qemu/cutils.h"
+#include "qemu/exit-with-parent.h"
 #include "qemu/sockets.h"
 #include "qobject/qdict.h"
 #include "qobject/qjson.h"
@@ -433,24 +434,6 @@
 #ifndef _WIN32
     pid = fork();
     if (pid == 0) {
-#ifdef __linux__
-        /*
-         * Although we register a ABRT handler to kill off QEMU
-         * when g_assert() triggers, we want an extra safety
-         * net. The QEMU process might be non-functional and
-         * thus not have responded to SIGTERM. The test script
-         * might also have crashed with SEGV, in which case the
-         * cleanup handlers won't ever run.
-         *
-         * This PR_SET_PDEATHSIG setup will ensure any remaining
-         * QEMU will get terminated with SIGKILL in these cases.
-         */
-        prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
-#endif /* __linux__ */
-#ifdef __FreeBSD__
-        int sig = SIGKILL;
-        procctl(P_PID, getpid(), PROC_PDEATHSIG_CTL, &sig);
-#endif /* __FreeBSD__ */
         execlp("/bin/sh", "sh", "-c", command->str, NULL);
         exit(1);
     }
@@ -482,12 +465,15 @@
                       "-display none "
                       "-audio none "
                       "%s"
+                      "%s"
                       " -accel qtest",
 
                       tracearg,
                       socket_path,
                       getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
                       qmp_socket_path,
+                      can_exit_with_parent() ?
+                      "-run-with exit-with-parent=on " : "",
                       extra_args ?: "");
 
     return args;
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index d5248ae..bd58029 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -110,9 +110,6 @@
   if pam.found()
     tests += {'test-authz-pam': [authz]}
   endif
-  if xts == 'private'
-    tests += {'test-crypto-xts': [crypto, io]}
-  endif
   if host_os != 'windows'
     tests += {
       'test-image-locking': [testblock],
diff --git a/tests/unit/test-crypto-block.c b/tests/unit/test-crypto-block.c
index 3ac7f17..218e585 100644
--- a/tests/unit/test-crypto-block.c
+++ b/tests/unit/test-crypto-block.c
@@ -31,8 +31,7 @@
 #endif
 
 #if (defined(_WIN32) || defined RUSAGE_THREAD) && \
-    (defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT) || \
-     defined(CONFIG_GNUTLS_CRYPTO))
+    (defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT))
 #define TEST_LUKS
 #else
 #undef TEST_LUKS
diff --git a/tests/unit/test-crypto-tlscredsx509.c b/tests/unit/test-crypto-tlscredsx509.c
index a5f2172..b1ad7d5 100644
--- a/tests/unit/test-crypto-tlscredsx509.c
+++ b/tests/unit/test-crypto-tlscredsx509.c
@@ -95,16 +95,16 @@
         if (access(data->crt, R_OK) == 0) {
             g_assert(link(data->crt,
                           CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
+            g_assert(link(KEYFILE,
+                          CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
         }
-        g_assert(link(KEYFILE,
-                      CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
     } else {
         if (access(data->crt, R_OK) == 0) {
             g_assert(link(data->crt,
                           CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
+            g_assert(link(KEYFILE,
+                          CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
         }
-        g_assert(link(KEYFILE,
-                      CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
     }
 
     creds = test_tls_creds_create(
diff --git a/tests/unit/test-crypto-tlssession.c b/tests/unit/test-crypto-tlssession.c
index d0baf3b..0d06a68 100644
--- a/tests/unit/test-crypto-tlssession.c
+++ b/tests/unit/test-crypto-tlssession.c
@@ -36,7 +36,7 @@
 #define KEYFILE WORKDIR "key-ctx.pem"
 
 static ssize_t
-testWrite(const char *buf, size_t len, void *opaque, Error **errp)
+testWrite(const void *buf, size_t len, void *opaque, Error **errp)
 {
     int *fd = opaque;
     int ret;
@@ -54,7 +54,7 @@
 }
 
 static ssize_t
-testRead(char *buf, size_t len, void *opaque, Error **errp)
+testRead(void *buf, size_t len, void *opaque, Error **errp)
 {
     int *fd = opaque;
     int ret;
diff --git a/tests/unit/test-crypto-xts.c b/tests/unit/test-crypto-xts.c
deleted file mode 100644
index 7acbc95..0000000
--- a/tests/unit/test-crypto-xts.c
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * QEMU Crypto XTS cipher mode
- *
- * Copyright (c) 2015-2018 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- *
- * This code is originally derived from public domain / WTFPL code in
- * LibTomCrypt crytographic library http://libtom.org. The XTS code
- * was donated by Elliptic Semiconductor Inc (www.ellipticsemi.com)
- * to the LibTom Projects
- *
- */
-
-#include "qemu/osdep.h"
-#include "crypto/init.h"
-#include "crypto/xts.h"
-#include "crypto/aes.h"
-
-typedef struct {
-    const char *path;
-    int keylen;
-    unsigned char key1[32];
-    unsigned char key2[32];
-    uint64_t seqnum;
-    unsigned long PTLEN;
-    unsigned char PTX[512], CTX[512];
-} QCryptoXTSTestData;
-
-static const QCryptoXTSTestData test_data[] = {
-    /* #1 32 byte key, 32 byte PTX */
-    {
-        "/crypto/xts/t-1-key-32-ptx-32",
-        32,
-        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
-        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
-        0,
-        32,
-        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
-        { 0x91, 0x7c, 0xf6, 0x9e, 0xbd, 0x68, 0xb2, 0xec,
-          0x9b, 0x9f, 0xe9, 0xa3, 0xea, 0xdd, 0xa6, 0x92,
-          0xcd, 0x43, 0xd2, 0xf5, 0x95, 0x98, 0xed, 0x85,
-          0x8c, 0x02, 0xc2, 0x65, 0x2f, 0xbf, 0x92, 0x2e },
-    },
-
-    /* #2, 32 byte key, 32 byte PTX */
-    {
-        "/crypto/xts/t-2-key-32-ptx-32",
-        32,
-        { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
-          0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 },
-        { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
-          0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22 },
-        0x3333333333LL,
-        32,
-        { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 },
-        { 0xc4, 0x54, 0x18, 0x5e, 0x6a, 0x16, 0x93, 0x6e,
-          0x39, 0x33, 0x40, 0x38, 0xac, 0xef, 0x83, 0x8b,
-          0xfb, 0x18, 0x6f, 0xff, 0x74, 0x80, 0xad, 0xc4,
-          0x28, 0x93, 0x82, 0xec, 0xd6, 0xd3, 0x94, 0xf0 },
-    },
-
-    /* #5 from xts.7, 32 byte key, 32 byte PTX */
-    {
-        "/crypto/xts/t-5-key-32-ptx-32",
-        32,
-        { 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
-          0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
-        { 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
-          0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
-        0x123456789aLL,
-        32,
-        { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
-          0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 },
-        { 0xb0, 0x1f, 0x86, 0xf8, 0xed, 0xc1, 0x86, 0x37,
-          0x06, 0xfa, 0x8a, 0x42, 0x53, 0xe3, 0x4f, 0x28,
-          0xaf, 0x31, 0x9d, 0xe3, 0x83, 0x34, 0x87, 0x0f,
-          0x4d, 0xd1, 0xf9, 0x4c, 0xbe, 0x98, 0x32, 0xf1 },
-    },
-
-    /* #4, 32 byte key, 512 byte PTX  */
-    {
-        "/crypto/xts/t-4-key-32-ptx-512",
-        32,
-        { 0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45,
-          0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26 },
-        { 0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93,
-          0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95 },
-        0,
-        512,
-        {
-            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-            0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-            0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
-            0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
-            0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
-            0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-            0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
-            0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-            0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
-            0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-            0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
-            0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
-            0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
-            0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
-            0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
-            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-            0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
-            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
-            0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
-            0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
-            0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
-            0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
-            0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
-            0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-            0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
-            0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
-            0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
-            0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
-            0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
-            0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
-            0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
-            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-            0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-            0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
-            0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
-            0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
-            0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-            0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
-            0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-            0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
-            0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-            0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
-            0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
-            0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
-            0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
-            0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
-            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-            0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
-            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
-            0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
-            0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
-            0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
-            0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
-            0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
-            0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-            0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
-            0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
-            0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
-            0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
-            0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
-            0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
-            0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
-        },
-        {
-            0x27, 0xa7, 0x47, 0x9b, 0xef, 0xa1, 0xd4, 0x76,
-            0x48, 0x9f, 0x30, 0x8c, 0xd4, 0xcf, 0xa6, 0xe2,
-            0xa9, 0x6e, 0x4b, 0xbe, 0x32, 0x08, 0xff, 0x25,
-            0x28, 0x7d, 0xd3, 0x81, 0x96, 0x16, 0xe8, 0x9c,
-            0xc7, 0x8c, 0xf7, 0xf5, 0xe5, 0x43, 0x44, 0x5f,
-            0x83, 0x33, 0xd8, 0xfa, 0x7f, 0x56, 0x00, 0x00,
-            0x05, 0x27, 0x9f, 0xa5, 0xd8, 0xb5, 0xe4, 0xad,
-            0x40, 0xe7, 0x36, 0xdd, 0xb4, 0xd3, 0x54, 0x12,
-            0x32, 0x80, 0x63, 0xfd, 0x2a, 0xab, 0x53, 0xe5,
-            0xea, 0x1e, 0x0a, 0x9f, 0x33, 0x25, 0x00, 0xa5,
-            0xdf, 0x94, 0x87, 0xd0, 0x7a, 0x5c, 0x92, 0xcc,
-            0x51, 0x2c, 0x88, 0x66, 0xc7, 0xe8, 0x60, 0xce,
-            0x93, 0xfd, 0xf1, 0x66, 0xa2, 0x49, 0x12, 0xb4,
-            0x22, 0x97, 0x61, 0x46, 0xae, 0x20, 0xce, 0x84,
-            0x6b, 0xb7, 0xdc, 0x9b, 0xa9, 0x4a, 0x76, 0x7a,
-            0xae, 0xf2, 0x0c, 0x0d, 0x61, 0xad, 0x02, 0x65,
-            0x5e, 0xa9, 0x2d, 0xc4, 0xc4, 0xe4, 0x1a, 0x89,
-            0x52, 0xc6, 0x51, 0xd3, 0x31, 0x74, 0xbe, 0x51,
-            0xa1, 0x0c, 0x42, 0x11, 0x10, 0xe6, 0xd8, 0x15,
-            0x88, 0xed, 0xe8, 0x21, 0x03, 0xa2, 0x52, 0xd8,
-            0xa7, 0x50, 0xe8, 0x76, 0x8d, 0xef, 0xff, 0xed,
-            0x91, 0x22, 0x81, 0x0a, 0xae, 0xb9, 0x9f, 0x91,
-            0x72, 0xaf, 0x82, 0xb6, 0x04, 0xdc, 0x4b, 0x8e,
-            0x51, 0xbc, 0xb0, 0x82, 0x35, 0xa6, 0xf4, 0x34,
-            0x13, 0x32, 0xe4, 0xca, 0x60, 0x48, 0x2a, 0x4b,
-            0xa1, 0xa0, 0x3b, 0x3e, 0x65, 0x00, 0x8f, 0xc5,
-            0xda, 0x76, 0xb7, 0x0b, 0xf1, 0x69, 0x0d, 0xb4,
-            0xea, 0xe2, 0x9c, 0x5f, 0x1b, 0xad, 0xd0, 0x3c,
-            0x5c, 0xcf, 0x2a, 0x55, 0xd7, 0x05, 0xdd, 0xcd,
-            0x86, 0xd4, 0x49, 0x51, 0x1c, 0xeb, 0x7e, 0xc3,
-            0x0b, 0xf1, 0x2b, 0x1f, 0xa3, 0x5b, 0x91, 0x3f,
-            0x9f, 0x74, 0x7a, 0x8a, 0xfd, 0x1b, 0x13, 0x0e,
-            0x94, 0xbf, 0xf9, 0x4e, 0xff, 0xd0, 0x1a, 0x91,
-            0x73, 0x5c, 0xa1, 0x72, 0x6a, 0xcd, 0x0b, 0x19,
-            0x7c, 0x4e, 0x5b, 0x03, 0x39, 0x36, 0x97, 0xe1,
-            0x26, 0x82, 0x6f, 0xb6, 0xbb, 0xde, 0x8e, 0xcc,
-            0x1e, 0x08, 0x29, 0x85, 0x16, 0xe2, 0xc9, 0xed,
-            0x03, 0xff, 0x3c, 0x1b, 0x78, 0x60, 0xf6, 0xde,
-            0x76, 0xd4, 0xce, 0xcd, 0x94, 0xc8, 0x11, 0x98,
-            0x55, 0xef, 0x52, 0x97, 0xca, 0x67, 0xe9, 0xf3,
-            0xe7, 0xff, 0x72, 0xb1, 0xe9, 0x97, 0x85, 0xca,
-            0x0a, 0x7e, 0x77, 0x20, 0xc5, 0xb3, 0x6d, 0xc6,
-            0xd7, 0x2c, 0xac, 0x95, 0x74, 0xc8, 0xcb, 0xbc,
-            0x2f, 0x80, 0x1e, 0x23, 0xe5, 0x6f, 0xd3, 0x44,
-            0xb0, 0x7f, 0x22, 0x15, 0x4b, 0xeb, 0xa0, 0xf0,
-            0x8c, 0xe8, 0x89, 0x1e, 0x64, 0x3e, 0xd9, 0x95,
-            0xc9, 0x4d, 0x9a, 0x69, 0xc9, 0xf1, 0xb5, 0xf4,
-            0x99, 0x02, 0x7a, 0x78, 0x57, 0x2a, 0xee, 0xbd,
-            0x74, 0xd2, 0x0c, 0xc3, 0x98, 0x81, 0xc2, 0x13,
-            0xee, 0x77, 0x0b, 0x10, 0x10, 0xe4, 0xbe, 0xa7,
-            0x18, 0x84, 0x69, 0x77, 0xae, 0x11, 0x9f, 0x7a,
-            0x02, 0x3a, 0xb5, 0x8c, 0xca, 0x0a, 0xd7, 0x52,
-            0xaf, 0xe6, 0x56, 0xbb, 0x3c, 0x17, 0x25, 0x6a,
-            0x9f, 0x6e, 0x9b, 0xf1, 0x9f, 0xdd, 0x5a, 0x38,
-            0xfc, 0x82, 0xbb, 0xe8, 0x72, 0xc5, 0x53, 0x9e,
-            0xdb, 0x60, 0x9e, 0xf4, 0xf7, 0x9c, 0x20, 0x3e,
-            0xbb, 0x14, 0x0f, 0x2e, 0x58, 0x3c, 0xb2, 0xad,
-            0x15, 0xb4, 0xaa, 0x5b, 0x65, 0x50, 0x16, 0xa8,
-            0x44, 0x92, 0x77, 0xdb, 0xd4, 0x77, 0xef, 0x2c,
-            0x8d, 0x6c, 0x01, 0x7d, 0xb7, 0x38, 0xb1, 0x8d,
-            0xeb, 0x4a, 0x42, 0x7d, 0x19, 0x23, 0xce, 0x3f,
-            0xf2, 0x62, 0x73, 0x57, 0x79, 0xa4, 0x18, 0xf2,
-            0x0a, 0x28, 0x2d, 0xf9, 0x20, 0x14, 0x7b, 0xea,
-            0xbe, 0x42, 0x1e, 0xe5, 0x31, 0x9d, 0x05, 0x68,
-        }
-    },
-
-    /* #7, 32 byte key, 17 byte PTX */
-    {
-        "/crypto/xts/t-7-key-32-ptx-17",
-        32,
-        { 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
-          0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
-        { 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
-          0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
-        0x123456789aLL,
-        17,
-        { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-          0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 },
-        { 0x6c, 0x16, 0x25, 0xdb, 0x46, 0x71, 0x52, 0x2d,
-          0x3d, 0x75, 0x99, 0x60, 0x1d, 0xe7, 0xca, 0x09, 0xed },
-    },
-
-    /* #15, 32 byte key, 25 byte PTX */
-    {
-        "/crypto/xts/t-15-key-32-ptx-25",
-        32,
-        { 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
-          0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
-        { 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
-          0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
-        0x123456789aLL,
-        25,
-        { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-          0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-          0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 },
-        { 0x8f, 0x4d, 0xcb, 0xad, 0x55, 0x55, 0x8d, 0x7b,
-          0x4e, 0x01, 0xd9, 0x37, 0x9c, 0xd4, 0xea, 0x22,
-          0xed, 0xbf, 0x9d, 0xac, 0xe4, 0x5d, 0x6f, 0x6a, 0x73 },
-    },
-
-    /* #21, 32 byte key, 31 byte PTX */
-    {
-        "/crypto/xts/t-21-key-32-ptx-31",
-        32,
-        { 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
-          0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
-        { 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
-          0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
-        0x123456789aLL,
-        31,
-        { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-          0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-          0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-          0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e },
-        { 0xd0, 0x5b, 0xc0, 0x90, 0xa8, 0xe0, 0x4f, 0x1b,
-          0x3d, 0x3e, 0xcd, 0xd5, 0xba, 0xec, 0x0f, 0xd4,
-          0xed, 0xbf, 0x9d, 0xac, 0xe4, 0x5d, 0x6f, 0x6a,
-          0x73, 0x06, 0xe6, 0x4b, 0xe5, 0xdd, 0x82 },
-    },
-};
-
-#define STORE64L(x, y)                                                  \
-    do {                                                                \
-        (y)[7] = (unsigned char)(((x) >> 56) & 255);                    \
-        (y)[6] = (unsigned char)(((x) >> 48) & 255);                    \
-        (y)[5] = (unsigned char)(((x) >> 40) & 255);                    \
-        (y)[4] = (unsigned char)(((x) >> 32) & 255);                    \
-        (y)[3] = (unsigned char)(((x) >> 24) & 255);                    \
-        (y)[2] = (unsigned char)(((x) >> 16) & 255);                    \
-        (y)[1] = (unsigned char)(((x) >> 8) & 255);                     \
-        (y)[0] = (unsigned char)((x) & 255);                            \
-    } while (0)
-
-struct TestAES {
-    AES_KEY enc;
-    AES_KEY dec;
-};
-
-static void test_xts_aes_encrypt(const void *ctx,
-                                 size_t length,
-                                 uint8_t *dst,
-                                 const uint8_t *src)
-{
-    const struct TestAES *aesctx = ctx;
-
-    AES_encrypt(src, dst, &aesctx->enc);
-}
-
-
-static void test_xts_aes_decrypt(const void *ctx,
-                                 size_t length,
-                                 uint8_t *dst,
-                                 const uint8_t *src)
-{
-    const struct TestAES *aesctx = ctx;
-
-    AES_decrypt(src, dst, &aesctx->dec);
-}
-
-
-static void test_xts(const void *opaque)
-{
-    const QCryptoXTSTestData *data = opaque;
-    uint8_t out[512], Torg[16], T[16];
-    uint64_t seq;
-    struct TestAES aesdata;
-    struct TestAES aestweak;
-
-    AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
-    AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
-    AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
-    AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
-
-    seq = data->seqnum;
-    STORE64L(seq, Torg);
-    memset(Torg + 8, 0, 8);
-
-    memcpy(T, Torg, sizeof(T));
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out, data->PTX);
-
-    g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
-
-    memcpy(T, Torg, sizeof(T));
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out, data->CTX);
-
-    g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
-}
-
-
-static void test_xts_split(const void *opaque)
-{
-    const QCryptoXTSTestData *data = opaque;
-    uint8_t out[512], Torg[16], T[16];
-    uint64_t seq;
-    unsigned long len = data->PTLEN / 2;
-    struct TestAES aesdata;
-    struct TestAES aestweak;
-
-    AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
-    AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
-    AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
-    AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
-
-    seq = data->seqnum;
-    STORE64L(seq, Torg);
-    memset(Torg + 8, 0, 8);
-
-    memcpy(T, Torg, sizeof(T));
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, len, out, data->PTX);
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, len, &out[len], &data->PTX[len]);
-
-    g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
-
-    memcpy(T, Torg, sizeof(T));
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, len, out, data->CTX);
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, len, &out[len], &data->CTX[len]);
-
-    g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
-}
-
-
-static void test_xts_unaligned(const void *opaque)
-{
-#define BAD_ALIGN 3
-    const QCryptoXTSTestData *data = opaque;
-    uint8_t in[512 + BAD_ALIGN], out[512 + BAD_ALIGN];
-    uint8_t Torg[16], T[16 + BAD_ALIGN];
-    uint64_t seq;
-    struct TestAES aesdata;
-    struct TestAES aestweak;
-
-    AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
-    AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
-    AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
-    AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
-
-    seq = data->seqnum;
-    STORE64L(seq, Torg);
-    memset(Torg + 8, 0, 8);
-
-    /* IV not aligned */
-    memcpy(T + BAD_ALIGN, Torg, 16);
-    memcpy(in, data->PTX, data->PTLEN);
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T + BAD_ALIGN, data->PTLEN, out, in);
-
-    g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
-
-    /* plain text not aligned */
-    memcpy(T, Torg, 16);
-    memcpy(in + BAD_ALIGN, data->PTX, data->PTLEN);
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out, in + BAD_ALIGN);
-
-    g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
-
-    /* cipher text not aligned */
-    memcpy(T, Torg, 16);
-    memcpy(in, data->PTX, data->PTLEN);
-    xts_encrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out + BAD_ALIGN, in);
-
-    g_assert(memcmp(out + BAD_ALIGN, data->CTX, data->PTLEN) == 0);
-
-
-    /* IV not aligned */
-    memcpy(T + BAD_ALIGN, Torg, 16);
-    memcpy(in, data->CTX, data->PTLEN);
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T + BAD_ALIGN, data->PTLEN, out, in);
-
-    g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
-
-    /* cipher text not aligned */
-    memcpy(T, Torg, 16);
-    memcpy(in + BAD_ALIGN, data->CTX, data->PTLEN);
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out, in + BAD_ALIGN);
-
-    g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
-
-    /* plain text not aligned */
-    memcpy(T, Torg, 16);
-    memcpy(in, data->CTX, data->PTLEN);
-    xts_decrypt(&aesdata, &aestweak,
-                test_xts_aes_encrypt,
-                test_xts_aes_decrypt,
-                T, data->PTLEN, out + BAD_ALIGN, in);
-
-    g_assert(memcmp(out + BAD_ALIGN, data->PTX, data->PTLEN) == 0);
-}
-
-
-int main(int argc, char **argv)
-{
-    size_t i;
-
-    g_test_init(&argc, &argv, NULL);
-
-    g_assert(qcrypto_init(NULL) == 0);
-
-    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
-        gchar *path = g_strdup_printf("%s/basic", test_data[i].path);
-        g_test_add_data_func(path, &test_data[i], test_xts);
-        g_free(path);
-
-        /* skip the cases where the length is smaller than 2*blocklen
-         * or the length is not a multiple of 32
-         */
-        if ((test_data[i].PTLEN >= 32) && !(test_data[i].PTLEN % 32)) {
-            path = g_strdup_printf("%s/split", test_data[i].path);
-            g_test_add_data_func(path, &test_data[i], test_xts_split);
-            g_free(path);
-        }
-
-        path = g_strdup_printf("%s/unaligned", test_data[i].path);
-        g_test_add_data_func(path, &test_data[i], test_xts_unaligned);
-        g_free(path);
-    }
-
-    return g_test_run();
-}
diff --git a/ui/vnc.c b/ui/vnc.c
index 0094ec6..50016ff 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -578,7 +578,6 @@
 bool vnc_display_reload_certs(const char *id, Error **errp)
 {
     VncDisplay *vd = vnc_display_find(id);
-    QCryptoTLSCredsClass *creds = NULL;
 
     if (!vd) {
         error_setg(errp, "Can not find vnc display");
@@ -590,13 +589,7 @@
         return false;
     }
 
-    creds = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(vd->tlscreds));
-    if (creds->reload == NULL) {
-        error_setg(errp, "%s doesn't support to reload TLS credential",
-                   object_get_typename(OBJECT(vd->tlscreds)));
-        return false;
-    }
-    if (!creds->reload(vd->tlscreds, errp)) {
+    if (!qcrypto_tls_creds_reload(vd->tlscreds, errp)) {
         return false;
     }