TrustDecider for Verifying JAR Files
One of the most promising tools I've come across is the TrustDecider class. It is available to all Applets in JRE 1.3+ (plugin.jar), and to all Java Web Start applications in JRE 1.5+ (deploy.jar). In my case, I have a digitally-signed Java Applet that downloads digitally-signed content which must be verified programmatically at runtime. TrustDecider provides a method called isAllPermissionGranted that returns a boolean value indicating whether certificates associated with a CodeSource (JAR file) have AllPermissions privileges, which is to say that they are trusted.
In JRE 1.3, the TrustDecider class has the full-name sun.plugin.security.TrustDecider; however, in JRE 1.5, the full-name is com.sun.deploy.security.TrustDecider. Locating the appropriate implementation of the class can be accomplished with Java's Reflection capabilities.
To verify the integrity of an archive, you must enable verification when constructing the JAR instance, and then actually read each entry in the JAR to trigger verification of the entry's digital signature(s). The last part is not so obvious to people, since it seems like just constructing the JAR instance with verification enabled should be enough.
To verify the authenticity of each archive entry, you can invoke the isAllPermissionGranted method in TrustDecider. The user's Java keystore will be accessed by the TrustDecider class to determine if the certificate is already trusted. If not, the user will be presented with a dialog prompting them if they trust the certificate subject and signer. They will then have the option to approve the certificate for that invocation, or all future invocations. Problem solved!
As a word of warning, the TrustDecider class is part of the com.sun.* and sun.* packages and is not guaranteed to work in any version of Java. It should be used at your own risk.
Here's a method that verifies a JAR file using the TrustDecider class:
private boolean checkArchivePermissions(File file) { if (null == file || !file.exists() || !file.getName().endsWith(".jar")) { return false; } Class trustDeciderClass; try { trustDeciderClass = Class .forName("com.sun.deploy.security.TrustDecider"); } catch (ClassNotFoundException e) { System.out.println("Trying Plugin TrustDecider..."); try { trustDeciderClass = Class .forName("sun.plugin.security.TrustDecider"); } catch (ClassNotFoundException ee) { System.err.println("TrustDecider not found, cannot verify archive"); return false; } } Object trustDecider; Method verifyMethod; try { trustDecider = trustDeciderClass.newInstance(); verifyMethod = trustDeciderClass.getMethod( "isAllPermissionGranted", new Class[] { CodeSource.class }); } catch (Exception e) { e.printStackTrace(); return false; } JarFile jarFile = null; try { // attempt to load a Java class from the JAR jarFile = new JarFile(file, true); Enumeration fileEntriesEnum = jarFile.entries(); while (fileEntriesEnum.hasMoreElements()) { JarEntry je = (JarEntry) fileEntriesEnum.nextElement(); if (je.isDirectory()) { continue; } // Verify the integrity of the file against the digest InputStream is = null; try { is = jarFile.getInputStream(je); JarUtilities.getDataFromZipInputStream(is); } finally { if (null != is) { is.close(); } } // Verify that the certificates used to assert the integrity are trusted if (!je.getName().startsWith("META-INF")) { Certificate[] certs = je.getCertificates(); CodeSource cs = new CodeSource(file.toURI().toURL(), certs); if (!Boolean.TRUE.equals(verifyMethod.invoke(trustDecider, new Object[] { cs }))) { System.err.println("Not Trusted: " + jarFile.getName()); return false; } } } } catch (Exception e) { e.printStackTrace(); } finally { if (null != jarFile) { try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } } } return true; }