Supporting V3 cert rotation logic in V4.

+fixing tests to check V4 signatures

Fixes: 169094510
Test: gradlew test

$ ~/trunk/master/out/host/linux-x86/bin/apksigner sign -v --key ec-p256.pk8 --cert ec-p256.x509.der --next-signer --key ec-p256_2.pk8 --cert ec-p256_2.x509.der --lineage ec-p256-lineage-2-signers -out ./original-signed.apk ./original.apk
Signed

$ ~/trunk/master/out/host/linux-x86/bin/apksigner verify -v -v4-signature-file ./original-signed.apk.idsig ./original-signed.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Verified using v4 scheme (APK Signature Scheme v4): true
Verified for SourceStamp: false
Number of signers: 1

$ adb install ./original-signed.apk
Performing Incremental Install
Serving...
All files should be loaded. Notifying the device.
Success
Install command complete in 372 ms

Change-Id: Iee836b723270b746ec6b27a984c8b2c42a060791
Merged-In: Iee836b723270b746ec6b27a984c8b2c42a060791
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index 12f56ed..90f2a6d 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -311,13 +311,8 @@
         }
     }
 
-    private List<ApkSigningBlockUtils.SignerConfig> createV3SignerConfigs(
-            boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
-        List<ApkSigningBlockUtils.SignerConfig> rawConfigs =
-                createSigningBlockSignerConfigs(
-                        apkSigningBlockPaddingSupported,
-                        ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
-
+    private List<ApkSigningBlockUtils.SignerConfig> processV3Configs(
+            List<ApkSigningBlockUtils.SignerConfig> rawConfigs) throws InvalidKeyException {
         List<ApkSigningBlockUtils.SignerConfig> processedConfigs = new ArrayList<>();
 
         // we have our configs, now touch them up to appropriately cover all SDK levels since APK
@@ -365,13 +360,23 @@
                     "Provided key algorithms not supported on all desired "
                             + "Android SDK versions");
         }
+
         return processedConfigs;
     }
 
+    private List<ApkSigningBlockUtils.SignerConfig> createV3SignerConfigs(
+            boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
+        return processV3Configs(createSigningBlockSignerConfigs(apkSigningBlockPaddingSupported,
+                ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3));
+    }
+
     private ApkSigningBlockUtils.SignerConfig createV4SignerConfig() throws InvalidKeyException {
-        List<ApkSigningBlockUtils.SignerConfig> configs =
-                createSigningBlockSignerConfigs(
-                        true, ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
+        List<ApkSigningBlockUtils.SignerConfig> configs = createSigningBlockSignerConfigs(true,
+                ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
+        if (configs.size() != 1) {
+            // V4 only uses signer config to connect back to v3. Use the same filtering logic.
+            configs = processV3Configs(configs);
+        }
         if (configs.size() != 1) {
             throw new InvalidKeyException("Only accepting one signer config for V4 Signature.");
         }
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 9697b9a..4e18852 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -42,23 +42,23 @@
 import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
 import com.android.apksig.internal.zip.CentralDirectoryRecord;
 import com.android.apksig.internal.zip.LocalFileRecord;
-import com.android.apksig.util.DataSinks;
 import com.android.apksig.util.DataSource;
 import com.android.apksig.util.DataSources;
-import com.android.apksig.util.ReadableDataSink;
 import com.android.apksig.zip.ZipFormatException;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
 import java.nio.file.Files;
-import java.nio.file.StandardOpenOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -90,6 +90,9 @@
     private static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
             "rsa-2048-lineage-2-signers";
 
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     public static void main(String[] params) throws Exception {
         File outDir = (params.length > 0) ? new File(params[0]) : new File(".");
         generateGoldenFiles(outDir);
@@ -136,21 +139,24 @@
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
         signGolden(
                 "golden-legacy-aligned-in.apk",
                 new File(outDir, "golden-legacy-aligned-v1-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
         signGolden(
                 "golden-aligned-in.apk",
                 new File(outDir, "golden-aligned-v1-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
 
         signGolden(
                 "golden-unaligned-in.apk",
@@ -367,7 +373,13 @@
         DataSource in =
                 DataSources.asDataSource(
                         ByteBuffer.wrap(Resources.toByteArray(ApkSigner.class, inResourceName)));
-        apkSignerBuilder.setInputApk(in).setOutputApk(outFile).build().sign();
+        apkSignerBuilder.setInputApk(in).setOutputApk(outFile);
+
+        File outFileIdSig = new File(outFile.getCanonicalPath() + ".idsig");
+        apkSignerBuilder.setV4SignatureOutputFile(outFileIdSig);
+        apkSignerBuilder.setV4ErrorReportingEnabled(true);
+
+        apkSignerBuilder.build().sign();
     }
 
     @Test
@@ -399,7 +411,8 @@
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
         assertGolden(
                 "golden-unaligned-in.apk",
                 "golden-unaligned-v2-out.apk",
@@ -474,7 +487,8 @@
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
         assertGolden(
                 "golden-legacy-aligned-in.apk",
                 "golden-legacy-aligned-v2-out.apk",
@@ -548,7 +562,8 @@
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
                         .setV2SigningEnabled(false)
-                        .setV3SigningEnabled(false));
+                        .setV3SigningEnabled(false)
+                        .setV4SigningEnabled(false));
         assertGolden(
                 "golden-aligned-in.apk",
                 "golden-aligned-v2-out.apk",
@@ -661,7 +676,7 @@
         String in = "original.apk";
 
         // Sign so that the APK is guaranteed to verify on API Level 1+
-        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+        File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
         assertVerified(verifyForMinSdkVersion(out, 1));
 
         // Sign so that the APK is guaranteed to verify on API Level 18+
@@ -679,7 +694,7 @@
         String in = "original.apk";
 
         // Sign so that the APK is guaranteed to verify on API Level 1+
-        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+        File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
         assertVerified(verifyForMinSdkVersion(out, 1));
 
         // Sign so that the APK is guaranteed to verify on API Level 21+
@@ -698,7 +713,7 @@
 
         // NOTE: EC APK signatures are not supported prior to API Level 18
         // Sign so that the APK is guaranteed to verify on API Level 18+
-        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
+        File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
         assertVerified(verifyForMinSdkVersion(out, 18));
         // Does not verify on API Level 17 because EC not supported
         assertVerificationFailure(
@@ -930,14 +945,13 @@
                 Arrays.asList(
                         getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
                         getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
-        DataSource out =
+        File out =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signerConfigs)
                                 .setV3SigningEnabled(true)
                                 .setSigningCertificateLineage(lineage));
-        SigningCertificateLineage lineageFromApk =
-                SigningCertificateLineage.readFromApkDataSource(out);
+        SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkFile(out);
         assertTrue(
                 "The first signer was not in the lineage from the signed APK",
                 lineageFromApk.isSignerInLineage((firstSigner)));
@@ -960,7 +974,7 @@
                         getDefaultSignerConfigFromResources(
                                 FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
                                 FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS));
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signersList)
@@ -1009,28 +1023,32 @@
         messageDigest.update(sourceStampSigner.getCertificates().get(0).getEncoded());
         byte[] expectedStampCertificateDigest = messageDigest.digest();
 
-        DataSource signedApk =
+        File signedApkFile =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signers)
                                 .setV1SigningEnabled(true)
                                 .setSourceStampSignerConfig(sourceStampSigner));
 
-        ApkUtils.ZipSections zipSections = findZipSections(signedApk);
-        List<CentralDirectoryRecord> cdRecords =
-                V1SchemeVerifier.parseZipCentralDirectory(signedApk, zipSections);
-        CentralDirectoryRecord stampCdRecord = null;
-        for (CentralDirectoryRecord cdRecord : cdRecords) {
-            if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
-                stampCdRecord = cdRecord;
-                break;
+        try (RandomAccessFile f = new RandomAccessFile(signedApkFile, "r")) {
+            DataSource signedApk = DataSources.asDataSource(f, 0, f.length());
+
+            ApkUtils.ZipSections zipSections = findZipSections(signedApk);
+            List<CentralDirectoryRecord> cdRecords =
+                    V1SchemeVerifier.parseZipCentralDirectory(signedApk, zipSections);
+            CentralDirectoryRecord stampCdRecord = null;
+            for (CentralDirectoryRecord cdRecord : cdRecords) {
+                if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
+                    stampCdRecord = cdRecord;
+                    break;
+                }
             }
+            assertNotNull(stampCdRecord);
+            byte[] actualStampCertificateDigest =
+                    LocalFileRecord.getUncompressedData(
+                            signedApk, stampCdRecord, zipSections.getZipCentralDirectoryOffset());
+            assertArrayEquals(expectedStampCertificateDigest, actualStampCertificateDigest);
         }
-        assertNotNull(stampCdRecord);
-        byte[] actualStampCertificateDigest =
-                LocalFileRecord.getUncompressedData(
-                        signedApk, stampCdRecord, zipSections.getZipCentralDirectoryOffset());
-        assertArrayEquals(expectedStampCertificateDigest, actualStampCertificateDigest);
     }
 
     @Test
@@ -1041,7 +1059,7 @@
         ApkSigner.SignerConfig sourceStampSigner =
                 getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
 
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original-with-stamp-file.apk",
                         new ApkSigner.Builder(signers)
@@ -1092,7 +1110,7 @@
         ApkSigner.SignerConfig sourceStampSigner =
                 getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
 
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original-with-stamp-file.apk",
                         new ApkSigner.Builder(signers)
@@ -1113,7 +1131,7 @@
                 Collections.singletonList(
                         getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
 
-        DataSource signedApk =
+        File signedApkFile =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signersList)
@@ -1121,17 +1139,21 @@
                                 .setV2SigningEnabled(true)
                                 .setV3SigningEnabled(true));
 
-        ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(signedApk);
-        ApkSigningBlockUtils.Result result =
-                new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
-        assertThrows(
-                ApkSigningBlockUtils.SignatureNotFoundException.class,
-                () ->
-                        ApkSigningBlockUtils.findSignature(
-                                signedApk,
-                                zipSections,
-                                ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
-                                result));
+        try (RandomAccessFile f = new RandomAccessFile(signedApkFile, "r")) {
+            DataSource signedApk = DataSources.asDataSource(f, 0, f.length());
+
+            ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(signedApk);
+            ApkSigningBlockUtils.Result result =
+                    new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
+            assertThrows(
+                    ApkSigningBlockUtils.SignatureNotFoundException.class,
+                    () ->
+                            ApkSigningBlockUtils.findSignature(
+                                    signedApk,
+                                    zipSections,
+                                    ApkSigningBlockUtils.VERSION_SOURCE_STAMP,
+                                    result));
+        }
     }
 
     @Test
@@ -1142,13 +1164,14 @@
         ApkSigner.SignerConfig sourceStampSigner =
                 getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
 
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signersList)
                                 .setV1SigningEnabled(true)
                                 .setV2SigningEnabled(false)
                                 .setV3SigningEnabled(false)
+                                .setV4SigningEnabled(false)
                                 .setSourceStampSignerConfig(sourceStampSigner));
 
         ApkVerifier.Result sourceStampVerificationResult =
@@ -1164,7 +1187,7 @@
         ApkSigner.SignerConfig sourceStampSigner =
                 getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
 
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signersList)
@@ -1186,7 +1209,7 @@
         ApkSigner.SignerConfig sourceStampSigner =
                 getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
 
-        DataSource signedApk =
+        File signedApk =
                 sign(
                         "original.apk",
                         new ApkSigner.Builder(signersList)
@@ -1200,7 +1223,7 @@
         assertSourceStampVerified(signedApk, sourceStampVerificationResult);
     }
 
-    private RSAPublicKey getRSAPublicKeyFromSigningBlock(DataSource apk, int signatureVersionId)
+    private RSAPublicKey getRSAPublicKeyFromSigningBlock(File apk, int signatureVersionId)
             throws Exception {
         int signatureVersionBlockId;
         switch (signatureVersionId) {
@@ -1247,13 +1270,17 @@
     }
 
     private static SignatureInfo getSignatureInfoFromApk(
-            DataSource apk, int signatureVersionId, int signatureVersionBlockId)
+            File apkFile, int signatureVersionId, int signatureVersionBlockId)
             throws IOException, ZipFormatException,
                     ApkSigningBlockUtils.SignatureNotFoundException {
-        ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
-        ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(signatureVersionId);
-        return ApkSigningBlockUtils.findSignature(
-                apk, zipSections, signatureVersionBlockId, result);
+        try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
+            DataSource apk = DataSources.asDataSource(f, 0, f.length());
+            ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+            ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
+                    signatureVersionId);
+            return ApkSigningBlockUtils.findSignature(apk, zipSections, signatureVersionBlockId,
+                    result);
+        }
     }
 
     /**
@@ -1266,18 +1293,22 @@
             ApkSigner.Builder apkSignerBuilder)
             throws Exception {
         // Sign the provided golden input
-        DataSource out = sign(inResourceName, apkSignerBuilder);
+        File out = sign(inResourceName, apkSignerBuilder);
+        assertVerified(verify(out, AndroidSdkVersion.P));
 
         // Assert that the output is identical to the provided golden output
-        if (out.size() > Integer.MAX_VALUE) {
-            throw new RuntimeException("Output too large: " + out.size() + " bytes");
+        if (out.length() > Integer.MAX_VALUE) {
+            throw new RuntimeException("Output too large: " + out.length() + " bytes");
         }
-        ByteBuffer actualOutBuf = out.getByteBuffer(0, (int) out.size());
+        byte[] outData = new byte[(int)out.length()];
+        try (FileInputStream fis = new FileInputStream(out)) {
+            fis.read(outData);
+        }
+        ByteBuffer actualOutBuf = ByteBuffer.wrap(outData);
 
         ByteBuffer expectedOutBuf =
                 ByteBuffer.wrap(Resources.toByteArray(getClass(), expectedOutResourceName));
 
-        int actualStartPos = actualOutBuf.position();
         boolean identical = false;
         if (actualOutBuf.remaining() == expectedOutBuf.remaining()) {
             while (actualOutBuf.hasRemaining()) {
@@ -1291,47 +1322,47 @@
         if (identical) {
             return;
         }
-        actualOutBuf.position(actualStartPos);
 
         if (KEEP_FAILING_OUTPUT_AS_FILES) {
             File tmp = File.createTempFile(getClass().getSimpleName(), ".apk");
-            try (ByteChannel outChannel =
-                    Files.newByteChannel(
-                            tmp.toPath(),
-                            StandardOpenOption.WRITE,
-                            StandardOpenOption.CREATE,
-                            StandardOpenOption.TRUNCATE_EXISTING)) {
-                while (actualOutBuf.hasRemaining()) {
-                    outChannel.write(actualOutBuf);
-                }
-            }
+            Files.copy(out.toPath(), tmp.toPath());
             fail(tmp + " differs from " + expectedOutResourceName);
         } else {
             fail("Output differs from " + expectedOutResourceName);
         }
     }
 
-    private DataSource sign(String inResourceName, ApkSigner.Builder apkSignerBuilder)
+    private File sign(String inResourceName, ApkSigner.Builder apkSignerBuilder)
             throws Exception {
         DataSource in =
                 DataSources.asDataSource(
                         ByteBuffer.wrap(Resources.toByteArray(getClass(), inResourceName)));
-        ReadableDataSink out = DataSinks.newInMemoryDataSink();
-        apkSignerBuilder.setInputApk(in).setOutputApk(out).build().sign();
-        return out;
+        File outFile = mTemporaryFolder.newFile();
+        apkSignerBuilder.setInputApk(in).setOutputApk(outFile);
+
+        File outFileIdSig = new File(outFile.getCanonicalPath() + ".idsig");
+        apkSignerBuilder.setV4SignatureOutputFile(outFileIdSig);
+        apkSignerBuilder.setV4ErrorReportingEnabled(true);
+
+        apkSignerBuilder.build().sign();
+        return outFile;
     }
 
-    private static ApkVerifier.Result verifyForMinSdkVersion(DataSource apk, int minSdkVersion)
+    private static ApkVerifier.Result verifyForMinSdkVersion(File apk, int minSdkVersion)
             throws IOException, ApkFormatException, NoSuchAlgorithmException {
         return verify(apk, minSdkVersion);
     }
 
-    private static ApkVerifier.Result verify(DataSource apk, Integer minSdkVersionOverride)
+    private static ApkVerifier.Result verify(File apk, Integer minSdkVersionOverride)
             throws IOException, ApkFormatException, NoSuchAlgorithmException {
         ApkVerifier.Builder builder = new ApkVerifier.Builder(apk);
         if (minSdkVersionOverride != null) {
             builder.setMinCheckedPlatformVersion(minSdkVersionOverride);
         }
+        File idSig = new File(apk.getCanonicalPath() + ".idsig");
+        if (idSig.exists()) {
+            builder.setV4SignatureFile(idSig);
+        }
         return builder.build().verify();
     }
 
@@ -1339,7 +1370,7 @@
         ApkVerifierTest.assertVerified(result);
     }
 
-    private static void assertSourceStampVerified(DataSource signedApk, ApkVerifier.Result result)
+    private static void assertSourceStampVerified(File signedApk, ApkVerifier.Result result)
             throws ApkSigningBlockUtils.SignatureNotFoundException, IOException,
                     ZipFormatException {
         SignatureInfo signatureInfo =