3

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?

Staros
  • 3,232
  • 6
  • 30
  • 41
  • Have you tried the params and tests shown here https://blogs.oracle.com/java-platform-group/jdk-8-will-use-tls-12-as-default – pvg May 04 '17 at 01:34
  • Additionally you might want to include a short [MCVE] so other users on JDK 7 can reproduce what you're attempting. – pvg May 04 '17 at 01:38
  • You should not enable all the supported protocols, as they include the insecure anonymous cipher suites. – user207421 May 04 '17 at 01:51
  • @pvg - Now I have. I've listed all the JVM parameters I've tried in the question. I've also included a short example. – Staros May 04 '17 at 02:01
  • One last thing to throw in your sample, for the sake of completeness, might be a plain URLConnection test so you know it's not (or is) an httpclient-specific problem. – pvg May 04 '17 at 02:05
  • @pvg Great point. It appears to be something specific to httpclient. – Staros May 04 '17 at 02:28
  • Ah that narrows it down some, maybe this is a dupe - http://stackoverflow.com/questions/28391798/how-to-set-tls-version-on-apache-httpclient – pvg May 04 '17 at 02:36
  • @pvg Close, but my goal is to fix this with configuration. I'd love to avoid manually specifying TLSv1.2 in the "getInstance" request since that would inclure another code change when the servers are update, new protocols are introduced, etc. – Staros May 04 '17 at 02:50
  • TLS 1.3 is a ways off. You'll be on JDK 8 before that so this might be a reasonable temporary trade-off/fix. – pvg May 04 '17 at 02:51
  • Very fair. And that may end up being the route we end up going. But just covering my bases and see if we can avoid a code change. – Staros May 04 '17 at 02:59
  • @EJP: the anonymous ciphersuites (and also the null-encryption ones) are ciphersuites not protocols; enabling/disabling protocols has no effect on ciphersuites (except that some ciphersuites are only _used_ if TLS1.2 is enabled locally _AND negotiated by the peer_). Even SSL3 is really insecure only if attacker can repeat adaptively chosen plaintext, which is easy for browsers but not _most_ API clients.You can cause alarms by mistakenly enabling SSLv2Hello, but not actual insecurity because it can't actually do SSL2. – dave_thompson_085 May 04 '17 at 04:31

2 Answers2

8

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.

Telmo Marques
  • 5,066
  • 1
  • 24
  • 34
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • `jdk.tls.client.protocols` should actually be available in OpenJDK 7u91 and higher and 6u115 and higher if I am understanding [this OpenJDK enhancement request](https://bugs.openjdk.java.net/browse/JDK-8076369) correctly. – russianmario May 04 '17 at 13:06
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

ok2c
  • 26,450
  • 5
  • 63
  • 71