Java、Apache HttpClient、TLSv1.2和OpenJDK 7

3
我们有一小组Tomcat服务器运行OpenJDK v1.7.0_111。我们计划在今年夏季升级和迁移它们,但我们发现我们与之交互的客户端API即将要求TLSv1.2。我最终的愿望是找到一种配置更改以允许这种情况。
应用程序在那里托管,以非常直观的方式创建它的SSL上下文:
SSLContext sslContext = SSLContexts.createDefault()
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
< p > 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 类的源码,发现它仅使用 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参数,但都没有成功。

是否有人有想法如何在这种配置下启用TLSv1.2而不升级到v8或更改应用程序以专门请求TLSv1.2实例?


你尝试过这里展示的参数和测试吗?https://blogs.oracle.com/java-platform-group/jdk-8-will-use-tls-12-as-default - pvg
此外,您可能希望包括一个简短的[MCVE],以便JDK 7上的其他用户可以重现您正在尝试的内容。 - pvg
@pvg - 现在我有了。我已经在问题中列出了我尝试过的所有JVM参数。我还包括了一个简短的例子。 - Staros
TLS 1.3还有一段时间才能到来。在那之前,您将使用JDK 8,因此这可能是一个合理的临时权衡/修复。 - pvg
非常公平。这可能最终成为我们走的路线。但我只是想确保万无一失,看看是否可以避免更改代码。 - Staros
显示剩余6条评论
2个回答

8

jdk.tls.client.protocols 只适用于 Java 8(可能也适用于 9),而您没有使用这个版本。

https.protocols 只在 HttpsURLConnection 中默认使用,而 httpclient 不使用。

deployment.* 只适用于 JNLP 和 applet(如果有任何浏览器仍允许 applet),而您没有使用它们。

根据您所述的问题,至少对于 4.5,假设 您使用 HttpClientBuilderHttpClients(您没有提到),可以使用 .useSystemProperties().createSystem(),这些方法分别使用与 *URLConnection 相同的系统属性--或者至少包括了许多属性,包括 https.protocols。您应该检查此集合中未配置任何其他属性以执行您不想要的操作。这样做需要更改应用程序,但不需要更改它们“专门请求... TLSv1.2”。

除此之外,您可以配置 SSLConnectionSocketFactory 来指定允许的确切协议,如 @pvg 链接的 Q 中所示,或者使用 SSLContexts.custom().useProtocol(String).build() 来指定上限--这对您的情况足够了,因为向要求 1.2 的服务器提供“高达 1.2”的范围将选择 1.2。


1
如果我理解得正确的话,jdk.tls.client.protocols 应该在 OpenJDK 7u91 及更高版本以及 6u115 及更高版本中实际可用。参考 这个 OpenJDK 增强请求 - russianmario

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的回答


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接