Just An Application

July 27, 2013

The Great Android Security Hole Of ’08 ? – Part Eight: Signed JAR Verification Revisited

1.0 Basic Verification Using The java.util.jar.JarFile API

1.1 JARVerifierV1

A class that does basic verification of a signed JAR using the java.util.jar.JarFile API.

    final class JARVerifierV1 
                implements
                    JARVerifier
    {
        public boolean verify(File theFile)
        {
            Log.d(TAG, "verify: verifying " + theFile);
            try
            {
                JarFile jf = new JarFile(theFile, true);
			
                try
                {
                    verify(jf);
                    Log.d(TAG, "verify: " + theFile + " verified");
                    return true;
                }
                finally
                {
                    jf.close();
                }
            }
            catch (Exception e)
            {
                Log.d(TAG, "verify: verification failed");
                e.printStackTrace();
                return false;
            }
        }
	
        //
	
        JARVerifierV1()
        {
            buffer = new byte[8192];
        }
	
        //

        private void verify(JarFile theJARFile) 
                     throws
                         Exception
        {
            Enumeration<JarEntry> entries = theJARFile.entries();
		
            while (entries.hasMoreElements())
            {
                verify(theJARFile, entries.nextElement());
            }
		
        }

        private void verify(JarFile theJARFile, JarEntry theEntry) 
                     throws
                         Exception
        {
            Log.d(TAG, "verify: " + theEntry.getName());
		
            InputStream is = theJARFile.getInputStream(theEntry);
		
            while (is.read(buffer) != -1)
            {
			
            }
        }
	
        //
	
        private byte[] buffer;
	
        //
	
        private static final String TAG = "JARVerifierV1";
    }

1.1 Verification Of A Vanilla APK

If we create a vanilla APK which looks like this

    Archive:  GoodApp.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
         1588  07-10-13 21:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1034  07-10-13 21:11   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
     --------                   -------
       389395                   15 files

and we attempt to verify it using JARVerifierV1 we succeed.

    ...

    D/JARVerifierV1(  281): verify: verifying /data/data/xper.jv/files/GoodApp.apk
    D/JARVerifierV1(  281): verify: res/layout/activity_main.xml
    D/JARVerifierV1(  281): verify: res/menu/activity_main.xml
    D/JARVerifierV1(  281): verify: AndroidManifest.xml
    D/JARVerifierV1(  281): verify: resources.arsc
    D/JARVerifierV1(  281): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV1(  281): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV1(  281): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV1(  281): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV1(  281): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV1(  281): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV1(  281): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV1(  281): verify: classes.dex
    D/JARVerifierV1(  281): verify: META-INF/MANIFEST.MF
    D/JARVerifierV1(  281): verify: META-INF/CERT.SF
    D/JARVerifierV1(  281): verify: META-INF/CERT.RSA
    D/JARVerifierV1(  281): verify: /data/data/xper.jv/files/GoodApp.apk verified

    ...

This is what we would expect but it is not much of a test.

We need to know what happens when we modify a signed JAR in some way.

1.2 Verification After File Modification

If we modify our vanilla APK by replacing the original version of AndroidManifest.xml with an empty one

    Archive:  GoodApp_modified.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
            0  07-21-13 11:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1034  07-10-13 21:11   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
     --------                   -------
       387807                   15 files

and then attempt to verify it we get a SecurityException which is what we would expect.

    ...

    D/JARVerifierV1(  280): verify: verifying /data/data/xper.jv/files/GoodApp_modified.apk
    D/JARVerifierV1(  280): verify: res/layout/activity_main.xml
    D/JARVerifierV1(  280): verify: res/menu/activity_main.xml
    D/JARVerifierV1(  280): verify: AndroidManifest.xml
    D/JARVerifierV1(  280): verify: verification failed
    W/System.err(  280): java.lang.SecurityException: META-INF/MANIFEST.MF has \
         invalid digest for AndroidManifest.xml in /data/data/xper.jv/files/GoodApp_modified.apk
    W/System.err(  280): 	at java.util.jar.JarVerifier$VerifierEntry.verify(JarVerifier.java:129)
    W/System.err(  280): 	at java.util.jar.JarFile$JarFileInputStream.read(JarFile.java:127)
    W/System.err(  280): 	at java.io.FilterInputStream.read(FilterInputStream.java:130)
    W/System.err(  280): 	at xper.jv.JARVerifierV1.verify(JARVerifierV1.java:71)
    W/System.err(  280): 	at xper.jv.JARVerifierV1.verify(JARVerifierV1.java:58)
    W/System.err(  280): 	at xper.jv.JARVerifierV1.verify(JARVerifierV1.java:24)
    W/System.err(  280): 	at xper.jv.XperJVActivity.verify(XperJVActivity.java:127)
    W/System.err(  280): 	at xper.jv.XperJVActivity.access$0(XperJVActivity.java:117)
    W/System.err(  280): 	at xper.jv.XperJVActivity$1.run(XperJVActivity.java:111)
    W/System.err(  280): 	at java.lang.Thread.run(Thread.java:1096)

    ...

1.3 Verification After File Addition

If we add the file assets/new_asset to our vanilla APK

    Archive:  GoodApp_added.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
         1588  07-10-13 21:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1034  07-10-13 21:11   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
           48  07-24-13 21:56   assets/new_asset
     --------                   -------
       389443                   16 files

and then we attempt to verify it we succeed which is a bit disconcerting.

    ...

    D/JARVerifierV1(  285): verify: verifying /data/data/xper.jv/files/GoodApp_added.apk
    D/JARVerifierV1(  285): verify: res/layout/activity_main.xml
    D/JARVerifierV1(  285): verify: res/menu/activity_main.xml
    D/JARVerifierV1(  285): verify: AndroidManifest.xml
    D/JARVerifierV1(  285): verify: resources.arsc
    D/JARVerifierV1(  285): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV1(  285): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV1(  285): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV1(  285): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV1(  285): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV1(  285): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV1(  285): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV1(  285): verify: classes.dex
    D/JARVerifierV1(  285): verify: META-INF/MANIFEST.MF
    D/JARVerifierV1(  285): verify: META-INF/CERT.SF
    D/JARVerifierV1(  285): verify: META-INF/CERT.RSA
    D/JARVerifierV1(  285): verify: assets/new_asset
    D/JARVerifierV1(  285): verify: /data/data/xper.jv/files/GoodApp_added.apk verified

    ...

In fact the JarFile implementation does not distinguish between a file whose computed digest matches the digest specified in the manifest, and a file which does not have a digest specified in the manifest.

This is a feature just not necessarily a good one.

1.4 Verification After File Removal

If we were to remove a file from our vanilla APK then we know a priori that verification would still succeed because all we are doing is iterating over the files that are in the JAR and verifying them.

To determine whether files had been removed from the JAR would require additional code to look for digests and for the digests of digests.

2.0 Fun With Digests

We can modify the code so that it checks for digest entries in the manifest.

2.1 JarVerifierV2

    final class JARVerifierV2 
                implements
                    JARVerifier
    {
        public boolean verify(File theFile)
        {
            Log.d(TAG, "verify: verifying " + theFile);
            try
            {
                JarFile jf = new JarFile(theFile, true);
			
                try
                {
                    verify(jf);
                    Log.d(TAG, "verify: " + theFile + " verified");
                    return true;
                }
                finally
                {
                    jf.close();
                }
            }
            catch (Exception e)
            {
                Log.d(TAG, "verify: verification failed");
                e.printStackTrace();
                return false;
            }
        }
	
        //
	
        JARVerifierV2()
        {
            buffer = new byte[8192];
        }
	
        //

        private void verify(JarFile theJARFile) 
                     throws
                         Exception
        {
            Enumeration<JarEntry> entries = theJARFile.entries();
		
            while (entries.hasMoreElements())
            {
                verify(theJARFile, entries.nextElement());
            }
		
        }

        private void verify(JarFile theJARFile, JarEntry theEntry) 
                     throws
                         Exception
        {
            Log.d(TAG, "verify: " + theEntry.getName());
		
            ensureDigest(theEntry);
		
            InputStream is = theJARFile.getInputStream(theEntry);
		
            while (is.read(buffer) != -1)
            {
			
            }
        }
	
        private void ensureDigest(JarEntry theEntry)
                     throws
                         Exception
        {
            if (!theEntry.getName().startsWith(META_INF))
            {
                Log.d(TAG, "ensureDigest: " + theEntry.getName());
		
                Attributes attributes = theEntry.getAttributes();
		
                if (attributes != null)
                {
                    for (Object o : attributes.keySet())
                    {
                        if (o.toString().endsWith(DIGEST_SUFFIX))
                        {
                            return;
                        }
                    }
                }
                throw new SecurityException("No digest attribute: " + theEntry.getName());
            }
        }
	
        //
	
        private byte[] buffer;
	
        //
	

        private static final String DIGEST_SUFFIX = "-Digest";
	
        private static final String META_INF      = "META-INF/";
	
        //
	
        private static final String TAG = "JARVerifierV2";
    }


2.2 Verification After File Addition

This time when we attempt we get a SecurityException, which is nice

    ...

    D/JARVerifierV2(  316): verify: verifying /data/data/xper.jv/files/GoodApp_added.apk
    D/JARVerifierV2(  316): verify: res/layout/activity_main.xml
    D/JARVerifierV2(  316): ensureDigest: res/layout/activity_main.xml
    D/JARVerifierV2(  316): verify: res/menu/activity_main.xml
    D/JARVerifierV2(  316): ensureDigest: res/menu/activity_main.xml
    D/JARVerifierV2(  316): verify: AndroidManifest.xml
    D/JARVerifierV2(  316): ensureDigest: AndroidManifest.xml
    D/JARVerifierV2(  316): verify: resources.arsc
    D/JARVerifierV2(  316): ensureDigest: resources.arsc
    D/JARVerifierV2(  316): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV2(  316): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV2(  316): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV2(  316): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV2(  316): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV2(  316): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV2(  316): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV2(  316): ensureDigest: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV2(  316): verify: classes.dex
    D/JARVerifierV2(  316): ensureDigest: classes.dex
    D/JARVerifierV2(  316): verify: META-INF/MANIFEST.MF
    D/JARVerifierV2(  316): verify: META-INF/CERT.SF
    D/JARVerifierV2(  316): verify: META-INF/CERT.RSA
    D/JARVerifierV2(  316): verify: assets/new_asset
    D/JARVerifierV2(  316): ensureDigest: assets/new_asset
    D/JARVerifierV2(  316): verify: verification failed
    W/System.err(  316): java.lang.SecurityException: No digest attribute: assets/new_asset
    W/System.err(  316): 	at xper.jv.JARVerifierV2.ensureDigest(JARVerifierV2.java:100)
    W/System.err(  316): 	at xper.jv.JARVerifierV2.verify(JARVerifierV2.java:70)
    W/System.err(  316): 	at xper.jv.JARVerifierV2.verify(JARVerifierV2.java:59)
    W/System.err(  316): 	at xper.jv.JARVerifierV2.verify(JARVerifierV2.java:25)
    W/System.err(  316): 	at xper.jv.XperJVActivity.verify(XperJVActivity.java:127)
    W/System.err(  316): 	at xper.jv.XperJVActivity.access$0(XperJVActivity.java:117)
    W/System.err(  316): 	at xper.jv.XperJVActivity$1.run(XperJVActivity.java:111)
    W/System.err(  316): 	at java.lang.Thread.run(Thread.java:1096)

    ...

2.3 Manipulating The Manifest

There is a slight problem with this approach, namely that it is possible to add entries to the manifest of a signed JAR.

We can add an entry for our new file to the manifest like so

    Manifest-Version: 1.0
    Created-By: 1.0 (Android)

    Name: res/drawable-xhdpi/ic_launcher.png
    SHA1-Digest: TJE1tg63y88xLdIALKGpuLmgh0s=

    Name: res/drawable-mdpi/ic_action_search.png
    SHA1-Digest: XjltJdEB3tvakTn9CN7KwdaNT68=

    Name: AndroidManifest.xml
    SHA1-Digest: iXwTXVvLX3gY+ByGhybEJvIxP08=

    Name: res/drawable-mdpi/ic_launcher.png
    SHA1-Digest: aM73uFWfPOOvq5Kk9Ffd3cWm0OQ=

    Name: res/drawable-hdpi/ic_launcher.png
    SHA1-Digest: ioH2V9g4FYKkqpDHk7jPryMKtcE=

    Name: res/layout/activity_main.xml
    SHA1-Digest: zd1eNopMfZ7CmMk8GjyPGPpYloc=

    Name: resources.arsc
    SHA1-Digest: 7nYq90PW0OZVg8ytyjBEcwQhYiA=

    Name: classes.dex
    SHA1-Digest: b80KFg5gdqlORweY5LrUS87tbVU=

    Name: res/drawable-hdpi/ic_action_search.png
    SHA1-Digest: rOWnNxWCWKwkicf+FpYGMg1fX2Y=

    Name: res/drawable-xhdpi/ic_action_search.png
    SHA1-Digest: WzSVDDIHZpn0cFeuxgKRJete4TU=

    Name: res/menu/activity_main.xml
    SHA1-Digest: TfxVPQP5IRVlJun7A7PdOqdkp+I=

    Name: res/drawable-ldpi/ic_launcher.png
    SHA1-Digest: r6Mdl54h2qEvVnqgfsgxU4CysiI=

    Name: assets/new_asset
    SHA1-Digest: PW9oQUbA9MQOVYfbtHutF0urHCg=

and then create a new APK containing the added file and the modified manifest

   Archive:  GoodApp_added_modified_manifest.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
         1588  07-10-13 21:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1103  07-26-13 14:51   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
           48  07-24-13 21:56   assets/new_asset
     --------                   -------
       389512                   16 files

If we attempt to verify it we succeed once more

    ...

    D/JARVerifierV2(  280): verify: verifying /data/data/xper.jv/files/GoodApp_added_modified_manifest.apk
    D/JARVerifierV2(  280): verify: res/layout/activity_main.xml
    D/JARVerifierV2(  280): ensureDigest: res/layout/activity_main.xml
    D/JARVerifierV2(  280): verify: res/menu/activity_main.xml
    D/JARVerifierV2(  280): ensureDigest: res/menu/activity_main.xml
    D/JARVerifierV2(  280): verify: AndroidManifest.xml
    D/JARVerifierV2(  280): ensureDigest: AndroidManifest.xml
    D/JARVerifierV2(  280): verify: resources.arsc
    D/JARVerifierV2(  280): ensureDigest: resources.arsc
    D/JARVerifierV2(  280): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV2(  280): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV2(  280): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV2(  280): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV2(  280): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV2(  280): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV2(  280): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV2(  280): ensureDigest: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV2(  280): verify: classes.dex
    D/JARVerifierV2(  280): ensureDigest: classes.dex
    D/JARVerifierV2(  280): verify: META-INF/MANIFEST.MF
    D/JARVerifierV2(  280): verify: META-INF/CERT.SF
    D/JARVerifierV2(  280): verify: META-INF/CERT.RSA
    D/JARVerifierV2(  280): verify: assets/new_asset
    D/JARVerifierV2(  280): ensureDigest: assets/new_asset
    D/JARVerifierV2(  280): verify: /data/data/xper.jv/files/GoodApp_added_modified_manifest.apk verified

    ...

Back to square one.

2.4 But What About The Digest Manifest ?

Given that the signature file contains a digest for the manifest and that has not been changed why is it possible to modify the manifest ?

The answer is because the digest manifest is considered to be an ‘optimization’ not a way of preventing the manifest from being changed.

If the computed digest of the manifest matches the one in the signature file then it is known to be unchanged and there is no need to check the individual digest entries in the manifest.

This is the ‘optimization’

If the computed digest of the manifest does not match then the individual digests in the signature file are used to check the corresponding digest entries in the manifest.

3.0 It Will Have To Be The Certificates Then

3.1 The JarEntry getCertificates Method

The Javadoc for the JerEntry getCertificates method for the implementation we are using says

Returns an array of Certificate Objects associated with this entry or null if none exists. Make sure that the everything is read from the input stream before calling this method, or else the method returns null.

The Javadoc for a.n.other implementation says

Returns the Certificate objects for this entry, or null if none. This method can only be called once the JarEntry has been completely verified by reading from the entry input stream until the end of the stream has been reached. Otherwise, this method will return null.

The returned certificate array comprises all the signer certificates that were used to verify this entry. Each signer certificate is followed by its supporting certificate chain (which may be empty). Each signer certificate and its supporting certificate chain are ordered bottom-to-top (i.e., with the signer certificate first and the (root) certificate authority last).

which is a bit more useful.

If the getCertificates method returns an array of Certificates we know that there is a digest entry in the manifest and a digest of the digest entry in a signature file which has been ‘verified’

3.2 JARVerifierV3

    final class JARVerifierV3 
                implements
                    JARVerifier
    {
        public boolean verify(File theFile)
        {
            Log.d(TAG, "verify: verifying " + theFile);
		    try
            {
                JarFile jf = new JarFile(theFile, true);
			
                try
                {
                    verify(jf);
                    Log.d(TAG, "verify: " + theFile + " verified");
                    return true;
                }
                finally
                {
                    jf.close();
                }
            }
            catch (Exception e)
            {
                Log.d(TAG, "verify: verification failed");
                e.printStackTrace();
                return false;
            }
        }
	
        //
	
        JARVerifierV3()
        {
            buffer = new byte[8192];
        }
	
        //

        private void verify(JarFile theJARFile) 
                     throws
                         Exception
        {
            Enumeration<JarEntry> entries = theJARFile.entries();
		
            while (entries.hasMoreElements())
            {
                verify(theJARFile, entries.nextElement());
            }
		
        }

        private void verify(JarFile theJARFile, JarEntry theEntry) 
                     throws
                         Exception
        {
            Log.d(TAG, "verify: " + theEntry.getName());
		
		
		
            InputStream is = theJARFile.getInputStream(theEntry);
		
            while (is.read(buffer) != -1)
            {
			
            }
		
            ensureCertificates(theEntry);
        }
	
        private void ensureCertificates(JarEntry theEntry)
                     throws
                         Exception
        {
            if (!theEntry.getName().startsWith(META_INF))
            {
                Log.d(TAG, "ensureCertificates: " + theEntry.getName());
		
                Certificate[] certificates = theEntry.getCertificates();
			
                if (certificates == null)
                {
                    throw new SecurityException("No certificates: " + theEntry.getName());
                }
            }
        }
	
        //
	
        private byte[] buffer;
	
        //
		
        private static final String META_INF      = "META-INF/";
	
        //
	
        private static final String TAG = "JARVerifierV3";
    }

3.3 Verification After File Addition

If we attempt to verify our APK with added file and modified manifest using JARVerifierV3 we return to the status quo ante.

    ...

    D/JARVerifierV3(  280): verify: verifying /data/data/xper.jv/files/GoodApp_added_modified_manifest.apk
    D/JARVerifierV3(  280): verify: res/layout/activity_main.xml
    D/JARVerifierV3(  280): ensureCertificates: res/layout/activity_main.xml
    D/JARVerifierV3(  280): verify: res/menu/activity_main.xml
    D/JARVerifierV3(  280): ensureCertificates: res/menu/activity_main.xml
    D/JARVerifierV3(  280): verify: AndroidManifest.xml
    D/JARVerifierV3(  280): ensureCertificates: AndroidManifest.xml
    D/JARVerifierV3(  280): verify: resources.arsc
    D/JARVerifierV3(  280): ensureCertificates: resources.arsc
    D/JARVerifierV3(  280): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV3(  280): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV3(  280): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV3(  280): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV3(  280): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV3(  280): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV3(  280): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV3(  280): ensureCertificates: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV3(  280): verify: classes.dex
    D/JARVerifierV3(  280): ensureCertificates: classes.dex
    D/JARVerifierV3(  280): verify: META-INF/MANIFEST.MF
    D/JARVerifierV3(  280): verify: META-INF/CERT.SF
    D/JARVerifierV3(  280): verify: META-INF/CERT.RSA
    D/JARVerifierV3(  280): verify: assets/new_asset
    D/JARVerifierV3(  280): ensureCertificates: assets/new_asset
    D/JARVerifierV3(  280): verify: verification failed
    W/System.err(  280): java.lang.SecurityException: No certificates: assets/new_asset
    W/System.err(  280): 	at xper.jv.JARVerifierV3.ensureCertificates(JARVerifierV3.java:95)
    W/System.err(  280): 	at xper.jv.JARVerifierV3.verify(JARVerifierV3.java:80)
    W/System.err(  280): 	at xper.jv.JARVerifierV3.verify(JARVerifierV3.java:60)
    W/System.err(  280): 	at xper.jv.JARVerifierV3.verify(JARVerifierV3.java:26)
    W/System.err(  280): 	at xper.jv.XperJVActivity.verify(XperJVActivity.java:128)
    W/System.err(  280): 	at xper.jv.XperJVActivity.access$0(XperJVActivity.java:118)
    W/System.err(  280): 	at xper.jv.XperJVActivity$1.run(XperJVActivity.java:112)
    W/System.err(  280): 	at java.lang.Thread.run(Thread.java:1096)

    ...

3.4 Signing Additional Manifest Entries

Needless to say there is a catch. Not only is it possible to add digest entries to the manifest of a signed JAR it is possible to sign them as well.

We can create a second signature file BERT.SF which contains the digest of the added manifest entry

    Signature-Version: 1.0

    Name: assets/new_asset
    SHA1-Digest: 6G+NGDseZOsVO904ZG1VLkFAr30=

and a signed signature file BERT.DSA and then create a new APK

    Archive:  GoodApp_added_modified_manifest_ds.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
         1588  07-10-13 21:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1103  07-26-13 14:51   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
           48  07-24-13 21:56   assets/new_asset
         2365  07-26-13 22:10   META-INF/BERT.DSA
           95  07-26-13 22:10   META-INF/BERT.SF
     --------                   -------
       391972                   18 files

Now our new file assets/new_asset has certificates associated with it and verification succeeds one more

    ...

    D/JARVerifierV3(  281): verify: verifying /data/data/xper.jv/files/GoodApp_added_modified_manifest_ds.apk
    D/JARVerifierV3(  281): verify: res/layout/activity_main.xml
    D/dalvikvm(  281): GC_FOR_MALLOC freed 4701 objects / 294968 bytes in 59ms
    D/JARVerifierV3(  281): ensureCertificates: res/layout/activity_main.xml
    D/JARVerifierV3(  281): verify: res/menu/activity_main.xml
    D/JARVerifierV3(  281): ensureCertificates: res/menu/activity_main.xml
    D/JARVerifierV3(  281): verify: AndroidManifest.xml
    D/JARVerifierV3(  281): ensureCertificates: AndroidManifest.xml
    D/JARVerifierV3(  281): verify: resources.arsc
    D/JARVerifierV3(  281): ensureCertificates: resources.arsc
    D/JARVerifierV3(  281): verify: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-hdpi/ic_action_search.png
    D/JARVerifierV3(  281): verify: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-hdpi/ic_launcher.png
    D/JARVerifierV3(  281): verify: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-ldpi/ic_launcher.png
    D/JARVerifierV3(  281): verify: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-mdpi/ic_action_search.png
    D/JARVerifierV3(  281): verify: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-mdpi/ic_launcher.png
    D/JARVerifierV3(  281): verify: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-xhdpi/ic_action_search.png
    D/JARVerifierV3(  281): verify: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV3(  281): ensureCertificates: res/drawable-xhdpi/ic_launcher.png
    D/JARVerifierV3(  281): verify: classes.dex
    D/JARVerifierV3(  281): ensureCertificates: classes.dex
    D/JARVerifierV3(  281): verify: META-INF/MANIFEST.MF
    D/JARVerifierV3(  281): verify: META-INF/CERT.SF
    D/JARVerifierV3(  281): verify: META-INF/CERT.RSA
    D/JARVerifierV3(  281): verify: assets/new_asset
    D/JARVerifierV3(  281): ensureCertificates: assets/new_asset
    D/JARVerifierV3(  281): verify: META-INF/BERT.DSA
    D/JARVerifierV3(  281): verify: META-INF/BERT.SF
    D/JARVerifierV3(  281): verify: /data/data/xper.jv/files/GoodApp_added_modified_manifest_ds.apk verified

    ...

Back to square one again

4.0 PackageParser collectCertificates Again

This is why the PackageParser collectCertificates method is doing this

                ...

                Enumeration<JarEntry> entries = jarFile.entries();
                final Manifest manifest = jarFile.getManifest();
                while (entries.hasMoreElements()) {
                    final JarEntry je = entries.nextElement();
                    if (je.isDirectory()) continue;

                    final String name = je.getName();

                    if (name.startsWith("META-INF/"))
                        continue;

                    if (ANDROID_MANIFEST_FILENAME.equals(name)) {
                        final Attributes attributes = manifest.getAttributes(name);
                        pkg.manifestDigest = ManifestDigest.fromAttributes(attributes);
                    }

                    final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
                    if (DEBUG_JAR) {
                        Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
                                + ": certs=" + certs + " ("
                                + (certs != null ? certs.length : 0) + ")");
                    }

                    if (localCerts == null) {
                        Slog.e(TAG, "Package " + pkg.packageName
                                + " has no certificates at entry "
                                + je.getName() + "; ignoring!");
                        jarFile.close();
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
                        return false;
                    } else if (certs == null) {
                        certs = localCerts;
                    } else {
                        // Ensure all certificates match.
                        for (int i=0; i<certs.length; i++) {
                            boolean found = false;
                            for (int j=0; j<localCerts.length; j++) {
                                if (certs[i] != null &&
                                    certs[i].equals(localCerts[j])) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found || certs.length != localCerts.length) {
                                Slog.e(TAG, "Package " + pkg.packageName
                                        + " has mismatched certificates at entry "
                                        + je.getName() + "; ignoring!");
                                jarFile.close();
                                mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
                                return false;
                            }
                        }
                    }
                }
                
                ...

not only checking for the existence of certificates associated with each file

                    ...

                    if (localCerts == null) {
                        Slog.e(TAG, "Package " + pkg.packageName
                                + " has no certificates at entry "
                                + je.getName() + "; ignoring!");
                        jarFile.close();
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
                        return false;
                    } 
                    
                    ...

but also checking, albeit in a somewhat convoluted manner, that all the files have been signed with the same set of certificates .

                        ...

                        // Ensure all certificates match.
                        for (int i=0; i<certs.length; i++) {
                            boolean found = false;
                            for (int j=0; j<localCerts.length; j++) {
                                if (certs[i] != null &&
                                    certs[i].equals(localCerts[j])) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found || certs.length != localCerts.length) {
                                Slog.e(TAG, "Package " + pkg.packageName
                                        + " has mismatched certificates at entry "
                                        + je.getName() + "; ignoring!");
                                jarFile.close();
                                mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
                                return false;
                            }
                        }

                        ...

This is what verification of a signed JAR amounts to.

Establishing in the most complicated way possible whether all of the files present in the JAR have been signed with the ‘same’set of certificates.

Note that this does not address the question of whether files have been removed.

Note also that this relies on establishing the ‘equality’ of certficates which is an ‘interesting’ problem in its own right.


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Advertisements

1 Comment »

  1. […] we have seen the cost is the complexity of the verification process and the inconclusiveness of the […]

    Pingback by The Great Android Security Hole Of ’08 ? – Part Nine: The Root Cause ? — Signed JARs Considered Harmful | Just An Application — July 28, 2013 @ 6:36 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: