changeset 89:3bd328e4b515

RH672262, CVE-2011-0025: IcedTea jarfile signature verification bypass Fixes JAR signature handling so that multiply/partially signed jars are correctly handled.
author Deepak Bhole <dbhole@redhat.com>
date Tue, 01 Feb 2011 10:53:44 -0500
parents e84432bca9a3
children 0db0d2392ef2
files ChangeLog NEWS netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/security/CertVerifier.java netx/net/sourceforge/jnlp/security/CertsInfoPane.java netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java netx/net/sourceforge/jnlp/tools/JarSigner.java
diffstat 7 files changed, 109 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Jan 28 15:51:03 2011 -0500
+++ b/ChangeLog	Tue Feb 01 10:53:44 2011 -0500
@@ -1,3 +1,33 @@
+2011-01-24 Deepak Bhole <dbhole@redhat.com>
+
+	RH672262, CVE-2011-0025: IcedTea jarfile signature verification bypass
+	* rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
+	(initializeResources): Prompt user only if there is a single certificate
+	that signs all jars in the jnlp file, otherwise treat as unsigned.
+	* rt/net/sourceforge/jnlp/security/CertVerifier.java: Rename getCerts to
+	getCertPath and make it return a CertPath.
+	* rt/net/sourceforge/jnlp/security/CertsInfoPane.java: Rename certs
+	variable to certPath and change its type to CertPath.
+	(buildTree): Use new certPath variable.
+	(populateTable): Same.
+	* rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java: Rename getCerts
+	to getCertPath and make it return a CertPath.
+	* rt/net/sourceforge/jnlp/tools/JarSigner.java: Change type for certs
+	variable to be a hashmap that stores certs and the number of entries they
+	have signed.
+	(totalSignableEntries): New variable to track how many signable entries
+	have been encountered.
+	(getCerts): Updated method to return certs from new hashmap.
+	(isFullySignedByASingleCert): New method. Returns if there is a single
+	cert that signs all the entries in the jars specified in the jnlp file.
+	(verifyJars): Move verifiedJars and unverifiedJars out of the for loop so
+	that the data is not lost when the next jar is processed. After verifying
+	each jar, see if there is a single signer, and prompt the user if there is
+	such an untrusted signer.
+	(verifyJar): Increment totalSignableEntries for each signable entry
+	encountered and the count for each cert when it signs an entry. Move
+	checkTrustedCerts() out of the function into verifyJars(). 
+
 2011-01-28  Deepak Bhole <dbhole@redhat.com>
 
 	* Makefile.am
--- a/NEWS	Fri Jan 28 15:51:03 2011 -0500
+++ b/NEWS	Tue Feb 01 10:53:44 2011 -0500
@@ -14,6 +14,7 @@
 * Security updates
   - RH645843, CVE-2010-3860: IcedTea System property information leak via public static
   - RH663680, CVE-2010-4351: IcedTea JNLP SecurityManager bypass
+  - RH672262, CVE-2011-0025: IcedTea jarfile signature verification bypass
 * New Features
   - IcedTea-Web now uses a deployment.properties file to specify configuration
   - System-level as well as user-level deployment.properties files with locked configuration are supported
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Fri Jan 28 15:51:03 2011 -0500
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Tue Feb 01 10:53:44 2011 -0500
@@ -430,7 +430,7 @@
             }
 
             //Case when at least one jar has some signing
-            if (js.anyJarsSigned()) {
+            if (js.anyJarsSigned() && js.isFullySignedByASingleCert()) {
                 signing = true;
 
                 if (!js.allJarsSigned() &&
--- a/netx/net/sourceforge/jnlp/security/CertVerifier.java	Fri Jan 28 15:51:03 2011 -0500
+++ b/netx/net/sourceforge/jnlp/security/CertVerifier.java	Tue Feb 01 10:53:44 2011 -0500
@@ -76,7 +76,7 @@
      * Return a valid certificate path to this certificate(s) being verified
      * @return The CertPath
      */
-    public ArrayList<CertPath> getCerts();
+    public CertPath getCertPath();
 
     /**
      * Returns the application's publisher's certificate.
@@ -89,4 +89,5 @@
      * the event that the application is self signed.
      */
     public abstract Certificate getRoot();
+
 }
--- a/netx/net/sourceforge/jnlp/security/CertsInfoPane.java	Fri Jan 28 15:51:03 2011 -0500
+++ b/netx/net/sourceforge/jnlp/security/CertsInfoPane.java	Tue Feb 01 10:53:44 2011 -0500
@@ -67,7 +67,7 @@
  */
 public class CertsInfoPane extends SecurityDialogPanel {
 
-    private ArrayList<CertPath> certs;
+    private CertPath certPath;
     private JList list;
     protected JTree tree;
     private JTable table;
@@ -87,12 +87,9 @@
      * Builds the JTree out of CertPaths.
      */
     void buildTree() {
-        certs = parent.getJarSigner().getCerts();
-        //for now, we're only going to display the first signer, even though
-        //jars can be signed by multiple people.
-        CertPath firstPath = certs.get(0);
+        certPath = parent.getJarSigner().getCertPath();
         X509Certificate firstCert =
-                        ((X509Certificate) firstPath.getCertificates().get(0));
+                        ((X509Certificate) certPath.getCertificates().get(0));
         String subjectString =
                         SecurityUtil.getCN(firstCert.getSubjectX500Principal().getName());
         String issuerString =
@@ -104,9 +101,9 @@
 
         //not self signed
         if (!firstCert.getSubjectDN().equals(firstCert.getIssuerDN())
-                        && (firstPath.getCertificates().size() > 1)) {
+                        && (certPath.getCertificates().size() > 1)) {
             X509Certificate secondCert =
-                                ((X509Certificate) firstPath.getCertificates().get(1));
+                                ((X509Certificate) certPath.getCertificates().get(1));
             subjectString =
                                 SecurityUtil.getCN(secondCert.getSubjectX500Principal().getName());
             issuerString =
@@ -125,12 +122,12 @@
      * Fills in certsNames, certsData with data from the certificates.
      */
     protected void populateTable() {
-        certNames = new String[certs.get(0).getCertificates().size()];
+        certNames = new String[certPath.getCertificates().size()];
         certsData = new ArrayList<String[][]>();
 
-        for (int i = 0; i < certs.get(0).getCertificates().size(); i++) {
+        for (int i = 0; i < certPath.getCertificates().size(); i++) {
 
-            X509Certificate c = (X509Certificate) certs.get(0).getCertificates().get(i);
+            X509Certificate c = (X509Certificate) certPath.getCertificates().get(i);
             certsData.add(parseCert(c));
             certNames[i] = SecurityUtil.getCN(c.getSubjectX500Principal().getName())
                                 + " (" + SecurityUtil.getCN(c.getIssuerX500Principal().getName()) + ")";
--- a/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Fri Jan 28 15:51:03 2011 -0500
+++ b/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Tue Feb 01 10:53:44 2011 -0500
@@ -83,7 +83,7 @@
         return isTrusted;
     }
 
-    public ArrayList<CertPath> getCerts() {
+    public CertPath getCertPath() {
 
         ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
         for (int i = 0; i < chain.length; i++)
@@ -99,7 +99,7 @@
             // carry on
         }
 
-        return certPaths;
+        return certPaths.get(0);
     }
 
     public ArrayList<String> getDetails() {
--- a/netx/net/sourceforge/jnlp/tools/JarSigner.java	Fri Jan 28 15:51:03 2011 -0500
+++ b/netx/net/sourceforge/jnlp/tools/JarSigner.java	Tue Feb 01 10:53:44 2011 -0500
@@ -138,11 +138,13 @@
     private ArrayList<String> unverifiedJars = null;
 
     /** the certificates used for jar verification */
-    private ArrayList<CertPath> certs = null;
+    private HashMap<CertPath, Integer> certs = new HashMap<CertPath, Integer>();
 
     /** details of this signing */
     private ArrayList<String> details = new ArrayList<String>();
 
+    private int totalSignableEntries = 0;
+
     /* (non-Javadoc)
      * @see net.sourceforge.jnlp.tools.CertVerifier2#getAlreadyTrustPublisher()
      */
@@ -191,18 +193,41 @@
      * @see net.sourceforge.jnlp.tools.CertVerifier2#getCerts()
      */
     public ArrayList<CertPath> getCerts() {
-        return certs;
+        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
+     */
+    public boolean isFullySignedByASingleCert() {
+
+        for (CertPath cPath : certs.keySet()) {
+            // If this cert has signed everything, return true
+            if (certs.get(cPath) == totalSignableEntries)
+                return true;
+        }
+
+        // No cert found that signed all entries. Return false.
+        return false;
     }
 
     public void verifyJars(List<JARDesc> jars, ResourceTracker tracker)
             throws Exception {
 
-        certs = new ArrayList<CertPath>();
+        verifiedJars = new ArrayList<String>();
+        unverifiedJars = new ArrayList<String>();
+
         for (int i = 0; i < jars.size(); i++) {
 
             JARDesc jar = jars.get(i);
-            verifiedJars = new ArrayList<String>();
-            unverifiedJars = new ArrayList<String>();
 
             try {
 
@@ -231,6 +256,22 @@
                 throw e;
             }
         }
+
+        //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;
+        }
+
     }
 
     public verifyResult verifyJar(String jarName) throws Exception {
@@ -238,10 +279,6 @@
         boolean hasUnsignedEntry = false;
         JarFile jarFile = null;
 
-        // certs could be uninitialized if one calls this method directly
-        if (certs == null)
-            certs = new ArrayList<CertPath>();
-
         try {
             jarFile = new JarFile(jarName, true);
             Vector<JarEntry> entriesVec = new Vector<JarEntry>();
@@ -280,21 +317,22 @@
                     CodeSigner[] signers = je.getCodeSigners();
                     boolean isSigned = (signers != null);
                     anySigned |= isSigned;
-                    hasUnsignedEntry |= !je.isDirectory() && !isSigned
-                                        && !signatureRelated(name);
+
+                    boolean shouldHaveSignature = !je.isDirectory()
+                                                && !signatureRelated(name);
+
+                    hasUnsignedEntry |= shouldHaveSignature &&  !isSigned;
+
+                    if (shouldHaveSignature)
+                        totalSignableEntries++;
+
                     if (isSigned) {
-                        // TODO: Perhaps we should check here that
-                        // signers.length is only of size 1, and throw an
-                        // exception if it's not?
                         for (int i = 0; i < signers.length; i++) {
                             CertPath certPath = signers[i].getSignerCertPath();
-                            if (!certs.contains(certPath))
-                                certs.add(certPath);
-
-                            //we really only want the first certPath
-                            if (!certPath.equals(this.certPath)) {
-                                this.certPath = certPath;
-                            }
+                            if (!certs.containsKey(certPath))
+                                certs.put(certPath, 1);
+                            else
+                                certs.put(certPath, certs.get(certPath) + 1);
 
                             Certificate cert = signers[i].getSignerCertPath()
                                     .getCertificates().get(0);
@@ -314,7 +352,12 @@
                         }
                     }
                 } //while e has more elements
-            } //if man not null
+            } 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) {
@@ -355,9 +398,6 @@
             }
         }
 
-        // check if the certs added above are in the trusted path
-        checkTrustedCerts();
-
         //anySigned does not guarantee that all files were signed.
         return (anySigned && !(hasUnsignedEntry || hasExpiredCert
                               || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || notYetValidCert)) ? verifyResult.SIGNED_OK : verifyResult.SIGNED_NOT_OK;