2

I am using jpackage to distribute a java app on Mac and PC and I have a handshake_failure when it tries to load an image using https from certain sites. The code works fine if I run it from Eclipse or from the command line, both on Mac and PC, but not if run it as a packaged app. The problem goes away if I load an image from certain sites: https://st4.depositphotos.com, for example. This makes me think that the “problem sites” are not in the chain of trust.

But why should the chain of trust be different when running as a packaged app?

The same behaviour is seen using java 14.0.2 and 15. The following example uses OpenJDK Runtime Environment (build 15+36-1562).

Note that jpackage integrates a runtime into the app. This is a non-modular app (I show the jpackage options used below).

Debugging on the Mac and using the -Djavax.net.debug=all option, I looked for the trustStore being used by the packaged app. The printed path was invalid as it started with /Applications, rather than /Volumes, but apart from that it was ok. Maybe this was just a printing problem? Either way, I used -Djavax.net.ssl.trustStore=/Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home/lib/security/cacerts to force the packaged app to use the same trustStore as the JDK and this didn’t improve matters.

Do you think that my code is buggy or is there a problem in jpackage? Many thanks for any help!

Here is the packaging command:

--verbose \
--type pkg \
--input HelloTest \
--name HelloTest \
--main-class HelloTest.HelloTest \
--main-jar HelloTest.jar \
--runtime-image target/java-runtime \
--java-options -Djavax.net.ssl.trustStore=/Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home/lib/security/cacerts \
--java-options -Djavax.net.debug=all \
--vendor "ACME Inc." \
--copyright "Copyright © 2019-20 ACME Inc." \
--mac-package-identifier com.acme.app \
--mac-package-name ACME

Here is the code:

public class HelloTest{

    public static void main(String... args) throws IOException {

        System.out.println("javax.net.ssl.trustStore = " + System.getProperty("javax.net.ssl.trustStore"));
        
        JFrame f = new JFrame(); //creates jframe f
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); //this is your screen size
        int halfWidth = screenSize.width/2;
        int halfHeight = screenSize.height/2;

        ImageIcon img = new ImageIcon();
        //OK: ... https://st4.depositphotos.com seems to be well trusted
        // String urlName = "https://st4.depositphotos.com/36188500/38581/i/1600/depositphotos_385811360-stock-photo-woman-lingerie-dog-rose.jpg";
        //KO: ... https://en.iconda.solutions is only trusted when code is run from Eclipse or the command line
        String urlName = "https://en.iconda.solutions/wp-content/uploads/2020/07/getting_equipped.png";
        
        JLabel lbl = new JLabel();
        URL url;
        
        try {
         url = new URL(urlName);
         HttpsURLConnection httpsConnection = (HttpsURLConnection)url.openConnection();

            try {
                /* The following works from Eclipse and from the command line, but not from an app with an integrated runtime
                 * that was produced using jpackage ... */
                try {
                    
                    img = new ImageIcon(ImageIO.read(httpsConnection.getInputStream())
                            .getScaledInstance(screenSize.width, screenSize.height, Image.SCALE_SMOOTH));
        
                } catch(Exception e) {
                    System.out.println("went wrong #1 for " + urlName);
                    e.printStackTrace();
                }
                
            } catch(Exception e) {
                System.out.println("went wrong #2 for " + urlName);
                e.printStackTrace();
            }
            
        } catch (MalformedURLException e) {
         e.printStackTrace();
        } catch (IOException e) {
         e.printStackTrace();
        }

        lbl.setIcon(img);
        
        f.getContentPane().add(lbl); //puts label inside the jframe
        f.setSize(halfWidth, halfHeight); // set frame size to half of screen ... but need to resize the image
        int x = (screenSize.width - f.getSize().width)/2; //These two lines are the dimensions
        int y = (screenSize.height - f.getSize().height)/2;//of the center of the screen
        f.setLocation(x, y); //sets the location of the jframe
        f.setVisible(true); //makes the jframe visible
    }
    
}

And here are a few lines from the debug output:

$ /Applications/HelloTest.app/Contents/MacOS/HelloTest ; exit;
javax.net.ssl.trustStore = /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home/lib/security/cacerts
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:05.852 CEST|null:-1|System property jdk.tls.client.cipherSuites is set to 'null'
…
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.130 CEST|null:-1|trustStore is: /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home/lib/security/cacerts
trustStore type is: pkcs12
trustStore provider is: 
the last modified time is: Wed Aug 12 02:19:32 CEST 2020
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.131 CEST|null:-1|Reload the trust store
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.283 CEST|null:-1|Reload trust certs
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.286 CEST|null:-1|Reloaded 91 trust certs
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.430 CEST|null:-1|adding as trusted certificates (
  "certificate" : {
    "version"            : "v3",
    "serial number"      : "00 A6 8B 79 29 00 00 00 00 50 D0 91 F9",
    "signature algorithm": "SHA384withECDSA",
    "issuer"             : "CN=Entrust Root Certification Authority - EC1, OU="(c) 2012 Entrust, Inc. - for authorized use only", OU=See www.entrust.net/legal-terms, O="Entrust, Inc.", C=US",
…
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.445 CEST|null:-1|keyStore is : 
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.445 CEST|null:-1|keyStore type is : pkcs12
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.445 CEST|null:-1|keyStore provider is : 
javax.net.ssl|ALL|01|main|2020-09-17 07:27:06.445 CEST|null:-1|init keystore
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.446 CEST|null:-1|init keymanager of type SunX509
javax.net.ssl|ALL|01|main|2020-09-17 07:27:06.447 CEST|null:-1|trigger seeding of SecureRandom
javax.net.ssl|ALL|01|main|2020-09-17 07:27:06.449 CEST|null:-1|done seeding of SecureRandom
javax.net.ssl|DEBUG|01|main|2020-09-17 07:27:06.476 CEST|null:-1|System property jdk.tls.client.SignatureSchemes is set to 'null'
javax.net.ssl|WARNING|01|main|2020-09-17 07:27:06.478 CEST|null:-1|Signature algorithm, ed25519, not supported by JSSE
javax.net.ssl|WARNING|01|main|2020-09-17 07:27:06.479 CEST|null:-1|Signature algorithm, ed448, not supported by JSSE
javax.net.ssl|WARNING|01|main|2020-09-17 07:27:06.480 CEST|null:-1|No AlgorithmParameters for x25519 (
"throwable" : {
  java.security.NoSuchAlgorithmException: Algorithm x25519 not available
    at java.base/javax.crypto.KeyAgreement.getInstance(Unknown Source)
    at java.base/sun.security.ssl.NamedGroup.<init>(Unknown Source)
…
javax.net.ssl|ERROR|01|main|2020-09-17 07:27:09.230 CEST|null:-1|Fatal (HANDSHAKE_FAILURE): Received fatal alert: handshake_failure (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source)

Following the helpful comments on my question, here is an example packaging script that solves the issue:

detected_modules=`jdeps \
  -q \
  --ignore-missing-deps \
  --print-module-deps \
  --class-path "MyApp.jar:../sandbox/jars/*" \
  -recursive MyApp.jar \
    MyApp/MyApp.class`
echo "detected modules: ${detected_modules}"

manual_modules=jdk.crypto.cryptoki
echo "manual modules: ${manual_modules}"

rm -rf ../runtime

jlink \
  --no-header-files \
  --no-man-pages  \
  --compress=2  \
  --strip-debug \
  --add-modules "${detected_modules},${manual_modules}" \
  --output ../runtime

jpackage \
--verbose \
--type pkg \
--input ../sandbox \
--dest ../output \
--name MyApp \
--app-version $1 \
--main-class MyApp.MyApp \ 
--main-jar MyApp.jar \
--runtime-image ../runtime \
--mac-package-name MyApp 
andyb
  • 21
  • 2
  • "_Received_ fatal alert: handshake_failure" is definitively not due to any problem involving your truststore (although that server does send me an incomplete chain which would fail validation with the default truststore, causing a very different exception). It most likely is due to something your system(s) sent, in the part you have redacted with an ellipsis, but it could also be other things like the IP or location or ISP you're sending from or even environmental things like day, time, or recent election or football results. – dave_thompson_085 Sep 17 '20 at 08:53
  • Solved. Need to add jdk.crypto.cryptoki to the --add-modules list in jlink. – andyb Sep 17 '20 at 09:32
  • @Dave: Many thanks! You were right - it was due to something else in my system. I am not sure where encryption fits into the picture yet, but at least the packaged app is working. – andyb Sep 17 '20 at 09:37

1 Answers1

4

Found this on the Interwebs. Same no such algorithm error from the looks of it.

java.security.NoSuchAlgorithmException: Algorithm x25519 not available

The solution from the link:

Need to add jdk.crypto.cryptoki to the --add-modules list in jlink.

Henry F
  • 41
  • 2
  • Hi @Henry F, It might be more useful if you describe the steps on how to add this library and potentially add some screen shots. – Jacques Ramsden Sep 17 '20 at 14:15
  • This works. I'll add an example of a packaging script that incorporates the idea. Thanks to Henry for finding the path to the solution! – andyb Sep 18 '20 at 14:11
  • While this works, apparently it is working because jdk.crypto.cryptoki has a dependency on jdk.crypto.ec which is what is actually missing. So if you --add-module jdk.crypto.ec it fixes this issue as well. cryptoki adds support for a SUN PKCS11, which I didn't need. – JoeBun Dec 04 '20 at 23:08