]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
rpc: support loading multiple certificate identities
authorDaniel P. Berrangé <berrange@redhat.com>
Thu, 6 Nov 2025 11:46:49 +0000 (11:46 +0000)
committerDaniel P. Berrangé <berrange@redhat.com>
Mon, 24 Nov 2025 15:05:09 +0000 (15:05 +0000)
In addition to servercert.pem / serverkey.pem, we now also support
loading servercert{N}.pem / serverkey{N}.pem, for values of {N}
between 0 and 3 inclusive.

If servercert0.pem is provided, then using servercert.pem becomes
optional. The first missing index terminates the loading process.
eg if servercert1.pem is NOT present, then it will NOT attempt to
look for servercert2.pem / servercert3.pem.

This also applies to clientcert.pem / clientkey.pem.

This facilitates the transition to post-quantum cryptography by
allowing loading of certificates with different algorithms,
eg traditional RSA based cert, and optional ECC based cert or
MLDSA based cert for PQC.

The use of CA cert files is unchanged with only a single cacert.pem
loaded. WHen multiple CAs are needed they must be concatenated in
the single cacert.pem file.

Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
src/rpc/virnettlsconfig.c
src/rpc/virnettlsconfig.h
src/rpc/virnettlscontext.c

index 59cb8c2566744b859e904b60fae4da27c687217a..eec20cf6b7f77da6c1ca9764cb0a6677dec215de 100644 (file)
@@ -31,6 +31,9 @@
 
 VIR_LOG_INIT("rpc.nettlsconfig");
 
+
+#define VIR_NET_TLS_CONFIG_MAX_INDEXED 4
+
 char *virNetTLSConfigUserPKIBaseDir(void)
 {
     g_autofree char *userdir = virGetUserDirectory();
@@ -69,6 +72,24 @@ static void virNetTLSConfigIdentity(bool isServer,
     VIR_DEBUG("TLS cert %s", *cert);
 }
 
+static void virNetTLSConfigIdentityIndexed(bool isServer,
+                                           const char *certdir,
+                                           const char *keydir,
+                                           size_t idx,
+                                           char **cert,
+                                           char **key)
+{
+    if (!*key)
+        *key = g_strdup_printf("%s/%skey%zu.pem", keydir,
+                               (isServer ? "server" : "client"), idx);
+    if (!*cert)
+        *cert = g_strdup_printf("%s/%scert%zu.pem", certdir,
+                                (isServer ? "server" : "client"), idx);
+
+    VIR_DEBUG("TLS key %s", *key);
+    VIR_DEBUG("TLS cert %s", *cert);
+}
+
 void virNetTLSConfigCustomTrust(const char *pkipath,
                                 char **cacert,
                                 char **cacrl)
@@ -257,8 +278,8 @@ static int virNetTLSConfigCreds(const char *cacertdir,
                                 bool allowMissingIdentity,
                                 char **cacert,
                                 char **cacrl,
-                                char **cert,
-                                char **key)
+                                char ***certs,
+                                char ***keys)
 {
     virNetTLSConfigTrust(cacertdir,
                          cacrldir,
@@ -268,14 +289,68 @@ static int virNetTLSConfigCreds(const char *cacertdir,
     if (virNetTLSConfigEnsureTrust(cacert, cacrl, allowMissingCA) < 0)
         return -1;
 
-    virNetTLSConfigIdentity(isServer,
-                            certdir,
-                            keydir,
-                            cert,
-                            key);
+    if (!*certs && !*keys) {
+        g_auto(GStrv) certlist =
+            g_new0(char *, VIR_NET_TLS_CONFIG_MAX_INDEXED + 2);
+        g_auto(GStrv) keylist =
+            g_new0(char *, VIR_NET_TLS_CONFIG_MAX_INDEXED + 2);
+        size_t i;
+
+        /*
+         * When searching for indexed certs/keys, the first
+         * missing index terminates the search, so we don't
+         * get gaps in the returned array. All indexed files
+         * are optional, as if they're all missing, we'll
+         * still honour the traditional file names
+         */
+        for (i = 0; i < VIR_NET_TLS_CONFIG_MAX_INDEXED; i++) {
+            virNetTLSConfigIdentityIndexed(isServer,
+                                           certdir,
+                                           keydir,
+                                           i,
+                                           &(certlist[i + 1]),
+                                           &(keylist[i + 1]));
+
+            if (virNetTLSConfigEnsureIdentity(&(certlist[i + 1]),
+                                              &(keylist[i + 1]), true) < 0)
+                return -1;
 
-    if (virNetTLSConfigEnsureIdentity(cert, key, allowMissingIdentity) < 0)
-        return -1;
+            if (certlist[i + 1] == NULL) {
+                break;
+            }
+        }
+
+        /*
+         * If we find index 0, then allow the traditional
+         * default files to be optional
+         */
+        if (certlist[1] != NULL)
+            allowMissingIdentity = true;
+
+        virNetTLSConfigIdentity(isServer,
+                                certdir,
+                                keydir,
+                                &(certlist[0]),
+                                &(keylist[0]));
+
+        if (virNetTLSConfigEnsureIdentity(&(certlist[0]),
+                                          &(keylist[0]), allowMissingIdentity) < 0)
+            return -1;
+
+        if (certlist[0] == NULL) {
+            memmove(certlist, certlist + 1,
+                    VIR_NET_TLS_CONFIG_MAX_INDEXED * sizeof(char *));
+            memmove(keylist, keylist + 1,
+                    VIR_NET_TLS_CONFIG_MAX_INDEXED * sizeof(char *));
+            certlist[VIR_NET_TLS_CONFIG_MAX_INDEXED] = NULL;
+            keylist[VIR_NET_TLS_CONFIG_MAX_INDEXED] = NULL;
+        }
+
+        if (certlist[0] != NULL) {
+            *certs = g_steal_pointer(&certlist);
+            *keys = g_steal_pointer(&keylist);
+        }
+    }
 
     return 0;
 }
@@ -285,8 +360,8 @@ int virNetTLSConfigCustomCreds(const char *pkipath,
                                bool isServer,
                                char **cacert,
                                char **cacrl,
-                               char **cert,
-                               char **key)
+                               char ***certs,
+                               char ***keys)
 {
     VIR_DEBUG("Locating creds in custom dir %s", pkipath);
 
@@ -296,15 +371,15 @@ int virNetTLSConfigCustomCreds(const char *pkipath,
                                 false,
                                 !isServer,
                                 cacert, cacrl,
-                                cert, key);
+                                certs, keys);
 }
 
 
 int virNetTLSConfigUserCreds(bool isServer,
                              char **cacert,
                              char **cacrl,
-                             char **cert,
-                             char **key)
+                             char ***certs,
+                             char ***keys)
 {
     g_autofree char *pkipath = virNetTLSConfigUserPKIBaseDir();
 
@@ -316,14 +391,14 @@ int virNetTLSConfigUserCreds(bool isServer,
                                 true,
                                 true,
                                 cacert, cacrl,
-                                cert, key);
+                                certs, keys);
 }
 
 int virNetTLSConfigSystemCreds(bool isServer,
                                char **cacert,
                                char **cacrl,
-                               char **cert,
-                               char **key)
+                               char ***certs,
+                               char ***keys)
 {
     VIR_DEBUG("Locating creds in system dir %s", LIBVIRT_PKI_DIR);
 
@@ -335,5 +410,5 @@ int virNetTLSConfigSystemCreds(bool isServer,
                                 false,
                                 !isServer,
                                 cacert, cacrl,
-                                cert, key);
+                                certs, keys);
 }
index 9ad213fe06ea805939d141cdad753bdeca226cac..c0bf604950b1dbcf97667909500902d4f438753e 100644 (file)
@@ -60,15 +60,15 @@ int virNetTLSConfigCustomCreds(const char *pkipath,
                                bool isServer,
                                char **cacert,
                                char **cacrl,
-                               char **cert,
-                               char **key);
+                               char ***certs,
+                               char ***keys);
 int virNetTLSConfigUserCreds(bool isServer,
                              char **cacert,
                              char **cacrl,
-                             char **cert,
-                             char **key);
+                             char ***certs,
+                             char ***keys);
 int virNetTLSConfigSystemCreds(bool isServer,
                                char **cacert,
                                char **cacrl,
-                               char **cert,
-                               char **key);
+                               char ***certs,
+                               char ***keys);
index 7061eb59531317b80d78399988592b1c48300b08..dfe793e5db14a9ec544109a0b1d44f946e1df53f 100644 (file)
@@ -220,13 +220,13 @@ static int virNetTLSContextLocateCredentials(const char *pkipath,
                                              bool isServer,
                                              char **cacert,
                                              char **cacrl,
-                                             char **cert,
-                                             char **key)
+                                             char ***certs,
+                                             char ***keys)
 {
     *cacert = NULL;
     *cacrl = NULL;
-    *key = NULL;
-    *cert = NULL;
+    *keys = NULL;
+    *certs = NULL;
 
     VIR_DEBUG("pkipath=%s isServer=%d tryUserPkiPath=%d",
               pkipath, isServer, tryUserPkiPath);
@@ -237,20 +237,31 @@ static int virNetTLSContextLocateCredentials(const char *pkipath,
     if (pkipath) {
         if (virNetTLSConfigCustomCreds(pkipath, isServer,
                                        cacert, cacrl,
-                                       cert, key) < 0)
+                                       certs, keys) < 0)
             return -1;
     } else {
         if (tryUserPkiPath &&
             virNetTLSConfigUserCreds(isServer,
                                      cacert, cacrl,
-                                     cert, key) < 0)
+                                     certs, keys) < 0)
             return -1;
 
         if (virNetTLSConfigSystemCreds(isServer,
                                        cacert, cacrl,
-                                       cert, key) < 0)
+                                       certs, keys) < 0)
             return -1;
     }
+
+    /*
+     * Ensure the cert list is always non-NULL, even
+     * if it is an empty list, so that callers don't
+     * need to have repeated checks for a NULL array.
+     */
+    if (*certs == NULL)
+        *certs = g_new0(char *, 1);
+    if (*keys == NULL)
+        *keys = g_new0(char *, 1);
+
     return 0;
 }
 
@@ -265,16 +276,16 @@ static virNetTLSContext *virNetTLSContextNewPath(const char *pkipath,
 {
     g_autofree char *cacert = NULL;
     g_autofree char *cacrl = NULL;
-    g_autofree char *key = NULL;
-    g_autofree char *cert = NULL;
-    const char *certs[] = { cert, NULL };
-    const char *keys[] = { key, NULL };
+    g_auto(GStrv) keys = NULL;
+    g_auto(GStrv) certs = NULL;
 
     if (virNetTLSContextLocateCredentials(pkipath, tryUserPkiPath, isServer,
-                                          &cacert, &cacrl, &cert, &key) < 0)
+                                          &cacert, &cacrl, &certs, &keys) < 0)
         return NULL;
 
-    return virNetTLSContextNew(cacert, cacrl, certs, keys,
+    return virNetTLSContextNew(cacert, cacrl,
+                               (const char *const *)certs,
+                               (const char *const *)keys,
                                x509dnACL, priority, sanityCheckCert,
                                requireValidCert, isServer);
 }
@@ -329,15 +340,13 @@ int virNetTLSContextReloadForServer(virNetTLSContext *ctxt,
     int err;
     g_autofree char *cacert = NULL;
     g_autofree char *cacrl = NULL;
-    g_autofree char *cert = NULL;
-    g_autofree char *key = NULL;
-    const char *certs[] = { cert, NULL };
-    const char *keys[] = { key, NULL };
+    g_auto(GStrv) certs = NULL;
+    g_auto(GStrv) keys = NULL;
 
     x509credBak = g_steal_pointer(&ctxt->x509cred);
 
     if (virNetTLSContextLocateCredentials(NULL, tryUserPkiPath, true,
-                                          &cacert, &cacrl, &cert, &key))
+                                          &cacert, &cacrl, &certs, &keys))
         goto error;
 
     err = gnutls_certificate_allocate_credentials(&ctxt->x509cred);
@@ -348,11 +357,13 @@ int virNetTLSContextReloadForServer(virNetTLSContext *ctxt,
         goto error;
     }
 
-    if (virNetTLSCertSanityCheck(true, cacert, certs))
+    if (virNetTLSCertSanityCheck(true, cacert,
+                                 (const char *const *)certs))
         goto error;
 
     if (virNetTLSContextLoadCredentials(ctxt, cacert, cacrl,
-                                        certs, keys))
+                                        (const char *const *)certs,
+                                        (const char *const *)keys))
         goto error;
 
     gnutls_certificate_free_credentials(x509credBak);