/hg/icedtea-web: Checks and verifies a signed JNLP file at the l...

Jiri Vanek jvanek at redhat.com
Tue Aug 23 04:36:17 PDT 2011


On 08/22/2011 09:09 PM, smohammad at icedtea.classpath.org wrote:
> changeset bd59947fa857 in /hg/icedtea-web
> details: http://icedtea.classpath.org/hg/icedtea-web?cmd=changeset;node=bd59947fa857
> author: Saad Mohammad<smohammad at redhat.com>
> date: Mon Aug 22 15:09:47 2011 -0400
>
> 	Checks and verifies a signed JNLP file at the launch of the
> 	application. A signed JNLP warning is displayed if appropriate.
>
>

Please do not forget to add reproducers for all this work.

Tyvm!

J.

> diffstat:
>
>   ChangeLog                                               |   43 ++
>   netx/net/sourceforge/jnlp/JNLPFile.java                 |   55 +++
>   netx/net/sourceforge/jnlp/SecurityDesc.java             |   12 +
>   netx/net/sourceforge/jnlp/resources/Messages.properties |    2 +
>   netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java  |  255 +++++++++++++++-
>   netx/net/sourceforge/jnlp/security/MoreInfoPane.java    |   13 +
>   netx/net/sourceforge/jnlp/security/SecurityDialog.java  |   14 +-
>   7 files changed, 389 insertions(+), 5 deletions(-)
>
> diffs (truncated from 592 to 500 lines):
>
> diff -r 61e08e67b176 -r bd59947fa857 ChangeLog
> --- 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 at 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 at redhat.com>
>
>   	Update UI for SecurityDialog
> diff -r 61e08e67b176 -r bd59947fa857 netx/net/sourceforge/jnlp/JNLPFile.java
> --- 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;
> +    }
> +
>   }
> diff -r 61e08e67b176 -r bd59947fa857 netx/net/sourceforge/jnlp/SecurityDesc.java
> --- 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;
> +    }
>
>   }
> diff -r 61e08e67b176 -r bd59947fa857 netx/net/sourceforge/jnlp/resources/Messages.properties
> --- 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.
> diff -r 61e08e67b176 -r bd59947fa857 netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
> --- 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();




More information about the distro-pkg-dev mailing list