From 19f6508647bdcd3ce21130201e484d7ca6d962c5 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 16 Mar 2026 15:29:40 +0100
Subject: x509/name-constraints: compare domain names case-insensitive

RFC 5280 7.2:
 > When comparing DNS names for equality, conforming implementations
 > MUST perform a case-insensitive exact match on the entire DNS name.
 > When evaluating name constraints, conforming implementations MUST
 > perform a case-insensitive exact match on a label-by-label basis.

Domain name comparison during name constraints processing
was case-sensitive. For excluded name constraints, this could lead to
incorrectly accepting domain names that should've been rejected.
The code for comparing domain names and domain name parts of emails
has been modified to perform case-insensitive comparison instead.

Reported-by: Oleh Konko <security@1seal.org>
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1223
Fixes: #1803
Fixes: #1852
Fixes: CVE-2026-3833
Fixes: GNUTLS-SA-2026-04-29-5
CVSS: 7.4 High CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 lib/x509/name_constraints.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

From 0fd5bfe0c777a4be30fb481c78c99d1052297f01 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 16 Mar 2026 15:48:57 +0100
Subject: [PATCH] tests/name-constraints: add case-sensitivity check

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 tests/name-constraints.c | 52 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

Index: gnutls-3.8.10/lib/x509/name_constraints.c
===================================================================
--- gnutls-3.8.10.orig/lib/x509/name_constraints.c
+++ gnutls-3.8.10/lib/x509/name_constraints.c
@@ -35,6 +35,7 @@
 #include "x509_int.h"
 #include "x509_ext_int.h"
 #include <libtasn1.h>
+#include "c-strcase.h"
 
 #include "ip.h"
 #include "ip-in-cidr.h"
@@ -80,7 +81,7 @@ enum name_constraint_relation {
 	NC_SORTS_AFTER = 2 /* unrelated constraints */
 };
 
-/* A helper to compare just a pair of strings with this rich comparison */
+/* Helpers to compare just a pair of strings with this rich comparison */
 static enum name_constraint_relation
 compare_strings(const void *n1, size_t n1_len, const void *n2, size_t n2_len)
 {
@@ -96,6 +97,22 @@ compare_strings(const void *n1, size_t n
 	return NC_EQUAL;
 }
 
+static enum name_constraint_relation
+compare_strings_case_insensitive(const void *n1, size_t n1_len, const void *n2,
+				 size_t n2_len)
+{
+	int r = c_strncasecmp(n1, n2, MIN(n1_len, n2_len));
+	if (r < 0)
+		return NC_SORTS_BEFORE;
+	if (r > 0)
+		return NC_SORTS_AFTER;
+	if (n1_len < n2_len)
+		return NC_SORTS_BEFORE;
+	if (n1_len > n2_len)
+		return NC_SORTS_AFTER;
+	return NC_EQUAL;
+}
+
 /* Rich-compare DNS names. Example order/relationships:
  * z.x.a INCLUDED_BY x.a BEFORE y.a INCLUDED_BY a BEFORE x.b BEFORE y.b */
 static enum name_constraint_relation compare_dns_names(const gnutls_datum_t *n1,
@@ -121,8 +138,8 @@ static enum name_constraint_relation com
 		while (j && n2->data[j - 1] != '.')
 			j--;
 
-		rel = compare_strings(&n1->data[i], i_end - i, &n2->data[j],
-				      j_end - j);
+		rel = compare_strings_case_insensitive(&n1->data[i], i_end - i,
+						       &n2->data[j], j_end - j);
 		if (rel == NC_SORTS_BEFORE) /* x.a BEFORE y.a */
 			return NC_SORTS_BEFORE;
 		if (rel == NC_SORTS_AFTER) /* y.a AFTER x.a */
Index: gnutls-3.8.10/tests/name-constraints.c
===================================================================
--- gnutls-3.8.10.orig/tests/name-constraints.c
+++ gnutls-3.8.10/tests/name-constraints.c
@@ -366,6 +366,58 @@ void doit(void)
 
 	gnutls_x509_name_constraints_deinit(nc);
 
+	/* 4b: case insensitivity */
+
+	ret = gnutls_x509_name_constraints_init(&nc);
+	check_for_error(ret);
+
+	set_name("example.net", &name);
+	ret = gnutls_x509_name_constraints_add_excluded(nc, GNUTLS_SAN_DNSNAME,
+							&name);
+	check_for_error(ret);
+	set_name("email@example.net", &name);
+	ret = gnutls_x509_name_constraints_add_excluded(
+		nc, GNUTLS_SAN_RFC822NAME, &name);
+	check_for_error(ret);
+
+	set_name("example.org", &name); /* unrelated: accepted */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+	check_test_result(ret, NAME_ACCEPTED, &name);
+
+	set_name("example.net", &name); /* exact match: rejected */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+	check_test_result(ret, NAME_REJECTED, &name);
+
+	set_name("eXample.net", &name); /* case *insensitive*: rejected */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+	check_test_result(ret, NAME_REJECTED, &name);
+
+	set_name("mail@example.net", &name); /* unrelated: accepted */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+						 &name);
+	check_test_result(ret, NAME_ACCEPTED, &name);
+
+	set_name("email@example.net", &name); /* exact match: rejected */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+						 &name);
+	check_test_result(ret, NAME_REJECTED, &name);
+
+	set_name("eMail@example.net", &name); /* case *sensitive*: accepted */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+						 &name);
+	check_test_result(ret, NAME_ACCEPTED, &name);
+
+	set_name("email@EXAMPLE.NET", &name); /* case *insensitive*: rejected */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+						 &name);
+	check_test_result(ret, NAME_REJECTED, &name);
+
+	set_name("www.ExAmPlE.NeT", &name); /* case *insensitive*: inexact */
+	ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+	check_test_result(ret, NAME_REJECTED, &name);
+
+	gnutls_x509_name_constraints_deinit(nc);
+
 	// Test suite end.
 
 	if (debug)
