From 9be087242758ce2a9dcebbdaee8f701c563209d9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 23 Mar 2026 17:45:11 +0100 Subject: [PATCH] constraints: Case-insensitive matching and reject excluded DN name constraints The case is generally ignored when matching 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. With directoryName (DN) name constraints the issue is a bit more comples. 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 --- .../constraints/constraints_validator.c | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libstrongswan/plugins/constraints/constraints_validator.c b/src/libstrongswan/plugins/constraints/constraints_validator.c index 62ccc710865d..569d3a11dc83 100644 --- a/src/libstrongswan/plugins/constraints/constraints_validator.c +++ b/src/libstrongswan/plugins/constraints/constraints_validator.c @@ -51,6 +51,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/RFC822 constraint matches (suffix match) */ @@ -61,7 +73,7 @@ static bool suffix_matches(identification_t *constraint, identification_t *id) c = constraint->get_encoding(constraint); i = id->get_encoding(id); - return i.len >= c.len && chunk_equals(c, chunk_skip(i, i.len - c.len)); + return i.len >= c.len && string_matches(c, chunk_skip(i, i.len - c.len)); } /** @@ -106,6 +118,13 @@ static bool name_constraint_matches(identification_t *constraint, type = constraint->get_type(constraint); if (type == ID_DER_ASN1_DN) { + if (!permitted) + { + DBG1(DBG_CFG, "excluded %N NameConstraint not supported", + id_type_names, type); + /* we have to return TRUE to let the constraint fail */ + return TRUE; + } matches = dn_matches(constraint, cert->get_subject(cert)); if (matches != permitted) { -- 2.43.0