aboutsummaryrefslogtreecommitdiffstats
path: root/netx
diff options
context:
space:
mode:
authorSaad Mohammad <[email protected]>2011-08-22 15:09:47 -0400
committerSaad Mohammad <[email protected]>2011-08-22 15:09:47 -0400
commitca57a77f66bbca5e2be1da83868ba0b5daab0ca3 (patch)
tree69fae35674d0d6c7059b2eea12685a3fffcb89a2 /netx
parent3fdbc63fe69b247c9aaa49a322142a2085248095 (diff)
Checks and verifies a signed JNLP file at the launch of the application. A signed JNLP warning is displayed if appropriate.
Diffstat (limited to 'netx')
-rw-r--r--netx/net/sourceforge/jnlp/JNLPFile.java55
-rw-r--r--netx/net/sourceforge/jnlp/SecurityDesc.java12
-rw-r--r--netx/net/sourceforge/jnlp/resources/Messages.properties2
-rw-r--r--netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java255
-rw-r--r--netx/net/sourceforge/jnlp/security/MoreInfoPane.java13
-rw-r--r--netx/net/sourceforge/jnlp/security/SecurityDialog.java14
6 files changed, 346 insertions, 5 deletions
diff --git a/netx/net/sourceforge/jnlp/JNLPFile.java b/netx/net/sourceforge/jnlp/JNLPFile.java
index 183f6b1..714ff7e 100644
--- a/netx/net/sourceforge/jnlp/JNLPFile.java
+++ b/netx/net/sourceforge/jnlp/JNLPFile.java
@@ -107,7 +107,18 @@ public class JNLPFile {
/** the default jvm */
protected String defaultArch = null;
+
+ /** A signed JNLP file is missing from the main jar */
+ private boolean missingSignedJNLP = false;
+
+ /** JNLP file contains special properties */
+ private boolean containsSpecialProperties = false;
+ /**
+ * List of acceptable properties (not-special)
+ */
+ private String[] generalProperties = SecurityDesc.getJnlpRIAPermissions();
+
{ // initialize defaults if security allows
try {
defaultLocale = Locale.getDefault();
@@ -608,6 +619,9 @@ public class JNLPFile {
launchType = parser.getLauncher(root);
component = parser.getComponent(root);
security = parser.getSecurity(root);
+
+ checkForSpecialProperties();
+
} catch (ParseException ex) {
throw ex;
} catch (Exception ex) {
@@ -619,6 +633,30 @@ public class JNLPFile {
}
/**
+ * Inspects the JNLP file to check if it contains any special properties
+ */
+ private void checkForSpecialProperties() {
+
+ for (ResourcesDesc res : resources) {
+ for (PropertyDesc propertyDesc : res.getProperties()) {
+
+ for (int i = 0; i < generalProperties.length; i++) {
+ String property = propertyDesc.getKey();
+
+ if (property.equals(generalProperties[i])) {
+ break;
+ } else if (!property.equals(generalProperties[i])
+ && i == generalProperties.length - 1) {
+ containsSpecialProperties = true;
+ return;
+ }
+ }
+
+ }
+ }
+ }
+
+ /**
*
* @return true if the JNLP file specifies things that can only be
* applied on a new vm (eg: different max heap memory)
@@ -690,4 +728,21 @@ public class JNLPFile {
return new DownloadOptions(usePack, useVersion);
}
+ /**
+ * Returns a boolean after determining if a signed JNLP warning should be
+ * displayed in the 'More Information' panel.
+ *
+ * @return true if a warning should be displayed; otherwise false
+ */
+ public boolean requiresSignedJNLPWarning() {
+ return (missingSignedJNLP && containsSpecialProperties);
+ }
+
+ /**
+ * Informs that a signed JNLP file is missing in the main jar
+ */
+ public void setSignedJNLPAsMissing() {
+ missingSignedJNLP = true;
+ }
+
}
diff --git a/netx/net/sourceforge/jnlp/SecurityDesc.java b/netx/net/sourceforge/jnlp/SecurityDesc.java
index f044237..abb61bd 100644
--- a/netx/net/sourceforge/jnlp/SecurityDesc.java
+++ b/netx/net/sourceforge/jnlp/SecurityDesc.java
@@ -244,5 +244,17 @@ public class SecurityDesc {
return permissions;
}
+
+ /**
+ * Returns all the names of the basic JNLP system properties accessible by RIAs
+ */
+ public static String[] getJnlpRIAPermissions() {
+ String[] jnlpPermissions = new String[jnlpRIAPermissions.length];
+
+ for (int i = 0; i < jnlpRIAPermissions.length; i++)
+ jnlpPermissions[i] = jnlpRIAPermissions[i].getName();
+
+ return jnlpPermissions;
+ }
}
diff --git a/netx/net/sourceforge/jnlp/resources/Messages.properties b/netx/net/sourceforge/jnlp/resources/Messages.properties
index bab3dcd..7365532 100644
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties
@@ -80,6 +80,7 @@ LUnsignedJarWithSecurity=Cannot grant permissions to unsigned jars.
LUnsignedJarWithSecurityInfo=Application requested security permissions, but jars are not signed.
LSignedAppJarUsingUnsignedJar=Signed application using unsigned jars.
LSignedAppJarUsingUnsignedJarInfo=The main application jar is signed, but some of the jars it is using aren't.
+LSignedJNLPFileDidNotMatch=The signed JNLP file did not match the launching JNLP file.
JNotApplet=File is not an applet.
JNotApplication=File is not an application.
@@ -210,6 +211,7 @@ SNotAllSignedSummary=Only parts of this application code are signed.
SNotAllSignedDetail=This application contains both signed and unsigned code. While signed code is safe if you trust the provider, unsigned code may imply code outside of the trusted provider's control.
SNotAllSignedQuestion=Do you wish to proceed and run this application anyway?
SAuthenticationPrompt=The {0} server at {1} is requesting authentication. It says "{2}"
+SJNLPFileIsNotSigned=This application contains a digital signature in which the launching JNLP file is not signed.
# Security - used for the More Information dialog
SBadKeyUsage=Resources contain entries whose signer certificate's KeyUsage extension doesn't allow code signing.
diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
index 5a74e11..9b93a2d 100644
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
@@ -17,10 +17,13 @@ package net.sourceforge.jnlp.runtime;
import static net.sourceforge.jnlp.runtime.Translator.R;
+import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -46,11 +49,14 @@ import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
-
+import net.sourceforge.jnlp.AppletDesc;
+import net.sourceforge.jnlp.ApplicationDesc;
import net.sourceforge.jnlp.DownloadOptions;
import net.sourceforge.jnlp.ExtensionDesc;
import net.sourceforge.jnlp.JARDesc;
import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.JNLPMatcher;
+import net.sourceforge.jnlp.JNLPMatcherException;
import net.sourceforge.jnlp.LaunchException;
import net.sourceforge.jnlp.ParseException;
import net.sourceforge.jnlp.PluginBridge;
@@ -81,6 +87,13 @@ public class JNLPClassLoader extends URLClassLoader {
// extension classes too so that main file classes can load
// resources in an extension.
+ /** Signed JNLP File and Template */
+ final public static String TEMPLATE = "JNLP-INF/APPLICATION_TEMPLATE.JNLP";
+ final public static String APPLICATION = "JNLP-INF/APPLICATION.JNLP";
+
+ /** True if the application has a signed JNLP File */
+ private boolean isSignedJNLP = false;
+
/** map from JNLPFile url to shared classloader */
private static Map<String, JNLPClassLoader> urlToLoader =
new HashMap<String, JNLPClassLoader>(); // never garbage collected!
@@ -153,6 +166,10 @@ public class JNLPClassLoader extends URLClassLoader {
/** Loader for codebase (which is a path, rather than a file) */
private CodeBaseClassLoader codeBaseLoader;
+
+ /** True if the jar with the main class has been found
+ * */
+ private boolean foundMainJar= false;
/**
* Create a new JNLPClassLoader from the specified file.
@@ -460,6 +477,26 @@ public class JNLPClassLoader extends URLClassLoader {
!SecurityDialogs.showNotAllSignedWarningDialog(file))
throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo"));
+
+ // Check for main class in the downloaded jars, and check/verify signed JNLP fill
+ checkForMain(initialJars);
+
+ // If jar with main class was not found, check available resources
+ while (!foundMainJar && available != null && available.size() != 0)
+ addNextResource();
+
+ // If jar with main class was not found and there are no more
+ // available jars, throw a LaunchException
+ if (!foundMainJar
+ && (available == null || available.size() == 0))
+ throw new LaunchException(file, null, R("LSFatal"),
+ R("LCClient"), R("LCantDetermineMainClass"),
+ R("LCantDetermineMainClassInfo"));
+
+ // If main jar was found, but a signed JNLP file was not located
+ if (!isSignedJNLP && foundMainJar)
+ file.setSignedJNLPAsMissing();
+
//user does not trust this publisher
if (!js.getAlreadyTrustPublisher()) {
checkTrustWithUser(js);
@@ -518,10 +555,205 @@ public class JNLPClassLoader extends URLClassLoader {
System.err.println(mfe.getMessage());
}
}
-
activateJars(initialJars);
}
+
+ /***
+ * Checks for the jar that contains the main class. If the main class was
+ * found, it checks to see if the jar is signed and whether it contains a
+ * signed JNLP file
+ *
+ * @param jars Jars that are checked to see if they contain the main class
+ * @throws LaunchException Thrown if the signed JNLP file, within the main jar, fails to be verified or does not match
+ */
+ private void checkForMain(List<JARDesc> jars) throws LaunchException {
+
+ Object obj = file.getLaunchInfo();
+ String mainClass;
+
+ if (obj instanceof ApplicationDesc) {
+ ApplicationDesc ad = (ApplicationDesc) file.getLaunchInfo();
+ mainClass = ad.getMainClass();
+ } else if (obj instanceof AppletDesc) {
+ AppletDesc ad = (AppletDesc) file.getLaunchInfo();
+ mainClass = ad.getMainClass();
+ } else
+ return;
+
+ for (int i = 0; i < jars.size(); i++) {
+
+ try {
+ File localFile = tracker
+ .getCacheFile(jars.get(i).getLocation());
+
+ if (localFile == null)
+ throw new NullPointerException(
+ "Could not locate jar file, returned null");
+
+ JarFile jarFile = new JarFile(localFile);
+ Enumeration<JarEntry> entries = jarFile.entries();
+ JarEntry je;
+
+ while (entries.hasMoreElements()) {
+ je = entries.nextElement();
+ String jeName = je.getName().replaceAll("/", ".");
+
+ if (!jeName.startsWith(mainClass + "$Inner")
+ && (jeName.startsWith(mainClass) && jeName.endsWith(".class"))) {
+ foundMainJar = true;
+ verifySignedJNLP(jars.get(i), jarFile);
+ break;
+ }
+ }
+ } catch (IOException e) {
+ /*
+ * After this exception is caught, it is escaped. This will skip
+ * the jarFile that may have thrown this exception and move on
+ * to the next jarFile (if there are any)
+ */
+ }
+ }
+ }
+ /**
+ * Is called by checkForMain() to check if the jar file is signed and if it
+ * contains a signed JNLP file.
+ *
+ * @param jarDesc JARDesc of jar
+ * @param jarFile the jar file
+ * @throws LaunchException thrown if the signed JNLP file, within the main jar, fails to be verified or does not match
+ */
+ private void verifySignedJNLP(JARDesc jarDesc, JarFile jarFile)
+ throws LaunchException {
+
+ JarSigner signer = new JarSigner();
+ List<JARDesc> desc = new ArrayList<JARDesc>();
+ desc.add(jarDesc);
+
+ // Initialize streams
+ InputStream inStream = null;
+ InputStreamReader inputReader = null;
+ FileReader fr = null;
+ InputStreamReader jnlpReader = null;
+
+ try {
+ signer.verifyJars(desc, tracker);
+
+ if (signer.allJarsSigned()) { // If the jar is signed
+
+ Enumeration<JarEntry> entries = jarFile.entries();
+ JarEntry je;
+
+ while (entries.hasMoreElements()) {
+ je = entries.nextElement();
+ String jeName = je.getName().toUpperCase();
+
+ if (jeName.equals(TEMPLATE) || jeName.equals(APPLICATION)) {
+
+ if (JNLPRuntime.isDebug())
+ System.err.println("Creating Jar InputStream from JarEntry");
+
+ inStream = jarFile.getInputStream(je);
+ inputReader = new InputStreamReader(inStream);
+
+ if (JNLPRuntime.isDebug())
+ System.err.println("Creating File InputStream from lauching JNLP file");
+
+ JNLPFile jnlp = this.getJNLPFile();
+ URL url = jnlp.getFileLocation();
+ File jn = null;
+
+ // If the file is on the local file system, use original path, otherwise find cached file
+ if (url.getProtocol().toLowerCase().equals("file"))
+ jn = new File(url.getPath());
+ else
+ jn = CacheUtil.getCacheFile(url, null);
+
+ fr = new FileReader(jn);
+ jnlpReader = fr;
+
+ // Initialize JNLPMatcher class
+ JNLPMatcher matcher;
+
+ if (jeName.equals(APPLICATION)) { // If signed application was found
+ if (JNLPRuntime.isDebug())
+ System.err.println("APPLICATION.JNLP has been located within signed JAR. Starting verfication...");
+
+ matcher = new JNLPMatcher(inputReader, jnlpReader, false);
+ } else { // Otherwise template was found
+ if (JNLPRuntime.isDebug())
+ System.err.println("APPLICATION_TEMPLATE.JNLP has been located within signed JAR. Starting verfication...");
+
+ matcher = new JNLPMatcher(inputReader, jnlpReader,
+ true);
+ }
+
+ // If signed JNLP file does not matches launching JNLP file, throw JNLPMatcherException
+ if (!matcher.isMatch())
+ throw new JNLPMatcherException("Signed Application did not match launching JNLP File");
+
+ this.isSignedJNLP = true;
+ if (JNLPRuntime.isDebug())
+ System.err.println("Signed Application Verification Successful");
+
+ break;
+ }
+ }
+ }
+ } catch (JNLPMatcherException e) {
+
+ /*
+ * Throws LaunchException if signed JNLP file fails to be verified
+ * or fails to match the launching JNLP file
+ */
+
+ throw new LaunchException(file, null, R("LSFatal"), R("LCClient"),
+ R("LSignedJNLPFileDidNotMatch"), R(e.getMessage()));
+
+ /*
+ * Throwing this exception will fail to initialize the application
+ * resulting in the termination of the application
+ */
+
+ } catch (Exception e) {
+
+ if (JNLPRuntime.isDebug())
+ e.printStackTrace(System.err);
+
+ /*
+ * After this exception is caught, it is escaped. If an exception is
+ * thrown while handling the jar file, (mainly for
+ * JarSigner.verifyJars) it assumes the jar file is unsigned and
+ * skip the check for a signed JNLP file
+ */
+
+ } finally {
+
+ //Close all streams
+ closeStream(inStream);
+ closeStream(inputReader);
+ closeStream(fr);
+ closeStream(jnlpReader);
+ }
+
+ if (JNLPRuntime.isDebug())
+ System.err.println("Ending check for signed JNLP file...");
+ }
+
+ /***
+ * Closes a stream
+ *
+ * @param stream the stream that will be closed
+ */
+ private void closeStream (Closeable stream) {
+ if (stream != null)
+ try {
+ stream.close();
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+ }
+
private void checkTrustWithUser(JarSigner js) throws LaunchException {
if (!js.getRootInCacerts()) { //root cert is not in cacerts
boolean b = SecurityDialogs.showCertWarningDialog(
@@ -1154,7 +1386,20 @@ public class JNLPClassLoader extends URLClassLoader {
// add resources until found
while (true) {
- JNLPClassLoader addedTo = addNextResource();
+ JNLPClassLoader addedTo = null;
+
+ try {
+ addedTo = addNextResource();
+ } catch (LaunchException e) {
+
+ /*
+ * This method will never handle any search for the main class
+ * [It is handled in initializeResources()]. Therefore, this
+ * exception will never be thrown here and is escaped
+ */
+
+ throw new IllegalStateException(e);
+ }
if (addedTo == null)
throw new ClassNotFoundException(name);
@@ -1245,8 +1490,9 @@ public class JNLPClassLoader extends URLClassLoader {
* no more resources to add, the method returns immediately.
*
* @return the classloader that resources were added to, or null
+ * @throws LaunchException Thrown if the signed JNLP file, within the main jar, fails to be verified or does not match
*/
- protected JNLPClassLoader addNextResource() {
+ protected JNLPClassLoader addNextResource() throws LaunchException {
if (available.size() == 0) {
for (int i = 1; i < loaders.length; i++) {
JNLPClassLoader result = loaders[i].addNextResource();
@@ -1262,6 +1508,7 @@ public class JNLPClassLoader extends URLClassLoader {
jars.add(available.get(0));
fillInPartJars(jars);
+ checkForMain(jars);
activateJars(jars);
return this;
diff --git a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java
index b6a27d1..a46f3ff 100644
--- a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java
+++ b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java
@@ -61,8 +61,11 @@ import javax.swing.SwingConstants;
*/
public class MoreInfoPane extends SecurityDialogPanel {
+ private boolean showSignedJNLPWarning;
+
public MoreInfoPane(SecurityDialog x, CertVerifier certVerifier) {
super(x, certVerifier);
+ showSignedJNLPWarning= x.requiresSignedJNLPWarning();
addComponents();
}
@@ -72,6 +75,11 @@ public class MoreInfoPane extends SecurityDialogPanel {
private void addComponents() {
ArrayList<String> details = certVerifier.getDetails();
+ // Show signed JNLP warning if the signed main jar does not have a
+ // signed JNLP file and the launching JNLP file contains special properties
+ if(showSignedJNLPWarning)
+ details.add(R("SJNLPFileIsNotSigned"));
+
int numLabels = details.size();
JPanel errorPanel = new JPanel(new GridLayout(numLabels, 1));
errorPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
@@ -88,6 +96,11 @@ public class MoreInfoPane extends SecurityDialogPanel {
errorPanel.add(new JLabel(htmlWrap(details.get(i)), icon, SwingConstants.LEFT));
}
+
+ // Removes signed JNLP warning after it has been used. This will avoid
+ // any alteration to certVerifier.
+ if(showSignedJNLPWarning)
+ details.remove(details.size()-1);
JPanel buttonsPanel = new JPanel(new BorderLayout());
JButton certDetails = new JButton(R("SCertificateDetails"));
diff --git a/netx/net/sourceforge/jnlp/security/SecurityDialog.java b/netx/net/sourceforge/jnlp/security/SecurityDialog.java
index 1988e5e..8ffbdd4 100644
--- a/netx/net/sourceforge/jnlp/security/SecurityDialog.java
+++ b/netx/net/sourceforge/jnlp/security/SecurityDialog.java
@@ -92,6 +92,9 @@ public class SecurityDialog extends JDialog {
*/
private Object value;
+ /** Should show signed JNLP file warning */
+ private boolean requiresSignedJNLPWarning;
+
SecurityDialog(DialogType dialogType, AccessType accessType,
JNLPFile file, CertVerifier jarSigner, X509Certificate cert, Object[] extras) {
super();
@@ -103,6 +106,9 @@ public class SecurityDialog extends JDialog {
this.extras = extras;
initialized = true;
+ if(file != null)
+ requiresSignedJNLPWarning= file.requiresSignedJNLPWarning();
+
initDialog();
}
@@ -164,8 +170,9 @@ public class SecurityDialog extends JDialog {
public static void showMoreInfoDialog(
CertVerifier jarSigner, SecurityDialog parent) {
+ JNLPFile file= parent.getFile();
SecurityDialog dialog =
- new SecurityDialog(DialogType.MORE_INFO, null, null,
+ new SecurityDialog(DialogType.MORE_INFO, null, file,
jarSigner);
dialog.setModalityType(ModalityType.APPLICATION_MODAL);
dialog.setVisible(true);
@@ -372,5 +379,10 @@ public class SecurityDialog extends JDialog {
public void addActionListener(ActionListener listener) {
listeners.add(listener);
}
+
+ public boolean requiresSignedJNLPWarning()
+ {
+ return requiresSignedJNLPWarning;
+ }
}