在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上未调用NoopHostnameVerifier,该重写重写了从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上似乎并非如此?
答案 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时一样读取和缓存(和许多其他记录一样),随后不读取。您可以使用反射来闯入该类并更改缓存的值,但是我们不要去那里了。