Java、Apache HttpClient、TLSv1.2 和 OpenJDK 7

Java, Apache HttpClient, TLSv1.2 & OpenJDK 7

我们有一小组 Tomcat 服务器 运行 OpenJDK v1.7.0_111。我们计划在今年夏天对它们进行升级和迁移,但我们发现与我们互动的客户 API 在短期内需要 TLSv1.2。我的最终愿望是找到一个配置更改来实现这一点。

那里托管的应用程序以非常直接的方式创建它的 SSL 上下文:

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

SSLContexts 来自 Apache 的 httpclient 库(版本 4.4.1),并且在创建 SSL 上下文方面也非常直接:

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

并且通过 SSLConnectionSocketFactory class 挖掘,它似乎只是使用 SSLSocket.getEnabledProtocols() 方法来确定哪些协议可用。请注意,this.supportedProtocols 在我的例子中为 null。

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

我遇到的问题是,虽然 运行 进行了一些初步测试,但我无法让这些客户端连接到需要 TLSv1.2 的 API。

在下面的示例中,我可以通过包含 -Dhttps.protocols=TLSv1.2 参数来完成 URLConnection 代码,但我无法获得要连接的 Apache 连接。

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

    }

连同 -Dhttps.protocols=TLSv1.2 我已经尝试了 -Djdk.tls.client.protocols=TLSv1.2-Ddeployment.security.TLSv1.2=true JVM 参数但没有任何运气。

有没有人想过如何在不升级到 v8 或更改应用程序以专门请求 TLSv1.2 实例的情况下在此配置中启用 TLSv1.2?

jdk.tls.client.protocols 仅适用于您未使用的 Java 8(大概是 9)。

https.protocols 仅在 httpclient 不使用的 HttpsURLConnection 默认情况下有效。

deployment.* 仅适用于您未使用的 JNLP 和小程序(如果任何浏览器仍允许小程序)。

你的问题的答案至少是 4.5,假设 你使用 HttpClientBuilderHttpClients(你没有说) ,就是分别使用.useSystemProperties()或者.createSystem();这些 do 使用与 *URLConnection 相同的系统属性——或者至少其中许多包括 https.protocols。您应该检查此集合中包含的其他属性的 none 是否配置为执行您不想要的操作。 确实需要更改应用程序,但不需要更改它们 'to specifically request ... TLSv1.2'。

除此之外,您可以配置 SSLConnectionSocketFactory 以指定在 中允许的确切协议,或配置 SSLContexts.custom().useProtocol(String).build() 以指定上限 - 这足以满足您的情况因为向需要 1.2 的服务器提供范围 'up to 1.2' 将 select 1.2.

这是配置 Apache HttpClient 4.x 以使用特定 TLS/SSL 版本的推荐方法

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

投票给 dave_thompson_085 的回答