Saturday, January 25, 2014

So you are having trouble with Webstart on Java 7u51?

The Joys of Java Webstart and Java 7u51

Well, it seemed like a great idea back in 2004 to adopt Java Webstart for our enterprise solution.
10 years later, we are still using it and Oracle has done it's best to make sure we have to make substantial changes to our desktop app to comply with the latest security baseline.

Starting with Java 7u45 and maximizing the pain with 7u51, there are new restrictions for what Oracle calls RIAs (applets and Webstart applications), that will affect you if you are in a similar situation, especially if your customers are setting the Java security settings in the Java Control panel to High, Very High, Ultra High or Insanely High I Won't Run Anything That Starts With CA FE BA BE.

Here are the stumbling blocks you will encounter and how to solve them.

#0 I don't wanna change my stuff


In this case, good luck convincing the IT department of your Fortune 100 customer to use Exception Site Lists (which may or may not work. In our testing you had to clear out the Webstart cache for every start) or Deployment Rule Sets (yeah, good luck with telling their IT to copy a signed JAR file into  c:\Windows\Sun\Java\Deployment for each desktop).
Deployment Rule Sets do work, but someone needs to pay for a proper certificate for the DeploymentRuleSet.jar signature. Which brings us to ...

#1 You need a proper code-signing certificate

Gone are the times where an innocent self-signed certificate will get you access to the user's local machine. You will have to cough up the cash to have your certificate signed.

From the dozens of vendors our there, chose one that has a root certificate in the JRE's cacerts keystore.

keytool -v -list -keystore /Library/Java/Home/lib/security/cacerts

Here's a tool to get you started that will create a new keystore as well as a certificate signing request (CSR) to send to the code signer.

Note that I don't endorse any of those vendors. They are greedy bastards, making you pay on a yearly basis for a one-time activity. Where else is that ok? Right, if you have children :)

I would also strongly advise to get on the phone with the code signing authority. The information you provided in the Common Name and Company Name field is likely not correct and if you change that information you need to create another CSR. You can save a lot of hassle by getting this out of the way before you submit your CSR.

If you do have to change the information in your key, some vendors offer to re-key your key quickly. If you did everything correct, importing the signed certificate into your keystore should not throw an error message.

Damn. I get an error message running keytool.

keytool -import -trustcacerts -alias server -file your_domain_name.p7b -keystore your_site_name.jks 
 
Well, check the certificate in the file provided with the one in the keystore. The easiest way to do this is to use Keystore Explorer, an excellent little tool that saved me from losing my sanity.
You might have to re-issue a CSR or you might have to insist on a SHA-1 key from the signer.

If all goes well, you have a nice new keystore with a certificate that you should use to sign all JAR files referenced by your JNLP file.

Oh no, you have Bouncy Castle JARs in there that are already signed?
No worries, sign them with your signature as well. The trick how to do it is here!

#2 You need a main JAR and new entries in your META-INF/MANIFEST.MF


Declare one of your JAR files as main in the JNLP file:

<jar href="lib/yourMain.jar" main="true"/>

In yourMain.jar, add a Permissions entry:

Permissions: all-permissions

You can add a Codebase entry as well, but often times the codebase isn't known in advance.
It's ok. You can specify it in the JNLP file:

<jnlp ...  codebase="http://enterprisesoftwareforzewin.com">

Luckily you don't need to add the Permission header to all of your JARs, just the main one.

#3 You need to say good-bye to your old property entries in the JNLP file

This one took the most doing. In its wisdom, Oracle decided that most of the system properties you use are not OK anymore.

Now have fun finding those pieces in your code which read those properties.
Some properties are considered safe, but you'll probably have to rename all your properties and prefix them with either jnlp. or javaws. :

<property name="jnlp.var.dir" value="${user.home}/.mylittleconfigdirectory"/>

That is a particular joy if you use 3rd party libraries that don't expect that.
Like log4j. If you used <property name="log4j.configuration" value="..../log.cfg"/> in your JNLP file, say hello to configuring Log4j in your main method manually!
Especially painful if your log.cfg referenced other system properties.

Here's what I had to do:
String url = System.getProperty("jnlp.log4j.configuration");
      if (url != null) {
          try {
              Properties p = new Properties();
              URL logURL = new URL(url);
              InputStream in = logURL.openStream();
              p.load(in);
              // replace log.dir with jnlp.log.dir
              p.setProperty("log.dir", System.getProperty("jnlp.log.dir"));
              in.close();
               PropertyConfigurator.configure(p);
            } catch (Exception e) {
                System.err.println("Unable to configure log4j with "+ url);
                e.printStackTrace();
            }

Haven't touched the code of our Launcher class since 2004. Joy! Code Archeology!

Trick: If you are unsure what properties are passed to your app, use jconsole to connect to the launcher process (you can do it while it shows you a dialog telling you how bad your app sucks and that it can't run it).

#4 You are done. Nope, you are not. Signing the JNLP file (is optional)


There's much confusion about this.
Here's what will happen if you run your Webstart app now without JNLP signing.
Your users will get a warning message with a nice 'Run' button and your app will start nevertheless.
That happens with Medium, High and Very High security settings.



If you get other error messages, you screwed something up earlier.
Set the security setting to Medium which gives you slightly more informative error messages.

Signing your JNLP file is easy if your JNLP file never changes.
This link describes the process. There is some wiggle room with attributes like codebase in which case you want to use an APPLICATION_TEMPLATE.JNLP template.
Put this stuff in your main JAR file in the right directory (and sign it of course).

If your JNLP file is created dynamically with different resources, you are out of luck buddy. And I'm too, because we use the horrors of JSP to create a JNLP file just for you.

In that case, consider creating a new single main class that starts your usual main class, and just use this class along with the APPLICATION_TEMPLATE.JNLP to start your app. You can probably dynamically create that JAR file and sign it on the fly. (Probably not a good idea to have your keystore file on your webserver though)
You could even try to use an <extension> tag with the rest of your app's resources.

#5 Test and test some more with the different JREs


Goes without saying. You might even have to tell your users to delete their temporary files in the Java Control panel.


Since I'm feeling all 2004ish now, I'll ask you all to share this on MySpace.


3 comments :

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. Awesome post! Thanks, beder, for a lucid (and hilarious) explanation! This is is exactly what I needed.

    ReplyDelete
  3. Thanks for the informative article, especially point #4. I was hoping a standard method existed for signing a dynamic jnlp... I still haven't given up on trying wildcards for both href and attribute fields, though I'm a ways off from trying that all out. Cheers.

    ReplyDelete