From abf18d97bdd0a78d4f64ba4f2d62db8ea96f3c98 Mon Sep 17 00:00:00 2001
From: Rahul Jain <rahul.jain@suse.com>
Date: Wed, 15 Apr 2026 13:54:47 +0530
Subject: [PATCH] Fix CVE-2026-35331: constraints plugin X.509 name constraints

---
 .../constraints/constraints_validator.c       | 23 ++++++++++++++++---
 .../tests/suites/test_certnames.c             | 21 ++++++++++++-----
 2 files changed, 35 insertions(+), 9 deletions(-)

diff --git a/src/libstrongswan/plugins/constraints/constraints_validator.c b/src/libstrongswan/plugins/constraints/constraints_validator.c
index 27bdb89..62a3062 100644
--- a/src/libstrongswan/plugins/constraints/constraints_validator.c
+++ b/src/libstrongswan/plugins/constraints/constraints_validator.c
@@ -55,6 +55,15 @@ static bool check_pathlen(x509_t *issuer, int pathlen)
 	return TRUE;
 }
 
+/* new helper added string match */
+static bool string_matches(chunk_t constraint, chunk_t id)
+{
+    return constraint.len == id.len &&
+           memchr(constraint.ptr, 0, constraint.len) == NULL &&
+           memchr(id.ptr, 0, id.len) == NULL &&
+           strncasecmp(constraint.ptr, id.ptr, constraint.len) == 0;
+}
+
 /**
  * Check if a FQDN constraint matches
  */
@@ -70,7 +79,7 @@ static bool fqdn_matches(identification_t *constraint, identification_t *id)
 		return FALSE;
 	}
 	diff = chunk_create(i.ptr, i.len - c.len);
-	if (!chunk_equals(c, chunk_skip(i, diff.len)))
+	if (!string_matches(c, chunk_skip(i, diff.len)))
 	{
 		return FALSE;
 	}
@@ -104,7 +113,7 @@ static bool email_matches(identification_t *constraint, identification_t *id)
 		return chunk_equals(c, i);
 	}
 	diff = chunk_create(i.ptr, i.len - c.len);
-	if (!chunk_equals(c, chunk_skip(i, diff.len)))
+	if (!string_matches(c, chunk_skip(i, diff.len)))
 	{
 		return FALSE;
 	}
@@ -389,9 +398,17 @@ static bool collect_constraints(x509_t *x509, bool permitted, hashtable_t **out)
 		type = constraint->get_type(constraint);
 		switch (type)
 		{
+			case ID_DER_ASN1_DN:
+			     if (!permitted)
+  			     {
+         			 DBG1(DBG_CFG, "excluded %N NameConstraint not supported",
+              			     id_type_names, type);
+         			 success = FALSE;
+         			 break;
+     			     }
+     			     /* fall-through */
 			case ID_FQDN:
 			case ID_RFC822_ADDR:
-			case ID_DER_ASN1_DN:
 			case ID_IPV4_ADDR_SUBNET:
 			case ID_IPV6_ADDR_SUBNET:
 				break;
diff --git a/src/libstrongswan/tests/suites/test_certnames.c b/src/libstrongswan/tests/suites/test_certnames.c
index 2549fb6..46092bd 100644
--- a/src/libstrongswan/tests/suites/test_certnames.c
+++ b/src/libstrongswan/tests/suites/test_certnames.c
@@ -207,8 +207,10 @@ static struct {
 	bool good;
 } permitted_san[] = {
 	{ ".strongswan.org", "test.strongswan.org", TRUE },
+	{ ".strongswan.org", "test.strongSwan.org", TRUE },   /* case-insensitive FQDN suffix */
 	{ "strongswan.org", "test.strongswan.org", TRUE },
 	{ "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", TRUE },
+	{ "a.b.c.strongswan.org", "d.A.b.C.strongswan.org", TRUE },   /* mixed-case multi-label */
 	{ "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", FALSE },
 	{ "strongswan.org", "strongswan.org.com", FALSE },
 	{ ".strongswan.org", "strongswan.org", FALSE },
@@ -216,8 +218,11 @@ static struct {
 	{ "strongswan.org", "swan.org", FALSE },
 	{ "strongswan.org", "swan.org", FALSE },
 	{ "tester@strongswan.org", "tester@strongswan.org", TRUE },
+	{ "tester@strongswan.org", "tester@strongSwan.org", TRUE },   /* email domain case */
+	{ "tester@strongswan.org", "TESTER@strongswan.org", TRUE },   /* email local-part case */
 	{ "tester@strongswan.org", "atester@strongswan.org", FALSE },
 	{ "email:strongswan.org", "tester@strongswan.org", TRUE },
+	{ "email:strongswan.org", "tester@strongSwan.org", TRUE },    /* email domain-only constraint */
 	{ "email:strongswan.org", "tester@test.strongswan.org", FALSE },
 	{ "email:.strongswan.org", "tester@test.strongswan.org", TRUE },
 	{ "email:.strongswan.org", "tester@strongswan.org", FALSE },
@@ -248,11 +253,11 @@ static struct {
 	char *subject;
 	bool good;
 } excluded_dn[] = {
-	{ "C=CH, O=another", "C=CH, O=strongSwan, CN=tester", TRUE },
-	{ "C=CH, O=another", "C=CH, O=anot", TRUE },
-	{ "C=CH, O=another", "C=CH, O=anot, CN=tester", TRUE },
+	{ "C=CH, O=another", "C=CH, O=strongSwan, CN=tester", FALSE },
+	{ "C=CH, O=another", "C=CH, O=anot", FALSE },
+	{ "C=CH, O=another", "C=CH, O=anot, CN=tester", FALSE },
 	{ "C=CH, O=another", "C=CH, O=another, CN=tester", FALSE },
-	{ "C=CH, O=another", "C=CH, CN=tester, O=another", TRUE },
+	{ "C=CH, O=another", "C=CH, CN=tester, O=another", FALSE },
 };
 
 START_TEST(test_excluded_dn)
@@ -281,7 +286,9 @@ static struct {
 } excluded_san[] = {
 	{ ".strongswan.org", "test.strongswan.org", FALSE },
 	{ "strongswan.org", "test.strongswan.org", FALSE },
+	{ "strongswan.org", "test.strongSwan.org", FALSE },
 	{ "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", FALSE },
+	{ "a.b.c.strongswan.org", "d.a.b.C.strongswan.org", FALSE },
 	{ "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", TRUE },
 	{ "strongswan.org", "strongswan.org.com", TRUE },
 	{ ".strongswan.org", "strongswan.org", TRUE },
@@ -289,8 +296,10 @@ static struct {
 	{ "strongswan.org", "swan.org", TRUE },
 	{ "strongswan.org", "swan.org", TRUE },
 	{ "tester@strongswan.org", "tester@strongswan.org", FALSE },
+	{ "tester@strongswan.org", "TESTER@strongswan.org", FALSE },   /* uppercase local-part must be caught */
 	{ "tester@strongswan.org", "atester@strongswan.org", TRUE },
 	{ "email:strongswan.org", "tester@strongswan.org", FALSE },
+	{ "email:strongswan.org", "tester@strongSwan.org", FALSE },    /* mixed-case domain must be caught */
 	{ "email:strongswan.org", "tester@test.strongswan.org", TRUE },
 	{ "email:.strongswan.org", "tester@test.strongswan.org", FALSE },
 	{ "email:.strongswan.org", "tester@strongswan.org", TRUE },
@@ -418,9 +427,9 @@ static struct {
 	char *subject;
 	bool good;
 } excluded_dn_levels[] = {
-	{ "C=CH, O=strongSwan", "C=CH", "C=DE", TRUE },
+	{ "C=CH, O=strongSwan", "C=CH", "C=DE", FALSE },
 	{ "C=CH, O=strongSwan", "C=CH", "C=CH", FALSE },
-	{ "C=CH, O=strongSwan", "C=DE", "C=CH", TRUE },
+	{ "C=CH, O=strongSwan", "C=DE", "C=CH", FALSE },
 	{ "C=CH, O=strongSwan", "C=DE", "C=DE", FALSE },
 	{ "C=CH, O=strongSwan", "C=DE", "C=CH, O=strongSwan", FALSE },
 	{ NULL, "C=CH", "C=CH, O=strongSwan", FALSE },
-- 
2.50.0

