How to force Commons HTTPClient 3.1 to use TLS 1.2 only for HTTPS?
Asked Answered
M

3

22

I wish to force Apache Commons HTTP-Client (version 3.1) to use TLS 1.2 as the only protocol for HTTPS.

This is due to the server supposedly being upgraded to TLS 1.2 and not accepting any older protocol anymore (causing 'Connection Reset' to be returned).

For further context, probably irrelevant, the HTTP-Client is used along with Axis2 to make a SOAP; some of the code used for setting up the HttpClient is below:

MultiThreadedHttpConnectionManager connMgr = new MultiThreadedHttpConnectionManager();
this.httpClient = new HttpClient(connMgr);

// initialize HttpClient parameters
HttpClientParams hcParams = this.httpClient.getParams();

// Maximum time to wait to receive connection from pool
hcParams.setConnectionManagerTimeout(this.maxWait);
hcParams.setSoTimeout(this.timeout);
hcParams.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(this.retryCount, false));

// Initialize global Connection manager parameters
HttpConnectionManagerParams cmParams = connMgr.getParams();
cmParams.setDefaultMaxConnectionsPerHost(this.maxActive);
cmParams.setStaleCheckingEnabled(this.checkStaleConnections);
cmParams.setConnectionTimeout(this.timeout);

Thanks a lot for the help!

Markham answered 15/9, 2015 at 13:16 Comment(2)
Since you are using this old and unmaintained software I assume that you are using an old Java version too. Are you sure that your Java is able to speak Java 1.2 at all (i.e. which version of Java do you use?)Josuejosy
Nope I'm using Java 7, and the code is not too old, it works pretty well until now.Markham
M
26

Too bad nobody answered; I was able to do it, first you write a CustomHttpSocketFactory, then you do:

String scheme = "https";
Protocol baseHttps = Protocol.getProtocol(scheme);
int defaultPort = baseHttps.getDefaultPort();

ProtocolSocketFactory baseFactory = baseHttps.getSocketFactory();
ProtocolSocketFactory customFactory = new CustomHttpsSocketFactory(baseFactory);

Protocol customHttps = new Protocol(scheme, customFactory, defaultPort);
Protocol.registerProtocol(scheme, customHttps); 

A sample custom socket factory code is found here, but instead I did:

public class CustomHttpsSocketFactory implements SecureProtocolSocketFactory
{

   private final SecureProtocolSocketFactory base;

   public CustomHttpsSocketFactory(ProtocolSocketFactory base)
   {
      if(base == null || !(base instanceof SecureProtocolSocketFactory)) throw new IllegalArgumentException();
      this.base = (SecureProtocolSocketFactory) base;
   }

   private Socket acceptOnlyTLS12(Socket socket)
   {
      if(!(socket instanceof SSLSocket)) return socket;
      SSLSocket sslSocket = (SSLSocket) socket;
      sslSocket.setEnabledProtocols(new String[]{"TLSv1.2" });
      return sslSocket;
   }

   @Override
   public Socket createSocket(String host, int port) throws IOException
   {
      return acceptOnlyTLS12(base.createSocket(host, port));
   }
   @Override
   public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException
   {
      return acceptOnlyTLS12(base.createSocket(host, port, localAddress, localPort));
   }
   @Override
   public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException
   {
      return acceptOnlyTLS12(base.createSocket(host, port, localAddress, localPort, params));
   }
   @Override
   public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException
   {
      return acceptOnlyTLS12(base.createSocket(socket, host, port, autoClose));
   }

}
Markham answered 16/9, 2015 at 10:20 Comment(5)
Is SecureProtocolSocketFactory different from SSLSocketFactory?Jakoba
SecureProtocolSocketFactory is an interface in Apache's HTTPClient helpful for implementing a custom socket factory; I believe java's SSLSocketFactory would be less helpful in this case.Markham
@united-expression I have same requirement except that I am using HTTP-Client (version 4.1). Your code is helpful though I am still not clear where are you binding httpClient with this registered Protocol?Sinewy
@united-expression why do need a separate class in this case and whats the use of RegisterProtocol ? How is the methods of CustomHttpsSocketFactory will be called?Allisan
It is showing setEnabledProtocols(String[]); is undefined for the type javax.net.ssl.SSLSocket.Jotter
J
2

You need a Socket reference in your code. Then you can set enabled protocols on it like this:

if (socket != null && (socket instanceof SSLSocket)) {
    ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
}
Jakoba answered 21/4, 2016 at 14:5 Comment(3)
I'm confused as to how to use this in my implementation of HttpClient (3.1). Where is the socket object initialized? I want to also ensure that the protocols I set are only used by my HttpClient but no other classes/implementations that might be making http requests elsewhere. Using Java 8, and cannot upgrade to other versions of HttpClient at this time.Inenarrable
@ChiefNish This is Java's socket: docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLSocket.htmlJakoba
Does that mean that the HttpClient, whose guide says "HttpClient provides full support for HTTP over Secure Sockets Layer (SSL) or IETF Transport Layer Security (TLS) protocols by leveraging the Java Secure Socket Extension (JSSE). ", means that by default the sockets provided by Java 8, are enabled? So support up to TLSv1.2? My intention is to find out what protocol is used by default and then ensure that at least SSL 3.0 and forward are supported. Turned out to be harder to find out than expected, maybe I'm asking the wrong questions. Thank you!Inenarrable
A
2

It depends on how you are writing your clients and what JRE versions you are using:

If you are using JRE8 (unless you have replaced the default SunJSSE that comes with JRE8), there is a system property "jdk.tls.client.protocols". By default, whatever you mention here will be used for all client communication.

If you are using HttpsURLConnection object for client connection, u can use the system property "https.protocols". This will work for all JRE versions, not just JRE8.

If you don't specify anything, for TLS clients, in JRE8, TLSv1, v1.1 and v1.2 are enabled, so it will work with a server what supports any one of this versions. However in JRE7 by default TLSv1 alone is enabled.

In your code u can always override the default or what u pass through the system properties. What u set in the code will take higher precedence. To override in the code...

1) If you are using raw socket and SSLEngine, u can set the protocol and ciphers in the SSLEngine (sslEngine.setEnabledProtocols(..)

2) If you are using SSLSocket, u can set the protocol and ciphers in the SSLSocket (sslSocket.setEnabledProtocols(..)

You can also get an SSLContext with the required protocol enabled and use that for whatever SSL components you use. SSLContext.getInstance("TLSvx.x"). Note that by default it will return a context with all the protocols lesser that TLSvx.x enabled. If u have configured "jdk.tls.client.protocols", this will return a context with those protocols enabled.

It would not be a good idea to hard coded the protocols in the code. Quite often, we will encounter certain customers want specific version either because they use old servers or some serious vulnerabilities are encountered in some TLS versions. Either set it through the system properties or even if you are explicitly setting in sslsocket or sslengine, read that from some conf file.

Also refer:

https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html

http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html

Acromion answered 12/8, 2016 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.