Search code examples
ssljavafxjavafx-webenginejlinkjava-14

javafx 16 WebEngine Exception "SSL Handshake failed"


I am porting an android app to javaFX for windows deployment, i'm new to javaFX and desktop deployment, but not so new to java.

The app contains a WebView that loads a url obtained from a server via Json (so could be essentially anything).

openJDK 14 , openJfx 16, intellij idea ultimate 2021.1.3, gradle: plugins: org.beryx.jlink & org.openjfx.javafxplugin

This works fine when I run the program in development and testing (on a windows 10 machine), but when it is packaged and deployed on a windows machine (any windows 10 so far) I get an "java.lang.Throwable: SSL handshake failed" exception when the page is loaded.

This is the stack trace:

    [ERROR] 2021-07-14 14:13:53.737 [JavaFX Application Thread] MediaElementWeb - WebView Failed: 
java.lang.Throwable: SSL handshake failed
    at javafx.scene.web.WebEngine$LoadWorker.describeError(WebEngine.java:1440) ~[javafx.web:?]
    at javafx.scene.web.WebEngine$LoadWorker.dispatchLoadEvent(WebEngine.java:1379) ~[javafx.web:?]
    at javafx.scene.web.WebEngine$PageLoadListener.dispatchLoadEvent(WebEngine.java:1240) ~[javafx.web:?]
    at com.sun.webkit.WebPage.fireLoadEvent(WebPage.java:2524) ~[javafx.web:?]
    at com.sun.webkit.WebPage.fwkFireLoadEvent(WebPage.java:2369) ~[javafx.web:?]
    at com.sun.webkit.network.URLLoaderBase.twkDidFail(Native Method) ~[javafx.web:?]
    at com.sun.webkit.network.URLLoader.notifyDidFail(URLLoader.java:799) ~[javafx.web:?]
    at com.sun.webkit.network.URLLoader.lambda$didFail$6(URLLoader.java:782) ~[javafx.web:?]
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:447) ~[javafx.graphics:?]
    at java.security.AccessController.doPrivileged(AccessController.java:391) ~[?:?]
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:446) ~[javafx.graphics:?]
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96) ~[javafx.graphics:?]
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) ~[javafx.graphics:?]
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174) ~[javafx.graphics:?]
    at java.lang.Thread.run(Thread.java:832) [?:?]

The problem is not specific to any particular certificate, so I know it is not a problem specifically with the certificate generally, tested with many sites. and I ONLY get this error in the deployed app.

The page is being loaded in a standard way: WebEngine.load(targetURL);

I am capturing the error with:

tNode.getEngine().getLoadWorker().stateProperty().addListener((o, ov, nv) -> {
            if (nv == Worker.State.FAILED) {
                logger.error("WebView Failed: ", tNode.getEngine().getLoadWorker().getException());
            }
        });

I have searched and tried solutions offered by other people that seem to have experienced similar errors, such as:

Setting a trust manager before calling load(page):

TrustManager[] trustAllCerts = new TrustManager[] {
                new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                    public void checkServerTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                }
        };
        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (GeneralSecurityException e) {
            logger.error("SSLContext Failed: ", e);
        }

(No errors or effect here)

and

settings the JVMarg on the deployment

-Dcom.sun.webkit.useHTTP2Loader=false

Which I can see has the effect of "com.sun.webkit.network.URLLoader" being shown in the stack trace instead of http2 (as was suggested on other threads) but also no change here either.

Has anyone any thoughts (bearing in mind I'm a novice at java desktop deployment) at what the issue can be and how to resolve it?

Many thanks

Update:

Trace from console on deployed test https://pastebin.com/R5SkR4w1

First few rows:

javax.net.ssl|WARNING|2C|URL-Loader-1|2021-07-15 10:46:45.991 BST|SignatureScheme.java:295|Signature algorithm, ed25519, is not supported by the underlying providers
javax.net.ssl|WARNING|2C|URL-Loader-1|2021-07-15 10:46:45.992 BST|SignatureScheme.java:295|Signature algorithm, ed448, is not supported by the underlying providers
javax.net.ssl|WARNING|2C|URL-Loader-1|2021-07-15 10:46:45.995 BST|NamedGroup.java:297|No AlgorithmParameters for x25519 (
"throwable" : {
  java.security.NoSuchAlgorithmException: Algorithm x25519 not available
        at java.base/javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:192)
        at java.base/sun.security.ssl.NamedGroup.<init>(NamedGroup.java:286)
        at java.base/sun.security.ssl.NamedGroup.<clinit>(NamedGroup.java:184)
        at java.base/sun.security.ssl.SignatureScheme.<clinit>(SignatureScheme.java:59)
        at java.base/sun.security.ssl.SSLSessionImpl.<clinit>(SSLSessionImpl.java:823)
        at java.base/sun.security.ssl.TransportContext.<init>(TransportContext.java:133)
        at java.base/sun.security.ssl.TransportContext.<init>(TransportContext.java:103)
        at java.base/sun.security.ssl.SSLSocketImpl.<init>(SSLSocketImpl.java:111)
        at java.base/sun.security.ssl.SSLSocketFactoryImpl.createSocket(SSLSocketFactoryImpl.java:72)
        at java.base/sun.net.www.protocol.https.HttpsClient.createSocket(HttpsClient.java:413)
        at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:162)
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:474)
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:569)
        at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:265)
        at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:372)
        at ...

Solution

  • Managed to solve this with helpful comments from @slaw about a suggestion I had tried initially, but ultimately not implemented correctly, and help from @dave_thompson_085 with how to provide extra debugging info. So the resolution, and a few tips for people in the same boat:

    1. The solution:

    The Badass Jlink Plugin for gradle on Intellij IDEA, for Java 14 and JavaFX 16 was not correctly merging the security provider classes. This was resolved by adding "jdk.crypto.ec" to the merged modules list.

    2. Adding the module manually

    The org.beryx.jlink plugin (2.24.0) is really complex and powerful, so it was a struggle to work out how to do it with my implementation.

    I tried many combinations, but the following code did it for me in my build.gradle:

    jlink  {
        //... Other jlink \ jpackage stuff
        mergedModule {
            additive = true
            requires 'jdk.crypto.ec'
        }
        //... Other jlink \ jpackage stuff
    }
    

    3. Debugging packaged java binary for windows

    Some Jlink config changes that helped along the way.

    jlink  {
        //..
        jpackage {
            //..
            imageOptions = [
                    "--win-console"
            ]
        }
        //..    
        launcher {
            jvmArgs = ['-Dcom.sun.webkit.useHTTP2Loader=false','-Djavax.net.debug=ssl:handshake']
        }
    
    }
    

    --win-console opens a console window when you start your program so you can see the logging out put

    -Djavax.net.debug=ssl:handshake gives more information about the SSL process and handshaking so you can see what is happening.