Updated: July 2, 2024
Java applets and warnings: do you want both? I’m sure you don’t.
We have extensive experience in using java applets. They really help to speed up your system significantly, still at the same time they may cause issues for the client and their users such as annoying warning messages. Actually it can be easily solved if you know how to do that.
We would like to share a piece of advice with you about how to avoid security problems with java applets. You are always welcome to leave your comments.
Problem definition:
As a WYSIWYG XML editor in our application we use java applet Oxygen Author Component. Java shouldn’t throw out any scaring warnings about dangerous code while loading this applet on the client. It should calmly and silently load this applet without stressing the user and making him take responsibility. We have a serious application after all.
How the applet works in layman’s terms
When a tag <applet> in HTML page is detected, browser passes the applet load control to the corresponding java-plug-in, that in its turn passes the control to the JRE installed on the client’s computer. There are two ways of applet loading (under applet we understand some java application, that represents a set of jars, where there is a main jar with main class implementing class Applet):
- Applet’s jar files, that need to be loaded and from where they need to be loaded, are listed in the tag Applet;
- With a jnlp file, where this information and a lot of other options and arguments are indicated.
In our case we use jnlp. The jnlp approach has the advantage, that if you change some applet loading parameters (e.g. codebase), you don’t have to change HTML or JavaScript code responsible for the applet loading. It’s enough to change just jnlp. Codebase change (codebase is a url address, that points, where the applet’s jars are) is quite an unpleasant problem, because you have to give the absolute URL, what means, that depending on what server the applet is launched, so has to be the codebase. On the local computer it is one address, on the QA stand the other one, in production – the third one. That’s why while assembling the application you definitely need to give the Context Path, in other words the absolute address of the web-application, where it’s going to work. Using jnlp file solves this problem in following way: there is a special servlet that on-the-fly changes the codebase to the current one when the jnlp file is loaded by the client’s java.
Let’s go
So, the applet loading begins with the jnlp file loading, there are there not only the list of necessary jar files and their codebase, but also java arguments (it is very important, I’ll tell you later why), that java has to start on the client’s computer with. Java starts loading jars and checking their safety. Here java security tool takes effect, which starts checking the loading jar, before the classes will be loaded from it. This tool is rather complicated and multisided: general safety level adjustment while loading applets and java security policy on the client (permission grants), safety parameters in the manifests of the jars, jar’s digital signature verification checking etc. I don’t want to go deep into this and I’m going to touch only the aspects, that are really important in the described task.
for information: applet signing means jar files signing. Jar signing is a performance of java-operation signjar, as a result of which in the jar the information about the key appears, that was used for signing in encrypted form. And also each resource, packed by jar, is associated with some code, encrypted by this key, that contains the information about the content of this resource. So, if you’re trying to change the signed jar giving it e.g. some class or changing the old one, such a jar is getting invalid and it’s not going to pass a security test and to be loaded.
So, the applet can be not signed, self-signed and signed by the trusted certificate. Depending on the client’s java version, where the applet loads, the signing level of the applet has an effect on whether the applet will be loaded at all or blocked, loaded with lots of warnings and messages like “If you start this applet, the global disaster comes and everything disappears, so start it on your own risk”.
It could be also loaded with a nice and not scaring message that can be not repeated in the future, if you tick it. And finally we’ve come to the task itself. At the moment of solving this problem our Oxygen applet was self-signed. It means that there was a signature. But it was fake – we made up a private key and generated a public key for it.
Of course during the loading of such an applet java expressed extreme dissatisfaction, but as the final result it loaded the applet. Actually we needed to get rid of these messages, that’s why we needed a certificate from Trusted Certificate Authority.
How do we get it? It’s easy, but not very cheap. A certificate for a year costs about $500. We used the services of www.verisign.com. You have to create a keystore with your alias and other information about the publisher (with a utility keytool) and form a special request (and pay, of course). In reply CA sent the keys. We got three of them: Code Signing certificate, intermediate CA certificate and certificate in pkcs7 format. For JKS type certificate we’re going to need intermediate and Code Signing certificate. First we add intermediate certificate into the earlier created keystore, and then – the main Code Signing certificate. Received keystore is going to be used for jar signing.
If your jars were signed earlier (in my case they were self-signed), you have to delete old signatures, before signing them again. If you don’t do this, java will drown during the applet loading on the first jar and stop working. You can delete the signatures manually – delete files .RSA (or .DSA) and .SF from the folder META-INF and delete all the Digest resources’ signatures from the manifest file.
Almost forgot: before jar signing you have to add security attributes into the manifest:
Permissions: all-permissions Codebase: * Caller-Allowable-Codebase: * Application-Library-Allowable-Codebase: *
Starting from the 51st update of the 7th java all jars without security attributes will be automatically blocked.
Here is an ant script for that:
<target name="addSecurityProperty"> <jar file="${jarFile}" update="true"> <manifest> <attribute name="Permissions" value="all-permissions"/> <attribute name="Codebase" value="*"/> <attribute name="Application-Library-Allowable-Codebase" value="*"/> <attribute name="Caller-Allowable-Codebase" value="*"/> </manifest> </jar> </target> <target name="addSecurityProperties" if="hasForEach"> <foreach target="addSecurityProperty" param="jarFile"> <path> <fileset dir="lib" includes="**/*.jar, **/*.zip"/> </path> </foreach> </target>
Important: you are going to need antcontrib for using foreach in this script.
So, we clean jars, add necessary attributes to manifests, sign, start and… Warning again!
Again? It turns out that you have to sign not only jars, but also jnlp file. How to sign it? Like this. Jnlp file has to be put into your main jar in the directory. JNLP-INF and the file name has to be exactly APPLICATION.JNLP. so, we add into our ant script, that builds main jar and signs jars, a simple code that copies the initial jnlp into the signed jnlp.
<target name="compile"> <mkdir dir="classes"/> <javac srcdir="src" destdir="classes" includeantruntime="false" debug="on"> <classpath> <fileset dir="lib"> <include name="*.jar"/> </fileset> </classpath> </javac> <mkdir dir="classes/JNLP-INF"/> <copy file="author-component-dita.jnlp" tofile="classes/JNLP-INF/APPLICATION.JNLP" overwrite="true"/> </target>
I’ll explain what happens in this ant’s target. Everything is clear in the part one – we create directory classes and compile the code of our applet in it. Please, notice, that during the compilation a folder lib will be added into the classpath. In this folder there are a lot of jars necessary for the applet and all of them have to be signed. Then in the same place a folder JNLP-INF will be created and our initial author-component-dita.jnlp will be copied in it.
Then we pack everything into the jar (this is our main jar) and put it into the folder lib to the rest of the jars.
Now we have two jnlp files: initial author-component-dita.jnlp and APPLICATION.JNLP packed into the jar. Something wrong… We start the applet – error!
What now? These jnlp files don’t match, but they have to. The initial jnlp is used for loading the applet, and the packed one is used to check the signature, they should not be different. But why are they different? They are copies! Now remember our servlet (JnlpDownloadServlet), that is used to simplify the deploy of our web application. Using it we can use the variable $$CODEBASE and don’t write into jnlp a certain codebase (e.g. localhost:8888/oxygen-editor/). The servlet changes jnlp during runtime substituting in it necessary values of variables. That’s why the jnlp to be loaded doesn’t match with the signed one. What do we have to do? It’s simple: we have to use APPLICATION-TEMPLATE.JNLP instead of APPLICATION.JNLP. Using APPLICATION-TEMPLATE.JNLP pattern has such an aspect, that it can be different from the initial jnlp, if you give “*” instead of certain parameters, for example, codebase=”*”. Let’s change the ant’s build.xml:
<target name="compile"> <mkdir dir="classes"/> <javac srcdir="src" destdir="classes" includeantruntime="false" debug="on"> <classpath> <fileset dir="lib"> <include name="*.jar"/> </fileset> </classpath> </javac> <mkdir dir="classes/JNLP-INF"/> <copy file="author-component-dita.jnlp" tofile="classes/JNLP-INF/APPLICATION-TEMPLATE.JNLP" overwrite="true"/> <replace file="classes/JNLP-INF/APPLICATION-TEMPLATE.JNLP" token="@@CODEBASE@@" value="*"/> <replace file="classes/JNLP-INF/APPLICATION-TEMPLATE.JNLP" token="@@HREF@@" value="*"/> </target>
So, is that all? Will I see during the applet loading a long-expected user-friendly message with a blue shield saying that the applet is trusted, safe and doesn’t raise any suspicions? I’m very excited and I finally launch the application, load the applet and…
Yes! I did it! There is the message with the blue shield! Extremely happy I tick “Always trust content from this publisher”, it closes, the applet starts loading and now we get this:
What the hell – what’s insecure there again? It took me two days to find this line in jnlp file:
<j2se java-vm-args="-Xmx512m -XX:MaxPermSize=80m -Xss4m -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5021" version="1.6+" />
There are java arguments the applet starts with. Well, debug arguments are insecure. And if your applet is signed by the trusted certificate, these arguments are forbidden. It is interesting, that if your applet is self-signed, you can launch the applet with everything you want, because the client is warned, that the running application is unknown and initially dangerous.
Here is the full list of forbidden arguments:
// note: this list MUST correspond to native secure.c file private static String[] secureVmArgs = { "-d32", /* use 32-bit data model if available */ "-client", /* to select the "client" VM */ "-server", /* to select the "server" VM */ "-verbose", /* enable verbose output */ "-version", /* print product version and exit */ "-showversion", /* print product version and continue */ "-help", /* print this help message */ "-X", /* print help on non-standard options */ "-ea", /* enable assertions */ "-enableassertions", /* enable assertions */ "-da", /* disable assertions */ "-disableassertions", /* disable assertions */ "-esa", /* enable system assertions */ "-enablesystemassertions", /* enable system assertions */ "-dsa", /* disable system assertione */ "-disablesystemassertions", /* disable system assertione */ "-Xmixed", /* mixed mode execution (default) */ "-Xint", /* interpreted mode execution only */ "-Xnoclassgc", /* disable class garbage collection */ "-Xincgc", /* enable incremental gc. */ "-Xbatch", /* disable background compilation */ "-Xprof", /* output cpu profiling data */ "-Xdebug", /* enable remote debugging */ "-Xfuture", /* enable strictest checks */ "-Xrs", /* reduce use of OS signals */ "-XX:+ForceTimeHighResolution", /* use high resolution timer */ "-XX:-ForceTimeHighResolution", /* use low resolution (default) */ "-XX:+PrintGCDetails", /* Gives some details about the GCs */ "-XX:+PrintGCTimeStamps", /* Prints GCs times happen to the start of the application */ "-XX:+PrintHeapAtGC", /* Prints detailed GC info including heap occupancy */ "-XX:PrintCMSStatistics", /* If > 0, Print statistics about the concurrent collections */ "-XX:+PrintTenuringDistribution", /* Gives the aging distribution of the allocated objects */ "-XX:+TraceClassUnloading", /* Display classes as they are unloaded */ "-XX:SurvivorRatio", /* Sets the ratio of the survivor spaces */ "-XX:MaxTenuringThreshol", /* Determines how much the objects may age */ "-XX:CMSMarkStackSize", "-XX:CMSMarkStackSizeMax", "-XX:+CMSClassUnloadingEnabled",/* It needs to be combined with -XX:+CMSPermGenSweepingEnabled */ "-XX:+CMSIncrementalMode", /* Enables the incremental mode */ "-XX:CMSIncrementalDutyCycleMin", /* The percentage which is the lower bound on the duty cycle */ "-XX:+CMSIncrementalPacing", /* Automatic adjustment of the incremental mode duty cycle */ "-XX:CMSInitiatingOccupancyFraction", /* Sets the threshold percentage of the used heap */ "-XX:+UseConcMarkSweepGC", /* Turns on concurrent garbage collection */ "-XX:-ParallelRefProcEnabled", "-XX:ParallelGCThreads", /* Sets the number of parallel GC threads */ "-XX:ParallelCMSThreads", "-XX:+DisableExplicitGC", /* Disable calls to System.gc() */ "-XX:+UseCompressedOops", /* Enables compressed references in 64-bit JVMs */ "-XX:+UseG1GC", "-XX:GCPauseIntervalMillis", "-XX:MaxGCPauseMillis" /* A hint to the virtual machine to pause times */ };
Thank you for attention. I hope, the article will be helpful.
Do you know another ways of realisation of this task?