diff options
author | Saad Mohammad <[email protected]> | 2011-08-22 15:09:47 -0400 |
---|---|---|
committer | Saad Mohammad <[email protected]> | 2011-08-22 15:09:47 -0400 |
commit | ca57a77f66bbca5e2be1da83868ba0b5daab0ca3 (patch) | |
tree | 69fae35674d0d6c7059b2eea12685a3fffcb89a2 /netx | |
parent | 3fdbc63fe69b247c9aaa49a322142a2085248095 (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.java | 55 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/SecurityDesc.java | 12 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/resources/Messages.properties | 2 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java | 255 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/security/MoreInfoPane.java | 13 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/security/SecurityDialog.java | 14 |
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; + } } |