How to change client TLS preferences in Java?

jpthesolver2 picture jpthesolver2 · Nov 5, 2019 · Viewed 7.3k times · Source

I'm trying to make a POST request to an endpoint in Java, and when I try to send the request, I get the following error:

Caused by: javax.net.ssl.SSLHandshakeException: The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]

This is what I have so far

Map<Object, Object> data = new HashMap<>();
data.put("username","foo");
data.put("password","bar");

String url = "https://google.com";

HttpRequest request = HttpRequest.newBuilder()
     .POST(buildFormDataFromMap(data))
     .uri(URI.create(url))
     .build();

try{
     HttpResponse<String> response =  httpClient.send(request, 
          HttpResponse.BodyHandlers.ofString());
     System.out.println(response.statusCode());
     System.out.println(response.body());
} catch (Exception e){
     e.printStackTrace();
}

Then when I run the code, the error gets thrown when sending the request/making the response object. My question is, if the TLS preferences are different for the server than the client, how can I change the preferences within Java so it can still make the request?

Answer

Java ist auch eine Insel picture Java ist auch eine Insel · May 15, 2020

To solve this problem in jdk 11, I had to create an javax.net.ssl.SSLParameters object to enable "TLSv1", etc:

SSLParameters sslParameters = new SSLParameters();
        sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});

Then create the HttpClient and add the sslParamters object:

HttpClient httpClient = HttpClient.newBuilder()
            .sslParameters(sslParameters)
            .build();

If you also want to disable hostname verification, add following code BEFORE HttpClient initialization;

final Properties props = System.getProperties(); 
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());

Also you can add a new TrustManager to trust all certificates (self signed). To do so, add following code into your Class:

    TrustManager[] trustAllCerts = new TrustManager[] { 
        new X509TrustManager() {     
            public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
                return new X509Certificate[0];
            } 
            public void checkClientTrusted( 
                java.security.cert.X509Certificate[] certs, String authType) {
                } 
            public void checkServerTrusted( 
                java.security.cert.X509Certificate[] certs, String authType) {
            }
        } 
    }; 

After this, you have to create an SSLContext object and add the TrustManger object:

SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

And finally alter the HttpClient initialization like this:

httpClient = HttpClient.newBuilder()
            .sslContext(sslContext)
            .sslParameters(sslParameters)
            .build()

Here is a complete Class example:

import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Properties;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpSSLClient {

    private SSLContext sslContext;
    private SSLParameters sslParameters;
    private HttpClient httpClient;

    public HttpSSLClient() throws KeyManagementException, NoSuchAlgorithmException {

        sslParameters = new SSLParameters();
        sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});

        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        final Properties props = System.getProperties(); 
        props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());

        httpClient = HttpClient.newBuilder()
                .sslContext(sslContext)
                .sslParameters(sslParameters)
                .build();
    }

    public HttpClient getHttplClient() {
        return httpClient;
    }

    TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {     
                public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
                    return new X509Certificate[0];
                } 
                public void checkClientTrusted( 
                    java.security.cert.X509Certificate[] certs, String authType) {
                    } 
                public void checkServerTrusted( 
                    java.security.cert.X509Certificate[] certs, String authType) {
                }
            } 
        }; 
}

You can use the getHttplClient() function while calling your HttpRequest.