0

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?

Ricky Sixx
  • 581
  • 1
  • 10
  • 25

3 Answers3

1

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

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
1

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:

Peter Dettman
  • 3,867
  • 20
  • 34
0

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.

Ricky Sixx
  • 581
  • 1
  • 10
  • 25