Package: release.debian.org
Severity: normal
X-Debbugs-Cc: ktls-ut...@packages.debian.org, debian-ker...@lists.debian.org
Control: affects -1 + src:ktls-utils
User: release.debian....@packages.debian.org
Usertags: unblock

Please unblock package ktls-utils

[ Reason ]
Update to the first stable upstream version.

[ Impact ]
Several bugs were fixed in this upstream version:
- Memory leak during every handshake
- Timeout errors were reported to the kernel with the wrong error
  code, which looks like it could lead to data loss
- Possible buffer overruns (but it's not clear that they are
  exploitable)
- A kernel request to handshake a QUIC connection would wrongly be
  handled as regular TLS over TCP, and is now treated as an error
  (since QUIC support is not enabled)

This is also supposed to make NVMe over TLS work without extra
configuration, but I have not yet tested NVMe support at all.

[ Tests ]
I manually tested use of tlshd for NFS with mutual authentication
(mTLS), following the instructions in README.Debian.

[ Risks ]
This is a new upstream version with a comparatively large delta.
However, most of that is for adding QUIC support, and that is
currently disabled at build time.  I have reviewed all the remaining
changes.

This package is new for trixie, so there is no risk of a regression
from bookworm.

[ Checklist ]
  [X] all changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in testing

[ Other info ]

unblock ktls-utils/1.0.0-1
diff -Nru ktls-utils-0.11/.github/workflows/makefile.yml 
ktls-utils-1.0.0/.github/workflows/makefile.yml
--- ktls-utils-0.11/.github/workflows/makefile.yml      2024-06-14 
16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/.github/workflows/makefile.yml     2025-05-05 
19:58:55.000000000 +0200
@@ -16,12 +16,12 @@
           - linux/arm64
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
 
     - name: Install dependencies
       run: |
         sudo apt-get update
-        sudo apt-get -y install gnutls-dev libkeyutils-dev libnl-3-dev 
libnl-genl-3-dev
+        sudo apt-get -y install gnutls-dev libkeyutils-dev libnl-3-dev 
libnl-genl-3-dev libglib2.0-dev
 
     - name: Configure
       run: |
diff -Nru ktls-utils-0.11/ChangeLog ktls-utils-1.0.0/ChangeLog
--- ktls-utils-0.11/ChangeLog   2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/ChangeLog  2025-05-05 19:58:55.000000000 +0200
@@ -1,5 +1,10 @@
 Change Log - In newest-release-first order
 
+ktls-utils 1.0 - 2025-05-05
+ * Handshake support for in-kernel QUIC
+ * Package is no longer "experimental"
+ * Fix minor bugs
+
 ktls-utils 0.11 - 2024-06-05
  * Add support for chained certs
  * Move to-do items to the GitHub issue tracker
diff -Nru ktls-utils-0.11/NEWS ktls-utils-1.0.0/NEWS
--- ktls-utils-0.11/NEWS        2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/NEWS       2025-05-05 19:58:55.000000000 +0200
@@ -1,4 +1,4 @@
-ktls-utils 0.11 - 2024-06-05
- * Add support for chained certs
- * Move to-do items to the GitHub issue tracker
+ktls-utils 1.0 - 2025-05-05
+ * Handshake support for in-kernel QUIC
+ * Package is no longer "experimental"
  * Fix minor bugs
diff -Nru ktls-utils-0.11/README ktls-utils-1.0.0/README
--- ktls-utils-0.11/README      2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/README     2025-05-05 19:58:55.000000000 +0200
@@ -1,7 +1,4 @@
-# Release Notes for ktls-utils 0.11
-
-Note well: This is experimental prototype software. It's purpose is
-purely as a demonstration and proof-of-concept. USE AT YOUR OWN RISK.
+# Release Notes for ktls-utils 1.0.0
 
 In-kernel TLS consumers need a mechanism to perform TLS handshakes
 on a connected socket to negotiate TLS session parameters that can
diff -Nru ktls-utils-0.11/README.md ktls-utils-1.0.0/README.md
--- ktls-utils-0.11/README.md   2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/README.md  2025-05-05 19:58:55.000000000 +0200
@@ -1,7 +1,4 @@
-# Release Notes for ktls-utils 0.11
-
-Note well: This is experimental prototype software. It's purpose is
-purely as a demonstration and proof-of-concept. USE AT YOUR OWN RISK.
+# Release Notes for ktls-utils 1.0.0
 
 In-kernel TLS consumers need a mechanism to perform TLS handshakes
 on a connected socket to negotiate TLS session parameters that can
diff -Nru ktls-utils-0.11/configure.ac ktls-utils-1.0.0/configure.ac
--- ktls-utils-0.11/configure.ac        2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/configure.ac       2025-05-05 19:58:55.000000000 +0200
@@ -18,7 +18,7 @@
 dnl
 
 AC_PREREQ([2.69])
-AC_INIT([ktls-utils],[0.11],[kernel-tls-handsh...@lists.linux.dev])
+AC_INIT([ktls-utils],[1.0.0],[kernel-tls-handsh...@lists.linux.dev])
 AM_INIT_AUTOMAKE
 AM_SILENT_RULES([yes])
 AC_CONFIG_SRCDIR([config.h.in])
@@ -59,6 +59,13 @@
 PKG_CHECK_MODULES([LIBNL3], libnl-3.0 >= 3.1)
 AC_SUBST([LIBNL3_CFLAGS])
 AC_SUBST([LIBNL3_LIBS])
+PKG_CHECK_MODULES([LIBNL_GENL3], libnl-genl-3.0 >= 3.1)
+AC_SUBST([LIBNL_GENL3_CFLAGS])
+AC_SUBST([LIBNL_GENL3_LIBS])
+
+AC_CHECK_FILE([/usr/include/linux/quic.h],
+              [AC_CHECK_LIB([gnutls], [gnutls_handshake_set_secret_function],
+                            [AC_DEFINE([HAVE_GNUTLS_QUIC], [1], [Define to 1 
if QUIC is found.])])])
 
 AC_CHECK_LIB([gnutls], [gnutls_transport_is_ktls_enabled],
              [AC_DEFINE([HAVE_GNUTLS_TRANSPORT_IS_KTLS_ENABLED], [1],
@@ -69,6 +76,9 @@
 AC_CHECK_LIB([gnutls], [gnutls_get_system_config_file],
              [AC_DEFINE([HAVE_GNUTLS_GET_SYSTEM_CONFIG_FILE], [1],
                         [Define to 1 if you have the 
gnutls_get_system_config_file function.])])
+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_SUBST([AM_CPPFLAGS])
 
 AC_CONFIG_FILES([Makefile src/Makefile src/tlshd/Makefile systemd/Makefile])
diff -Nru ktls-utils-0.11/debian/README.debian 
ktls-utils-1.0.0/debian/README.debian
--- ktls-utils-0.11/debian/README.debian        2023-07-24 01:53:26.000000000 
+0200
+++ ktls-utils-1.0.0/debian/README.debian       2025-05-25 17:33:19.000000000 
+0200
@@ -3,11 +3,14 @@
 
 ktls-utils currently includes the tlshd daemon, which is used by
 "in-kernel TLS consumers" to establish TLS connections including
-certificate validation.  The current in-kernel users of TLS are the
-NFS server (from Linux 6.4) and the NFS client (from Linux 6.5).
+certificate validation.  The current in-kernel users of TLS are:
 
-WARNING: tlshd is currently experimental and probably does not
-implement all of the certificate validation that you want.
+* NFS server (from Linux 6.4)
+* NFS client (from Linux 6.5)
+* NVMe host and target (from Linux 6.7)
+
+WARNING: tlshd may not implement all of the certificate validation
+that you want; see <https://github.com/oracle/ktls-utils/issues/21>.
 
 To enable TLS on an NFS server:
 
@@ -40,4 +43,4 @@
 3. If the client should verify the server's certificate, use a domain
    name for the server in /etc/fstab, not an IP address.
 
- -- Ben Hutchings <b...@debian.org>, Mon, 24 Jul 2023 01:53:26 +0200
+ -- Ben Hutchings <b...@debian.org>, Sun, 25 May 2025 17:33:19 +0200
diff -Nru ktls-utils-0.11/debian/changelog ktls-utils-1.0.0/debian/changelog
--- ktls-utils-0.11/debian/changelog    2024-07-10 23:31:48.000000000 +0200
+++ ktls-utils-1.0.0/debian/changelog   2025-05-25 22:55:34.000000000 +0200
@@ -1,3 +1,14 @@
+ktls-utils (1.0.0-1) unstable; urgency=medium
+
+  * New upstream version
+  * d/watch: Disable tag signature check as recent tags were not signed
+  * tlshd: fix a regression for certificate verification
+  * configure: Disable currently broken QUIC implementation
+  * configure: Disable use of GnuTLS API not yet accepted upstream
+  * README.Debian: Update for changes to kernel and ktls-utils
+
+ -- Ben Hutchings <b...@debian.org>  Sun, 25 May 2025 22:55:34 +0200
+
 ktls-utils (0.11-1) unstable; urgency=medium
 
   * New upstream version
diff -Nru 
ktls-utils-0.11/debian/patches/configure-disable-currently-broken-quic-implementation.patch
 
ktls-utils-1.0.0/debian/patches/configure-disable-currently-broken-quic-implementation.patch
--- 
ktls-utils-0.11/debian/patches/configure-disable-currently-broken-quic-implementation.patch
 1970-01-01 01:00:00.000000000 +0100
+++ 
ktls-utils-1.0.0/debian/patches/configure-disable-currently-broken-quic-implementation.patch
        2025-05-19 22:47:32.000000000 +0200
@@ -0,0 +1,29 @@
+From: Ben Hutchings <b...@debian.org>
+Date: Mon, 19 May 2025 22:35:22 +0200
+Subject: configure: Disable currently broken QUIC implementation
+Bug: https://github.com/oracle/ktls-utils/issues/97
+Bug: https://github.com/oracle/ktls-utils/issues/99
+Bug: https://github.com/oracle/ktls-utils/issues/100
+
+The QUIC implementation currently has several obvious bugs.  As our
+kernel UAPI headers don't yet include /usr/include/linux/quic.h, it is
+automatically disabled by the configure script, but I want to ensure
+that it stays disabled until those issues are resolved.
+
+---
+--- a/configure.ac
++++ b/configure.ac
+@@ -63,9 +63,10 @@ PKG_CHECK_MODULES([LIBNL_GENL3], libnl-g
+ AC_SUBST([LIBNL_GENL3_CFLAGS])
+ AC_SUBST([LIBNL_GENL3_LIBS])
+ 
+-AC_CHECK_FILE([/usr/include/linux/quic.h],
+-              [AC_CHECK_LIB([gnutls], [gnutls_handshake_set_secret_function],
+-                            [AC_DEFINE([HAVE_GNUTLS_QUIC], [1], [Define to 1 
if QUIC is found.])])])
++dnl QUIC is broken (upstream bugs #97, #99, #100)
++dnl AC_CHECK_FILE([/usr/include/linux/quic.h],
++dnl               [AC_CHECK_LIB([gnutls], 
[gnutls_handshake_set_secret_function],
++dnl                             [AC_DEFINE([HAVE_GNUTLS_QUIC], [1], [Define 
to 1 if QUIC is found.])])])
+ 
+ AC_CHECK_LIB([gnutls], [gnutls_transport_is_ktls_enabled],
+              [AC_DEFINE([HAVE_GNUTLS_TRANSPORT_IS_KTLS_ENABLED], [1],
diff -Nru 
ktls-utils-0.11/debian/patches/configure-disable-use-of-gnutls-api-not-yet-accepted-upstream.patch
 
ktls-utils-1.0.0/debian/patches/configure-disable-use-of-gnutls-api-not-yet-accepted-upstream.patch
--- 
ktls-utils-0.11/debian/patches/configure-disable-use-of-gnutls-api-not-yet-accepted-upstream.patch
  1970-01-01 01:00:00.000000000 +0100
+++ 
ktls-utils-1.0.0/debian/patches/configure-disable-use-of-gnutls-api-not-yet-accepted-upstream.patch
 2025-05-19 22:58:04.000000000 +0200
@@ -0,0 +1,27 @@
+From: Ben Hutchings <b...@debian.org>
+Date: Mon, 19 May 2025 22:52:39 +0200
+Subject: configure: Disable use of GnuTLS API not yet accepted upstream
+
+The gnutls_psk_allocate_{client,server}_credentials2() functions were
+proposed in <https://gitlab.com/gnutls/gnutls/-/merge_requests/1939>
+but have not yet been accepted.  Disable use of these functions
+unconditionally, since they could be added upstream with different
+semantics.
+
+---
+--- a/configure.ac
++++ b/configure.ac
+@@ -77,9 +77,10 @@ AC_CHECK_LIB([gnutls], [gnutls_protocol_
+ AC_CHECK_LIB([gnutls], [gnutls_get_system_config_file],
+              [AC_DEFINE([HAVE_GNUTLS_GET_SYSTEM_CONFIG_FILE], [1],
+                         [Define to 1 if you have the 
gnutls_get_system_config_file function.])])
+-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.])])
++dnl This has not yet been accepted into upstream GnuTLS and might change
++dnl AC_CHECK_LIB([gnutls], [gnutls_psk_allocate_client_credentials2],
++dnl              [AC_DEFINE([HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2], [1],
++dnl                         [Define to 1 if you have the 
gnutls_psk_allocate_client_credentials2 function.])])
+ AC_SUBST([AM_CPPFLAGS])
+ 
+ AC_CONFIG_FILES([Makefile src/Makefile src/tlshd/Makefile systemd/Makefile])
diff -Nru ktls-utils-0.11/debian/patches/series 
ktls-utils-1.0.0/debian/patches/series
--- ktls-utils-0.11/debian/patches/series       2024-05-15 13:11:15.000000000 
+0200
+++ ktls-utils-1.0.0/debian/patches/series      2025-05-25 17:22:03.000000000 
+0200
@@ -0,0 +1,3 @@
+tlshd-fix-a-regression-for-certificate-verification.patc
+configure-disable-currently-broken-quic-implementation.patch
+configure-disable-use-of-gnutls-api-not-yet-accepted-upstream.patch
diff -Nru 
ktls-utils-0.11/debian/patches/tlshd-fix-a-regression-for-certificate-verification.patc
 
ktls-utils-1.0.0/debian/patches/tlshd-fix-a-regression-for-certificate-verification.patc
--- 
ktls-utils-0.11/debian/patches/tlshd-fix-a-regression-for-certificate-verification.patc
     1970-01-01 01:00:00.000000000 +0100
+++ 
ktls-utils-1.0.0/debian/patches/tlshd-fix-a-regression-for-certificate-verification.patc
    2025-05-25 17:26:33.000000000 +0200
@@ -0,0 +1,38 @@
+From: Benjamin Coddington <bcodd...@redhat.com>
+Date: Tue, 20 May 2025 09:06:19 -0400
+Subject: tlshd: fix a regression for certificate verification
+Origin: 
https://github.com/oracle/ktls-utils/commit/2f609c509e8c9087c584be96fe07a53e929a0746
+Bug: https://github.com/oracle/ktls-utils/issues/98
+
+Commit b010190cfed2 left session_status unset for
+GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR.  Fix this by always setting
+EACCESS in the error handling switch statement.
+
+Fixes: b010190cfed2 ("tlshd: Pass ETIMEDOUT from gnutls to kernel")
+Closes: #98
+Signed-off-by: Benjamin Coddington <bcodd...@redhat.com>
+---
+ src/tlshd/handshake.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/tlshd/handshake.c b/src/tlshd/handshake.c
+index 53c91e2..b9de6b3 100644
+--- a/src/tlshd/handshake.c
++++ b/src/tlshd/handshake.c
+@@ -90,6 +90,8 @@ void tlshd_start_tls_handshake(gnutls_session_t session,
+       } while (ret < 0 && !gnutls_error_is_fatal(ret));
+       tlshd_set_nagle(session, saved);
+       if (ret < 0) {
++              /* Any errors here should default to blocking access: */
++              parms->session_status = EACCES;
+               switch (ret) {
+               case GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR:
+                       tlshd_log_cert_verification_error(session);
+@@ -100,7 +102,6 @@ void tlshd_start_tls_handshake(gnutls_session_t session,
+                       break;
+               default:
+                       tlshd_log_notice("tlshd_start_tls_handshake unhandled 
error %d, returning EACCES\n", ret);
+-                      parms->session_status = EACCES;
+               }
+               return;
+       }
diff -Nru ktls-utils-0.11/debian/watch ktls-utils-1.0.0/debian/watch
--- ktls-utils-0.11/debian/watch        2023-07-21 18:26:05.000000000 +0200
+++ ktls-utils-1.0.0/debian/watch       2025-05-19 20:15:29.000000000 +0200
@@ -1,4 +1,4 @@
 version=3
-opts="mode=git, gitmode=shallow, pgpmode=gittag" \
+opts="mode=git, gitmode=shallow" \
 https://github.com/oracle/ktls-utils.git \
 refs/tags/ktls-utils-(.*) debian
diff -Nru ktls-utils-0.11/src/tlshd/Makefile.am 
ktls-utils-1.0.0/src/tlshd/Makefile.am
--- ktls-utils-0.11/src/tlshd/Makefile.am       2024-06-14 16:54:21.000000000 
+0200
+++ ktls-utils-1.0.0/src/tlshd/Makefile.am      2025-05-05 19:58:55.000000000 
+0200
@@ -24,10 +24,11 @@
 
 sbin_PROGRAMS          = tlshd
 tlshd_CFLAGS           = -Werror -Wall -Wextra $(LIBGNUTLS_CFLAGS) \
-                         $(LIBKEYUTILS_CFLAGS) $(GLIB_CFLAGS) $(LIBNL3_CFLAGS)
+                         $(LIBKEYUTILS_CFLAGS) $(GLIB_CFLAGS) $(LIBNL3_CFLAGS) 
\
+                         $(LIBNL_GENL3_CFLAGS)
 tlshd_SOURCES          = client.c config.c handshake.c keyring.c ktls.c log.c \
-                         main.c netlink.c netlink.h server.c tlshd.h
+                         main.c netlink.c netlink.h server.c tlshd.h quic.c
 tlshd_LDADD            = $(LIBGNUTLS_LIBS) $(LIBKEYUTILS_LIBS) $(GLIB_LIBS) \
-                         $(LIBNL3_LIBS) -lnl-genl-3
+                         $(LIBNL3_LIBS) $(LIBNL_GENL3_LIBS)
 
 MAINTAINERCLEANFILES   = Makefile.in cscope.out
diff -Nru ktls-utils-0.11/src/tlshd/client.c ktls-utils-1.0.0/src/tlshd/client.c
--- ktls-utils-0.11/src/tlshd/client.c  2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/client.c 2025-05-05 19:58:55.000000000 +0200
@@ -3,6 +3,7 @@
  *
  * Copyright (c) 2022 Oracle and/or its affiliates.
  * Copyright (c) 2022 SUSE LLC.
+ * Copyright (c) 2024 Red Hat, Inc.
  *
  * ktls-utils is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -42,7 +43,7 @@
 #include "tlshd.h"
 #include "netlink.h"
 
-static void tlshd_client_anon_handshake(struct tlshd_handshake_parms *parms)
+static void tlshd_tls13_client_anon_handshake(struct tlshd_handshake_parms 
*parms)
 {
        gnutls_certificate_credentials_t xcred;
        gnutls_session_t session;
@@ -196,22 +197,21 @@
 /**
  * tlshd_client_x509_verify_function - Verify remote's x.509 certificate
  * @session: session in the midst of a handshake
+ * @parms: handshake parameters
  *
  * Return values:
  *   %GNUTLS_E_SUCCESS: Incoming certificate has been successfully verified
  *   %GNUTLS_E_CERTIFICATE_ERROR: certificate verification failed
  */
-static int tlshd_client_x509_verify_function(gnutls_session_t session)
+static int tlshd_client_x509_verify_function(gnutls_session_t session,
+                                            struct tlshd_handshake_parms 
*parms)
 {
-       struct tlshd_handshake_parms *parms;
        const gnutls_datum_t *peercerts;
        gnutls_certificate_type_t type;
        unsigned int i, status;
        gnutls_datum_t out;
        int ret;
 
-       parms = gnutls_session_get_ptr(session);
-
        ret = gnutls_certificate_verify_peers3(session, parms->peername,
                                               &status);
        if (ret != GNUTLS_E_SUCCESS) {
@@ -262,7 +262,14 @@
        return GNUTLS_E_SUCCESS;
 }
 
-static void tlshd_client_x509_handshake(struct tlshd_handshake_parms *parms)
+static int tlshd_tls13_client_x509_verify_function(gnutls_session_t session)
+{
+       struct tlshd_handshake_parms *parms = gnutls_session_get_ptr(session);
+
+       return tlshd_client_x509_verify_function(session, parms);
+}
+
+static void tlshd_tls13_client_x509_handshake(struct tlshd_handshake_parms 
*parms)
 {
        gnutls_certificate_credentials_t xcred;
        gnutls_session_t session;
@@ -321,7 +328,7 @@
                goto out_free_creds;
        }
        gnutls_certificate_set_verify_function(xcred,
-                                              
tlshd_client_x509_verify_function);
+                                              
tlshd_tls13_client_x509_verify_function);
 
        tlshd_start_tls_handshake(session, parms);
 
@@ -331,11 +338,17 @@
        gnutls_certificate_free_credentials(xcred);
 }
 
-static void tlshd_client_psk_handshake_one(struct tlshd_handshake_parms *parms,
-                                          key_serial_t peerid)
+static void tlshd_tls13_client_psk_handshake_one(struct tlshd_handshake_parms 
*parms,
+                                                key_serial_t peerid)
 {
+#ifdef HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2
+       gnutls_mac_algorithm_t mac = GNUTLS_MAC_SHA256;
+#endif
        gnutls_psk_client_credentials_t psk_cred;
        gnutls_session_t session;
+#ifdef HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2
+       int version, type, hash;
+#endif
        gnutls_datum_t key;
        unsigned int flags;
        char *identity;
@@ -352,7 +365,27 @@
                return;
        }
 
+#ifdef HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2
+       if (sscanf(identity, "NVMe%01d%c%02d %*s",
+                  &version, &type, &hash) == 3) {
+               switch (hash) {
+               case 1:
+                       mac = GNUTLS_MAC_SHA256;
+                       break;
+               case 2:
+                       mac = GNUTLS_MAC_SHA384;
+                       break;
+               default:
+                       tlshd_log_error("invalid key identity");
+                       free(identity);
+                       return;
+               }
+       }
+
+       ret = gnutls_psk_allocate_client_credentials2(&psk_cred, mac);
+#else
        ret = gnutls_psk_allocate_client_credentials(&psk_cred);
+#endif
        if (ret != GNUTLS_E_SUCCESS) {
                tlshd_log_gnutls_error(ret);
                free(identity);
@@ -397,7 +430,7 @@
        free(identity);
 }
 
-static void tlshd_client_psk_handshake(struct tlshd_handshake_parms *parms)
+static void tlshd_tls13_client_psk_handshake(struct tlshd_handshake_parms 
*parms)
 {
        unsigned int i;
 
@@ -411,51 +444,204 @@
         * Retry ClientHello with each identity on the kernel's list.
         */
        for (i = 0; i < parms->num_peerids; i++) {
-               tlshd_client_psk_handshake_one(parms, parms->peerids[i]);
+               tlshd_tls13_client_psk_handshake_one(parms, parms->peerids[i]);
                if (parms->session_status != EACCES)
                        break;
        }
 }
 
 /**
- * tlshd_clienthello_handshake - send a TLSv1.3 ClientHello
+ * tlshd_tls13_clienthello_handshake - send a TLSv1.3 ClientHello
  * @parms: handshake parameters
  *
  */
-void tlshd_clienthello_handshake(struct tlshd_handshake_parms *parms)
+void tlshd_tls13_clienthello_handshake(struct tlshd_handshake_parms *parms)
 {
-       int ret;
+       switch (parms->auth_mode) {
+       case HANDSHAKE_AUTH_UNAUTH:
+               tlshd_tls13_client_anon_handshake(parms);
+               break;
+       case HANDSHAKE_AUTH_X509:
+               tlshd_tls13_client_x509_handshake(parms);
+               break;
+       case HANDSHAKE_AUTH_PSK:
+               tlshd_tls13_client_psk_handshake(parms);
+               break;
+       default:
+               tlshd_log_debug("Unrecognized auth mode (%d)",
+                               parms->auth_mode);
+       }
+}
 
-       ret = gnutls_global_init();
-       if (ret != GNUTLS_E_SUCCESS) {
-               tlshd_log_gnutls_error(ret);
-               return;
+#ifdef HAVE_GNUTLS_QUIC
+static int tlshd_quic_client_x509_verify_function(gnutls_session_t session)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+
+       return tlshd_client_x509_verify_function(session, conn->parms);
+}
+
+#define TLSHD_QUIC_NO_CERT_AUTH        3
+
+static int tlshd_quic_client_set_x509_session(struct tlshd_quic_conn *conn)
+{
+       struct tlshd_handshake_parms *parms = conn->parms;
+       gnutls_certificate_credentials_t cred;
+       gnutls_session_t session;
+       int ret = -EINVAL;
+       char *cafile;
+
+       if (conn->cert_req != TLSHD_QUIC_NO_CERT_AUTH) {
+               if (!tlshd_x509_client_get_certs(parms) || 
!tlshd_x509_client_get_privkey(parms)) {
+                       tlshd_log_error("cert/privkey get error %d", -ret);
+                       return ret;
+               }
        }
+       ret = gnutls_certificate_allocate_credentials(&cred);
+       if (ret)
+               goto err;
+       if (tlshd_config_get_client_truststore(&cafile)) {
+               ret = gnutls_certificate_set_x509_trust_file(cred, cafile, 
GNUTLS_X509_FMT_PEM);
+               free(cafile);
+       } else
+               ret = gnutls_certificate_set_x509_system_trust(cred);
+       if (ret < 0)
+               goto err_cred;
+       tlshd_log_debug("System trust: Loaded %d certificate(s).", ret);
 
-       if (tlshd_tls_debug)
-               gnutls_global_set_log_level(tlshd_tls_debug);
-       gnutls_global_set_log_function(tlshd_gnutls_log_func);
-       gnutls_global_set_audit_log_function(tlshd_gnutls_audit_func);
-
-#ifdef HAVE_GNUTLS_GET_SYSTEM_CONFIG_FILE
-       tlshd_log_debug("System config file: %s",
-                       gnutls_get_system_config_file());
-#endif
+       if (conn->cert_req == TLSHD_QUIC_NO_CERT_AUTH) {
+               gnutls_certificate_set_verify_flags(cred, 
GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2 |
+                                                         
GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5);
+               gnutls_certificate_set_flags(cred, 
GNUTLS_CERTIFICATE_SKIP_KEY_CERT_MATCH |
+                                                  
GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK);
+       } else {
+               gnutls_certificate_set_verify_function(cred,
+                                                      
tlshd_quic_client_x509_verify_function);
+               gnutls_certificate_set_retrieve_function2(cred, 
tlshd_x509_retrieve_key_cb);
+       }
+
+       ret = gnutls_init(&session, GNUTLS_CLIENT |
+                                   GNUTLS_ENABLE_EARLY_DATA | 
GNUTLS_NO_END_OF_EARLY_DATA);
+       if (ret)
+               goto err_cred;
+       gnutls_session_set_ptr(session, conn);
+       if (conn->ticket_len) {
+               ret = gnutls_session_set_data(session, conn->ticket, 
conn->ticket_len);
+               if (ret)
+                       goto err_session;
+       }
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
+       if (ret)
+               goto err_session;
+       if (parms->peername) {
+               ret = gnutls_server_name_set(session, GNUTLS_NAME_DNS,
+                                            parms->peername, 
strlen(parms->peername));
+               if (ret)
+                       goto err_session;
+       }
+       conn->session = session;
+       return 0;
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_certificate_free_credentials(cred);
+err:
+       tlshd_log_gnutls_error(ret);
+       return ret;
+}
+
+static int tlshd_quic_client_set_anon_session(struct tlshd_quic_conn *conn)
+{
+       conn->cert_req = TLSHD_QUIC_NO_CERT_AUTH;
+       return tlshd_quic_client_set_x509_session(conn);
+}
+
+static int tlshd_quic_client_set_psk_session(struct tlshd_quic_conn *conn)
+{
+       key_serial_t peerid = conn->parms->peerids[0];
+       gnutls_psk_client_credentials_t cred;
+       gnutls_session_t session;
+       char *identity = NULL;
+       gnutls_datum_t key;
+       int ret = -EINVAL;
+
+       if (!tlshd_keyring_get_psk_username(peerid, &identity) ||
+           !tlshd_keyring_get_psk_key(peerid, &key)) {
+               free(identity);
+               tlshd_log_error("identity/key get error %d", -ret);
+               return ret;
+       }
+
+       ret = gnutls_psk_allocate_client_credentials(&cred);
+       if (ret)
+               goto err;
+       ret = gnutls_psk_set_client_credentials(cred, identity, &key, 
GNUTLS_PSK_KEY_RAW);
+       if (ret)
+               goto err_cred;
+
+       ret = gnutls_init(&session, GNUTLS_CLIENT);
+       if (ret)
+               goto err_cred;
+       gnutls_session_set_ptr(session, conn);
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
+       if (ret)
+               goto err_session;
+       conn->session = session;
+       return 0;
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_psk_free_client_credentials(cred);
+err:
+       free(identity);
+       tlshd_log_gnutls_error(ret);
+       return ret;
+}
+
+/**
+ * tlshd_quic_clienthello_handshake - send a QUIC Client Initial
+ * @parms: handshake parameters
+ *
+ */
+void tlshd_quic_clienthello_handshake(struct tlshd_handshake_parms *parms)
+{
+       struct tlshd_quic_conn *conn;
+       int ret;
+
+       ret = tlshd_quic_conn_create(&conn, parms);
+       if (ret) {
+               parms->session_status = -ret;
+               return gnutls_global_deinit();
+       }
 
        switch (parms->auth_mode) {
        case HANDSHAKE_AUTH_UNAUTH:
-               tlshd_client_anon_handshake(parms);
+               ret = tlshd_quic_client_set_anon_session(conn);
                break;
        case HANDSHAKE_AUTH_X509:
-               tlshd_client_x509_handshake(parms);
+               ret = tlshd_quic_client_set_x509_session(conn);
                break;
        case HANDSHAKE_AUTH_PSK:
-               tlshd_client_psk_handshake(parms);
+               ret = tlshd_quic_client_set_psk_session(conn);
                break;
        default:
-               tlshd_log_debug("Unrecognized auth mode (%d)",
-                               parms->auth_mode);
+               ret = -EINVAL;
+               tlshd_log_debug("Unrecognized auth mode (%d)", 
parms->auth_mode);
+       }
+       if (ret) {
+               conn->errcode = -ret;
+               goto out;
        }
 
-       gnutls_global_deinit();
+       tlshd_quic_start_handshake(conn);
+out:
+       parms->session_status = conn->errcode;
+       tlshd_quic_conn_destroy(conn);
+}
+#else
+void tlshd_quic_clienthello_handshake(struct tlshd_handshake_parms *parms)
+{
+       tlshd_log_debug("QUIC handshake is not enabled (%d)", parms->auth_mode);
+       parms->session_status = EOPNOTSUPP;
 }
+#endif
diff -Nru ktls-utils-0.11/src/tlshd/config.c ktls-utils-1.0.0/src/tlshd/config.c
--- ktls-utils-0.11/src/tlshd/config.c  2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/config.c 2025-05-05 19:58:55.000000000 +0200
@@ -84,17 +84,24 @@
                                          "nl", NULL);
        tmp = g_key_file_get_integer(tlshd_configuration, "debug",
                                     "delay_done", NULL);
-       tlshd_delay_done = tmp > 0 ? tmp : 0;
+       tlshd_delay_done = tmp > 0 ? (unsigned int)tmp : 0;
 
        keyrings = g_key_file_get_string_list(tlshd_configuration,
                                              "authenticate",
                                              "keyrings", &length, NULL);
        if (keyrings) {
                for (i = 0; i < length; i++) {
+                       if (!strcmp(keyrings[i], ".nvme"))
+                               continue;
                        tlshd_keyring_link_session(keyrings[i]);
                }
                g_strfreev(keyrings);
        }
+       /*
+        * Always link the default nvme subsystem keyring into the
+        * session.
+        */
+       tlshd_keyring_link_session(".nvme");
 
        return true;
 }
@@ -186,6 +193,7 @@
                return false;
        } else if (access(pathname, F_OK)) {
                tlshd_log_debug("client x509.truststore pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
@@ -222,6 +230,7 @@
                return false;
        } else if (access(pathname, F_OK)) {
                tlshd_log_debug("client x509.certificate pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
@@ -268,7 +277,8 @@
                g_error_free(error);
                return false;
        } else if (access(pathname, F_OK)) {
-               tlshd_log_debug("client x095.private_key pathname \"%s\" is not 
accessible", pathname);
+               tlshd_log_debug("client x509.private_key pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
@@ -321,6 +331,7 @@
                return false;
        } else if (access(pathname, F_OK)) {
                tlshd_log_debug("server x509.truststore pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
@@ -357,6 +368,7 @@
                return false;
        } else if (access(pathname, F_OK)) {
                tlshd_log_debug("server x509.certificate pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
@@ -378,6 +390,7 @@
 
        tlshd_log_debug("Retrieved %u x.509 server certificate(s) from %s",
                        *certs_len, pathname);
+       g_free(pathname);
        return true;
 }
 
@@ -403,6 +416,7 @@
                return false;
        } else if (access(pathname, F_OK)) {
                tlshd_log_debug("server x509.privkey pathname \"%s\" is not 
accessible", pathname);
+               g_free(pathname);
                return false;
        }
 
diff -Nru ktls-utils-0.11/src/tlshd/handshake.c 
ktls-utils-1.0.0/src/tlshd/handshake.c
--- ktls-utils-0.11/src/tlshd/handshake.c       2024-06-14 16:54:21.000000000 
+0200
+++ ktls-utils-1.0.0/src/tlshd/handshake.c      2025-05-05 19:58:55.000000000 
+0200
@@ -59,7 +59,7 @@
        int ret;
 
 
-       len = sizeof(saved);
+       len = sizeof(*saved);
        ret = getsockopt(gnutls_transport_get_int(session),
                         IPPROTO_TCP, TCP_NODELAY, saved, &len);
        if (ret < 0) {
@@ -94,10 +94,14 @@
                case GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR:
                        tlshd_log_cert_verification_error(session);
                        break;
-               default:
+               case -ETIMEDOUT:
                        tlshd_log_gnutls_error(ret);
+                       parms->session_status = -ret;
+                       break;
+               default:
+                       tlshd_log_notice("tlshd_start_tls_handshake unhandled 
error %d, returning EACCES\n", ret);
+                       parms->session_status = EACCES;
                }
-               parms->session_status = EACCES;
                return;
        }
 
@@ -115,22 +119,65 @@
 void tlshd_service_socket(void)
 {
        struct tlshd_handshake_parms parms;
+       int ret;
 
        if (tlshd_genl_get_handshake_parms(&parms) != 0)
                goto out;
 
+       ret = gnutls_global_init();
+       if (ret != GNUTLS_E_SUCCESS) {
+               tlshd_log_gnutls_error(ret);
+               goto out;
+       }
+
+       if (tlshd_tls_debug)
+               gnutls_global_set_log_level(tlshd_tls_debug);
+       gnutls_global_set_log_function(tlshd_gnutls_log_func);
+       gnutls_global_set_audit_log_function(tlshd_gnutls_audit_func);
+
+#ifdef HAVE_GNUTLS_GET_SYSTEM_CONFIG_FILE
+       tlshd_log_debug("System config file: %s",
+                       gnutls_get_system_config_file());
+#endif
+
        switch (parms.handshake_type) {
        case HANDSHAKE_MSG_TYPE_CLIENTHELLO:
-               tlshd_clienthello_handshake(&parms);
+               switch (parms.ip_proto) {
+               case IPPROTO_TCP:
+                       tlshd_tls13_clienthello_handshake(&parms);
+                       break;
+#ifdef HAVE_GNUTLS_QUIC
+               case IPPROTO_QUIC:
+                       tlshd_quic_clienthello_handshake(&parms);
+                       break;
+#endif
+               default:
+                       tlshd_log_debug("Unsupported ip_proto (%d)", 
parms.ip_proto);
+                       parms.session_status = EOPNOTSUPP;
+               }
                break;
        case HANDSHAKE_MSG_TYPE_SERVERHELLO:
-               tlshd_serverhello_handshake(&parms);
+               switch (parms.ip_proto) {
+               case IPPROTO_TCP:
+                       tlshd_tls13_serverhello_handshake(&parms);
+                       break;
+#ifdef HAVE_GNUTLS_QUIC
+               case IPPROTO_QUIC:
+                       tlshd_quic_serverhello_handshake(&parms);
+                       break;
+#endif
+               default:
+                       tlshd_log_debug("Unsupported ip_proto (%d)", 
parms.ip_proto);
+                       parms.session_status = EOPNOTSUPP;
+               }
                break;
        default:
                tlshd_log_debug("Unrecognized handshake type (%d)",
                                parms.handshake_type);
        }
 
+       gnutls_global_deinit();
+
 out:
        tlshd_genl_done(&parms);
 
diff -Nru ktls-utils-0.11/src/tlshd/main.c ktls-utils-1.0.0/src/tlshd/main.c
--- ktls-utils-0.11/src/tlshd/main.c    2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/main.c   2025-05-05 19:58:55.000000000 +0200
@@ -56,8 +56,9 @@
        { NULL,         0,                      NULL,    0 }
 };
 
-void usage(char *progname) {
-               fprintf(stderr, "usage: %s [-chsv]\n", progname);
+static void usage(char *progname)
+{
+       fprintf(stderr, "usage: %s [-chsv]\n", progname);
 }
 
 int main(int argc, char **argv)
diff -Nru ktls-utils-0.11/src/tlshd/netlink.c 
ktls-utils-1.0.0/src/tlshd/netlink.c
--- ktls-utils-0.11/src/tlshd/netlink.c 2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/netlink.c        2025-05-05 19:58:55.000000000 
+0200
@@ -237,6 +237,7 @@
        struct nlattr *tb[HANDSHAKE_A_ACCEPT_MAX + 1];
        struct tlshd_handshake_parms *parms = arg;
        char *peername = NULL;
+       socklen_t optlen;
        int err;
 
        tlshd_log_debug("Parsing a valid netlink message\n");
@@ -255,6 +256,12 @@
                        tlshd_log_perror("getpeername");
                        return NL_STOP;
                }
+               optlen = sizeof(parms->ip_proto);
+               if (getsockopt(parms->sockfd, SOL_SOCKET, SO_PROTOCOL,
+                              &parms->ip_proto, &optlen) == -1) {
+                       tlshd_log_perror("getsockopt (SO_PROTOCOL)");
+                       return NL_STOP;
+               }
        }
        if (tb[HANDSHAKE_A_ACCEPT_MESSAGE_TYPE])
                parms->handshake_type = 
nla_get_u32(tb[HANDSHAKE_A_ACCEPT_MESSAGE_TYPE]);
@@ -269,7 +276,7 @@
        tlshd_parse_certificate(parms, tb[HANDSHAKE_A_ACCEPT_CERTIFICATE]);
 
        if (peername)
-               strcpy(tlshd_peername, peername);
+               strncpy(tlshd_peername, peername, sizeof(tlshd_peername) - 1);
        else {
                err = getnameinfo(parms->peeraddr, parms->peeraddr_len,
                                  tlshd_peername, sizeof(tlshd_peername),
@@ -288,6 +295,7 @@
        .peeraddr               = (struct sockaddr *)&tlshd_peeraddr,
        .peeraddr_len           = sizeof(tlshd_peeraddr),
        .sockfd                 = -1,
+       .ip_proto               = -1,
        .handshake_type         = HANDSHAKE_MSG_TYPE_UNSPEC,
        .timeout_ms             = GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT,
        .auth_mode              = HANDSHAKE_AUTH_UNSPEC,
diff -Nru ktls-utils-0.11/src/tlshd/quic.c ktls-utils-1.0.0/src/tlshd/quic.c
--- ktls-utils-0.11/src/tlshd/quic.c    1970-01-01 01:00:00.000000000 +0100
+++ ktls-utils-1.0.0/src/tlshd/quic.c   2025-05-05 19:58:55.000000000 +0200
@@ -0,0 +1,636 @@
+/*
+ * Perform a QUIC server or client side handshake.
+ *
+ * Copyright (c) 2024 Red Hat, Inc.
+ *
+ * ktls-utils is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <gnutls/abstract.h>
+#include <sys/socket.h>
+#include <linux/tls.h>
+#include <keyutils.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "config.h"
+#include "tlshd.h"
+
+#ifdef HAVE_GNUTLS_QUIC
+static void quic_timer_handler(union sigval arg)
+{
+       struct tlshd_quic_conn *conn = arg.sival_ptr;
+
+       tlshd_log_error("conn timeout error %d", ETIMEDOUT);
+       conn->errcode = ETIMEDOUT;
+}
+
+static int quic_conn_setup_timer(struct tlshd_quic_conn *conn)
+{
+       uint64_t msec = conn->parms->timeout_ms;
+       struct itimerspec its = {};
+       struct sigevent sev = {};
+
+       sev.sigev_notify = SIGEV_THREAD;
+       sev.sigev_notify_function = quic_timer_handler;
+       sev.sigev_value.sival_ptr = conn;
+       if (timer_create(CLOCK_REALTIME, &sev, &conn->timer)) {
+               tlshd_log_error("timer creation error %d", errno);
+               return -1;
+       }
+
+       its.it_value.tv_sec  = msec / 1000;
+       its.it_value.tv_nsec = (msec % 1000) * 1000000;
+       if (timer_settime(conn->timer, 0, &its, NULL)) {
+               tlshd_log_error("timer setup error %d", errno);
+               return -1;
+       }
+       return 0;
+}
+
+static void quic_conn_delete_timer(struct tlshd_quic_conn *conn)
+{
+       timer_delete(conn->timer);
+}
+
+static uint32_t quic_get_tls_cipher_type(gnutls_cipher_algorithm_t cipher)
+{
+       switch (cipher) {
+       case GNUTLS_CIPHER_AES_128_GCM:
+               return TLS_CIPHER_AES_GCM_128;
+       case GNUTLS_CIPHER_AES_128_CCM:
+               return TLS_CIPHER_AES_CCM_128;
+       case GNUTLS_CIPHER_AES_256_GCM:
+               return TLS_CIPHER_AES_GCM_256;
+       case GNUTLS_CIPHER_CHACHA20_POLY1305:
+               return TLS_CIPHER_CHACHA20_POLY1305;
+       default:
+               tlshd_log_notice("%s: %d", __func__, cipher);
+               return 0;
+       }
+}
+
+static enum quic_crypto_level 
quic_get_crypto_level(gnutls_record_encryption_level_t level)
+{
+       switch (level) {
+       case GNUTLS_ENCRYPTION_LEVEL_INITIAL:
+               return QUIC_CRYPTO_INITIAL;
+       case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
+               return QUIC_CRYPTO_HANDSHAKE;
+       case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
+               return QUIC_CRYPTO_APP;
+       case GNUTLS_ENCRYPTION_LEVEL_EARLY:
+               return QUIC_CRYPTO_EARLY;
+       default:
+               tlshd_log_notice("%s: %d", __func__, level);
+               return QUIC_CRYPTO_MAX;
+       }
+}
+
+static int quic_secret_func(gnutls_session_t session, 
gnutls_record_encryption_level_t level,
+                           const void *rx_secret, const void *tx_secret, 
size_t secretlen)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       gnutls_cipher_algorithm_t type  = gnutls_cipher_get(session);
+       struct quic_crypto_secret secret = {};
+       int sockfd, ret, len = sizeof(secret);
+
+       if (conn->completed)
+               return 0;
+
+       if (level == GNUTLS_ENCRYPTION_LEVEL_EARLY)
+               type = gnutls_early_cipher_get(session);
+
+       sockfd = conn->parms->sockfd;
+       secret.level = quic_get_crypto_level(level);
+       secret.type = quic_get_tls_cipher_type(type);
+       if (tx_secret) {
+               secret.send = 1;
+               memcpy(secret.secret, tx_secret, secretlen);
+               if (setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_CRYPTO_SECRET, 
&secret, len)) {
+                       tlshd_log_error("socket setsockopt tx secret error %d 
%u", errno, level);
+                       return -1;
+               }
+       }
+       if (rx_secret) {
+               secret.send = 0;
+               memcpy(secret.secret, rx_secret, secretlen);
+               if (setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_CRYPTO_SECRET, 
&secret, len)) {
+                       tlshd_log_error("socket setsockopt rx secret error %d 
%u", errno, level);
+                       return -1;
+               }
+               if (secret.level == QUIC_CRYPTO_APP) {
+                       if (conn->is_serv) {
+                               ret = gnutls_session_ticket_send(session, 1, 0);
+                               if (ret) {
+                                       tlshd_log_gnutls_error(ret);
+                                       return ret;
+                               }
+                       }
+                       conn->completed = 1;
+               }
+       }
+       tlshd_log_debug("  Secret func: %u %u %u", secret.level, !!tx_secret, 
!!rx_secret);
+       return 0;
+}
+
+static int quic_alert_read_func(gnutls_session_t session,
+                               gnutls_record_encryption_level_t gtls_level,
+                               gnutls_alert_level_t alert_level,
+                               gnutls_alert_description_t alert_desc)
+{
+       tlshd_log_notice("%s: %u %u %u %u", __func__,
+                        !!session, gtls_level, alert_level, alert_desc);
+       return 0;
+}
+
+static int quic_tp_recv_func(gnutls_session_t session, const uint8_t *buf, 
size_t len)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       int sockfd = conn->parms->sockfd;
+
+       if (setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_TRANSPORT_PARAM_EXT, buf, 
len)) {
+               tlshd_log_error("socket setsockopt transport_param_ext error 
%d", errno);
+               return -1;
+       }
+       return 0;
+}
+
+static int quic_tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       int ret, sockfd = conn->parms->sockfd;
+       uint8_t buf[256];
+       unsigned int len;
+
+       len = sizeof(buf);
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_TRANSPORT_PARAM_EXT, buf, 
&len)) {
+               tlshd_log_error("socket getsockopt transport_param_ext error 
%d", errno);
+               return -1;
+       }
+
+       ret = gnutls_buffer_append_data(extdata, buf, len);
+       if (ret) {
+               tlshd_log_gnutls_error(ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int quic_read_func(gnutls_session_t session, 
gnutls_record_encryption_level_t level,
+                         gnutls_handshake_description_t htype, const void 
*data, size_t datalen)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       struct tlshd_quic_msg *msg;
+       uint32_t len = datalen;
+
+       if (htype == GNUTLS_HANDSHAKE_KEY_UPDATE)
+               return 0;
+
+       msg = malloc(sizeof(*msg));
+       if (!msg) {
+               tlshd_log_debug("msg malloc error %d", ENOMEM);
+               return -1;
+       }
+       memset(msg, 0, sizeof(*msg));
+       msg->len = len;
+       memcpy(msg->data, data, msg->len);
+
+       msg->level = quic_get_crypto_level(level);
+       if (!conn->send_list)
+               conn->send_list = msg;
+       else
+               conn->send_last->next = msg;
+       conn->send_last = msg;
+
+       tlshd_log_debug("  Read func: %u %u %u", level, htype, datalen);
+       return 0;
+}
+
+static char quic_priority[] =
+       
"%DISABLE_TLS13_COMPAT_MODE:NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:-CIPHER-ALL:+";
+
+static int quic_session_set_priority(gnutls_session_t session, uint32_t cipher)
+{
+       char p[136] = {};
+
+       memcpy(p, quic_priority, strlen(quic_priority));
+       switch (cipher) {
+       case TLS_CIPHER_AES_GCM_128:
+               strcat(p, "AES-128-GCM");
+               break;
+       case TLS_CIPHER_AES_GCM_256:
+               strcat(p, "AES-256-GCM");
+               break;
+       case TLS_CIPHER_AES_CCM_128:
+               strcat(p, "AES-128-CCM");
+               break;
+       case TLS_CIPHER_CHACHA20_POLY1305:
+               strcat(p, "CHACHA20-POLY1305");
+               break;
+       default:
+               strcat(p, 
"AES-128-GCM:+AES-256-GCM:+AES-128-CCM:+CHACHA20-POLY1305");
+       }
+
+       return gnutls_priority_set_direct(session, p, NULL);
+}
+
+static int quic_session_set_alpns(gnutls_session_t session, char *alpn_data)
+{
+       gnutls_datum_t alpns[TLSHD_QUIC_MAX_ALPNS_LEN / 2];
+       char *alpn = strtok(alpn_data, ",");
+       int count = 0;
+
+       while (alpn) {
+               while (*alpn == ' ')
+                       alpn++;
+               alpns[count].data = (unsigned char *)alpn;
+               alpns[count].size = strlen(alpn);
+               count++;
+               alpn = strtok(NULL, ",");
+       }
+
+       return gnutls_alpn_set_protocols(session, alpns, count, 
GNUTLS_ALPN_MANDATORY);
+}
+
+static gnutls_record_encryption_level_t quic_get_encryption_level(uint8_t 
level)
+{
+       switch (level) {
+       case QUIC_CRYPTO_INITIAL:
+               return GNUTLS_ENCRYPTION_LEVEL_INITIAL;
+       case QUIC_CRYPTO_HANDSHAKE:
+               return GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
+       case QUIC_CRYPTO_APP:
+               return GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
+       case QUIC_CRYPTO_EARLY:
+               return GNUTLS_ENCRYPTION_LEVEL_EARLY;
+       default:
+               tlshd_log_notice("%s: %d", __func__, level);
+               return GNUTLS_ENCRYPTION_LEVEL_APPLICATION + 1;
+       }
+}
+
+static int quic_conn_get_config(struct tlshd_quic_conn *conn)
+{
+       int sockfd = conn->parms->sockfd;
+       struct quic_config config = {};
+       unsigned int len;
+
+       len = sizeof(conn->alpns);
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_ALPN, conn->alpns, &len)) 
{
+               tlshd_log_error("socket getsockopt alpn error %d", errno);
+               return -1;
+       }
+       len = sizeof(conn->ticket);
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_SESSION_TICKET, 
conn->ticket, &len)) {
+               tlshd_log_error("socket getsockopt session ticket error %d", 
errno);
+               return -1;
+       }
+       conn->ticket_len = len;
+       len = sizeof(config);
+       if (getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_CONFIG, &config, &len)) {
+               tlshd_log_error("socket getsockopt config error %d", errno);
+               return -1;
+       }
+       conn->recv_ticket = config.receive_session_ticket;
+       conn->cert_req = config.certificate_request;
+       conn->cipher = config.payload_cipher_type;
+       return 0;
+}
+
+static int quic_handshake_sendmsg(int sockfd, struct tlshd_quic_msg *msg)
+{
+       char outcmsg[CMSG_SPACE(sizeof(struct quic_handshake_info))];
+       struct quic_handshake_info *info;
+       struct msghdr outmsg;
+       struct cmsghdr *cmsg;
+       struct iovec iov;
+       int flags = 0;
+
+       outmsg.msg_name = NULL;
+       outmsg.msg_namelen = 0;
+       outmsg.msg_iov = &iov;
+       iov.iov_base = (void *)msg->data;
+       iov.iov_len = msg->len;
+       outmsg.msg_iovlen = 1;
+
+       outmsg.msg_control = outcmsg;
+       outmsg.msg_controllen = sizeof(outcmsg);
+       outmsg.msg_flags = 0;
+       if (msg->next)
+               flags = MSG_MORE;
+
+       cmsg = CMSG_FIRSTHDR(&outmsg);
+       cmsg->cmsg_level = SOL_QUIC;
+       cmsg->cmsg_type = QUIC_HANDSHAKE_INFO;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(*info));
+
+       info = (struct quic_handshake_info *)CMSG_DATA(cmsg);
+       info->crypto_level = msg->level;
+
+       return sendmsg(sockfd, &outmsg, flags);
+}
+
+static int quic_handshake_recvmsg(int sockfd, struct tlshd_quic_msg *msg)
+{
+       char incmsg[CMSG_SPACE(sizeof(struct quic_handshake_info))];
+       struct quic_handshake_info info;
+       struct cmsghdr *cmsg = NULL;
+       struct msghdr inmsg;
+       struct iovec iov;
+       int ret;
+
+       msg->len = 0;
+       memset(&inmsg, 0, sizeof(inmsg));
+
+       iov.iov_base = msg->data;
+       iov.iov_len = sizeof(msg->data);
+
+       inmsg.msg_name = NULL;
+       inmsg.msg_namelen = 0;
+       inmsg.msg_iov = &iov;
+       inmsg.msg_iovlen = 1;
+       inmsg.msg_control = incmsg;
+       inmsg.msg_controllen = sizeof(incmsg);
+
+       ret = recvmsg(sockfd, &inmsg, MSG_DONTWAIT);
+       if (ret < 0)
+               return ret;
+       msg->len = ret;
+
+       for (cmsg = CMSG_FIRSTHDR(&inmsg); cmsg != NULL; cmsg = 
CMSG_NXTHDR(&inmsg, cmsg))
+               if (SOL_QUIC == cmsg->cmsg_level && QUIC_HANDSHAKE_INFO == 
cmsg->cmsg_type)
+                       break;
+       if (cmsg) {
+               memcpy(&info, CMSG_DATA(cmsg), sizeof(info));
+               msg->level = info.crypto_level;
+       }
+
+       return ret;
+}
+
+static int quic_handshake_completed(struct tlshd_quic_conn *conn)
+{
+       return conn->completed || conn->errcode;
+}
+
+static int quic_handshake_crypto_data(struct tlshd_quic_conn *conn, uint8_t 
level,
+                                     const uint8_t *data, size_t datalen)
+{
+       gnutls_session_t session = conn->session;
+       int ret;
+
+       level = quic_get_encryption_level(level);
+       if (datalen > 0) {
+               ret = gnutls_handshake_write(session, level, data, datalen);
+               if (ret != 0) {
+                       if (!gnutls_error_is_fatal(ret))
+                               return 0;
+                       goto err;
+               }
+       }
+
+       ret = gnutls_handshake(session);
+       if (ret < 0) {
+               if (!gnutls_error_is_fatal(ret))
+                       return 0;
+               goto err;
+       }
+       return 0;
+err:
+       gnutls_alert_send_appropriate(session, ret);
+       tlshd_log_gnutls_error(ret);
+       return ret;
+}
+
+/**
+ * tlshd_quic_conn_create - Create a context for QUIC handshake
+ * @conn_p: pointer to accept the QUIC handshake context created
+ * @parms: handshake parameters
+ *
+ * Returns: %0 on success, or a negative error code
+ */
+int tlshd_quic_conn_create(struct tlshd_quic_conn **conn_p, struct 
tlshd_handshake_parms *parms)
+{
+       struct tlshd_quic_conn *conn;
+       int ret;
+
+       conn = malloc(sizeof(*conn));
+       if (!conn) {
+               tlshd_log_error("conn malloc error %d", ENOMEM);
+               return -ENOMEM;
+       }
+
+       memset(conn, 0, sizeof(*conn));
+       conn->parms = parms;
+
+       if (quic_conn_get_config(conn)) {
+               ret = -errno;
+               goto err;
+       }
+
+       if (quic_conn_setup_timer(conn)) {
+               ret = -errno;
+               goto err;
+       }
+
+       *conn_p = conn;
+       return 0;
+err:
+       tlshd_quic_conn_destroy(conn);
+       return ret;
+}
+
+/**
+ * tlshd_quic_conn_destroy - Destroy a context for QUIC handshake
+ * @conn: QUIC handshake context to destroy
+ *
+ */
+void tlshd_quic_conn_destroy(struct tlshd_quic_conn *conn)
+{
+       struct tlshd_quic_msg *msg = conn->send_list;
+
+       while (msg) {
+               conn->send_list = msg->next;
+               free(msg);
+               msg = conn->send_list;
+       }
+
+       quic_conn_delete_timer(conn);
+       gnutls_deinit(conn->session);
+       free(conn);
+}
+
+#define QUIC_TLSEXT_TP_PARAM   0x39u
+
+static int tlshd_quic_session_configure(struct tlshd_quic_conn *conn)
+{
+       gnutls_session_t session = conn->session;
+       int ret;
+
+       ret = quic_session_set_priority(session, conn->cipher);
+       if (ret)
+               return ret;
+
+       if (conn->alpns[0]) {
+               ret = quic_session_set_alpns(session, conn->alpns);
+               if (ret)
+                       return ret;
+       }
+
+       gnutls_handshake_set_secret_function(session, quic_secret_func);
+       gnutls_handshake_set_read_function(session, quic_read_func);
+       gnutls_alert_set_read_function(session, quic_alert_read_func);
+
+       return gnutls_session_ext_register(
+               session, "QUIC Transport Parameters", QUIC_TLSEXT_TP_PARAM,
+               GNUTLS_EXT_TLS, quic_tp_recv_func, quic_tp_send_func, NULL, 
NULL, NULL,
+               GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | 
GNUTLS_EXT_FLAG_EE);
+}
+
+static void tlshd_quic_recv_session_ticket(struct tlshd_quic_conn *conn)
+{
+       gnutls_session_t session = conn->session;
+       int i, ret, sockfd = conn->parms->sockfd;
+       unsigned int len;
+       size_t size;
+
+       if (conn->is_serv || !conn->recv_ticket)
+               return;
+
+       for (i = 0; i < 10 * conn->recv_ticket; i++) { /* wait and try for 
conn->recv_ticket secs */
+               len = sizeof(conn->ticket);
+               ret = getsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_SESSION_TICKET, 
conn->ticket, &len);
+               if (ret) {
+                       tlshd_log_error("socket getsockopt session ticket error 
%d", errno);
+                       conn->errcode = errno;
+                       return;
+               }
+               if (len)
+                       break;
+               usleep(100000);
+       }
+       if (i == 10 * conn->recv_ticket)
+               return;
+
+       /* process new session ticket msg and get the generated session data */
+       ret = quic_handshake_crypto_data(conn, QUIC_CRYPTO_APP, conn->ticket, 
len);
+       if (ret) {
+               conn->errcode = -ret;
+               return;
+       }
+       size = sizeof(conn->ticket);
+       ret = gnutls_session_get_data(session, conn->ticket, &size);
+       if (ret) {
+               tlshd_log_gnutls_error(ret);
+               conn->errcode = -ret;
+               return;
+       }
+
+       /* set it back to kernel for session resumption of next connection */
+       len = size;
+       ret = setsockopt(sockfd, SOL_QUIC, QUIC_SOCKOPT_SESSION_TICKET, 
conn->ticket, len);
+       if (ret) {
+               tlshd_log_error("socket setsockopt session ticket error %d %u", 
errno, len);
+               conn->errcode = errno;
+       }
+}
+
+/**
+ * tlshd_quic_start_handshake - Drive the handshake interaction
+ * @conn: QUIC handshake context
+ *
+ */
+void tlshd_quic_start_handshake(struct tlshd_quic_conn *conn)
+{
+       int ret, sockfd = conn->parms->sockfd;
+       struct timeval tv = {1, 0};
+       struct tlshd_quic_msg *msg;
+       fd_set readfds;
+
+       FD_ZERO(&readfds);
+       FD_SET(sockfd, &readfds);
+
+       ret = tlshd_quic_session_configure(conn);
+       if (ret) {
+               tlshd_log_gnutls_error(ret);
+               conn->errcode = -ret;
+               return;
+       }
+
+       if (!conn->is_serv) {
+               ret = quic_handshake_crypto_data(conn, QUIC_CRYPTO_INITIAL, 
NULL, 0);
+               if (ret) {
+                       conn->errcode = -ret;
+                       return;
+               }
+
+               msg = conn->send_list;
+               while (msg) {
+                       tlshd_log_debug("< Handshake SEND: %d %d", msg->len, 
msg->level);
+                       ret = quic_handshake_sendmsg(sockfd, msg);
+                       if (ret < 0) {
+                               tlshd_log_error("socket sendmsg error %d", 
errno);
+                               conn->errcode = errno;
+                               return;
+                       }
+                       conn->send_list = msg->next;
+                       free(msg);
+                       msg = conn->send_list;
+               }
+       }
+
+       while (!quic_handshake_completed(conn)) {
+               ret = select(sockfd + 1, &readfds, NULL,  NULL, &tv);
+               if (ret < 0) {
+                       conn->errcode = errno;
+                       return tlshd_log_error("socket select error %d", errno);
+               }
+               msg = &conn->recv_msg;
+               while (!quic_handshake_completed(conn)) {
+                       ret = quic_handshake_recvmsg(sockfd, msg);
+                       if (ret <= 0) {
+                               if (errno == EAGAIN || errno == EWOULDBLOCK)
+                                       break;
+                               conn->errcode = errno;
+                               return tlshd_log_error("socket recvmsg error 
%d", errno);
+                       }
+                       tlshd_log_debug("> Handshake RECV: %u %u", msg->len, 
msg->level);
+                       ret = quic_handshake_crypto_data(conn, msg->level, 
msg->data, msg->len);
+                       if (ret) {
+                               conn->errcode = -ret;
+                               return;
+                       }
+               }
+
+               msg = conn->send_list;
+               while (msg) {
+                       tlshd_log_debug("< Handshake SEND: %u %u", msg->len, 
msg->level);
+                       ret = quic_handshake_sendmsg(sockfd, msg);
+                       if (ret < 0) {
+                               conn->errcode = errno;
+                               return tlshd_log_error("socket sendmsg error 
%d", errno);
+                       }
+                       conn->send_list = msg->next;
+                       free(msg);
+                       msg = conn->send_list;
+               }
+       }
+
+       tlshd_quic_recv_session_ticket(conn);
+}
+#endif
diff -Nru ktls-utils-0.11/src/tlshd/server.c ktls-utils-1.0.0/src/tlshd/server.c
--- ktls-utils-0.11/src/tlshd/server.c  2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/server.c 2025-05-05 19:58:55.000000000 +0200
@@ -2,6 +2,7 @@
  * Perform a TLSv1.3 server-side handshake.
  *
  * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2024 Red Hat, Inc.
  *
  * ktls-utils is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -128,6 +129,7 @@
 /**
  * tlshd_server_x509_verify_function - Verify remote's x.509 certificate
  * @session: session in the midst of a handshake
+ * @parms: handshake parameters
  *
  * A return value of %GNUTLS_E_SUCCESS indicates that the TLS session
  * has been allowed to continue. tlshd either sets the peerid array if
@@ -139,17 +141,15 @@
  * A return value of %GNUTLS_E_CERTIFICATE_ERROR means that certificate
  * verification failed. The server sends an ALERT to the client.
  */
-static int tlshd_server_x509_verify_function(gnutls_session_t session)
+static int tlshd_server_x509_verify_function(gnutls_session_t session,
+                                            struct tlshd_handshake_parms 
*parms)
 {
-       struct tlshd_handshake_parms *parms;
        const gnutls_datum_t *peercerts;
        gnutls_certificate_type_t type;
        unsigned int i, status;
        gnutls_datum_t out;
        int ret;
 
-       parms = gnutls_session_get_ptr(session);
-
        ret = gnutls_certificate_verify_peers3(session, NULL, &status);
        switch (ret) {
        case GNUTLS_E_SUCCESS:
@@ -207,7 +207,14 @@
        return GNUTLS_E_CERTIFICATE_ERROR;
 }
 
-static void tlshd_server_x509_handshake(struct tlshd_handshake_parms *parms)
+static int tlshd_tls13_server_x509_verify_function(gnutls_session_t session)
+{
+       struct tlshd_handshake_parms *parms = gnutls_session_get_ptr(session);
+
+       return tlshd_server_x509_verify_function(session, parms);
+}
+
+static void tlshd_tls13_server_x509_handshake(struct tlshd_handshake_parms 
*parms)
 {
        gnutls_certificate_credentials_t xcred;
        gnutls_session_t session;
@@ -255,7 +262,7 @@
                goto out_free_creds;
        }
        gnutls_certificate_set_verify_function(xcred,
-                                              
tlshd_server_x509_verify_function);
+                                              
tlshd_tls13_server_x509_verify_function);
        gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUEST);
 
        ret = tlshd_gnutls_priority_set(session, parms, 0);
@@ -294,15 +301,17 @@
 
        parms = gnutls_session_get_ptr(session);
 
-       ret = keyctl_search(KEY_SPEC_SESSION_KEYRING, "psk", username, 0);
+       ret = keyctl_search(KEY_SPEC_SESSION_KEYRING,
+                           TLS_DEFAULT_PSK_TYPE, username, 0);
        if (ret < 0) {
-               tlshd_log_error("failed to search key");
+               tlshd_log_error("failed to search '%s' key '%s'",
+                               TLS_DEFAULT_PSK_TYPE, username);
                return -1;
        }
 
        psk = (key_serial_t)ret;
        if (!tlshd_keyring_get_psk_key(psk, key)) {
-               tlshd_log_error("failed to load key");
+               tlshd_log_error("failed to load key %lu", (unsigned long)psk);
                return -1;
        }
 
@@ -312,13 +321,18 @@
        return 0;
 }
 
-static void tlshd_server_psk_handshake(struct tlshd_handshake_parms *parms)
+static void tlshd_tls13_server_psk_handshake(struct tlshd_handshake_parms 
*parms)
 {
        gnutls_psk_server_credentials_t psk_cred;
        gnutls_session_t session;
        int ret;
 
+#ifdef HAVE_GNUTLS_PSK_ALLOCATE_CREDENTIALS2
+       ret = gnutls_psk_allocate_server_credentials2(&psk_cred,
+                                                     GNUTLS_MAC_NONE);
+#else
        ret = gnutls_psk_allocate_server_credentials(&psk_cred);
+#endif
        if (ret != GNUTLS_E_SUCCESS) {
                tlshd_log_gnutls_error(ret);
                return;
@@ -352,41 +366,246 @@
 }
 
 /**
- * tlshd_serverhello_handshake - send a TLSv1.3 ServerHello
+ * tlshd_tls13_serverhello_handshake - send a TLSv1.3 ServerHello
  * @parms: handshake parameters
  *
  */
-void tlshd_serverhello_handshake(struct tlshd_handshake_parms *parms)
+void tlshd_tls13_serverhello_handshake(struct tlshd_handshake_parms *parms)
 {
+       switch (parms->auth_mode) {
+       case HANDSHAKE_AUTH_X509:
+               tlshd_tls13_server_x509_handshake(parms);
+               break;
+       case HANDSHAKE_AUTH_PSK:
+               tlshd_tls13_server_psk_handshake(parms);
+               break;
+       default:
+               tlshd_log_debug("Unrecognized auth mode (%d)",
+                               parms->auth_mode);
+       }
+}
+
+#ifdef HAVE_GNUTLS_QUIC
+static int tlshd_quic_server_alpn_verify(gnutls_session_t session, unsigned 
int htype,
+                                        unsigned int when, unsigned int 
incoming,
+                                        const gnutls_datum_t *msg)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       gnutls_datum_t alpn = {};
        int ret;
 
-       ret = gnutls_global_init();
-       if (ret != GNUTLS_E_SUCCESS) {
+       if (!conn->alpns[0])
+               return 0;
+
+       ret = gnutls_alpn_get_selected_protocol(session, &alpn);
+       if (ret) {
                tlshd_log_gnutls_error(ret);
-               return;
+               return ret;
        }
+       if (setsockopt(conn->parms->sockfd, SOL_QUIC, QUIC_SOCKOPT_ALPN, 
alpn.data, alpn.size)) {
+               tlshd_log_error("socket setsockopt alpn error %d %u", errno, 
alpn.size);
+               return -1;
+       }
+       tlshd_log_debug("  ALPN verify: %u %u %u %u", htype, when, incoming, 
msg->size);
+       return 0;
+}
 
-       if (tlshd_tls_debug)
-               gnutls_global_set_log_level(tlshd_tls_debug);
-       gnutls_global_set_log_function(tlshd_gnutls_log_func);
-       gnutls_global_set_audit_log_function(tlshd_gnutls_audit_func);
-
-#ifdef HAVE_GNUTLS_GET_SYSTEM_CONFIG_FILE
-       tlshd_log_debug("System config file: %s",
-                       gnutls_get_system_config_file());
-#endif
+static int tlshd_quic_server_anti_replay_db_add_func(void *dbf, time_t 
exp_time,
+                                                    const gnutls_datum_t *key,
+                                                    const gnutls_datum_t *data)
+{
+       tlshd_log_debug("  Anti replay: %u %u %u %u", !!dbf, exp_time, 
key->size, data->size);
+       return 0;
+}
+
+static gnutls_anti_replay_t tlshd_quic_server_anti_replay;
+
+static int tlshd_quic_server_x509_verify_function(gnutls_session_t session)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+
+       return tlshd_server_x509_verify_function(session, conn->parms);
+}
+
+static int tlshd_quic_server_psk_cb(gnutls_session_t session, const char 
*username,
+                                   gnutls_datum_t *key)
+{
+       struct tlshd_quic_conn *conn = gnutls_session_get_ptr(session);
+       struct tlshd_handshake_parms *parms = conn->parms;
+       key_serial_t psk;
+       long ret;
+
+       ret = keyctl_search(KEY_SPEC_SESSION_KEYRING, "psk", username, 0);
+       if (ret >= 0)
+               goto found;
+
+       ret = keyctl_search(KEY_SPEC_SESSION_KEYRING, "user", username, 0);
+       if (ret < 0) {
+               tlshd_log_error("key search error %d %s", errno, username);
+               return -1;
+       }
+
+found:
+       psk = (key_serial_t)ret;
+       if (!tlshd_keyring_get_psk_key(psk, key)) {
+               tlshd_log_error("key load error %d", errno);
+               return -1;
+       }
+
+       /* PSK uses the same identity for both client and server */
+       parms->remote_peerid[0] = psk;
+       parms->num_remote_peerids = 1;
+       return 0;
+}
+
+static int tlshd_quic_server_set_x509_session(struct tlshd_quic_conn *conn)
+{
+       struct tlshd_handshake_parms *parms = conn->parms;
+       gnutls_certificate_credentials_t cred;
+       gnutls_datum_t ticket_key;
+       gnutls_session_t session;
+       int ret = -EINVAL;
+       char *cafile;
+
+       if (!tlshd_x509_server_get_certs(parms) || 
!tlshd_x509_server_get_privkey(parms)) {
+               tlshd_log_error("cert/privkey get error %d", -ret);
+               return ret;
+       }
+
+       ret = gnutls_certificate_allocate_credentials(&cred);
+       if (ret)
+               goto err;
+       if (tlshd_config_get_server_truststore(&cafile)) {
+               ret = gnutls_certificate_set_x509_trust_file(cred, cafile,
+                                                            
GNUTLS_X509_FMT_PEM);
+               free(cafile);
+       } else
+               ret = gnutls_certificate_set_x509_system_trust(cred);
+       if (ret < 0)
+               goto err_cred;
+       tlshd_log_debug("System trust: Loaded %d certificate(s).", ret);
+
+       gnutls_certificate_set_retrieve_function2(cred, 
tlshd_x509_retrieve_key_cb);
+
+       gnutls_certificate_set_verify_function(cred, 
tlshd_quic_server_x509_verify_function);
+
+       ret = gnutls_init(&session, GNUTLS_SERVER | GNUTLS_NO_AUTO_SEND_TICKET |
+                                   GNUTLS_ENABLE_EARLY_DATA | 
GNUTLS_NO_END_OF_EARLY_DATA);
+       if (ret)
+               goto err_cred;
+
+       if (!tlshd_quic_server_anti_replay) {
+               ret = gnutls_anti_replay_init(&tlshd_quic_server_anti_replay);
+               if (ret)
+                       goto err_session;
+               
gnutls_anti_replay_set_add_function(tlshd_quic_server_anti_replay,
+                                                   
tlshd_quic_server_anti_replay_db_add_func);
+               gnutls_anti_replay_set_ptr(tlshd_quic_server_anti_replay, NULL);
+       }
+       gnutls_anti_replay_enable(session, tlshd_quic_server_anti_replay);
+       ret = gnutls_record_set_max_early_data_size(session, 0xffffffffu);
+       if (ret)
+               goto err_session;
+
+       gnutls_session_set_ptr(session, conn);
+       ticket_key.data = conn->ticket;
+       ticket_key.size = conn->ticket_len;
+       ret = gnutls_session_ticket_enable_server(session, &ticket_key);
+       if (ret)
+               goto err_session;
+       gnutls_handshake_set_hook_function(session, 
GNUTLS_HANDSHAKE_CLIENT_HELLO,
+                                          GNUTLS_HOOK_POST, 
tlshd_quic_server_alpn_verify);
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
+       if (ret)
+               goto err_session;
+       gnutls_certificate_server_set_request(session, conn->cert_req);
+
+       conn->is_serv = 1;
+       conn->session = session;
+       return 0;
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_certificate_free_credentials(cred);
+err:
+       tlshd_log_gnutls_error(ret);
+       return ret;
+}
+
+static int tlshd_quic_server_set_psk_session(struct tlshd_quic_conn *conn)
+{
+       gnutls_psk_server_credentials_t cred;
+       gnutls_session_t session;
+       int ret;
+
+       ret = gnutls_psk_allocate_server_credentials(&cred);
+       if (ret)
+               goto err;
+       gnutls_psk_set_server_credentials_function(cred, 
tlshd_quic_server_psk_cb);
+
+       ret = gnutls_init(&session, GNUTLS_SERVER | GNUTLS_NO_AUTO_SEND_TICKET);
+       if (ret)
+               goto err_cred;
+       gnutls_session_set_ptr(session, conn);
+       gnutls_handshake_set_hook_function(session, 
GNUTLS_HANDSHAKE_CLIENT_HELLO,
+                                          GNUTLS_HOOK_POST, 
tlshd_quic_server_alpn_verify);
+       ret = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
+       if (ret)
+               goto err_session;
+
+       conn->is_serv = 1;
+       conn->session = session;
+       return 0;
+err_session:
+       gnutls_deinit(session);
+err_cred:
+       gnutls_psk_free_server_credentials(cred);
+err:
+       tlshd_log_gnutls_error(ret);
+       return ret;
+}
+
+/**
+ * tlshd_quic_serverhello_handshake - send a QUIC Server Initial
+ * @parms: handshake parameters
+ *
+ */
+void tlshd_quic_serverhello_handshake(struct tlshd_handshake_parms *parms)
+{
+       struct tlshd_quic_conn *conn;
+       int ret;
+
+       ret = tlshd_quic_conn_create(&conn, parms);
+       if (ret) {
+               parms->session_status = ret;
+               return gnutls_global_deinit();
+       }
 
        switch (parms->auth_mode) {
        case HANDSHAKE_AUTH_X509:
-               tlshd_server_x509_handshake(parms);
+               ret = tlshd_quic_server_set_x509_session(conn);
                break;
        case HANDSHAKE_AUTH_PSK:
-               tlshd_server_psk_handshake(parms);
+               ret = tlshd_quic_server_set_psk_session(conn);
                break;
        default:
-               tlshd_log_debug("Unrecognized auth mode (%d)",
-                               parms->auth_mode);
+               ret = -EINVAL;
+               tlshd_log_debug("Unrecognized auth mode (%d)", 
parms->auth_mode);
+       }
+       if (ret) {
+               conn->errcode = -ret;
+               goto out;
        }
 
-       gnutls_global_deinit();
+       tlshd_quic_start_handshake(conn);
+out:
+       parms->session_status = conn->errcode;
+       tlshd_quic_conn_destroy(conn);
 }
+#else
+void tlshd_quic_serverhello_handshake(struct tlshd_handshake_parms *parms)
+{
+       tlshd_log_debug("QUIC handshake is not enabled (%d)", parms->auth_mode);
+       parms->session_status = EOPNOTSUPP;
+}
+#endif
diff -Nru ktls-utils-0.11/src/tlshd/tlshd.conf.man 
ktls-utils-1.0.0/src/tlshd/tlshd.conf.man
--- ktls-utils-0.11/src/tlshd/tlshd.conf.man    2024-06-14 16:54:21.000000000 
+0200
+++ ktls-utils-1.0.0/src/tlshd/tlshd.conf.man   2025-05-05 19:58:55.000000000 
+0200
@@ -112,10 +112,6 @@
 .B x509.private_key
 This option specifies the pathname of a file containing
 a PEM-encoded private key associated with the above certificate.
-.SH NOTES
-This software is a prototype.
-It's purpose is for demonstration and as a proof-of-concept.
-USE THIS SOFTWARE AT YOUR OWN RISK.
 .SH SEE ALSO
 .BR tlshd (8)
 .SH AUTHOR
diff -Nru ktls-utils-0.11/src/tlshd/tlshd.h ktls-utils-1.0.0/src/tlshd/tlshd.h
--- ktls-utils-0.11/src/tlshd/tlshd.h   2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/tlshd.h  2025-05-05 19:58:55.000000000 +0200
@@ -32,6 +32,7 @@
        struct sockaddr *peeraddr;
        socklen_t       peeraddr_len;
        int             sockfd;
+       int             ip_proto;
        uint32_t        handshake_type;
        unsigned int    timeout_ms;
        uint32_t        auth_mode;
@@ -48,7 +49,8 @@
 };
 
 /* client.c */
-extern void tlshd_clienthello_handshake(struct tlshd_handshake_parms *parms);
+extern void tlshd_tls13_clienthello_handshake(struct tlshd_handshake_parms 
*parms);
+extern void tlshd_quic_clienthello_handshake(struct tlshd_handshake_parms 
*parms);
 
 /* config.c */
 bool tlshd_config_init(const gchar *pathname);
@@ -118,9 +120,51 @@
 extern void tlshd_genl_done(struct tlshd_handshake_parms *parms);
 
 /* server.c */
-extern void tlshd_serverhello_handshake(struct tlshd_handshake_parms *parms);
+extern void tlshd_tls13_serverhello_handshake(struct tlshd_handshake_parms 
*parms);
+extern void tlshd_quic_serverhello_handshake(struct tlshd_handshake_parms 
*parms);
+
+#ifdef HAVE_GNUTLS_QUIC
+#include <linux/quic.h>
+
+#define TLSHD_QUIC_MAX_DATA_LEN                4096
+#define TLSHD_QUIC_MAX_ALPNS_LEN       128
+
+struct tlshd_quic_msg {
+       struct tlshd_quic_msg *next;
+       uint8_t data[TLSHD_QUIC_MAX_DATA_LEN];
+       uint32_t len;
+       uint8_t level;
+};
+
+struct tlshd_quic_conn {
+       struct tlshd_handshake_parms *parms;
+       char alpns[TLSHD_QUIC_MAX_ALPNS_LEN];
+       uint8_t ticket[TLSHD_QUIC_MAX_DATA_LEN];
+       uint32_t ticket_len;
+       uint32_t cipher;
+
+       gnutls_session_t session;
+       uint8_t recv_ticket:1;
+       uint8_t completed:1;
+       uint8_t cert_req:2;
+       uint8_t is_serv:1;
+       uint32_t errcode;
+       timer_t timer;
+
+       struct tlshd_quic_msg *send_list;
+       struct tlshd_quic_msg *send_last;
+       struct tlshd_quic_msg recv_msg;
+};
+
+/* quic.c */
+extern int tlshd_quic_conn_create(struct tlshd_quic_conn **conn_p,
+                                 struct tlshd_handshake_parms *parms);
+extern void tlshd_quic_conn_destroy(struct tlshd_quic_conn *conn);
+extern void tlshd_quic_start_handshake(struct tlshd_quic_conn *conn);
+#endif
 
 #define TLS_DEFAULT_PRIORITIES (NULL)
+#define TLS_DEFAULT_PSK_TYPE   "psk"
 #define TLS_NO_PEERID          (0)
 #define TLS_NO_CERT            (0)
 #define TLS_NO_PRIVKEY         (0)
diff -Nru ktls-utils-0.11/src/tlshd/tlshd.man 
ktls-utils-1.0.0/src/tlshd/tlshd.man
--- ktls-utils-0.11/src/tlshd/tlshd.man 2024-06-14 16:54:21.000000000 +0200
+++ ktls-utils-1.0.0/src/tlshd/tlshd.man        2025-05-05 19:58:55.000000000 
+0200
@@ -76,10 +76,6 @@
 .B GNUTLS_FORCE_FIPS_MODE
 When set to `1', this variable forces the TLS library into FIPS mode
 if FIPS140-2 support is available.
-.SH NOTES
-This software is a prototype.
-It's purpose is for demonstration and as a proof-of-concept.
-USE THIS SOFTWARE AT YOUR OWN RISK.
 .SH SEE ALSO
 .BR tlshd.conf (5),
 .BR ssl (7)

Reply via email to