收到握手警告:无法识别的名称

时间:2019-04-29 12:31:45

标签: java http

在Java 11中是否有一种方法可以处理“ javax.net.ssl.SSLHandshakeException:收到握手警告:无法识别的名称”,而无需通过使用禁用SNI系统范围

System.setProperty("jsse.enableSNIExtension", "false")

使用此系统属性将导致对主机的任何后续请求都将失败,具体取决于SNI。所以基本上我确实需要按需解决方案。

非常具体:我正在尝试从https://www.minervamedica.it网站获取内容,该网站似乎对Java> 8有问题。

我确实尝试过这种方法:https://javabreaks.blogspot.com/2015/12/java-ssl-handshake-with-server-name.html

        final TrustManager[] trustAllCerts = new TrustManager[] { new X509ExtendedTrustManager() {
            @Override
            public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s)
                    throws CertificateException {
            }

            @Override
            public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s)
                    throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s,
                    final Socket socket) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s,
                    final Socket socket) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s,
                    final SSLEngine sslEngine) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s,
                    final SSLEngine sslEngine) throws CertificateException {
            }
        } };

        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        URL url = new URL("https://www.minervamedica.it");
        SSLParameters sslParameters = new SSLParameters();
        List<SNIServerName> sniHostNames = new ArrayList<>();
        sniHostNames.add(new SNIHostName(url.getHost()));
//      sniHostNames.add(new SNIHostName("minervamedica.it"));
        sslParameters.setServerNames(sniHostNames);
        SSLSocketFactory wrappedSSLSocketFactory = new SSLSocketFactoryWrapper(sslContext.getSocketFactory(), sslParameters);

        HttpsURLConnection connection = (HttpsURLConnection) url
                .openConnection();
        connection.setSSLSocketFactory(wrappedSSLSocketFactory);
        connection.setDoOutput(true);
        connection.setRequestMethod("GET");
        System.out.print(connection.getResponseCode());

SSLSocketFactoryWrapper

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class SSLSocketFactoryWrapper extends SSLSocketFactory {

    private final SSLSocketFactory wrappedFactory;
    private final SSLParameters sslParameters;

    public SSLSocketFactoryWrapper(SSLSocketFactory factory, SSLParameters sslParameters) {
        this.wrappedFactory = factory;
        this.sslParameters = sslParameters;
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port);
        setParameters(socket);
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
            throws IOException, UnknownHostException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port, localHost, localPort);
        setParameters(socket);
        return socket;
    }


    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port);
        setParameters(socket);
        return socket;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(address, port, localAddress, localPort);
        setParameters(socket);
        return socket;

    }

    @Override
    public Socket createSocket() throws IOException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket();
        setParameters(socket);
        return socket;
    }

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

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

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(s, host, port, autoClose);
        setParameters(socket);
        return socket;
    }

    private void setParameters(SSLSocket socket) {
        socket.setSSLParameters(sslParameters);
    }

}

编辑30.04.2019:

同样无法在Java 11上运行:

URL url = new URL("https://www.minervamedica.it");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier((s, sslSession) -> true);
System.out.println(new String(conn.getInputStream().readAllBytes()));

尽管使用了自定义HostnameVerifier并始终返回true,但这也会导致异常,并显示“收到握手警告:无法识别的名称”。


编辑02.05.2019

这显然是服务器配置错误(请参见下文)。

openssl s_client -servername www.minervamedica.it -connect www.minervamedica.it:443 -state

显示

CONNECTED(00000003)
SSL_connect:before SSL initialization
SSL_connect:SSLv3/TLS write client hello
SSL3 alert read:warning:unrecognized name
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS read server hello
depth=0 CN = minervamedica.it
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = minervamedica.it
verify error:num=21:unable to verify the first certificate
verify return:1
SSL_connect:SSLv3/TLS read server certificate
SSL_connect:SSLv3/TLS read server key exchange
SSL_connect:SSLv3/TLS read server done
SSL_connect:SSLv3/TLS write client key exchange
SSL_connect:SSLv3/TLS write change cipher spec
SSL_connect:SSLv3/TLS write finished
SSL_connect:SSLv3/TLS write finished
SSL_connect:SSLv3/TLS read server session ticket
SSL_connect:SSLv3/TLS read change cipher spec
SSL_connect:SSLv3/TLS read finished
---
Certificate chain
 0 s:/CN=minervamedica.it
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFbTCCBFWgAwIBAgISA7svnlD9ZgJAww8LaYbVvgQ2MA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA1MDIwNjAyNDlaFw0x
OTA3MzEwNjAyNDlaMBsxGTAXBgNVBAMTEG1pbmVydmFtZWRpY2EuaXQwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuJUYVVxEhkHmGlPBT/zKZ6NrGuKls
pYSeeJsa8mMWwCf5b0ZOe8jS8w36EHvTUbHHOqd63zXjsFDkt14SmNunogSCSYvq
8+UmHPudv2q4ygPLY728bU5YpXVXaBh6hcJmfckCs0WnxLbPFC3rJdlC77syDbpi
O/fX5XY7cmzB7gCH3MmKltGzk3oQDYst4IIFZZV11Hk1VVDJ7MAb23E4PINKEJwJ
5IqFJRjko3nVvKEY+FVv0Bl4N7PN8xl9M+Xw4Bcp8sUaGmgbRSPAbPj2S1LWoRq+
dUFyqsmmks0YsdMbuRfkjWUuJ5h0MUtpW0yCbJIFtUgEysJzREgfTX5NAgMBAAGj
ggJ6MIICdjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG                                                          
AQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLjAn5EGedAmqtkEk2OHsrJw                                                          
cOW6MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEB                                                          
BGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0
Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0
Lm9yZy8wLwYDVR0RBCgwJoISKi5taW5lcnZhbWVkaWNhLml0ghBtaW5lcnZhbWVk
aWNhLml0MEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYI
KwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBBQYKKwYBBAHW
eQIEAgSB9gSB8wDxAHYA4mlLribo6UAJ6IYbtjuD1D7n/nSI+6SPKJMBnd3x2/4A
AAFqd1pUHgAABAMARzBFAiEAsPuYjgg2alECcOV36YTwGawdYOi2dcPuTUHN7FL2
kJUCIGnckeJZKe1Xb9tJA3YkuxptbOFlHEUBAaDQEiz49CMJAHcAKTxRllTIOWW6
qlD8WAfUt2+/WHopctykwwz05UVH9HgAAAFqd1pUBQAABAMASDBGAiEAx0NQhPfK
FpAPHJ8ZU6BxLGl4gXMND4FxuMVsGb+pfxYCIQDv0lsXnvPmEIQdCMero8IyjrYk
L8K9f1zVbSAFn/6PxzANBgkqhkiG9w0BAQsFAAOCAQEAOf0IE45r4ytrFtFXrVMY
ATpt/UcTgJvqgapg4KsQSr4k007MZtxeALRn6B5KdekGhhKzIlHz6O/JD4+95Btv
mempZgo166Nr4sf4UMfNsENNqUX1jgT2i74Ss6058t6YtTanuNdrokL/mMxSynIt
5O49srcpEwhTvIaeKq84DLd6Es9OcBuRAJZCEw/SGtLypkC0PSSHayuGvJjssDX6
RB+CkftpKJC9c6M+5e1fXjMHDHrUPukS467vs0Ky5jK0ZzFna7NAQtip7XY1TyAi
b6AdnLLGbKt6DIb6eOnTf5aIMatyTCRAVcVKSSqdtAhX0aS33/iXFPbApF3E0GyQ
gA==
-----END CERTIFICATE-----
subject=/CN=minervamedica.it
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 2106 bytes and written 331 bytes
Verification error: unable to verify the first certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: 008EB1ED9FEA6E2FE477231DB86F669C3C44792C2A02A80FCBB955186E141C86
    Session-ID-ctx: 
    Master-Key: 7745BDCCE5390A15586866EBA311DDCA90BD75AA7D91D5825A23DEF83B6C88CD56BFFC53ECDBA67271BFD8AB720D8522
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - ef 8d 8c e9 3a 3c 79 d9-74 cd 3f f2 f2 d7 55 4a   ....:<y.t.?...UJ
    0010 - a0 45 9b a9 f0 22 2b 13-19 8c d5 8b be 57 be 6c   .E..."+......W.l
    0020 - 38 ab f6 92 21 a4 ef 93-20 bf c2 f9 53 ee df 96   8...!... ...S...
    0030 - a0 68 fe ab ff 5e e0 85-c7 7f 2f 4d f7 b6 c6 7f   .h...^..../M....
    0040 - 6b d1 42 ff ab 96 eb 1e-1b ef 98 f4 68 bb ee 45   k.B.........h..E
    0050 - 0a f1 0b 4e 88 41 95 fc-b9 a2 9a 93 38 21 bd 6e   ...N.A......8!.n
    0060 - 84 9d 54 d7 27 d5 c9 94-87 b6 03 29 5d c7 87 07   ..T.'......)]...
    0070 - 99 ee c3 27 5a 57 02 19-66 fe 89 43 d5 b6 bb 90   ...'ZW..f..C....
    0080 - 4c ce fb 3c da 91 75 75-e7 99 a4 87 7c 92 57 d3   L..<..uu....|.W.
    0090 - f3 5b 5d 62 45 82 27 97-d8 8a 0d c3 e1 f3 7b b8   .[]bE.'.......{.
    00a0 - fd 28 1f 59 7f 74 a2 29-ae 11 c4 b4 ef c0 65 23   .(.Y.t.)......e#
    00b0 - 48 6e c2 a3 fc fa cf 05-56 f0 ce 2c 36 54 02 b9   Hn......V..,6T..
    00c0 - a2 12 ef 86 cb 8d bd ae-b0 ff 4c 0c a2 72 36 11   ..........L..r6.

    Start Time: 1556795487
    Timeout   : 7200 (sec)
    Verify return code: 21 (unable to verify the first certificate)
    Extended master secret: no
---

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
SSL3 alert read:warning:close notify
closed
SSL3 alert write:warning:close notify

但是,尽管如此,Java 11似乎无法像以前的版本那样处理这种情况。使用自定义HostnameVerifier似乎被完全忽略了。这是Java错误吗?

更新11.05.2019

已打开错误报告:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8223677

编辑12.05.2019

正如dave_thompson_085正确指出的那样,在Java 8和Java 11中,行为是相同的:总是抛出异常。

解决此问题直至Java 8的唯一方法是使用org.apache.http,但这不适用于Java 11等较新的Java版本:

package test;

import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultHttpResponseParserFactory;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import util.NoSNISSLSocketFactory;

public class TestSNI {

    public static void main(String[] args) {

         HttpEntity entity = null;

            try {

                final BasicCookieStore cookieStore = new BasicCookieStore();
                final HttpClientContext localContext = HttpClientContext.create();
                localContext.setCookieStore(cookieStore);

                // accept all certificates
                final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                    public boolean isTrusted(final X509Certificate[] arg0, final String arg1)
                            throws CertificateException {
                        return true;
                    }
                }).build();

                // set NoopHostnameVerifier()
                final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                        .<ConnectionSocketFactory> create().register("http", new PlainConnectionSocketFactory())
                        .register("https", new SSLConnectionSocketFactory(
                                new NoSNISSLSocketFactory(sslContext.getSocketFactory()), new NoopHostnameVerifier()))
                        .build();

                final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory(
                        new DefaultHttpRequestWriterFactory(),
                        new DefaultHttpResponseParserFactory());

                final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
                        socketFactoryRegistry, connFactory);

                final CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();

                final HttpGet httpGet = new HttpGet("https://www.minervamedica.it");
                final CloseableHttpResponse rp = httpclient.execute(httpGet, localContext);

                entity = rp.getEntity();

                if (entity != null) {
                    System.out.println(EntityUtils.toString(entity));
                }

            } catch (final ClientProtocolException e) {
                System.out.println(e);
            } catch (final IOException e) {
                System.out.println(e);
            } catch (final Exception e) {
                System.out.println(e);
            } finally {
                EntityUtils.consumeQuietly(entity);
            }
    }

}

所以看来Java> 8仍然没有办法解决这个问题?

编辑12.05.2019

进一步的调试显示,在Java 11上未调用NoopH​​ostnameVerifier,该重写重写了从javax.net.ssl.HostnameVerifier验证的方法以返回true,该异常已在NoSNISSLSocketFactory中发生,该NoSNISSLSocketFactory使用空主机扩展了SSLSocketFactory:< / p>

    @Override
    public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose)
            throws IOException {
        return sslSocketFactory.createSocket(socket, "", port, autoClose);
    }

因此看来,在Java 8上使用空主机创建Socket确实禁用了SNI,而在Java 11上似乎并非如此?

1 个答案:

答案 0 :(得分:0)

好吧,尽管不确定不确定我的推荐方式如何,我还是从源头上找到了一种方法。

尽管SSLParameters.setServerNames的javadoc并未这么说,但是如果值集为空的List(没有元素),则ClientHandshaker实际上根本不发送SNI。我怀疑这是因为RFC e.g. for 1.2将最小大小指定为1,禁止使用空列表。 (与TLS和SSL中的证书消息中的certificate_list比较;在SSL中,最小大小为1,并且没有适合服务器请求的证书和密钥的客户端根本没有发送消息,而在TLS中,它为0,并且明确指定了没有适当cert&key的客户端以发送包含空列表的消息。)虽然这是合乎逻辑的,但是由于既没有文档也没有明确评论,所以我很不愿意依靠它。

由于直接确定所需的其他参数非常复杂(且脆弱),我认为最好的方法是从现有参数开始并进行修改,例如为SSLSocket

SSLSocket s = SSLSocketFactory.getDefault() /* or other */ .createSocket("host", 443);
SSLParameters p = s.getSSLParameters();
p.setServerNames( new ArrayList<SNIServerName>() );
/* or j9+ p.setServerNames( List<SNIServerName>.of() ); */
s.setSSLParameters(p);
...

对于HttpsURLConnection,您原来的SSLSocketFactoryWrapper方法非常接近,除了如上所述,我将根据创建的SSLSocket的实际参数进行修改,并且必须使用空的new ArrayList<SNIServerName>()和而不是.add 任何

非常类似的事情对于Apache HttpClient应该适用,但是我没有经历过,因为我发现这令人讨厌,就像迷宫般的小类迷宫一样。

PS:消息来源还证实了为什么更改sysprop jsse.enableSNIExtension无效的原因;像第一次加载JSSE时一样读取和缓存(和许多其他记录一样),随后不读取。您可以使用反射来闯入该类并更改缓存的值,但是我们不要去那里了。