Verifying Jar Signature

Amasuriel picture Amasuriel · Apr 7, 2011 · Viewed 11.5k times · Source

I'm trying to programmatically verify that a jar file has not been obviously tampered with. I have 2 use cases I want to prevent. 1) Modifications of existing classes 2) additions of new classes in the jar

I signed the jar using jarsigner. When I verify either of the above cases with jarsigner it works like I would expect.

When I try to do it programmatically using the samples in How to verify a jar signed with jarsigner programmatically or How to verify signature on self signed jar? however, I don't get any SecurityExceptions...or any exceptions at all for that matter.

Not sure what I am doing wrong since those snippets seemed to work for other people. Any ideas? This is JDK 1.6 BTW.

Edit: As requested below, a sample of the code...supply your own modified jar :)

    JarFile myJar;

    try
    {
        //Insert the full path to the jar here      
        String libPath =  ""
        stature = new JarFile(libPath,true);

        //Don't really need this right now but was using it to inspect the SHA1 hashes

        InputStream is = myJar.getInputStream(myJar.getEntry("META-INF/MANIFEST.MF"));
        Manifest man = myJar.getManifest();            
        is.close();

        verifyJar(myJar);

    }
    catch (IOException ioe)
    {
        throw new Exception("Cannot load jar file", ioe);
    }


private void verifyJar(JarFile jar) throws Exception
{
    Enumeration<java.util.jar.JarEntry> entries = jar.entries();
    while (entries.hasMoreElements())
    {
        java.util.jar.JarEntry entry = entries.nextElement();

        try
        {
            jar.getInputStream(entry);

            //Also tried actually creating a variable from the stream in case it was discarding it before verification
            //InputStream is = jar.getInputStream(entry);
            //is.close();
        }
            catch (SecurityException se)
            {
                /* Incorrect signature */                    
                throw new Exception("Signature verification failed", se);
            }
            catch (IOException ioe)
            {
                throw new Exception("Cannot load jar file entry", ioe);
            }
    }
}

Answer

trashgod picture trashgod · Apr 8, 2011

Using the example below, I obtained the expected result for a correctly signed JAR (true) and an altered JAR (false). One simple way to trigger the effect for testing is to change one of the digests listed in META-INF/MANIFEST.MF.

Note that this approach ignores entries that are not listed in the manifest. Using jarsigner -verify reports, "This jar contains unsigned entries which have not been integrity-checked." After reading the stream completely, entry.getCodeSigners() may be used to determine if an entry has any signers.

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/** @see http://stackoverflow.com/questions/5587656 */
public class Verify {

    public static void main(String[] args) throws IOException {
        System.out.println(verify(new JarFile(args[0])));
    }

    private static boolean verify(JarFile jar) throws IOException {
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            try {
                byte[] buffer = new byte[8192];
                InputStream is = jar.getInputStream(entry);
                while ((is.read(buffer, 0, buffer.length)) != -1) {
                    // We just read. This will throw a SecurityException
                    // if a signature/digest check fails.
                }
            } catch (SecurityException se) {
                return false;
            }
        }
        return true;
    }
}

Note: For JDK 8, its not enough to merely get the input stream. As in jarsigner, the stream must be read from, too. In the code above, a loop adapted from the jar signer source has been added after getting the input stream.