BlindSSLSocketFactory实现LDAPS证书绕过在生产环境失效求助
javax.naming.CommunicationException After Packaging Environment
- Java Version: Amazon Corretto 1.8.0_275
- Requirement: Bypass LDAPS certificate check during authentication, redirect to homepage after successful login.
Issue Summary
I've implemented a BlindSSLSocketFactory to bypass LDAPS certificate validation and injected it into LDAP query properties. When running the application in Eclipse, the certificate check is successfully bypassed and users can log in normally (no signed certificates in Java truststore). However, after compiling and packaging the project into an installer, I get the following javax.naming.CommunicationException: simple bind failed error. I've confirmed that the system property -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true is set.
Error Stack Trace
javax.naming.CommunicationException: simple bind failed: 10.148.129.11:636 at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:219) ~[na:1.8.0_275] at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2897) ~[na:1.8.0_275] ... Caused by: java.net.SocketException: Connection or outbound has closed at sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:967) ~[na:1.8.0_275] ... 88 common frames omitted
Relevant Code
BlindSSLSocketFactory Implementation
public class BlindSSLSocketFactory extends SocketFactory { private static SocketFactory blindFactory = null; static { TrustManager[] blindTrustMan = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] c, String a) { } public void checkServerTrusted(X509Certificate[] c, String a) { } } }; try { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, blindTrustMan, new java.security.SecureRandom()); blindFactory = sc.getSocketFactory(); } catch (GeneralSecurityException e) { e.printStackTrace(); } } public static SocketFactory getDefault() { return new BlindSSLSocketFactory(); } public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException { return blindFactory.createSocket(arg0, arg1); } public Socket createSocket(InetAddress arg0, int arg1) throws IOException { return blindFactory.createSocket(arg0, arg1); } public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException { return blindFactory.createSocket(arg0, arg1, arg2, arg3); } public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException { return blindFactory.createSocket(arg0, arg1, arg2, arg3); } }
LdapAuthentication Component
@Component public class LdapAuthentication { private final Logger logger = LoggerFactory.getLogger(getClass()); private String username; private String password; private boolean isLoggedIn; public void startLoginSession(String username, String password) throws NamingException { this.username = username; this.password = password; this.isLoggedIn = createLoginSession(this.username, this.password); } private boolean createLoginSession(String username, String password) throws NamingException { String ldapServerUrl = buildLdapPrefix() + SettingsResolver.getInstance().getSetting("ldap.server.address") + ":" + SettingsResolver.getInstance().getSetting("ldap.server.port.number"); Properties props = new Properties(); props.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName()); props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); props.put(Context.PROVIDER_URL, ldapServerUrl); props.put(Context.SECURITY_PRINCIPAL, username); props.put(Context.SECURITY_CREDENTIALS, password); InitialDirContext context = null; try { context = new InitialDirContext(props); SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration<SearchResult> results = context.search(toDC(SettingsResolver.getInstance().getSetting("ldap.server.domain.name")), String.format("(& (userPrincipalName=%s)(objectClass=user))", this.username), controls); return results.hasMore(); } catch (NamingException namingException) { logger.error("Exception occurred while authenticating to LDAP Server: ", namingException); throw namingException; } finally { try { if (context != null) context.close(); } catch (Exception ex) { } } } private static String toDC(String username) { String result = ""; String[] parts = username.split("\\."); for (int index = 0; index < parts.length - 1; index++) result = result.concat("DC=").concat(parts[index]).concat(","); return result.concat("DC=").concat(parts[parts.length - 1]); } public boolean isUserLoggedIn() { return this.isLoggedIn; } private String buildLdapPrefix() { String securePortEnabled = SettingsResolver.getInstance().getSetting("ldap.server.secure.port.enabled"); return securePortEnabled.contains("true") ? "ldaps://" : "ldap://"; } }
Request
Please help troubleshoot why the certificate bypass fails in the production/packaged environment and provide solutions.
I’ve run into nearly identical issues with LDAPS certificate bypass in packaged Java applications, so let’s break down the most likely causes and fixes based on your setup:
1. Class Loading Issues with Custom SocketFactory
The BlindSSLSocketFactory might not be loading correctly in the packaged environment, which breaks the SSL handshake before even reaching the certificate check step. Here’s why and how to fix it:
- Problem: JNDI uses the system classloader to load the socket factory specified in
java.naming.ldap.factory.socket. If your factory class is packaged inside your application’s JAR (not in the system classpath), the system classloader might not find it. Also, your static initializer only prints exceptions to the console—if the SSLContext initialization fails (e.g., missing security providers), you won’t see the error in packaged logs. - Fix:
- Replace the custom socket factory approach with a global SSLContext override. This avoids classloader conflicts entirely:
// Add this static block to your application's startup class (e.g., main class or LdapAuthentication) static { try { TrustManager[] blindTrustMan = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] c, String a) {} public void checkServerTrusted(X509Certificate[] c, String a) {} } }; // Use TLS instead of SSL for better compatibility with modern servers SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, blindTrustMan, new SecureRandom()); SSLContext.setDefault(sc); // Disable hostname verification to match your bypass intent HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } catch (GeneralSecurityException e) { // Throw a runtime exception here so you'll catch initialization failures immediately throw new RuntimeException("Failed to initialize blind SSL context", e); } } - Remove the
props.put("java.naming.ldap.factory.socket", ...)line from yourLdapAuthenticationcode—JNDI will now use the default SSLContext you’ve configured.
- Replace the custom socket factory approach with a global SSLContext override. This avoids classloader conflicts entirely:
2. Missing System Property in Packaged Runtime
Even if you set -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true during development, the packaged installer might not pass this property to the JVM.
- Fix:
- Add the property directly in code at application startup (before any LDAP calls):
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); - Verify that your installer’s startup script/configuration includes this JVM argument. For example, if using an EXE installer, check the shortcut’s target or the JVM parameters section in the installer config.
- Add the property directly in code at application startup (before any LDAP calls):
3. SSL/TLS Protocol Version Mismatch
Amazon Corretto 8u275 defaults to TLS 1.2, but some older LDAP servers only support TLS 1.0 or 1.1. If the server and client can’t agree on a protocol, the SSL handshake fails, leading to a closed connection.
- Fix:
- Specify compatible protocols when initializing the SSLContext:
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // Or "TLSv1" if your server only supports older versions - Alternatively, set system properties to enable multiple protocols:
System.setProperty("jdk.tls.client.protocols", "TLSv1,TLSv1.1,TLSv1.2"); System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
- Specify compatible protocols when initializing the SSLContext:
4. Network/Firewall Restrictions
Don’t overlook basic network issues! The packaged environment might have different firewall rules blocking port 636, or the LDAP server’s IP isn’t reachable from that network.
- Fix:
- Test connectivity from the packaged environment using
telnet 10.148.129.11 636oropenssl s_client -connect 10.148.129.11:636to see if the connection can be established. - Work with your infrastructure team to ensure the client IP is allowed to access the LDAP server’s 636 port.
- Test connectivity from the packaged environment using
Verification Steps
- Add detailed logging to track SSLContext initialization and LDAP connection parameters in the packaged environment.
- Test the global SSLContext approach first—this eliminates most classloader-related issues.
- Confirm all required system properties are set either in code or the JVM startup arguments.
内容的提问来源于stack exchange,提问作者Ahmet Eroğlu

