From cb1833afd9b6309563211b1c0a7c291f52ca98d5 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:26:10 +0200
Subject: [PATCH] lib/auth/rsa_psk: fix binary PSK identity lookup

A server looking up PSK username with a NUL-character in it
was wrongfully matching username truncated at a NUL-character.
Fix the check to compare up to the full username length.

Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1850
Fixes: CVE-2026-42010
Fixes: GNUTLS-SA-2026-04-29-4
CVSS: 7.1 High CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 lib/auth/rsa_psk.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

From e3ffd31846d1e6624338a26ca7fce7d1685b17cd Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:02:43 +0200
Subject: tests/pskself2: extend with RSA-PSK support

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 tests/pskself2.c | 79 ++++++++++++++++++++++++++++++++----------------
 1 file changed, 53 insertions(+), 26 deletions(-)

From 0f8539fac736a2cdcc79ee4ea5a2f2590a6bea6b Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:49:47 +0200
Subject: [PATCH] tests/pskself2: sprinkle NUL into key for good measure

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 tests/pskself2.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

Index: gnutls-3.8.10/tests/pskself2.c
===================================================================
--- gnutls-3.8.10.orig/tests/pskself2.c
+++ gnutls-3.8.10/tests/pskself2.c
@@ -27,6 +27,7 @@
 #include "config.h"
 #endif
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -51,6 +52,7 @@ int main(int argc, char **argv)
 
 #include "utils.h"
 #include "extras/hex.h"
+#include "cert-common.h"
 
 /* A very basic TLS client, with PSK authentication.
  */
@@ -65,14 +67,15 @@ static void tls_log_func(int level, cons
 #define MAX_BUF 1024
 #define MSG "Hello TLS"
 
-static void client(int sd, const char *prio, unsigned exp_hint)
+static void client(int sd, const char *prio, bool exp_hint, bool rsa)
 {
 	int ret, ii;
 	gnutls_session_t session;
 	char buffer[MAX_BUF + 1];
 	gnutls_psk_client_credentials_t pskcred;
+	gnutls_certificate_credentials_t xcred = NULL;
 	/* Need to enable anonymous KX specifically. */
-	const gnutls_datum_t key = { (void *)"DEADBEEF", 8 };
+	const gnutls_datum_t key = { (void *)"DEAD00BEEF", 10 };
 	gnutls_datum_t user;
 	const char *hint;
 
@@ -83,12 +86,15 @@ static void client(int sd, const char *p
 
 	side = "client";
 
-	user.data = gnutls_malloc(4);
+	user.data = gnutls_malloc(5);
+	assert(user.data != NULL);
+
 	user.data[0] = 0xCA;
 	user.data[1] = 0xFE;
-	user.data[2] = 0xCA;
-	user.data[3] = 0xFE;
-	user.size = 4;
+	user.data[2] = 0x00;
+	user.data[3] = 0xCA;
+	user.data[4] = 0xFE;
+	user.size = 5;
 
 	gnutls_psk_allocate_client_credentials(&pskcred);
 	ret = gnutls_psk_set_client_credentials2(pskcred, &user, &key,
@@ -110,6 +116,11 @@ static void client(int sd, const char *p
 	 */
 	gnutls_credentials_set(session, GNUTLS_CRD_PSK, pskcred);
 
+	if (rsa) {
+		gnutls_certificate_allocate_credentials(&xcred);
+		gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+	}
+
 	gnutls_transport_set_int(session, sd);
 
 	/* Perform the TLS handshake
@@ -165,6 +176,8 @@ end:
 
 	gnutls_free(user.data);
 	gnutls_psk_free_client_credentials(pskcred);
+	if (xcred)
+		gnutls_certificate_free_credentials(xcred);
 
 	gnutls_global_deinit();
 }
@@ -179,27 +192,34 @@ end:
 static int pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
 		   gnutls_datum_t *key)
 {
+	const unsigned char expected_user[] = { 0xCA, 0xFE, 0x00, 0xCA, 0xFE };
+	const unsigned char expected_key[] = { 0xDE, 0xAD, 0x00, 0xBE, 0xEF };
+
 	if (debug)
 		printf("psk: Got username with length %d\n", username->size);
 
-	key->data = gnutls_malloc(4);
-	key->data[0] = 0xDE;
-	key->data[1] = 0xAD;
-	key->data[2] = 0xBE;
-	key->data[3] = 0xEF;
-	key->size = 4;
+	/* verify callback received full 5-byte username (#1850) */
+	if (username->size != 5 ||
+	    memcmp(username->data, expected_user, 5) != 0)
+		fail("pskfunc: username mismatch: got %u bytes, expected 5\n",
+		     username->size);
+
+	key->data = gnutls_malloc(5);
+	memcpy(key->data, expected_key, 5);
+	key->size = 5;
 
 	return 0;
 }
 
-static void server(int sd, const char *prio)
+static void server(int sd, const char *prio, bool rsa)
 {
 	gnutls_psk_server_credentials_t server_pskcred;
+	gnutls_certificate_credentials_t serverx509cred = NULL;
 	int ret;
 	gnutls_session_t session;
 	gnutls_datum_t psk_username;
-	char buffer[MAX_BUF + 1],
-		expected_psk_username[] = { 0xDE, 0xAD, 0xBE, 0xEF };
+	char buffer[MAX_BUF + 1];
+	const char expected_psk_username[] = { 0xCA, 0xFE, 0x00, 0xCA, 0xFE };
 
 	/* this must be called once in the program
 	 */
@@ -214,6 +234,13 @@ static void server(int sd, const char *p
 	gnutls_psk_set_server_credentials_hint(server_pskcred, "hint");
 	gnutls_psk_set_server_credentials_function2(server_pskcred, pskfunc);
 
+	if (rsa) {
+		gnutls_certificate_allocate_credentials(&serverx509cred);
+		gnutls_certificate_set_x509_key_mem(serverx509cred,
+						    &server_cert, &server_key,
+						    GNUTLS_X509_FMT_PEM);
+	}
+
 	gnutls_init(&session, GNUTLS_SERVER);
 
 	/* avoid calling all the priority functions, since the defaults
@@ -222,6 +249,9 @@ static void server(int sd, const char *p
 	gnutls_priority_set_direct(session, prio, NULL);
 
 	gnutls_credentials_set(session, GNUTLS_CRD_PSK, server_pskcred);
+	if (serverx509cred)
+		gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
+				       serverx509cred);
 
 	gnutls_transport_set_int(session, sd);
 	ret = gnutls_handshake(session);
@@ -241,8 +271,8 @@ static void server(int sd, const char *p
 		if (gnutls_psk_server_get_username2(session, &psk_username) < 0)
 			fail("server: Could not get PSK username\n");
 
-		if (psk_username.size != 4 ||
-		    memcmp(psk_username.data, expected_psk_username, 4))
+		if (psk_username.size != 5 ||
+		    memcmp(psk_username.data, expected_psk_username, 5))
 			fail("server: Unexpected PSK username\n");
 
 		success("server: PSK username length: %d\n", psk_username.size);
@@ -278,6 +308,8 @@ static void server(int sd, const char *p
 	gnutls_deinit(session);
 
 	gnutls_psk_free_server_credentials(server_pskcred);
+	if (serverx509cred)
+		gnutls_certificate_free_credentials(serverx509cred);
 
 	gnutls_global_deinit();
 
@@ -285,7 +317,7 @@ static void server(int sd, const char *p
 		success("server: finished\n");
 }
 
-static void run_test(const char *prio, unsigned exp_hint)
+static void run_test(const char *prio, bool exp_hint, bool rsa)
 {
 	pid_t child;
 	int err;
@@ -311,42 +343,46 @@ static void run_test(const char *prio, u
 		int status;
 		/* parent */
 		close(sockets[1]);
-		server(sockets[0], prio);
+		server(sockets[0], prio, rsa);
 		wait(&status);
 		check_wait_status(status);
 	} else {
 		close(sockets[0]);
-		client(sockets[1], prio, exp_hint);
+		client(sockets[1], prio, exp_hint, rsa);
 		exit(0);
 	}
 }
 
 void doit(void)
 {
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", 1);
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", 1);
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", 1);
-
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:+PSK", 0);
-	run_test(
-		"NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
-		0);
-	run_test(
-		"NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
-		0);
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", 0);
-	run_test(
-		"NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
-		0);
-	run_test(
-		"NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
-		0);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", true, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", true,
+		 false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", true, false);
+
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:+PSK", false, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:"
+		 "-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+		 false, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:"
+		 "-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+		 false, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", false, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:"
+		 "-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+		 false, false);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:"
+		 "-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+		 false, false);
 	/* the following should work once we support PSK without DH */
-	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", 0);
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", false, false);
+
+	run_test("NORMAL:-KX-ALL:+PSK", false, false);
+	run_test("NORMAL:-KX-ALL:+ECDHE-PSK", false, false);
+	run_test("NORMAL:-KX-ALL:+DHE-PSK", false, false);
 
-	run_test("NORMAL:-KX-ALL:+PSK", 0);
-	run_test("NORMAL:-KX-ALL:+ECDHE-PSK", 0);
-	run_test("NORMAL:-KX-ALL:+DHE-PSK", 0);
+	/* RSA-PSK */
+	run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+RSA-PSK", false, true);
 }
 
 #endif /* _WIN32 */
Index: gnutls-3.8.10/lib/auth/rsa_psk.c
===================================================================
--- gnutls-3.8.10.orig/lib/auth/rsa_psk.c
+++ gnutls-3.8.10/lib/auth/rsa_psk.c
@@ -321,8 +321,7 @@ static int _gnutls_proc_rsa_psk_client_k
 	 * filled in if the key is not found.
 	 */
 	ret = _gnutls_psk_pwd_find_entry(session, info->username,
-					 strlen(info->username), &pwd_psk,
-					 NULL);
+					 info->username_len, &pwd_psk, NULL);
 	if (ret < 0)
 		return gnutls_assert_val(ret);
 
Index: gnutls-3.8.10/lib/auth/psk_passwd.c
===================================================================
--- gnutls-3.8.10.orig/lib/auth/psk_passwd.c
+++ gnutls-3.8.10/lib/auth/psk_passwd.c
@@ -78,7 +78,7 @@ ATTRIBUTE_NONNULL((1, 2))
 static bool username_matches(const gnutls_datum_t *username, const char *line,
 			     size_t line_size)
 {
-	int retval;
+	bool retval;
 	unsigned i;
 	gnutls_datum_t hexline, hex_username = { NULL, 0 };
 
@@ -91,7 +91,7 @@ static bool username_matches(const gnutl
 		return false;
 
 	if (line_size == 0)
-		return (username->size == 0);
+		return false;
 
 	/* move to first ':' */
 	i = 0;
@@ -99,6 +99,9 @@ static bool username_matches(const gnutl
 		i++;
 	}
 
+	if (line[i] != ':')
+		return false;
+
 	/* if format is in hex, e.g. #FAFAFA */
 	if (line[0] == '#' && line_size > 1) {
 		hexline.data = (void *)&line[1];
@@ -107,19 +110,17 @@ static bool username_matches(const gnutl
 		if (gnutls_hex_decode2(&hexline, &hex_username) < 0)
 			return gnutls_assert_val(0);
 
-		if (hex_username.size == username->size)
-			retval = memcmp(username->data, hex_username.data,
-					username->size);
-		else
-			retval = -1;
+		retval = hex_username.size == username->size &&
+			 memcmp(username->data, hex_username.data,
+				username->size) == 0;
 
 		_gnutls_free_datum(&hex_username);
 	} else {
-		retval = strncmp((const char *)username->data, line,
-				 MAX(i, username->size));
+		retval = i == username->size &&
+			 strncmp((const char *)username->data, line, i) == 0;
 	}
 
-	return (retval == 0);
+	return retval;
 }
 
 /* Randomizes the given password entry. It actually sets a random password. 
