Fix JAR sig verification with multiple digests am: 483b4735ef am: 23af335fa3 am: df4616c317
am: 7731297815

Change-Id: I6939b94f25493baf8d82b6d9263d69896ce79656
diff --git a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
index d7a4eb0..fdf459a 100644
--- a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
@@ -1425,18 +1425,18 @@
                 continue;
             }
 
-            Collection<NamedDigest> expectedDigests =
-                    getDigestsToVerify(manifestSection, "-Digest", minSdkVersion, maxSdkVersion);
+            List<NamedDigest> expectedDigests =
+                    new ArrayList<>(
+                            getDigestsToVerify(
+                                    manifestSection, "-Digest", minSdkVersion, maxSdkVersion));
             if (expectedDigests.isEmpty()) {
                 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
                 continue;
             }
 
             MessageDigest[] mds = new MessageDigest[expectedDigests.size()];
-            int mdIndex = 0;
-            for (NamedDigest expectedDigest : expectedDigests) {
-                mds[mdIndex] = getMessageDigest(expectedDigest.jcaDigestAlgorithm);
-                mdIndex++;
+            for (int i = 0; i < expectedDigests.size(); i++) {
+                mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm);
             }
 
             try {
@@ -1451,9 +1451,9 @@
                 throw new IOException("Failed to read entry: " + entryName, e);
             }
 
-            mdIndex = 0;
-            for (NamedDigest expectedDigest : expectedDigests) {
-                byte[] actualDigest = mds[mdIndex].digest();
+            for (int i = 0; i < expectedDigests.size(); i++) {
+                NamedDigest expectedDigest = expectedDigests.get(i);
+                byte[] actualDigest = mds[i].digest();
                 if (!Arrays.equals(expectedDigest.digest, actualDigest)) {
                     result.addError(
                             Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 218d450..4268d84 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -525,19 +525,100 @@
         assertVerified(verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N));
     }
 
+    @Test
+    public void testV1MultipleDigestAlgsInManifestAndSignatureFile() throws Exception {
+        // MANIFEST.MF contains SHA-1 and SHA-256 digests for each entry, .SF contains only SHA-1
+        // digests. This file was obtained by:
+        //   jarsigner -sigalg SHA256withRSA -digestalg SHA-256 ... <file> ...
+        //   jarsigner -sigalg SHA1withRSA -digestalg SHA1 ... <same file> ...
+        assertVerified(verify("v1-sha1-sha256-manifest-and-sha1-sf.apk"));
+
+        // MANIFEST.MF and .SF contain SHA-1 and SHA-256 digests for each entry. This file was
+        // obtained by modifying apksigner to output multiple digests.
+        assertVerified(verify("v1-sha1-sha256-manifest-and-sf.apk"));
+
+        // One of the digests is wrong in either MANIFEST.MF or .SF. These files were obtained by
+        // modifying apksigner to output multiple digests and to flip a bit to create a wrong
+        // digest.
+
+        // SHA-1 digests in MANIFEST.MF are wrong, but SHA-256 digests are OK.
+        // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18
+        // and higher.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk"),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 17),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 18));
+
+        // SHA-1 digests in .SF are wrong, but SHA-256 digests are OK.
+        // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18
+        // and higher.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk"),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 17),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 18));
+
+        // SHA-256 digests in MANIFEST.MF are wrong, but SHA-1 digests are OK.
+        // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17
+        // and lower.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk"),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 18),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 17));
+
+        // SHA-256 digests in .SF are wrong, but SHA-1 digests are OK.
+        // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17
+        // and lower.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk"),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 18),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 17));
+    }
+
     private ApkVerifier.Result verify(String apkFilenameInResources)
             throws IOException, ApkFormatException, NoSuchAlgorithmException {
-        return verify(apkFilenameInResources, null);
+        return verify(apkFilenameInResources, null, null);
     }
 
     private ApkVerifier.Result verifyForMinSdkVersion(
             String apkFilenameInResources, int minSdkVersion)
                     throws IOException, ApkFormatException, NoSuchAlgorithmException {
-        return verify(apkFilenameInResources, minSdkVersion);
+        return verify(apkFilenameInResources, minSdkVersion, null);
+    }
+
+    private ApkVerifier.Result verifyForMaxSdkVersion(
+            String apkFilenameInResources, int maxSdkVersion)
+                    throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        return verify(apkFilenameInResources, null, maxSdkVersion);
     }
 
     private ApkVerifier.Result verify(
-            String apkFilenameInResources, Integer minSdkVersionOverride)
+            String apkFilenameInResources,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride)
                     throws IOException, ApkFormatException, NoSuchAlgorithmException {
         byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources);
 
@@ -546,6 +627,9 @@
         if (minSdkVersionOverride != null) {
             builder.setMinCheckedPlatformVersion(minSdkVersionOverride);
         }
+        if (maxSdkVersionOverride != null) {
+            builder.setMaxCheckedPlatformVersion(maxSdkVersionOverride);
+        }
         return builder.build().verify();
     }
 
@@ -585,8 +669,11 @@
     }
 
     private void assertVerified(
-            String apkFilenameInResources, Integer minSdkVersionOverride) throws Exception {
-        assertVerified(verify(apkFilenameInResources, minSdkVersionOverride));
+            String apkFilenameInResources,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride) throws Exception {
+        assertVerified(
+                verify(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride));
     }
 
     static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) {
@@ -643,23 +730,25 @@
 
     private void assertVerifiedForEach(
             String apkFilenamePatternInResources, String[] args) throws Exception {
-        assertVerifiedForEach(apkFilenamePatternInResources, args, null);
+        assertVerifiedForEach(apkFilenamePatternInResources, args, null, null);
     }
 
     private void assertVerifiedForEach(
-            String apkFilenamePatternInResources, String[] args, Integer minSdkVersionOverride)
-                    throws Exception {
+            String apkFilenamePatternInResources,
+            String[] args,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride) throws Exception {
         for (String arg : args) {
             String apkFilenameInResources =
                     String.format(Locale.US, apkFilenamePatternInResources, arg);
-            assertVerified(apkFilenameInResources, minSdkVersionOverride);
+            assertVerified(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride);
         }
     }
 
     private void assertVerifiedForEachForMinSdkVersion(
             String apkFilenameInResources, String[] args, int minSdkVersion)
                     throws Exception {
-        assertVerifiedForEach(apkFilenameInResources, args, minSdkVersion);
+        assertVerifiedForEach(apkFilenameInResources, args, minSdkVersion, null);
     }
 
     private static byte[] sha256(byte[] msg) throws Exception {
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk
new file mode 100644
index 0000000..1d90c7e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk
new file mode 100644
index 0000000..a16e442
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk
new file mode 100644
index 0000000..29a6030
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk
new file mode 100644
index 0000000..eb12bcc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk
new file mode 100644
index 0000000..f347a1c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk
new file mode 100644
index 0000000..f347a1c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk
Binary files differ