SSLSocketFactory in Java, LDAP network connection - java

My question is similar to: SSLSocketFactory in java
I need to set a custom SSLSocketFactory...except I do NOT have an https connection (it's LDAPS), so can't use:
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
...to set the SSLSocketFactory. I have an SSLContext object initialized but when I make the LDAP connection the default SSLContext is called automatically since my custom one is not set:
dirContext = new InitialDirContext(env); // <-- reverts to default ssl context
Is there a non-HTTPS equivalent method to line #3 below:
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(myKeyManagerFactory.getKeyManagers(), myTrustManagerArray, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

Yes, there is.
env.put("java.naming.ldap.factory.socket", UnsecuredSSLSocketFactory.class.getName());
UnsecuredSSLSocketFactory.java:
public class UnsecuredSSLSocketFactory extends SSLSocketFactory
{
private SSLSocketFactory socketFactory;
public UnsecuredSSLSocketFactory()
{
try
{
var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager()
{
#Override
public void checkClientTrusted(X509Certificate[] xcs, String string){}
#Override
public void checkServerTrusted(X509Certificate[] xcs, String string){}
#Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}}, new SecureRandom());
socketFactory = sslContext.getSocketFactory();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
#SuppressWarnings("unused")
public static SocketFactory getDefault()
{
return new UnsecuredSSLSocketFactory();
}
#Override
public String[] getDefaultCipherSuites()
{
return socketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites()
{
return socketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException
{
return socketFactory.createSocket(socket, string, i, bln);
}
#Override
public Socket createSocket(String string, int i) throws IOException
{
return socketFactory.createSocket(string, i);
}
#Override
public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException
{
return socketFactory.createSocket(string, i, ia, i1);
}
#Override
public Socket createSocket(InetAddress ia, int i) throws IOException
{
return socketFactory.createSocket(ia, i);
}
#Override
public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException
{
return socketFactory.createSocket(ia, i, ia1, i1);
}
#Override
public Socket createSocket() throws IOException
{
return socketFactory.createSocket();
}
}

Note, if the issue is just a hostname mismatch (which is super common in clustered Active Directory Environments), you can just set the system property com.sun.jndi.ldap.object.disableEndpointIdentification to true, so as a command line arg -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true
Note this will only ignore a hostname mismatch on the certificate, you will still need to have a trust chain from ldap's cert to something in your truststore, but this seems to be the most common issue people have with SSL, LDAP and Active Directory, as the certificate's the domain generate for each domain controller don't include a subject alternate name for the domain itself, so if you follow the standard example of just pointing ldap to yourcomapanydomain.com, when it resolves to domaincontroller1.yourcompanydomain.com you get a failure. Note, if you are upgrading from an old java version, this behavior changed in https://www.oracle.com/java/technologies/javase/8u181-relnotes.html

Related

Your app is using an unsafe implementation of the X509TrustManager interface with an Apache HTTP client, resulting in a security vulnerability

i had a code which app can access https, just like this :
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
chain[0].checkValidity();
} catch (Exception e) {
throw new CertificateException("Certificate not valid or trusted.");
}
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
after that, google play console give me a notice :
Apache Trust Manager
Your app is using an unsafe implementation of the X509TrustManager interface with an Apache HTTP client, resulting in a security vulnerability. Please see this Google Help Center article for details, including the deadline for fixing the vulnerability.
Insecure Hostname Verifier
Your app is using an unsafe implementation of HostnameVerifier. Please see this Google Help Center article for details, including the deadline for fixing the vulnerability.
is there something wrong with my code ??

javax.net.ssl.SSLException: hostname in certificate didn't match

My Android app tells me that my https certificate doesn't match the hostname:
javax.net.ssl.SSLException: hostname in certificate didn't match: <hostname1> != <oldhostname>
What is odd is that
The website (hostname1) gives the correct certificate (checked with browsers and the ssllabs tool)
oldhostname is the previous hostname I had set up in previous versions of the app
Is there some kind of cache for certificates? I cant't find any info on that
Add this class
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(
X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(
X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context != null ? context.getSocketFactory() : null);
}
}
and call it from your MainActivity with HttpsTrustManager.allowAllSSL();
Although it's not save approach but i solve my problem with this.

How to disable SSLv3 in android for HttpsUrlConnection?

We wrote client application in android which connects with https servers using HttpsUrlConnection apis. Due to Poodle vulnerability, we need to disable SSLv3 from the list of enabled protocols while invoking any request.
We followed the guidelines captured by oracle
and added following line before invoking url connection
java.lang.System.setProperty("https.protocols", "TLSv1");
This solution works fine with normal java program.
We got SSLHandShakeException when tried to connect with a server which only works on SSLv3 protocol.
But concern is : same fix does not work for android. Am I missing something or should I try another approach for android? Please suggest.
I found the solution for it by analyzing the data packets using wireshark. What I found is that while making a secure connection, android was falling back to SSLv3 from TLSv1 . It is a bug in android versions < 4.4 , and it can be solved by removing the SSLv3 protocol from Enabled Protocols list. I made a custom socketFactory class called NoSSLv3SocketFactory.java. Use this to make a socketfactory.
/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory{
private final SSLSocketFactory delegate;
public NoSSLv3SocketFactory() {
this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}
public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket makeSocketSafe(Socket socket) {
if (socket instanceof SSLSocket) {
socket = new NoSSLv3SSLSocket((SSLSocket) socket);
}
return socket;
}
#Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose));
}
#Override
public Socket createSocket(String host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort));
}
#Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
}
private class NoSSLv3SSLSocket extends DelegateSSLSocket {
private NoSSLv3SSLSocket(SSLSocket delegate) {
super(delegate);
}
#Override
public void setEnabledProtocols(String[] protocols) {
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
if (enabledProtocols.size() > 1) {
enabledProtocols.remove("SSLv3");
System.out.println("Removed SSLv3 from enabled protocols");
} else {
System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
}
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
}
super.setEnabledProtocols(protocols);
}
}
public class DelegateSSLSocket extends SSLSocket {
protected final SSLSocket delegate;
DelegateSSLSocket(SSLSocket delegate) {
this.delegate = delegate;
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public String[] getEnabledCipherSuites() {
return delegate.getEnabledCipherSuites();
}
#Override
public void setEnabledCipherSuites(String[] suites) {
delegate.setEnabledCipherSuites(suites);
}
#Override
public String[] getSupportedProtocols() {
return delegate.getSupportedProtocols();
}
#Override
public String[] getEnabledProtocols() {
return delegate.getEnabledProtocols();
}
#Override
public void setEnabledProtocols(String[] protocols) {
delegate.setEnabledProtocols(protocols);
}
#Override
public SSLSession getSession() {
return delegate.getSession();
}
#Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.addHandshakeCompletedListener(listener);
}
#Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.removeHandshakeCompletedListener(listener);
}
#Override
public void startHandshake() throws IOException {
delegate.startHandshake();
}
#Override
public void setUseClientMode(boolean mode) {
delegate.setUseClientMode(mode);
}
#Override
public boolean getUseClientMode() {
return delegate.getUseClientMode();
}
#Override
public void setNeedClientAuth(boolean need) {
delegate.setNeedClientAuth(need);
}
#Override
public void setWantClientAuth(boolean want) {
delegate.setWantClientAuth(want);
}
#Override
public boolean getNeedClientAuth() {
return delegate.getNeedClientAuth();
}
#Override
public boolean getWantClientAuth() {
return delegate.getWantClientAuth();
}
#Override
public void setEnableSessionCreation(boolean flag) {
delegate.setEnableSessionCreation(flag);
}
#Override
public boolean getEnableSessionCreation() {
return delegate.getEnableSessionCreation();
}
#Override
public void bind(SocketAddress localAddr) throws IOException {
delegate.bind(localAddr);
}
#Override
public synchronized void close() throws IOException {
delegate.close();
}
#Override
public void connect(SocketAddress remoteAddr) throws IOException {
delegate.connect(remoteAddr);
}
#Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
delegate.connect(remoteAddr, timeout);
}
#Override
public SocketChannel getChannel() {
return delegate.getChannel();
}
#Override
public InetAddress getInetAddress() {
return delegate.getInetAddress();
}
#Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
#Override
public boolean getKeepAlive() throws SocketException {
return delegate.getKeepAlive();
}
#Override
public InetAddress getLocalAddress() {
return delegate.getLocalAddress();
}
#Override
public int getLocalPort() {
return delegate.getLocalPort();
}
#Override
public SocketAddress getLocalSocketAddress() {
return delegate.getLocalSocketAddress();
}
#Override
public boolean getOOBInline() throws SocketException {
return delegate.getOOBInline();
}
#Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
#Override
public int getPort() {
return delegate.getPort();
}
#Override
public synchronized int getReceiveBufferSize() throws SocketException {
return delegate.getReceiveBufferSize();
}
#Override
public SocketAddress getRemoteSocketAddress() {
return delegate.getRemoteSocketAddress();
}
#Override
public boolean getReuseAddress() throws SocketException {
return delegate.getReuseAddress();
}
#Override
public synchronized int getSendBufferSize() throws SocketException {
return delegate.getSendBufferSize();
}
#Override
public int getSoLinger() throws SocketException {
return delegate.getSoLinger();
}
#Override
public synchronized int getSoTimeout() throws SocketException {
return delegate.getSoTimeout();
}
#Override
public boolean getTcpNoDelay() throws SocketException {
return delegate.getTcpNoDelay();
}
#Override
public int getTrafficClass() throws SocketException {
return delegate.getTrafficClass();
}
#Override
public boolean isBound() {
return delegate.isBound();
}
#Override
public boolean isClosed() {
return delegate.isClosed();
}
#Override
public boolean isConnected() {
return delegate.isConnected();
}
#Override
public boolean isInputShutdown() {
return delegate.isInputShutdown();
}
#Override
public boolean isOutputShutdown() {
return delegate.isOutputShutdown();
}
#Override
public void sendUrgentData(int value) throws IOException {
delegate.sendUrgentData(value);
}
#Override
public void setKeepAlive(boolean keepAlive) throws SocketException {
delegate.setKeepAlive(keepAlive);
}
#Override
public void setOOBInline(boolean oobinline) throws SocketException {
delegate.setOOBInline(oobinline);
}
#Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
}
#Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
delegate.setReceiveBufferSize(size);
}
#Override
public void setReuseAddress(boolean reuse) throws SocketException {
delegate.setReuseAddress(reuse);
}
#Override
public synchronized void setSendBufferSize(int size) throws SocketException {
delegate.setSendBufferSize(size);
}
#Override
public void setSoLinger(boolean on, int timeout) throws SocketException {
delegate.setSoLinger(on, timeout);
}
#Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
delegate.setSoTimeout(timeout);
}
#Override
public void setTcpNoDelay(boolean on) throws SocketException {
delegate.setTcpNoDelay(on);
}
#Override
public void setTrafficClass(int value) throws SocketException {
delegate.setTrafficClass(value);
}
#Override
public void shutdownInput() throws IOException {
delegate.shutdownInput();
}
#Override
public void shutdownOutput() throws IOException {
delegate.shutdownOutput();
}
#Override
public String toString() {
return delegate.toString();
}
#Override
public boolean equals(Object o) {
return delegate.equals(o);
}
}
}
Use this class like this while connecting :
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null,
null,
null);
SSLSocketFactory NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
l_connection = (HttpsURLConnection) l_url.openConnection();
l_connection.connect();
UPDATE :
Now, correct solution would be to install a newer security provider using Google Play Services:
ProviderInstaller.installIfNeeded(getApplicationContext());
This effectively gives your app access to a newer version of OpenSSL and Java Security Provider, which includes support for TLSv1.2 in SSLEngine. Once the new provider is installed, you can create an SSLEngine which supports SSLv3, TLSv1, TLSv1.1 and TLSv1.2 the usual way:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLEngine engine = sslContext.createSSLEngine();
Or you can restrict the enabled protocols using engine.setEnabledProtocols.
Don't forget to add the following dependency (latest version found here):
compile 'com.google.android.gms:play-services-auth:11.8.0'
For more info, checkout this link.
Inspired by Bhavit S. Sengar's answer, it bundled that technique into a dead simple method call. You can use the NetCipher library to get a modern TLS config when using Android's HttpsURLConnection. NetCipher configures the HttpsURLConnection instance to use the best supported TLS version, removes SSLv3 support, and configures the best suite of ciphers for that TLS version. First, add it to your build.gradle:
compile 'info.guardianproject.netcipher:netcipher:1.2'
Or you can download the netcipher-1.2.jar and include it directly in your app. Then instead of calling:
HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();
Call this:
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
At first I tried Bhavit S. Sengar's answer and it worked for most cases. But sometimes there where issues even when SSLv3 protocol was removed from Enabled Protocols on an Android 4.4.4 device. So the NetCipher library by Hans-Christoph Steiner is perfect to solve that problem as far as I could test it.
We use jsoup to make a bunch of web scraping on different servers, so we cannot set HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);. I assume that's the same problem if you use OkHttp.
The best solution we've come to is to set the info.guardianproject.netcipher.client.TlsOnlySocketFactory from NetCipher as DefaultSSLSocketFactory in a static block. So it's set for the whole runtime of our app:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);
If you like to inspect the full details (with trustAllCertificates) you can do it here.
use this code snippet, if server is SSLv3 enable then it will fail handshaking.
SocketFactory sf = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443);
socket.setEnabledProtocols(new String[] { "TLSv1"});
socket.startHandshake();
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, null, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
httpURLConnection.setSSLSocketFactory(socketFactory);
HttpsURLConnection using TSL create a security failed, the Android implementation will fall back to SSLV3 to connection.
Please refer this http://code.google.com/p/android/issues/detail?id=78431
Using PlayService publisher client libraries running on Android I experienced the same problem when running the sample.
Fixed it with #bhavit-s-sengar's awnser above. Had to also change AndroidPublisherHelper.newTrustedTransport() to this:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
// NoSSLv3SocketFactory is #bhavit-s-sengar's http://stackoverflow.com/a/29946540/8524
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder();
netTransportBuilder.setSslSocketFactory(noSSLv3Factory);
HTTP_TRANSPORT = netTransportBuilder.build();
Connects with https server we need certificate in handshaking from client side. 1 year back I solved a similar issue using self sign certificate in the following way-
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
Usage in client side before HttpsUrlConnection
HttpsTrustManager.allowAllSSL();
hopefully it will work :)
Actually we don't need to disable the SSLV3 or TLSV1.0, What we just need to enable TLSV1.1 or TLSv1.2 in android < 5 devices.
The problem is TLSv1.1 and TLSv1.2 not enabled on Android <5 by default and to connect using these latest secure protocol we must have to enable in Android <5 devices.
This solution fixed my problem : https://stackoverflow.com/a/45853669/3448003

Grizzly 2 Embedded Https Server?

I'm trying to get a simple https embedded server running. This is for a test prototype, so real security isn't important at this point and fake or omitted certificates are fine, but my situation requires https vs regular http.
This code seems to call the right APIs, it runs, but the client browser gets "SSL Connection Error":
public class SimpleHttps {
public static final String SERVER_NAME = "https://localhost:8090";
public static final URI BASE_URI = URI.create(SERVER_NAME);
private static class TrustAllCerts implements X509TrustManager {
#Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
#Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
#Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
private final static TrustManager[] trustAllCerts = new TrustManager[] { new TrustAllCerts() };
public static SSLEngineConfigurator getSslEngineConfig() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(sc, false, false, false);
return sslEngineConfigurator;
}
public static void main(String[] args) throws Exception {
final ResourceConfig rc = new ResourceConfig().register(MyResource.class);
HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, rc, true, getSslEngineConfig());
System.in.read();
httpServer.stop();
}
}

jndi LDAPS custom HostnameVerifier and TrustManager

We are writing an application that shall connect to different LDAP servers. For each server we may only accept a certain certificate. The hostname in that certificate shall not matter. This is easy, when we use LDAP and STARTTLS, because we can use StartTlsResponse.setHostnameVerifier(..-) and use StartTlsResponse.negotiate(...) with a matching SSLSocketFactory. However we also need to support LDAPS connections. Java supports this natively, but only if the server certificate is trusted by the default java keystore. While we could replace that, we still cannot use different keystores for different servers.
The existing connection code is as follows:
Hashtable<String,String> env = new Hashtable<String,String>();
env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
env.put( Context.PROVIDER_URL, ( encryption == SSL ? "ldaps://" : "ldap://" ) + host + ":" + port );
if ( encryption == SSL ) {
// env.put( "java.naming.ldap.factory.socket", "CustomSocketFactory" );
}
ctx = new InitialLdapContext( env, null );
if ( encryption != START_TLS )
tls = null;
else {
tls = (StartTlsResponse) ctx.extendedOperation( new StartTlsRequest() );
tls.setHostnameVerifier( hostnameVerifier );
tls.negotiate( sslContext.getSocketFactory() );
}
We could add out own CustomSocketFactory, but how to pass information to that?
For others have the same problem: I found a very ugly solution for my case:
import javax.net.SocketFactory;
public abstract class ThreadLocalSocketFactory
extends SocketFactory
{
static ThreadLocal<SocketFactory> local = new ThreadLocal<SocketFactory>();
public static SocketFactory getDefault()
{
SocketFactory result = local.get();
if ( result == null )
throw new IllegalStateException();
return result;
}
public static void set( SocketFactory factory )
{
local.set( factory );
}
public static void remove()
{
local.remove();
}
}
Using it like this:
env.put( "java.naming.ldap.factory.socket", ThreadLocalSocketFactory.class.getName() );
ThreadLocalSocketFactory.set( sslContext.getSocketFactory() );
try {
ctx = new InitialLdapContext( env, null );
} finally {
ThreadLocalSocketFactory.remove();
}
Not nice, but it works.
JNDI should be more flexible here...
You should pass the name of own SSLSocketFactory subclass and pass its fully qualified named into the "java.naming.ldap.factory.socket" env property, as described in the "Using Custom Sockets" section of the Java LDAP/SSL guide:
env.put("java.naming.ldap.factory.socket", "example.CustomSocketFactory");
You can't pass any specific argument to this class, see instantiation in com.sun.jndi.ldap.Connection.createSocket(...):
Class socketFactoryClass = Obj.helper.loadClass(socketFactory);
Method getDefault =
socketFactoryClass.getMethod("getDefault", new Class[]{});
Object factory = getDefault.invoke(null, new Object[]{});
If you want additional parameters, you may have to use static members or JNDI perhaps (usually not ideal).
As far as I can tell, there doesn't seem to be any hostname verification when using ldaps:// in this implementation unfortunately. If you only trust one explicit certificate in your trust manager, this should compensate for the lack of hostname verification anyway.
I've built my own solution that works for me but it's far from perfect. I'm actually afraid there's no perfect solution due to the unfortunate implementation in javax.naming.
My SelectiveLdapSslSocketFactory contains a static Map mapping hosts to different SSLSocketFactories. When any of the createSocket(...) methods is called, the call is delegated to the corresponding SSLSocketFactory. It also contains a defaultSslSocketFactory used for hosts without any mapping and also in the getDefaultCipherSuites() and getSupportedCipherSuites() methods. I'm not sure, if it's correct but it works fine in my case so test it if you like:
public class SelectiveLdapSslSocketFactory extends SSLSocketFactory {
private static SSLSocketFactory defaultSslSocketFactory;
private static final Map<String, SSLSocketFactory> hostToSslSocketFactoryMap = new HashMap<>();
{
try {
defaultSslSocketFactory = <yourOwnDefaultSslSocketFactory>;
} catch (Exception ex) {
Logger.warn(ex, "Couldn't initialize a defaultSslSocketFactory for LDAP connections!");
}
}
public static SSLSocketFactory getRegisteredSslSocketFactory(String host) {
return hostToSslSocketFactoryMap.get(host);
}
public static void registerSslSocketFactory(String host, SSLSocketFactory sslSocketFactory) {
hostToSslSocketFactoryMap.put(host, sslSocketFactory);
}
public static void deregisterSslSocketFactory(String host) {
hostToSslSocketFactoryMap.remove(host);
}
public SelectiveLdapSslSocketFactory() {
}
public static SocketFactory getDefault() {
return new SelectiveLdapSslSocketFactory();
}
#Override
public String[] getDefaultCipherSuites() {
return defaultSslSocketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return defaultSslSocketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(s, host, port, autoClose);
}
#Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(host, port);
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(host, port, localHost, localPort);
}
#Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocketFactory sslSocketFactory = getSslSocketFactory(host);
return sslSocketFactory.createSocket(host, port);
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
SSLSocketFactory sslSocketFactory = getSslSocketFactory(address);
return sslSocketFactory.createSocket(address, port, localAddress, localPort);
}
private SSLSocketFactory getSslSocketFactory(InetAddress inetAddress) {
SSLSocketFactory sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getCanonicalHostName(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostName(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostAddress(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = defaultSslSocketFactory;
}
}
}
return sslSocketFactory;
}
}
You can then use it like:
...
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host01, sslSocketFactory01);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host02, sslSocketFactory02);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host03, sslSocketFactory03);
props.put("java.naming.ldap.factory.socket", SelectiveLdapSslSocketFactory.class.getName());

Categories