Commit c66143db48 for strongswan.org

commit c66143db48bab9eb82cc86190687938b809611eb
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Mar 23 17:45:11 2026 +0100

    constraints: Match FQDN and email addresses case-insensitively

    The case is generally ignored when matching such identities.  So this is
    an issue with excluded name constraints where a malicious intermediate
    CA could evade the constraints by issuing certificates with names that
    just modify the case (e.g. strongSwan.org instead strongswan.org).

    Note that it's likely that permitted name constraints are preferred over
    excluded name constraints as it might be difficult to come up with a
    conclusive list of names to exclude.

    Fixes: a2b340764fac ("Implemented NameConstraint matching in constraints plugin")
    Fixes: CVE-2026-35331

diff --git a/src/libstrongswan/plugins/constraints/constraints_validator.c b/src/libstrongswan/plugins/constraints/constraints_validator.c
index 51c10576c1..cf12ca4e76 100644
--- a/src/libstrongswan/plugins/constraints/constraints_validator.c
+++ b/src/libstrongswan/plugins/constraints/constraints_validator.c
@@ -55,6 +55,18 @@ static bool check_pathlen(x509_t *issuer, int pathlen)
 	return TRUE;
 }

+/**
+ * Check if the constraint and ID strings match case-insensitively
+ */
+static bool string_matches(chunk_t constraint, chunk_t id)
+{
+	/* make sure the two strings have actually the same length */
+	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 +82,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;
 	}
@@ -101,10 +113,10 @@ static bool email_matches(identification_t *constraint, identification_t *id)
 	}
 	if (memchr(c.ptr, '@', c.len))
 	{	/* constraint is a full email address */
-		return chunk_equals(c, i);
+		return string_matches(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;
 	}
diff --git a/src/libstrongswan/tests/suites/test_certnames.c b/src/libstrongswan/tests/suites/test_certnames.c
index 2549fb6e33..7773f8d476 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 },
 	{ "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 },
 	{ "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 },
+	{ "tester@strongswan.org", "TESTER@strongswan.org", TRUE },
 	{ "tester@strongswan.org", "atester@strongswan.org", FALSE },
 	{ "email:strongswan.org", "tester@strongswan.org", TRUE },
+	{ "email:strongswan.org", "tester@strongSwan.org", TRUE },
 	{ "email:strongswan.org", "tester@test.strongswan.org", FALSE },
 	{ "email:.strongswan.org", "tester@test.strongswan.org", TRUE },
 	{ "email:.strongswan.org", "tester@strongswan.org", FALSE },
@@ -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 },
 	{ "tester@strongswan.org", "atester@strongswan.org", TRUE },
 	{ "email:strongswan.org", "tester@strongswan.org", FALSE },
+	{ "email:strongswan.org", "tester@strongSwan.org", FALSE },
 	{ "email:strongswan.org", "tester@test.strongswan.org", TRUE },
 	{ "email:.strongswan.org", "tester@test.strongswan.org", FALSE },
 	{ "email:.strongswan.org", "tester@strongswan.org", TRUE },