Javascript, Java and Signed Applets
1. |
Background |
Wanted: to have a Web browser open a local file, make changes via a textarea in a HTML form, then save the updated version to local disk. Not wanted: any requirement for a server (so no CGI or similar).
Javascript could do this easily — only it can't: Javascript has no way to read/write from/to local disk for security reasons. However, Javascript can call Java applet methods which can write to local disk provided the applet is signed and/or the local Java security policy allows it. (This policy is very fine-grained, so, e.g., one can allow read/write to/from specified files only, from an applet signed by a specified author.)
2. |
Javascript Security Model |
From Javascript running within a Web browser, it is not possible to do local (client-side) file IO (for security reasons). One can work around this by calling methods in a Java applet (or ActiveX object).
3. |
Calling Java Methods from Javascript |
This is easy, for example:
<FORM name="editform" onSubmit="document.editFile3.applyEdits('/path/file.suffix', document.editform.field.value)"> . . . </FORM>
4. |
Java Applet Security Model |
By default, Java applets cannot write to local (client-side) disk. However, the Java applet security model provides a fine-grained permissions system which can be configured to allow applets to read/write to/from specified files, if said applets are signed by specified authors, and if the browser user choosed to accept the certificated with which the applet is signed (i.e., clicks "yes" in the popped-up window).
A bit more detail:
- Signed Applets
- http://www-personal.umich.edu/~lsiden/tutorials/signed-applet/signed-applet.html http://www.pawlan.com/monica/articles/signedapps/
- Java Security Policy
-
Is defined by the contents of
/usr/local/jre1.6.0_17/lib/security/java.policy. Permissions may be
granted to all applets, or those signed by specified authors. Examples may be
found at
http://java.sun.com/j2se/1.3/docs/guide/security/PolicyFiles.html#Examples
As a proof-of-concept, I have this working:grant codeBase "file:/home/simonh/private_html/PIM/__EDITS/-" { permission java.io.FilePermission "${user.home}/newfile", "write"; };
i.e., any applet with the specified codeBase — which can be determined from the Java console by hitting l — can write to /home/simonh/newfile (in my case), irrespective of who, if anyone, it is signed by.
5. |
Stumbling Blocks and Trouble Shooting |
There are lots of places to stumble and fall; fixing things is easy — provided one can find out what actually is the problem.
6. |
Tools |
A couple of sources of information on what is actually going on --- or not:
- Javascript Console
- In Firefox v3.0.x, at least, menu: Tools, Error Console opens up a window with the Javascript console in it — essentially, STDERR goes here.
- Java Console
- Again, more or less, STDERR goes here. Two ways to open it: /usr/local/jre1.<version>/bin/ControlPanel, then select the Advanced tab, then Java Console, Show Console and Apply; or get an add-on for Firefox — menu: Tools, Add-ons, Get Add-ons, enter "java console" and install.
7. |
The Trouble Shooting History |
A short history, in chronological order, of what went wrong and the fixes applied.
- Javascript Console Error: PrivilegedActionException
-
On the Javascript console:
Error: uncaught exception: java.security.PrivilegedActionException: java.security.PrivilegedActionException: java.lang.reflect.InvocationTargetException
But what exactly is the problem? And doesn't this look like a Java exception, not a Javascript exception?What's happening here: a Java exception is not getting caught within the Applet and is bubbling up to the calling Javascript. This was because I had
try { BufferedWriter output = new BufferedWriter(new FileWriter("/home/simonh/newfile")); output.write(newItemText, 0, 100); . . } catch (IOException ex){ . .
and should have hadcatch (Exception ex){
Catching the exception in Java and getting a stack trace gives us the required info. - Java Console Stack Trace: java.io.FilePermission
-
Once catch (IOException ex) had been changed to catch (Exception ex),
a stack trace asked for. . .
catch (Exception ex){ ex.printStackTrace(); }
. . . and javac, jar cvf... and jarsigner all rerun, we got- no errors in the Javascript console;
- a nice stack trace in the Java console allowing us to figure out what exactly is the problem.
- Java Security Policy File
-
The Java security policy is defined by
/usr/local/jre1.6.0_17/lib/security/java.policy. Adding
grant codeBase "file:/home/simonh/private_html/PIM/__EDITS/-" { permission java.io.FilePermission "${user.home}/newfile", "write"; };
for example, means that any applet — signed or unsigned — which lives within file:/home/simonh/private_html/PIM/__EDITS can write to a file called /home/simonh/newfile.
8. |
Annoyances, Tips and Tricks |
- Things get cached all over the place — Web pages, Java bits and pieces. The Java console can be used to empty the class cache (I believe): hit x.
- I believe this is true: with some settings in /usr/local/jre<version>/lib/security/java.policy the (self-signed) certificate is automatically loaded from the .jar file, rather than a dialogue box appearing: "Do you want me, the browser, to trust that applet, or no?" This is not what I expected!
Appendix: Complete Working Example
The Applet code
import java.applet.*; import java.io.*; public class editFile3 extends Applet { Frame f = null; public void init() { } public void applyEdits(String itemFile, String newItemText) { try { BufferedWriter output = new BufferedWriter(new FileWriter("/home/simonh/newfile")); output.write(newItemText, 0, 100); output.flush(); output.close(); Process proc = Runtime.getRuntime().exec("/home/simonh/src/_pim/pim2.pl"); proc.waitFor(); // ...ensure external process finishes before we finish, else hosting Web page // will be reloaded before the html is updated... } catch (Exception ex){ ex.printStackTrace(); } }
The HTML/Web Page
<HTML> <HEAD><TITLE>EDIT: Netflow_Stats</TITLE></HEAD> <BODY> <APPLET name="editFile3" archive="editFile3.jar" code="editFile3.class" width=0 height=0> </APPLET> <FORM name="editform" onSubmit="document.editFile3.applyEdits('/home/simonh/private_html/PIM/__ITEMS/Netflow_Stats.html', document.editform.edits.value)"> <TEXTAREA name="edits" rows="24" cols="80"> <MODULES>Back_Burner</MODULES> <TITLE>Netflow_Stats</TITLE> <MORE>Get Netflow data from Malcolm and process.</MORE> <PEOPLE>Malcolm_Pitcher</PEOPLE> <EPOCH>2145920461</EPOCH> </TEXTAREA> <INPUT type="submit" value="Apply Edits"> </FORM> </BODY> </HTML>
The Required Addition to the java.policy File
grant codeBase "file:/home/simonh/private_html/PIM/__ITEMS/-" { permission java.io.FilePermission "${user.home}/-", "write"; }; grant codeBase "file:/home/simonh/private_html/PIM/__EDITS/-" { permission java.io.FilePermission "${user.home}/-", "write"; }; grant codeBase "file:/home/simonh/private_html/PIM/__NEW_ITEM/-" { permission java.io.FilePermission "${user.home}/-", "write"; }; grant codeBase "file:/home/simonh/private_html/PIM/__ITEMS/-" { permission java.io.FilePermission "${user.home}/src/_pim/pim2.pl", "execute"; }; grant codeBase "file:/home/simonh/private_html/PIM/__EDITS/-" { permission java.io.FilePermission "${user.home}/src/_pim/pim2.pl", "execute"; }; grant codeBase "file:/home/simonh/private_html/PIM/__NEW_ITEM/-" { permission java.io.FilePermission "${user.home}/src/_pim/pim2.pl", "execute"; };