Mercurial > hg > release > icedtea-web-1.6
changeset 278:bd59947fa857
Checks and verifies a signed JNLP file at the launch of the application. A signed JNLP warning is displayed if appropriate.
author | Saad Mohammad <smohammad@redhat.com> |
---|---|
date | Mon, 22 Aug 2011 15:09:47 -0400 |
parents | 61e08e67b176 |
children | 334a44162495 |
files | ChangeLog netx/net/sourceforge/jnlp/JNLPFile.java netx/net/sourceforge/jnlp/SecurityDesc.java netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/security/MoreInfoPane.java netx/net/sourceforge/jnlp/security/SecurityDialog.java |
diffstat | 7 files changed, 389 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Wed Aug 17 12:01:19 2011 -0400 +++ b/ChangeLog Mon Aug 22 15:09:47 2011 -0400 @@ -1,3 +1,46 @@ +2011-08-22 Saad Mohammad <smohammad@redhat.com> + * netx/net/sourceforge/jnlp/JNLPFile.java: + (parse): After the file has been parsed, it calls + checkForSpecialProperties() to check if the resources contain any special + properties. + (checkForSpecialProperties): Scans through resources and checks if it + contains any special properties. + (requiresSignedJNLPWarning): Returns a boolean after determining if a signed + JNLP warning should be displayed. + (setSignedJNLPAsMissing): Informs JNLPFile that a signed JNLP file is + missing in the main jar. + * netx/net/sourceforge/jnlp/SecurityDesc.java: + (getJnlpRIAPermissions): Returns all the names of the basic JNLP system + properties accessible by RIAs. + * netx/net/sourceforge/jnlp/resources/Messages.properties: + Added LSignedJNLPFileDidNotMatch and SJNLPFileIsNotSigned. + * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: + (initializeResources): Locates the jar file that contains the main class + and verifies if a signed JNLP file is also located in that jar. This also + checks 'lazy' jars if the the main class was not found in 'eager' jars. + If the main jar was not found, a LaunchException is thrown which terminates + the launch of the application. + (checkForMain): A method that goes through each jar and checks to see + if it has the main class. If the main class is found, it calls + verifySignedJNLP() to verify if a valid signed JNLP file is also found in + the jar. + (verifySignedJNLP): A method that checks if the jar file contains a valid + signed JNLP file. + (closeStream): Closes a stream. + (loadClassExt): Added a try/catch block when addNextResource() is called. + (addNextResource): If the main jar has not been found, checkForMain() is + called to check if the jar contains the main class, and verifies if a signed + JNLP file is also located. + * netx/net/sourceforge/jnlp/security/MoreInfoPane.java: + (addComponents): Displays the signed JNLP warning message if necessary. + * netx/net/sourceforge/jnlp/security/SecurityDialog.java: + (SecurityDialog): Stores the value of whether a signed JNLP warning should + be displayed. + (showMoreInfoDialog): Passes in the associated JNLP file when creating a + SecurityDialog object. + (requiresSignedJNLPWarning): Returns a boolean after determining if a signed + JNLP warning should be displayed. + 2011-08-17 Danesh Dadachanji <ddadacha@redhat.com> Update UI for SecurityDialog
--- a/netx/net/sourceforge/jnlp/JNLPFile.java Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/JNLPFile.java Mon Aug 22 15:09:47 2011 -0400 @@ -107,7 +107,18 @@ /** 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 @@ 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 @@ } /** + * 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 @@ 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; + } + }
--- a/netx/net/sourceforge/jnlp/SecurityDesc.java Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/SecurityDesc.java Mon Aug 22 15:09:47 2011 -0400 @@ -244,5 +244,17 @@ 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; + } }
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties Mon Aug 22 15:09:47 2011 -0400 @@ -80,6 +80,7 @@ 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 @@ 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.
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Mon Aug 22 15:09:47 2011 -0400 @@ -17,10 +17,13 @@ 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.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 @@ // 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 @@ /** 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 @@ !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 @@ 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 @@ // 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 @@ * 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 @@ jars.add(available.get(0)); fillInPartJars(jars); + checkForMain(jars); activateJars(jars); return this;
--- a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java Mon Aug 22 15:09:47 2011 -0400 @@ -61,8 +61,11 @@ */ 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 @@ 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 @@ 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"));
--- a/netx/net/sourceforge/jnlp/security/SecurityDialog.java Wed Aug 17 12:01:19 2011 -0400 +++ b/netx/net/sourceforge/jnlp/security/SecurityDialog.java Mon Aug 22 15:09:47 2011 -0400 @@ -92,6 +92,9 @@ */ 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 @@ this.extras = extras; initialized = true; + if(file != null) + requiresSignedJNLPWarning= file.requiresSignedJNLPWarning(); + initDialog(); } @@ -164,8 +170,9 @@ 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 void addActionListener(ActionListener listener) { listeners.add(listener); } + + public boolean requiresSignedJNLPWarning() + { + return requiresSignedJNLPWarning; + } }