Hi all,
I am really struggling with setting up a working security policy file for an applet I have developed. All was working fine with JRE 1.5_06 (at which point my simple single class applet wasn't even packed into a JAR, let alone signed) but the applet, and in particular JFileChooser became unstable (by which I mean it frequently hangs) with JRE 1.5_09 and above which forced me to change from using JFileChooser to using AWT's FileDialog. The application is now stable with a wide-open security policy (i.e. permissions applicable to all CodeBases and signed by anyone), but as soon as I tighten up the policy for secific CodeBases or signed by specific aliases the applet fails with the aforementioned exception.
Ideally I want to sign the applet and rely on the fact that, as long as the user allows it to run, it will get 'AllPermission' and be able to access to all files. This set-up would prevent end users from having to adjust their policy files, which has proven to be a problem area from a support perspective.
So for testing, I generated a self certified key using keytool (-selfcert), packaged the applet class into a jar and signed it with jarsigner. As expected, when my browser loaded the applet it said it couldn't verify the certificate and asked whether the applet should be allowed to run. I let it run but after selecting a file using the FileDialog, the applet then attempts to open it at which point it throws the somewhat unexpected AccessControlException.
I must be doing something wrong somewhere!
I have lost count of the number of different variations I have tried, but the only policy file that allows the applet to access the file is one with a policy which gives the required java.io.FilePermission but has no CodeBase defined and no 'SignedBy' defined, making it applicable to all applets. No much security there! I was expecting an empty policy file incombination with a signed applet to give just the result I needed.
The environment I am testing in is as follows:
Applet compiled with JDK 1.4.2.
Browser is IE6.0 sp2, with Sun JRE 1.5.0.11 Plug-in
Browser is running on Windows XP Professional sp2.
Applet code is:
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Frame;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.StringReader;
import javax.swing.JApplet;
import javax.swing.UIManager;
import netscape.javascript.JSObject;
/*
* FileHelper.java
*
* sccswhatF="%W%"
*/
public class FileHelper extends JApplet
{
private String directoryURI = null;
private File currDirectory = null;
private String fileContents = null;
private String fileHeader;
private String fileName = null;
private String fullFileName = null;
private String fileURI = null;
private File savedFile = null;
private String savedFileName = null;
private String savedFileNameURI = null;
private File fileToLoad = null;
private FileDialog fod; // File open dialog
private FileDialog fsd; // File save dialog
int maxNumGrps;
private int command;
static final int SAVE_FILE = 1;
static final int OPEN_FILE = 2;
static final int OPEN_SPECIFIC_FILE = 3;
static final int MAX_NUM_GRPS =
999; // default maximum number of groups Web Forms will cope with
JSObject win;
String debug = null;
/** Initialization method that will be called after the applet is loaded
* into the browser.
*/
public void init()
{
// Get parameters
debug = getParameter("DEBUG");
String sMaxNumGrps = getParameter("MAX_NUM_GRPS");
if(sMaxNumGrps == null)
{
maxNumGrps = MAX_NUM_GRPS; // Set to default
}
else
{
try
{
maxNumGrps = Integer.parseInt(sMaxNumGrps);
}
catch(java.lang.NumberFormatException e)
{
maxNumGrps = MAX_NUM_GRPS;
}
}
// get a handle on the window hosting this applet
win = JSObject.getWindow(this);
directoryURI = (String)win.eval("getBaseDir()");
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e)
{
debugMsg(e.toString());
}
setBackground(Color.white);
// Set the browse start location
if(directoryURI != null && !directoryURI.trim().equals(""))
{
try
{
currDirectory = new File(new java.net.URI(directoryURI));
}
catch(java.net.URISyntaxException ue)
{
debugMsg(ue.toString());
}
}
fsd =
new FileDialog(new Frame(), "Please select name and location for saved file: ",
FileDialog.SAVE);
fod =
new FileDialog(new Frame(), "Please select file to open: ",
FileDialog.LOAD);
debugMsg("Value of DEBUG parameter is: " + debug);
debugMsg("Value of MAX_NUM_GROUPS parameter is: " + sMaxNumGrps);
debugMsg("Maximum number of groups allowed: " + maxNumGrps);
debugMsg("Code base is : " + getCodeBase());
debugMsg("Document base is : " + getDocumentBase());
debugMsg("Base working directory URI is: " + directoryURI);
debugMsg("Base working directory is: " + currDirectory.getAbsolutePath());
debugMsg("Initialised.");
}
// Method to send debug messages to the console
private void debugMsg(String msg)
{
if(debug != null)
{
System.out.println(msg);
}
}
/** Getter for property directory.
* @return Value of property directory.
*
*/
public java.lang.String getDirectoryURI()
{
return directoryURI;
}
/** Setter for property directory.
* @param directoryURI New value of property directory.
*
*/
public void setDirectoryURI(java.lang.String directoryURI)
{
this.directoryURI = directoryURI;
}
/** Setter for property fileContents.
* @param fileContents New value of property fileContents.
*
*/
public void setFileContents(java.lang.String fileContents)
{
this.fileContents = fileContents;
}
/** Getter for property savedFileNameURI.
* @return Value of property savedFileNameURI.
*
*/
public java.lang.String getSavedFileNameURI()
{
debugMsg("getSavedFileNameURI called");
if(savedFile != null)
{
return savedFileNameURI;
}
else
{
return null;
}
}
/** Getter for property fileName.
* @return Value of property fileName.
*
*/
public java.lang.String getFileName()
{
return fileName;
}
/** Getter for property fullFileName.
* @return Value of property fullFileName.
*
*/
public java.lang.String getFullFileName()
{
return fullFileName;
}
public String getFileURI()
{
return fileURI;
}
/** Setter for property fileName.
* @param fileName New value of property fileName.
*
*/
public void setFileName(java.lang.String fileName)
{
this.fileName = fileName;
}
/** Setter for property command.
* @param command New value of property command.
*
*/
public void setCommand(int command)
{
debugMsg("Command set to: " + command);
this.command = command;
try
{
Thread t = new Thread(new Runnable()
{
public void run()
{
doCommand();
}
});
t.start();
t.join(); // Waits for thread to die
debugMsg("Command completed.");
//waitForCompletion();
}
catch(Exception e)
{
debugMsg("Failed to invoke new thread");
}
}
private void waitForCompletion()
{
debugMsg("waitForCompletion called");
try
{
while(this.command != 0)
{
Thread.sleep(1000);
}
}
catch(Exception e)
{
debugMsg("exception in sleep");
if(debug != null)
{
e.printStackTrace();
}
System.exit(1);
}
}
public void setFileToLoad(String fileToLoadURI)
{
try
{
debugMsg("Requested to load: " + fileToLoadURI);
fileToLoad = new File(new java.net.URI(fileToLoadURI));
setCommand(OPEN_SPECIFIC_FILE);
}
catch(java.net.URISyntaxException e)
{
debugMsg("Invalid URI");
}
}
public void saveFile(String directoryURI, String fileContents, String fileName)
{
debugMsg("Saving...");
debugMsg(fileContents);
debugMsg(fileName);
if(directoryURI != null && directoryURI.length() > 0)
{
this.directoryURI = directoryURI;
}
this.fileContents = fileContents;
this.fileName = fileName;
savedFile = null;
savedFileName = null;
savedFileNameURI = null;
setCommand(SAVE_FILE);
}
public java.lang.String getSavedFileName()
{
debugMsg("getSavedFileName called");
if(savedFile != null)
{
return savedFileName;
}
else
{
return null;
}
}
public String getCurrDirectory()
{
if(currDirectory != null)
{
return currDirectory.getAbsolutePath();
}
else
{
return "";
}
}
public String getFileContents()
{
debugMsg("getFileContents called");
return fileContents;
}
public String getFileHeader()
{
debugMsg("getFileHeader called");
return fileHeader;
}
private void doCommand()
{
switch(command)
{
case SAVE_FILE:
debugMsg("Save button pressed");
// Set the browse start location
if(directoryURI != null)
{
try
{
// If we have a specific file name, use it.
// Otherwise just open the chooser and it should start browsing where we were before
if(currDirectory != null && !currDirectory.getAbsolutePath().equals(""))
{
fsd.setDirectory(currDirectory.getAbsolutePath());
}
if(fileName != null && !fileName.equals(""))
{
fsd.setFile(fileName);
}
fsd.show();
if(fsd.getFile() != null)
{
try
{
File selectedFile = new File(fsd.getDirectory(),fsd.getFile());
debugMsg("Elected to save the file to: " + selectedFile.getAbsolutePath());
currDirectory = new File(fsd.getDirectory());
directoryURI = currDirectory.toURI().toString();
FileWriter fw = new FileWriter(selectedFile);
fw.write(fileContents);
fw.close();
savedFile = selectedFile;
savedFileName = selectedFile.getAbsolutePath();
savedFileNameURI =
selectedFile.toURI().toString();
command = 0;
fileContents = null;
}
catch(java.io.IOException ioe)
{
debugMsg(ioe.toString());
}
}
}
catch(java.security.AccessControlException ace)
{
debugMsg(ace.toString());
debugMsg("Permission is " + ace.getPermission().toString());
win.eval("displayError('Save failed: Access denied')");
}
}
else
{
win.eval("displayError('WARNING: Save operation will not function until you define a BASE_DIRECTORY preference. (See Help->Guide for details)')");
}
command = 0;
fileContents = null;
break;
case OPEN_FILE:
debugMsg("Open button pressed");
// Set the browse start location
if(directoryURI != null)
{
try
{
File baseDir = new File(new java.net.URI(directoryURI));
fod.setDirectory(baseDir.getAbsolutePath());
fod.show();
if(fod.getFile() != null)
{
File f = new File(fod.getDirectory(), fod.getFile());
loadFile(f);
}
}
catch(java.net.URISyntaxException ue)
{
debugMsg(ue.toString());
}
catch(java.security.AccessControlException ace)
{
debugMsg(ace.toString());
debugMsg("Permission is " + ace.getPermission().toString());
win.eval("displayError('Open failed: Access denied')");
}
}
else
{
win.eval("displayError('WARNING: Open operation will not function until you define a BASE_DIRECTORY preference. (See Help->Guide for details)')");
}
command = 0;
break;
case OPEN_SPECIFIC_FILE:
loadFile(fileToLoad);
fileToLoad = null;
command = 0;
break;
}
}
private void loadFile(File f)
{
fileName = f.getName();
fullFileName = f.getAbsolutePath();
currDirectory = f.getParentFile();
directoryURI = currDirectory.toURI().toString();
fileURI = f.toURI().toString();
debugMsg("Loading file: " + f.getAbsolutePath());
try
{
long fileSize = f.length();
if(fileSize < maxNumGrps * 1024)
{
char[] buffer = new char[new Long(fileSize).intValue()];
debugMsg("Creating file reader");
FileReader fr = new FileReader(f);
debugMsg("About to read file");
int bytes = fr.read(buffer);
debugMsg(bytes + " bytes read from the file");
fr.close();
fileContents = new String(buffer);
try
{
BufferedReader br =
new BufferedReader(new StringReader(fileContents));
int grpCount = 0;
String grp = br.readLine();
while(grp != null)
{
grpCount++;
if(grpCount == 1)
{
if(!grp.startsWith("ZHV") && !grp.startsWith("ZHD"))
{
fileContents = null;
fileHeader = null;
win.eval("displayError('File format not recognised')");
break;
}
else
{
fileHeader = grp;
}
}
grp = br.readLine();
}
if(grpCount > maxNumGrps)
{
fileContents = null;
win.eval("displayError('Number of groups (" + grpCount +
") in " + f.toURI().getSchemeSpecificPart() +
" exceeds maximum allowed (" + maxNumGrps +
")')");
}
}
catch(Exception e)
{
debugMsg("Exception occurred: " + e);
}
}
else
{
win.eval("displayError('The selected file (" +
f.toURI().getSchemeSpecificPart() +
") is too large to be processed by this application')");
}
}
catch(java.security.AccessControlException ace)
{
win.eval("displayError('Insufficient privilege to read file (" +
f.toURI().getSchemeSpecificPart() + ")')");
}
catch(Exception e)
{
win.eval("displayError('Unexpected exception occurred:" +
e.toString() + "')");
}
debugMsg("content is:");
debugMsg(fileContents);
}
}
The AccessControlException is thrown when the .java.policy file in my home directory is empty, but I have also tried the following in a last ditch attempt to force the Java plugin to give my applet the correct permissions:
/* AUTOMATICALLY GENERATED ON Thu Jun 28 10:54:47 BST 2007*/
/* DO NOT EDIT */
keystore "file:/D:/Documents and Settings/czpny2/.keystore", "jks";
grant {
permission java.lang.RuntimePermission "usePolicy";
};
grant signedBy "dtskey" {
permission java.lang.RuntimePermission "modifyThread";
permission java.io.FilePermission "<<ALL FILES>>", "read,write";
};
The output from the applet at runtime is:
Value of DEBUG parameter is: no
Value of MAX_NUM_GROUPS parameter is: 999
Maximum number of groups allowed: 999
Code base is :
http://localhost/secure/
Document base is : http://localhost/secure/Autogen.Globals
Base working directory URI is: file:///c:/temp
Base working directory is: c:\temp
Initialised.
Command set to: 2
Open button pressed
java.security.AccessControlException: access denied (java.io.FilePermission C:\Temp read)
Permission is (java.io.FilePermission C:\Temp read)
Command completed.
getFileContents called
Logging set to : true ... completed.
Command set to: 2
Open button pressed
java.security.AccessControlException: access denied (java.io.FilePermission C:\Temp read)
Permission is (java.io.FilePermission C:\Temp read)
Hope someone can shed some light on this problem.
Thanks in advance.