-
Bug
-
Resolution: Duplicate
-
P3
-
None
-
8u51, 9
-
x86_64
-
linux
FULL PRODUCT VERSION :
A DESCRIPTION OF THE PROBLEM :
When an "ldaps" provider url is set in the environment, a connection timeout > 0 is specified in the environment and a custom SocketFactory, which sets the SSLSocket endpoint identification algorithm to "LDAPS", is set in the ldap environment hostname because of the way the fix for JDK-806769 was implemented some clients encounter a CertificateException. This occurs because the code inside the com.sun.jndi.ldap.Connection class's createSocket method[0] "prefers" to invoke socketFactory.createSocket() instead of socketFactory.createSocket(String host, int port) when a connection timeout > 0 is specified.
[0] http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/7d1d5f4d019a/src/share/classes/com/sun/jndi/ldap/Connection.java#l272
REGRESSION. Last worked in version 8u60
ADDITIONAL REGRESSION INFORMATION:
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. git clone https://bitbucket.org/atlassian/cwd-4444-java-bug-reproducer.git
2. build the code - `cd src/main/java && javac Broken.java Main.java Working.java`
3. run the Main class and provide an ldaps url - `java Main ldaps://example.java:10636`
4. Observe that when the "Broken" SocketFactory is in use a hostname verification error occurs.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No hostname verification error occurs when the remote ldaps resource has the correct hostname and has a certificate that is considered to be trusted.
ACTUAL -
A hostname verification error occurs when the remote ldaps resource has the correct hostname and has a certificate that is considered to be trusted.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
... 39 common frames omitted
Caused by: javax.naming.CommunicationException: <DNS.NAME>:636
at com.sun.jndi.ldap.Connection.<init>(Unknown Source) ~[na:1.8.0_51]
at com.sun.jndi.ldap.LdapClient.<init>(Unknown Source) ~[na:1.8.0_51]
...
... 43 common frames omitted
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address XXXXX found
at sun.security.ssl.Alerts.getSSLException(Unknown Source) ~[na:1.8.0_51]
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source) ~[na:1.8.0_51]
...
... 58 common frames omitted
Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address XXXXX found
at sun.security.util.HostnameChecker.matchIP(Unknown Source) ~[na:1.8.0_51]
...
... 67 common frames omitted
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Main.java:
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class Main
{
// args[0] should look like "ldaps://example.com:10636/o=example"
public static void main(String[] args) throws NamingException
{
ldapsConnect("Working", args[0]);
System.out.println("Working worked :)");
ldapsConnect("Broken", args[0]);
System.out.println("Broken worked :(");
}
static void ldapsConnect(String classname, String ldapsUrl) throws NamingException
{
Hashtable<String, Object> environment = new Hashtable<String, Object>();
environment.put(Context.SECURITY_PROTOCOL, "ssl");
environment.put("java.naming.ldap.factory.socket", classname);
environment.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapsUrl);
environment.put("com.sun.jndi.ldap.connect.timeout", "5000");
DirContext ctx = new InitialDirContext(environment);
ctx.lookup("");
}
}
----
Broken.java:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class Broken extends SSLSocketFactory
{
SSLSocketFactory sf;
public Broken() throws NoSuchAlgorithmException
{
sf = SSLContext.getDefault().getSocketFactory();
}
public static SSLSocketFactory getDefault()
{
try
{
return new Broken();
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
}
@Override
public String[] getDefaultCipherSuites()
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public String[] getSupportedCipherSuites()
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket() throws IOException {
SSLSocket s = (SSLSocket) sf.createSocket();
SSLParameters param = s.getSSLParameters();
param.setEndpointIdentificationAlgorithm("LDAPS");
s.setSSLParameters(param);
return s;
}
}
----
Working.java:
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class Working
{
SSLSocketFactory sf;
public Working() throws NoSuchAlgorithmException
{
sf = SSLContext.getDefault().getSocketFactory();
}
public static Object getDefault()
{
try
{
return new Working();
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
SSLSocket s = (SSLSocket) sf.createSocket(host, port);
SSLParameters param = s.getSSLParameters();
param.setEndpointIdentificationAlgorithm("LDAPS");
s.setSSLParameters(param);
return s;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Create and use a "SocketFactory" class that only implements the `getDefault()` and `createSocket(String host, int port)` methods.
A DESCRIPTION OF THE PROBLEM :
When an "ldaps" provider url is set in the environment, a connection timeout > 0 is specified in the environment and a custom SocketFactory, which sets the SSLSocket endpoint identification algorithm to "LDAPS", is set in the ldap environment hostname because of the way the fix for JDK-806769 was implemented some clients encounter a CertificateException. This occurs because the code inside the com.sun.jndi.ldap.Connection class's createSocket method[0] "prefers" to invoke socketFactory.createSocket() instead of socketFactory.createSocket(String host, int port) when a connection timeout > 0 is specified.
[0] http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/7d1d5f4d019a/src/share/classes/com/sun/jndi/ldap/Connection.java#l272
REGRESSION. Last worked in version 8u60
ADDITIONAL REGRESSION INFORMATION:
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. git clone https://bitbucket.org/atlassian/cwd-4444-java-bug-reproducer.git
2. build the code - `cd src/main/java && javac Broken.java Main.java Working.java`
3. run the Main class and provide an ldaps url - `java Main ldaps://example.java:10636`
4. Observe that when the "Broken" SocketFactory is in use a hostname verification error occurs.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No hostname verification error occurs when the remote ldaps resource has the correct hostname and has a certificate that is considered to be trusted.
ACTUAL -
A hostname verification error occurs when the remote ldaps resource has the correct hostname and has a certificate that is considered to be trusted.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
... 39 common frames omitted
Caused by: javax.naming.CommunicationException: <DNS.NAME>:636
at com.sun.jndi.ldap.Connection.<init>(Unknown Source) ~[na:1.8.0_51]
at com.sun.jndi.ldap.LdapClient.<init>(Unknown Source) ~[na:1.8.0_51]
...
... 43 common frames omitted
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address XXXXX found
at sun.security.ssl.Alerts.getSSLException(Unknown Source) ~[na:1.8.0_51]
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source) ~[na:1.8.0_51]
...
... 58 common frames omitted
Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address XXXXX found
at sun.security.util.HostnameChecker.matchIP(Unknown Source) ~[na:1.8.0_51]
...
... 67 common frames omitted
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Main.java:
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class Main
{
// args[0] should look like "ldaps://example.com:10636/o=example"
public static void main(String[] args) throws NamingException
{
ldapsConnect("Working", args[0]);
System.out.println("Working worked :)");
ldapsConnect("Broken", args[0]);
System.out.println("Broken worked :(");
}
static void ldapsConnect(String classname, String ldapsUrl) throws NamingException
{
Hashtable<String, Object> environment = new Hashtable<String, Object>();
environment.put(Context.SECURITY_PROTOCOL, "ssl");
environment.put("java.naming.ldap.factory.socket", classname);
environment.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapsUrl);
environment.put("com.sun.jndi.ldap.connect.timeout", "5000");
DirContext ctx = new InitialDirContext(environment);
ctx.lookup("");
}
}
----
Broken.java:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class Broken extends SSLSocketFactory
{
SSLSocketFactory sf;
public Broken() throws NoSuchAlgorithmException
{
sf = SSLContext.getDefault().getSocketFactory();
}
public static SSLSocketFactory getDefault()
{
try
{
return new Broken();
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
}
@Override
public String[] getDefaultCipherSuites()
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public String[] getSupportedCipherSuites()
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException
{
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Socket createSocket() throws IOException {
SSLSocket s = (SSLSocket) sf.createSocket();
SSLParameters param = s.getSSLParameters();
param.setEndpointIdentificationAlgorithm("LDAPS");
s.setSSLParameters(param);
return s;
}
}
----
Working.java:
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class Working
{
SSLSocketFactory sf;
public Working() throws NoSuchAlgorithmException
{
sf = SSLContext.getDefault().getSocketFactory();
}
public static Object getDefault()
{
try
{
return new Working();
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
SSLSocket s = (SSLSocket) sf.createSocket(host, port);
SSLParameters param = s.getSSLParameters();
param.setEndpointIdentificationAlgorithm("LDAPS");
s.setSSLParameters(param);
return s;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Create and use a "SocketFactory" class that only implements the `getDefault()` and `createSocket(String host, int port)` methods.