【问题标题】:All trusting HostnameVerifier causes SSL errors with HttpURLConnection所有信任的 HostnameVerifier 都会导致 HttpURLConnection 出现 SSL 错误
【发布时间】:2017-06-01 06:09:21
【问题描述】:

我正在使用 HttpURLConnection 连接到 SSL 站点。有时这些使用自签名证书/否则表现不佳的 SSL,所以我有一个可以访问这些的模式。我用的是网上很多地方推荐的典型代码:

        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
            }
        };

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());


        // Don't verify host names
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String urlHostName, SSLSession session) {
                return true;
            }
        };
        HttpsURLConnection.setDefaultHostnameVerifier(hv);

问题是这会导致许多网站出现 SSL 错误,例如:

https://cong-shalom.org/ 原因:

javax.net.ssl.SSLException: Received fatal alert: internal_error

启用 javax.net.debug=all 时的输出:

    [java] Thread-8, READ: TLSv1.2 Alert, length = 2
    [java] Thread-8, RECV TLSv1.2 ALERT:  fatal, internal_error
    [java] Thread-8, called closeSocket()

https://www.territrespicio.com 原因:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

启用 javax.net.debug=all 时的输出:

         [java] Thread-13, READ: TLSv1.2 Alert, length = 2
         [java] Thread-13, RECV TLSv1.2 ALERT:  fatal, handshake_failure
         [java] Thread-13, called closeSocket()

这些问题都与 TrustManager 无关,注释掉 HostnameVerifier 部分始终允许连接正常工作。

注意事项:

  • Java 版本:1.8.0_111
  • 很遗憾,我无法切换到 apache httpclient(围绕 HttpURLConnection 构建了很多代码)

【问题讨论】:

    标签: java ssl https httpurlconnection


    【解决方案1】:

    您的问题是由于 JDK Custom HostnameVerifier disables SNI extension 中的错误引起的。你也可以在这个帖子里看到Extended server_name (SNI Extension) not sent with jdk1.8.0 but send with jdk1.7.0

    从 jdk 8u66 开始,使用自定义 HostnameVerifier 在握手期间不会发送 Extended server_name 扩展。此错误将在版本 8u152 中修复。

    这是使用您的代码调用https://www.territrespicio.com 的握手痕迹

    *** ClientHello, TLSv1.2
    RandomCookie:  GMT: 1467981635 bytes = { 253, 197, 8, 192, 170, 180, 230, 6, 212, 233, 219, 201, 182, 39, 204, 176, 49, 215, 43, 41, 112, 204, 188, 29, 115, 235, 191, 74 }
    Session ID:  {}
    Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
    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, SHA256withDSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
    ***
    main, WRITE: TLSv1.2 Handshake, length = 233
    main, READ: TLSv1.2 Alert, length = 2
    main, RECV TLSv1.2 ALERT:  fatal, handshake_failure
    main, called closeSocket()
    main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    Exception in thread "main" javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    

    这是删除的痕迹(工作)

    HttpsURLConnection.setDefaultHostnameVerifier(hv);
    

    *** ClientHello, TLSv1.2
    RandomCookie:  GMT: 1467981305 bytes = { 187, 36, 131, 239, 148, 148, 198, 107, 89, 74, 67, 33, 127, 76, 24, 17, 108, 254, 79, 104, 242, 239, 51, 36, 180, 244, 181, 45 }
    Session ID:  {}
    Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
    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, SHA256withDSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
    Extension server_name, server_name: [type=host_name (0), value=www.territrespicio.com]
    ***
    main, WRITE: TLSv1.2 Handshake, length = 264
    main, READ: TLSv1.2 Handshake, length = 89
    *** ServerHello, TLSv1.2
    RandomCookie:  GMT: -2137263708 bytes = { 105, 47, 249, 171, 64, 55, 195, 235, 198, 159, 46, 193, 42, 65, 156, 243, 134, 177, 35, 221, 75, 16, 222, 103, 42, 55, 103, 231 }
    Session ID:  {42, 151, 108, 66, 43, 195, 53, 201, 234, 24, 245, 14, 183, 242, 185, 128, 66, 115, 60, 35, 167, 159, 178, 238, 93, 155, 20, 195, 95, 155, 11, 79}
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    Compression Method: 0
    Extension renegotiation_info, renegotiated_connection: <empty>
    Extension ec_point_formats, formats: [uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2]
    ***
    

    可以看到在ClientHello中添加了需要的扩展

    Extension server_name, server_name: [type=host_name (0), value=www.territrespicio.com]
    

    没有记录在案的解决方法。 所以考虑使用 JDK 版本 =8u152

    已更新 -- 解决方法

    SNI client-side mystery using Java8查看@Dawuid的答案

    如果您使用自定义 SSLSocketFactory 而不覆盖 createSocket()(没有参数的方法),则使用参数化好的 createSocket 并且所有工作都按预期工作(带有客户端 sni 扩展)。

    您可以找到类似的解决方法here。我试过只用一个包装器,它可以工作。

    HttpsURLConnection.setDefaultSSLSocketFactory(
       new SSLSocketFactoryWrapper(sc.getSocketFactory()));
    

    SSLSocketFactoryWrapper(完整代码)

    package test;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    import javax.net.ssl.SSLSocketFactory;
    
    public class SSLSocketFactoryWrapper extends SSLSocketFactory {
    
        private SSLSocketFactory factory;
    
        public   SSLSocketFactoryWrapper(SSLSocketFactory factory){
            this.factory = factory;
        }
    
        /* 
        @Override
        public Socket createSocket() throws IOException {
            return  factory.createSocket();
        }
        */
    
        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {         
            return factory.createSocket(s, host, port, autoClose);
        }
    
        @Override
        public String[] getDefaultCipherSuites() {
            return factory.getDefaultCipherSuites();
        }
    
        @Override
        public String[] getSupportedCipherSuites() {
            return factory.getSupportedCipherSuites();
        }
    
        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
            return factory.createSocket(host, port);
        }
    
        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            return factory.createSocket(host, port);
        }
    
        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
                throws IOException, UnknownHostException {
            return factory.createSocket(host, port, localHost, localPort);
        }
    
        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return factory.createSocket(address, port, localAddress, localPort);
        }
    
    }
    

    【讨论】:

    • 感谢 pedrofb - 我正在努力使用 8u65 进行测试(我的构建环境导致延迟)。我注意到了这个错误,但因为我在最新的 jre 上而忽略了它(我想我应该注意到修复程序还没有发布!)。测试后会更新。再次感谢。
    • 再次感谢 pedrofb!我在我的环境中降级 JDK 时遇到了很多问题,所以我尝试了解决方法。它似乎在我的初始测试中运行良好。可以肯定的是,在接下来的几天里,我会给出很多 SSL 示例,但看起来很肯定解决方法是可行的方法(如果不是,那么努力降级 JDK 就应该这样做)。跨度>
    猜你喜欢
    • 1970-01-01
    • 2016-01-06
    • 2017-04-03
    • 1970-01-01
    • 1970-01-01
    • 2022-09-25
    • 2022-11-07
    • 2020-12-27
    • 2017-07-27
    相关资源
    最近更新 更多