7

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);
            }
    }
}

Community
  • 1
  • 1
Amasuriel
  • 2,212
  • 3
  • 18
  • 25
  • How did you tamper with the JAR under test? – trashgod Apr 07 '11 at 21:49
  • I opened it up in 7zip. I added a new package directory with some class files in it and modified some of the existing class files with recompiled versions. – Amasuriel Apr 07 '11 at 21:52
  • If `jarsigner` rejects the altered JAR, but your code accepts it, an [sscce](http://sscce.org/) may help spot the problem. – trashgod Apr 07 '11 at 21:56

2 Answers2

9

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.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Hmm. Your sample is almost identical to mine yet you get security exceptions and I don't. What exact JDK are you using? – Amasuriel Apr 08 '11 at 16:22
  • Version is irrelevant. `SecurityException` is thrown only if an entry is incorrectly signed. You can also trigger it by tampering with `META-INF/.SF`. – trashgod Apr 08 '11 at 20:46
  • I'm going to mark this as the answer since clearly it works for most people. I'm going to try it with a simpler example when I get a chance...If I figure out why it doesn't work for my but not how to fix it I guess I will open a new question. – Amasuriel Apr 11 '11 at 18:12
  • No problem. Feel free ping me if you update this question or reference it in a new one. Also consider running `jarsigner -verify` via `exec()` or similar. – trashgod Apr 11 '11 at 18:28
  • This doesn't seem to work. I took an unsigned Jar and there were no exceptions about unsigned entries. – Nathan Jun 08 '16 at 01:08
  • @Nathan: Right; it only returns `false` for JARs that have been modified _after_ being signed. – trashgod Jun 08 '16 at 02:17
  • Based [on the grepcode source](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/security/tools/JarSigner.java#JarSigner.verifyJar%28java.lang.String%29), It looks like you need to actively **read** the content of the jarfile entry to get the security exception to trigger - simply opening the entry isn't enough to trigger it. – Anya Shenanigans Sep 05 '16 at 07:55
  • 1
    @Petesh: A similar notation is seen [here](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/security/tools/jarsigner/Main.java#l570) in jdk8. – trashgod Sep 05 '16 at 08:26
  • Whats the use of this? You can self sign anything. You obviously have to check if the signed jar file was signed with a RSA private key, so how to do that? – M. H. May 21 '17 at 05:50
  • Try `jarsigner -verify -verbose …`; may need `-keystore`, too. – trashgod May 21 '17 at 10:56
  • 1
    Worth noting that, without a policy manager insisting that all JARs are signed, this method will return true if the JAR isn't signed at all ;-) – earcam Jul 01 '18 at 22:44
1

I figured out why this was happening to me...it was a stupid mistake.

I had my tampered signed jar, but I also had all the same classes compiled since this was my dev env. So the classloader picked up the compiled classes over the jar classes. There is no manifest for the compiled classes, so no security errors were generated.

Once I deleted my compiled classes I got the expected security exceptions.

Amasuriel
  • 2,212
  • 3
  • 18
  • 25