3

I'm failing to send simple GET request to 3rd party https URL which works fine in browser:

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://...": Received fatal alert: handshake_failure; nested exception is javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:673)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:635)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:556)

I try follow related answers, but I didn't find solution.

There's no certificate and I'm using Java 8, tried solutions as adding -Djsse.enableSNIExtension=false -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1

Added same headers as browser sends (Accept and User-Agent) , but no luck

Code

UriBuilder uriBuilder = UriBuilder.fromUri(URLDecoder.decode(URL, "UTF-8"));
HttpHeaders headers = new HttpHeaders();
headers.add("Accept", "text/html,application/xhtml+xml,application/xml");
headers.add(HttpHeaders.USER_AGENT, "Mozilla...");
HttpEntity<?> entity = new HttpEntity<Object>(headers);
ResponseEntity<ResponseVO> response = restTemplate.exchange(uriBuilder.build(), 
       HttpMethod.GET, entity, ResponseVO.class);
  • Site uses cloudflare services

Also curl is working by putting full url, from verbose output it uses Server certificate with:

SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Tried also with configuration skip SSL certificate verification with same output:

TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom() .loadTrustMaterial(null, acceptingTrustStrategy) .build();

Or also with NoopHostnameVerifier:

CloseableHttpClient httpClient = HttpClients.custom() .setSSLHostnameVerifier(new NoopHostnameVerifier()) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient);

curl -v results Server certificate:

*       subject: CN=*.site.com,OU=Domain Control Validated
*       start date: May 24 08:13:23 2019 GMT
*       expire date: May 24 08:13:23 2021 GMT
*       common name: *.site.com
*       issuer: CN=Go Daddy Secure Certificate Authority - G2,OU=http://certs.godaddy.com/repository/,O="GoDaddy.com, Inc.",L=Scottsdale,ST=Arizona,C=US

EDIT

I added certificate to java as @Walk suggested:

sudo keytool -importcert -file filename.cer -alias randomaliasname -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit 

Certificate was added to keystore

I see certificate loaded as in browser, it works in JMeter, but still failed with same error using restTemplate or apache HTTPClient.

I'm using Java 8 update 151.

Tried solution from @rmunge answer to add BouncyCastleProvider, but still same error

security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • Can you do the request using cURL or similar? Maybe your browser has a different trust-store than Java or something like that. – Smutje May 26 '20 at 12:25
  • @Smutje curl is working by putting full url – Ori Marko May 26 '20 at 12:28
  • Is certificate check is enabled? – Nish May 26 '20 at 12:58
  • @Nish it happens also with default restTemplate as `new RestTemplate()` – Ori Marko May 31 '20 at 12:04
  • I believe @Smutje can be right. Perhaps your java trust-store does not have the server CA certificate, you can try to add it to your java trust-store, that would be my first try. – Henrique Droog Jun 01 '20 at 08:52
  • @HenriqueDroog can't resttemplate accept any (/ignore) certificate? – Ori Marko Jun 01 '20 at 09:08
  • @Smutje can resttemplate be configure to accept any (/ignore) certificate? – Ori Marko Jun 01 '20 at 10:20
  • @user7294900 Could you give us the SSL-related part of the log when java runs with `-Djavax.net.debug=all` flag? Please also show `curl -v` output after `Server certificate:` line. Does Subject of subjectAltName match the server's name? Who is the issuer? My bet is that after you add the issuer's certificated to the Java trust store, it will start working. – Pak Uula Jun 02 '20 at 01:54
  • @PakUula Edit the curl -v output, the site seem correct, I rename it to site.com, about debug=all flag, there are alot of logs, no Go Daddy reference – Ori Marko Jun 02 '20 at 06:23
  • @user7294900, the debugging output must contain the reason why handshake failed. Feel free to send the trace to my private email pakuula@gmail.com, I'll check and tell what the reason could be. – Pak Uula Jun 03 '20 at 01:49
  • Is it possible to share the url? Or a similar url which you are able to share which is giving the same exception – Hakan54 Jun 04 '20 at 13:23

2 Answers2

2

The root cause is most likely missing support for latest EC-based cipher suites like TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Elliptical Curve Cryptography (ECC) is implemented by the SunEC provider. To be fully functional the following is required:

  • SunEC has to be listed in jre/lib/security/java.security and the value of jdk.tls.disabledAlgorithms must not contain ECDHE
  • The native library libsunce.so / sunce.dll has to be available in jre/lib (Linux, Mac) or jre/bin (Windows) folder
  • Ensure that the JDK JCE framework uses the unlimited policy. Since Java 8u161 this is the new default, for older versions you may have to install the JCE Unlimited Strength Jurisdiction Policy Files and / or make changes in the java.security file. See here for further instructions.

If the native library is not available the provider still works but provides only a subset of ECC-based ciphers (see comments in source of SunEC.java). It seems that some linux distributions explicitely removed the native library or disabled the provider by default (e.g. RedHat, Amazon Linux). So if the library should not be part of your JRE, update to the latest package version or directly download and install latest OpenJDK 8 version - simply copying the native lib from the download could also be an option - see here for example.

Another option would be to use a third-party cryptography provider like Bouncy Castle which has its own Provider for ECC. For instruction see this question and its accepted answer.

rmunge
  • 3,653
  • 5
  • 19
  • It's true I have `Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` in ssl log – Ori Marko Jun 04 '20 at 10:58
  • I can't execute `jrunscript -e 'exit (javax.crypto.Cipher.getMaxAllowedKeyLength("RC5") >= 256);' || if [ $? -eq 1 ]; then echo "JCE Installed"; else echo "JCE Not Installed or Error"; fi` error `$? was unexpected at this time.` and `libsunec.so` file isn't found in openjdk github project – Ori Marko Jun 04 '20 at 11:05
  • `javax.crypto.Cipher.getMaxAllowedKeyLength("RC5") >= 256` return false – Ori Marko Jun 04 '20 at 11:20
  • openjdk github project only contains the corresponding source: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/share/native/sun/security/ec/impl The OpenJDK builds from https://adoptopenjdk.net/ for example contain the libsunec.so file. My downloaded 64-bit linux build from adoptopenjdk.net contains the file in jre/lib/amd64 – rmunge Jun 04 '20 at 12:30
  • What exact java version do you have? – rmunge Jun 04 '20 at 12:36
  • version 1.8.0_191-b12 – Ori Marko Jun 04 '20 at 12:37
  • Starting with 8u161 OpenJDK ships by default with unlimited cryptography: https://bugs.openjdk.java.net/browse/JDK-8189377 The easiest would be update to latest Java 8 build from adoptopenjdk.net. This version should contain the native library and cryptography should be unlimited by default. – rmunge Jun 04 '20 at 12:50
1

Can you please specify which version of Java you are using!!! If Java 7 then this will do the job for you.....

I can see that your client resolves to TLSv1. From openssl output that your server does not support TLSv1.

TLS ver. 1.1 and 1.2 are disabled in Java 7 by default.

Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and refuse to talk to TLS 1.1 or TLS 1.2 clients. For interoperability, SunJSSE does not enable TLS 1.1 or TLS 1.2 by default for client connections.

Enable TLSv1.1 and TLSv1.2 either by:

  1. JVM argument:

    -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
    
  2. Or set the same property from Java code:

    System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1");
    
  3. Or install JCE Unlimited Strength policy files for Java 7. I am not 100% sure if this single step would solve the problem although it is always worth to install JCE while it allows JVM to use stronger versions of existing algorithms.

Note: Order of protocols changed from better to worse (TLS ver. 1.2 to 1) in options 1 and 2.

Kunal Vohra
  • 2,703
  • 2
  • 15
  • 33