Code is usually signed when it requires more than the default set of permissions to perform some tasks. Although it is often a bad idea to sign code (_c.f._ \[[ENV00-J. Do not sign code that performs only unprivileged operations]\]), some actions necessitate this step. For example, if an application requires an {{ Wiki Markup http
}} connection with an external host to download plugins or extensions, its vendor may provide signed code rather than requiring that the user deal with complex security policies. Because executing signed code can be extremely dangerous, verifying the authenticity of its origin is of utmost importance.
Java based technologies typically use the Java Archive (JAR) feature for packaging files to facilitate platform independent deployment. JAR files are the preferred means of distribution for Enterprise Java Beans (EJB), MIDlets (J2ME) and Weblogic Server J2EE applications, for example. The point and click installation provided by Java Web Start also relies on the JAR file format for packaging. Vendors sign their JAR files when required. This certifies the authenticity of the code; it cannot guarantee the safety of the code.
...
Client code may lack programatic checks of code signatures. For example, any instances of URLClassLoader
and its subclasses and java.util.jar
automatically verify signatures of signed JAR files. Developer-implemented custom classloaders that subclass java.lang.ClassLoader
may lack this check. Moreover, even in the URLClassLoader
case, the automatic verification performs only an integrity check; it fails to authenticate the loaded class because the check uses a public key contained within the JAR. The legitimate JAR file may be replaced with a malicious JAR file containing a different public key along with appropriately modified digest values.
The default automatic signature verification process may still be used, but is not sufficient. Systems that use the default automatic signature verification process must perform additional checks to ensure that the signature is correct (such as comparing it against a known trusted signature).
Noncompliant Code Example
...
Code Block | ||
---|---|---|
| ||
public class JarRunner {
public static void main(String[] args) throws IOException,
ClassNotFoundException,NoSuchMethodException, InvocationTargetException {
URL url = new URL(args[0]);
// Create the class loader for the application jar file
JarClassLoader cl = new JarClassLoader(url);
// Get the application's main class name
String name = cl.getMainClassName();
// Get arguments for the application
String[] newArgs = new String[args.length - 1];
System.arraycopy(args, 1, newArgs, 0, newArgs.length);
// Invoke application's main class
cl.invokeClass(name, newArgs);
}
}
final class JarClassLoader extends URLClassLoader {
private URL url;
public JarClassLoader(URL url) {
super(new URL[] { url });
this.url = url;
}
public String getMainClassName() throws IOException {
URL u = new URL("jar", "", url + "!/");
JarURLConnection uc = (JarURLConnection) u.openConnection();
Attributes attr = uc.getMainAttributes();
return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null;
}
public void invokeClass(String name, String[] args)
throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException {
Class c = loadClass(name);
Method m = c.getMethod("main", new Class[] { args.getClass() });
m.setAccessible(true);
int mods = m.getModifiers();
if (m.getReturnType() != void.class || !Modifier.isStatic(mods)
|| !Modifier.isPublic(mods)) { throw new NoSuchMethodException("main"); }
try {
m.invoke(null, new Object[] { args });
} catch (IllegalAccessException e) { System.out.println("Access denied"); }
}
}
|
Compliant Solution (jarsigner
)
Users can — but usually do not — explicitly check JAR file signatures at the command line; this may be an adequate solution for programs that require manual installation of JAR files. Any malicious tampering results in a SecurityException
when the jarsigner
tool is invoked with the -verify
option.
Code Block | ||
---|---|---|
| ||
jarsigner -verify signed-updates-jar-file.jar |
Compliant Solution (certificate chain)
When the local system cannot reliably verify the signature, the invoking program must verify the signature programmatically by obtaining the chain of certificates from the CodeSource
of the class being loaded and checking whether any of the certificates belongs to a trusted signer whose certificate has been securely obtained beforehand and stored in a local keystore. This compliant solution demonstrates the necessary modifications to the invokeClass
method.
Code Block | ||
---|---|---|
| ||
public void invokeClass(String name, String[] args)
throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, GeneralSecurityException, IOException {
Class c = loadClass(name);
Certificate[] certs = c.getProtectionDomain().getCodeSource().getCertificates();
if (certs == null) {
System.out.println("No signature!"); return; // return, do not execute if unsigned
}
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(System.getProperty("user.home"+ File.separator + "keystore.jks")),
"loadkeystorepassword".toCharArray());
Certificate pubCert = ks.getCertificate("user"); // user is the alias
certs[0].verify(pubCert.getPublicKey()); // check with the trusted public key, else throws exception
}
|
...