Commit 64130ede5c for strongswan.org

commit 64130ede5cd8f61edd35a1b488c874fa328a42b0
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Mar 23 18:02:19 2026 +0100

    constraints: Reject excluded directoryName (DN) name constraints

    There is an issue similar to the one fixed with the previous commit when
    using directoryName (DN) name constraints.  Some RDNs have to be matched
    in a case-insensitive manner, which we e.g. do in
    `identification.c::rdn_equals`.  By not doing it for name constraints,
    a malicious intermediate CA could evade an excluded name constraint
    just by modifying the case in such an RDN.

    While we could use the mentioned function in `dn_matches`, this doesn't
    properly fix the problem because the function is basically too strict.
    Especially in regards to RDNs of type UTF8String, which are only compared
    binary.  To match these properly, we'd have to implement the string
    preparation described in RFC 5280, section 7.1 and the referenced RFCs.
    Until that's the case, we reject excluded name constraints of type
    directoryName as we are unable to enforce them.

    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 cf12ca4e76..1f55d115b5 100644
--- a/src/libstrongswan/plugins/constraints/constraints_validator.c
+++ b/src/libstrongswan/plugins/constraints/constraints_validator.c
@@ -401,9 +401,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 7773f8d476..14570eedf7 100644
--- a/src/libstrongswan/tests/suites/test_certnames.c
+++ b/src/libstrongswan/tests/suites/test_certnames.c
@@ -253,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)
@@ -427,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 },