/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