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

冷暖自知 提交于 2019-12-17 10:47:29

问题


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!


回答1:


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));
   }

}



回答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"});
}



回答3:


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



来源:https://stackoverflow.com/questions/32587141/how-to-force-commons-httpclient-3-1-to-use-tls-1-2-only-for-https

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!