Merge "Resolve handling of supported signatures and digests for max SDK"
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index 5e458ef..6e0a520 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -40,6 +40,7 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -185,6 +186,26 @@
 
         Result result = new Result();
 
+        // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
+        // name, but the verifiers use this parameter as the schemes supported by the target SDK
+        // range. Since the code below skips signature verification based on max SDK the mapping of
+        // supported schemes needs to be modified to ensure the verifiers do not report a stripped
+        // signature for an SDK range that does not support that signature version. For instance an
+        // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
+        // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
+        // verification is performed it would see the stripping protection attribute, see that V3
+        // is in the list of supported signatures, and report a stripped signature.
+        Map<Integer, String> supportedSchemeNames;
+        if (maxSdkVersion >= AndroidSdkVersion.P) {
+            supportedSchemeNames = SUPPORTED_APK_SIG_SCHEME_NAMES;
+        } else if (maxSdkVersion >= AndroidSdkVersion.N) {
+            supportedSchemeNames = new HashMap<>(1);
+            supportedSchemeNames.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2,
+                    SUPPORTED_APK_SIG_SCHEME_NAMES.get(
+                            ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2));
+        } else {
+            supportedSchemeNames = Collections.EMPTY_MAP;
+        }
         // Android N and newer attempts to verify APKs using the APK Signing Block, which can
         // include v2 and/or v3 signatures.  If none is found, it falls back to JAR signature
         // verification. If the signature is found but does not verify, the APK is rejected.
@@ -220,7 +241,7 @@
                             V2SchemeVerifier.verify(
                                     apk,
                                     zipSections,
-                                    SUPPORTED_APK_SIG_SCHEME_NAMES,
+                                    supportedSchemeNames,
                                     foundApkSigSchemeIds,
                                     Math.max(minSdkVersion, AndroidSdkVersion.N),
                                     maxSdkVersion);
@@ -261,7 +282,7 @@
                     V1SchemeVerifier.verify(
                             apk,
                             zipSections,
-                            SUPPORTED_APK_SIG_SCHEME_NAMES,
+                            supportedSchemeNames,
                             foundApkSigSchemeIds,
                             minSdkVersion,
                             maxSdkVersion);
diff --git a/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java b/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
index c9cc4c1..556c643 100644
--- a/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
+++ b/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java
@@ -215,6 +215,12 @@
                 }
                 ContentDigestAlgorithm contentDigestAlgorithm =
                         signatureAlgorithm.getContentDigestAlgorithm();
+                // if the current digest algorithm is not in the list provided by the caller then
+                // ignore it; the signer may contain digests not recognized by the specified SDK
+                // range.
+                if (!contentDigestAlgorithms.contains(contentDigestAlgorithm)) {
+                    continue;
+                }
                 byte[] expectedDigest = expected.getValue();
                 byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
                 if (!Arrays.equals(expectedDigest, actualDigest)) {
diff --git a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
index 484334d..615d251 100644
--- a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
+++ b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
@@ -36,6 +36,9 @@
     /** Android 5.0. A flat one with beautiful shadows. But still tasty. */
     public static final int LOLLIPOP = 21;
 
+    /** Android 6.0. M is for Marshmallow! */
+    public static final int M = 23;
+
     /** Android 7.0. N is for Nougat. */
     public static final int N = 24;
 
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 1f9e208..351d0a8 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -267,6 +267,17 @@
     }
 
     @Test
+    public void testSignaturesIgnoredForMaxSDK() throws Exception {
+        // The V2 signature scheme was introduced in N, and V3 was introduced in P. This test
+        // verifies a max SDK of pre-P ignores the V3 signature and a max SDK of pre-N ignores both
+        // the V2 and V3 signatures.
+        assertVerified(verifyForMaxSdkVersion("v1v2v3-with-rsa-2048-lineage-3-signers.apk",
+                AndroidSdkVersion.O));
+        assertVerified(verifyForMaxSdkVersion("v1v2v3-with-rsa-2048-lineage-3-signers.apk",
+                AndroidSdkVersion.M));
+    }
+
+    @Test
     public void testV2OneSignerOneSignatureAccepted() throws Exception {
         // APK signed with v2 scheme only, one signer, one signature
         assertVerifiedForEachForMinSdkVersion(