0

I'm trying to make a HTTPS request from a Java 8 client (using Apache HttpClient 4.3) to a public server (that I don't own). I've never normally had any problems with the requests, but the target server appears to have made some changes and requests started to fail.

Initially, the error was an ValidatorException: PKIX Path Building Failed but I fixed this by adding the servers certificate to my Java trust store. The error I'm getting now is hostname in certificate didn't match: <target.server.com> != <*.something.else.com> OR <something.else.com>.

The something.else.com host is some sort of hosting site etc. anyway I'm satisfied there's nothing suspicious going on. I pulled the certificate for target.server.com by running the following command:

openssl s_client -connect target.server.com:443 -showcerts

I don't see any reference to target.server.com in the certificate info, only for something.else.com. I don't have any problems visiting target.server.com in a web browser (Chrome) and there are no complaints about the SSL certificate. I added the servers certificate to my trust store using this command:

sudo keytool -import -file /tmp/target.cer -keystore /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/cacerts -alias targetserv

Is there some way I can fix this without a Java code change e.g. adding something to the certificate import command etc. and if not, how else can I get these requests working again.


Here's my HTTP request code (using HttpClient 4.3):

CloseableHttpClient httpClient = HttpClients.custom().setUserAgent(HTTP_USER_AGENT).build();
HttpGet request = new HttpGet(url);
HttpResponse response = httpClient.execute(request);

...throws java.io.IOException: hostname in certificate didn't match

Also, this is the actual target server that I'm trying to reach: https://www.isi.gov.ie


I've tried using HttpClient v4.5 and I still get the same error as with 4.3. I've tried making a request using Java's built HTTP request classes, and the request succeeds (a 302 is returned):

URL obj = new URL("https://www.isi.gov.ie");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();

con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);

int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);

BufferedReader in = new BufferedReader(
        new InputStreamReader(con.getInputStream()));

String inputLine;
StringBuffer response = new StringBuffer();

while ((inputLine = in.readLine()) != null) {
    response.append(inputLine);
}
in.close();

//print result
System.out.println(response.toString());

Is there any way I can fix these issues with HttpClient?


It turns out that elsewhere in my application I had been disabling SNI using the Java system property jsse.enableSNIExtension and I had forgotten about it. The reason I was doing that is because it was the only fix I could find that would prevent ALL HTTP requests using HttpClient from failing with the error:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

How can I re-enable SNI without getting these errors?
I'm using the following version of Java:

openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03)
OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)
RTF
  • 6,214
  • 12
  • 64
  • 132
  • You've kind left out all the details here including your code so it's hard to understand where the problem is. If the browser accepts the connection then so should your code, without added the server certificate to your truststore either (unless you're attempting to do some kind of certificate pinning, which is a good idea in general). – President James K. Polk Jun 26 '19 at 14:01

2 Answers2

1

TLDR: it could be SNI (and chain)

That server apparently, like many today, supports multiple 'virtual' hosts with different certificates using the TLS extension 'Server Name Indication' aka SNI. If contacted using SNI for www.isi.gov.ie it provides a certificate which is valid for that domain and isi.gov.ie, but if contacted using no SNI it provides an expired certificate for the domains *.rdccremote.ie and rdccremote.ie; I take it those are what you call a 'hosting site', although I see no indication of that; my best google hit for it is this parliamentary question which seems to say authoritatively that it is on the government department's 'shared service' -- although it does not resolve to the same address (146.0.55.149) using the DNS easily accessible to me.

Further, both of these certs are issued by DigiCert, but in both cases the server does not send the chain/intermediate cert needed for validation, as TLS specifications require. Browsers usually can 'fill in' missing chain certs, but Java JSSE (and most other software tools) cannot; this probably caused your pathbuilding error, but does not affect the hostname mismatch.

openssl s_client in versions below 1.1.0 does not send SNI by default, and your redacted command doesn't indicate that you specified the option for it. (And -showcerts is useless on a server than doesn't send chain cert(s)). Also be aware s_client displays in decoded form only the Subject field of the cert (and Issuer, as s: and i: respectively), but not the SubjectAlt[ernative]Name aka SAN extension which is used for the hostname(s) of an SSL/TLS server cert since about the beginning of this century. To see SAN, either feed the PEM block from s_client through openssl x509 -text -noout or another decoding tool like keytool -printcert -file $pemfile or the cert viewer on Windows.

Java 8 up normally does send SNI, although this can be influenced by the calling code (application or middleware). AHC 4.3 is pretty old and I'm not sure about it, but 4.5 works for me on both Java 7 and 8 (Oracle, but AFAIK JSSE is identical between Oracle and OpenJDK). Could you (or anyone) have configured your JVM to prevent SNI with sysprop jsse.enableSNIExtension=false? Can you run with sysprop javax.net.debug=ssl and capture or log the (large) output, or alternatively capture and examine the wire data with wireshark, tcpdump, or similar, to verify what your Java is actually sending in ClientHello?

Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • Thanks for detailed response. I only remembered this after I read your response: I have been disabling SNI upon launch of my web server using the enableSNIExtension option you mentioned. I've been doing this all the way back to when I was using Java 7. The reason is because ALL my requests to ANY host using HttpClient were failing with the error `sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target`. Disabling SNI fixed this. Any ideas how I can turn it on? – RTF Jun 27 '19 at 13:50
  • There's no official way to turn SNI on for a single request, although you could try reflection similar to my comment on https://stackoverflow.com/questions/32068009/how-to-set-jsse-enablesniextension-to-false-only-for-a-single-httpurlconnectio . You could leave it on and turn if _off_ for a single request as in https://stackoverflow.com/questions/55903477/received-handshake-warning-unrecognized-name . But SNI _shouldn't_ cause _or_ fix pathbuilding problems, and I would investigate and (at least try to) fix the actual problem rather than lobbing 1000-lb bombs about. – dave_thompson_085 Jun 28 '19 at 15:30
0

I solve the same issuse with following way. when you setting up the sslContext, loadTrustMaterial with a TrustStrategy (which is the second parameter for loadTrustMaterial) - pass a trusStrategy that always return True, this way you can ignore the ceritificate verification from your client side.

            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream keyStoreFile = new FileInputStream(CERT_PATH);
        ks.load(keyStoreFile, CERT_PASSWORD.toCharArray());

        SSLContext sslContext = SSLContexts
                .custom()
                .loadKeyMaterial(ks, CERT_PASSWORD.toCharArray())
                .loadTrustMaterial(null, (chain, authType) -> true)
                .build();

        SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslContext);

        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory>create()
                .register(REST.HTTPS.LABEL, sslConnectionFactory)
                .register(REST.HTTP.LABEL, new PlainConnectionSocketFactory())
                .build();

        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(registry);

        httpClient = HttpClients
                .custom()
                .setConnectionManager(connManager)
                .setSSLSocketFactory(sslConnectionFactory)
                .build();
Dan Lu
  • 1