1

I'm using Java 8, trying to post https third party (other subdomain works), works with postman, but using RestTemplate throws SSLHandshakeException

new RestTemplate().postForEntity("https://external-host.com" ,new HttpEntity<>(null, new HttpHeaders()), String.class);

I have JCE Unlimited jars in jdk1.8.0_151\jre\lib\security\policy\unlimited folder and I have bouncy castle bcpkix-jdk15on and bcprov-jdk15on version 1.55

Exception:

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://external-host.com": 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:785)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:468)
...
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
SSLConnectionSocketFactory.java:353
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
DefaultHttpClientConnectionOperator.java:141
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
PoolingHttpClientConnectionManager.java:353
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
MainClientExec.java:380
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
MainClientExec.java:236
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
ProtocolExec.java:184
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
RetryExec.java:88
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
RedirectExec.java:110
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
InternalHttpClient.java:184
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
CloseableHttpClient.java:82
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
CloseableHttpClient.java:55
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:87)
HttpComponentsClientHttpRequest.java:87
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
AbstractBufferingClientHttpRequest.java:48
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
AbstractClientHttpRequest.java:66
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:776)
RestTemplate.java:776
    ... 42 more

Same output using other solutions configuring RestTemplate as:

TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

SSL log:

Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 for TLSv1.1
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1645533027 bytes = { 229, 64, 215, 234, 240, 91, 46, 176, 144, 108, 104, 176, 6, 192, 147, 200, 69, 213, 196, 106, 125, 235, 5, 167, 51, 215, 144, 174 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, secp384r1, secp521r1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA256withDSA, SHA224withECDSA, SHA224withRSA, SHA224withDSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
Extension server_name, server_name: [type=host_name (0), value=external-host.com]
***
ajp-nio-8009-exec-32, WRITE: TLSv1.2 Handshake, length = 211
ajp-nio-8009-exec-32, READ: TLSv1.2 Alert, length = 2
ajp-nio-8009-exec-32, RECV TLSv1.2 ALERT:  fatal, handshake_failure
ajp-nio-8009-exec-32, called closeSocket()
ajp-nio-8009-exec-32, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
08:42:43.535 [ajp-nio-8009-exec-32] TRACE o.s.web.servlet.DispatcherServlet - Failed to complete request

EDIT I download cer file from site, added to Java's cacerts and created p12 file, and tried using the following code, but still handshake exception

KeyStore clientStore = KeyStore.getInstance("PKCS12");
        clientStore.load(new FileInputStream(utils.getStoreProperty("./external.p12")),
                "MYPASS".toCharArray());

        SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
        sslContextBuilder.useProtocol("TLSv1.2");
        sslContextBuilder.loadKeyMaterial(clientStore, "MYPASS".toCharArray());
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
        sslContextBuilder.loadTrustMaterial(null, acceptingTrustStrategy);
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build());
        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .build();
        

The following output for openssl s_client -connect host:443 as suggested @dave_thompson_085 , @yan

WARNING: can't open config file: /usr/local/ssl/openssl.cnf
CONNECTED(000001A4)
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority
verify error:num=19:self signed certificate in certificate chain
---
---
No client certificate CA names sent
Peer signing digest: SHA256
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5814 bytes and written 433 bytes
---
New, TLSv1/SSLv3, 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: A4966A2268EE5CCFA25DEE734DC980D01DF40A4763A4DF3CD19ADA49FF9AD90E
    Session-ID-ctx:
    Master-Key: 58BE6AF39E3DB3CBF3166A286550F2333028E66A9CC59AE886EAA777BAEA82A21D318E89746B97B1BFE0E3E7BF60F5E1
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 25 c2 24 26 c2 8f d1 6d-32 5e 2f f6 40 95 af d6   %.$&...m2^/.@...
    0010 - 02 de 28 3e 34 ae 47 96-2c 6a 87 2e 61 e6 fd a2   ..(>4.G.,j..a...
    0020 - 75 3b 3c 3b b2 ee 3c 16-ba e5 49 1c 18 f6 a1 16   u;<;..<...I.....
    0030 - e3 4e 7b 6f 48 5d e7 e3-03 df c5 86 45 81 12 5b   .N{oH]......E..[
    0040 - 4e d7 27 da f5 cd ed 18-6d 41 00 55 7a 8e 62 54   N.'.....mA.Uz.bT
    0050 - 31 75 90 3c aa 2a f9 2b-51 c2 c0 10 5d ca 02 6f   1u.<.*.+Q...]..o
    0060 - 51 0e e0 62 6d 94 12 bd-85 4b 01 88 dc 5d 90 ad   Q..bm....K...]..
    0070 - 30 53 8c 09 a7 01 d9 d7-1b 89 ec 77 35 93 9f ae   0S.........w5...
    0080 - b1 00 c7 ba 1c ea 84 77-36 bf 58 59 7a 78 44 f2   .......w6.XYzxD.
    0090 - 77 55 f4 41 2b dd 3c 54-02 38 ae 37 ec 8a c6 10   wU.A+.<T.8.7....
    00a0 - 6e d5 23 0a 05 5c 19 9f-02 4d 9a 0a 1c 9a be 2e   n.#..\...M......

    Start Time: 1645945411
    Timeout   : 300 (sec)
    Verify return code: 19 (self signed certificate in certificate chain)

EDIT I followed @taleb answer , but same handshake exception remains

Add the bcprov-.jar to /usr/lib/jvm/jre/lib/ext

Edit /usr/lib/jvm/jre/lib/security/java.security adding the following line to the list of providers:

security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider

(I added it as the 6th entry but you can add higher in the order if you prefer)

Restarted my application

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • is it possible Postman has SSL validation turned off? If you access the site in any browser is the certificate trusted? or possibly run`openssl s_client -connect external-host.com:443` to see the certificate presented. you can also start the app with `java -Djavax.net.debug=all` to see what's happening https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html. https://access.redhat.com/solutions/973783 – Yan Feb 23 '22 at 04:09
  • @Yan postman ssl validation is on, updating question with debug all output – Ori Marko Feb 23 '22 at 06:47
  • (@Yan: _received_ handshake_failure is _never_ due to mistrust of the server cert) OP: Many things _can_ cause handshake_failure and cannot be distinguished from the alert itself. The best approach to check server log(s) to see what _it_ says is the problem. If you can't do that, get a network trace of the successful postman handshake -- or use `openssl s_client -connect host:443` (as suggested) plus if below 1.1.1 `-servername host` and if that works add `-debug` -- and compare _every significant field_ of the working case to your non-working case. – dave_thompson_085 Feb 25 '22 at 09:14
  • You could also run openssl with a specific cipher (as explained [here](https://security.stackexchange.com/questions/93143/how-to-pass-cipher-list-to-openssl-s-client)) to check if the server accepts any of your Java cipher suites. – Olivier Feb 27 '22 at 09:04

1 Answers1

4

Please, be aware that the cipher suites described in your debug output doesn't show the cipher suite that was actually used by openssl, ECDHE-RSA-AES256-GCM-SHA384. In fact, they don't include any cipher suite that requires AES 256. It may not be of relevance, but it may be a symptom of any misconfiguration, and can explain why the handshake is failing. As indicated in the Oracle documentation when describing Java 8 supported cipher suites:

Cipher suites that use AES_256 require installation of the JCE Unlimited Strength Jurisdiction Policy Files.

As a consequence, please, be sure you installed and properly configured the JCE Unlimited Strength Jurisdiction Policy Files.

As indicated by @dave_thompson_085 in his excellent comment, only Oracle Java 8 below 8u161 requires adding the unlimited policy, as stated in Appendix C of the aforementioned Oracle documentation.

The JCE Unlimited Strength Jurisdiction Policy Files are bundled into the JDK since JDK 8u151, but the unlimited policy was not defined as the default one since JDK 8u161.

In JDK 8u151 or 8u152, as stated in one of the previous cited links, and explained as well by @dave_thompson_085 - thank you very much again, in order to make the unlimited version of the JCE the one that should be used, you need to define the system property crypto.policy. From the docs:

This release introduces a new feature whereby the JCE jurisdiction policy files used by the JDK can be controlled via a new Security property. In older releases, JCE jurisdiction files had to be downloaded and installed separately to allow unlimited cryptography to be used by the JDK. The download and install steps are no longer necessary. To enable unlimited cryptography, one can use the new crypto.policy Security property. If the new Security property (crypto.policy) is set in the java.security file, or has been set dynamically by using the Security.setProperty() call before the JCE framework has been initialized, that setting will be honored. By default, the property will be undefined. If the property is undefined and the legacy JCE jurisdiction files don't exist in the legacy lib/security directory, then the default cryptographic level will remain at 'limited'. To configure the JDK to use unlimited cryptography, set the crypto.policy to a value of 'unlimited'. See the notes in the java.security file shipping with this release for more information.

The issue is not present in OpenJDK.

As an alternative solution, as suggested in this related SO question, probably using an alternate provider like BouncyCastle could be of help as well.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • *"One thing you could try is configuring Apache HttpClient to trust the server certificate"* As @dave_thompson_085 stated in a comment, the handshake occurs *before* the validation of the server cert. – Olivier Feb 27 '22 at 15:31
  • 1
    *"depending on the specific JDK 8 version you are using, TLS 1.0 and 1.1 may be disabled"* The log provided in the question shows that TLS 1.2 is being used, so that's not the problem. – Olivier Feb 27 '22 at 15:33
  • Thank you very much for the feedback @Olivier. I think the answer try providing as much information as possible, at least it was my intention. I tried to indicate how to debug the TLS session, it links to several related SO questions with information that I think it could be valuable. You are right, TLS 1.2 is shown in both traces, the one from Java and the one from `openssl`, so probably it is not the issue: I edited the original answer to include that information, with the idea to give further advice, but I will remove it now. Sorry, I didn't realize the comment from Dave: as I told, I just – jccampanero Feb 27 '22 at 16:01
  • try providing a working configuration to try to shed some light in the issue. I will wait for some feedback from the OP, but remove the answer if necessary. Again, thank you very much for the feedback, I appreciate it a lot. – jccampanero Feb 27 '22 at 16:16
  • @Olivier I redacted the answer from scratch. Again, thank you very much for pointing out me my initial error, I appreciate it a lot. – jccampanero Feb 27 '22 at 17:03
  • Good catch. Nit: only _Oracle_ Java 8 _below 8u161_ requires adding the unlimited policy, as stated more exactly in Appendix C of your first link, and on [this newer download page](https://www.oracle.com/java/technologies/javase-jce-all-downloads.html) instead of the one you linked. _OpenJDK_ (once it became available separately from Sun/Oracle) did not have the policy issue. – dave_thompson_085 Feb 27 '22 at 17:32
  • Thank you very much @dave_thompson_085, and thank you very much for your excellent comment. I included those references in the answer. Please, excuse me for initially not realizing the comment you posted on the question. Again, thank you very much! – jccampanero Feb 27 '22 at 22:37
  • I have JCE Unlimited jars in jdk1.8.0_151\jre\lib\security\policy\unlimited folder and I have bouncy castle bcpkix-jdk15on and bcprov-jdk15on version 1.55 – Ori Marko Feb 28 '22 at 06:05
  • I follow @taleb answer in linked answer, but same handshake exception remains – Ori Marko Feb 28 '22 at 07:51
  • @user7294900+ 8u151/2 is unique; the policy files are _present_ in the install (you don't need to download) but _disabled_; as the release note says, you must **_change_ crypto.policy=unlimited in JRE/lib/security/java.security**. Using another provider like Bouncy does NOT bypass the policy limit, and the Q (and _correct_ A) you link is about a missing _algorithm_ (EC) not low strength. – dave_thompson_085 Feb 28 '22 at 07:52
  • Thank you very much for the feedback @user7294900. As indicated in the [documentation I linked](https://www.oracle.com/java/technologies/javase/8u151-relnotes.html#JDK-8157561) If you are using JDK 8u151, you need to configure the system property `crypto.policy` to a value `unlimited` in order to enable JCE Unlimited Strength Jurisdiction Policy Files. This setting is not the default until JDK 8u161. Can you try? I am sorry, I will update the answer to clarify that point. – jccampanero Feb 28 '22 at 07:54
  • Sorry for the late reply. That is great user7294900, I am happy to hear that the answer was helpful and you manage to solve the problem. @dave_thompson_085 thank you very much for your new comment, I think we posted roughly the same information at the same time!! Thank you very much for your help, I appreciate it a lot. – jccampanero Feb 28 '22 at 18:24
  • Thank you, @jccampanero I had this problem for hours and nearly went even more insane. I tried importing certificates, which of course didn't work. After I saw your post, I updated the JDK8 to a newer version, which worked. Putting `crypto.policy=unlimited` in `java.security` didn't work for me. I upgraded the JDK from `1.8.0_111` to `1.8.0_181` – 98percentmonkey Apr 25 '23 at 09:25
  • You are welcome @98percentmonkey. That is nice, I am very happy to hear that the answer was helpful and you were able to solve your problem. – jccampanero Apr 25 '23 at 21:19