From nobody Fri Nov 21 10:08:32 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass(p=reject dis=none) header.from=lists.libvirt.org ARC-Seal: i=1; a=rsa-sha256; t=1762441432; cv=none; d=zohomail.com; s=zohoarc; b=IcPUjcmYlz6SEiB2liv5nAQ8KkyWgegswx3hYWu5vDiUVmpzdMDXQJbs2UQuHv21cWU+qAynHNWy0cpmL6po+43ptCJTv0q30AI/eHtmNP6O0iq4OXHv8DsY3YK7vuf5BIuDUtZdMsS27GfT2ROVjJktffEoN9gZ+KEcwisfXT4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1762441432; h=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Owner:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:References:Subject:Subject:To:To:Message-Id:Cc; bh=hXNW4w3KF28K8IOPKO1E+GGPnvtcAwvi4OSBT5lBWSA=; b=LGyrrleuN3Gr+yt9meoDCBDU0jsslEQN4zToolD0QYdFcpfvNOtK37sulKa/dKWkPcpnmDz8ENB4WvFHFIApkN1AbotfmID99sMrCP3AK12cVrYO59ksE9xh6m5nNm4ToGdfnaDAvOBnYTVarJxCyU22srOdJ/JDouzCNf7YEU4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1762441432055205.67512189636523; Thu, 6 Nov 2025 07:03:52 -0800 (PST) Received: by lists.libvirt.org (Postfix, from userid 993) id EE31B440F9; Thu, 6 Nov 2025 10:03:50 -0500 (EST) Received: from [172.19.199.29] (lists.libvirt.org [8.43.85.245]) by lists.libvirt.org (Postfix) with ESMTP id 51750444B7; Thu, 6 Nov 2025 09:53:53 -0500 (EST) Received: by lists.libvirt.org (Postfix, from userid 993) id A1289418F9; Thu, 6 Nov 2025 09:51:13 -0500 (EST) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id A689841988 for ; Thu, 6 Nov 2025 09:51:12 -0500 (EST) Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-645-o4AYYa8OOf-1BsMPei2vvQ-1; Thu, 06 Nov 2025 09:51:05 -0500 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 01FBE180057D for ; Thu, 6 Nov 2025 14:51:05 +0000 (UTC) Received: from toolbx.redhat.com (unknown [10.42.28.39]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 122141800451; Thu, 6 Nov 2025 14:51:03 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-5.0 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_PASS autolearn=unavailable autolearn_force=no version=4.0.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1762440672; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hXNW4w3KF28K8IOPKO1E+GGPnvtcAwvi4OSBT5lBWSA=; b=S12nLdT7ccn/+rOySkN643CpPFp10QdwrGifppPxAgB0zrd0x10BxgL4Av9pQNRF93ZHTf KpzGzcIYs64juoZRdkhccvUCcaSuTLzHEO2WQxFpsTgEqCgGbtvRdmON/qIwV1DadKPwd8 cVy043gP7Xm/7L1YF/mEx1eQvloSiMU= X-MC-Unique: o4AYYa8OOf-1BsMPei2vvQ-1 X-Mimecast-MFC-AGG-ID: o4AYYa8OOf-1BsMPei2vvQ_1762440665 To: devel@lists.libvirt.org Subject: [PATCH 09/10] rpc: support loading multiple certificate identities Date: Thu, 6 Nov 2025 14:50:49 +0000 Message-ID: <20251106145050.1851526-10-berrange@redhat.com> In-Reply-To: <20251106145050.1851526-1-berrange@redhat.com> References: <20251106145050.1851526-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 0Gu0a_yvYecirTNtn3Y01tPlCACPPAAHv1_NacNa6SQ_1762440665 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID-Hash: THGIKUUMFDBF4R4HBNWA7VDS6I2KDPT3 X-Message-ID-Hash: THGIKUUMFDBF4R4HBNWA7VDS6I2KDPT3 X-MailFrom: berrange@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.lists.libvirt.org-0; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: =?utf-8?q?Daniel_P=2E_Berrang=C3=A9_via_Devel?= Reply-To: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1762441434323158501 From: Daniel P. Berrang=C3=A9 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. Signed-off-by: Daniel P. Berrang=C3=A9 --- src/rpc/virnettlsconfig.c | 111 +++++++++++++++++++++++++++++++------ src/rpc/virnettlsconfig.h | 12 ++-- src/rpc/virnettlscontext.c | 51 ++++++++++------- 3 files changed, 130 insertions(+), 44 deletions(-) diff --git a/src/rpc/virnettlsconfig.c b/src/rpc/virnettlsconfig.c index 59cb8c2566..eec20cf6b7 100644 --- a/src/rpc/virnettlsconfig.c +++ b/src/rpc/virnettlsconfig.c @@ -31,6 +31,9 @@ =20 VIR_LOG_INIT("rpc.nettlsconfig"); =20 + +#define VIR_NET_TLS_CONFIG_MAX_INDEXED 4 + char *virNetTLSConfigUserPKIBaseDir(void) { g_autofree char *userdir =3D virGetUserDirectory(); @@ -69,6 +72,24 @@ static void virNetTLSConfigIdentity(bool isServer, VIR_DEBUG("TLS cert %s", *cert); } =20 +static void virNetTLSConfigIdentityIndexed(bool isServer, + const char *certdir, + const char *keydir, + size_t idx, + char **cert, + char **key) +{ + if (!*key) + *key =3D g_strdup_printf("%s/%skey%zu.pem", keydir, + (isServer ? "server" : "client"), idx); + if (!*cert) + *cert =3D 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; =20 - virNetTLSConfigIdentity(isServer, - certdir, - keydir, - cert, - key); + if (!*certs && !*keys) { + g_auto(GStrv) certlist =3D + g_new0(char *, VIR_NET_TLS_CONFIG_MAX_INDEXED + 2); + g_auto(GStrv) keylist =3D + 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 =3D 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; =20 - if (virNetTLSConfigEnsureIdentity(cert, key, allowMissingIdentity) < 0) - return -1; + if (certlist[i + 1] =3D=3D NULL) { + break; + } + } + + /* + * If we find index 0, then allow the traditional + * default files to be optional + */ + if (certlist[1] !=3D NULL) + allowMissingIdentity =3D true; + + virNetTLSConfigIdentity(isServer, + certdir, + keydir, + &(certlist[0]), + &(keylist[0])); + + if (virNetTLSConfigEnsureIdentity(&(certlist[0]), + &(keylist[0]), allowMissingIdent= ity) < 0) + return -1; + + if (certlist[0] =3D=3D 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] =3D NULL; + keylist[VIR_NET_TLS_CONFIG_MAX_INDEXED] =3D NULL; + } + + if (certlist[0] !=3D NULL) { + *certs =3D g_steal_pointer(&certlist); + *keys =3D g_steal_pointer(&keylist); + } + } =20 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); =20 @@ -296,15 +371,15 @@ int virNetTLSConfigCustomCreds(const char *pkipath, false, !isServer, cacert, cacrl, - cert, key); + certs, keys); } =20 =20 int virNetTLSConfigUserCreds(bool isServer, char **cacert, char **cacrl, - char **cert, - char **key) + char ***certs, + char ***keys) { g_autofree char *pkipath =3D virNetTLSConfigUserPKIBaseDir(); =20 @@ -316,14 +391,14 @@ int virNetTLSConfigUserCreds(bool isServer, true, true, cacert, cacrl, - cert, key); + certs, keys); } =20 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); =20 @@ -335,5 +410,5 @@ int virNetTLSConfigSystemCreds(bool isServer, false, !isServer, cacert, cacrl, - cert, key); + certs, keys); } diff --git a/src/rpc/virnettlsconfig.h b/src/rpc/virnettlsconfig.h index 9ad213fe06..c0bf604950 100644 --- a/src/rpc/virnettlsconfig.h +++ b/src/rpc/virnettlsconfig.h @@ -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); diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c index 7061eb5953..dfe793e5db 100644 --- a/src/rpc/virnettlscontext.c +++ b/src/rpc/virnettlscontext.c @@ -220,13 +220,13 @@ static int virNetTLSContextLocateCredentials(const ch= ar *pkipath, bool isServer, char **cacert, char **cacrl, - char **cert, - char **key) + char ***certs, + char ***keys) { *cacert =3D NULL; *cacrl =3D NULL; - *key =3D NULL; - *cert =3D NULL; + *keys =3D NULL; + *certs =3D NULL; =20 VIR_DEBUG("pkipath=3D%s isServer=3D%d tryUserPkiPath=3D%d", pkipath, isServer, tryUserPkiPath); @@ -237,20 +237,31 @@ static int virNetTLSContextLocateCredentials(const ch= ar *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; =20 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 =3D=3D NULL) + *certs =3D g_new0(char *, 1); + if (*keys =3D=3D NULL) + *keys =3D g_new0(char *, 1); + return 0; } =20 @@ -265,16 +276,16 @@ static virNetTLSContext *virNetTLSContextNewPath(cons= t char *pkipath, { g_autofree char *cacert =3D NULL; g_autofree char *cacrl =3D NULL; - g_autofree char *key =3D NULL; - g_autofree char *cert =3D NULL; - const char *certs[] =3D { cert, NULL }; - const char *keys[] =3D { key, NULL }; + g_auto(GStrv) keys =3D NULL; + g_auto(GStrv) certs =3D NULL; =20 if (virNetTLSContextLocateCredentials(pkipath, tryUserPkiPath, isServe= r, - &cacert, &cacrl, &cert, &key) < = 0) + &cacert, &cacrl, &certs, &keys) = < 0) return NULL; =20 - 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 =3D NULL; g_autofree char *cacrl =3D NULL; - g_autofree char *cert =3D NULL; - g_autofree char *key =3D NULL; - const char *certs[] =3D { cert, NULL }; - const char *keys[] =3D { key, NULL }; + g_auto(GStrv) certs =3D NULL; + g_auto(GStrv) keys =3D NULL; =20 x509credBak =3D g_steal_pointer(&ctxt->x509cred); =20 if (virNetTLSContextLocateCredentials(NULL, tryUserPkiPath, true, - &cacert, &cacrl, &cert, &key)) + &cacert, &cacrl, &certs, &keys)) goto error; =20 err =3D gnutls_certificate_allocate_credentials(&ctxt->x509cred); @@ -348,11 +357,13 @@ int virNetTLSContextReloadForServer(virNetTLSContext = *ctxt, goto error; } =20 - if (virNetTLSCertSanityCheck(true, cacert, certs)) + if (virNetTLSCertSanityCheck(true, cacert, + (const char *const *)certs)) goto error; =20 if (virNetTLSContextLoadCredentials(ctxt, cacert, cacrl, - certs, keys)) + (const char *const *)certs, + (const char *const *)keys)) goto error; =20 gnutls_certificate_free_credentials(x509credBak); --=20 2.51.1