changeset 536:274ff243ba73

Major rework of JarCertVerifier certificate management. This is a long-planned rework of JarCertVerifier, allowing it to handle multiple certificates. The algorithms used to verify jars with multiple certificates vary between JNLPs and Applets.
author Danesh Dadachanji <ddadacha@redhat.com>
date Mon, 22 Oct 2012 11:02:38 -0400
parents cdc7637779f4
children 09c91b85a1a7
files ChangeLog NEWS netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/security/AppVerifier.java netx/net/sourceforge/jnlp/security/CertVerifier.java netx/net/sourceforge/jnlp/security/CertWarningPane.java netx/net/sourceforge/jnlp/security/CertsInfoPane.java netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java netx/net/sourceforge/jnlp/security/MoreInfoPane.java netx/net/sourceforge/jnlp/security/PluginAppVerifier.java netx/net/sourceforge/jnlp/tools/CertInformation.java netx/net/sourceforge/jnlp/tools/JarCertVerifier.java tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java
diffstat 16 files changed, 1882 insertions(+), 410 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Oct 22 10:39:35 2012 -0400
+++ b/ChangeLog	Mon Oct 22 11:02:38 2012 -0400
@@ -28,6 +28,76 @@
 	* tests/reproducers/custom/AppletFolderInArchiveTag/srcs/Makefile: and
 	* tests/reproducers/custom/UnsignedContentInMETAINF/srcs/Makefile: following above renaming
 
+2012-10-19  Adam Domurad  <adomurad@redhat.com>
+
+	* netx/net/sourceforge/jnlp/security/AppVerifier.java: Use interface 
+	types for declared types where applicable.
+	* netx/net/sourceforge/jnlp/security/PluginAppVerifier.java: Same.
+	* netx/net/sourceforge/jnlp/tools/JarCertVerifier.java: Same.
+
+2012-10-19  Danesh Dadachanji  <ddadacha@redhat.com>
+
+	Rework JarCertVerifier certificate management to handle multiple
+	certificates and use different algorithms to verify JNLPs and Applets.
+	* netx/net/sourceforge/jnlp/resources/Messages.properties:
+	Removed SHasUnsignedEntry.
+	* netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java:
+	Set JCV instance to final but uninitialized.
+	(JNLPClassLoader): Initialized JCV with runtime dependent verifier.
+	(addNewJar), (initializeResources), (verifySignedJNLP):
+	Replaced use of local JarCertVerifier variable with the instance  variable.
+	Added calls to isFullySigned wherever signer verification is done.
+	(activateJars): No longer verifies nested jars. These receive the same
+	security permissions as their parent jar, regardless of the nested
+	jar's signing.
+	(checkTrustWithUser): Removed JCV param, reimplemented to wrap around
+	JCV's checkTrustWithUser method.
+	(verifyJars): Removed.
+	* netx/net/sourceforge/jnlp/security/AppVerifier.java:
+	New strategy pattern interface that specifies verification methods
+	required regardless of the runtime.
+	* netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java:
+	* netx/net/sourceforge/jnlp/security/PluginAppVerifier.java:
+	New strategy pattern classes used to determine which algorithms to use
+	depending on the runtime.
+	* netx/net/sourceforge/jnlp/security/CertVerifier.java:
+	Added CertPath param to all the methods.
+	(noSigningIssues): Removed.
+	* netx/net/sourceforge/jnlp/security/CertWarningPane.java:
+	* netx/net/sourceforge/jnlp/security/CertsInfoPane.java:
+	* netx/net/sourceforge/jnlp/security/MoreInfoPane.java:
+	Updated calls to the verifier's methods with the new CertPath param. All
+	are set to null so far.
+	* netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java:
+	Added CertPath param to all the methods. It's mostly ignored though.
+	* netx/net/sourceforge/jnlp/tools/CertInformation.java:
+	New class to represent all the information about a signer with
+	with respect to all of the entries it has signed for the app.
+	* netx/net/sourceforge/jnlp/tools/JarCertVerifier.java:
+	Completely reworked to use CertInformation and AppVerifier functionality.
+	(getCertPath), (getCertInformation), (checkTrustWithUser),
+	(getJarSignableEntries), (getTotalJarEntries): New method.
+	(noSigningIssues), (anyJarsSigned): Removed.
+	(verifyResult): Renamed enum to VerifyResult
+	(JarCertVerifier): New constructor used to set AppVerifier instance.
+	(getAlreadyTrustPublisher), (getRootInCacerts): Now uses strategy pattern.
+	(hasSigningIssues), (getDetails), (checkTrustedCerts), (checkCertUsage):
+	Now uses cert info class.
+	(getCerts): Renamed to getCertsList.
+	(isFullySignedByASingleCert): renamed to isFullySigned and to use
+	the strategy pattern.
+	(add): New public method that resets some instance vars and
+	calls verifyJars.
+	(verifyJars): Modifier changed to private, above method should be used.
+	Also skips jars that have been verified before.
+	(verifyJar): Removed actual verification code, only reads jars into the JVM.
+	(verifyJarEntryCerts): New method. Does actual verification of jars.
+	(getPublisher), (getRoot): Use hacky currentlyUsed variable as the signer.
+	* tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java:
+	Unit test JCV's verifyJarEntryCerts method.
+	* tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java:
+	Unit test helper that creates CodeSigner instances. 
+
 2012-10-16  Adam Domurad  <adomurad@redhat.com>
 
 	* tests/reproducers/simple/AppletTakesLastParam/srcs/AppletTakesLastParam.java:
--- a/NEWS	Mon Oct 22 10:39:35 2012 -0400
+++ b/NEWS	Mon Oct 22 11:02:38 2012 -0400
@@ -19,6 +19,7 @@
   - PR955: regression: SweetHome3D fails to run
   - PR1145: IcedTea-Web can cause ClassCircularityError
   - PR1161: X509VariableTrustManager does not work correctly with OpenJDK7
+  - PR822: Applets fail to load if jars have different signers
 
 New in release 1.3 (2012-XX-XX):
 * NetX
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties	Mon Oct 22 11:02:38 2012 -0400
@@ -82,7 +82,7 @@
 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.
 LNoSecInstance=Error: No security instance for {0}. The application may have trouble continuing
-LCertFoundIn={0} found in cacerts ({1})
+LCertFoundIn={0} found in cacerts ({1})
 LSingleInstanceExists=Another instance of this applet already exists and only one may be run at the same time.
  
 JNotApplet=File is not an applet.
@@ -227,7 +227,6 @@
 SBadKeyUsage=Resources contain entries whose signer certificate's KeyUsage extension doesn't allow code signing.
 SBadExtendedKeyUsage=Resources contain entries whose signer certificate's ExtendedKeyUsage extension doesn't allow code signing.
 SBadNetscapeCertType=Resources contain entries whose signer certificate's NetscapeCertType extension doesn't allow code signing.
-SHasUnsignedEntry=Resources contain unsigned entries which have not been integrity-checked.
 SHasExpiredCert=The digital signature has expired.
 SHasExpiringCert=Resources contain entries whose signer certificate will expire within six months.
 SNotYetValidCert=Resources contain entries whose signer certificate is not yet valid.
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Mon Oct 22 11:02:38 2012 -0400
@@ -79,8 +79,10 @@
 import net.sourceforge.jnlp.cache.IllegalResourceDescriptorException;
 import net.sourceforge.jnlp.cache.ResourceTracker;
 import net.sourceforge.jnlp.cache.UpdatePolicy;
+import net.sourceforge.jnlp.security.AppVerifier;
+import net.sourceforge.jnlp.security.JNLPAppVerifier;
+import net.sourceforge.jnlp.security.PluginAppVerifier;
 import net.sourceforge.jnlp.security.SecurityDialogs;
-import net.sourceforge.jnlp.security.SecurityDialogs.AccessType;
 import net.sourceforge.jnlp.tools.JarCertVerifier;
 import net.sourceforge.jnlp.util.FileUtils;
 import sun.misc.JarIndex;
@@ -153,14 +155,8 @@
     /** all jars not yet part of classloader or active */
     private List<JARDesc> available = new ArrayList<JARDesc>();
 
-    /** all of the jar files that were verified */
-    private ArrayList<String> verifiedJars = null;
-
-    /** all of the jar files that were not verified */
-    private ArrayList<String> unverifiedJars = null;
-
     /** the jar cert verifier tool to verify our jars */
-    private JarCertVerifier jcv = null;
+    private final JarCertVerifier jcv;
 
     private boolean signing = false;
 
@@ -223,6 +219,16 @@
 
         this.mainClass = mainName;
 
+        AppVerifier verifier;
+
+        if (file instanceof PluginBridge && !((PluginBridge)file).useJNLPHref()) {
+            verifier = new PluginAppVerifier();
+        } else {
+            verifier = new JNLPAppVerifier();
+        }
+
+        jcv = new JarCertVerifier(verifier);
+
         // initialize extensions
         initializeExtensions();
 
@@ -604,10 +610,8 @@
 
         if (JNLPRuntime.isVerifying()) {
 
-            JarCertVerifier jcv;
-
             try {
-                jcv = verifyJars(initialJars);
+                jcv.add(initialJars, tracker);
             } catch (Exception e) {
                 //we caught an Exception from the JarCertVerifier class.
                 //Note: one of these exceptions could be from not being able
@@ -618,7 +622,7 @@
             }
 
             //Case when at least one jar has some signing
-            if (jcv.anyJarsSigned() && jcv.isFullySignedByASingleCert()) {
+            if (jcv.isFullySigned()) {
                 signing = true;
 
                 if (!jcv.allJarsSigned() &&
@@ -650,10 +654,10 @@
                 // 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 (!jcv.getAlreadyTrustPublisher() && !jcv.isTriviallySigned()) {
-                    checkTrustWithUser(jcv);
+                if (!jcv.isTriviallySigned()) {
+                    checkTrustWithUser();
                 } else {
                     /**
                      * If the user trusts this publisher (i.e. the publisher's certificate
@@ -864,7 +868,6 @@
     private void verifySignedJNLP(JARDesc jarDesc, JarFile jarFile)
             throws LaunchException {
 
-        JarCertVerifier signer = new JarCertVerifier();
         List<JARDesc> desc = new ArrayList<JARDesc>();
         desc.add(jarDesc);
 
@@ -875,9 +878,9 @@
         InputStreamReader jnlpReader = null;
 
         try {
-            signer.verifyJars(desc, tracker);
-
-            if (signer.allJarsSigned()) { // If the jar is signed
+            // NOTE: verification should have happened by now. In other words,
+            // calling jcv.verifyJars(desc, tracker) here should have no affect.
+            if (jcv.isFullySigned()) {
 
                 Enumeration<JarEntry> entries = jarFile.entries();
                 JarEntry je;
@@ -961,7 +964,7 @@
             /*
              * After this exception is caught, it is escaped. If an exception is
              * thrown while handling the jar file, (mainly for
-             * JarCertVerifier.verifyJars) it assumes the jar file is unsigned and
+             * JarCertVerifier.add) it assumes the jar file is unsigned and
              * skip the check for a signed JNLP file
              */
             
@@ -991,28 +994,18 @@
                 e.printStackTrace(System.err);
             }
     }
-    
-    private void checkTrustWithUser(JarCertVerifier jcv) throws LaunchException {
+
+    /**
+     * Prompt the user for trust on all the signers that require approval.
+     * @throws LaunchException if the user does not approve every dialog prompt.
+     */
+    private void checkTrustWithUser() throws LaunchException {
         if (JNLPRuntime.isTrustAll()){
             return;
         }
-        if (!jcv.getRootInCacerts()) { //root cert is not in cacerts
-            boolean b = SecurityDialogs.showCertWarningDialog(
-                    AccessType.UNVERIFIED, file, jcv);
-            if (!b)
-                throw new LaunchException(null, null, R("LSFatal"),
-                        R("LCLaunching"), R("LNotVerified"), "");
-        } else if (jcv.getRootInCacerts()) { //root cert is in cacerts
-            boolean b = false;
-            if (jcv.noSigningIssues())
-                b = SecurityDialogs.showCertWarningDialog(
-                        AccessType.VERIFIED, file, jcv);
-            else if (!jcv.noSigningIssues())
-                b = SecurityDialogs.showCertWarningDialog(
-                        AccessType.SIGNING_ERROR, file, jcv);
-            if (!b)
-                throw new LaunchException(null, null, R("LSFatal"),
-                        R("LCLaunching"), R("LCancelOnUserRequest"), "");
+
+        if (jcv.isFullySigned() && !jcv.getAlreadyTrustPublisher()) {
+            jcv.checkTrustWithUser(file);
         }
     }
 
@@ -1226,15 +1219,25 @@
                                         continue;
                                     }
 
-                                    JarCertVerifier signer = new JarCertVerifier();
-                                    List<JARDesc> jars = new ArrayList<JARDesc>();
-                                    JARDesc jarDesc = new JARDesc(new File(extractedJarLocation).toURL(), null, null, false, false, false, false);
-                                    jars.add(jarDesc);
                                     tracker.addResource(new File(extractedJarLocation).toURL(), null, null, null);
-                                    signer.verifyJars(jars, tracker);
+
+                                    URL codebase = file.getCodeBase();
+                                    if (codebase == null) {
+                                        //FIXME: codebase should be the codebase of the Main Jar not
+                                        //the location. Although, it still works in the current state.
+                                        codebase = file.getResources().getMainJAR().getLocation();
+                                    }
 
-                                    if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) {
-                                        checkTrustWithUser(signer);
+                                    SecurityDesc jarSecurity = null;
+                                    if (jcv.isFullySigned()) {
+                                        // Already trust application, nested jar should be given
+                                        jarSecurity = new SecurityDesc(file,
+                                                SecurityDesc.ALL_PERMISSIONS,
+                                                codebase.getHost());
+                                    } else {
+                                        jarSecurity = new SecurityDesc(file,
+                                                SecurityDesc.SANDBOX_PERMISSIONS,
+                                                codebase.getHost());
                                     }
 
                                     try {
@@ -1244,25 +1247,6 @@
                                         CachedJarFileCallback.getInstance().addMapping(fakeRemote, fileURL);
                                         addURL(fakeRemote);
 
-                                        SecurityDesc jarSecurity = file.getSecurity();
-
-                                        if (file instanceof PluginBridge) {
-
-                                            URL codebase = null;
-
-                                            if (file.getCodeBase() != null) {
-                                                codebase = file.getCodeBase();
-                                            } else {
-                                                //Fixme: codebase should be the codebase of the Main Jar not
-                                                //the location. Although, it still works in the current state.
-                                                codebase = file.getResources().getMainJAR().getLocation();
-                                            }
-
-                                            jarSecurity = new SecurityDesc(file,
-                                                    SecurityDesc.ALL_PERMISSIONS,
-                                                    codebase.getHost());
-                                        }
-
                                         jarLocationSecurityMap.put(fakeRemote, jarSecurity);
 
                                     } catch (MalformedURLException mfue) {
@@ -1475,18 +1459,6 @@
     }
 
     /**
-         * Verifies code signing of jars to be used.
-         *
-         * @param jars the jars to be verified.
-         */
-    private JarCertVerifier verifyJars(List<JARDesc> jars) throws Exception {
-
-        jcv = new JarCertVerifier();
-        jcv.verifyJars(jars, tracker);
-        return jcv;
-    }
-
-    /**
      * Find the loaded class in this loader or any of its extension loaders.
      */
     protected Class findLoadedClassAll(String name) {
@@ -1642,7 +1614,6 @@
 
             // Verify if needed
 
-            final JarCertVerifier signer = new JarCertVerifier();
             final List<JARDesc> jars = new ArrayList<JARDesc>();
             jars.add(desc);
 
@@ -1654,14 +1625,12 @@
 
             AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                 public Void run() throws Exception {
-                    signer.verifyJars(jars, tracker);
+                    jcv.add(jars, tracker);
 
-                    if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) {
-                        checkTrustWithUser(signer);
-                    }
+                    checkTrustWithUser();
 
                     final SecurityDesc security;
-                    if (signer.anyJarsSigned()) {
+                    if (jcv.isFullySigned()) {
                         security = new SecurityDesc(file,
                                 SecurityDesc.ALL_PERMISSIONS,
                                 file.getCodeBase().getHost());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/AppVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -0,0 +1,91 @@
+/* AppVerifier.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+*/
+
+package net.sourceforge.jnlp.security;
+
+import java.security.cert.CertPath;
+import java.util.HashMap;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.tools.CertInformation;
+import net.sourceforge.jnlp.tools.JarCertVerifier;
+
+/**
+ * An interface that provides various details about an app's signers.
+ */
+public interface AppVerifier {
+
+    /**
+     * Checks if the app has already found trust in its publisher(s).
+     * @param certs The certs to search through and their cert information
+     * @param signedJars A map of all the jars of this app and the number of
+     * signed entries each one has.
+     * @return True if the app trusts its publishers.
+     */
+    public boolean hasAlreadyTrustedPublisher(
+            HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars);
+
+    /**
+     * Checks if the app has signer(s) whose certs along their chains are in CA certs.
+     * @param certs The certs to search through and their cert information
+     * @param signedJars A map of all the jars of this app and the number of
+     * signed entries each one has.
+     * @return True if the app has a root in the CA certs store.
+     */
+    public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars);
+
+    /**
+     * Checks if the app's jars are covered by the provided certificates, enough
+     * to consider the app fully signed.
+     * @param certs Any possible signer and their respective information regarding this app.
+     * @param signedJars A map of all the jars of this app and the number of
+     * signed entries each one has.
+     * @return
+     */
+    public boolean isFullySigned(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars);
+
+    /**
+     * Prompt the user with requests for trusting the certificates used by this app
+     * @throws LaunchException
+     */
+    public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file)
+            throws LaunchException;
+}
--- a/netx/net/sourceforge/jnlp/security/CertVerifier.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/security/CertVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -1,5 +1,5 @@
 /* CertVerifier.java
-   Copyright (C) 2009 Red Hat, Inc.
+   Copyright (C) 2012 Red Hat, Inc.
 
 This file is part of IcedTea.
 
@@ -39,10 +39,10 @@
 
 import java.security.cert.CertPath;
 import java.security.cert.Certificate;
-import java.util.ArrayList;
+import java.util.List;
 
 /**
- * An interface that provides various details about a certificate
+ * An interface that provides various details about certificates of an app.
  */
 
 public interface CertVerifier {
@@ -58,36 +58,30 @@
     public boolean getRootInCacerts();
 
     /**
-     * Return if there are signing issues with the certificate(s) being veried
+     * Return if there are signing issues with the certificate being verified
      */
-    public boolean hasSigningIssues();
-
-    /**
-     * Return if there are no signing issues with this cert (!hasSigningIssues())
-     */
-    public boolean noSigningIssues();
+    public boolean hasSigningIssues(CertPath certPath);
 
     /**
-     * Get the details regarding issue(s) with this certificate
+     * Get the details regarding issue with this certificate
      */
-    public ArrayList<String> getDetails();
+    public List<String> getDetails(CertPath certPath);
 
     /**
-     * Return a valid certificate path to this certificate(s) being verified
+     * Return a valid certificate path to this certificate being verified
      * @return The CertPath
      */
-    public CertPath getCertPath();
+    public CertPath getCertPath(CertPath certPath);
 
     /**
      * Returns the application's publisher's certificate.
      */
-    public abstract Certificate getPublisher();
+    public abstract Certificate getPublisher(CertPath certPath);
 
     /**
      * Returns the application's root's certificate. This
-     * may return the same certificate as getPublisher() in
+     * may return the same certificate as getPublisher(CertPath certPath) in
      * the event that the application is self signed.
      */
-    public abstract Certificate getRoot();
-
+    public abstract Certificate getRoot(CertPath certPath);
 }
--- a/netx/net/sourceforge/jnlp/security/CertWarningPane.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/security/CertWarningPane.java	Mon Oct 22 11:02:38 2012 -0400
@@ -96,7 +96,7 @@
     private void addComponents() {
         AccessType type = parent.getAccessType();
         JNLPFile file = parent.getFile();
-        Certificate c = parent.getCertVerifier().getPublisher();
+        Certificate c = parent.getCertVerifier().getPublisher(null);
 
         String name = "";
         String publisher = "";
@@ -253,7 +253,7 @@
             if (alwaysTrust != null && alwaysTrust.isSelected()) {
                 try {
                     KeyStore ks = KeyStores.getKeyStore(Level.USER, Type.CERTS);
-                    X509Certificate c = (X509Certificate) parent.getCertVerifier().getPublisher();
+                    X509Certificate c = (X509Certificate) parent.getCertVerifier().getPublisher(null);
                     CertificateUtils.addToKeyStore(c, ks);
                     File keyStoreFile = new File(KeyStores.getKeyStoreLocation(Level.USER, Type.CERTS));
                     if (!keyStoreFile.isFile()) {
--- a/netx/net/sourceforge/jnlp/security/CertsInfoPane.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/security/CertsInfoPane.java	Mon Oct 22 11:02:38 2012 -0400
@@ -84,7 +84,7 @@
      * Builds the JTree out of CertPaths.
      */
     void buildTree() {
-        certPath = parent.getCertVerifier().getCertPath();
+        certPath = parent.getCertVerifier().getCertPath(null);
         X509Certificate firstCert =
                         ((X509Certificate) certPath.getCertificates().get(0));
         String subjectString =
--- a/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -80,7 +80,14 @@
         return isTrusted;
     }
 
-    public CertPath getCertPath() {
+
+    /* XXX: Most of these methods have a CertPath param that should be passed
+     * from the UI dialogs. However, this is not implemented yet so most of
+     * the params are ignored.
+     */
+
+    @Override
+    public CertPath getCertPath(CertPath certPath) { // Parameter ignored.
 
         ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
         for (int i = 0; i < chain.length; i++)
@@ -99,7 +106,8 @@
         return certPaths.get(0);
     }
 
-    public ArrayList<String> getDetails() {
+    @Override
+    public List<String> getDetails(CertPath certPath) { // Parameter ignored.
 
         boolean hasExpiredCert = false;
         boolean hasExpiringCert = false;
@@ -192,13 +200,15 @@
             details.add(detail);
     }
 
-    public Certificate getPublisher() {
+    @Override
+    public Certificate getPublisher(CertPath certPath) { // Paramater ignored.
         if (chain.length > 0)
             return (Certificate) chain[0];
         return null;
     }
 
-    public Certificate getRoot() {
+    @Override
+    public Certificate getRoot(CertPath certPath) { // Parameter ignored.
         if (chain.length > 0)
             return (Certificate) chain[chain.length - 1];
         return null;
@@ -207,18 +217,14 @@
     public boolean getRootInCacerts() {
         try {
             KeyStore[] caCertsKeyStores = KeyStores.getCAKeyStores();
-            return CertificateUtils.inKeyStores((X509Certificate) getRoot(), caCertsKeyStores);
+            return CertificateUtils.inKeyStores((X509Certificate) getRoot(null), caCertsKeyStores);
         } catch (Exception e) {
         }
         return false;
     }
 
-    public boolean hasSigningIssues() {
+    @Override
+    public boolean hasSigningIssues(CertPath certPath) {
         return false;
     }
-
-    public boolean noSigningIssues() {
-        return false;
-    }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -0,0 +1,143 @@
+/* JNLPAppVerifier.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+
+package net.sourceforge.jnlp.security;
+
+import static net.sourceforge.jnlp.runtime.Translator.R;
+
+import java.security.cert.CertPath;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.security.SecurityDialogs.AccessType;
+import net.sourceforge.jnlp.tools.CertInformation;
+import net.sourceforge.jnlp.tools.JarCertVerifier;
+
+public class JNLPAppVerifier implements AppVerifier {
+
+    @Override
+    public boolean hasAlreadyTrustedPublisher(
+            HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+        int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars);
+        for (CertInformation certInfo : certs.values()) {
+            Map<String, Integer> certSignedJars = certInfo.getSignedJars();
+
+            if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries
+                    && certInfo.isPublisherAlreadyTrusted()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+        int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars);
+        for (CertInformation certInfo : certs.values()) {
+            Map<String, Integer> certSignedJars = certInfo.getSignedJars();
+
+            if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries
+                    && certInfo.isRootInCacerts()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isFullySigned(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+        int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars);
+        for (CertPath cPath : certs.keySet()) {
+            // If this cert has signed everything, return true
+            if (hasCompletelySignedApp(certs.get(cPath), sumOfSignableEntries)) {
+                return true;
+            }
+        }
+
+        // No cert found that signed all entries. Return false.
+        return false;
+    }
+
+    @Override
+    public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file)
+            throws LaunchException {
+
+        int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(jcv.getJarSignableEntries());
+        for (CertPath cPath : jcv.getCertsList()) {
+            jcv.setCurrentlyUsedCertPath(cPath);
+            CertInformation info = jcv.getCertInformation(cPath);
+            if (hasCompletelySignedApp(info, sumOfSignableEntries)) {
+                if (info.isPublisherAlreadyTrusted()) {
+                    return;
+                }
+
+                AccessType dialogType;
+                if (info.isRootInCacerts() && !info.hasSigningIssues()) {
+                    dialogType = AccessType.VERIFIED;
+                } else if (info.isRootInCacerts()) {
+                    dialogType = AccessType.SIGNING_ERROR;
+                } else {
+                    dialogType = AccessType.UNVERIFIED;
+                }
+
+                boolean wasApproved = SecurityDialogs.showCertWarningDialog(
+                        dialogType, file, jcv);
+                if (wasApproved) {
+                    return;
+                }
+            }
+        }
+
+        throw new LaunchException(null, null, R("LSFatal"), R("LCLaunching"),
+                R("LCancelOnUserRequest"), "");
+    }
+
+    /**
+     * Find out if the CertPath with the given info has fully signed the app.
+     * @param info The information regarding the CertPath in question
+     * @param sumOfSignableEntries The total number of signable entries in the app.
+     * @return True if the signer has fully signed this app.
+     */
+    public boolean hasCompletelySignedApp(CertInformation info, int sumOfSignableEntries) {
+        return JarCertVerifier.getTotalJarEntries(info.getSignedJars()) == sumOfSignableEntries;
+    }
+}
--- a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java	Mon Oct 22 11:02:38 2012 -0400
@@ -44,7 +44,7 @@
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.util.ArrayList;
+import java.util.List;
 
 import javax.swing.BorderFactory;
 import javax.swing.ImageIcon;
@@ -73,7 +73,7 @@
      * Constructs the GUI components of this panel
      */
     private void addComponents() {
-        ArrayList<String> details = certVerifier.getDetails();
+        List<String> details = certVerifier.getDetails(null);
 
         // Show signed JNLP warning if the signed main jar does not have a
         // signed JNLP file and the launching JNLP file contains special properties
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/PluginAppVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -0,0 +1,224 @@
+/* PluginAppVerifier.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+
+package net.sourceforge.jnlp.security;
+
+import static net.sourceforge.jnlp.runtime.Translator.R;
+
+import java.security.cert.CertPath;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.security.SecurityDialogs.AccessType;
+import net.sourceforge.jnlp.tools.CertInformation;
+import net.sourceforge.jnlp.tools.JarCertVerifier;
+
+public class PluginAppVerifier implements AppVerifier {
+
+    @Override
+    public boolean hasAlreadyTrustedPublisher(
+            HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+
+        boolean allPublishersTrusted = true;
+
+        for(String jarName : signedJars.keySet()) {
+            int numbSignableEntries = signedJars.get(jarName);
+            boolean publisherTrusted = false;
+
+            for (CertInformation certInfo : certs.values()) {
+                if(certInfo.isSignerOfJar(jarName)
+                        && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName)
+                        && certInfo.isPublisherAlreadyTrusted()) {
+                    publisherTrusted = true;
+                    break;
+                }
+            }
+
+            allPublishersTrusted &= publisherTrusted;
+        }
+        return allPublishersTrusted;
+    }
+
+    @Override
+    public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+
+        boolean allRootCAsTrusted = true;
+
+        for(String jarName : signedJars.keySet()) {
+            int numbSignableEntries = signedJars.get(jarName);
+            boolean rootCATrusted = false;
+
+            for (CertInformation certInfo : certs.values()) {
+                if(certInfo.isSignerOfJar(jarName)
+                        && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName)
+                        && certInfo.isRootInCacerts()) {
+                    rootCATrusted = true;
+                    break;
+                }
+            }
+
+            allRootCAsTrusted &= rootCATrusted;
+        }
+        return allRootCAsTrusted;
+    }
+
+    @Override
+    public boolean isFullySigned(HashMap<CertPath, CertInformation> certs,
+            HashMap<String, Integer> signedJars) {
+
+        boolean isFullySigned = true;
+
+        for(String jarName : signedJars.keySet()) {
+            int numbSignableEntries = signedJars.get(jarName);
+            boolean isSigned = false;
+
+            for (CertInformation certInfo : certs.values()) {
+                if(certInfo.isSignerOfJar(jarName)
+                        && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName)) {
+                    isSigned = true;
+                    break;
+                }
+            }
+
+            isFullySigned &= isSigned;
+        }
+
+        return isFullySigned;
+    }
+
+    @Override
+    public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file)
+            throws LaunchException {
+        ArrayList<CertPath> certPaths = buildCertPathsList(jcv);
+        ArrayList<CertPath> alreadyApprovedByUser = new ArrayList<CertPath>();
+        for (String jarName : jcv.getJarSignableEntries().keySet()) {
+            boolean trustFoundOrApproved = false;
+            for (CertPath cPathApproved : alreadyApprovedByUser) {
+                jcv.setCurrentlyUsedCertPath(cPathApproved);
+                CertInformation info = jcv.getCertInformation(cPathApproved);
+                if (info.isSignerOfJar(jarName)
+                        && alreadyApprovedByUser.contains(cPathApproved)) {
+                    trustFoundOrApproved = true;
+                    break;
+                }
+            }
+
+            if (trustFoundOrApproved) {
+                continue;
+            }
+
+            for (CertPath cPath : certPaths) {
+                jcv.setCurrentlyUsedCertPath(cPath);
+                CertInformation info = jcv.getCertInformation(cPath);
+                if (info.isSignerOfJar(jarName)) {
+                    if (info.isPublisherAlreadyTrusted()) {
+                        trustFoundOrApproved = true;
+                        alreadyApprovedByUser.add(cPath);
+                        break;
+                    }
+
+                    AccessType dialogType;
+                    if (info.isRootInCacerts() && !info.hasSigningIssues()) {
+                        dialogType = AccessType.VERIFIED;
+                    } else if (info.isRootInCacerts()) {
+                        dialogType = AccessType.SIGNING_ERROR;
+                    } else {
+                        dialogType = AccessType.UNVERIFIED;
+                    }
+
+                    boolean wasApproved = SecurityDialogs.showCertWarningDialog(
+                            dialogType, file, jcv);
+                    if (wasApproved) {
+                        alreadyApprovedByUser.add(cPath);
+                        trustFoundOrApproved = true;
+                        break;
+                    }
+                }
+            }
+            if (!trustFoundOrApproved) {
+                throw new LaunchException(null, null, R("LSFatal"),
+                    R("LCLaunching"), R("LCancelOnUserRequest"), "");
+            }
+        }
+    }
+
+    /**
+     * Build a list of all the CertPaths that were detected in the provided
+     * JCV, placing them in the most trusted possible order.
+     * @param jcv The verifier containing the CertPaths to examine.
+     * @return A list of CertPaths sorted in the following order: Signers with
+     *   1. Already trusted publishers
+     *   2. Roots in the CA store and have no signing issues
+     *   3. Roots in the CA store but have signing issues
+     *   4. Everything else
+     */
+    public ArrayList<CertPath> buildCertPathsList(JarCertVerifier jcv) {
+        ArrayList<CertPath> certPathsList = jcv.getCertsList();
+        ArrayList<CertPath> returnList = new ArrayList<CertPath>();
+
+        for (CertPath cPath : certPathsList) {
+            if (!returnList.contains(cPath)
+                    && jcv.getCertInformation(cPath).isPublisherAlreadyTrusted())
+                returnList.add(cPath);
+        }
+
+        for (CertPath cPath : certPathsList) {
+            if (!returnList.contains(cPath)
+                    && jcv.getCertInformation(cPath).isRootInCacerts()
+                    && !jcv.getCertInformation(cPath).hasSigningIssues())
+                returnList.add(cPath);
+        }
+
+        for (CertPath cPath : certPathsList) {
+            if (!returnList.contains(cPath)
+                    && jcv.getCertInformation(cPath).isRootInCacerts()
+                    && jcv.getCertInformation(cPath).hasSigningIssues())
+                returnList.add(cPath);
+        }
+
+        for (CertPath cPath : certPathsList) {
+            if (!returnList.contains(cPath))
+                returnList.add(cPath);
+        }
+
+        return returnList;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/tools/CertInformation.java	Mon Oct 22 11:02:38 2012 -0400
@@ -0,0 +1,292 @@
+/* CertInformation.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+
+package net.sourceforge.jnlp.tools;
+
+import static net.sourceforge.jnlp.runtime.Translator.R;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+/**
+ * Maintains information about a CertPath that has signed at least one of the
+ * entries provided by a jar of the app.
+ */
+public class CertInformation {
+
+    private boolean hasExpiredCert = false;
+    private boolean hasExpiringCert = false;
+
+    private boolean isNotYetValidCert = false;
+
+    /* Code signer properties of the certificate. */
+    private boolean hasBadKeyUsage = false;
+    private boolean hasBadExtendedKeyUsage = false;
+    private boolean hasBadNetscapeCertType = false;
+
+    private boolean alreadyTrustPublisher = false;
+    private boolean rootInCacerts = false;
+
+    static enum Detail {
+        TRUSTED (R("STrustedCertificate")),
+        UNTRUSTED (R("SUntrustedCertificate")),
+        RUN_WITHOUT_RESTRICTIONS(R("SRunWithoutRestrictions")),
+        EXPIRED (R("SHasExpiredCert")),
+        EXPIRING (R("SHasExpiringCert")),
+        NOT_YET_VALID (R("SNotYetValidCert")),
+        BAD_KEY_USAGE (R("SBadKeyUsage")),
+        BAT_EXTENDED_KEY_USAGE (R("SBadExtendedKeyUsage")),
+        BAD_NETSCAPE_CERT_TYPE (R("SBadNetscapeCertType"));
+
+        private final String message;
+        Detail(String issue) {
+            message = issue;
+        }
+
+        public String message() {
+            return message;
+        }
+    }
+
+    private EnumSet<Detail> details = EnumSet.noneOf(Detail.class);
+
+    /** The jars and their number of entries this cert has signed. */
+    private HashMap<String, Integer> signedJars = new HashMap<String, Integer>();
+
+    /**
+     * Return if there are signing issues with this certificate.
+     * @return true if there are any issues with expiry, validity or bad key usage.
+     */
+    public boolean hasSigningIssues() {
+        return hasExpiredCert || isNotYetValidCert || hasBadKeyUsage
+                || hasBadExtendedKeyUsage || hasBadNetscapeCertType;
+    }
+
+    /**
+     * Return whether or not the publisher is already trusted.
+     *
+     * @return True if the publisher is trusted already.
+     */
+    public boolean isPublisherAlreadyTrusted() {
+        return alreadyTrustPublisher;
+    }
+
+    /**
+     * Set whether or not the publisher is already trusted.
+     *
+     */
+    public void setAlreadyTrustPublisher() {
+        alreadyTrustPublisher = true;
+    }
+
+    /**
+     * Return whether or not the root is in the list of trusted CA certificates.
+     *
+     * @return True if the root is in the list of CA certificates.
+     */
+    public boolean isRootInCacerts() {
+        return rootInCacerts;
+    }
+
+    /**
+     * Set that this cert's root CA is to be trusted.
+     */
+    public void setRootInCacerts() {
+        rootInCacerts = true;
+        details.add(Detail.TRUSTED);
+    }
+
+    /**
+     * Resets any trust of the root and publisher. Also removes unnecessary
+     * details from the list of issues.
+     */
+    public void resetForReverification() {
+        alreadyTrustPublisher = false;
+        rootInCacerts = false;
+        removeFromDetails(Detail.UNTRUSTED);
+        removeFromDetails(Detail.TRUSTED);
+    }
+    /**
+     * Check if this cert is the signer of a jar.
+     * @param jarName The absolute path of the jar this certificate has signed.
+     * @return true if this cert has signed the jar found at jarName.
+     */
+    public boolean isSignerOfJar(String jarName) {
+        return signedJars.containsKey(jarName);
+    }
+
+    /**
+     * Add a jar to the list of jars this certificate has signed along with the
+     * number of entries it has signed in the jar.
+     *
+     * @param jarName The absolute path of the jar this certificate has signed.
+     * @param signedEntriesCount The number of entries this cert has signed in jarName.
+     */
+    public void setNumJarEntriesSigned(String jarName, int signedEntriesCount) {
+        if (signedJars.containsKey(jarName)) {
+            if (JNLPRuntime.isDebug())
+                System.err.println("WARNING: A jar that has already been "
+                        + "verified is being yet again verified: " + jarName);
+        } else {
+            signedJars.put(jarName, signedEntriesCount);
+        }
+    }
+
+    /**
+     * Find the number of entries this cert has signed in the specified jar.
+     * @param jarName The absolute path of the jar this certificate has signed.
+     * @return The number of entries this cert has signed in jarName.
+     */
+    public int getNumJarEntriesSigned(String jarName) {
+        return signedJars.get(jarName);
+    }
+
+    /**
+     * Get all the jars this cert has signed along with the number of entries
+     * in each jar.
+     * @return
+     */
+    public Map<String, Integer> getSignedJars() {
+        return signedJars;
+    }
+
+    /**
+     * Get the details regarding issue(s) with this certificate.
+     *
+     * @return A list of all the details/issues with this app.
+     */
+    public List<String> getDetailsAsStrings() {
+        List<String> detailsToStr = new ArrayList<String>();
+        for (Detail issue : details) {
+            detailsToStr.add(issue.message());
+        }
+        return detailsToStr;
+    }
+
+    /**
+     * Remove an issue from the list of details of issues with this certificate.
+     * List is unchanged if detail was not present.
+     *
+     * @param detail The issue to be removed regarding this certificate.
+     */
+    private void removeFromDetails(Detail detail) {
+        details.remove(detail);
+    }
+
+    /**
+     * Set that this cert is expired and add this issue to the list of details.
+     */
+    public void setHasExpiredCert() {
+        hasExpiredCert = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.EXPIRED);
+    }
+
+    /**
+     * Set that this cert is expiring within 6 months and add this issue to
+     * the list of details.
+     */
+    public void setHasExpiringCert() {
+        hasExpiringCert = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.EXPIRING);
+    }
+
+    /**
+     * Get whether or not this cert will expire within 6 months.
+     * @return true if the cert will be expired after 6 months.
+     */
+    public boolean hasExpiringCert() {
+        return hasExpiringCert;
+    }
+
+    /**
+     * Set that this cert is not yet valid
+     * and add this issue to the list of details.
+     */
+    public void setNotYetValidCert() {
+        isNotYetValidCert = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.NOT_YET_VALID);
+    }
+
+
+    /**
+     * Set that this cert has bad key usage
+     * and add this issue to the list of details.
+     */
+    public void setBadKeyUsage() {
+        hasBadKeyUsage = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.BAD_KEY_USAGE);
+    }
+
+
+    /**
+     * Set that this cert has bad extended key usage
+     * and add this issue to the list of details.
+     */
+    public void setBadExtendedKeyUsage() {
+        hasBadExtendedKeyUsage = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.BAT_EXTENDED_KEY_USAGE);
+    }
+
+
+    /**
+     * Set that this cert has a bad netscape cert type
+     * and add this issue to the list of details.
+     */
+    public void setBadNetscapeCertType() {
+        hasBadNetscapeCertType = true;
+        details.add(Detail.RUN_WITHOUT_RESTRICTIONS);
+        details.add(Detail.BAD_NETSCAPE_CERT_TYPE);
+    }
+
+    /**
+     * Set that this cert and all of its CAs are untrusted so far.
+     */
+    public void setUntrusted() {
+        details.add(Detail.UNTRUSTED);
+
+    }
+}
--- a/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java	Mon Oct 22 11:02:38 2012 -0400
@@ -25,25 +25,41 @@
 
 package net.sourceforge.jnlp.tools;
 
-import static net.sourceforge.jnlp.runtime.Translator.R;
-
-import java.io.*;
-import java.util.*;
-import java.util.jar.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.CodeSigner;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
-import java.security.cert.CertPath;
-import java.security.*;
-import sun.security.x509.*;
-import sun.security.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.cache.*;
-import net.sourceforge.jnlp.security.*;
+import net.sourceforge.jnlp.JARDesc;
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.cache.ResourceTracker;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.security.AppVerifier;
+import net.sourceforge.jnlp.security.CertVerifier;
+import net.sourceforge.jnlp.security.CertificateUtils;
+import net.sourceforge.jnlp.security.KeyStores;
+import sun.security.util.DerInputStream;
+import sun.security.util.DerValue;
+import sun.security.x509.NetscapeCertTypeExtension;
 
 /**
- * <p>The jar certificate verifier utility.
- *
+ * <p>
+ * The jar certificate verifier utility.
+ * 
  * @author Roland Schemers
  * @author Jan Luehe
  */
@@ -55,53 +71,39 @@
     // prefix for new signature-related files in META-INF directory
     private static final String SIG_PREFIX = META_INF + "SIG-";
 
-    private static final long SIX_MONTHS = 180 * 24 * 60 * 60 * 1000L; //milliseconds
+    private static final long SIX_MONTHS = 180 * 24 * 60 * 60 * 1000L; // milliseconds
 
-    static enum verifyResult {
+    static enum VerifyResult {
         UNSIGNED, SIGNED_OK, SIGNED_NOT_OK
     }
 
-    // signer's certificate chain (when composing)
-    X509Certificate[] certChain;
+    /** All of the jar files that were verified for signing */
+    private ArrayList<String> verifiedJars = new ArrayList<String>();
 
-    boolean verbose = false; // verbose output when signing/verifying
-    boolean showcerts = false; // show certs when verifying
+    /** All of the jar files that were not verified */
+    private ArrayList<String> unverifiedJars = new ArrayList<String>();
+
+    /** The certificates used for jar verification linked to their respective information */
+    private HashMap<CertPath, CertInformation> certs = new HashMap<CertPath, CertInformation>();
 
-    private boolean hasExpiredCert = false;
-    private boolean hasExpiringCert = false;
-    private boolean notYetValidCert = false;
+    /** Temporary cert path hack to be used to keep track of which one a UI dialog is using */
+    private CertPath currentlyUsed;
 
-    private boolean badKeyUsage = false;
-    private boolean badExtendedKeyUsage = false;
-    private boolean badNetscapeCertType = false;
+    /** Absolute location to jars and the number of entries which are possibly signable */
+    private HashMap<String, Integer> jarSignableEntries = new HashMap<String, Integer>();
 
-    private boolean alreadyTrustPublisher = false;
-    private boolean rootInCacerts = false;
+    /** The application verifier to use by this instance */
+    private AppVerifier appVerifier;
 
     /**
-     * The single certPath used in this JarSiging. We're only keeping
-     * track of one here, since in practice there's only one signer
-     * for a JNLP Application.
+     * Create a new jar certificate verifier utility that uses the provided verifier for its strategy pattern.
+     * 
+     * @param verifier
+     *            The application verifier to be used by the new instance.
      */
-    private CertPath certPath = null;
-
-    private boolean noSigningIssues = true;
-
-    private boolean anyJarsSigned = false;
-
-    /** all of the jar files that were verified */
-    private ArrayList<String> verifiedJars = null;
-
-    /** all of the jar files that were not verified */
-    private ArrayList<String> unverifiedJars = null;
-
-    /** the certificates used for jar verification */
-    private HashMap<CertPath, Integer> certs = new HashMap<CertPath, Integer>();
-
-    /** details of this signing */
-    private ArrayList<String> details = new ArrayList<String>();
-
-    private int totalSignableEntries = 0;
+    public JarCertVerifier(AppVerifier verifier) {
+        appVerifier = verifier;
+    }
 
     /** Whether a signable entry was found within jars (jars with content more than just META-INF/*) */
     private boolean triviallySigned = false;
@@ -113,88 +115,107 @@
         return triviallySigned;
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getAlreadyTrustPublisher()
-     */
     public boolean getAlreadyTrustPublisher() {
-        return alreadyTrustPublisher;
+        boolean allPublishersTrusted = appVerifier.hasAlreadyTrustedPublisher(
+                certs, jarSignableEntries);
+        if (JNLPRuntime.isDebug()) {
+            System.out.println("App already has trusted publisher: "
+                    + allPublishersTrusted);
+        }
+        return allPublishersTrusted;
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getRootInCacerts()
-     */
     public boolean getRootInCacerts() {
-        return rootInCacerts;
-    }
-
-    public CertPath getCertPath() {
-        return certPath;
+        boolean allRootCAsTrusted = appVerifier.hasRootInCacerts(certs,
+                jarSignableEntries);
+        if (JNLPRuntime.isDebug()) {
+            System.out.println("App has trusted root CA: " + allRootCAsTrusted);
+        }
+        return allRootCAsTrusted;
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#hasSigningIssues()
-     */
-    public boolean hasSigningIssues() {
-        return hasExpiredCert || notYetValidCert || badKeyUsage
-                || badExtendedKeyUsage || badNetscapeCertType;
+    public CertPath getCertPath(CertPath cPath) { // Parameter ignored.
+        return currentlyUsed;
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#noSigningIssues()
-     */
-    public boolean noSigningIssues() {
-        return noSigningIssues;
+    public boolean hasSigningIssues(CertPath certPath) {
+        return certs.get(certPath).hasSigningIssues();
     }
 
-    public boolean anyJarsSigned() {
-        return anyJarsSigned;
+    public List<String> getDetails(CertPath certPath) {
+        if (certPath != null) {
+            currentlyUsed = certPath;
+        }
+        return certs.get(currentlyUsed).getDetailsAsStrings();
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getDetails()
+    /**
+     * Get a list of the cert paths of all signers across the app.
+     * 
+     * @return ArrayList of CertPath vars representing each of the signers present on any jar.
      */
-    public ArrayList<String> getDetails() {
-        return details;
-    }
-
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getCerts()
-     */
-    public ArrayList<CertPath> getCerts() {
+    public ArrayList<CertPath> getCertsList() {
         return new ArrayList<CertPath>(certs.keySet());
     }
 
     /**
-     * Returns whether or not all entries have a common signer.
-     *  
-     * It is possible to create jars where only some entries are signed. In 
-     * such cases, we should not prompt the user to accept anything, as the whole 
-     * application must be treated as unsigned. This method should be called by a 
-     * caller before it is about to ask the user to accept a cert and determine 
-     * whether the application is trusted or not.
-     *  
-     * @return Whether or not all entries have a common signer
+     * Find the information the specified cert path has with respect to this application.
+     * 
+     * @return All the information the path has with this app.
      */
-    public boolean isFullySignedByASingleCert() {
+    public CertInformation getCertInformation(CertPath cPath) {
+        return certs.get(cPath);
+    }
 
+    /**
+     * Returns whether or not the app is considered completely signed.
+     * 
+     * An app using a JNLP is considered signed if all of the entries of its jars are signed by at least one common signer.
+     * 
+     * An applet on the other hand only needs to have each individual jar be fully signed by a signer. The signers can differ between jars.
+     * 
+     * @return Whether or not the app is considered signed.
+     */
+    // FIXME: Change javadoc once applets do not need entire jars signed.
+    public boolean isFullySigned() {
         if (triviallySigned)
             return true;
-
-        for (CertPath cPath : certs.keySet()) {
-            // If this cert has signed everything, return true
-            if (certs.get(cPath) == totalSignableEntries)
-                return true;
+        boolean fullySigned = appVerifier.isFullySigned(certs,
+                jarSignableEntries);
+        if (JNLPRuntime.isDebug()) {
+            System.out.println("App already has trusted publisher: "
+                    + fullySigned);
         }
-
-        // No cert found that signed all entries. Return false.
-        return false;
+        return fullySigned;
     }
 
-    public void verifyJars(List<JARDesc> jars, ResourceTracker tracker)
+    /**
+     * Update the verifier to consider new jars when verifying.
+     * 
+     * @param jars
+     *            List of new jars to be verified.
+     * @param tracker
+     *            Resource tracker used to obtain the the jars from cache
+     * @throws Exception
+     *             Caused by issues with obtaining the jars' entries or interacting with the tracker.
+     */
+    public void add(List<JARDesc> jars, ResourceTracker tracker)
             throws Exception {
+        verifyJars(jars, tracker);
+    }
 
-        verifiedJars = new ArrayList<String>();
-        unverifiedJars = new ArrayList<String>();
+    /**
+     * Verify the jars provided and update the state of this instance to match the new information.
+     * 
+     * @param jars
+     *            List of new jars to be verified.
+     * @param tracker
+     *            Resource tracker used to obtain the the jars from cache
+     * @throws Exception
+     *             Caused by issues with obtaining the jars' entries or interacting with the tracker.
+     */
+    private void verifyJars(List<JARDesc> jars, ResourceTracker tracker)
+            throws Exception {
 
         for (JARDesc jar : jars) {
 
@@ -209,17 +230,22 @@
                 }
 
                 String localFile = jarFile.getAbsolutePath();
-                verifyResult result = verifyJar(localFile);
+                if (verifiedJars.contains(localFile)
+                        || unverifiedJars.contains(localFile)) {
+                    continue;
+                }
+
+                VerifyResult result = verifyJar(localFile);
                 triviallySigned = false;
 
-                if (result == verifyResult.UNSIGNED) {
+                if (result == VerifyResult.UNSIGNED) {
                     unverifiedJars.add(localFile);
-                } else if (result == verifyResult.SIGNED_NOT_OK) {
-                    noSigningIssues = false;
+                } else if (result == VerifyResult.SIGNED_NOT_OK) {
                     verifiedJars.add(localFile);
-                } else if (result == verifyResult.SIGNED_OK) {
+                } else if (result == VerifyResult.SIGNED_OK) {
                     verifiedJars.add(localFile);
-                    triviallySigned = totalSignableEntries <= 0 && certs.size() <= 0;
+                    triviallySigned = getTotalJarEntries(jarSignableEntries) <= 0
+                            && certs.size() <= 0;
                 }
             } catch (Exception e) {
                 // We may catch exceptions from using verifyJar()
@@ -228,26 +254,20 @@
             }
         }
 
-        //we really only want the first certPath
-        for (CertPath cPath : certs.keySet()) {
-
-            if (certs.get(cPath) != totalSignableEntries)
-                continue;
-            else
-                certPath = cPath;
-
-            // check if the certs added above are in the trusted path
-            checkTrustedCerts();
-
-            if (alreadyTrustPublisher || rootInCacerts)
-                break;
-        }
-
+        for (CertPath certPath : certs.keySet())
+            checkTrustedCerts(certPath);
     }
 
-    private verifyResult verifyJar(String jarName) throws Exception {
-        boolean anySigned = false;
-        boolean hasUnsignedEntry = false;
+    /**
+     * Checks through all the jar entries of jarName for signers, storing all the common ones in the certs hash map.
+     * 
+     * @param jarName
+     *            The absolute path to the jar file.
+     * @return The return of {@link JarCertVerifier#verifyJarEntryCerts} using the entries found in the jar located at jarName.
+     * @throws Exception
+     *             Will be thrown if there are any problems with the jar.
+     */
+    private VerifyResult verifyJar(String jarName) throws Exception {
         JarFile jarFile = null;
 
         try {
@@ -262,10 +282,9 @@
 
                 InputStream is = jarFile.getInputStream(je);
                 try {
-                    int n;
-                    while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+                    while (is.read(buffer, 0, buffer.length) != -1) {
                         // we just read. this will throw a SecurityException
-                        // if  a signature/digest check fails.
+                        // if a signature/digest check fails.
                     }
                 } finally {
                     if (is != null) {
@@ -273,95 +292,9 @@
                     }
                 }
             }
-
-            if (jarFile.getManifest() != null) {
-                if (verbose)
-                    System.out.println();
-
-                long now = System.currentTimeMillis();
-
-                for (JarEntry je : entriesVec) {
-                    String name = je.getName();
-                    CodeSigner[] signers = je.getCodeSigners();
-                    boolean isSigned = (signers != null);
-                    anySigned |= isSigned;
-
-                    boolean shouldHaveSignature = !je.isDirectory()
-                                                && !isMetaInfFile(name);
-
-                    hasUnsignedEntry |= shouldHaveSignature &&  !isSigned;
-
-                    if (shouldHaveSignature)
-                        totalSignableEntries++;
-
-                    if (shouldHaveSignature && isSigned) {
-                        for (int i = 0; i < signers.length; i++) {
-                            CertPath certPath = signers[i].getSignerCertPath();
-                            if (!certs.containsKey(certPath))
-                                certs.put(certPath, 1);
-                            else
-                                certs.put(certPath, certs.get(certPath) + 1);
-
-                            Certificate cert = signers[i].getSignerCertPath()
-                                    .getCertificates().get(0);
-                            if (cert instanceof X509Certificate) {
-                                checkCertUsage((X509Certificate) cert, null);
-                                if (!showcerts) {
-                                    long notBefore = ((X509Certificate) cert)
-                                                     .getNotBefore().getTime();
-                                    long notAfter = ((X509Certificate) cert)
-                                                    .getNotAfter().getTime();
-
-                                    if (now < notBefore) {
-                                        notYetValidCert = true;
-                                    }
+            return verifyJarEntryCerts(jarName, jarFile.getManifest() != null,
+                    entriesVec);
 
-                                    if (notAfter < now) {
-                                        hasExpiredCert = true;
-                                    } else if (notAfter < now + SIX_MONTHS) {
-                                        hasExpiringCert = true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } //while e has more elements
-            } else { //if man not null
-
-                // Else increment totalEntries by 1 so that unsigned jars with 
-                // no manifests can't sneak in
-                totalSignableEntries++;
-            }
-
-            //Alert the user if any of the following are true.
-            if (!anySigned) {
-                return verifyResult.UNSIGNED;
-            } else {
-                anyJarsSigned = true;
-
-                //warnings
-                if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert ||
-                        badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
-                        notYetValidCert) {
-
-                    addToDetails(R("SRunWithoutRestrictions"));
-
-                    if (badKeyUsage)
-                        addToDetails(R("SBadKeyUsage"));
-                    if (badExtendedKeyUsage)
-                        addToDetails(R("SBadExtendedKeyUsage"));
-                    if (badNetscapeCertType)
-                        addToDetails(R("SBadNetscapeCertType"));
-                    if (hasUnsignedEntry)
-                        addToDetails(R("SHasUnsignedEntry"));
-                    if (hasExpiredCert)
-                        addToDetails(R("SHasExpiredCert"));
-                    if (hasExpiringCert)
-                        addToDetails(R("SHasExpiringCert"));
-                    if (notYetValidCert)
-                        addToDetails(R("SNotYetValidCert"));
-                }
-            }
         } catch (Exception e) {
             e.printStackTrace();
             throw e;
@@ -370,51 +303,175 @@
                 jarFile.close();
             }
         }
-
-        //anySigned does not guarantee that all files were signed.
-        return (anySigned && !(hasUnsignedEntry || hasExpiredCert
-                              || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || notYetValidCert)) ? verifyResult.SIGNED_OK : verifyResult.SIGNED_NOT_OK;
     }
 
     /**
-     * Checks the user's trusted.certs file and the cacerts file to see
-     * if a publisher's and/or CA's certificate exists there.
+     * Checks through all the jar entries for signers, storing all the common ones in the certs hash map.
+     * 
+     * @param jarName
+     *            The absolute path to the jar file.
+     * @param jarHasManifest
+     *            Whether or not the associated jar has a manifest.
+     * @param entries
+     *            The list of entries in the associated jar.
+     * @return If there is at least one signable entry that is not signed by a common signer, return UNSIGNED. Otherwise every signable entry is signed by at least one common signer. If the signer has no issues, return SIGNED_OK. If there are any signing issues, return SIGNED_NOT_OK.
+     * @throws Exception
+     *             Will be thrown if there are issues with entries.
      */
-    private void checkTrustedCerts() throws Exception {
-        if (certPath != null) {
-            try {
-                X509Certificate publisher = (X509Certificate) getPublisher();
-                KeyStore[] certKeyStores = KeyStores.getCertKeyStores();
-                alreadyTrustPublisher = CertificateUtils.inKeyStores(publisher, certKeyStores);
-                X509Certificate root = (X509Certificate) getRoot();
-                KeyStore[] caKeyStores = KeyStores.getCAKeyStores();
-                // Check entire cert path for a trusted CA
-                for (Certificate c : certPath.getCertificates()) {
-                	if ((rootInCacerts = CertificateUtils.inKeyStores(
-                            (X509Certificate) c, caKeyStores))) {
-                        break;
+    VerifyResult verifyJarEntryCerts(String jarName, boolean jarHasManifest,
+            Vector<JarEntry> entries) throws Exception {
+        // Contains number of entries the cert with this CertPath has signed.
+        HashMap<CertPath, Integer> jarSignCount = new HashMap<CertPath, Integer>();
+        int numSignableEntriesInJar = 0;
+
+        // Record current time just before checking the jar begins.
+        long now = System.currentTimeMillis();
+        if (jarHasManifest) {
+
+            for (JarEntry je : entries) {
+                String name = je.getName();
+                CodeSigner[] signers = je.getCodeSigners();
+                boolean isSigned = (signers != null);
+
+                boolean shouldHaveSignature = !je.isDirectory()
+                        && !isMetaInfFile(name);
+
+                if (shouldHaveSignature) {
+                    numSignableEntriesInJar++;
+                }
+
+                if (shouldHaveSignature && isSigned) {
+                    for (int i = 0; i < signers.length; i++) {
+                        CertPath certPath = signers[i].getSignerCertPath();
+
+                        if (!jarSignCount.containsKey(certPath))
+                            jarSignCount.put(certPath, 1);
+                        else
+                            jarSignCount.put(certPath,
+                                    jarSignCount.get(certPath) + 1);
                     }
                 }
-            } catch (Exception e) {
-                // TODO: Warn user about not being able to
-                // look through their cacerts/trusted.certs
-                // file depending on exception.
-                throw e;
+            } // while e has more elements
+        } else { // if manifest is null
+
+            // Else increment total entries by 1 so that unsigned jars with
+            // no manifests can't sneak in
+            numSignableEntriesInJar++;
+        }
+
+        jarSignableEntries.put(jarName, numSignableEntriesInJar);
+
+        // Find all signers that have signed every signable entry in this jar.
+        boolean allEntriesSignedBySingleCert = false;
+        for (CertPath certPath : jarSignCount.keySet()) {
+            if (jarSignCount.get(certPath) == numSignableEntriesInJar) {
+                allEntriesSignedBySingleCert = true;
+
+                boolean wasPreviouslyVerified = certs.containsKey(certPath);
+                if (!wasPreviouslyVerified)
+                    certs.put(certPath, new CertInformation());
+
+                CertInformation certInfo = certs.get(certPath);
+                if (wasPreviouslyVerified)
+                    certInfo.resetForReverification();
+
+                certInfo.setNumJarEntriesSigned(jarName,
+                        numSignableEntriesInJar);
+
+                Certificate cert = certPath.getCertificates().get(0);
+                if (cert instanceof X509Certificate) {
+                    checkCertUsage(certPath, (X509Certificate) cert, null);
+                    long notBefore = ((X509Certificate) cert).getNotBefore().getTime();
+                    long notAfter = ((X509Certificate) cert).getNotAfter().getTime();
+                    if (now < notBefore) {
+                        certInfo.setNotYetValidCert();
+                    }
+
+                    if (notAfter < now) {
+                        certInfo.setHasExpiredCert();
+                    } else if (notAfter < now + SIX_MONTHS) {
+                        certInfo.setHasExpiringCert();
+                    }
+                }
             }
+        }
 
-            if (!rootInCacerts)
-                addToDetails(R("SUntrustedCertificate"));
-            else
-                addToDetails(R("STrustedCertificate"));
+        // Every signable entry of this jar needs to be signed by at least
+        // one signer for the jar to be considered successfully signed.
+        VerifyResult result = null;
+        if (allEntriesSignedBySingleCert) {
+
+            // We need to find at least one signer without any issues.
+            for (CertPath entryCertPath : jarSignCount.keySet()) {
+                if (certs.containsKey(entryCertPath)
+                        && !hasSigningIssues(entryCertPath)) {
+                    result = VerifyResult.SIGNED_OK;
+                    break;
+                }
+            }
+            if (result == null) {
+                // All signers had issues
+                result = VerifyResult.SIGNED_NOT_OK;
+            }
+        } else {
+            result = VerifyResult.UNSIGNED;
         }
+
+        if (JNLPRuntime.isDebug()) {
+            System.out.println("Jar found at " + jarName
+                    + "has been verified as " + result);
+        }
+        return result;
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getPublisher()
+    /**
+     * Checks the user's trusted.certs file and the cacerts file to see if a
+     * publisher's and/or CA's certificate exists there.
+     *
+     * @param certPath
+     *            The cert path of the signer being checked for trust.
      */
-    public Certificate getPublisher() {
-        if (certPath != null) {
-            List<? extends Certificate> certList = certPath.getCertificates();
+    private void checkTrustedCerts(CertPath certPath) throws Exception {
+        CertInformation info = certs.get(certPath);
+        try {
+            X509Certificate publisher = (X509Certificate) getPublisher(certPath);
+            KeyStore[] certKeyStores = KeyStores.getCertKeyStores();
+            if (CertificateUtils.inKeyStores(publisher, certKeyStores))
+                info.setAlreadyTrustPublisher();
+            KeyStore[] caKeyStores = KeyStores.getCAKeyStores();
+            // Check entire cert path for a trusted CA
+            for (Certificate c : certPath.getCertificates()) {
+                if (CertificateUtils.inKeyStores((X509Certificate) c,
+                        caKeyStores)) {
+                    info.setRootInCacerts();
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            // TODO: Warn user about not being able to
+            // look through their cacerts/trusted.certs
+            // file depending on exception.
+            if (JNLPRuntime.isDebug()) {
+                System.out.println("WARNING: Unable to read through cert store files.");
+            }
+            throw e;
+        }
+
+        // Otherwise a parent cert was not found to be trusted.
+        info.setUntrusted();
+    }
+
+    public void setCurrentlyUsedCertPath(CertPath cPath) {
+        currentlyUsed = cPath;
+    }
+
+    public Certificate getPublisher(CertPath cPath) {
+        if (cPath != null) {
+            currentlyUsed = cPath;
+        }
+        if (currentlyUsed != null) {
+            List<? extends Certificate> certList = currentlyUsed
+                    .getCertificates();
             if (certList.size() > 0) {
                 return certList.get(0);
             } else {
@@ -425,12 +482,13 @@
         }
     }
 
-    /* (non-Javadoc)
-     * @see net.sourceforge.jnlp.tools.CertVerifier2#getRoot()
-     */
-    public Certificate getRoot() {
-        if (certPath != null) {
-            List<? extends Certificate> certList = certPath.getCertificates();
+    public Certificate getRoot(CertPath cPath) {
+        if (cPath != null) {
+            currentlyUsed = cPath;
+        }
+        if (currentlyUsed != null) {
+            List<? extends Certificate> certList = currentlyUsed
+                    .getCertificates();
             if (certList.size() > 0) {
                 return certList.get(certList.size() - 1);
             } else {
@@ -441,20 +499,10 @@
         }
     }
 
-    private void addToDetails(String detail) {
-        if (!details.contains(detail))
-            details.add(detail);
-    }
-
     /**
      * Returns whether a file is in META-INF, and thus does not require signing.
-     *
-     * Signature-related files under META-INF include:
-     * . META-INF/MANIFEST.MF
-     * . META-INF/SIG-*
-     * . META-INF/*.SF
-     * . META-INF/*.DSA
-     * . META-INF/*.RSA
+     * 
+     * Signature-related files under META-INF include: . META-INF/MANIFEST.MF . META-INF/SIG-* . META-INF/*.SF . META-INF/*.DSA . META-INF/*.RSA
      */
     static boolean isMetaInfFile(String name) {
         String ucName = name.toUpperCase();
@@ -463,15 +511,19 @@
 
     /**
      * Check if userCert is designed to be a code signer
-     * @param userCert the certificate to be examined
-     * @param bad 3 booleans to show if the KeyUsage, ExtendedKeyUsage,
-     *            NetscapeCertType has codeSigning flag turned on.
-     *            If null, the class field badKeyUsage, badExtendedKeyUsage,
+     * 
+     * @param userCert
+     *            the certificate to be examined
+     * @param bad
+     *            3 booleans to show if the KeyUsage, ExtendedKeyUsage, 
+     *            NetscapeCertType has codeSigning flag turned on. If null, 
+     *            the class field badKeyUsage, badExtendedKeyUsage, 
      *            badNetscapeCertType will be set.
-     *
-     * Required for verifyJar()
+     * 
+     *            Required for verifyJar()
      */
-    void checkCertUsage(X509Certificate userCert, boolean[] bad) {
+    void checkCertUsage(CertPath certPath, X509Certificate userCert,
+            boolean[] bad) {
 
         // Can act as a signer?
         // 1. if KeyUsage, then [0] should be true
@@ -489,7 +541,7 @@
                 if (bad != null) {
                     bad[0] = true;
                 } else {
-                    badKeyUsage = true;
+                    certs.get(certPath).setBadKeyUsage();
                 }
             }
         }
@@ -502,7 +554,7 @@
                     if (bad != null) {
                         bad[1] = true;
                     } else {
-                        badExtendedKeyUsage = true;
+                        certs.get(certPath).setBadExtendedKeyUsage();
                     }
                 }
             }
@@ -512,24 +564,24 @@
 
         try {
             // OID_NETSCAPE_CERT_TYPE
-            byte[] netscapeEx = userCert.getExtensionValue
-                                ("2.16.840.1.113730.1.1");
+            byte[] netscapeEx = userCert
+                    .getExtensionValue("2.16.840.1.113730.1.1");
             if (netscapeEx != null) {
                 DerInputStream in = new DerInputStream(netscapeEx);
                 byte[] encoded = in.getOctetString();
                 encoded = new DerValue(encoded).getUnalignedBitString()
                         .toByteArray();
 
-                NetscapeCertTypeExtension extn =
-                        new NetscapeCertTypeExtension(encoded);
+                NetscapeCertTypeExtension extn = new NetscapeCertTypeExtension(
+                        encoded);
 
-                Boolean val = (Boolean) extn.get(
-                                  NetscapeCertTypeExtension.OBJECT_SIGNING);
+                Boolean val = (Boolean) extn
+                        .get(NetscapeCertTypeExtension.OBJECT_SIGNING);
                 if (!val) {
                     if (bad != null) {
                         bad[2] = true;
                     } else {
-                        badNetscapeCertType = true;
+                        certs.get(certPath).setBadNetscapeCertType();
                     }
                 }
             }
@@ -540,11 +592,31 @@
 
     /**
      * Returns if all jars are signed.
-     *
+     * 
      * @return True if all jars are signed, false if there are one or more unsigned jars
      */
     public boolean allJarsSigned() {
         return this.unverifiedJars.size() == 0;
     }
 
+    public void checkTrustWithUser(JNLPFile file) throws LaunchException {
+        appVerifier.checkTrustWithUser(this, file);
+    }
+
+    public Map<String, Integer> getJarSignableEntries() {
+        return Collections.unmodifiableMap(jarSignableEntries);
+    }
+
+    /**
+     * Get the total number of entries in the provided map.
+     * 
+     * @return The number of entries.
+     */
+    public static int getTotalJarEntries(Map<String, Integer> map) {
+        int sum = 0;
+        for (int value : map.values()) {
+            sum += value;
+        }
+        return sum;
+    }
 }
--- a/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java	Mon Oct 22 10:39:35 2012 -0400
+++ b/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java	Mon Oct 22 11:02:38 2012 -0400
@@ -37,18 +37,484 @@
 
 package net.sourceforge.jnlp.tools;
 
-import static org.junit.Assert.*;
+import static net.sourceforge.jnlp.runtime.Translator.R;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+import java.security.CodeSigner;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+
+import net.sourceforge.jnlp.JARDesc;
+import net.sourceforge.jnlp.tools.JarCertVerifier.VerifyResult;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class JarCertVerifierTest {
 
     @Test
     public void testIsMetaInfFile() {
-        final String METAINF ="META-INF";
+        final String METAINF = "META-INF";
         assertFalse(JarCertVerifier.isMetaInfFile("some_dir/" + METAINF + "/filename"));
         assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "filename"));
         assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/filename"));
     }
 
+    class JarCertVerifierEntry extends JarEntry {
+        CodeSigner[] signers;
+
+        public JarCertVerifierEntry(String name, CodeSigner[] codesigners) {
+            super(name);
+            signers = codesigners;
+        }
+
+        public JarCertVerifierEntry(String name) {
+            this(name, null);
+        }
+
+        public CodeSigner[] getCodeSigners() {
+            return signers == null ? null : signers.clone();
+        }
+    }
+
+    // Empty list to be used with JarCertVerifier constructor.
+    private static final List<JARDesc> emptyJARDescList = new Vector<JARDesc>();
+
+    private static final String DNPARTIAL = ", OU=JarCertVerifier Unit Test, O=IcedTea, L=Toronto, ST=Ontario, C=CA";
+    private static CodeSigner alphaSigner, betaSigner, charlieSigner,
+            expiredSigner, expiringSigner, notYetValidSigner, expiringAndNotYetValidSigner;
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        Date currentDate = new Date();
+        Date pastDate = new Date(currentDate.getTime() - (1000L * 24L * 60L * 60L) - 1000L); // 1 day and 1 second in the past
+        Date futureDate = new Date(currentDate.getTime() + (1000L * 24L * 60L * 60L)); // 1 day in the future
+        alphaSigner = CodeSignerCreator.getOneCodeSigner("CN=Alpha Signer" + DNPARTIAL, currentDate, 365);
+        betaSigner = CodeSignerCreator.getOneCodeSigner("CN=Beta Signer" + DNPARTIAL, currentDate, 365);
+        charlieSigner = CodeSignerCreator.getOneCodeSigner("CN=Charlie Signer" + DNPARTIAL, currentDate, 365);
+        expiredSigner = CodeSignerCreator.getOneCodeSigner("CN=Expired Signer" + DNPARTIAL, pastDate, 1);
+        expiringSigner = CodeSignerCreator.getOneCodeSigner("CN=Expiring Signer" + DNPARTIAL, currentDate, 1);
+        notYetValidSigner = CodeSignerCreator.getOneCodeSigner("CN=Not Yet Valid Signer" + DNPARTIAL, futureDate, 365);
+        expiringAndNotYetValidSigner = CodeSignerCreator.getOneCodeSigner("CN=Expiring and Not Yet Valid Signer" + DNPARTIAL, futureDate, 3);
+    }
+
+    @Test
+    public void testNoManifest() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        VerifyResult result = jcv.verifyJarEntryCerts("", false, null);
+
+        Assert.assertEquals("No manifest should be considered unsigned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("No manifest means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testNoSignableEntries() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("OneDirEntry/"));
+        entries.add(new JarCertVerifierEntry("META-INF/MANIFEST.MF"));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("No signable entry (only dirs/manifests) should be considered unsigned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("No signable entry (only dirs/manifests) means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testSingleEntryNoSigners() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner"));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One unsigned entry should be considered unsigned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("One unsigned entry means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testManyEntriesNoSigners() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner"));
+        entries.add(new JarCertVerifierEntry("secondEntryWithoutSigner"));
+        entries.add(new JarCertVerifierEntry("thirdEntryWithoutSigner"));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Many unsigned entries should be considered unsigned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("Many unsigned entries means no signers in the verifier.", 0,
+                jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testSingleEntrySingleValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] signers = { alphaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByOne", signers));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One signed entry should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("One signed entry means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("One signed entry means one signer in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesSingleValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] signers = { alphaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByOne", signers));
+        entries.add(new JarCertVerifierEntry("secondSignedByOne", signers));
+        entries.add(new JarCertVerifierEntry("thirdSignedByOne", signers));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by one signer should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries signed by one signer means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by one signer means one signer in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testSingleEntryMultipleValidSigners() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByThree", signers));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by three signers should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("One entry signed by three means three signers in the verifier.",
+                3, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by three means three signers in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(betaSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(charlieSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesMultipleValidSigners() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByThree", signers));
+        entries.add(new JarCertVerifierEntry("secondSignedByThree", signers));
+        entries.add(new JarCertVerifierEntry("thirdSignedByThree", signers));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by three signers should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries signed by three means three signers in the verifier.",
+                3, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by three means three signers in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(betaSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(charlieSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testOneCommonSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] alphaSigners = { alphaSigner };
+        CodeSigner[] betaSigners = { alphaSigner, betaSigner };
+        CodeSigner[] charlieSigners = { alphaSigner, charlieSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByOne", alphaSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByTwo", betaSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByTwo", charlieSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by at least one common signer should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries signed completely by only one signer means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed completely by only one signer means one signer in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testNoCommonSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] alphaSigners = { alphaSigner };
+        CodeSigner[] betaSigners = { betaSigner };
+        CodeSigner[] charlieSigners = { charlieSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByBeta", betaSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByCharlie", charlieSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by no common signers should be considered unsigned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("Three entries signed by no common signers means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testFewButNotAllCommonSigners() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] alphaSigners = { alphaSigner };
+        CodeSigner[] betaSigners = { betaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByBeta", betaSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("First two entries signed by alpha signer, third entry signed by beta signer should be considered unisgned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("Three entries signed by some common signers but not all means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testNotAllEntriesSigned() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] alphaSigners = { alphaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners));
+        entries.add(new JarCertVerifierEntry("thirdUnsigned"));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, should be considered unisgned.",
+                VerifyResult.UNSIGNED, result);
+        Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, means no signers in the verifier.",
+                0, jcv.getCertsList().size());
+    }
+
+    @Test
+    public void testSingleEntryExpiredSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] expiredSigners = { expiredSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by expired cert, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("One entry signed by expired cert means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by expired cert means one signer in the verifier.",
+                jcv.getCertsList().contains(expiredSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesExpiredSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] expiredSigners = { expiredSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedBExpired", expiredSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by expired cert, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("Three entries signed by expired cert means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by expired cert means one signer in the verifier.",
+                jcv.getCertsList().contains(expiredSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testSingleEntryExpiringSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] expiringSigners = { expiringSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by expiring cert, should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("One entry signed by expiring cert means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by expiring cert means one signer in the verifier.",
+                jcv.getCertsList().contains(expiringSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesExpiringSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] expiringSigners = { expiringSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedBExpiring", expiringSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByExpiring", expiringSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by expiring cert, should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries signed by expiring cert means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by expiring cert means one signer in the verifier.",
+                jcv.getCertsList().contains(expiringSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testSingleEntryNotYetValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] notYetValidSigners = { notYetValidSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by cert that is not yet valid, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("One entry signed by cert that is not yet valid means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by cert that is not yet valid means one signer in the verifier.",
+                jcv.getCertsList().contains(notYetValidSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesNotYetValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] notYetValidSigners = { notYetValidSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByNotYetValid", notYetValidSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByNotYetValid", notYetValidSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by cert that is not yet valid, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("Three entries signed by cert that is not yet valid means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by cert that is not yet valid means one signer in the verifier.",
+                jcv.getCertsList().contains(notYetValidSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testSingleEntryExpiringAndNotYetValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.",
+                jcv.getCertsList().contains(expiringAndNotYetValidSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntryExpiringAndNotYetValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+
+        CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByExpiringNotYetValid", expiringAndNotYetValidSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByExpiringNotYetValid", expiringAndNotYetValidSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.",
+                jcv.getCertsList().contains(expiringAndNotYetValidSigner.getSignerCertPath()));
+        Assert.assertTrue("Three entries signed by cert that is not yet valid but also expiring means expiring issue should be in details list.",
+                jcv.getDetails(expiringAndNotYetValidSigner.getSignerCertPath()).contains(R("SHasExpiringCert")));
+    }
+
+    @Test
+    public void testSingleEntryOneExpiredOneValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("One entry signed by one expired cert and another valid cert, should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("One entry signed by one expired cert and another valid cert means two signers in the verifier.",
+                2, jcv.getCertsList().size());
+        Assert.assertTrue("One entry signed by one expired cert and another valid cert means two signers in the verifier.",
+                jcv.getCertsList().contains(expiredSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyEntriesOneExpiredOneValidSigner() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner };
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner));
+        entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigner));
+        entries.add(new JarCertVerifierEntry("thirdSignedByTwo", oneExpiredOneValidSigner));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries signed by one expired cert and another valid cert, should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries signed by one expired cert and another valid cert means two signers in the verifier.",
+                2, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries signed by one expired cert and another valid cert means two signers in the verifier.",
+                jcv.getCertsList().contains(expiredSigner.getSignerCertPath())
+                        && jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testSomeExpiredEntries() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] oneExpiredOneValidSigners = { expiredSigner, alphaSigner };
+        CodeSigner[] expiredSigners = { expiredSigner };
+
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigners));
+        entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigners));
+        entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert, should be considered signed but not okay.",
+                VerifyResult.SIGNED_NOT_OK, result);
+        Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.",
+                jcv.getCertsList().contains(expiredSigner.getSignerCertPath()));
+    }
+
+    @Test
+    public void testManyInvalidOneValidStillSignedOkay() throws Exception {
+        JarCertVerifier jcv = new JarCertVerifier(null);
+        CodeSigner[] oneExpiredOneValidSigners = { alphaSigner, expiredSigner };
+        CodeSigner[] oneNotYetValidOneValidSigners = { alphaSigner, notYetValidSigner };
+        CodeSigner[] oneExpiringSigners = { alphaSigner, expiringSigner };
+
+        Vector<JarEntry> entries = new Vector<JarEntry>();
+        entries.add(new JarCertVerifierEntry("META-INF/MANIFEST.MF"));
+        entries.add(new JarCertVerifierEntry("firstSigned", oneExpiredOneValidSigners));
+        entries.add(new JarCertVerifierEntry("secondSigned", oneNotYetValidOneValidSigners));
+        entries.add(new JarCertVerifierEntry("thirdSigned", oneExpiringSigners));
+        entries.add(new JarCertVerifierEntry("oneDir/"));
+        entries.add(new JarCertVerifierEntry("oneDir/fourthSigned", oneExpiredOneValidSigners));
+        VerifyResult result = jcv.verifyJarEntryCerts("", true, entries);
+
+        Assert.assertEquals("Three entries sharing valid cert and others with issues, should be considered signed and okay.",
+                VerifyResult.SIGNED_OK, result);
+        Assert.assertEquals("Three entries sharing valid cert and others with issues means one signer in the verifier.",
+                1, jcv.getCertsList().size());
+        Assert.assertTrue("Three entries sharing valid cert and others with issues means one signer in the verifier.",
+                jcv.getCertsList().contains(alphaSigner.getSignerCertPath()));
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java	Mon Oct 22 11:02:38 2012 -0400
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.sourceforge.jnlp.tools;
+
+import java.security.CodeSigner;
+import java.security.PrivateKey;
+import java.security.Timestamp;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertAndKeyGen;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateIssuerName;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateSubjectName;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+
+public class CodeSignerCreator {
+
+    /**
+     * Create an X509 Certificate signed using SHA1withRSA with a 2048 bit key.
+     * @param dname Domain Name to represent the certificate
+     * @param notBefore The date by which the certificate starts being valid. Cannot be null.
+     * @param validity The number of days the certificate is valid after notBefore.
+     * @return An X509 certificate setup with properties using the specified parameters.
+     * @throws Exception
+     */
+    public static X509Certificate createCert(String dname, Date notBefore, int validity)
+            throws Exception {
+        int keysize = 2048;
+        String keyAlgName = "RSA";
+        String sigAlgName = "SHA1withRSA";
+
+        if (dname == null)
+            throw new Exception("Required DN is null. Please specify cert Domain Name via dname");
+        if (notBefore == null)
+            throw new Exception("Required start date is null. Please specify the date at which the cert is valid via notBefore");
+        if (validity < 0)
+            throw new Exception("Required validity is negative. Please specify the number of days for which the cert is valid after the start date.");
+
+        // KeyTool#doGenKeyPair
+        X500Name x500Name = new X500Name(dname);
+
+        CertAndKeyGen keypair = new CertAndKeyGen(keyAlgName, sigAlgName);
+
+        keypair.generate(keysize);
+        PrivateKey privKey = keypair.getPrivateKey();
+
+        X509Certificate oldCert = keypair.getSelfCertificate(x500Name,
+                notBefore, validity * 24L * 60L * 60L);
+
+        // KeyTool#doSelfCert
+        byte[] encoded = oldCert.getEncoded();
+        X509CertImpl certImpl = new X509CertImpl(encoded);
+        X509CertInfo certInfo = (X509CertInfo) certImpl.get(X509CertImpl.NAME
+                + "." + X509CertImpl.INFO);
+
+        Date notAfter = new Date(notBefore.getTime() + validity*1000L*24L*60L*60L);
+
+        CertificateValidity interval = new CertificateValidity(notBefore,
+                notAfter);
+
+        certInfo.set(X509CertInfo.VALIDITY, interval);
+        certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(
+                    new java.util.Random().nextInt() & 0x7fffffff));
+        certInfo.set(X509CertInfo.SUBJECT + "." + CertificateSubjectName.DN_NAME, x500Name);
+        certInfo.set(X509CertInfo.ISSUER + "." + CertificateIssuerName.DN_NAME, x500Name);
+
+        // The inner and outer signature algorithms have to match.
+        // The way we achieve that is really ugly, but there seems to be no
+        // other solution: We first sign the cert, then retrieve the
+        // outer sigalg and use it to set the inner sigalg
+        X509CertImpl newCert = new X509CertImpl(certInfo);
+        newCert.sign(privKey, sigAlgName);
+        AlgorithmId sigAlgid = (AlgorithmId)newCert.get(X509CertImpl.SIG_ALG);
+        certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgid);
+
+        certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
+
+        // FIXME Figure out extensions
+//        CertificateExtensions ext = createV3Extensions(
+//                null,
+//                (CertificateExtensions)certInfo.get(X509CertInfo.EXTENSIONS),
+//                v3ext,
+//                oldCert.getPublicKey(),
+//                null);
+//        certInfo.set(X509CertInfo.EXTENSIONS, ext);
+
+        newCert = new X509CertImpl(certInfo);
+        newCert.sign(privKey, sigAlgName);
+
+        return newCert;
+    }
+
+    /**
+     * Create a new code signer with the specified information.
+     * @param domainName Domain Name to represent the certificate
+     * @param notBefore The date by which the certificate starts being valid. Cannot be null.
+     * @param validity The number of days the certificate is valid after notBefore.
+     * @return A code signer with the properties passed through its parameters.
+     */
+    public static CodeSigner getOneCodeSigner(String domainName, Date notBefore, int validity)
+            throws Exception {
+        X509Certificate jarEntryCert = createCert(domainName, notBefore, validity);
+
+        ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>(1);
+        certs.add(jarEntryCert);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        CertPath certPath = cf.generateCertPath(certs);
+        Timestamp certTimestamp = new Timestamp(jarEntryCert.getNotBefore(), certPath);
+        return new CodeSigner(certPath, certTimestamp);
+    }
+}