扩展server_name(SNI扩展)不与jdk1.8.0一起发送,而是与jdk1.7.0一起发送

时间:2015-06-13 11:03:58

标签: java web-services ssl cxf sni

我已经使用ApacheCXF(v3.0.4)实现了一个JAX-WS客户端,一切都运行成功但是当我想使用java 8(jdk1.8.0_25)的安全连接(SSL / TLS)时出现问题。

我在日志中看到以下异常(-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

经过依赖者分析之后,我发现问题是由于Java 8没有发送server_name(SNI)但是使用Java 7发送它并且Web服务调用成功运行。

Java 8日志(-Djavax.net.debug = all):缺少“Extension server_name”

[...]
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日志(-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]
***
[...]

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

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

5 个答案:

答案 0 :(得分:13)

如上所述,原因与使用setHostnameVerifier()中断SNI(Extension server_name)的JDK错误有关。 https://bugs.openjdk.java.net/browse/JDK-8144566

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

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

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

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

我们的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);
    }
}

答案 1 :(得分:7)

你或者底层的lib(WS lib就是这样)可能正在使用setHostnameVerifier(..)

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

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

答案 2 :(得分:3)

如果已修复此问题,请使用JDK版本8u141及更高版本。有关详细信息,请查看 JDK 8u141 Bugs Fixes页面

答案 3 :(得分:0)

首先,这个" server_name" stuff与SNI(服务器名称指示)扩展名相关联。 Java 8 JSSE文档讨论了它here

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

但是,我无法弄清楚为什么(显然)Java 7默认设置服务器名称,而Java 8不是。 (解决问题的简单方法是使用调试器来确定如何创建和初始化SSL引擎对象。)

答案 4 :(得分:0)

我尝试了本杰明·帕里(Benjamin Parry)提供的解决方案,但对我而言不起作用。经过一番挖掘,我还发现this solution看起来很相似,但是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();
    }
}

然后我可以打电话

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

其中sslConnectionHttpsURLConnection