46

How can I change the supported TLS versions on my HttpClient?

I'm doing:

SSLContext sslContext = SSLContext.getInstance("TLSv1.1");
sslContext.init(
    keymanagers.toArray(new KeyManager[keymanagers.size()]),
    null,
    null);

SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, new String[]{"TLSv1.1"}, null, null);
Scheme scheme = new Scheme("https", 443, socketFactory);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(scheme);
BasicClientConnectionManager cm = new BasicClientConnectionManager(schemeRegistry);
httpClient = new DefaultHttpClient(cm);

But when I check the created socket, it still says the supported protocols are TLSv1.0, TLSv1.1 and TLSv1.2.

In reality I just want it to stop using TLSv1.2, for this specific HttpClient.

Oliveira
  • 1,281
  • 1
  • 11
  • 19

10 Answers10

39

The solution is:

SSLContext sslContext = SSLContexts.custom()
    .useTLS()
    .build();

SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(
    sslContext,
    new String[]{"TLSv1", "TLSv1.1"},   
    null,
    BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

httpClient = HttpClients.custom()
    .setSSLSocketFactory(f)
    .build();

This requires org.apache.httpcomponents.httpclient 4.3.x though.

Oliveira
  • 1,281
  • 1
  • 11
  • 19
  • 3
    It seems that this has been deprecated. Can you please update to reflect new httpClient versions? – douglaslps Jul 07 '15 at 19:10
  • I know. Unfortunately I didn't investigate too much on how to get it working with the new httpClient version, so I'm using an old version. But let me know if you get it working. – Oliveira Jul 08 '15 at 12:45
  • 2
    Worked perfectly for me. I applied this solution to force calls to use TLSv1.2 since vendors like PayPay and UPS' API are now enforcing it. Java7 defaults to TLSv1, Java8 defaults to TLSv1.2 but I can't upgrade Java right now, so this post really helped me. – AntonioOtero Mar 10 '16 at 17:30
  • 1
    What if I want to do something similar with httpclient 4.1.1? – IgorGanapolsky Apr 20 '16 at 16:33
  • 2
    @Oliveira does setting system environment https.protocols=TLSv1,TLSv1.1 not achive the same result ? – Gaurav Rawat Jul 13 '16 at 23:03
  • @GauravRawat maybe. But then you change it for all the http clients, instead of just one. – Oliveira Jul 15 '16 at 09:21
  • That can still be done by using SSL connection socket factory.getSystemSocketFactory and be managed for the app easily, that shouldn't charge it for all but it's UpTo your choice anyways – Gaurav Rawat Jul 17 '16 at 03:42
  • @AntonioOtero did you get it working with JDK7 ? I created client using above method.But I see that during SSL handshake (ClientHello) the protocol listed is TLSv1. I did set https.protocols also but no effect.Using httpclient-4.3.3.jar. Do you mean that this only changes the supported protocol on socket but the underlying SSL handshake is still controlled by JSSE API ? I mean Apache HttpClient does not implement any of the TLS protocol aspects. It relies on JSSE APIs to do TLS/SSL handshaking and to establish secure SSL sessions ? – Shrikant Thakare Sep 28 '16 at 22:37
  • @ShrikantThakare I didn´t have those problems, I see TLSv1.2 listed. The only change I did to the code in this answer was to remove TLSv1.1 from the parameters of the factory, i.e. new String[]{"TLSv1.2"} Hope this helps – AntonioOtero Sep 30 '16 at 16:30
  • 1
    @Oliveira When you use `HttpClientBuilder`, this will work only if you enable `systemProperties` since it's off by default. – jebeaudet Nov 03 '17 at 15:29
  • what is useTLS()? what should be implementing in this?any idea? – Horrorgoogle Mar 25 '19 at 20:42
  • @DharmaKshetri https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/conn/ssl/SSLContextBuilder.html#useTLS() – Oliveira Mar 28 '19 at 10:08
  • @ShrikantThakare which version and build of Java did you use, please? – cviniciusm Jul 02 '20 at 21:12
15

This is how I got it working on httpClient 4.5 (as per Olive Tree request):

CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
        new AuthScope(AuthScope.ANY_HOST, 443),
        new UsernamePasswordCredentials(this.user, this.password));

SSLContext sslContext = SSLContexts.createDefault();

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
        new String[]{"TLSv1", "TLSv1.1"},
        null,
        new NoopHostnameVerifier());

CloseableHttpClient httpclient = HttpClients.custom()
        .setDefaultCredentialsProvider(credsProvider)
        .setSSLSocketFactory(sslsf)
        .build();

return httpclient;
douglaslps
  • 8,068
  • 2
  • 35
  • 54
  • 20
    Please note that the code snippet above uses NoopHostnameVerifier which essentially turns hostname verification off. In most cases, this is a very bad thing. – Anand Bhat Sep 15 '15 at 15:12
  • 1
    Can one just use the org.apache.http.conn.ssl.DefaultHostnameVerifier ? – John Russell Feb 20 '18 at 19:45
10

HttpClient-4.5,Use TLSv1.2 ,You must code like this:

 //Set the https use TLSv1.2
private static Registry<ConnectionSocketFactory> getRegistry() throws KeyManagementException, NoSuchAlgorithmException {
    SSLContext sslContext = SSLContexts.custom().build();
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
            new String[]{"TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    return RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslConnectionSocketFactory)
            .build();
}

public static void main(String... args) {
    try {
        //Set the https use TLSv1.2
        PoolingHttpClientConnectionManager clientConnectionManager = new PoolingHttpClientConnectionManager(getRegistry());
        clientConnectionManager.setMaxTotal(100);
        clientConnectionManager.setDefaultMaxPerRoute(20);
        HttpClient client = HttpClients.custom().setConnectionManager(clientConnectionManager).build();
        //Then you can do : client.execute(HttpGet or HttpPost);
    } catch (KeyManagementException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
}
sfxm
  • 131
  • 1
  • 5
6

Using the HttpClientBuilder in HttpClient 4.5.x with a custom HttpClientConnectionManager with the defaults of HttpClientBuilder :

SSLConnectionSocketFactory sslConnectionSocketFactory = 
    new SSLConnectionSocketFactory(SSLContexts.createDefault(),          
                                   new String[] { "TLSv1.2" },                                            
                                   null, 
           SSLConnectionSocketFactory.getDefaultHostnameVerifier());

PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
    new PoolingHttpClientConnectionManager(
        RegistryBuilder.<ConnectionSocketFactory> create()
                       .register("http",
                                 PlainConnectionSocketFactory.getSocketFactory())
                       .register("https",
                                 sslConnectionSocketFactory)
                       .build());

// Customize the connection pool

CloseableHttpClient httpClient = HttpClientBuilder.create()
                                                  .setConnectionManager(poolingHttpClientConnectionManager)
                                                  .build()

Without a custom HttpClientConnectionManager :

SSLConnectionSocketFactory sslConnectionSocketFactory = 
    new SSLConnectionSocketFactory(SSLContexts.createDefault(),          
                                   new String[] { "TLSv1.2" },                                            
                                   null, 
           SSLConnectionSocketFactory.getDefaultHostnameVerifier());

CloseableHttpClient httpClient = HttpClientBuilder.create()
                                                  .setSSLSocketFactory(sslConnectionSocketFactory)
                                                  .build()
jebeaudet
  • 1,533
  • 16
  • 15
5

For HttpClient-4.1 using TLSv1.2, code would go something like this:

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(null, null, new SecureRandom());
        SSLSocketFactory sf = new SSLSocketFactory(sslContext);
        Scheme httpsScheme = new Scheme("https", 443, sf);
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(httpsScheme);
        ClientConnectionManager cm =  new        SingleClientConnManager(schemeRegistry);
        HttpClient client = new DefaultHttpClient(cm);

       // Use client to make the connection and get the results.
user2122524
  • 516
  • 1
  • 7
  • 9
2

If you are using httpclient 4.2, then you need to write a small bit of extra code. I wanted to be able to customize both the "TLS enabled protocols" (e.g. TLSv1.1 specifically, and neither TLSv1 nor TLSv1.2) as well as the cipher suites.

public class CustomizedSSLSocketFactory
    extends SSLSocketFactory
{
    private String[] _tlsProtocols;
    private String[] _tlsCipherSuites;

    public CustomizedSSLSocketFactory(SSLContext sslContext,
                                      X509HostnameVerifier hostnameVerifier,
                                      String[] tlsProtocols,
                                      String[] cipherSuites)
    {
        super(sslContext, hostnameVerifier);

        if(null != tlsProtocols)
            _tlsProtocols = tlsProtocols;
        if(null != cipherSuites)
            _tlsCipherSuites = cipherSuites;
    }

    @Override
    protected void prepareSocket(SSLSocket socket)
    {
        // Enforce client-specified protocols or cipher suites
        if(null != _tlsProtocols)
            socket.setEnabledProtocols(_tlsProtocols);

        if(null != _tlsCipherSuites)
            socket.setEnabledCipherSuites(_tlsCipherSuites);
    }
}

Then:

    SSLContext sslContext = SSLContext.getInstance("TLS");

    sslContext.init(null, getTrustManagers(), new SecureRandom());

    // NOTE: not javax.net.SSLSocketFactory
    SSLSocketFactory sf = new CustomizedSSLSocketFactory(sslContext,
                                                         null,
                                                         [TLS protocols],
                                                         [TLS cipher suites]);

    Scheme httpsScheme = new Scheme("https", 443, sf);
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(httpsScheme);

    ConnectionManager cm = new BasicClientConnectionManager(schemeRegistry);

    HttpClient client = new DefaultHttpClient(cmgr);
    ...

You may be able to do this with slightly less code, but I mostly copy/pasted from a custom component where it made sense to build-up the objects in the way shown above.

Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • @Skynet The technique should be valid, but the specific TLS protocols and cipher suites are generally dictated by the underlying JVM. So you'll have to check out the protocol support for Android < 4.1. – Christopher Schultz Jun 29 '16 at 15:32
  • @comeOnGetIt Java 6 mostly does not support anything higher than TLS 1.0 (http://stackoverflow.com/questions/33364100/how-to-use-tls-1-2-in-java-6). Java 6 should be dead at this point, anyway. – Christopher Schultz May 12 '17 at 15:57
2

If you have a javax.net.ssl.SSLSocket class reference in your code, you can set the enabled TLS protocols by a call to SSLSocket.setEnabledProtocols():

import javax.net.ssl.*;
import java.net.*; 
...
Socket socket = SSLSocketFactory.getDefault().createSocket();
...
if (socket instanceof SSLSocket) {
   // "TLSv1.0" gives IllegalArgumentException in Java 8
   String[] protos = {"TLSv1.2", "TLSv1.1"}
   ((SSLSocket)socket).setEnabledProtocols(protos);
}
David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147
2

Using -Dhttps.protocols=TLSv1.2 JVM argument didn't work for me. What worked is the following code

RequestConfig.Builder requestBuilder = RequestConfig.custom();
//other configuration, for example
requestBuilder = requestBuilder.setConnectTimeout(1000);

SSLContext sslContext = SSLContextBuilder.create().useProtocol("TLSv1.2").build();

HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultRequestConfig(requestBuilder.build());
builder.setProxy(new HttpHost("your.proxy.com", 3333)); //if you have proxy
builder.setSSLContext(sslContext);

HttpClient client = builder.build();

Use the following JVM argument to verify

-Djavax.net.debug=all
duvo
  • 1,634
  • 2
  • 18
  • 30
  • `-Dhttps.protocols` also works with Apache http client together with [useSystemProperties()](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html#useSystemProperties()) – JonnyJD May 27 '19 at 12:46
1

You could just specify the following property -Dhttps.protocols=TLSv1.1,TLSv1.2 at your server which configures the JVM to specify which TLS protocol version should be used during all https connections from client.

  • 1
    Nope. Does NOT work server side, only in client side JVM Applets / Webstart. – Bertl Jun 30 '17 at 08:18
  • 1
    It does not seem to work as well if you use the apache httpclient (tested with 3 and 4). I am not sure if these settings are really necessary if the other solutions are implemented. – рüффп Feb 27 '18 at 13:23
  • Yes, if JDK makes the connection using `URL#openConnection()`, but these doesn't work with Apache HttpClient. Question is specifically tagged _apache-httpclient-4.x_ – Mohnish Jun 06 '18 at 16:07
  • 1
    This also works with Apache http client together with [useSystemProperties()](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html#useSystemProperties()) – JonnyJD May 27 '19 at 12:44
0

Since this only came up hidden in comments, difficult to find as a solution:

You can use java -Dhttps.protocols=TLSv1,TLSv1.1, but you need to use also useSystemProperties()

client = HttpClientBuilder.create().useSystemProperties();

We use this setup in our system now as this enables us to set this only for some usage of the code. In our case we still have some Java 7 running and one API end point disallowed TLSv1, so we use java -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 to enable current TLS versions. Thanks @jebeaudet for pointing in this direction.

JonnyJD
  • 2,593
  • 1
  • 28
  • 44