This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/xkms-ldap in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 08e9208f99c98c6e0f9b192bb4a053a574853ef3 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Thu May 7 16:54:39 2026 +0100 Adding LDAP XKMS tests --- .../xkms/x509/repo/ldap/LdapCertificateRepo.java | 90 ++++++++- systests/ldap/pom.xml | 6 + .../systest/ldap/xkms/LDAPCertificateRepoTest.java | 215 +++++++++++++++++++++ 3 files changed, 304 insertions(+), 7 deletions(-) diff --git a/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java index a588e5cb9bd..a586d8d153e 100644 --- a/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java +++ b/services/xkms/xkms-x509-repo-ldap/src/main/java/org/apache/cxf/xkms/x509/repo/ldap/LdapCertificateRepo.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -200,7 +199,7 @@ public class LdapCertificateRepo implements CertificateRepo { if (cert == null) { // Try to find certificate by search for uid attribute try { - String filter = String.format(ldapConfig.getServiceCertUIDTemplate(), serviceName); + String filter = String.format(ldapConfig.getServiceCertUIDTemplate(), escapeFilterValue(serviceName)); Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); return getCert(attr); } catch (NamingException e) { @@ -213,7 +212,7 @@ public class LdapCertificateRepo implements CertificateRepo { @Override public X509Certificate findByEndpoint(String endpoint) { X509Certificate cert = null; - String filter = String.format("(%s=%s)", ldapConfig.getAttrEndpoint(), endpoint); + String filter = String.format("(%s=%s)", ldapConfig.getAttrEndpoint(), escapeFilterValue(endpoint)); try { Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); cert = getCert(attr); @@ -225,8 +224,50 @@ public class LdapCertificateRepo implements CertificateRepo { protected String getDnForIdentifier(String id) { - String escapedIdentifier = id.replaceAll("\\/", Matcher.quoteReplacement("\\/")); - return String.format(ldapConfig.getServiceCertRDNTemplate(), escapedIdentifier) + "," + rootDN; + return String.format(ldapConfig.getServiceCertRDNTemplate(), escapeDnValue(id)) + "," + rootDN; + } + + /** + * Escapes RFC4514 special characters in an LDAP DN attribute value, plus + * the JNDI composite-name separator '/' to prevent namespace traversal. + */ + protected String escapeDnValue(String value) { + if (value == null) { + return null; + } + StringBuilder escaped = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + // Leading space or '#' must be escaped per RFC4514 + if (i == 0 && (ch == ' ' || ch == '#')) { + escaped.append('\\').append(ch); + continue; + } + switch (ch) { + case '\\': + case ',': + case '+': + case '"': + case '<': + case '>': + case ';': + case '/': + escaped.append('\\').append(ch); + break; + case '\0': + escaped.append("\\00"); + break; + default: + escaped.append(ch); + break; + } + } + // Trailing space must be escaped per RFC4514 + int len = escaped.length(); + if (len > 1 && escaped.charAt(len - 1) == ' ' && escaped.charAt(len - 2) != '\\') { + escaped.insert(len - 1, '\\'); + } + return escaped.toString(); } protected X509Certificate getCertificateForDn(String dn) throws NamingException { @@ -235,7 +276,7 @@ public class LdapCertificateRepo implements CertificateRepo { } protected X509Certificate getCertificateForUIDAttr(String uid) throws NamingException { - String filter = String.format(filterUIDTemplate, uid); + String filter = String.format(filterUIDTemplate, escapeFilterValue(uid)); Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); return getCert(attr); } @@ -245,7 +286,8 @@ public class LdapCertificateRepo implements CertificateRepo { if (issuer == null || serial == null) { throw new IllegalArgumentException("Issuer and serial applications are expected in request"); } - String filter = String.format(filterIssuerSerialTemplate, issuer, serial); + String filter = String.format(filterIssuerSerialTemplate, escapeFilterValue(issuer), + escapeFilterValue(serial)); try { Attribute attr = ldapSearch.findAttribute(rootDN, filter, ldapConfig.getAttrCrtBinary()); return getCert(attr); @@ -254,6 +296,40 @@ public class LdapCertificateRepo implements CertificateRepo { } } + /** + * Escapes RFC4515 special characters in an LDAP search filter assertion value. + */ + protected String escapeFilterValue(String value) { + if (value == null) { + return null; + } + StringBuilder escaped = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + switch (ch) { + case '\\': + escaped.append("\\5c"); + break; + case '*': + escaped.append("\\2a"); + break; + case '(': + escaped.append("\\28"); + break; + case ')': + escaped.append("\\29"); + break; + case '\0': + escaped.append("\\00"); + break; + default: + escaped.append(ch); + break; + } + } + return escaped.toString(); + } + protected X509Certificate getCert(Attribute attr) { if (attr == null) { return null; diff --git a/systests/ldap/pom.xml b/systests/ldap/pom.xml index 5b4360a18f3..c5be77ded4c 100644 --- a/systests/ldap/pom.xml +++ b/systests/ldap/pom.xml @@ -162,6 +162,12 @@ <version>${project.version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.cxf.services.xkms</groupId> + <artifactId>cxf-services-xkms-x509-handlers</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.apache.cxf.services.xkms</groupId> <artifactId>cxf-services-xkms-x509-repo-ldap</artifactId> diff --git a/systests/ldap/src/test/java/org/apache/cxf/systest/ldap/xkms/LDAPCertificateRepoTest.java b/systests/ldap/src/test/java/org/apache/cxf/systest/ldap/xkms/LDAPCertificateRepoTest.java index 9b0d0d381de..a38085c000d 100644 --- a/systests/ldap/src/test/java/org/apache/cxf/systest/ldap/xkms/LDAPCertificateRepoTest.java +++ b/systests/ldap/src/test/java/org/apache/cxf/systest/ldap/xkms/LDAPCertificateRepoTest.java @@ -24,12 +24,18 @@ import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.UUID; import javax.naming.NamingException; import org.apache.cxf.testutil.common.AbstractClientServerTestBase; import org.apache.cxf.xkms.handlers.Applications; +import org.apache.cxf.xkms.handlers.XKMSConstants; +import org.apache.cxf.xkms.model.xkms.LocateRequestType; +import org.apache.cxf.xkms.model.xkms.QueryKeyBindingType; +import org.apache.cxf.xkms.model.xkms.UnverifiedKeyBindingType; import org.apache.cxf.xkms.model.xkms.UseKeyWithType; +import org.apache.cxf.xkms.x509.handlers.X509Locator; import org.apache.cxf.xkms.x509.repo.CertificateRepo; import org.apache.cxf.xkms.x509.repo.ldap.LdapCertificateRepo; import org.apache.cxf.xkms.x509.repo.ldap.LdapSchemaConfig; @@ -61,6 +67,8 @@ public class LDAPCertificateRepoTest { private static final String ROOT_DN = "dc=example,dc=com"; private static final String EXPECTED_SUBJECT_DN2 = "cn=newuser,ou=users"; private static final String EXPECTED_SERVICE_URI = "http://myservice.apache.org/MyServiceName"; + private static final String LOCATOR_SERVICE_URI = "http://x509locator.test/MyServiceName"; + private static final String LOCATOR_ENDPOINT_URI = "http://x509locator.test/endpoint"; @org.junit.AfterClass public static void cleanup() throws Exception { @@ -134,6 +142,213 @@ public class LDAPCertificateRepoTest { assertNotNull(foundCert); } + @Test + public void testX509LocatorFindBySubjectDn() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + X509Locator locator = new X509Locator(persistenceManager); + + LocateRequestType request = createLocateRequest(Applications.PKIX, EXPECTED_SUBJECT_DN); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNotNull(result); + assertNotNull(result.getKeyInfo()); + } + + @Test + public void testX509LocatorFindBySubjectDnLDAPInjection() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + X509Locator locator = new X509Locator(persistenceManager); + + LocateRequestType request = createLocateRequest(Applications.PKIX, "cn=*"); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNull(result); + } + + @Test + public void testX509LocatorReturnsNullForUnknownSubjectDn() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + X509Locator locator = new X509Locator(persistenceManager); + + LocateRequestType request = createLocateRequest(Applications.PKIX, "cn=nobody,ou=users"); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNull(result); + } + + @Test + public void testX509LocatorFindByServiceName() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert under a service name first + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.SERVICE_NAME.getUri()); + key.setIdentifier(LOCATOR_SERVICE_URI); + persistenceManager.saveCertificate(cert, key); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createLocateRequest(Applications.SERVICE_NAME, LOCATOR_SERVICE_URI); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNotNull(result); + assertNotNull(result.getKeyInfo()); + } + + @Test + public void testX509LocatorFindByServiceNameLDAPInjection() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert under a service name first + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.SERVICE_NAME.getUri()); + // Ensure this test can be rerun in the same JVM without DN collisions. + key.setIdentifier(LOCATOR_SERVICE_URI + "/" + UUID.randomUUID()); + persistenceManager.saveCertificate(cert, key); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createLocateRequest(Applications.SERVICE_NAME, "cn=*"); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNull(result); + } + + @Test + public void testX509LocatorFindByEndpoint() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert with a service endpoint attribute first + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.SERVICE_ENDPOINT.getUri()); + String identifier = LOCATOR_ENDPOINT_URI + UUID.randomUUID(); + key.setIdentifier(identifier); + persistenceManager.saveCertificate(cert, key); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createLocateRequest(Applications.SERVICE_ENDPOINT, identifier); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNotNull(result); + assertNotNull(result.getKeyInfo()); + } + + @Test + public void testX509LocatorFindByEndpointLDAPInjection() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert with a service endpoint attribute first + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.SERVICE_ENDPOINT.getUri()); + String identifier = LOCATOR_ENDPOINT_URI + UUID.randomUUID(); + key.setIdentifier(identifier); + persistenceManager.saveCertificate(cert, key); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createLocateRequest(Applications.SERVICE_ENDPOINT, "*"); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNull(result); + } + + @Test + public void testX509LocatorFindByIssuerSerial() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert so its issuer/serial attributes are written to LDAP + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.PKIX.getUri()); + key.setIdentifier("cn=issuerserialtest,ou=users"); + persistenceManager.saveCertificate(cert, key); + + String issuer = cert.getIssuerX500Principal().getName(); + String serial = cert.getSerialNumber().toString(16); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createIssuerSerialLocateRequest(issuer, serial); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNotNull(result); + assertNotNull(result.getKeyInfo()); + } + + @Test + public void testX509LocatorFindByIssuerSerialLDAPInjection() throws Exception { + CertificateRepo persistenceManager = createLdapCertificateRepo(); + + // Save a cert so its issuer/serial attributes are written to LDAP + URL url = this.getClass().getResource("cert1.cer"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + UseKeyWithType key = new UseKeyWithType(); + key.setApplication(Applications.PKIX.getUri()); + // Ensure this test does not collide with the non-injection issuer/serial test entry. + key.setIdentifier("cn=issuerserialtest-" + UUID.randomUUID() + ",ou=users"); + persistenceManager.saveCertificate(cert, key); + + String serial = cert.getSerialNumber().toString(16); + + X509Locator locator = new X509Locator(persistenceManager); + LocateRequestType request = createIssuerSerialLocateRequest("*", serial); + UnverifiedKeyBindingType result = locator.locate(request); + + assertNull(result); + } + + private LocateRequestType createIssuerSerialLocateRequest(String issuer, String serial) { + org.apache.cxf.xkms.model.xkms.ObjectFactory xkmsOf = + new org.apache.cxf.xkms.model.xkms.ObjectFactory(); + + UseKeyWithType issuerKey = xkmsOf.createUseKeyWithType(); + issuerKey.setApplication(Applications.ISSUER.getUri()); + issuerKey.setIdentifier(issuer); + + UseKeyWithType serialKey = xkmsOf.createUseKeyWithType(); + serialKey.setApplication(Applications.SERIAL.getUri()); + serialKey.setIdentifier(serial); + + QueryKeyBindingType queryKeyBinding = xkmsOf.createQueryKeyBindingType(); + queryKeyBinding.getUseKeyWith().add(issuerKey); + queryKeyBinding.getUseKeyWith().add(serialKey); + + LocateRequestType request = xkmsOf.createLocateRequestType(); + request.setQueryKeyBinding(queryKeyBinding); + request.setService(XKMSConstants.XKMS_ENDPOINT_NAME); + request.setId(UUID.randomUUID().toString()); + return request; + } + + private LocateRequestType createLocateRequest(Applications application, String identifier) { + org.apache.cxf.xkms.model.xkms.ObjectFactory xkmsOf = + new org.apache.cxf.xkms.model.xkms.ObjectFactory(); + + UseKeyWithType useKeyWithType = xkmsOf.createUseKeyWithType(); + useKeyWithType.setApplication(application.getUri()); + useKeyWithType.setIdentifier(identifier); + + QueryKeyBindingType queryKeyBinding = xkmsOf.createQueryKeyBindingType(); + queryKeyBinding.getUseKeyWith().add(useKeyWithType); + + LocateRequestType request = xkmsOf.createLocateRequestType(); + request.setQueryKeyBinding(queryKeyBinding); + request.setService(XKMSConstants.XKMS_ENDPOINT_NAME); + request.setId(UUID.randomUUID().toString()); + return request; + } + private CertificateRepo createLdapCertificateRepo() throws CertificateException { LdapSearch ldapSearch = new LdapSearch("ldap://localhost:" + embeddedLdapRule.embeddedServerPort(), "UID=admin,DC=example,DC=com", "ldap_su", 2);
