[PATCH 00/21] crypto: support multiple parallel certificate identities
This series aims to improve the support for post-quantum cryptography in TLS connections by allowing multiple sets of certificates to be loaded. The idea is that during a transition period servers will have a traditional RSA based certificate in parallel with an MLDSA based certificate for PQC, and the right one will be dynamically determined during the TLS handshake. The first 12 patches are trivial cleanups. The next 3 patches fix a potential use-after-free problem The last patches introduce support for multiple certificates. NB, in terms of testing this will require either CentOS Stream 10, or Fedora 43. Most other distros will not support PQC out of the box at this time even if they have new enough gnutls, since they don't make use of the crypto-policies package which is needed to enable PQC by default. Daniel P. Berrangé (21): crypto: remove redundant parameter checking CA certs crypto: add missing free of certs array crypto: replace stat() with access() for credential checks crypto: remove redundant access() checks before loading certs crypto: move check for TLS creds 'dir' property crypto: use g_autofree when loading x509 credentials crypto: remove needless indirection via parent_obj field crypto: move release of DH parameters into TLS creds parent crypto: shorten the endpoint == server check in TLS creds crypto: remove duplication loading x509 CA cert crypto: reduce duplication in handling TLS priority strings crypto: introduce method for reloading TLS creds crypto: introduce a wrapper around gnutls credentials crypto: fix lifecycle handling of gnutls credentials objects crypto: make TLS credentials structs private crypto: deprecate use of external dh-params.pem file crypto: avoid loading the CA certs twice crypto: avoid loading the identity certs twice crypto: expand logic to cope with multiple certificate identities crypto: support upto 5 parallel certificate identities docs: creation of x509 certs compliant with post-quantum crypto crypto/meson.build | 5 +- crypto/tlscreds.c | 77 ++-- crypto/tlscredsanon.c | 62 +-- crypto/tlscredsbox.c | 101 +++++ crypto/tlscredsbox.h | 46 ++ crypto/tlscredspriv.h | 36 +- crypto/tlscredspsk.c | 64 ++- crypto/tlscredsx509.c | 592 +++++++++++++++++--------- crypto/tlssession.c | 139 ++---- crypto/trace-events | 1 + docs/about/deprecated.rst | 9 + docs/system/tls.rst | 134 +++++- include/crypto/tlscreds.h | 26 ++ include/crypto/tlscredsx509.h | 6 + tests/unit/test-crypto-tlscredsx509.c | 8 +- ui/vnc.c | 9 +- 16 files changed, 849 insertions(+), 466 deletions(-) create mode 100644 crypto/tlscredsbox.c create mode 100644 crypto/tlscredsbox.h -- 2.51.1
The only caller of qcrypto_tls_creds_check_authority_chain always passes 'true' for the 'isCA' parameter. The point of this method is to check the CA chani, so no other value would ever make sense. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index db2b74bafa..847fd4d9fa 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -315,7 +315,6 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, unsigned int ncacerts, const char *cacertFile, bool isServer, - bool isCA, Error **errp) { gnutls_x509_crt_t cert_to_check = certs[ncerts - 1]; @@ -356,7 +355,7 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, */ return qcrypto_tls_creds_check_cert( creds, cert_to_check, cacertFile, - isServer, isCA, errp); + isServer, true, errp); } for (int i = 0; i < ncacerts; i++) { if (gnutls_x509_crt_check_issuer(cert_to_check, @@ -370,7 +369,7 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, } if (qcrypto_tls_creds_check_cert(creds, cert_issuer, cacertFile, - isServer, isCA, errp) < 0) { + isServer, true, errp) < 0) { return -1; } @@ -534,7 +533,7 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, certs, ncerts, cacerts, ncacerts, cacertFile, isServer, - true, errp) < 0) { + errp) < 0) { goto cleanup; } -- 2.51.1
On 30/10/25 15:49, Daniel P. Berrangé wrote:
The only caller of qcrypto_tls_creds_check_authority_chain always passes 'true' for the 'isCA' parameter. The point of this method is to check the CA chani, so no other value would ever make sense.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 847fd4d9fa..75c70af522 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -550,6 +550,7 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, for (i = 0; i < ncerts; i++) { gnutls_x509_crt_deinit(certs[i]); } + g_free(certs); for (i = 0; i < ncacerts; i++) { gnutls_x509_crt_deinit(cacerts[i]); } -- 2.51.1
Readability of the credential files is what matters for our usage, so access() is more appropriate than stat(). Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 9e59594d67..208a7e6d8f 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -100,7 +100,6 @@ qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, char **cred, Error **errp) { - struct stat sb; int ret = -1; if (!creds->dir) { @@ -114,7 +113,7 @@ qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, *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 { -- 2.51.1
On 30/10/25 15:49, Daniel P. Berrangé wrote:
Readability of the credential files is what matters for our usage, so access() is more appropriate than stat().
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
The qcrypto_tls_creds_get_path method will perform an access() check on the file and return a NULL path if it fails. By the time we get to loading the cert files we know they must exist on disk and thus the second access() check is redundant. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 75c70af522..0acb17b6ec 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -496,8 +496,7 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, size_t i; int ret = -1; - if (certFile && - access(certFile, R_OK) == 0) { + if (certFile) { if (qcrypto_tls_creds_load_cert_list(creds, certFile, &certs, @@ -508,16 +507,15 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, goto cleanup; } } - if (access(cacertFile, R_OK) == 0) { - if (qcrypto_tls_creds_load_cert_list(creds, - cacertFile, - &cacerts, - &ncacerts, - isServer, - true, - errp) < 0) { - goto cleanup; - } + + if (qcrypto_tls_creds_load_cert_list(creds, + cacertFile, + &cacerts, + &ncacerts, + isServer, + true, + errp) < 0) { + goto cleanup; } for (i = 0; i < ncerts; i++) { -- 2.51.1
The check for the 'dir' property is being repeated for every credential file to be loaded, but this results in incorrect logic for optional credentials. The 'dir' property is mandatory for PSK and x509 creds, even if some individual files are optional. Address this by separating the check for the 'dir' property. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 9 --------- crypto/tlscredsanon.c | 3 ++- crypto/tlscredspsk.c | 5 +++++ crypto/tlscredsx509.c | 8 ++++++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 208a7e6d8f..65e97ddd11 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -102,15 +102,6 @@ qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, { int ret = -1; - if (!creds->dir) { - if (required) { - error_setg(errp, "Missing 'dir' property value"); - return -1; - } else { - return 0; - } - } - *cred = g_strdup_printf("%s/%s", creds->dir, filename); if (access(*cred, R_OK) < 0) { diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 44af9e6c9a..bc3351b5d6 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -43,7 +43,8 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, 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, + if (creds->parent_obj.dir && + qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_DH_PARAMS, false, &dhparams, errp) < 0) { return -1; diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 5b68a6b7ba..545d3e45db 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -81,6 +81,11 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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) { if (creds->username) { error_setg(errp, "username should not be set when endpoint=server"); diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 0acb17b6ec..8fe6cc8e93 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -567,8 +567,12 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, 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) { if (qcrypto_tls_creds_get_path(&creds->parent_obj, -- 2.51.1
This allows removal of goto jumps during loading of the credentials and will simplify the diff in following commits. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 8fe6cc8e93..e5b869a35f 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -562,10 +562,12 @@ static int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) { - char *cacert = NULL, *cacrl = NULL, *cert = NULL, - *key = NULL, *dhparams = NULL; + g_autofree char *cacert = NULL; + g_autofree char *cacrl = NULL; + g_autofree char *cert = NULL; + g_autofree char *key = NULL; + g_autofree char *dhparams = NULL; int ret; - int rv = -1; if (!creds->parent_obj.dir) { error_setg(errp, "Missing 'dir' property value"); @@ -590,7 +592,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_DH_PARAMS, false, &dhparams, errp) < 0) { - goto cleanup; + return -1; } } else { if (qcrypto_tls_creds_get_path(&creds->parent_obj, @@ -602,7 +604,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, false, &key, errp) < 0) { - goto cleanup; + return -1; } } @@ -610,14 +612,14 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, qcrypto_tls_creds_x509_sanity_check(creds, creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, cacert, cert, errp) < 0) { - goto cleanup; + return -1; } ret = gnutls_certificate_allocate_credentials(&creds->data); if (ret < 0) { error_setg(errp, "Cannot allocate credentials: '%s'", gnutls_strerror(ret)); - goto cleanup; + return -1; } ret = gnutls_certificate_set_x509_trust_file(creds->data, @@ -626,7 +628,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, if (ret < 0) { error_setg(errp, "Cannot load CA certificate '%s': %s", cacert, gnutls_strerror(ret)); - goto cleanup; + return -1; } if (cert != NULL && key != NULL) { @@ -635,7 +637,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, password = qcrypto_secret_lookup_as_utf8(creds->passwordid, errp); if (!password) { - goto cleanup; + return -1; } } ret = gnutls_certificate_set_x509_key_file2(creds->data, @@ -647,7 +649,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, if (ret < 0) { error_setg(errp, "Cannot load certificate '%s' & key '%s': %s", cert, key, gnutls_strerror(ret)); - goto cleanup; + return -1; } } @@ -658,7 +660,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, if (ret < 0) { error_setg(errp, "Cannot load CRL '%s': %s", cacrl, gnutls_strerror(ret)); - goto cleanup; + return -1; } } @@ -666,20 +668,13 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, &creds->parent_obj.dh_params, errp) < 0) { - goto cleanup; + return -1; } gnutls_certificate_set_dh_params(creds->data, creds->parent_obj.dh_params); } - rv = 0; - cleanup: - g_free(cacert); - g_free(cacrl); - g_free(cert); - g_free(key); - g_free(dhparams); - return rv; + return 0; } -- 2.51.1
On 30/10/25 15:49, Daniel P. Berrangé wrote:
This allows removal of goto jumps during loading of the credentials and will simplify the diff in following commits.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
The reload method already has a pointer to the parent object in the 'creds' parameter that is passed in, so indirect access via the subclass 'parent_obj' field is redundant. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index e5b869a35f..39f80b33ad 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -773,15 +773,15 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) 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; + gnutls_dh_params_t creds_dh_params = creds->dh_params; x509_creds->data = NULL; - x509_creds->parent_obj.dh_params = NULL; + creds->dh_params = 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->dh_params = creds_dh_params; error_propagate(errp, local_err); return false; } -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The reload method already has a pointer to the parent object in the 'creds' parameter that is passed in, so indirect access via the subclass 'parent_obj' field is redundant.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscredsx509.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index e5b869a35f..39f80b33ad 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -773,15 +773,15 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) 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; + gnutls_dh_params_t creds_dh_params = creds->dh_params;
x509_creds->data = NULL; - x509_creds->parent_obj.dh_params = NULL; + creds->dh_params = 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->dh_params = creds_dh_params; error_propagate(errp, local_err); return false; } -- 2.51.1
On 30/10/25 15:49, Daniel P. Berrangé wrote:
The reload method already has a pointer to the parent object in the 'creds' parameter that is passed in, so indirect access via the subclass 'parent_obj' field is redundant.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
The code for releasing DH parameters is common to all credential subclasses, so can be moved into the parent. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 4 ++++ crypto/tlscredsanon.c | 4 ---- crypto/tlscredspsk.c | 4 ---- crypto/tlscredsx509.c | 7 +++---- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 65e97ddd11..1e39ee1141 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -246,6 +246,10 @@ qcrypto_tls_creds_finalize(Object *obj) { QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + if (creds->dh_params) { + gnutls_dh_params_deinit(creds->dh_params); + } + g_free(creds->dir); g_free(creds->priority); } diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index bc3351b5d6..1ddfe4eb31 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -92,10 +92,6 @@ qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds) 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 */ diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 545d3e45db..bf4efe2114 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -175,10 +175,6 @@ qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds) 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 */ diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 39f80b33ad..1555285910 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -685,10 +685,6 @@ qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) 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; - } } @@ -780,6 +776,9 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) qcrypto_tls_creds_x509_load(x509_creds, &local_err); if (local_err) { qcrypto_tls_creds_x509_unload(x509_creds); + if (creds->dh_params) { + gnutls_dh_params_deinit(creds->dh_params); + } x509_creds->data = creds_data; creds->dh_params = creds_dh_params; error_propagate(errp, local_err); -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The code for releasing DH parameters is common to all credential subclasses, so can be moved into the parent.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
and unload() was only called from finalize (and qcrypto_tls_creds_x509_reload()) Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 4 ++++ crypto/tlscredsanon.c | 4 ---- crypto/tlscredspsk.c | 4 ---- crypto/tlscredsx509.c | 7 +++---- 4 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 65e97ddd11..1e39ee1141 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -246,6 +246,10 @@ qcrypto_tls_creds_finalize(Object *obj) { QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+ if (creds->dh_params) { + gnutls_dh_params_deinit(creds->dh_params); + } + g_free(creds->dir); g_free(creds->priority); } diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index bc3351b5d6..1ddfe4eb31 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -92,10 +92,6 @@ qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds) 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 */ diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 545d3e45db..bf4efe2114 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -175,10 +175,6 @@ qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds) 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 */ diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 39f80b33ad..1555285910 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -685,10 +685,6 @@ qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) 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; - } }
@@ -780,6 +776,9 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) qcrypto_tls_creds_x509_load(x509_creds, &local_err); if (local_err) { qcrypto_tls_creds_x509_unload(x509_creds); + if (creds->dh_params) { + gnutls_dh_params_deinit(creds->dh_params); + } x509_creds->data = creds_data; creds->dh_params = creds_dh_params; error_propagate(errp, local_err); -- 2.51.1
This eliminates a number of long lines aiding readability. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 1555285910..08223781d7 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -567,6 +567,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, g_autofree char *cert = NULL; g_autofree char *key = NULL; g_autofree char *dhparams = NULL; + bool isServer = (creds->parent_obj.endpoint == + QCRYPTO_TLS_CREDS_ENDPOINT_SERVER); int ret; if (!creds->parent_obj.dir) { @@ -576,7 +578,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, trace_qcrypto_tls_creds_x509_load(creds, creds->parent_obj.dir); - if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (isServer) { if (qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_X509_CA_CERT, true, &cacert, errp) < 0 || @@ -609,9 +611,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, - creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, - cacert, cert, errp) < 0) { + qcrypto_tls_creds_x509_sanity_check(creds, isServer, + cacert, cert, errp) < 0) { return -1; } @@ -664,7 +665,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } } - 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, errp) < 0) { -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
This eliminates a number of long lines aiding readability.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> ---
crypto/tlscredsx509.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 1555285910..08223781d7 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -567,6 +567,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, g_autofree char *cert = NULL; g_autofree char *key = NULL; g_autofree char *dhparams = NULL; + bool isServer = (creds->parent_obj.endpoint == + QCRYPTO_TLS_CREDS_ENDPOINT_SERVER); int ret;
if (!creds->parent_obj.dir) { @@ -576,7 +578,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
trace_qcrypto_tls_creds_x509_load(creds, creds->parent_obj.dir);
- if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (isServer) { if (qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_X509_CA_CERT, true, &cacert, errp) < 0 || @@ -609,9 +611,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, }
if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, - creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, - cacert, cert, errp) < 0) { + qcrypto_tls_creds_x509_sanity_check(creds, isServer, + cacert, cert, errp) < 0) { return -1; }
@@ -664,7 +665,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } }
- 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, errp) < 0) { -- 2.51.1
The CA cert is mandatory in both client and server scenarios. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 08223781d7..f2f1aa2815 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -578,11 +578,14 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, trace_qcrypto_tls_creds_x509_load(creds, creds->parent_obj.dir); + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, 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, @@ -598,9 +601,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } } 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, -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The CA cert is mandatory in both client and server scenarios.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> ---
crypto/tlscredsx509.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 08223781d7..f2f1aa2815 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -578,11 +578,14 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
trace_qcrypto_tls_creds_x509_load(creds, creds->parent_obj.dir);
+ if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, 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, @@ -598,9 +601,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } } 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, -- 2.51.1
The logic for setting the TLS priority string on a session object has a significant amount of logic duplication across the different credential types. By recording the extra priority string suffix against the credential class, we can introduce a common method for building the priority string. The TLS session can now set the priority string without caring about the credential type. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 15 ++++++++++ crypto/tlscredsanon.c | 2 ++ crypto/tlscredspsk.c | 2 ++ crypto/tlssession.c | 60 ++++++--------------------------------- include/crypto/tlscreds.h | 13 +++++++++ 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 1e39ee1141..49c7eb46a5 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -266,6 +266,21 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, 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); + } +} + + 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 1ddfe4eb31..5c55b07b2f 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -137,8 +137,10 @@ 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"; } diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index bf4efe2114..6c2feae077 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -240,8 +240,10 @@ static void 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/tlssession.c b/crypto/tlssession.c index 92fe4f0380..77f334add3 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -155,9 +155,6 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) } } -#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 +164,7 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, { QCryptoTLSSession *session; int ret; + g_autofree char *prio = NULL; session = g_new0(QCryptoTLSSession, 1); trace_qcrypto_tls_session_new( @@ -200,28 +198,17 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, goto error; } + 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; + } + 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, @@ -239,25 +226,6 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, } 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, @@ -275,17 +243,7 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, } 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); diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h index 2a8a857010..afd1016088 100644 --- a/include/crypto/tlscreds.h +++ b/include/crypto/tlscreds.h @@ -47,6 +47,7 @@ typedef bool (*CryptoTLSCredsReload)(QCryptoTLSCreds *, Error **); struct QCryptoTLSCredsClass { ObjectClass parent_class; CryptoTLSCredsReload reload; + const char *prioritySuffix; }; /** @@ -64,4 +65,16 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, 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); + #endif /* QCRYPTO_TLSCREDS_H */ -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The logic for setting the TLS priority string on a session object has a significant amount of logic duplication across the different credential types. By recording the extra priority string suffix against the credential class, we can introduce a common method for building the priority string. The TLS session can now set the priority string without caring about the credential type.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 15 ++++++++++ crypto/tlscredsanon.c | 2 ++ crypto/tlscredspsk.c | 2 ++ crypto/tlssession.c | 60 ++++++--------------------------------- include/crypto/tlscreds.h | 13 +++++++++ 5 files changed, 41 insertions(+), 51 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 1e39ee1141..49c7eb46a5 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -266,6 +266,21 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, 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); + } +} + + 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 1ddfe4eb31..5c55b07b2f 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -137,8 +137,10 @@ 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"; }
diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index bf4efe2114..6c2feae077 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -240,8 +240,10 @@ static void 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/tlssession.c b/crypto/tlssession.c index 92fe4f0380..77f334add3 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -155,9 +155,6 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) } }
-#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 +164,7 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, { QCryptoTLSSession *session; int ret; + g_autofree char *prio = NULL;
session = g_new0(QCryptoTLSSession, 1); trace_qcrypto_tls_session_new( @@ -200,28 +198,17 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, goto error; }
+ 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; + } + 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, @@ -239,25 +226,6 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, } 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, @@ -275,17 +243,7 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, } 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); diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h index 2a8a857010..afd1016088 100644 --- a/include/crypto/tlscreds.h +++ b/include/crypto/tlscreds.h @@ -47,6 +47,7 @@ typedef bool (*CryptoTLSCredsReload)(QCryptoTLSCreds *, Error **); struct QCryptoTLSCredsClass { ObjectClass parent_class; CryptoTLSCredsReload reload; + const char *prioritySuffix;
sneaking camelCase to scare the snakes? anyhow: Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
};
/** @@ -64,4 +65,16 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, 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); + #endif /* QCRYPTO_TLSCREDS_H */ -- 2.51.1
This prevents direct access of the class members by the VNC display code. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 15 +++++++++++++++ include/crypto/tlscreds.h | 13 +++++++++++++ ui/vnc.c | 9 +-------- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 49c7eb46a5..9433b4c363 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -281,6 +281,21 @@ char *qcrypto_tls_creds_get_priority(QCryptoTLSCreds *creds) } +bool qcrypto_tls_creds_reload(QCryptoTLSCreds *creds, + Error **errp) +{ + QCryptoTLSCredsClass *credscls = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(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/include/crypto/tlscreds.h b/include/crypto/tlscreds.h index afd1016088..bb9280ed1a 100644 --- a/include/crypto/tlscreds.h +++ b/include/crypto/tlscreds.h @@ -77,4 +77,17 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, */ 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/ui/vnc.c b/ui/vnc.c index 77c823bf2e..6b32dd0fe9 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -578,7 +578,6 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp) 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 @@ bool vnc_display_reload_certs(const char *id, Error **errp) 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; } -- 2.51.1
On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
This prevents direct access of the class members by the VNC display code.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 15 +++++++++++++++ include/crypto/tlscreds.h | 13 +++++++++++++ ui/vnc.c | 9 +-------- 3 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 49c7eb46a5..9433b4c363 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -281,6 +281,21 @@ char *qcrypto_tls_creds_get_priority(QCryptoTLSCreds *creds) }
+bool qcrypto_tls_creds_reload(QCryptoTLSCreds *creds, + Error **errp) +{ + QCryptoTLSCredsClass *credscls = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(creds)); +
OBJECT() unnecessary here
+ 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/include/crypto/tlscreds.h b/include/crypto/tlscreds.h index afd1016088..bb9280ed1a 100644 --- a/include/crypto/tlscreds.h +++ b/include/crypto/tlscreds.h @@ -77,4 +77,17 @@ bool qcrypto_tls_creds_check_endpoint(QCryptoTLSCreds *creds, */ 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/ui/vnc.c b/ui/vnc.c index 77c823bf2e..6b32dd0fe9 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -578,7 +578,6 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp) 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 @@ bool vnc_display_reload_certs(const char *id, Error **errp) 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; }
-- 2.51.1
On Thu, Oct 30, 2025 at 11:43:29PM +0400, Marc-André Lureau wrote:
On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
This prevents direct access of the class members by the VNC display code.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 15 +++++++++++++++ include/crypto/tlscreds.h | 13 +++++++++++++ ui/vnc.c | 9 +-------- 3 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 49c7eb46a5..9433b4c363 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -281,6 +281,21 @@ char *qcrypto_tls_creds_get_priority(QCryptoTLSCreds *creds) }
+bool qcrypto_tls_creds_reload(QCryptoTLSCreds *creds, + Error **errp) +{ + QCryptoTLSCredsClass *credscls = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(creds)); +
OBJECT() unnecessary here
Ah yes, will remove it. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
The gnutls_credentials_set() method has a very suprising API contract that requires the caller to preserve the passed in credentials pointer for as long as the gnutls_session_t object is alive. QEMU is failing to ensure this happens. In QEMU the GNUTLS credentials object is owned by the QCryptoTLSCreds object instance while the GNUTLS session object is owned by the QCryptoTLSSession object instance. Their lifetimes are not guaranteed to be the same, though in most common usage the credentials will outlive the session. This is notably not the case, however, after the VNC server gained the ability to reload credentials on the fly with: commit 1f08e3415120637cad7f540d9ceb4dba3136dbdd Author: Zihao Chang <changzihao1@huawei.com> Date: Tue Mar 16 15:58:44 2021 +0800 vnc: support reload x509 certificates for vnc If that is triggered while a VNC client is in the middle of performing a TLS handshake, we might hit a use-after-free. It is difficult to correct this problem because there's no way to deep- clone a GNUTLS credentials object, nor is it reference counted. Thus we introduce a QCryptoTLSCredsBox object whose only purpose is to add reference counting around the GNUTLS credentials object. The DH parameters set against a credentials object also have to be kept alive for as long as the credentials exist. So the box must also hold the DH parameters pointer. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/meson.build | 5 ++- crypto/tlscredsbox.c | 101 +++++++++++++++++++++++++++++++++++++++++++ crypto/tlscredsbox.h | 46 ++++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 crypto/tlscredsbox.c create mode 100644 crypto/tlscredsbox.h diff --git a/crypto/meson.build b/crypto/meson.build index 735635de1f..1fc14b2a04 100644 --- a/crypto/meson.build +++ b/crypto/meson.build @@ -25,7 +25,10 @@ crypto_ss.add(files( )) if gnutls.found() - crypto_ss.add(files('x509-utils.c')) + crypto_ss.add(files( + 'tlscredsbox.c', + 'x509-utils.c', + )) endif if nettle.found() diff --git a/crypto/tlscredsbox.c b/crypto/tlscredsbox.c new file mode 100644 index 0000000000..b8d9846af8 --- /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 0000000000..5d89335f46 --- /dev/null +++ b/crypto/tlscredsbox.h @@ -0,0 +1,46 @@ +/* + * 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 + 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 */ -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The gnutls_credentials_set() method has a very suprising API contract that requires the caller to preserve the passed in credentials pointer for as long as the gnutls_session_t object is alive. QEMU is failing to ensure this happens.
In QEMU the GNUTLS credentials object is owned by the QCryptoTLSCreds object instance while the GNUTLS session object is owned by the QCryptoTLSSession object instance. Their lifetimes are not guaranteed to be the same, though in most common usage the credentials will outlive the session. This is notably not the case, however, after the VNC server gained the ability to reload credentials on the fly with:
commit 1f08e3415120637cad7f540d9ceb4dba3136dbdd Author: Zihao Chang <changzihao1@huawei.com> Date: Tue Mar 16 15:58:44 2021 +0800
vnc: support reload x509 certificates for vnc
If that is triggered while a VNC client is in the middle of performing a TLS handshake, we might hit a use-after-free.
It is difficult to correct this problem because there's no way to deep- clone a GNUTLS credentials object, nor is it reference counted. Thus we introduce a QCryptoTLSCredsBox object whose only purpose is to add reference counting around the GNUTLS credentials object.
The DH parameters set against a credentials object also have to be kept alive for as long as the credentials exist. So the box must also hold the DH parameters pointer.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/meson.build | 5 ++- crypto/tlscredsbox.c | 101 +++++++++++++++++++++++++++++++++++++++++++ crypto/tlscredsbox.h | 46 ++++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 crypto/tlscredsbox.c create mode 100644 crypto/tlscredsbox.h
diff --git a/crypto/meson.build b/crypto/meson.build index 735635de1f..1fc14b2a04 100644 --- a/crypto/meson.build +++ b/crypto/meson.build @@ -25,7 +25,10 @@ crypto_ss.add(files( ))
if gnutls.found() - crypto_ss.add(files('x509-utils.c')) + crypto_ss.add(files( + 'tlscredsbox.c', + 'x509-utils.c', + )) endif
if nettle.found() diff --git a/crypto/tlscredsbox.c b/crypto/tlscredsbox.c new file mode 100644 index 0000000000..b8d9846af8 --- /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 0000000000..5d89335f46 --- /dev/null +++ b/crypto/tlscredsbox.h @@ -0,0 +1,46 @@ +/* + * 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;
since any is used in code to cast the variant to a void *, it may be worth a comment to say that all fields are expected to be pointers.
+#ifdef CONFIG_GNUTLS + 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 */ -- 2.51.1
On Fri, Oct 31, 2025 at 03:23:51PM +0400, Marc-André Lureau wrote:
Hi
On Thu, Oct 30, 2025 at 6:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The gnutls_credentials_set() method has a very suprising API contract that requires the caller to preserve the passed in credentials pointer for as long as the gnutls_session_t object is alive. QEMU is failing to ensure this happens.
In QEMU the GNUTLS credentials object is owned by the QCryptoTLSCreds object instance while the GNUTLS session object is owned by the QCryptoTLSSession object instance. Their lifetimes are not guaranteed to be the same, though in most common usage the credentials will outlive the session. This is notably not the case, however, after the VNC server gained the ability to reload credentials on the fly with:
commit 1f08e3415120637cad7f540d9ceb4dba3136dbdd Author: Zihao Chang <changzihao1@huawei.com> Date: Tue Mar 16 15:58:44 2021 +0800
vnc: support reload x509 certificates for vnc
If that is triggered while a VNC client is in the middle of performing a TLS handshake, we might hit a use-after-free.
It is difficult to correct this problem because there's no way to deep- clone a GNUTLS credentials object, nor is it reference counted. Thus we introduce a QCryptoTLSCredsBox object whose only purpose is to add reference counting around the GNUTLS credentials object.
The DH parameters set against a credentials object also have to be kept alive for as long as the credentials exist. So the box must also hold the DH parameters pointer.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
+#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;
since any is used in code to cast the variant to a void *, it may be worth a comment to say that all fields are expected to be pointers.
Yep, in practice almost all gnutls _t types are pointers, but I'll explain that here since it is not obvious to casual observers.
+#ifdef CONFIG_GNUTLS + 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 +}; +
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
As described in the previous commit, the gnutls credentials need to be kept alive for as long as the gnutls session object exists. Convert the QCryptoTLSCreds objects to use QCryptoTLSCredsBox and holding the gnutls credential objects. When loading the credentials into a gnutls session, store a reference to the box into the QCryptoTLSSession object. This has the useful side effect that the QCryptoTLSSession code no longer needs to know about all the different credential types, it can use the generic pointer stored in the box. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 5 +-- crypto/tlscredsanon.c | 48 +++++--------------------- crypto/tlscredspriv.h | 20 ++--------- crypto/tlscredspsk.c | 46 ++++++++----------------- crypto/tlscredsx509.c | 71 +++++++++++++------------------------- crypto/tlssession.c | 80 ++++++++++++++----------------------------- 6 files changed, 75 insertions(+), 195 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 9433b4c363..798c9712fb 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -246,10 +246,7 @@ qcrypto_tls_creds_finalize(Object *obj) { QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); - if (creds->dh_params) { - gnutls_dh_params_deinit(creds->dh_params); - } - + qcrypto_tls_creds_box_unref(creds->box); g_free(creds->dir); g_free(creds->priority); } diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 5c55b07b2f..0a728ccbf6 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -36,6 +36,7 @@ static int qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, Error **errp) { + g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autofree char *dhparams = NULL; int ret; @@ -43,6 +44,8 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + 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, @@ -50,7 +53,7 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, 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)); @@ -58,42 +61,26 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, } 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); + 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; - } - } -} - #else /* ! CONFIG_GNUTLS */ @@ -105,13 +92,6 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED, } -static void -qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */ @@ -124,15 +104,6 @@ qcrypto_tls_creds_anon_complete(UserCreatable *uc, Error **errp) } -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) { @@ -148,7 +119,6 @@ static const TypeInfo qcrypto_tls_creds_anon_info = { .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/tlscredspriv.h b/crypto/tlscredspriv.h index df9815a286..4e6dffa22f 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,39 +32,22 @@ struct QCryptoTLSCreds { Object parent_obj; char *dir; QCryptoTLSCredsEndpoint endpoint; -#ifdef CONFIG_GNUTLS - gnutls_dh_params_t dh_params; -#endif bool verifyPeer; char *priority; + QCryptoTLSCredsBox *box; }; 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; }; diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 6c2feae077..5568f1ad0c 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -71,6 +71,7 @@ static int 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; @@ -87,6 +88,8 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, } 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; @@ -101,7 +104,7 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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)); @@ -109,20 +112,23 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, } 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); + 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) { @@ -138,14 +144,14 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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", @@ -153,6 +159,7 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, goto cleanup; } } + creds->parent_obj.box = g_steal_pointer(&box); rv = 0; cleanup: @@ -160,23 +167,6 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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; - } - } -} - #else /* ! CONFIG_GNUTLS */ @@ -188,13 +178,6 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED, } -static void -qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */ @@ -212,7 +195,6 @@ qcrypto_tls_creds_psk_finalize(Object *obj) { QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj); - qcrypto_tls_creds_psk_unload(creds); g_free(creds->username); } diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index f2f1aa2815..ef31ea664c 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -562,6 +562,7 @@ static int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) { + g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autofree char *cacert = NULL; g_autofree char *cacrl = NULL; g_autofree char *cert = NULL; @@ -578,6 +579,19 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, 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; + } + if (qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_X509_CA_CERT, true, &cacert, errp) < 0) { @@ -616,14 +630,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } - ret = gnutls_certificate_allocate_credentials(&creds->data); - if (ret < 0) { - error_setg(errp, "Cannot allocate credentials: '%s'", - gnutls_strerror(ret)); - return -1; - } - - ret = gnutls_certificate_set_x509_trust_file(creds->data, + ret = gnutls_certificate_set_x509_trust_file(box->data.cert, cacert, GNUTLS_X509_FMT_PEM); if (ret < 0) { @@ -641,7 +648,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } } - ret = gnutls_certificate_set_x509_key_file2(creds->data, + ret = gnutls_certificate_set_x509_key_file2(box->data.cert, cert, key, GNUTLS_X509_FMT_PEM, password, @@ -655,7 +662,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } 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) { @@ -667,28 +674,18 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, if (isServer) { if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, - &creds->parent_obj.dh_params, + &box->dh_params, errp) < 0) { return -1; } - gnutls_certificate_set_dh_params(creds->data, - creds->parent_obj.dh_params); + gnutls_certificate_set_dh_params(box->data.cert, box->dh_params); } + creds->parent_obj.box = g_steal_pointer(&box); return 0; } -static void -qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) -{ - if (creds->data) { - gnutls_certificate_free_credentials(creds->data); - creds->data = NULL; - } -} - - #else /* ! CONFIG_GNUTLS */ @@ -700,13 +697,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED, } -static void -qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */ @@ -769,29 +759,17 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) { 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 = creds->dh_params; + QCryptoTLSCredsBox *creds_box = creds->box; - x509_creds->data = NULL; - creds->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); - if (creds->dh_params) { - gnutls_dh_params_deinit(creds->dh_params); - } - x509_creds->data = creds_data; - creds->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; } @@ -824,7 +802,6 @@ qcrypto_tls_creds_x509_finalize(Object *obj) 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 77f334add3..a1dc3b3ce0 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 @@ qcrypto_tls_session_free(QCryptoTLSSession *session) 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); @@ -206,63 +208,31 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, goto error; } - if (object_dynamic_cast(OBJECT(creds), - TYPE_QCRYPTO_TLS_CREDS_ANON)) { - QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds); - 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); - 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); + 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; + } - 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; - } + /* + * 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 (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))); - goto error; + 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); -- 2.51.1
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
As described in the previous commit, the gnutls credentials need to be kept alive for as long as the gnutls session object exists. Convert the QCryptoTLSCreds objects to use QCryptoTLSCredsBox and holding the gnutls credential objects. When loading the credentials into a gnutls session, store a reference to the box into the QCryptoTLSSession object.
This has the useful side effect that the QCryptoTLSSession code no longer needs to know about all the different credential types, it can use the generic pointer stored in the box.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 5 +-- crypto/tlscredsanon.c | 48 +++++--------------------- crypto/tlscredspriv.h | 20 ++--------- crypto/tlscredspsk.c | 46 ++++++++----------------- crypto/tlscredsx509.c | 71 +++++++++++++------------------------- crypto/tlssession.c | 80 ++++++++++++++----------------------------- 6 files changed, 75 insertions(+), 195 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 9433b4c363..798c9712fb 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -246,10 +246,7 @@ qcrypto_tls_creds_finalize(Object *obj) { QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
- if (creds->dh_params) { - gnutls_dh_params_deinit(creds->dh_params); - } - + qcrypto_tls_creds_box_unref(creds->box); g_free(creds->dir); g_free(creds->priority); } diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 5c55b07b2f..0a728ccbf6 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -36,6 +36,7 @@ static int qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, Error **errp) { + g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autofree char *dhparams = NULL; int ret;
@@ -43,6 +44,8 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>");
if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + 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, @@ -50,7 +53,7 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, 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)); @@ -58,42 +61,26 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, }
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); + 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; - } - } -} - #else /* ! CONFIG_GNUTLS */
@@ -105,13 +92,6 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED, }
-static void -qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */
@@ -124,15 +104,6 @@ qcrypto_tls_creds_anon_complete(UserCreatable *uc, Error **errp) }
-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) { @@ -148,7 +119,6 @@ static const TypeInfo qcrypto_tls_creds_anon_info = { .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/tlscredspriv.h b/crypto/tlscredspriv.h index df9815a286..4e6dffa22f 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,39 +32,22 @@ struct QCryptoTLSCreds { Object parent_obj; char *dir; QCryptoTLSCredsEndpoint endpoint; -#ifdef CONFIG_GNUTLS - gnutls_dh_params_t dh_params; -#endif bool verifyPeer; char *priority; + QCryptoTLSCredsBox *box; };
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; }; diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 6c2feae077..5568f1ad0c 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -71,6 +71,7 @@ static int 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; @@ -87,6 +88,8 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, }
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; @@ -101,7 +104,7 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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)); @@ -109,20 +112,23 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, }
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); + 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) { @@ -138,14 +144,14 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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", @@ -153,6 +159,7 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, goto cleanup; } } + creds->parent_obj.box = g_steal_pointer(&box);
rv = 0; cleanup: @@ -160,23 +167,6 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, 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; - } - } -} - #else /* ! CONFIG_GNUTLS */
@@ -188,13 +178,6 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED, }
-static void -qcrypto_tls_creds_psk_unload(QCryptoTLSCredsPSK *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */
@@ -212,7 +195,6 @@ qcrypto_tls_creds_psk_finalize(Object *obj) { QCryptoTLSCredsPSK *creds = QCRYPTO_TLS_CREDS_PSK(obj);
- qcrypto_tls_creds_psk_unload(creds); g_free(creds->username); }
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index f2f1aa2815..ef31ea664c 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -562,6 +562,7 @@ static int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) { + g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autofree char *cacert = NULL; g_autofree char *cacrl = NULL; g_autofree char *cert = NULL; @@ -578,6 +579,19 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
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; + } + if (qcrypto_tls_creds_get_path(&creds->parent_obj, QCRYPTO_TLS_CREDS_X509_CA_CERT, true, &cacert, errp) < 0) { @@ -616,14 +630,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; }
- ret = gnutls_certificate_allocate_credentials(&creds->data); - if (ret < 0) { - error_setg(errp, "Cannot allocate credentials: '%s'", - gnutls_strerror(ret)); - return -1; - } - - ret = gnutls_certificate_set_x509_trust_file(creds->data, + ret = gnutls_certificate_set_x509_trust_file(box->data.cert, cacert, GNUTLS_X509_FMT_PEM); if (ret < 0) { @@ -641,7 +648,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } } - ret = gnutls_certificate_set_x509_key_file2(creds->data, + ret = gnutls_certificate_set_x509_key_file2(box->data.cert, cert, key, GNUTLS_X509_FMT_PEM, password, @@ -655,7 +662,7 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, }
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) { @@ -667,28 +674,18 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
if (isServer) { if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, - &creds->parent_obj.dh_params, + &box->dh_params, errp) < 0) { return -1; } - gnutls_certificate_set_dh_params(creds->data, - creds->parent_obj.dh_params); + gnutls_certificate_set_dh_params(box->data.cert, box->dh_params); } + creds->parent_obj.box = g_steal_pointer(&box);
return 0; }
-static void -qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) -{ - if (creds->data) { - gnutls_certificate_free_credentials(creds->data); - creds->data = NULL; - } -} - - #else /* ! CONFIG_GNUTLS */
@@ -700,13 +697,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED, }
-static void -qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED) -{ - /* nada */ -} - - #endif /* ! CONFIG_GNUTLS */
@@ -769,29 +759,17 @@ qcrypto_tls_creds_x509_reload(QCryptoTLSCreds *creds, Error **errp) { 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 = creds->dh_params; + QCryptoTLSCredsBox *creds_box = creds->box;
- x509_creds->data = NULL; - creds->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); - if (creds->dh_params) { - gnutls_dh_params_deinit(creds->dh_params); - } - x509_creds->data = creds_data; - creds->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; }
@@ -824,7 +802,6 @@ qcrypto_tls_creds_x509_finalize(Object *obj) 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 77f334add3..a1dc3b3ce0 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 @@ qcrypto_tls_session_free(QCryptoTLSSession *session) 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); @@ -206,63 +208,31 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds, goto error; }
- if (object_dynamic_cast(OBJECT(creds), - TYPE_QCRYPTO_TLS_CREDS_ANON)) { - QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds); - 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); - 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); + 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; + }
- 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; - } + /* + * 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 (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))); - goto error; + 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); -- 2.51.1
Now that the TLS session code no longer needs to look at the TLS credential structs, they can be made private. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsanon.c | 3 +++ crypto/tlscredspriv.h | 15 --------------- crypto/tlscredspsk.c | 5 +++++ crypto/tlscredsx509.c | 6 ++++++ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 0a728ccbf6..69ed1d792a 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -31,6 +31,9 @@ #include <gnutls/gnutls.h> +struct QCryptoTLSCredsAnon { + QCryptoTLSCreds parent_obj; +}; static int qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h index 4e6dffa22f..69dac02437 100644 --- a/crypto/tlscredspriv.h +++ b/crypto/tlscredspriv.h @@ -37,21 +37,6 @@ struct QCryptoTLSCreds { QCryptoTLSCredsBox *box; }; -struct QCryptoTLSCredsAnon { - QCryptoTLSCreds parent_obj; -}; - -struct QCryptoTLSCredsPSK { - QCryptoTLSCreds parent_obj; - char *username; -}; - -struct QCryptoTLSCredsX509 { - QCryptoTLSCreds parent_obj; - bool sanityCheck; - char *passwordid; -}; - #ifdef CONFIG_GNUTLS int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 5568f1ad0c..e437985260 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -31,6 +31,11 @@ #include <gnutls/gnutls.h> +struct QCryptoTLSCredsPSK { + QCryptoTLSCreds parent_obj; + char *username; +}; + static int lookup_key(const char *pskfile, const char *username, gnutls_datum_t *key, Error **errp) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index ef31ea664c..2fc0872627 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -33,6 +33,12 @@ #include <gnutls/gnutls.h> #include <gnutls/x509.h> +struct QCryptoTLSCredsX509 { + QCryptoTLSCreds parent_obj; + bool sanityCheck; + char *passwordid; +}; + static int qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert, -- 2.51.1
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
Now that the TLS session code no longer needs to look at the TLS credential structs, they can be made private.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscredsanon.c | 3 +++ crypto/tlscredspriv.h | 15 --------------- crypto/tlscredspsk.c | 5 +++++ crypto/tlscredsx509.c | 6 ++++++ 4 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 0a728ccbf6..69ed1d792a 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -31,6 +31,9 @@
#include <gnutls/gnutls.h>
+struct QCryptoTLSCredsAnon { + QCryptoTLSCreds parent_obj; +};
static int qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h index 4e6dffa22f..69dac02437 100644 --- a/crypto/tlscredspriv.h +++ b/crypto/tlscredspriv.h @@ -37,21 +37,6 @@ struct QCryptoTLSCreds { QCryptoTLSCredsBox *box; };
-struct QCryptoTLSCredsAnon { - QCryptoTLSCreds parent_obj; -}; - -struct QCryptoTLSCredsPSK { - QCryptoTLSCreds parent_obj; - char *username; -}; - -struct QCryptoTLSCredsX509 { - QCryptoTLSCreds parent_obj; - bool sanityCheck; - char *passwordid; -}; - #ifdef CONFIG_GNUTLS
int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index 5568f1ad0c..e437985260 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -31,6 +31,11 @@
#include <gnutls/gnutls.h>
+struct QCryptoTLSCredsPSK { + QCryptoTLSCreds parent_obj; + char *username; +}; + static int lookup_key(const char *pskfile, const char *username, gnutls_datum_t *key, Error **errp) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index ef31ea664c..2fc0872627 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -33,6 +33,12 @@ #include <gnutls/gnutls.h> #include <gnutls/x509.h>
+struct QCryptoTLSCredsX509 { + QCryptoTLSCreds parent_obj; + bool sanityCheck; + char *passwordid; +}; +
static int qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert, -- 2.51.1
GNUTLS has deprecated use of externally provided diffie-hellman parameters, since it will automatically negotiate DH params in accordance with RFC7919. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 24 ++++++++---------------- crypto/tlscredsanon.c | 6 ++++-- crypto/tlscredspsk.c | 6 ++++-- crypto/tlscredsx509.c | 4 +++- docs/about/deprecated.rst | 9 +++++++++ docs/system/tls.rst | 12 +++++++----- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 798c9712fb..85268f3b57 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 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, 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 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, 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,6 +77,8 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, filename, gnutls_strerror(ret)); return -1; } + } else { + *dh_params = NULL; } return 0; diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 69ed1d792a..777cc4f5bb 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -68,8 +68,10 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, return -1; } - gnutls_anon_set_server_dh_params(box->data.anonserver, - box->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(&box->data.anonclient); if (ret < 0) { diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index e437985260..801da50625 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -129,8 +129,10 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, gnutls_strerror(ret)); goto cleanup; } - gnutls_psk_set_server_dh_params(box->data.pskserver, - box->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); diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 2fc0872627..7e79af4266 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -684,7 +684,9 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, errp) < 0) { return -1; } - gnutls_certificate_set_dh_params(box->data.cert, box->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); diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index ca6b3769b5..694a69da64 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -365,6 +365,15 @@ Options are: - 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 a4f6781d62..44c4bf04e9 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -251,11 +251,13 @@ When specifying the object, the ``dir`` parameters specifies which 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 -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
GNUTLS has deprecated use of externally provided diffie-hellman parameters, since it will automatically negotiate DH params in accordance with RFC7919.
The doc says: Since 3.6.0, DH parameters are negotiated following RFC7919. But QEMU doesn't require >= 3.6. Add a preliminary patch?
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 24 ++++++++---------------- crypto/tlscredsanon.c | 6 ++++-- crypto/tlscredspsk.c | 6 ++++-- crypto/tlscredsx509.c | 4 +++- docs/about/deprecated.rst | 9 +++++++++ docs/system/tls.rst | 12 +++++++----- 6 files changed, 35 insertions(+), 26 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 798c9712fb..85268f3b57 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 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds,
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 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, 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,6 +77,8 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, filename, gnutls_strerror(ret)); return -1; } + } else { + *dh_params = NULL; }
return 0; diff --git a/crypto/tlscredsanon.c b/crypto/tlscredsanon.c index 69ed1d792a..777cc4f5bb 100644 --- a/crypto/tlscredsanon.c +++ b/crypto/tlscredsanon.c @@ -68,8 +68,10 @@ qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, return -1; }
- gnutls_anon_set_server_dh_params(box->data.anonserver, - box->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(&box->data.anonclient); if (ret < 0) { diff --git a/crypto/tlscredspsk.c b/crypto/tlscredspsk.c index e437985260..801da50625 100644 --- a/crypto/tlscredspsk.c +++ b/crypto/tlscredspsk.c @@ -129,8 +129,10 @@ qcrypto_tls_creds_psk_load(QCryptoTLSCredsPSK *creds, gnutls_strerror(ret)); goto cleanup; } - gnutls_psk_set_server_dh_params(box->data.pskserver, - box->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);
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 2fc0872627..7e79af4266 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -684,7 +684,9 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, errp) < 0) { return -1; } - gnutls_certificate_set_dh_params(box->data.cert, box->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);
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index ca6b3769b5..694a69da64 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -365,6 +365,15 @@ Options are: - 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 a4f6781d62..44c4bf04e9 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -251,11 +251,13 @@ When specifying the object, the ``dir`` parameters specifies which 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 -- 2.51.1
On Fri, Oct 31, 2025 at 03:32:51PM +0400, Marc-André Lureau wrote:
Hi
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
GNUTLS has deprecated use of externally provided diffie-hellman parameters, since it will automatically negotiate DH params in accordance with RFC7919.
The doc says: Since 3.6.0, DH parameters are negotiated following RFC7919.
But QEMU doesn't require >= 3.6. Add a preliminary patch?
Oh whoops. I mis-read the meson.build rules. Our gnutls bump to 3.5.18 was done in: commit d4c7ee330cd0ca05cc0c026f845af6711e37b0f7 Author: Daniel P. Berrangé <berrange@redhat.com> Date: Fri May 14 13:04:09 2021 +0100 crypto: bump min gnutls to 3.5.18, dropping RHEL-7 support It has been over two years since RHEL-8 was released, and thus per the platform build policy, we no longer need to support RHEL-7 as a build target. This lets us increment the minimum required gnutls version Per repology, current shipping versions are: RHEL-8: 3.6.14 Debian Buster: 3.6.7 openSUSE Leap 15.2: 3.6.7 Ubuntu LTS 18.04: 3.5.18 Ubuntu LTS 20.04: 3.6.13 FreeBSD: 3.6.15 Fedora 33: 3.6.16 Fedora 34: 3.7.1 OpenBSD: 3.6.15 macOS HomeBrew: 3.6.15 the only one not already on 3.6 was Ubuntu 18.04 and that is long outside our support matrix. IOW we can easily assume at least 3.6 these days and this patch is safe on that basis. I'll prepare another standalone patch to explicit increase the min version though. Can probably bump gcrypt & nettle min versions too. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
The x509 TLS credentials code will load the CA certs once to perform sanity chcking on the certs, then discard the certificate objects and let gnutls load them a second time. This introduces a new QCryptoTLSCredsX509Files struct which will hold the CA certificates loaded for sanity checking and pass them on to gnutls, avoiding the duplicated loading. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 141 ++++++++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 54 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 7e79af4266..6a830af50d 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -40,6 +40,35 @@ struct QCryptoTLSCredsX509 { }; +typedef struct QCryptoTLSCredsX509Files QCryptoTLSCredsX509Files; +struct QCryptoTLSCredsX509Files { + char *cacertpath; + gnutls_x509_crt_t *cacerts; + unsigned int ncacerts; +}; + +static QCryptoTLSCredsX509Files * +qcrypto_tls_creds_x509_files_new(void) +{ + return g_new0(QCryptoTLSCredsX509Files, 1); +} + + +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); + 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, @@ -315,11 +344,9 @@ qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds, 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, Error **errp) { @@ -360,13 +387,13 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, * reached the root of trust. */ return qcrypto_tls_creds_check_cert( - creds, cert_to_check, cacertFile, + 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; } } @@ -374,7 +401,7 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, break; } - if (qcrypto_tls_creds_check_cert(creds, cert_issuer, cacertFile, + if (qcrypto_tls_creds_check_cert(creds, cert_issuer, files->cacertpath, isServer, true, errp) < 0) { return -1; } @@ -394,19 +421,17 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, } 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 ? @@ -414,7 +439,7 @@ qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t *certs, "CA certificate %s" : "Unable to verify client certificate %s against " "CA certificate %s", - certFile, cacertFile); + certFile, files->cacertpath); return -1; } @@ -439,7 +464,7 @@ qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t *certs, error_setg(errp, "Our own certificate %s failed validation against %s: %s", - certFile, cacertFile, reason); + certFile, files->cacertpath, reason); return -1; } @@ -490,15 +515,13 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds, 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; @@ -514,16 +537,6 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, } } - if (qcrypto_tls_creds_load_cert_list(creds, - cacertFile, - &cacerts, - &ncacerts, - isServer, - true, - errp) < 0) { - goto cleanup; - } - for (i = 0; i < ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, certs[i], certFile, @@ -533,17 +546,14 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, } if (ncerts && - qcrypto_tls_creds_check_authority_chain(creds, + qcrypto_tls_creds_check_authority_chain(creds, files, certs, ncerts, - cacerts, ncacerts, - cacertFile, isServer, - errp) < 0) { + isServer, errp) < 0) { goto cleanup; } - if (ncerts && ncacerts && - qcrypto_tls_creds_check_cert_pair(certs, ncerts, certFile, - cacerts, ncacerts, cacertFile, + if (ncerts && + qcrypto_tls_creds_check_cert_pair(files, certs, ncerts, certFile, isServer, errp) < 0) { goto cleanup; } @@ -555,21 +565,53 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, gnutls_x509_crt_deinit(certs[i]); } g_free(certs); - for (i = 0; i < ncacerts; i++) { - gnutls_x509_crt_deinit(cacerts[i]); - } - g_free(cacerts); return ret; } +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, + isServer, + true, + 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 int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) { g_autoptr(QCryptoTLSCredsBox) box = NULL; - g_autofree char *cacert = NULL; + g_autoptr(QCryptoTLSCredsX509Files) files = NULL; g_autofree char *cacrl = NULL; g_autofree char *cert = NULL; g_autofree char *key = NULL; @@ -598,9 +640,9 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } - if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CA_CERT, - true, &cacert, errp) < 0) { + files = qcrypto_tls_creds_x509_files_new(); + + if (qcrypto_tls_creds_x509_load_ca(creds, box, files, isServer, errp) < 0) { return -1; } @@ -631,17 +673,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, isServer, - cacert, cert, errp) < 0) { - return -1; - } - - ret = gnutls_certificate_set_x509_trust_file(box->data.cert, - cacert, - GNUTLS_X509_FMT_PEM); - if (ret < 0) { - error_setg(errp, "Cannot load CA certificate '%s': %s", - cacert, gnutls_strerror(ret)); + qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, + cert, errp) < 0) { return -1; } -- 2.51.1
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The x509 TLS credentials code will load the CA certs once to perform sanity chcking on the certs, then discard the certificate objects and let gnutls load them a second time.
This introduces a new QCryptoTLSCredsX509Files struct which will hold the CA certificates loaded for sanity checking and pass them on to gnutls, avoiding the duplicated loading.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscredsx509.c | 141 ++++++++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 54 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 7e79af4266..6a830af50d 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -40,6 +40,35 @@ struct QCryptoTLSCredsX509 { };
+typedef struct QCryptoTLSCredsX509Files QCryptoTLSCredsX509Files; +struct QCryptoTLSCredsX509Files { + char *cacertpath; + gnutls_x509_crt_t *cacerts; + unsigned int ncacerts; +}; + +static QCryptoTLSCredsX509Files * +qcrypto_tls_creds_x509_files_new(void) +{ + return g_new0(QCryptoTLSCredsX509Files, 1); +} + + +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); + 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, @@ -315,11 +344,9 @@ qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds,
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, Error **errp) { @@ -360,13 +387,13 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, * reached the root of trust. */ return qcrypto_tls_creds_check_cert( - creds, cert_to_check, cacertFile, + 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; } } @@ -374,7 +401,7 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, break; }
- if (qcrypto_tls_creds_check_cert(creds, cert_issuer, cacertFile, + if (qcrypto_tls_creds_check_cert(creds, cert_issuer, files->cacertpath, isServer, true, errp) < 0) { return -1; } @@ -394,19 +421,17 @@ qcrypto_tls_creds_check_authority_chain(QCryptoTLSCredsX509 *creds, }
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 ? @@ -414,7 +439,7 @@ qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t *certs, "CA certificate %s" : "Unable to verify client certificate %s against " "CA certificate %s", - certFile, cacertFile); + certFile, files->cacertpath); return -1; }
@@ -439,7 +464,7 @@ qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t *certs,
error_setg(errp, "Our own certificate %s failed validation against %s: %s", - certFile, cacertFile, reason); + certFile, files->cacertpath, reason); return -1; }
@@ -490,15 +515,13 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds,
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;
@@ -514,16 +537,6 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, } }
- if (qcrypto_tls_creds_load_cert_list(creds, - cacertFile, - &cacerts, - &ncacerts, - isServer, - true, - errp) < 0) { - goto cleanup; - } - for (i = 0; i < ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, certs[i], certFile, @@ -533,17 +546,14 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, }
if (ncerts && - qcrypto_tls_creds_check_authority_chain(creds, + qcrypto_tls_creds_check_authority_chain(creds, files, certs, ncerts, - cacerts, ncacerts, - cacertFile, isServer, - errp) < 0) { + isServer, errp) < 0) { goto cleanup; }
- if (ncerts && ncacerts && - qcrypto_tls_creds_check_cert_pair(certs, ncerts, certFile, - cacerts, ncacerts, cacertFile, + if (ncerts && + qcrypto_tls_creds_check_cert_pair(files, certs, ncerts, certFile, isServer, errp) < 0) { goto cleanup; } @@ -555,21 +565,53 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, gnutls_x509_crt_deinit(certs[i]); } g_free(certs); - for (i = 0; i < ncacerts; i++) { - gnutls_x509_crt_deinit(cacerts[i]); - } - g_free(cacerts);
return ret; }
+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, + isServer, + true, + 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 int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) { g_autoptr(QCryptoTLSCredsBox) box = NULL; - g_autofree char *cacert = NULL; + g_autoptr(QCryptoTLSCredsX509Files) files = NULL; g_autofree char *cacrl = NULL; g_autofree char *cert = NULL; g_autofree char *key = NULL; @@ -598,9 +640,9 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; }
- if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CA_CERT, - true, &cacert, errp) < 0) { + files = qcrypto_tls_creds_x509_files_new(); + + if (qcrypto_tls_creds_x509_load_ca(creds, box, files, isServer, errp) < 0) { return -1; }
@@ -631,17 +673,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, }
if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, isServer, - cacert, cert, errp) < 0) { - return -1; - } - - ret = gnutls_certificate_set_x509_trust_file(box->data.cert, - cacert, - GNUTLS_X509_FMT_PEM); - if (ret < 0) { - error_setg(errp, "Cannot load CA certificate '%s': %s", - cacert, gnutls_strerror(ret)); + qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, + cert, errp) < 0) { return -1; }
-- 2.51.1
The x509 TLS credentials code will load the identity certs once to perform sanity chcking on the certs, then discard the certificate objects and let gnutls load them a second time. This extends the previous QCryptoTLSCredsX509Files struct to also hold the identity certificates & key loaded for sanity checking and pass them on to gnutls, avoiding the duplicated loading. The unit tests need updating because we now correctly diagnose the error scenario where the cert PEM file exists, without its matching key PEM file. Previously that error was mistakenly ignored. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 247 +++++++++++++++++--------- tests/unit/test-crypto-tlscredsx509.c | 8 +- 2 files changed, 164 insertions(+), 91 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 6a830af50d..3cb0a6c31f 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -45,6 +45,12 @@ struct QCryptoTLSCredsX509Files { char *cacertpath; gnutls_x509_crt_t *cacerts; unsigned int ncacerts; + + char *certpath; + char *keypath; + gnutls_x509_crt_t *certs; + unsigned int ncerts; + gnutls_x509_privkey_t key; }; static QCryptoTLSCredsX509Files * @@ -63,6 +69,13 @@ qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) } g_free(files->cacerts); g_free(files->cacertpath); + 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); } @@ -477,14 +490,13 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds, 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); @@ -499,13 +511,60 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds, 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; } @@ -517,56 +576,34 @@ static int qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsX509Files *files, bool isServer, - const char *certFile, Error **errp) { - gnutls_x509_crt_t *certs = NULL; - unsigned int ncerts = 0; size_t i; - int ret = -1; - - if (certFile) { - if (qcrypto_tls_creds_load_cert_list(creds, - certFile, - &certs, - &ncerts, - isServer, - false, - errp) < 0) { - goto cleanup; - } - } - for (i = 0; i < ncerts; i++) { + for (i = 0; i < files->ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, - certs[i], certFile, + files->certs[i], files->certpath, isServer, i != 0, errp) < 0) { - goto cleanup; + return -1; } } - if (ncerts && + if (files->ncerts && qcrypto_tls_creds_check_authority_chain(creds, files, - certs, ncerts, + files->certs, files->ncerts, isServer, errp) < 0) { - goto cleanup; - } - - if (ncerts && - qcrypto_tls_creds_check_cert_pair(files, certs, ncerts, certFile, - isServer, errp) < 0) { - goto cleanup; + return -1; } - ret = 0; - - cleanup: - for (i = 0; i < ncerts; i++) { - gnutls_x509_crt_deinit(certs[i]); + if (files->ncerts && + qcrypto_tls_creds_check_cert_pair(files, + files->certs, files->ncerts, + files->certpath, isServer, + errp) < 0) { + return -1; } - g_free(certs); - return ret; + return 0; } @@ -589,8 +626,6 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, files->cacertpath, &files->cacerts, &files->ncacerts, - isServer, - true, errp) < 0) { return -1; } @@ -606,6 +641,79 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, return 0; } + +static int +qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsBox *box, + QCryptoTLSCredsX509Files *files, + bool isServer, + Error **errp) +{ + int ret; + + if (isServer) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &files->keypath, errp) < 0) { + return -1; + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &files->keypath, errp) < 0) { + return -1; + } + } + + if (!files->certpath && + !files->keypath) { + return 0; + } + if (files->certpath && !files->keypath) { + error_setg(errp, "Cert '%s' without corresponding key", + files->certpath); + return -1; + } + if (!files->certpath && files->keypath) { + error_setg(errp, "Key '%s' without corresponding cert", + files->keypath); + return -1; + } + + if (qcrypto_tls_creds_load_cert_list(creds, + files->certpath, + &files->certs, + &files->ncerts, + errp) < 0) { + return -1; + } + + if (qcrypto_tls_creds_load_privkey(creds, + files->keypath, + &files->key, + errp) < 0) { + return -1; + } + + 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 -1; + } + return 0; +} + + static int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) @@ -613,8 +721,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autoptr(QCryptoTLSCredsX509Files) files = NULL; g_autofree char *cacrl = NULL; - g_autofree char *cert = NULL; - g_autofree char *key = NULL; g_autofree char *dhparams = NULL; bool isServer = (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER); @@ -646,60 +752,27 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } + if (qcrypto_tls_creds_x509_load_identity(creds, box, files, + isServer, errp) < 0) { + return -1; + } + if (isServer) { if (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) { return -1; } - } else { - if (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) { - return -1; - } } if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, - cert, errp) < 0) { + qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, errp) < 0) { return -1; } - if (cert != NULL && key != NULL) { - char *password = NULL; - if (creds->passwordid) { - password = qcrypto_secret_lookup_as_utf8(creds->passwordid, - errp); - if (!password) { - return -1; - } - } - ret = gnutls_certificate_set_x509_key_file2(box->data.cert, - 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)); - return -1; - } - } - if (cacrl != NULL) { ret = gnutls_certificate_set_x509_crl_file(box->data.cert, cacrl, diff --git a/tests/unit/test-crypto-tlscredsx509.c b/tests/unit/test-crypto-tlscredsx509.c index a5f21728d4..b1ad7d5c0d 100644 --- a/tests/unit/test-crypto-tlscredsx509.c +++ b/tests/unit/test-crypto-tlscredsx509.c @@ -95,16 +95,16 @@ static void test_tls_creds(const void *opaque) 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( -- 2.51.1
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The x509 TLS credentials code will load the identity certs once to perform sanity chcking on the certs, then discard the certificate objects and let gnutls load them a second time.
This extends the previous QCryptoTLSCredsX509Files struct to also hold the identity certificates & key loaded for sanity checking and pass them on to gnutls, avoiding the duplicated loading.
The unit tests need updating because we now correctly diagnose the error scenario where the cert PEM file exists, without its matching key PEM file. Previously that error was mistakenly ignored.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
lgtm Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscredsx509.c | 247 +++++++++++++++++--------- tests/unit/test-crypto-tlscredsx509.c | 8 +- 2 files changed, 164 insertions(+), 91 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 6a830af50d..3cb0a6c31f 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -45,6 +45,12 @@ struct QCryptoTLSCredsX509Files { char *cacertpath; gnutls_x509_crt_t *cacerts; unsigned int ncacerts; + + char *certpath; + char *keypath; + gnutls_x509_crt_t *certs; + unsigned int ncerts; + gnutls_x509_privkey_t key; };
static QCryptoTLSCredsX509Files * @@ -63,6 +69,13 @@ qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) } g_free(files->cacerts); g_free(files->cacertpath); + 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); }
@@ -477,14 +490,13 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds, 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); @@ -499,13 +511,60 @@ qcrypto_tls_creds_load_cert_list(QCryptoTLSCredsX509 *creds, 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; }
@@ -517,56 +576,34 @@ static int qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsX509Files *files, bool isServer, - const char *certFile, Error **errp) { - gnutls_x509_crt_t *certs = NULL; - unsigned int ncerts = 0; size_t i; - int ret = -1; - - if (certFile) { - if (qcrypto_tls_creds_load_cert_list(creds, - certFile, - &certs, - &ncerts, - isServer, - false, - errp) < 0) { - goto cleanup; - } - }
- for (i = 0; i < ncerts; i++) { + for (i = 0; i < files->ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, - certs[i], certFile, + files->certs[i], files->certpath, isServer, i != 0, errp) < 0) { - goto cleanup; + return -1; } }
- if (ncerts && + if (files->ncerts && qcrypto_tls_creds_check_authority_chain(creds, files, - certs, ncerts, + files->certs, files->ncerts, isServer, errp) < 0) { - goto cleanup; - } - - if (ncerts && - qcrypto_tls_creds_check_cert_pair(files, certs, ncerts, certFile, - isServer, errp) < 0) { - goto cleanup; + return -1; }
- ret = 0; - - cleanup: - for (i = 0; i < ncerts; i++) { - gnutls_x509_crt_deinit(certs[i]); + if (files->ncerts && + qcrypto_tls_creds_check_cert_pair(files, + files->certs, files->ncerts, + files->certpath, isServer, + errp) < 0) { + return -1; } - g_free(certs);
- return ret; + return 0; }
@@ -589,8 +626,6 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, files->cacertpath, &files->cacerts, &files->ncacerts, - isServer, - true, errp) < 0) { return -1; } @@ -606,6 +641,79 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, return 0; }
+ +static int +qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsBox *box, + QCryptoTLSCredsX509Files *files, + bool isServer, + Error **errp) +{ + int ret; + + if (isServer) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &files->keypath, errp) < 0) { + return -1; + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &files->keypath, errp) < 0) { + return -1; + } + } + + if (!files->certpath && + !files->keypath) { + return 0; + } + if (files->certpath && !files->keypath) { + error_setg(errp, "Cert '%s' without corresponding key", + files->certpath); + return -1; + } + if (!files->certpath && files->keypath) { + error_setg(errp, "Key '%s' without corresponding cert", + files->keypath); + return -1; + } + + if (qcrypto_tls_creds_load_cert_list(creds, + files->certpath, + &files->certs, + &files->ncerts, + errp) < 0) { + return -1; + } + + if (qcrypto_tls_creds_load_privkey(creds, + files->keypath, + &files->key, + errp) < 0) { + return -1; + } + + 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 -1; + } + return 0; +} + + static int qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, Error **errp) @@ -613,8 +721,6 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, g_autoptr(QCryptoTLSCredsBox) box = NULL; g_autoptr(QCryptoTLSCredsX509Files) files = NULL; g_autofree char *cacrl = NULL; - g_autofree char *cert = NULL; - g_autofree char *key = NULL; g_autofree char *dhparams = NULL; bool isServer = (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER); @@ -646,60 +752,27 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; }
+ if (qcrypto_tls_creds_x509_load_identity(creds, box, files, + isServer, errp) < 0) { + return -1; + } + if (isServer) { if (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) { return -1; } - } else { - if (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) { - return -1; - } }
if (creds->sanityCheck && - qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, - cert, errp) < 0) { + qcrypto_tls_creds_x509_sanity_check(creds, files, isServer, errp) < 0) { return -1; }
- if (cert != NULL && key != NULL) { - char *password = NULL; - if (creds->passwordid) { - password = qcrypto_secret_lookup_as_utf8(creds->passwordid, - errp); - if (!password) { - return -1; - } - } - ret = gnutls_certificate_set_x509_key_file2(box->data.cert, - 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)); - return -1; - } - } - if (cacrl != NULL) { ret = gnutls_certificate_set_x509_crl_file(box->data.cert, cacrl, diff --git a/tests/unit/test-crypto-tlscredsx509.c b/tests/unit/test-crypto-tlscredsx509.c index a5f21728d4..b1ad7d5c0d 100644 --- a/tests/unit/test-crypto-tlscredsx509.c +++ b/tests/unit/test-crypto-tlscredsx509.c @@ -95,16 +95,16 @@ static void test_tls_creds(const void *opaque) 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( -- 2.51.1
Currently only a single set of certificates can be loaded for a server / client. Certificates are created using a particular key algorithm and in some scenarios it can be useful to support multiple algorithms in parallel. This requires the ability to load multiple sets of certificates. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscredsx509.c | 164 ++++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 3cb0a6c31f..d7d1f594c0 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -39,6 +39,14 @@ struct QCryptoTLSCredsX509 { char *passwordid; }; +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 { @@ -46,11 +54,8 @@ struct QCryptoTLSCredsX509Files { gnutls_x509_crt_t *cacerts; unsigned int ncacerts; - char *certpath; - char *keypath; - gnutls_x509_crt_t *certs; - unsigned int ncerts; - gnutls_x509_privkey_t key; + QCryptoTLSCredsX509IdentFiles **identities; + size_t nidentities; }; static QCryptoTLSCredsX509Files * @@ -61,14 +66,9 @@ qcrypto_tls_creds_x509_files_new(void) static void -qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) +qcrypto_tls_creds_x509_ident_files_free(QCryptoTLSCredsX509IdentFiles *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->ncerts; i++) { gnutls_x509_crt_deinit(files->certs[i]); } @@ -79,6 +79,26 @@ qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) 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); @@ -573,33 +593,32 @@ qcrypto_tls_creds_load_privkey(QCryptoTLSCredsX509 *creds, static int -qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, - QCryptoTLSCredsX509Files *files, - bool isServer, - Error **errp) +qcrypto_tls_creds_x509_sanity_check_identity(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsX509Files *files, + QCryptoTLSCredsX509IdentFiles *ifiles, + bool isServer, + Error **errp) { size_t i; - for (i = 0; i < files->ncerts; i++) { + for (i = 0; i < ifiles->ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, - files->certs[i], files->certpath, + ifiles->certs[i], ifiles->certpath, isServer, i != 0, errp) < 0) { return -1; } } - if (files->ncerts && + if (ifiles->ncerts && qcrypto_tls_creds_check_authority_chain(creds, files, - files->certs, files->ncerts, + ifiles->certs, ifiles->ncerts, isServer, errp) < 0) { return -1; } - if (files->ncerts && - qcrypto_tls_creds_check_cert_pair(files, - files->certs, files->ncerts, - files->certpath, isServer, - errp) < 0) { + if (ifiles->ncerts && + qcrypto_tls_creds_check_cert_pair(files, ifiles->certs, ifiles->ncerts, + ifiles->certpath, isServer, errp) < 0) { return -1; } @@ -607,6 +626,26 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, } +static int +qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsX509Files *files, + bool isServer, + Error **errp) +{ + size_t i; + 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; + } + } + return 0; +} + + static int qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, @@ -642,48 +681,38 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, } -static int +static QCryptoTLSCredsX509IdentFiles * qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, - QCryptoTLSCredsX509Files *files, - bool isServer, + const char *certbase, + const char *keybase, + bool isOptional, Error **errp) { + g_autoptr(QCryptoTLSCredsX509IdentFiles) files = + g_new0(QCryptoTLSCredsX509IdentFiles, 1); int ret; - if (isServer) { - if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_SERVER_CERT, - true, &files->certpath, errp) < 0 || - qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_SERVER_KEY, - true, &files->keypath, errp) < 0) { - return -1; - } - } else { - if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, - false, &files->certpath, errp) < 0 || - qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, - false, &files->keypath, errp) < 0) { - return -1; - } + if (qcrypto_tls_creds_get_path(&creds->parent_obj, certbase, + !isOptional, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, keybase, + !isOptional, &files->keypath, errp) < 0) { + return NULL; } if (!files->certpath && !files->keypath) { - return 0; + return NULL; } if (files->certpath && !files->keypath) { error_setg(errp, "Cert '%s' without corresponding key", files->certpath); - return -1; + return NULL; } if (!files->certpath && files->keypath) { error_setg(errp, "Key '%s' without corresponding cert", files->keypath); - return -1; + return NULL; } if (qcrypto_tls_creds_load_cert_list(creds, @@ -691,14 +720,14 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, &files->certs, &files->ncerts, errp) < 0) { - return -1; + return NULL; } if (qcrypto_tls_creds_load_privkey(creds, files->keypath, &files->key, errp) < 0) { - return -1; + return NULL; } ret = gnutls_certificate_set_x509_key(box->data.cert, @@ -708,8 +737,39 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, 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) +{ + QCryptoTLSCredsX509IdentFiles *ifiles; + + 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, + !isServer, errp); + if (!ifiles) { return -1; } + + files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, + files->identities, + files->nidentities + 1); + files->identities[files->nidentities++] = ifiles; + return 0; } @@ -752,8 +812,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; } - if (qcrypto_tls_creds_x509_load_identity(creds, box, files, - isServer, errp) < 0) { + if (qcrypto_tls_creds_x509_load_identities(creds, box, files, + isServer, errp) < 0) { return -1; } -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
Currently only a single set of certificates can be loaded for a server / client. Certificates are created using a particular key algorithm and in some scenarios it can be useful to support multiple algorithms in parallel. This requires the ability to load multiple sets of certificates.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscredsx509.c | 164 ++++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 52 deletions(-)
diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 3cb0a6c31f..d7d1f594c0 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -39,6 +39,14 @@ struct QCryptoTLSCredsX509 { char *passwordid; };
+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 { @@ -46,11 +54,8 @@ struct QCryptoTLSCredsX509Files { gnutls_x509_crt_t *cacerts; unsigned int ncacerts;
- char *certpath; - char *keypath; - gnutls_x509_crt_t *certs; - unsigned int ncerts; - gnutls_x509_privkey_t key; + QCryptoTLSCredsX509IdentFiles **identities; + size_t nidentities; };
static QCryptoTLSCredsX509Files * @@ -61,14 +66,9 @@ qcrypto_tls_creds_x509_files_new(void)
static void -qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) +qcrypto_tls_creds_x509_ident_files_free(QCryptoTLSCredsX509IdentFiles *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->ncerts; i++) { gnutls_x509_crt_deinit(files->certs[i]); } @@ -79,6 +79,26 @@ qcrypto_tls_creds_x509_files_free(QCryptoTLSCredsX509Files *files) 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);
@@ -573,33 +593,32 @@ qcrypto_tls_creds_load_privkey(QCryptoTLSCredsX509 *creds,
static int -qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, - QCryptoTLSCredsX509Files *files, - bool isServer, - Error **errp) +qcrypto_tls_creds_x509_sanity_check_identity(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsX509Files *files, + QCryptoTLSCredsX509IdentFiles *ifiles, + bool isServer, + Error **errp) { size_t i;
- for (i = 0; i < files->ncerts; i++) { + for (i = 0; i < ifiles->ncerts; i++) { if (qcrypto_tls_creds_check_cert(creds, - files->certs[i], files->certpath, + ifiles->certs[i], ifiles->certpath, isServer, i != 0, errp) < 0) { return -1; } }
- if (files->ncerts && + if (ifiles->ncerts && qcrypto_tls_creds_check_authority_chain(creds, files, - files->certs, files->ncerts, + ifiles->certs, ifiles->ncerts, isServer, errp) < 0) { return -1; }
- if (files->ncerts && - qcrypto_tls_creds_check_cert_pair(files, - files->certs, files->ncerts, - files->certpath, isServer, - errp) < 0) { + if (ifiles->ncerts && + qcrypto_tls_creds_check_cert_pair(files, ifiles->certs, ifiles->ncerts, + ifiles->certpath, isServer, errp) < 0) { return -1; }
@@ -607,6 +626,26 @@ qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, }
+static int +qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, + QCryptoTLSCredsX509Files *files, + bool isServer, + Error **errp) +{ + size_t i; + 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; + } + } + return 0; +} + + static int qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, @@ -642,48 +681,38 @@ qcrypto_tls_creds_x509_load_ca(QCryptoTLSCredsX509 *creds, }
-static int +static QCryptoTLSCredsX509IdentFiles * qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, - QCryptoTLSCredsX509Files *files, - bool isServer, + const char *certbase, + const char *keybase, + bool isOptional, Error **errp) { + g_autoptr(QCryptoTLSCredsX509IdentFiles) files = + g_new0(QCryptoTLSCredsX509IdentFiles, 1); int ret;
- if (isServer) { - if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_SERVER_CERT, - true, &files->certpath, errp) < 0 || - qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_SERVER_KEY, - true, &files->keypath, errp) < 0) { - return -1; - } - } else { - if (qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, - false, &files->certpath, errp) < 0 || - qcrypto_tls_creds_get_path(&creds->parent_obj, - QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, - false, &files->keypath, errp) < 0) { - return -1; - } + if (qcrypto_tls_creds_get_path(&creds->parent_obj, certbase, + !isOptional, &files->certpath, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, keybase, + !isOptional, &files->keypath, errp) < 0) { + return NULL; }
if (!files->certpath && !files->keypath) { - return 0; + return NULL; } if (files->certpath && !files->keypath) { error_setg(errp, "Cert '%s' without corresponding key", files->certpath); - return -1; + return NULL; } if (!files->certpath && files->keypath) { error_setg(errp, "Key '%s' without corresponding cert", files->keypath); - return -1; + return NULL; }
if (qcrypto_tls_creds_load_cert_list(creds, @@ -691,14 +720,14 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, &files->certs, &files->ncerts, errp) < 0) { - return -1; + return NULL; }
if (qcrypto_tls_creds_load_privkey(creds, files->keypath, &files->key, errp) < 0) { - return -1; + return NULL; }
ret = gnutls_certificate_set_x509_key(box->data.cert, @@ -708,8 +737,39 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, 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) +{ + QCryptoTLSCredsX509IdentFiles *ifiles; + + 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, + !isServer, errp); + if (!ifiles) { return -1; } + + files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, + files->identities, + files->nidentities + 1); + files->identities[files->nidentities++] = ifiles; + return 0; }
@@ -752,8 +812,8 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, return -1; }
- if (qcrypto_tls_creds_x509_load_identity(creds, box, files, - isServer, errp) < 0) { + if (qcrypto_tls_creds_x509_load_identities(creds, box, files, + isServer, errp) < 0) { return -1; }
-- 2.51.1
The default (required) identity is stored in server-cert.pem / client-cert.pem and server-key.pem / client-key.pem. The 4 extra (optional) identities are stored in server-cert-$N.pem / client-cert-$N.pem and server-key-$N.pem / client-key-$N.pem. The numbering starts at 0 and the first missing cert/key pair will terminate the loading process. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- crypto/tlscreds.c | 10 +++++- crypto/tlscredspriv.h | 3 ++ crypto/tlscredsx509.c | 68 ++++++++++++++++++++++++++++------- crypto/tlssession.c | 1 + crypto/trace-events | 1 + docs/system/tls.rst | 54 ++++++++++++++++++++++++++-- include/crypto/tlscredsx509.h | 6 ++++ 7 files changed, 127 insertions(+), 16 deletions(-) diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 85268f3b57..b7e77f6285 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -85,6 +85,14 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, } +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, @@ -94,7 +102,7 @@ qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, { int ret = -1; - *cred = g_strdup_printf("%s/%s", creds->dir, filename); + *cred = qcrypto_tls_creds_build_path(creds, filename); if (access(*cred, R_OK) < 0) { if (errno == ENOENT && !required) { diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h index 69dac02437..8f2d096c7f 100644 --- a/crypto/tlscredspriv.h +++ b/crypto/tlscredspriv.h @@ -39,6 +39,9 @@ struct QCryptoTLSCreds { #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/tlscredsx509.c b/crypto/tlscredsx509.c index d7d1f594c0..fa92431906 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -686,7 +686,6 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, const char *certbase, const char *keybase, - bool isOptional, Error **errp) { g_autoptr(QCryptoTLSCredsX509IdentFiles) files = @@ -694,9 +693,9 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, int ret; if (qcrypto_tls_creds_get_path(&creds->parent_obj, certbase, - !isOptional, &files->certpath, errp) < 0 || + false, &files->certpath, errp) < 0 || qcrypto_tls_creds_get_path(&creds->parent_obj, keybase, - !isOptional, &files->keypath, errp) < 0) { + false, &files->keypath, errp) < 0) { return NULL; } @@ -705,13 +704,17 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, return NULL; } if (files->certpath && !files->keypath) { - error_setg(errp, "Cert '%s' without corresponding key", - files->certpath); + 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) { - error_setg(errp, "Key '%s' without corresponding cert", - 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; } @@ -750,7 +753,9 @@ qcrypto_tls_creds_x509_load_identities(QCryptoTLSCredsX509 *creds, bool isServer, Error **errp) { + ERRP_GUARD(); QCryptoTLSCredsX509IdentFiles *ifiles; + size_t i; ifiles = qcrypto_tls_creds_x509_load_identity( creds, box, @@ -760,15 +765,52 @@ qcrypto_tls_creds_x509_load_identities(QCryptoTLSCredsX509 *creds, isServer ? QCRYPTO_TLS_CREDS_X509_SERVER_KEY : QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, - !isServer, errp); - if (!ifiles) { + errp); + if (!ifiles && *errp) { return -1; } - files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, - files->identities, - files->nidentities + 1); - files->identities[files->nidentities++] = ifiles; + 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; + } + if (!ifiles) { + break; + } + + files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, + files->identities, + files->nidentities + 1); + files->identities[files->nidentities++] = ifiles; + } + + 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; + } return 0; } diff --git a/crypto/tlssession.c b/crypto/tlssession.c index a1dc3b3ce0..314e3e96ba 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -345,6 +345,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session, 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 d0e33427fa..771f9b8a6e 100644 --- a/crypto/trace-events +++ b/crypto/trace-events @@ -21,6 +21,7 @@ qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds # 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/docs/system/tls.rst b/docs/system/tls.rst index 44c4bf04e9..7cec4ac3df 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -36,8 +36,58 @@ server and exposing it directly to remote browser clients. In such a 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: diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h index c4daba21a6..61b7f73573 100644 --- a/include/crypto/tlscredsx509.h +++ b/include/crypto/tlscredsx509.h @@ -37,7 +37,13 @@ typedef struct QCryptoTLSCredsX509Class QCryptoTLSCredsX509Class; #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: -- 2.51.1
Hi On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
The default (required) identity is stored in server-cert.pem / client-cert.pem and server-key.pem / client-key.pem.
The 4 extra (optional) identities are stored in server-cert-$N.pem / client-cert-$N.pem and server-key-$N.pem / client-key-$N.pem. The numbering starts at 0 and the first missing cert/key pair will terminate the loading process.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- crypto/tlscreds.c | 10 +++++- crypto/tlscredspriv.h | 3 ++ crypto/tlscredsx509.c | 68 ++++++++++++++++++++++++++++------- crypto/tlssession.c | 1 + crypto/trace-events | 1 + docs/system/tls.rst | 54 ++++++++++++++++++++++++++-- include/crypto/tlscredsx509.h | 6 ++++ 7 files changed, 127 insertions(+), 16 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c index 85268f3b57..b7e77f6285 100644 --- a/crypto/tlscreds.c +++ b/crypto/tlscreds.c @@ -85,6 +85,14 @@ qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, }
+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, @@ -94,7 +102,7 @@ qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, { int ret = -1;
- *cred = g_strdup_printf("%s/%s", creds->dir, filename); + *cred = qcrypto_tls_creds_build_path(creds, filename);
if (access(*cred, R_OK) < 0) { if (errno == ENOENT && !required) { diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h index 69dac02437..8f2d096c7f 100644 --- a/crypto/tlscredspriv.h +++ b/crypto/tlscredspriv.h @@ -39,6 +39,9 @@ struct QCryptoTLSCreds {
#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/tlscredsx509.c b/crypto/tlscredsx509.c index d7d1f594c0..fa92431906 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -686,7 +686,6 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, QCryptoTLSCredsBox *box, const char *certbase, const char *keybase, - bool isOptional, Error **errp) { g_autoptr(QCryptoTLSCredsX509IdentFiles) files = @@ -694,9 +693,9 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, int ret;
if (qcrypto_tls_creds_get_path(&creds->parent_obj, certbase, - !isOptional, &files->certpath, errp) < 0 || + false, &files->certpath, errp) < 0 || qcrypto_tls_creds_get_path(&creds->parent_obj, keybase, - !isOptional, &files->keypath, errp) < 0) { + false, &files->keypath, errp) < 0) { return NULL; }
@@ -705,13 +704,17 @@ qcrypto_tls_creds_x509_load_identity(QCryptoTLSCredsX509 *creds, return NULL; } if (files->certpath && !files->keypath) { - error_setg(errp, "Cert '%s' without corresponding key", - files->certpath); + 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) { - error_setg(errp, "Key '%s' without corresponding cert", - 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; }
@@ -750,7 +753,9 @@ qcrypto_tls_creds_x509_load_identities(QCryptoTLSCredsX509 *creds, bool isServer, Error **errp) { + ERRP_GUARD(); QCryptoTLSCredsX509IdentFiles *ifiles; + size_t i;
ifiles = qcrypto_tls_creds_x509_load_identity( creds, box, @@ -760,15 +765,52 @@ qcrypto_tls_creds_x509_load_identities(QCryptoTLSCredsX509 *creds, isServer ? QCRYPTO_TLS_CREDS_X509_SERVER_KEY : QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, - !isServer, errp); - if (!ifiles) { + errp); + if (!ifiles && *errp) { return -1; }
- files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, - files->identities, - files->nidentities + 1); - files->identities[files->nidentities++] = ifiles; + 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; + } + if (!ifiles) { + break; + } + + files->identities = g_renew(QCryptoTLSCredsX509IdentFiles *, + files->identities, + files->nidentities + 1); + files->identities[files->nidentities++] = ifiles; + } + + 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; + }
return 0; } diff --git a/crypto/tlssession.c b/crypto/tlssession.c index a1dc3b3ce0..314e3e96ba 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -345,6 +345,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session, 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 d0e33427fa..771f9b8a6e 100644 --- a/crypto/trace-events +++ b/crypto/trace-events @@ -21,6 +21,7 @@ qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds # 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/docs/system/tls.rst b/docs/system/tls.rst index 44c4bf04e9..7cec4ac3df 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -36,8 +36,58 @@ server and exposing it directly to remote browser clients. In such a 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:
diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h index c4daba21a6..61b7f73573 100644 --- a/include/crypto/tlscredsx509.h +++ b/include/crypto/tlscredsx509.h @@ -37,7 +37,13 @@ typedef struct QCryptoTLSCredsX509Class QCryptoTLSCredsX509Class; #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: -- 2.51.1
Explain how to alter the certtool commands for creating certficates, so that they can use algorithms that are compliant with post-quantum crytography standards. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- docs/system/tls.rst | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/system/tls.rst b/docs/system/tls.rst index 7cec4ac3df..03fa1d8166 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -345,6 +345,74 @@ example with VNC: .. _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) ~~~~~~~~~~~~~~~~~~~~~~~~~ -- 2.51.1
On Thu, Oct 30, 2025 at 6:50 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
Explain how to alter the certtool commands for creating certficates, so that they can use algorithms that are compliant with post-quantum crytography standards.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
--- docs/system/tls.rst | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
diff --git a/docs/system/tls.rst b/docs/system/tls.rst index 7cec4ac3df..03fa1d8166 100644 --- a/docs/system/tls.rst +++ b/docs/system/tls.rst @@ -345,6 +345,74 @@ example with VNC:
.. _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) ~~~~~~~~~~~~~~~~~~~~~~~~~
-- 2.51.1
participants (3)
- 
                
Daniel P. Berrangé - 
                
Marc-André Lureau - 
                
Philippe Mathieu-Daudé