java 扩展 server_name(SNI 扩展)不是用 jdk1.8.0 发送而是用 jdk1.7.0 发送

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30817934/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 17:42:28  来源:igfitidea点击:

Extended server_name (SNI Extension) not sent with jdk1.8.0 but send with jdk1.7.0

javaweb-servicessslcxfsni

提问by hints4dev

I have implemented a JAX-WS client by using ApacheCXF (v3.0.4) and everything works successfully but the problem comes when I want to use a secure connection (SSL/TLS) with java 8 (jdk1.8.0_25).

我已经使用 ApacheCXF (v3.0.4) 实现了一个 JAX-WS 客户端并且一切正常,但是当我想使用带有 java 8 (jdk1.8.0_25) 的安全连接 (SSL/TLS) 时问题就出现了。

I see the following exception in log (-Djavax.net.debug=all):

我在日志中看到以下异常(-Djavax.net.debug=all):

main, handling exception: java.net.SocketException: Connection reset
main, SEND TLSv1.2 ALERT:  fatal, description =    unexpected_message
main, WRITE: TLSv1.2 Alert, length = 2
main, Exception sending alert: java.net.SocketException: Connection reset by peer: socket write error

After a depeer analysis I have observed the problem is caused because the with Java 8 the server_name (SNI) is not sent but with Java 7 it is sent and the web service invocation works successfully.

经过深入分析后,我观察到问题是由于 Java 8 未发送 server_name (SNI),但使用 Java 7 发送并且 Web 服务调用成功。

Java 8 log (-Djavax.net.debug=all):Missing "Extension server_name"

Java 8 日志(-Djavax.net.debug=all):缺少“扩展服务器名称”

[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[...]

Java 7 log (-Djavax.net.debug=all) (works):"Extension server_name" is set

Java 7 日志(-Djavax.net.debug=all)(有效):设置了“扩展服务器名称”

[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [host_name: testeo.hostname.es]
***
[...]

It is observed that with Java 7 the Extension server_name, server_name: [host_name: testeo.hostname.es]is set and then the web service invocation works successfully.

据观察,在 Java 7 中,扩展 server_name, server_name: [host_name: testeo.hostname.es]已设置,然后 Web 服务调用成功运行。

Why didn't Java 8 set the server_name as Java 7 did? Is it a Java configuration issue?

为什么 Java 8 没有像 Java 7 那样设置 server_name?这是Java配置问题吗?

回答by Benjamin Parry

As mentioned, the cause is related to the JDK bug where using setHostnameVerifier() breaks SNI (Extension server_name). https://bugs.openjdk.java.net/browse/JDK-8144566

如前所述,原因与使用 setHostnameVerifier() 破坏 SNI(扩展服务器名称)的 JDK 错误有关。 https://bugs.openjdk.java.net/browse/JDK-8144566

Our workaround: After testing we found that setting a connection's SSLSocketFactory to just about anything from the default seems to fix the issue.

我们的解决方法:经过测试,我们发现将连接的 SSLSocketFactory 设置为默认值的任何内容似乎都可以解决问题。

This does not work: HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());

这不起作用: HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());

This does work: HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());

这确实有效: HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());

So, to fix it for a JAX-WS client, you could do something like this: bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());

因此,要为 JAX-WS 客户端修复它,您可以执行以下操作: bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());

Our SSLSocketFactory facade: (Note that it really doesn't do anything)

我们的 SSLSocketFactory 外观:(请注意,它实际上什么也没做)

public class SSLSocketFactoryFacade extends SSLSocketFactory {

    SSLSocketFactory sslsf;

    public SSLSocketFactoryFacade() {
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
        return sslsf.createSocket(socket, s, i, b);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i, inetAddress, i1);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
        return createSocket(inetAddress, i);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
        return createSocket(inetAddress, i, inetAddress1, i1);
    }
}

回答by Patrik Beck

You, or the underlying libs (the WS lib does it) might be using setHostnameVerifier(..)

您或底层库(WS 库执行此操作)可能正在使用 setHostnameVerifier(..)

There is a bug in java8, where if setHostnameVerifier(..) is used the SNI is not done from the client side.

java8 中存在一个错误,如果使用 setHostnameVerifier(..),则 SNI 不会从客户端完成。

https://bugs.openjdk.java.net/browse/JDK-8072464

https://bugs.openjdk.java.net/browse/JDK-8072464

回答by Harish Kumar

Please use JDK version 8u141 and above where this issue has been fixed. Please review the JDK 8u141 Bugs Fixespage for more details

请使用已修复此问题的 JDK 版本 8u141 及更高版本。请查看JDK 8u141 错误修复页面以获取更多详细信息

回答by matt forsythe

I tried the solution provided by Benjamin Parry, but it did not work for me. After some digging around, I also found this solutionwhich looks very similar, however the SSLSocketFactoryFacade manually inserts the correct SSL header instead of being a pure pass-though. Providing my final code below which is slightly different, but credit to be given to Girish Kamath at javabreaks for the basic idea:

我尝试了 Benjamin Parry 提供的解决方案,但它对我不起作用。经过一番挖掘,我还发现了这个看起来非常相似的解决方案,但是 SSLSocketFactoryFacade 手动插入了正确的 SSL 标头,而不是纯粹的传递。下面提供我的最终代码,其中略有不同,但在 javabreaks 上将 Girish Kamath 的基本思想归功于:

    private static class SSLSocketFactoryFacade extends SSLSocketFactory {
    private SSLSocketFactory sslsf;
    private SSLParameters sslParameters;

    public SSLSocketFactoryFacade(String hostName) {
        sslParameters = new SSLParameters();
        sslParameters.setServerNames(Arrays.asList(new SNIHostName(hostName)));
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    public Socket createSocket() throws IOException {
        Socket socket = sslsf.createSocket();
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
            throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }
}

And then I can call

然后我可以打电话

sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));

where sslConnectionis the HttpsURLConnection.

这里sslConnectionHttpsURLConnection

回答by Stephen C

First of all, this "server_name" stuff is associate with the SNI (Server Name Indication) extension. The Java 8 JSSE documentation talks about it here.

首先,这个“server_name”的东西与 SNI(服务器名称指示)扩展相关联。Java 8 JSSE 文档在这里讨论了它。

The documentation includes example code that shows how to set the server names that are sent. The code is for Java 8.

该文档包括显示如何设置发送的服务器名称的示例代码。该代码适用于 Java 8。

However, I can't figure out why (apparently) Java 7 is setting the server name by default, and Java 8 isn't. (The easy way to figure it out would be to use a debugger to figure out how the SSL engine object is created and initialized.)

但是,我不明白为什么(显然)Java 7 默认设置服务器名称,而 Java 8 不是。(找出它的简单方法是使用调试器来确定 SSL 引擎对象是如何创建和初始化的。)