Java, Apache HttpClient, TLSv1.2 & OpenJDK 7

2020-06-23 07:26发布

问题:

We have a small group of Tomcat servers running OpenJDK v1.7.0_111. We have plans to upgrade them and migrate them this summer but we've found that a client API we interact with is moving to require TLSv1.2 in the near term. My ultimate desire is to find a configuration change to allow for this.

The application hosted there creates it's SSL context in a pretty straight forward way:

SSLContext sslContext = SSLContexts.createDefault()
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

SSLContexts is from Apache's httpclient library (version 4.4.1) and is also pretty straight forward with how it creates the SSL context:

public static SSLContext createDefault() throws SSLInitializationException {
    try {
        SSLContext ex = SSLContext.getInstance("TLS");
        ex.init((KeyManager[])null, (TrustManager[])null, (SecureRandom)null);
        return ex;
    } catch (NoSuchAlgorithmException var1) {
        throw new SSLInitializationException(var1.getMessage(), var1);
    } catch (KeyManagementException var2) {
        throw new SSLInitializationException(var2.getMessage(), var2);
    }
}

And digging through the SSLConnectionSocketFactory class, it appears that it's simply using the SSLSocket.getEnabledProtocols() method to determine which protocols are available for use. Note that this.supportedProtocols is null in my case.

public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
        SSLSocket sslsock = (SSLSocket)this.socketfactory.createSocket(socket, target, port, true);
        if(this.supportedProtocols != null) {
            sslsock.setEnabledProtocols(this.supportedProtocols);
        } else {
            String[] allProtocols = sslsock.getEnabledProtocols();
            ArrayList enabledProtocols = new ArrayList(allProtocols.length);
            String[] arr$ = allProtocols;
            int len$ = allProtocols.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                String protocol = arr$[i$];
                if(!protocol.startsWith("SSL")) {
                    enabledProtocols.add(protocol);
                }
            }

            if(!enabledProtocols.isEmpty()) {
                sslsock.setEnabledProtocols((String[])enabledProtocols.toArray(new String[enabledProtocols.size()]));
            }
        }

The problem I'm having is that while running a few preliminary tests I'm unable to get these clients to connect to an API requiring TLSv1.2.

In the following example I can get the URLConnection code to complete by including the -Dhttps.protocols=TLSv1.2 parameter, but I cannot get the Apache connection to connect.

public static void main(String[] args) throws Exception{
        String testURL = "https://testapi.com";

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(null, null, null);

        try {
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext);
            CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();

            HttpGet httpget = new HttpGet(testURL);

            CloseableHttpResponse response = client.execute(httpget);
            System.out.println("Response Code (Apache): " + response.getStatusLine().getStatusCode());
        }
        catch (Exception e){
            System.err.println("Apache HTTP Client Failed");
            e.printStackTrace();
        }

        try {
            HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(testURL).openConnection();
            urlConnection.setSSLSocketFactory(sslcontext.getSocketFactory());
            urlConnection.connect();
            System.out.println("Response Code (URLConnection): " + urlConnection.getResponseCode());
        }
        catch (Exception e){
            System.err.println("HttpsURLConnection Failed");
            e.printStackTrace();
        }

    }

Along with the -Dhttps.protocols=TLSv1.2 I've tried the -Djdk.tls.client.protocols=TLSv1.2 and the -Ddeployment.security.TLSv1.2=true JVM parameters without any luck.

Does anyone have thoughts to how to enable TLSv1.2 in this configuration without upgrading to v8 or changing the application to specifically request an instance of TLSv1.2?

回答1:

jdk.tls.client.protocols only works on Java 8 (and presumably 9) which you aren't using.

https.protocols only works by default in HttpsURLConnection which httpclient doesn't use.

deployment.* only applies to JNLP and applets (if any browser still permits applets) which you aren't using.

An answer to your Q as stated, at least for 4.5, assuming you use HttpClientBuilder or HttpClients (which you didn't say), is to use .useSystemProperties() or .createSystem(), respectively; these do use the same system properties as *URLConnection -- or at least many of them including https.protocols. You should check none of the other properties included in this set is configured to do something you don't want. This does require changing the apps, but not changing them 'to specifically request ... TLSv1.2'.

Other than that you can configure the SSLConnectionSocketFactory to specify the exact protocols allowed as in the Q linked by @pvg, or SSLContexts.custom().useProtocol(String).build() to specify the upper bound -- which is enough for your case because offering the range 'up to 1.2' to a server that requires 1.2 will select 1.2.



回答2:

Here is the recommended way of configuring Apache HttpClient 4.x to use a specific TLS/SSL version

CloseableHttpClient client = HttpClientBuilder.create()
    .setSSLSocketFactory(new SSLConnectionSocketFactory(SSLContext.getDefault(), new String[] { "TLSv1.2" }, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
    .build();

Vote up to dave_thompson_085's answer