英文:
java.security.cert.CertificateException: No subject alternative name found matching IP address even when using a FQDN
问题
我正在尝试从我的Java应用程序向 precision.epayworldwide.com
发送HTTPS请求。
这是我使用的代码:
URL url = new URL("https://precision.epayworldwide.com/up-interface");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String responseLine = reader.readLine();
while (responseLine != null)
{
System.out.printf("%s\n", responseLine);
responseLine = reader.readLine();
}
这是我得到的异常:
Exception in thread "main" org.bouncycastle.tls.TlsFatalAlert: certificate_unknown(46)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.checkServerTrusted(Unknown Source)
at org.bouncycastle.jsse.provider.ProvTlsClient$1.notifyServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsUtils.processServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.handleServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.processRecord(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.connect(Unknown Source)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:523)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1296)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
at Main.main(Main.java:17)
Caused by: java.security.cert.CertificateException: No subject alternative name found matching IP address 195.145.98.203
at org.bouncycastle.jsse.provider.HostnameUtil.checkHostname(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkExtendedTrust(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkTrusted(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkServerTrusted(Unknown Source)
... 18 more
我已经正确导入了该主机名的TLS证书:
Owner: C=GB,L=Billericay,O=Epay Limited,CN=*.epayworldwide.com
Issuer: C=US,O=DigiCert Inc,CN=DigiCert TLS RSA SHA256 2020 CA1
Serial number: 97cc7ab5601ca15b1e65530f3a3e8a3
Valid from: Mon Oct 10 02:00:00 CEST 2022 until: Sun Nov 05 00:59:59 CET 2023
Certificate fingerprints:
MD5: AF:C4:FA:F6:A2:99:D8:C3:C3:9A:1D:B7:A4:6D:D7:21
SHA1: 4D:55:B9:2B:69:67:2E:AA:2A:B4:3B:B6:D1:BC:35:77:B0:FD:50:A0
SHA256: 96:AF:91:70:80:F6:F1:9E:30:18:CC:97:53:10:B4:7E:B2:CC:37:31:77:CB:C1:E1:1C:14:BC:CF:19:08:04:3B
Signature algorithm name: SHA256WITHRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
我不明白为什么代码会抛出异常。我在StackOverflow上搜索过类似的问题(1,2,3),但这些人使用了IP地址连接到服务器,而不是FQDN。
我无法控制服务器,因此无法以任何方式更改证书。
有没有什么我可以在客户端执行而不禁用主机名验证?
英文:
I am trying to send an HTTPS request to precision.epayworldwide.com
from my Java application.
This is the code I am using:
URL url = new URL("https://precision.epayworldwide.com/up-interface");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String responseLine = reader.readLine();
while (responseLine != null)
{
System.out.printf("%s\n", responseLine);
responseLine = reader.readLine();
}
and this is the exception I get:
Exception in thread "main" org.bouncycastle.tls.TlsFatalAlert: certificate_unknown(46)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.checkServerTrusted(Unknown Source)
at org.bouncycastle.jsse.provider.ProvTlsClient$1.notifyServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsUtils.processServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.handleServerCertificate(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.processRecord(Unknown Source)
at org.bouncycastle.tls.RecordStream.readRecord(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown Source)
at org.bouncycastle.tls.TlsClientProtocol.connect(Unknown Source)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:523)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1296)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
at Main.main(Main.java:17)
Caused by: java.security.cert.CertificateException: No subject alternative name found matching IP address 195.145.98.203
at org.bouncycastle.jsse.provider.HostnameUtil.checkHostname(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkExtendedTrust(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkTrusted(Unknown Source)
at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkServerTrusted(Unknown Source)
... 18 more
I have correctly imported the TLS certificate of that hostname:
Owner: C=GB,L=Billericay,O=Epay Limited,CN=*.epayworldwide.com
Issuer: C=US,O=DigiCert Inc,CN=DigiCert TLS RSA SHA256 2020 CA1
Serial number: 97cc7ab5601ca15b1e65530f3a3e8a3
Valid from: Mon Oct 10 02:00:00 CEST 2022 until: Sun Nov 05 00:59:59 CET 2023
Certificate fingerprints:
MD5: AF:C4:FA:F6:A2:99:D8:C3:C3:9A:1D:B7:A4:6D:D7:21
SHA1: 4D:55:B9:2B:69:67:2E:AA:2A:B4:3B:B6:D1:BC:35:77:B0:FD:50:A0
SHA256: 96:AF:91:70:80:F6:F1:9E:30:18:CC:97:53:10:B4:7E:B2:CC:37:31:77:CB:C1:E1:1C:14:BC:CF:19:08:04:3B
Signature algorithm name: SHA256WITHRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
I don't understand why the code is throwing that exception. I have searched here on StackOverflow and I have found similar questions (1, 2, 3), but these people used an IP address to connect to the server, instead of an FQDN.
I do not control the server so I can't change the certificate in any way.
Is there something I can do client-side without disabling the hostname verification?
答案1
得分: 1
根据这个错误报告,HttpURLConnection仅在SunJSSE作为JSSE提供程序时使用SNI,但不适用于其他提供程序,比如您正在使用的BouncyCastle。这就是为什么您看到它尝试根据IP地址而不是完全限定域名(FQDN)来检查证书的原因:
由于:java.security.cert.CertificateException: 找不到匹配IP地址195.145.98.203的主题备用名称。
英文:
Based on this bug report HttpURLConnection will only use SNI with SunJSSE as JSSE provider but not with others like BouncyCastle - which you are using. That's why you see that it tries to check the certificate against the IP address instead of the FQDN:
> Caused by: java.security.cert.CertificateException: No subject alternative name found matching IP address 195.145.98.203
答案2
得分: 1
这是与HttpsURLConnection与第三方JSSE实现不正常工作的根本问题。
由于您直接打开URL(而不是通过某个难以修改的框架),最简单的解决方案可能是使用BCJSSE实用程序类org.bouncycastle.jsse.util.URLConnectionUtil
:
new URLConnectionUtil().openConnection(url)
如果未将BCJSSE配置为默认提供程序,您还可以将特定的SSLSocketFactory传递给URLConnectionUtil构造函数。
有关根本问题的背景,请参阅各种问题:
- https://github.com/bcgit/bc-java/issues/460
- https://github.com/bcgit/bc-java/issues/660
- https://github.com/bcgit/bc-java/issues/709
- https://github.com/bcgit/bc-java/issues/1331
英文:
This is an underlying issue with HttpsURLConnection not working properly with third-party JSSE implementations.
Since you are opening the URL directly (rather than via some framework that's hard to modify) the simplest solution is probably to use the BCJSSE utility class org.bouncycastle.jsse.util.URLConnectionUtil
:
new URLConnectionUtil().openConnection(url)
You can also pass a specific SSLSocketFactory to URLConnectionUtil constructor if BCJSSE is not configured as the default provider.
For background on the underlying problem refer to various issues:
答案3
得分: 0
Here is the translated content:
由于我不想更改代码,我查看了BouncyCastle的GitHub问题跟踪器,我找到了这个JVM属性:
从v1.70开始,如果您想使用选项1,请首选**
-Dorg.bouncycastle.jsse.client.assumeOriginalHostName=true
**,而不是-Djdk.tls.trustNameService=true
(参见问题460以及相关问题以获取更多参考信息)
因此,我已将此属性添加到我的JVM参数列表中,现在TLS握手到precision.epayworldwide.com
正常工作。
英文:
Since I didn't want to change the code, I digged in the BouncyCastle's Githb issue tracker and I found this JVM property:
> From v1.70 if you want to use option 1 please prefer
-Dorg.bouncycastle.jsse.client.assumeOriginalHostName=true
instead of
-Djdk.tls.trustNameService=true
(see issue 460 and linked issues for more references)
So I have added this property to my JVM arg list and now the TLS handshake to precision.epayworldwide.com
works.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论