Code should be signed only if it requires elevated privileges to perform one or more tasks (see ENV00-J. Do not sign code that performs only unprivileged operations for more information). For example, applets are denied the privilege of making HTTP connections to any hosts except the host from which they came. When an applet requires an HTTP connection with an external host to download plug-ins or extensions, its vendor may provide signed code rather than force the user to arbitrarily assign the permissions it requires. Because executing privilege-elevated 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 to package files for 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. Signing certifies the authenticity of the code, but it cannot guarantee the security of the code.

According to the Java Tutorials [Java Tutorials],

If you are creating applet code that you will sign, it needs to be placed in a JAR file. The same is true if you are creating application code that may be similarly restricted by running it with a security manager. The reason you need the JAR file is that when a policy file specifies that code signed by a particular entity is permitted one or more operations, such as specific file reads or writes, the code is expected to come from a signed JAR file. (The term "signed code" is an abbreviated way of saying "code in a class file that appears in a JAR file that was signed.")

Client code may lack programmatic checks of code signatures. For example, instances of URLClassLoader and its subclasses and java.util.jar automatically verify signatures of signed JAR files. Developer-implemented custom class loaders 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 the public key contained within the JAR without validating the public key. 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

This noncompliant code example demonstrates the JarRunner application, which can be used to dynamically execute a particular class residing within a JAR file (abridged version of the class in The Java Tutorials [Java Tutorials]). It creates a JarClassLoader that loads an application update, plug-in, or patch over an untrusted network such as the Internet. The URL to fetch the code is specified as the first argument (for example, http://www.securecoding.cert.org/software-updates.jar); any other arguments specify the arguments that are to be passed to the class that is loaded. JarRunner uses reflection to invoke the main() method of the loaded class. Unfortunately, by default, JarClassLoader verifies the signature using the public key contained within the JAR file.

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 solution may be adequate 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.

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 belong 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:

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) {
    // Return, do not execute if unsigned
    System.out.println("No signature!");
    return;  
  }  

  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(new FileInputStream(System.getProperty(
      "user.home"+ File.separator + "keystore.jks")),
      "loadkeystorepassword".toCharArray());
  // User is the alias
  Certificate pubCert = ks.getCertificate("user");  
  // Check with the trusted public key, else throws exception
  certs[0].verify(pubCert.getPublicKey()); 
}

Because the invokeClass() method now has two additional exceptions in its throws clause, the catch block in the main() method must be altered accordingly.

The URLClassLoader and all its subclasses are given by default only enough permissions to interact with the URL that was specified when the URLClassLoader object was created, which means that the loaded code can interact only with the specified host. It fails to mitigate the risk completely, however, because the loaded code may have been granted privileges that permit other sensitive operations such as updating an existing local JAR file.

Risk Assessment

Failure to verify a digital signature, whether manually or programmatically, can result in the execution of malicious code.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SEC06-J

High

Probable

Medium

P12

L1

Automated Detection

Automated detection is not feasible in the fully general case. However, an approach similar to Design Fragments [Fairbanks 2007] could assist both programmers and static analysis tools.

ToolVersionCheckerDescription
CodeSonar
8.1p0

JAVA.IO.INJ.ANDROID.MESSAGE
JAVA.IO.TAINT.MESSAGE

Android Message Injection (Java)
Tainted Message (Java)

Related Guidelines

ISO/IEC TR 24772:2010

Improperly Verified Signature [XZR]

MITRE CWE

CWE-300, Channel Accessible by Non-endpoint (aka "Man-in-the-Middle")
CWE-319, Cleartext Transmission of Sensitive Information
CWE-347, Improper Verification of Cryptographic Signature
CWE-494, Download of Code without Integrity Check

Bibliography



10 Comments

  1. I would like comments on the bold statement in the second noncompliant code example. This behavior resulted while I was experimenting and I couldn't find any clear documentation describing it.

    Another thing to consider is, if a jar file is signed by multiple signers, the compliant solution is checking only the first certificate certs[0]. Certificate.verify does not return anything so how to test whether our certificate exists in the chain, in a loop...

    Update: Another idea would be to move up the signature validation instead of waiting until loadclass has loaded the remote class. The reasoning is that loadclass could load an exploit (which has obviously not been signed by the trusted party) before the signature verification is performed. Slightly convoluted but it might be possible.

  2. I strongly suggest not using jarsigner for this. By signing a jar in the standard format you are effectively saying this code is perfectly safe to be used as an applet, JNLP application, etc. Principle of Least Privilege suggests this is a bad idea.

    If you want to verify the originator, use SSL/HTTPS or better sign the whole jar file as one thing after prepending a magic number that identifies the purpose (and, given the ZIP file format, probably appending something as well, and probably require decryption with a public key).

    1. I agree that SSL/TLS is a good choice here and I will suggest that as a CS. One disadvantage might be that it won't support non-repudiation. (I think it is important since we are dealing with updates/patches)

      Signing the whole jar file as a file can also be done though I don't know how widely it is used. I wonder why [Eclipse 08] JAR Signing and Signed bundles and protecting against malicious code talk about signing bundles with jarsigner and also advocate verifying them at runtime.

      This recommendation suggests performing sensitive operations like installing updates, patches etc. as it is, so might not violate the principle of LP, irrespective of how it is called. Moreover, it will depend on the user (or admin) giving it the required permissions in the policy file and/or clicking ok on the dialog. If for some reason the user should not be running it as an applet, we could define a main() in the class. We could also selectively sign files in the jar.

      In summary, if the user trusts the owner of the certificate, shouldn't the decision to execute the code be left to him/her?

      I am exploring this further; meantime, do feel free to comment.
      Thanks.

      1. You are making the assumption that because code was not written with malicious intent that it cannot be used maliciously. This is incorrect. Indeed, the point of most of these guidelines is that code can be abused.

        1. That is an interesting point though I am unaware if it has been observed in the wild. In that case, it follows that you cannot obtain a signed copy anyway, be it through SSL. I was relying on the user to make the best decision but I understand that it is not the best assumption from the security pov.

          I'll change the current CS to an NCCE and add an SSL example (or the jar magic number approach, you suggested). I suspect some software vendors will have to reconsider how they release code currently, if this is implemented.

          1. Here are some lines about code signing from the Android OS documentation (link):

            The Android system requires that all installed applications be digitally signed with a certificate whose private key is held by the application's developer. The system uses the certificate as a means of identifying the author of an application and establishing trust relationships between applications, rather than for controlling which applications the user can install. The certificate does not need to be signed by a certificate authority: it is perfectly allowable, and typical, for Android applications to use self-signed certificates.

            You can use standard tools — Keytool and Jarsigner — to generate keys and sign your application .apk files.

            I wonder if this can be counterproductive too.

            1. Dunno low-level details, but I suspect this is a secure solution when used by someone who understands certificates. (I'm mainly trusting Google has done their homework).

              I suspect counterproductive uses will mainly arise from tricking people who don't understand certificates into accepting a 'bad' certificate. I really wish there was a 'Certificate 101' class or tutorial for people who wonder why they are getting 'certificate changed' dialogs from some webpage they visit. I've seen several stories of webservers who change their certs by mistake or on purpose and instruct people to ignore the warning dialogs. Which doesn't help the credibility of certificates.

              1. It's about users accepting bad certificates usually. Here it seems, this is not a concern as Android does not use it to control what applications can be installed but rather for mutual interaction between applications.

                If we sign artifacts in general, we might end up signing something that should be better left unsigned (like applets). Android applications use a native .dex format for applications which are then signed. Since this filename/extension cannot be changed without the signature verification failing, it does seem it is not directly possible to "use" signed applications that can be subsequently run as applets. Moreover, its other applications (such as the default web browser) also do not allow opening these files (and Sun Java applets are non-executable on G1 phones, I suspect). The problem might occur if someone packages and signs a combination consisting of a dex file and an applet or JNLP applications for some reason (seems pretty far fetched).

                In summary, the principle of least privilege is not being violated in this design because there is nothing that requires "lower privileges" (to the best of my knowledge).

                1. I somehow want to capture the fact that the default verification mechanism of URLClassLoader and java.util.jar does not provide authenticity. (allegedly uses the public certificate from the signed jar for verification)

                  That way you need only show how to verify code that was signed for a legit reason and not expect people to "sign all artifacts". (This does not apply to designs such as Android, where there is no concept of a CA and hence no way to authenticate)

  3. There is no entry for Bea 2008 in the site bibliography. I didn't see it on the C or C++ sites either.