Add support for ML-DSA certificates (RHEL-105441)

Resolves: RHEL-105441
This commit is contained in:
Scott Mayhew 2025-09-17 16:12:00 -04:00
parent 3bd22847d2
commit b7d9966184
5 changed files with 1069 additions and 0 deletions

View File

@ -0,0 +1,135 @@
From facd084e43fc493ae8fbaca00cb65925ae30d0e9 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
Date: Wed, 10 Sep 2025 21:00:09 -0400
Subject: [PATCH 4/4] tlshd: Client-side dual certificate support
Add two new config options "x509.pq.certificate" and
"x509.pq.private_key", this time to the "[authenticate.client]" stanza
of tlshd.conf. This is for client-side handling of the server's
certificate request when the client is mounting with "xprtsec=mtls".
This commit also makes sure the client-side x509.pq.certificate is using
a post-quantum public-key algorithm, and we make sure that the server
supports that algorithm before returning that cert in the cert callback
(unlike the server-side cert callback, the pk_algos list is populated,
so this check is more straightforward than on the server-side).
Link: https://github.com/oracle/ktls-utils/issues/113
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
src/tlshd/client.c | 45 ++++++++++++++++++++++++++++++++++++--------
src/tlshd/tlshd.conf | 2 ++
2 files changed, 39 insertions(+), 8 deletions(-)
diff --git a/src/tlshd/client.c b/src/tlshd/client.c
index 257e835..ad9a793 100644
--- a/src/tlshd/client.c
+++ b/src/tlshd/client.c
@@ -134,17 +134,21 @@ out_free_creds:
gnutls_certificate_free_credentials(xcred);
}
+static gnutls_privkey_t tlshd_pq_privkey;
static gnutls_privkey_t tlshd_privkey;
+static unsigned int tlshd_pq_certs_len = TLSHD_MAX_CERTS;
static unsigned int tlshd_certs_len = TLSHD_MAX_CERTS;
static gnutls_pcert_st tlshd_certs[TLSHD_MAX_CERTS];
+static gnutls_pk_algorithm_t tlshd_pq_pkalg = GNUTLS_PK_UNKNOWN;
static bool tlshd_x509_client_get_certs(struct tlshd_handshake_parms *parms)
{
if (parms->x509_cert != TLS_NO_CERT)
return tlshd_keyring_get_certs(parms->x509_cert, tlshd_certs,
&tlshd_certs_len);
- return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs, NULL,
- &tlshd_certs_len, NULL);
+ return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs,
+ &tlshd_pq_certs_len, &tlshd_certs_len,
+ &tlshd_pq_pkalg);
}
static void tlshd_x509_client_put_certs(void)
@@ -160,12 +164,14 @@ static bool tlshd_x509_client_get_privkey(struct tlshd_handshake_parms *parms)
if (parms->x509_privkey != TLS_NO_PRIVKEY)
return tlshd_keyring_get_privkey(parms->x509_privkey,
&tlshd_privkey);
- return tlshd_config_get_privkey(PEER_TYPE_CLIENT, NULL, &tlshd_privkey);
+ return tlshd_config_get_privkey(PEER_TYPE_CLIENT, &tlshd_pq_privkey,
+ &tlshd_privkey);
}
static void tlshd_x509_client_put_privkey(void)
{
gnutls_privkey_deinit(tlshd_privkey);
+ gnutls_privkey_deinit(tlshd_pq_privkey);
}
static void tlshd_x509_log_issuers(const gnutls_datum_t *req_ca_rdn, int nreqs)
@@ -206,13 +212,15 @@ static void tlshd_x509_log_issuers(const gnutls_datum_t *req_ca_rdn, int nreqs)
static int
tlshd_x509_retrieve_key_cb(gnutls_session_t session,
const gnutls_datum_t *req_ca_rdn, int nreqs,
- __attribute__ ((unused)) const gnutls_pk_algorithm_t *pk_algos,
- __attribute__ ((unused)) int pk_algos_length,
+ const gnutls_pk_algorithm_t *pk_algos,
+ int pk_algos_length,
gnutls_pcert_st **pcert,
unsigned int *pcert_length,
gnutls_privkey_t *privkey)
{
gnutls_certificate_type_t type;
+ bool use_pq_cert = false;
+ int i;
tlshd_x509_log_issuers(req_ca_rdn, nreqs);
@@ -220,9 +228,30 @@ tlshd_x509_retrieve_key_cb(gnutls_session_t session,
if (type != GNUTLS_CRT_X509)
return -1;
- *pcert_length = tlshd_certs_len;
- *pcert = tlshd_certs;
- *privkey = tlshd_privkey;
+ if (tlshd_pq_pkalg != GNUTLS_PK_UNKNOWN) {
+ for (i = 0; i < pk_algos_length; i++) {
+ if (pk_algos[i] == tlshd_pq_pkalg) {
+ use_pq_cert = true;
+ break;
+ }
+ }
+ if (use_pq_cert == true) {
+ tlshd_log_debug("%s: Server supports %s", __func__,
+ gnutls_pk_algorithm_get_name(pk_algos[i]));
+ }
+ }
+
+ if (use_pq_cert == true) {
+ tlshd_log_debug("%s: Selecting x509.pq.certificate from conf file", __func__);
+ *pcert_length = tlshd_pq_certs_len;
+ *pcert = tlshd_certs;
+ *privkey = tlshd_pq_privkey;
+ } else {
+ tlshd_log_debug("%s: Selecting x509.certificate from conf file", __func__);
+ *pcert_length = tlshd_certs_len;
+ *pcert = tlshd_certs + tlshd_pq_certs_len;
+ *privkey = tlshd_privkey;
+ }
return 0;
}
diff --git a/src/tlshd/tlshd.conf b/src/tlshd/tlshd.conf
index 5419146..1d4220e 100644
--- a/src/tlshd/tlshd.conf
+++ b/src/tlshd/tlshd.conf
@@ -33,6 +33,8 @@ nl=0
#x509.crl= <pathname>
#x509.certificate= <pathname>
#x509.private_key= <pathname>
+#x509.pq.certificate= <pathname>
+#x509.pq.private_key= <pathname>
[authenticate.server]
#x509.truststore= <pathname>
--
2.51.0

View File

@ -0,0 +1,39 @@
From 9253f9df5ff986bddba38b8146ba6e650e7ebdd9 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
Date: Wed, 10 Sep 2025 17:02:16 -0400
Subject: [PATCH 2/4] tlshd: Fix priority string to allow PQC
Specifying either of the SECURE256 or SECURE128 keywords in the priority
string results in the ML-DSA algorithms being disabled because the
post-quantum algorithms do not map nicely to the security
classifications based on "bits of security" used for traditional
algorithms [1].
Use @SYSTEM instead, which will allow PQC on systems with newer versions
of GnuTLS. It will also allow users to disable PQC via a policy module
(on systems with the crypto-policies package).
[1] https://csrc.nist.gov/CSRC/media/Projects/Post-Quantum-Cryptography/documents/call-for-proposals-final-dec-2016.pdf#page=15
Link: https://github.com/oracle/ktls-utils/issues/113
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
src/tlshd/ktls.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tlshd/ktls.c b/src/tlshd/ktls.c
index 883256a..50381bf 100644
--- a/src/tlshd/ktls.c
+++ b/src/tlshd/ktls.c
@@ -357,7 +357,7 @@ static int tlshd_gnutls_priority_init_list(const unsigned int *ciphers,
const char *errpos;
int ret, i;
- pstring = strdup("SECURE256:+SECURE128:-COMP-ALL");
+ pstring = strdup("@SYSTEM:-COMP-ALL");
if (!pstring)
return -ENOMEM;
--
2.51.0

View File

@ -0,0 +1,445 @@
From 14f53496509eea9cbe14eb244161b0e26699d165 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
Date: Wed, 10 Sep 2025 20:45:41 -0400
Subject: [PATCH 3/4] tlshd: Server-side dual certificate support
Add two new config options, "x509.pq.certificate" and
"x509.pq.private_key" to configure tlshd to use an ML-DSA certificate.
If the cert callback determines that the client supports ML-DSA, it will
select this certificate. Otherwise, it will fall back to the
traditional certficate (i.e. the certificate configured via
"x509.certificate" and "x509.private_key").
Note that we check to ensure that the PQ certificate is using a
post-quantum public-key algorithm (ML-DSA-44, ML-DSA-65, or ML-DSA-87),
and we store the algorithm value so we can later compare it to the list
of signing algorithms supported by the client in the cert callback.
Link: https://github.com/oracle/ktls-utils/issues/113
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
configure.ac | 12 ++++
src/tlshd/client.c | 6 +-
src/tlshd/config.c | 132 ++++++++++++++++++++++++++++++++++++---
src/tlshd/server.c | 65 ++++++++++++++++++-
src/tlshd/tlshd.conf | 2 +
src/tlshd/tlshd.conf.man | 15 +++++
src/tlshd/tlshd.h | 7 ++-
7 files changed, 225 insertions(+), 14 deletions(-)
diff --git a/configure.ac b/configure.ac
index a6d9d09..0dd23f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,6 +79,18 @@ AC_CHECK_LIB([gnutls], [gnutls_get_system_config_file],
AC_CHECK_LIB([gnutls], [gnutls_psk_allocate_client_credentials2],
[AC_DEFINE([HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2], [1],
[Define to 1 if you have the gnutls_psk_allocate_client_credentials2 function.])])
+
+AC_MSG_CHECKING(for ML-DSA support in gnutls)
+AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM([[ #include <gnutls/gnutls.h> ]],
+ [[ (void) GNUTLS_SIGN_MLDSA65; ]])],
+ [ have_mldsa=yes ],
+ [ have_mldsa=no ])
+AC_MSG_RESULT([$have_mldsa])
+if test "x$have_mldsa" = xyes ; then
+ AC_DEFINE([HAVE_GNUTLS_MLDSA], [1], [Define to 1 if gnutls supports ML-DSA])
+fi
+
AC_SUBST([AM_CPPFLAGS])
AC_CONFIG_FILES([Makefile src/Makefile src/tlshd/Makefile systemd/Makefile])
diff --git a/src/tlshd/client.c b/src/tlshd/client.c
index c07ae29..257e835 100644
--- a/src/tlshd/client.c
+++ b/src/tlshd/client.c
@@ -143,8 +143,8 @@ static bool tlshd_x509_client_get_certs(struct tlshd_handshake_parms *parms)
if (parms->x509_cert != TLS_NO_CERT)
return tlshd_keyring_get_certs(parms->x509_cert, tlshd_certs,
&tlshd_certs_len);
- return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs,
- &tlshd_certs_len);
+ return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs, NULL,
+ &tlshd_certs_len, NULL);
}
static void tlshd_x509_client_put_certs(void)
@@ -160,7 +160,7 @@ static bool tlshd_x509_client_get_privkey(struct tlshd_handshake_parms *parms)
if (parms->x509_privkey != TLS_NO_PRIVKEY)
return tlshd_keyring_get_privkey(parms->x509_privkey,
&tlshd_privkey);
- return tlshd_config_get_privkey(PEER_TYPE_CLIENT, &tlshd_privkey);
+ return tlshd_config_get_privkey(PEER_TYPE_CLIENT, NULL, &tlshd_privkey);
}
static void tlshd_x509_client_put_privkey(void)
diff --git a/src/tlshd/config.c b/src/tlshd/config.c
index ff1f2a5..9a3b6b2 100644
--- a/src/tlshd/config.c
+++ b/src/tlshd/config.c
@@ -260,18 +260,66 @@ bool tlshd_config_get_crl(int peer_type, char **result)
return true;
}
+#ifdef HAVE_GNUTLS_MLDSA
+static bool tlshd_cert_check_pk_alg(gnutls_datum_t *data,
+ gnutls_pk_algorithm_t *pkalg)
+{
+ gnutls_x509_crt_t cert;
+ gnutls_pk_algorithm_t pk_alg;
+ int ret;
+
+ ret = gnutls_x509_crt_init(&cert);
+ if (ret < 0)
+ return false;
+
+ ret = gnutls_x509_crt_import(cert, data, GNUTLS_X509_FMT_PEM);
+ if (ret < 0) {
+ gnutls_x509_crt_deinit(cert);
+ return false;
+ }
+
+ pk_alg = gnutls_x509_crt_get_pk_algorithm(cert, NULL);
+ tlshd_log_debug("%s: certificate pk algorithm %s", __func__,
+ gnutls_pk_algorithm_get_name(pk_alg));
+ switch (pk_alg) {
+ case GNUTLS_PK_MLDSA44:
+ case GNUTLS_PK_MLDSA65:
+ case GNUTLS_PK_MLDSA87:
+ *pkalg = pk_alg;
+ break;
+ default:
+ gnutls_x509_crt_deinit(cert);
+ return false;
+ }
+
+ gnutls_x509_crt_deinit(cert);
+ return true;
+}
+#else
+static bool tlshd_cert_check_pk_alg(__attribute__ ((unused)) gnutls_datum_t *data,
+ __attribute__ ((unused)) gnutls_pk_algorithm_t *pkalg)
+{
+ tlshd_log_debug("%s: gnutls version does not have ML-DSA support",
+ __func__);
+ return false;
+}
+#endif /* HAVE_GNUTLS_MLDSA */
+
/**
- * tlshd_config_get_certs - Get certs for {Client,Server} Hello from .conf
+ * __tlshd_config_get_certs - Helper for tlshd_config_get_certs()
* @peer_type: IN: peer type
* @certs: OUT: in-memory certificates
* @certs_len: IN: maximum number of certs to get, OUT: number of certs found
+ * @pkgalg: IN: if non-NULL, indicates we want to retrieve the PQ cert,
+ * OUT: if non-NULL, store the PQ public-key alg that was used in the PQ cert
*
* Return values:
* %true: certificate retrieved successfully
* %false: certificate not retrieved
*/
-bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
- unsigned int *certs_len)
+static bool __tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
+ unsigned int *certs_len,
+ gnutls_pk_algorithm_t *pkalg)
{
gnutls_datum_t data;
gchar *pathname;
@@ -281,7 +329,10 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
peer_type == PEER_TYPE_CLIENT ?
"authenticate.client" :
"authenticate.server",
- "x509.certificate", NULL);
+ pkalg != NULL ?
+ "x509.pq.certificate" :
+ "x509.certificate",
+ NULL);
if (!pathname)
return false;
@@ -291,6 +342,15 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
return false;
}
+ if (pkalg && !tlshd_cert_check_pk_alg(&data, pkalg)) {
+ tlshd_log_debug("%s: %s not using a PQ public-key algorithm",
+ __func__, pathname);
+ free(data.data);
+ g_free(pathname);
+ *certs_len = 0;
+ return false;
+ }
+
/* Config file supports only PEM-encoded certificates */
ret = gnutls_pcert_list_import_x509_raw(certs, certs_len, &data,
GNUTLS_X509_FMT_PEM, 0);
@@ -310,15 +370,51 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
}
/**
- * tlshd_config_get_privkey - Get private key for {Client,Server}Hello from .conf
+ * tlshd_config_get_certs - Get certs for {Client,Server} Hello from .conf
+ * @peer_type: IN: peer type
+ * @certs: OUT: in-memory certificates
+ * @pq_certs_len: IN: maximum number of PQ certs to get, OUT: number of PQ certs found
+ * @certs_len: IN: maximum number of certs to get, OUT: number of certs found
+ * @pkgalg: OUT: the PQ public-key alg that was used in the PQ cert
+ *
+ * Retrieve the PQ cert(s) first, then the RSA cert(s). Both are stored in the
+ * same list. Note that @pq_certs_len is deducted from the available @certs_len
+ * and is also used to determine the offset to store the RSA cert(s) in the
+ * @certs array.
+ *
+ * Return values:
+ * %true: certificate retrieved successfully
+ * %false: certificate not retrieved
+ */
+bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
+ unsigned int *pq_certs_len,
+ unsigned int *certs_len,
+ gnutls_pk_algorithm_t *pkalg)
+{
+ bool ret;
+
+ ret = __tlshd_config_get_certs(peer_type, certs, pq_certs_len, pkalg);
+
+ if (ret == true)
+ *certs_len -= *pq_certs_len;
+ else
+ *pq_certs_len = 0;
+
+ return __tlshd_config_get_certs(peer_type, certs + *pq_certs_len,
+ certs_len, NULL);
+}
+
+/**
+ * __tlshd_config_get_privkey - Helper for tlshd_config_get_privkey()
* @peer_type: IN: peer type
* @privkey: OUT: in-memory private key
+ * @pq: IN: if true, retrieve the PQ private key
*
* Return values:
* %true: private key retrieved successfully
* %false: private key not retrieved
*/
-bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
+static bool __tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey, bool pq)
{
gnutls_datum_t data;
gchar *pathname;
@@ -328,7 +424,10 @@ bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
peer_type == PEER_TYPE_CLIENT ?
"authenticate.client" :
"authenticate.server",
- "x509.private_key", NULL);
+ pq == true ?
+ "x509.pq.private_key" :
+ "x509.private_key",
+ NULL);
if (!pathname)
return false;
@@ -360,3 +459,22 @@ bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
g_free(pathname);
return true;
}
+
+/**
+ * tlshd_config_get_privkey - Get private key for {Client,Server}Hello from .conf
+ * @peer_type: IN: peer type
+ * @pq_privkey: OUT: in-memory PQ private key
+ * @privkey: OUT: in-memory private key
+ *
+ * Retrieve the PQ private key first, then the RSA private key.
+ *
+ * Return values:
+ * %true: private key retrieved successfully
+ * %false: private key not retrieved
+ */
+bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *pq_privkey,
+ gnutls_privkey_t *privkey)
+{
+ __tlshd_config_get_privkey(peer_type, pq_privkey, true);
+ return __tlshd_config_get_privkey(peer_type, privkey, false);
+}
diff --git a/src/tlshd/server.c b/src/tlshd/server.c
index efea387..13b805c 100644
--- a/src/tlshd/server.c
+++ b/src/tlshd/server.c
@@ -42,9 +42,12 @@
#include "tlshd.h"
#include "netlink.h"
+static gnutls_privkey_t tlshd_server_pq_privkey;
static gnutls_privkey_t tlshd_server_privkey;
+static unsigned int tlshd_server_pq_certs_len = TLSHD_MAX_CERTS;
static unsigned int tlshd_server_certs_len = TLSHD_MAX_CERTS;
static gnutls_pcert_st tlshd_server_certs[TLSHD_MAX_CERTS];
+static gnutls_pk_algorithm_t tlshd_server_pq_pkalg = GNUTLS_PK_UNKNOWN;
static bool tlshd_x509_server_get_certs(struct tlshd_handshake_parms *parms)
{
@@ -53,14 +56,16 @@ static bool tlshd_x509_server_get_certs(struct tlshd_handshake_parms *parms)
tlshd_server_certs,
&tlshd_server_certs_len);
return tlshd_config_get_certs(PEER_TYPE_SERVER, tlshd_server_certs,
- &tlshd_server_certs_len);
+ &tlshd_server_pq_certs_len,
+ &tlshd_server_certs_len,
+ &tlshd_server_pq_pkalg);
}
static void tlshd_x509_server_put_certs(void)
{
unsigned int i;
- for (i = 0; i < tlshd_server_certs_len; i++)
+ for (i = 0; i < TLSHD_MAX_CERTS; i++)
gnutls_pcert_deinit(&tlshd_server_certs[i]);
}
@@ -70,11 +75,13 @@ static bool tlshd_x509_server_get_privkey(struct tlshd_handshake_parms *parms)
return tlshd_keyring_get_privkey(parms->x509_privkey,
&tlshd_server_privkey);
return tlshd_config_get_privkey(PEER_TYPE_SERVER,
+ &tlshd_server_pq_privkey,
&tlshd_server_privkey);
}
static void tlshd_x509_server_put_privkey(void)
{
+ gnutls_privkey_deinit(tlshd_server_pq_privkey);
gnutls_privkey_deinit(tlshd_server_privkey);
}
@@ -123,6 +130,11 @@ tlshd_x509_retrieve_key_cb(gnutls_session_t session,
gnutls_privkey_t *privkey)
{
gnutls_certificate_type_t type;
+#ifdef HAVE_GNUTLS_MLDSA
+ gnutls_sign_algorithm_t client_alg;
+ bool use_pq_cert = false;
+ int i, ret;
+#endif /* HAVE_GNUTLS_MLDSA */
tlshd_x509_log_issuers(req_ca_rdn, nreqs);
@@ -130,9 +142,58 @@ tlshd_x509_retrieve_key_cb(gnutls_session_t session,
if (type != GNUTLS_CRT_X509)
return -1;
+#ifdef HAVE_GNUTLS_MLDSA
+ /*
+ * NB: Unfortunately when the callback function is invoked server-side,
+ * pk_algos is NULL and pk_algos_length is 0. So we check the signature
+ * algorithms the client supports and try to match one of them to the
+ * public-key algorithm used by the server cert.
+ */
+ if (tlshd_server_pq_pkalg != GNUTLS_PK_UNKNOWN) {
+ for (i = 0; ; i++) {
+ ret = gnutls_sign_algorithm_get_requested(session, i, &client_alg);
+ if (ret != GNUTLS_E_SUCCESS)
+ break;
+ switch (client_alg) {
+ case GNUTLS_SIGN_MLDSA44:
+ if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA44)
+ use_pq_cert = true;
+ break;
+ case GNUTLS_SIGN_MLDSA65:
+ if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA65)
+ use_pq_cert = true;
+ break;
+ case GNUTLS_SIGN_MLDSA87:
+ if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA87)
+ use_pq_cert = true;
+ break;
+ default:
+ break;
+ }
+ if (use_pq_cert == true) {
+ tlshd_log_debug("%s: Client supports %s", __func__,
+ gnutls_sign_get_name(client_alg));
+ break;
+ }
+ }
+ }
+
+ if (use_pq_cert == true) {
+ tlshd_log_debug("%s: Selecting x509.pq.certificate from conf file", __func__);
+ *pcert_length = tlshd_server_pq_certs_len;
+ *pcert = tlshd_server_certs;
+ *privkey = tlshd_server_pq_privkey;
+ } else {
+ tlshd_log_debug("%s: Selecting x509.certificate from conf file", __func__);
+ *pcert_length = tlshd_server_certs_len;
+ *pcert = tlshd_server_certs + tlshd_server_pq_certs_len;
+ *privkey = tlshd_server_privkey;
+ }
+#else
*pcert_length = tlshd_server_certs_len;
*pcert = tlshd_server_certs;
*privkey = tlshd_server_privkey;
+#endif /* HAVE_GNUTLS_MLDSA */
return 0;
}
diff --git a/src/tlshd/tlshd.conf b/src/tlshd/tlshd.conf
index 620bd17..5419146 100644
--- a/src/tlshd/tlshd.conf
+++ b/src/tlshd/tlshd.conf
@@ -39,3 +39,5 @@ nl=0
#x509.crl= <pathname>
#x509.certificate= <pathname>
#x509.private_key= <pathname>
+#x509.pq.certificate= <pathname>
+#x509.pq.private_key= <pathname>
diff --git a/src/tlshd/tlshd.conf.man b/src/tlshd/tlshd.conf.man
index 914261e..575d88b 100644
--- a/src/tlshd/tlshd.conf.man
+++ b/src/tlshd/tlshd.conf.man
@@ -125,6 +125,21 @@ a handshake request when no other certificate is available.
.B x509.private_key
This option specifies the pathname of a file containing
a PEM-encoded private key associated with the above certificate.
+.TP
+.B x509.pq.certificate
+This option specifies the pathname of a file containing
+a PEM-encoded x.509 certificate that is to be presented during
+a handshake request if the peer supports post-quantum cryptography.
+This certificate must be using a post-quantum public-key algorithm
+(ML-DSA-44, ML-DSA-65, or ML-DSA-87).
+If the peer does not support post-quantum cryptography, the
+certificate configured in the
+.I x509.certificate
+option will be presented instead.
+.TP
+.B x509.pq.private_key
+This option specifies the pathname of a file containing
+a PEM-encoded private key associated with the above certificate.
.SH SEE ALSO
.BR tlshd (8)
.SH AUTHOR
diff --git a/src/tlshd/tlshd.h b/src/tlshd/tlshd.h
index 6ba45ac..664de67 100644
--- a/src/tlshd/tlshd.h
+++ b/src/tlshd/tlshd.h
@@ -60,8 +60,11 @@ void tlshd_config_shutdown(void);
bool tlshd_config_get_truststore(int peer_type, char **bundle);
bool tlshd_config_get_crl(int peer_type, char **result);
bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
- unsigned int *certs_len);
-bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey);
+ unsigned int *pq_certs_len,
+ unsigned int *certs_len,
+ gnutls_pk_algorithm_t *pkalg);
+bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *pq_privkey,
+ gnutls_privkey_t *privkey);
/* handshake.c */
extern void tlshd_start_tls_handshake(gnutls_session_t session,
--
2.51.0

View File

@ -0,0 +1,445 @@
From 6cea3a0e8a10aa893f42576059dcf5c74f092283 Mon Sep 17 00:00:00 2001
From: Scott Mayhew <smayhew@redhat.com>
Date: Wed, 10 Sep 2025 16:17:43 -0400
Subject: [PATCH 1/4] tlshd: deduplicate client and server config functions
The client and server variants of tlshd_config_get_* are identical
except for
1) the stanza they're looking at in the config file, and
2) whether the word "client" or "server" gets written in a log message
Add new parameter 'peer_type' to each of these functions so we can use
the same function for both the client and server code.
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
src/tlshd/client.c | 9 +-
src/tlshd/config.c | 213 +++++++++------------------------------------
src/tlshd/server.c | 11 +--
src/tlshd/tlshd.h | 20 ++---
4 files changed, 62 insertions(+), 191 deletions(-)
diff --git a/src/tlshd/client.c b/src/tlshd/client.c
index 8acb0aa..c07ae29 100644
--- a/src/tlshd/client.c
+++ b/src/tlshd/client.c
@@ -48,7 +48,7 @@ static int tlshd_client_get_truststore(gnutls_certificate_credentials_t cred)
char *pathname;
int ret;
- if (tlshd_config_get_client_truststore(&pathname)) {
+ if (tlshd_config_get_truststore(PEER_TYPE_CLIENT, &pathname)) {
ret = gnutls_certificate_set_x509_trust_file(cred, pathname,
GNUTLS_X509_FMT_PEM);
free(pathname);
@@ -60,7 +60,7 @@ static int tlshd_client_get_truststore(gnutls_certificate_credentials_t cred)
}
tlshd_log_debug("System trust: Loaded %d certificate(s).", ret);
- if (tlshd_config_get_client_crl(&pathname)) {
+ if (tlshd_config_get_crl(PEER_TYPE_CLIENT, &pathname)) {
ret = gnutls_certificate_set_x509_crl_file(cred, pathname,
GNUTLS_X509_FMT_PEM);
free(pathname);
@@ -143,7 +143,8 @@ static bool tlshd_x509_client_get_certs(struct tlshd_handshake_parms *parms)
if (parms->x509_cert != TLS_NO_CERT)
return tlshd_keyring_get_certs(parms->x509_cert, tlshd_certs,
&tlshd_certs_len);
- return tlshd_config_get_client_certs(tlshd_certs, &tlshd_certs_len);
+ return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs,
+ &tlshd_certs_len);
}
static void tlshd_x509_client_put_certs(void)
@@ -159,7 +160,7 @@ static bool tlshd_x509_client_get_privkey(struct tlshd_handshake_parms *parms)
if (parms->x509_privkey != TLS_NO_PRIVKEY)
return tlshd_keyring_get_privkey(parms->x509_privkey,
&tlshd_privkey);
- return tlshd_config_get_client_privkey(&tlshd_privkey);
+ return tlshd_config_get_privkey(PEER_TYPE_CLIENT, &tlshd_privkey);
}
static void tlshd_x509_client_put_privkey(void)
diff --git a/src/tlshd/config.c b/src/tlshd/config.c
index 029dbcc..ff1f2a5 100644
--- a/src/tlshd/config.c
+++ b/src/tlshd/config.c
@@ -187,18 +187,22 @@ out:
}
/**
- * tlshd_config_get_client_truststore - Get truststore for ClientHello from .conf
+ * tlshd_config_get_truststore - Get truststore for {Client,Server}Hello from .conf
+ * @peer_type: IN: peer type
* @bundle: OUT: pathname to truststore
*
* Return values:
* %false: pathname not retrieved
* %true: pathname retrieved successfully; caller must free @bundle using free(3)
*/
-bool tlshd_config_get_client_truststore(char **bundle)
+bool tlshd_config_get_truststore(int peer_type, char **bundle)
{
gchar *pathname;
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.client",
+ pathname = g_key_file_get_string(tlshd_configuration,
+ peer_type == PEER_TYPE_CLIENT ?
+ "authenticate.client" :
+ "authenticate.server",
"x509.truststore", NULL);
if (!pathname)
return false;
@@ -213,23 +217,29 @@ bool tlshd_config_get_client_truststore(char **bundle)
if (!*bundle)
return false;
- tlshd_log_debug("Client x.509 truststore is %s", *bundle);
+ tlshd_log_debug("%s x.509 truststore is %s",
+ peer_type == PEER_TYPE_CLIENT ? "Client" : "Server",
+ *bundle);
return true;
}
/**
- * tlshd_config_get_client_crl - Get CRL for ClientHello from .conf
+ * tlshd_config_get_crl - Get CRL for {Client,Server}Hello from .conf
+ * @peer_type: IN: peer type
* @result: OUT: pathname to CRL
*
* Return values:
* %false: pathname not retrieved
* %true: pathname retrieved successfully; caller must free @result using free(3)
*/
-bool tlshd_config_get_client_crl(char **result)
+bool tlshd_config_get_crl(int peer_type, char **result)
{
gchar *pathname;
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.client",
+ pathname = g_key_file_get_string(tlshd_configuration,
+ peer_type == PEER_TYPE_CLIENT ?
+ "authenticate.client" :
+ "authenticate.server",
"x509.crl", NULL);
if (!pathname)
return false;
@@ -244,12 +254,15 @@ bool tlshd_config_get_client_crl(char **result)
if (!*result)
return false;
- tlshd_log_debug("Client x.509 crl is %s", *result);
+ tlshd_log_debug("%s x.509 crl is %s",
+ peer_type == PEER_TYPE_CLIENT ? "Client" : "Server",
+ *result);
return true;
}
/**
- * tlshd_config_get_client_certs - Get certs for ClientHello from .conf
+ * tlshd_config_get_certs - Get certs for {Client,Server} Hello from .conf
+ * @peer_type: IN: peer type
* @certs: OUT: in-memory certificates
* @certs_len: IN: maximum number of certs to get, OUT: number of certs found
*
@@ -257,15 +270,18 @@ bool tlshd_config_get_client_crl(char **result)
* %true: certificate retrieved successfully
* %false: certificate not retrieved
*/
-bool tlshd_config_get_client_certs(gnutls_pcert_st *certs,
- unsigned int *certs_len)
+bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
+ unsigned int *certs_len)
{
gnutls_datum_t data;
gchar *pathname;
int ret;
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.client",
- "x509.certificate", NULL);
+ pathname = g_key_file_get_string(tlshd_configuration,
+ peer_type == PEER_TYPE_CLIENT ?
+ "authenticate.client" :
+ "authenticate.server",
+ "x509.certificate", NULL);
if (!pathname)
return false;
@@ -285,181 +301,34 @@ bool tlshd_config_get_client_certs(gnutls_pcert_st *certs,
return false;
}
- tlshd_log_debug("Retrieved %u x.509 client certificate(s) from %s",
- *certs_len, pathname);
+ tlshd_log_debug("Retrieved %u x.509 %s certificate(s) from %s",
+ *certs_len,
+ peer_type == PEER_TYPE_CLIENT ? "client" : "server",
+ pathname);
g_free(pathname);
return true;
}
/**
- * tlshd_config_get_client_privkey - Get private key for ClientHello from .conf
+ * tlshd_config_get_privkey - Get private key for {Client,Server}Hello from .conf
+ * @peer_type: IN: peer type
* @privkey: OUT: in-memory private key
*
* Return values:
* %true: private key retrieved successfully
* %false: private key not retrieved
*/
-bool tlshd_config_get_client_privkey(gnutls_privkey_t *privkey)
+bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
{
gnutls_datum_t data;
gchar *pathname;
int ret;
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.client",
- "x509.private_key", NULL);
- if (!pathname)
- return false;
-
- if (!tlshd_config_read_datum(pathname, &data, TLSHD_OWNER,
- TLSHD_PRIVKEY_MODE)) {
- g_free(pathname);
- return false;
- }
-
- ret = gnutls_privkey_init(privkey);
- if (ret != GNUTLS_E_SUCCESS) {
- tlshd_log_gnutls_error(ret);
- free(data.data);
- g_free(pathname);
- return false;
- }
-
- /* Config file supports only PEM-encoded keys */
- ret = gnutls_privkey_import_x509_raw(*privkey, &data,
- GNUTLS_X509_FMT_PEM, NULL, 0);
- free(data.data);
- if (ret != GNUTLS_E_SUCCESS) {
- tlshd_log_gnutls_error(ret);
- g_free(pathname);
- return false;
- }
-
- tlshd_log_debug("Retrieved private key from %s", pathname);
- g_free(pathname);
- return true;
-}
-
-/**
- * tlshd_config_get_server_truststore - Get truststore for ServerHello from .conf
- * @bundle: OUT: pathname to truststore
- *
- * Return values:
- * %false: pathname not retrieved
- * %true: pathname retrieved successfully; caller must free @bundle using free(3)
- */
-bool tlshd_config_get_server_truststore(char **bundle)
-{
- gchar *pathname;
-
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.server",
- "x509.truststore", NULL);
- if (!pathname)
- return false;
- if (access(pathname, F_OK)) {
- tlshd_log_debug("tlshd cannot access \"%s\"", pathname);
- g_free(pathname);
- return false;
- }
-
- *bundle = strdup(pathname);
- g_free(pathname);
- if (!*bundle)
- return false;
-
- tlshd_log_debug("Server x.509 truststore is %s", *bundle);
- return true;
-}
-
-/**
- * tlshd_config_get_server_crl - Get CRL for ServerHello from .conf
- * @result: OUT: pathname to CRL
- *
- * Return values:
- * %false: pathname not retrieved
- * %true: pathname retrieved successfully; caller must free @result using free(3)
- */
-bool tlshd_config_get_server_crl(char **result)
-{
- gchar *pathname;
-
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.server",
- "x509.crl", NULL);
- if (!pathname)
- return false;
- if (access(pathname, F_OK)) {
- tlshd_log_debug("tlshd cannot access \"%s\"", pathname);
- g_free(pathname);
- return false;
- }
-
- *result = strdup(pathname);
- g_free(pathname);
- if (!*result)
- return false;
-
- tlshd_log_debug("Server x.509 crl is %s", *result);
- return true;
-}
-
-/**
- * tlshd_config_get_server_certs - Get certs for ServerHello from .conf
- * @certs: OUT: in-memory certificates
- * @certs_len: IN: maximum number of certs to get, OUT: number of certs found
- *
- * Return values:
- * %true: certificate retrieved successfully
- * %false: certificate not retrieved
- */
-bool tlshd_config_get_server_certs(gnutls_pcert_st *certs,
- unsigned int *certs_len)
-{
- gnutls_datum_t data;
- gchar *pathname;
- int ret;
-
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.server",
- "x509.certificate", NULL);
- if (!pathname)
- return false;
-
- if (!tlshd_config_read_datum(pathname, &data, TLSHD_OWNER,
- TLSHD_CERT_MODE)) {
- g_free(pathname);
- return false;
- }
-
- /* Config file supports only PEM-encoded certificates */
- ret = gnutls_pcert_list_import_x509_raw(certs, certs_len, &data,
- GNUTLS_X509_FMT_PEM, 0);
- free(data.data);
- if (ret != GNUTLS_E_SUCCESS) {
- tlshd_log_gnutls_error(ret);
- g_free(pathname);
- return false;
- }
-
- tlshd_log_debug("Retrieved %u x.509 server certificate(s) from %s",
- *certs_len, pathname);
- g_free(pathname);
- return true;
-}
-
-/**
- * tlshd_config_get_server_privkey - Get private key for ServerHello from .conf
- * @privkey: OUT: in-memory private key
- *
- * Return values:
- * %true: private key retrieved successfully
- * %false: private key not retrieved
- */
-bool tlshd_config_get_server_privkey(gnutls_privkey_t *privkey)
-{
- gnutls_datum_t data;
- gchar *pathname;
- int ret;
-
- pathname = g_key_file_get_string(tlshd_configuration, "authenticate.server",
- "x509.private_key", NULL);
+ pathname = g_key_file_get_string(tlshd_configuration,
+ peer_type == PEER_TYPE_CLIENT ?
+ "authenticate.client" :
+ "authenticate.server",
+ "x509.private_key", NULL);
if (!pathname)
return false;
diff --git a/src/tlshd/server.c b/src/tlshd/server.c
index 44a91c4..efea387 100644
--- a/src/tlshd/server.c
+++ b/src/tlshd/server.c
@@ -52,8 +52,8 @@ static bool tlshd_x509_server_get_certs(struct tlshd_handshake_parms *parms)
return tlshd_keyring_get_certs(parms->x509_cert,
tlshd_server_certs,
&tlshd_server_certs_len);
- return tlshd_config_get_server_certs(tlshd_server_certs,
- &tlshd_server_certs_len);
+ return tlshd_config_get_certs(PEER_TYPE_SERVER, tlshd_server_certs,
+ &tlshd_server_certs_len);
}
static void tlshd_x509_server_put_certs(void)
@@ -69,7 +69,8 @@ static bool tlshd_x509_server_get_privkey(struct tlshd_handshake_parms *parms)
if (parms->x509_privkey != TLS_NO_PRIVKEY)
return tlshd_keyring_get_privkey(parms->x509_privkey,
&tlshd_server_privkey);
- return tlshd_config_get_server_privkey(&tlshd_server_privkey);
+ return tlshd_config_get_privkey(PEER_TYPE_SERVER,
+ &tlshd_server_privkey);
}
static void tlshd_x509_server_put_privkey(void)
@@ -140,7 +141,7 @@ static int tlshd_server_get_truststore(gnutls_certificate_credentials_t cred)
char *pathname;
int ret;
- if (tlshd_config_get_server_truststore(&pathname)) {
+ if (tlshd_config_get_truststore(PEER_TYPE_SERVER, &pathname)) {
ret = gnutls_certificate_set_x509_trust_file(cred, pathname,
GNUTLS_X509_FMT_PEM);
free(pathname);
@@ -150,7 +151,7 @@ static int tlshd_server_get_truststore(gnutls_certificate_credentials_t cred)
return ret;
tlshd_log_debug("System trust: Loaded %d certificate(s).", ret);
- if (tlshd_config_get_server_crl(&pathname)) {
+ if (tlshd_config_get_crl(PEER_TYPE_SERVER, &pathname)) {
ret = gnutls_certificate_set_x509_crl_file(cred, pathname,
GNUTLS_X509_FMT_PEM);
free(pathname);
diff --git a/src/tlshd/tlshd.h b/src/tlshd/tlshd.h
index 2857804..6ba45ac 100644
--- a/src/tlshd/tlshd.h
+++ b/src/tlshd/tlshd.h
@@ -45,6 +45,11 @@ struct tlshd_handshake_parms {
unsigned int session_status;
};
+enum peer_type {
+ PEER_TYPE_CLIENT,
+ PEER_TYPE_SERVER,
+};
+
/* client.c */
extern void tlshd_tls13_clienthello_handshake(struct tlshd_handshake_parms *parms);
extern void tlshd_quic_clienthello_handshake(struct tlshd_handshake_parms *parms);
@@ -52,16 +57,11 @@ extern void tlshd_quic_clienthello_handshake(struct tlshd_handshake_parms *parms
/* config.c */
bool tlshd_config_init(const gchar *pathname);
void tlshd_config_shutdown(void);
-bool tlshd_config_get_client_truststore(char **bundle);
-bool tlshd_config_get_client_crl(char **result);
-bool tlshd_config_get_client_certs(gnutls_pcert_st *certs,
- unsigned int *certs_len);
-bool tlshd_config_get_client_privkey(gnutls_privkey_t *privkey);
-bool tlshd_config_get_server_truststore(char **bundle);
-bool tlshd_config_get_server_crl(char **result);
-bool tlshd_config_get_server_certs(gnutls_pcert_st *certs,
- unsigned int *certs_len);
-bool tlshd_config_get_server_privkey(gnutls_privkey_t *privkey);
+bool tlshd_config_get_truststore(int peer_type, char **bundle);
+bool tlshd_config_get_crl(int peer_type, char **result);
+bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
+ unsigned int *certs_len);
+bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey);
/* handshake.c */
extern void tlshd_start_tls_handshake(gnutls_session_t session,
--
2.51.0

View File

@ -16,6 +16,11 @@ URL: %{forgeurl}
# FIXME: is this a bug in the tagging scheme or forgesource macro?
Source0: %{forgeurl}/releases/download/%{name}-%{baseversion}/%{name}-%{baseversion}.tar.gz
Patch0: ktls-utils-1.2.1-tlshd-deduplicate-client-and-server-config-functions.patch
Patch1: ktls-utils-1.2.1-tlshd-Fix-priority-string-to-allow-PQC.patch
Patch2: ktls-utils-1.2.1-tlshd-Server-side-dual-certificate-support.patch
Patch3: ktls-utils-1.2.1-tlshd-Client-side-dual-certificate-support.patch
BuildRequires: bash systemd-rpm-macros
BuildRequires: gcc make coreutils
BuildRequires: pkgconfig(gnutls) >= 3.3.0