Skip to content

Commit 14f5349

Browse files
committed
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: #113 Signed-off-by: Scott Mayhew <[email protected]>
1 parent 9253f9d commit 14f5349

File tree

7 files changed

+225
-14
lines changed

7 files changed

+225
-14
lines changed

configure.ac

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ AC_CHECK_LIB([gnutls], [gnutls_get_system_config_file],
7979
AC_CHECK_LIB([gnutls], [gnutls_psk_allocate_client_credentials2],
8080
[AC_DEFINE([HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2], [1],
8181
[Define to 1 if you have the gnutls_psk_allocate_client_credentials2 function.])])
82+
83+
AC_MSG_CHECKING(for ML-DSA support in gnutls)
84+
AC_COMPILE_IFELSE(
85+
[AC_LANG_PROGRAM([[ #include <gnutls/gnutls.h> ]],
86+
[[ (void) GNUTLS_SIGN_MLDSA65; ]])],
87+
[ have_mldsa=yes ],
88+
[ have_mldsa=no ])
89+
AC_MSG_RESULT([$have_mldsa])
90+
if test "x$have_mldsa" = xyes ; then
91+
AC_DEFINE([HAVE_GNUTLS_MLDSA], [1], [Define to 1 if gnutls supports ML-DSA])
92+
fi
93+
8294
AC_SUBST([AM_CPPFLAGS])
8395

8496
AC_CONFIG_FILES([Makefile src/Makefile src/tlshd/Makefile systemd/Makefile])

src/tlshd/client.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ static bool tlshd_x509_client_get_certs(struct tlshd_handshake_parms *parms)
143143
if (parms->x509_cert != TLS_NO_CERT)
144144
return tlshd_keyring_get_certs(parms->x509_cert, tlshd_certs,
145145
&tlshd_certs_len);
146-
return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs,
147-
&tlshd_certs_len);
146+
return tlshd_config_get_certs(PEER_TYPE_CLIENT, tlshd_certs, NULL,
147+
&tlshd_certs_len, NULL);
148148
}
149149

150150
static void tlshd_x509_client_put_certs(void)
@@ -160,7 +160,7 @@ static bool tlshd_x509_client_get_privkey(struct tlshd_handshake_parms *parms)
160160
if (parms->x509_privkey != TLS_NO_PRIVKEY)
161161
return tlshd_keyring_get_privkey(parms->x509_privkey,
162162
&tlshd_privkey);
163-
return tlshd_config_get_privkey(PEER_TYPE_CLIENT, &tlshd_privkey);
163+
return tlshd_config_get_privkey(PEER_TYPE_CLIENT, NULL, &tlshd_privkey);
164164
}
165165

166166
static void tlshd_x509_client_put_privkey(void)

src/tlshd/config.c

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,18 +260,66 @@ bool tlshd_config_get_crl(int peer_type, char **result)
260260
return true;
261261
}
262262

263+
#ifdef HAVE_GNUTLS_MLDSA
264+
static bool tlshd_cert_check_pk_alg(gnutls_datum_t *data,
265+
gnutls_pk_algorithm_t *pkalg)
266+
{
267+
gnutls_x509_crt_t cert;
268+
gnutls_pk_algorithm_t pk_alg;
269+
int ret;
270+
271+
ret = gnutls_x509_crt_init(&cert);
272+
if (ret < 0)
273+
return false;
274+
275+
ret = gnutls_x509_crt_import(cert, data, GNUTLS_X509_FMT_PEM);
276+
if (ret < 0) {
277+
gnutls_x509_crt_deinit(cert);
278+
return false;
279+
}
280+
281+
pk_alg = gnutls_x509_crt_get_pk_algorithm(cert, NULL);
282+
tlshd_log_debug("%s: certificate pk algorithm %s", __func__,
283+
gnutls_pk_algorithm_get_name(pk_alg));
284+
switch (pk_alg) {
285+
case GNUTLS_PK_MLDSA44:
286+
case GNUTLS_PK_MLDSA65:
287+
case GNUTLS_PK_MLDSA87:
288+
*pkalg = pk_alg;
289+
break;
290+
default:
291+
gnutls_x509_crt_deinit(cert);
292+
return false;
293+
}
294+
295+
gnutls_x509_crt_deinit(cert);
296+
return true;
297+
}
298+
#else
299+
static bool tlshd_cert_check_pk_alg(__attribute__ ((unused)) gnutls_datum_t *data,
300+
__attribute__ ((unused)) gnutls_pk_algorithm_t *pkalg)
301+
{
302+
tlshd_log_debug("%s: gnutls version does not have ML-DSA support",
303+
__func__);
304+
return false;
305+
}
306+
#endif /* HAVE_GNUTLS_MLDSA */
307+
263308
/**
264-
* tlshd_config_get_certs - Get certs for {Client,Server} Hello from .conf
309+
* __tlshd_config_get_certs - Helper for tlshd_config_get_certs()
265310
* @peer_type: IN: peer type
266311
* @certs: OUT: in-memory certificates
267312
* @certs_len: IN: maximum number of certs to get, OUT: number of certs found
313+
* @pkgalg: IN: if non-NULL, indicates we want to retrieve the PQ cert,
314+
* OUT: if non-NULL, store the PQ public-key alg that was used in the PQ cert
268315
*
269316
* Return values:
270317
* %true: certificate retrieved successfully
271318
* %false: certificate not retrieved
272319
*/
273-
bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
274-
unsigned int *certs_len)
320+
static bool __tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
321+
unsigned int *certs_len,
322+
gnutls_pk_algorithm_t *pkalg)
275323
{
276324
gnutls_datum_t data;
277325
gchar *pathname;
@@ -281,7 +329,10 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
281329
peer_type == PEER_TYPE_CLIENT ?
282330
"authenticate.client" :
283331
"authenticate.server",
284-
"x509.certificate", NULL);
332+
pkalg != NULL ?
333+
"x509.pq.certificate" :
334+
"x509.certificate",
335+
NULL);
285336
if (!pathname)
286337
return false;
287338

@@ -291,6 +342,15 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
291342
return false;
292343
}
293344

345+
if (pkalg && !tlshd_cert_check_pk_alg(&data, pkalg)) {
346+
tlshd_log_debug("%s: %s not using a PQ public-key algorithm",
347+
__func__, pathname);
348+
free(data.data);
349+
g_free(pathname);
350+
*certs_len = 0;
351+
return false;
352+
}
353+
294354
/* Config file supports only PEM-encoded certificates */
295355
ret = gnutls_pcert_list_import_x509_raw(certs, certs_len, &data,
296356
GNUTLS_X509_FMT_PEM, 0);
@@ -310,15 +370,51 @@ bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
310370
}
311371

312372
/**
313-
* tlshd_config_get_privkey - Get private key for {Client,Server}Hello from .conf
373+
* tlshd_config_get_certs - Get certs for {Client,Server} Hello from .conf
374+
* @peer_type: IN: peer type
375+
* @certs: OUT: in-memory certificates
376+
* @pq_certs_len: IN: maximum number of PQ certs to get, OUT: number of PQ certs found
377+
* @certs_len: IN: maximum number of certs to get, OUT: number of certs found
378+
* @pkgalg: OUT: the PQ public-key alg that was used in the PQ cert
379+
*
380+
* Retrieve the PQ cert(s) first, then the RSA cert(s). Both are stored in the
381+
* same list. Note that @pq_certs_len is deducted from the available @certs_len
382+
* and is also used to determine the offset to store the RSA cert(s) in the
383+
* @certs array.
384+
*
385+
* Return values:
386+
* %true: certificate retrieved successfully
387+
* %false: certificate not retrieved
388+
*/
389+
bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
390+
unsigned int *pq_certs_len,
391+
unsigned int *certs_len,
392+
gnutls_pk_algorithm_t *pkalg)
393+
{
394+
bool ret;
395+
396+
ret = __tlshd_config_get_certs(peer_type, certs, pq_certs_len, pkalg);
397+
398+
if (ret == true)
399+
*certs_len -= *pq_certs_len;
400+
else
401+
*pq_certs_len = 0;
402+
403+
return __tlshd_config_get_certs(peer_type, certs + *pq_certs_len,
404+
certs_len, NULL);
405+
}
406+
407+
/**
408+
* __tlshd_config_get_privkey - Helper for tlshd_config_get_privkey()
314409
* @peer_type: IN: peer type
315410
* @privkey: OUT: in-memory private key
411+
* @pq: IN: if true, retrieve the PQ private key
316412
*
317413
* Return values:
318414
* %true: private key retrieved successfully
319415
* %false: private key not retrieved
320416
*/
321-
bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
417+
static bool __tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey, bool pq)
322418
{
323419
gnutls_datum_t data;
324420
gchar *pathname;
@@ -328,7 +424,10 @@ bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
328424
peer_type == PEER_TYPE_CLIENT ?
329425
"authenticate.client" :
330426
"authenticate.server",
331-
"x509.private_key", NULL);
427+
pq == true ?
428+
"x509.pq.private_key" :
429+
"x509.private_key",
430+
NULL);
332431
if (!pathname)
333432
return false;
334433

@@ -360,3 +459,22 @@ bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey)
360459
g_free(pathname);
361460
return true;
362461
}
462+
463+
/**
464+
* tlshd_config_get_privkey - Get private key for {Client,Server}Hello from .conf
465+
* @peer_type: IN: peer type
466+
* @pq_privkey: OUT: in-memory PQ private key
467+
* @privkey: OUT: in-memory private key
468+
*
469+
* Retrieve the PQ private key first, then the RSA private key.
470+
*
471+
* Return values:
472+
* %true: private key retrieved successfully
473+
* %false: private key not retrieved
474+
*/
475+
bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *pq_privkey,
476+
gnutls_privkey_t *privkey)
477+
{
478+
__tlshd_config_get_privkey(peer_type, pq_privkey, true);
479+
return __tlshd_config_get_privkey(peer_type, privkey, false);
480+
}

src/tlshd/server.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@
4242
#include "tlshd.h"
4343
#include "netlink.h"
4444

45+
static gnutls_privkey_t tlshd_server_pq_privkey;
4546
static gnutls_privkey_t tlshd_server_privkey;
47+
static unsigned int tlshd_server_pq_certs_len = TLSHD_MAX_CERTS;
4648
static unsigned int tlshd_server_certs_len = TLSHD_MAX_CERTS;
4749
static gnutls_pcert_st tlshd_server_certs[TLSHD_MAX_CERTS];
50+
static gnutls_pk_algorithm_t tlshd_server_pq_pkalg = GNUTLS_PK_UNKNOWN;
4851

4952
static bool tlshd_x509_server_get_certs(struct tlshd_handshake_parms *parms)
5053
{
@@ -53,14 +56,16 @@ static bool tlshd_x509_server_get_certs(struct tlshd_handshake_parms *parms)
5356
tlshd_server_certs,
5457
&tlshd_server_certs_len);
5558
return tlshd_config_get_certs(PEER_TYPE_SERVER, tlshd_server_certs,
56-
&tlshd_server_certs_len);
59+
&tlshd_server_pq_certs_len,
60+
&tlshd_server_certs_len,
61+
&tlshd_server_pq_pkalg);
5762
}
5863

5964
static void tlshd_x509_server_put_certs(void)
6065
{
6166
unsigned int i;
6267

63-
for (i = 0; i < tlshd_server_certs_len; i++)
68+
for (i = 0; i < TLSHD_MAX_CERTS; i++)
6469
gnutls_pcert_deinit(&tlshd_server_certs[i]);
6570
}
6671

@@ -70,11 +75,13 @@ static bool tlshd_x509_server_get_privkey(struct tlshd_handshake_parms *parms)
7075
return tlshd_keyring_get_privkey(parms->x509_privkey,
7176
&tlshd_server_privkey);
7277
return tlshd_config_get_privkey(PEER_TYPE_SERVER,
78+
&tlshd_server_pq_privkey,
7379
&tlshd_server_privkey);
7480
}
7581

7682
static void tlshd_x509_server_put_privkey(void)
7783
{
84+
gnutls_privkey_deinit(tlshd_server_pq_privkey);
7885
gnutls_privkey_deinit(tlshd_server_privkey);
7986
}
8087

@@ -123,16 +130,70 @@ tlshd_x509_retrieve_key_cb(gnutls_session_t session,
123130
gnutls_privkey_t *privkey)
124131
{
125132
gnutls_certificate_type_t type;
133+
#ifdef HAVE_GNUTLS_MLDSA
134+
gnutls_sign_algorithm_t client_alg;
135+
bool use_pq_cert = false;
136+
int i, ret;
137+
#endif /* HAVE_GNUTLS_MLDSA */
126138

127139
tlshd_x509_log_issuers(req_ca_rdn, nreqs);
128140

129141
type = gnutls_certificate_type_get(session);
130142
if (type != GNUTLS_CRT_X509)
131143
return -1;
132144

145+
#ifdef HAVE_GNUTLS_MLDSA
146+
/*
147+
* NB: Unfortunately when the callback function is invoked server-side,
148+
* pk_algos is NULL and pk_algos_length is 0. So we check the signature
149+
* algorithms the client supports and try to match one of them to the
150+
* public-key algorithm used by the server cert.
151+
*/
152+
if (tlshd_server_pq_pkalg != GNUTLS_PK_UNKNOWN) {
153+
for (i = 0; ; i++) {
154+
ret = gnutls_sign_algorithm_get_requested(session, i, &client_alg);
155+
if (ret != GNUTLS_E_SUCCESS)
156+
break;
157+
switch (client_alg) {
158+
case GNUTLS_SIGN_MLDSA44:
159+
if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA44)
160+
use_pq_cert = true;
161+
break;
162+
case GNUTLS_SIGN_MLDSA65:
163+
if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA65)
164+
use_pq_cert = true;
165+
break;
166+
case GNUTLS_SIGN_MLDSA87:
167+
if (tlshd_server_pq_pkalg == GNUTLS_PK_MLDSA87)
168+
use_pq_cert = true;
169+
break;
170+
default:
171+
break;
172+
}
173+
if (use_pq_cert == true) {
174+
tlshd_log_debug("%s: Client supports %s", __func__,
175+
gnutls_sign_get_name(client_alg));
176+
break;
177+
}
178+
}
179+
}
180+
181+
if (use_pq_cert == true) {
182+
tlshd_log_debug("%s: Selecting x509.pq.certificate from conf file", __func__);
183+
*pcert_length = tlshd_server_pq_certs_len;
184+
*pcert = tlshd_server_certs;
185+
*privkey = tlshd_server_pq_privkey;
186+
} else {
187+
tlshd_log_debug("%s: Selecting x509.certificate from conf file", __func__);
188+
*pcert_length = tlshd_server_certs_len;
189+
*pcert = tlshd_server_certs + tlshd_server_pq_certs_len;
190+
*privkey = tlshd_server_privkey;
191+
}
192+
#else
133193
*pcert_length = tlshd_server_certs_len;
134194
*pcert = tlshd_server_certs;
135195
*privkey = tlshd_server_privkey;
196+
#endif /* HAVE_GNUTLS_MLDSA */
136197
return 0;
137198
}
138199

src/tlshd/tlshd.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ nl=0
3939
#x509.crl= <pathname>
4040
#x509.certificate= <pathname>
4141
#x509.private_key= <pathname>
42+
#x509.pq.certificate= <pathname>
43+
#x509.pq.private_key= <pathname>

src/tlshd/tlshd.conf.man

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@ a handshake request when no other certificate is available.
125125
.B x509.private_key
126126
This option specifies the pathname of a file containing
127127
a PEM-encoded private key associated with the above certificate.
128+
.TP
129+
.B x509.pq.certificate
130+
This option specifies the pathname of a file containing
131+
a PEM-encoded x.509 certificate that is to be presented during
132+
a handshake request if the peer supports post-quantum cryptography.
133+
This certificate must be using a post-quantum public-key algorithm
134+
(ML-DSA-44, ML-DSA-65, or ML-DSA-87).
135+
If the peer does not support post-quantum cryptography, the
136+
certificate configured in the
137+
.I x509.certificate
138+
option will be presented instead.
139+
.TP
140+
.B x509.pq.private_key
141+
This option specifies the pathname of a file containing
142+
a PEM-encoded private key associated with the above certificate.
128143
.SH SEE ALSO
129144
.BR tlshd (8)
130145
.SH AUTHOR

src/tlshd/tlshd.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ void tlshd_config_shutdown(void);
6060
bool tlshd_config_get_truststore(int peer_type, char **bundle);
6161
bool tlshd_config_get_crl(int peer_type, char **result);
6262
bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
63-
unsigned int *certs_len);
64-
bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *privkey);
63+
unsigned int *pq_certs_len,
64+
unsigned int *certs_len,
65+
gnutls_pk_algorithm_t *pkalg);
66+
bool tlshd_config_get_privkey(int peer_type, gnutls_privkey_t *pq_privkey,
67+
gnutls_privkey_t *privkey);
6568

6669
/* handshake.c */
6770
extern void tlshd_start_tls_handshake(gnutls_session_t session,

0 commit comments

Comments
 (0)