If you only occasionally need to perform actions that require elevated privileges, you can delay the security dialog to the absolute last moment (for example: right before reading/writing a file). The trick is to keep most of your code in an unsigned JAR, and the code that requires elevated privileges into a signed JAR. Use Class.forName(String) to load the signed class, which will prompt the security dialog.
Using an interface in the unsigned code, and the implementation in the signed code, you can keep your code tidy.
Note:
The browser will remember the choice of the user, until a *restart* of the browser. To workaround this (when people declined), create a dozen tiny signed JARs (with a dozen different certificates, mind you) and use a roundrobin algorithm, using serverside code or javascript that generates the applet-archive attribute. After a dozen hits and rejections, you can be sure your visitor will never grant access to his system anyway.
Update:
To make it work in MSIE, both classes MUST be in separate packages.
IMPORTANT: Since 6u19 this doesn't work anymore. You not only get a rather confusing security dialog (clicking [YES] means deny access, clicking [NO] means allow access), the two classes end up in different classloaders that cannot access eachother, resulting in ClassNotFoundException / NoClassDefFoundException. Thanks Oracle, for making Java's user experience even more secure and crap at the same time.
http://java.sun.com/javase/6/docs/technotes/guides/jweb/mixed_code.html
On to the code, which is reasonably simple.
Unsigned JAR:
package some.unsigned.stuff; public interface SecureAccess { public byte[] loadFile(File file); public void storeFile(File file, byte[] data); } // Usage: File file = new File("/home/silly/image.jpg"); Class< ? > clazz = Class.forName("some.signed.stuff.LocalSecureAccess"); SecureAccess access = (SecureAccess) clazz.newInstance(); byte[] data = access.loadFile(file);
Signed JAR:
package some.signed.stuff; public class LocalSecureAccess implements SecureAccess { public byte[] loadFile(final File file) { return AccessController.doPrivileged(new PrivilegedAction<byte[]>() { @Override public byte[] run() { return loadFileImpl(file); } }); } @Override public void storeFile(final File file, final byte[] data) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { storeFileImpl(file, data); return null; } }); } // implementation static final int MAX_FILE_SIZE = 8 * 1024 * 1024; // prevent applet running out of memory byte[] loadFileImpl(File file) { DataInputStream input = null; try { long len = file.length(); if (len > MAX_FILE_SIZE) throw new IllegalStateException("file too big: " + file); byte[] data = new byte[(int) len]; input = new DataInputStream(new FileInputStream(file)); input.readFully(data); input.close(); return data; } catch (IOException exc) { throw new IllegalStateException(exc); } finally { try { if(input!=null) input.close(); } catch(IOException exc) {} } } void storeFileImpl(File file, byte[] data) { OutputStream output = null; try { output = new FileOutputStream(file); output.write(data); output.flush(); } catch (IOException exc) { throw new IllegalStateException(exc); } finally { try { if(output!=null) output.close(); } catch(IOException exc) {} } } }
Jar signing 101: (using DSA keys instead of RSA for Java 1.4 compatibility)
PATH=%PATH%;path\to\JDK\bin SET ALIAS=MY_ALIAS SET PASS=MY_PASSWORD SET JAR=my.jar keytool -delete -storepass %PASS% -alias %ALIAS% keytool -genkey -storepass %PASS% -keypass %PASS% -keyalg DSA -alias %ALIAS% -dname "CN=full.domainname.com, OU=Your unit, O=Your Company, L=Your city, ST=Your state, C=CA, EMAILADDRESS=your@server.com DC=server, DC=com" -validity 999 (put all of this on one line) keytool -selfcert -storepass %PASS% -alias %ALIAS% -validity 999 keytool -exportcert -storepass %PASS% -alias %ALIAS% -rfc -file %ALIAS%.cer jarsigner -storepass %PASS% -keypass %PASS% %JAR% %ALIAS% pause
Applet code: (nothing special)
<applet code="package/of/YourApplet.class" archive="unsigned.jar,signed.jar" width="640" height="480"> no applet? </applet>