diff --git a/ktls-utils-1.2.1-tlshd-Client-side-dual-certificate-support.patch b/ktls-utils-1.2.1-tlshd-Client-side-dual-certificate-support.patch new file mode 100644 index 0000000..8d01b27 --- /dev/null +++ b/ktls-utils-1.2.1-tlshd-Client-side-dual-certificate-support.patch @@ -0,0 +1,135 @@ +From facd084e43fc493ae8fbaca00cb65925ae30d0e9 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +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 +--- + 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= + #x509.certificate= + #x509.private_key= ++#x509.pq.certificate= ++#x509.pq.private_key= + + [authenticate.server] + #x509.truststore= +-- +2.51.0 + diff --git a/ktls-utils-1.2.1-tlshd-Fix-priority-string-to-allow-PQC.patch b/ktls-utils-1.2.1-tlshd-Fix-priority-string-to-allow-PQC.patch new file mode 100644 index 0000000..bafa8b5 --- /dev/null +++ b/ktls-utils-1.2.1-tlshd-Fix-priority-string-to-allow-PQC.patch @@ -0,0 +1,39 @@ +From 9253f9df5ff986bddba38b8146ba6e650e7ebdd9 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +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 +--- + 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 + diff --git a/ktls-utils-1.2.1-tlshd-Server-side-dual-certificate-support.patch b/ktls-utils-1.2.1-tlshd-Server-side-dual-certificate-support.patch new file mode 100644 index 0000000..cd61d87 --- /dev/null +++ b/ktls-utils-1.2.1-tlshd-Server-side-dual-certificate-support.patch @@ -0,0 +1,445 @@ +From 14f53496509eea9cbe14eb244161b0e26699d165 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +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 +--- + 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 ]], ++ [[ (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= + #x509.certificate= + #x509.private_key= ++#x509.pq.certificate= ++#x509.pq.private_key= +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 + diff --git a/ktls-utils-1.2.1-tlshd-deduplicate-client-and-server-config-functions.patch b/ktls-utils-1.2.1-tlshd-deduplicate-client-and-server-config-functions.patch new file mode 100644 index 0000000..4d55fda --- /dev/null +++ b/ktls-utils-1.2.1-tlshd-deduplicate-client-and-server-config-functions.patch @@ -0,0 +1,445 @@ +From 6cea3a0e8a10aa893f42576059dcf5c74f092283 Mon Sep 17 00:00:00 2001 +From: Scott Mayhew +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 +--- + 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 + diff --git a/ktls-utils.spec b/ktls-utils.spec index 4cdd9c1..7ebbc83 100644 --- a/ktls-utils.spec +++ b/ktls-utils.spec @@ -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