in Code

Solving “Received fatal alert: handshake_failure” error when performing HTTPS connections on a custom made JRE with jlink

TL; Tell me already what to do:
Add the jdk.crypto.cryptoki module to the list of --add-modules parameter to your jlink command invocation


If you’re reading this you’re one of the few developers out there that wanted to distribute a java 9+ app (using either jdk 9, jdk 10, jdk 11 or jdk 12, as of this writing) with a smaller version of the jdk, to build your custom jre, you used the jlink tool.

When you run your app using the full JRE that comes with the OpenJDK, your app is working fine when it comes to making https requests, but when you run your app using your custom jre you get the following error when opening https connections

Received fatal alert: handshake_failure

This issue occurs because your JRE is missing lots of Cipher Suites that come with the full JDK.

With your JDK, you can try to check the list of supported ciphers with this one liner using the jrunscript tool:

jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"

however that might not work for your custom JRE if you haven’t included the scripting module, so here’s a Java program I made that prints all the available Ciphers of your JRE

public class PrintCiphers {
    public static void main(String[] args) {
	var sslSocketFactory = javax.net.ssl.SSLServerSocketFactory.getDefault();
	System.out.println("SSLServerSocketFactory -> " + sslSocketFactory.getClass().getName());
	try {
  	    var getSupportedCipherSuitesMethod = sslSocketFactory.getClass().getMethod("getSupportedCipherSuites");
	    String[] ciphers = (String[]) getSupportedCipherSuitesMethod.invoke(sslSocketFactory);
	    int i=1;
            for (String c : ciphers) {
		System.out.println(i++ + " " + c);
	    }
	} catch(Throwable t) {
	    t.printStackTrace();
	}
    }
}

If you run PrintCiphers on your OpenJDK’s full JRE, you will see almost 50 Cipher Suites available:

$ java PrintCiphers;
1 TLS_AES_128_GCM_SHA256
2 TLS_AES_256_GCM_SHA384
3 TLS_CHACHA20_POLY1305_SHA256
4 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
5 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
6 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
7 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
8 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
9 TLS_RSA_WITH_AES_256_GCM_SHA384
10 TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
11 TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
12 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
13 TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
14 TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
15 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
16 TLS_RSA_WITH_AES_128_GCM_SHA256
17 TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
18 TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
19 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
20 TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
21 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
22 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
23 TLS_RSA_WITH_AES_256_CBC_SHA256
24 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
25 TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
26 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
27 TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
28 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
29 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
30 TLS_RSA_WITH_AES_256_CBC_SHA
31 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
32 TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
33 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
34 TLS_DHE_DSS_WITH_AES_256_CBC_SHA
35 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
36 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
37 TLS_RSA_WITH_AES_128_CBC_SHA256
38 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
39 TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
40 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
41 TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
42 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
43 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
44 TLS_RSA_WITH_AES_128_CBC_SHA
45 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
46 TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
47 TLS_DHE_RSA_WITH_AES_128_CBC_SHA
48 TLS_DHE_DSS_WITH_AES_128_CBC_SHA
49 TLS_EMPTY_RENEGOTIATION_INFO_SCSV

but if you use your custom JRE to run PrintCiphers you will see only 23 Cipher Suites available:

$ jre/bin/java PrintCiphers
1 TLS_AES_128_GCM_SHA256
2 TLS_AES_256_GCM_SHA384
3 TLS_CHACHA20_POLY1305_SHA256
4 TLS_RSA_WITH_AES_256_GCM_SHA384
5 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
6 TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
7 TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
8 TLS_RSA_WITH_AES_128_GCM_SHA256
9 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
10 TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
11 TLS_RSA_WITH_AES_256_CBC_SHA256
12 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
13 TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
14 TLS_RSA_WITH_AES_256_CBC_SHA
15 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
16 TLS_DHE_DSS_WITH_AES_256_CBC_SHA
17 TLS_RSA_WITH_AES_128_CBC_SHA256
18 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
19 TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
20 TLS_RSA_WITH_AES_128_CBC_SHA
21 TLS_DHE_RSA_WITH_AES_128_CBC_SHA
22 TLS_DHE_DSS_WITH_AES_128_CBC_SHA
23 TLS_EMPTY_RENEGOTIATION_INFO_SCSV

To solve the problem you must add the jdk.crypto.cryptoki module to the list of --add-modules parameter to your jlink command invocation, next time your run PrintCiphers you should see the full list of Cipher Suites and your SSL handshake issues should be gone.

Write a Comment

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.