Add apksig Signature Scheme v3 tests
am: dded3f8f4d

Change-Id: Ic7e64ca6aeb922c487b7de884a3c5bc17b8188c0
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 4713bab..aeb02cd 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -20,7 +20,10 @@
 import com.android.apksig.ApkVerifier;
 import com.android.apksig.SigningCertificateLineage;
 import com.android.apksig.SigningCertificateLineage.SignerCapabilities;
+import com.android.apksig.apk.ApkFormatException;
 import com.android.apksig.apk.MinSdkVersionException;
+import com.android.apksig.util.DataSource;
+import com.android.apksig.util.DataSources;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
@@ -31,6 +34,9 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -85,6 +91,8 @@
     private static MessageDigest sha1 = null;
     private static MessageDigest md5 = null;
 
+    public static final int ZIP_MAGIC = 0x04034b50;
+
     public static void main(String[] params) throws Exception {
         if ((params.length == 0) || ("--help".equals(params[0])) || ("-h".equals(params[0]))) {
             printUsage(HELP_PAGE_GENERAL);
@@ -217,7 +225,7 @@
                 signerParams.certFile = optionsParser.getRequiredValue("Certificate file");
             } else if ("lineage".equals(optionName)) {
                 File lineageFile = new File(optionsParser.getRequiredValue("Lineage File"));
-                lineage = SigningCertificateLineage.readFromFile(lineageFile);
+                lineage = getLineageFromInputFile(lineageFile);
             } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) {
                 verbose = optionsParser.getOptionalBooleanValue(true);
             } else if ("next-provider".equals(optionName)) {
@@ -651,7 +659,7 @@
             SigningCertificateLineage lineage;
             if (inputKeyLineage != null) {
                 // we already have history, add the new key to the end of it
-                lineage = SigningCertificateLineage.readFromFile(inputKeyLineage);
+                lineage = getLineageFromInputFile(inputKeyLineage);
                 lineage.updateSignerCapabilities(oldSignerConfig,
                         oldSignerParams.signerCapabilitiesBuilder.build());
                 lineage = lineage.spawnDescendant(oldSignerConfig,
@@ -682,6 +690,7 @@
         boolean verbose = false;
         boolean printCerts = false;
         boolean lineageUpdated = false;
+        File inputKeyLineage = null;
         File outputKeyLineage = null;
         String optionName;
         OptionsParser optionsParser = new OptionsParser(params);
@@ -692,8 +701,7 @@
                 printUsage(HELP_PAGE_LINEAGE);
                 return;
             } else if ("in".equals(optionName)) {
-                File inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name"));
-                lineage = SigningCertificateLineage.readFromFile(inputKeyLineage);
+                inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name"));
             } else if ("out".equals(optionName)) {
                 outputKeyLineage = new File(optionsParser.getRequiredValue("Output file name"));
             } else if ("signer".equals(optionName)) {
@@ -709,9 +717,11 @@
                                 + ". See --help for supported options.");
             }
         }
-        if (lineage == null) {
+        if (inputKeyLineage == null) {
             throw new ParameterException("Input lineage file parameter not present");
         }
+        lineage = getLineageFromInputFile(inputKeyLineage);
+
         try (PasswordRetriever passwordRetriever = new PasswordRetriever()) {
             for (int i = 0; i < signers.size(); i++) {
                 SignerParams signerParams = signers.get(i);
@@ -775,6 +785,29 @@
         }
     }
 
+    /**
+     * Extracts the Signing Certificate Lineage from the provided lineage or APK file.
+     */
+    private static SigningCertificateLineage getLineageFromInputFile(File inputLineageFile)
+            throws ParameterException {
+        try (RandomAccessFile f = new RandomAccessFile(inputLineageFile, "r")) {
+            if (f.length() < 4) {
+                throw new ParameterException("The input file is not a valid lineage file.");
+            }
+            DataSource apk = DataSources.asDataSource(f);
+            int magicValue = apk.getByteBuffer(0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
+            if (magicValue == SigningCertificateLineage.MAGIC) {
+                return SigningCertificateLineage.readFromFile(inputLineageFile);
+            } else if (magicValue == ZIP_MAGIC) {
+                return SigningCertificateLineage.readFromApkFile(inputLineageFile);
+            } else {
+                throw new ParameterException("The input file is not a valid lineage file.");
+            }
+        } catch (IOException | ApkFormatException | IllegalArgumentException e) {
+            throw new ParameterException(e.getMessage());
+        }
+    }
+
     private static SignerParams processSignerParams(OptionsParser optionsParser)
             throws OptionsParser.OptionsException, ParameterException {
         SignerParams signerParams = new SignerParams();
diff --git a/src/apksigner/java/com/android/apksigner/help_lineage.txt b/src/apksigner/java/com/android/apksigner/help_lineage.txt
index 0816176..3f4922d 100644
--- a/src/apksigner/java/com/android/apksigner/help_lineage.txt
+++ b/src/apksigner/java/com/android/apksigner/help_lineage.txt
@@ -10,6 +10,8 @@
 --in                  Input SigningCertificateLineage. This file contains a binary representation of
                       a SigningCertificateLineage object which contains the proof-of-rotation for
                       different signing certificates.
+                      An APK previously signed with a SigningCertificateLineage can also be
+                      specified; the lineage will then be read from the signed data in the APK.
 
 --out                 File into which to put the binary representation of a
                       SigningCertificateLineage object.
diff --git a/src/apksigner/java/com/android/apksigner/help_rotate.txt b/src/apksigner/java/com/android/apksigner/help_rotate.txt
index cd569e7..ff58372 100644
--- a/src/apksigner/java/com/android/apksigner/help_rotate.txt
+++ b/src/apksigner/java/com/android/apksigner/help_rotate.txt
@@ -10,7 +10,8 @@
                       a SigningCertificateLineage object, which contains the proof-of-rotation for
                       different signing certificates.  This can be used with APK Signature Scheme v3
                       to rotate the signing certificate for an APK.
-
+                      An APK previously signed with a SigningCertificateLineage can also be
+                      specified; the lineage will then be read from the signed data in the APK.
 
 --out                 File into which to put the binary representation of a
                       SigningCertificateLineage object.
diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt
index 07d0f03..b9d0146 100644
--- a/src/apksigner/java/com/android/apksigner/help_sign.txt
+++ b/src/apksigner/java/com/android/apksigner/help_sign.txt
@@ -73,6 +73,9 @@
                       points in the lineage and will be used on older platform
                       versions when the newest signer in the lineage is
                       unsupported.
+                      An APK previously signed with a SigningCertificateLineage
+                      can also be specified; the lineage will then be read from
+                      the signed data in the APK.
 
 -h, --help            Show help about this command and exit
 
diff --git a/src/main/java/com/android/apksig/SigningCertificateLineage.java b/src/main/java/com/android/apksig/SigningCertificateLineage.java
index a27cbe9..54340d7 100644
--- a/src/main/java/com/android/apksig/SigningCertificateLineage.java
+++ b/src/main/java/com/android/apksig/SigningCertificateLineage.java
@@ -19,17 +19,21 @@
 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
 
 import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.apk.ApkUtils;
 import com.android.apksig.internal.apk.ApkSigningBlockUtils;
 import com.android.apksig.internal.apk.SignatureAlgorithm;
+import com.android.apksig.internal.apk.SignatureInfo;
 import com.android.apksig.internal.apk.v3.V3SchemeSigner;
 import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage;
 import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage.SigningCertificateNode;
 import com.android.apksig.internal.util.AndroidSdkVersion;
+import com.android.apksig.internal.util.ByteBufferUtils;
 import com.android.apksig.internal.util.Pair;
 import com.android.apksig.internal.util.RandomAccessFileDataSink;
 import com.android.apksig.util.DataSink;
 import com.android.apksig.util.DataSource;
 import com.android.apksig.util.DataSources;
+import com.android.apksig.zip.ZipFormatException;
 
 import java.io.File;
 import java.io.IOException;
@@ -66,7 +70,7 @@
  */
 public class SigningCertificateLineage {
 
-    private final static int MAGIC = 0x3eff39d1;
+    public final static int MAGIC = 0x3eff39d1;
 
     private final static int FIRST_VERSION = 1;
 
@@ -157,8 +161,125 @@
         return  new SigningCertificateLineage(minSdkVersion, parsedLineage);
     }
 
-    public static SigningCertificateLineage readFromApkFile(File apkFile) {
-        throw new UnsupportedOperationException("Not yet implemented");
+    /**
+     * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
+     * signature block of the provided APK File.
+     *
+     * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
+     * or if the V3 signature block does not contain a valid lineage.
+     */
+    public static SigningCertificateLineage readFromApkFile(File apkFile)
+            throws IOException, ApkFormatException {
+        try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
+            DataSource apk = DataSources.asDataSource(f, 0, f.length());
+            return readFromApkDataSource(apk);
+        }
+    }
+
+    /**
+     * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
+     * signature block of the provided APK DataSource.
+     *
+     * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
+     * or if the V3 signature block does not contain a valid lineage.
+     */
+    public static SigningCertificateLineage readFromApkDataSource(DataSource apk)
+            throws IOException, ApkFormatException {
+        SignatureInfo signatureInfo;
+        try {
+            ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+            ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
+                    ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
+            signatureInfo =
+                    ApkSigningBlockUtils.findSignature(apk, zipSections,
+                            V3SchemeSigner.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result);
+        } catch (ZipFormatException e) {
+            throw new ApkFormatException(e.getMessage());
+        } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
+            throw new IllegalArgumentException(
+                    "The provided APK does not contain a valid V3 signature block.");
+        }
+
+        // FORMAT:
+        // * length-prefixed sequence of length-prefixed signers:
+        //   * length-prefixed signed data
+        //   * minSDK
+        //   * maxSDK
+        //   * length-prefixed sequence of length-prefixed signatures
+        //   * length-prefixed public key
+        ByteBuffer signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+        List<SigningCertificateLineage> lineages = new ArrayList<>(1);
+        while (signers.hasRemaining()) {
+            ByteBuffer signer = getLengthPrefixedSlice(signers);
+            ByteBuffer signedData = getLengthPrefixedSlice(signer);
+            try {
+                SigningCertificateLineage lineage = readFromSignedData(signedData);
+                lineages.add(lineage);
+            } catch (IllegalArgumentException ignored) {
+                // The current signer block does not contain a valid lineage, but it is possible
+                // another block will.
+            }
+        }
+        SigningCertificateLineage result;
+        if (lineages.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "The provided APK does not contain a valid lineage.");
+        } else if (lineages.size() > 1) {
+            result = consolidateLineages(lineages);
+        } else {
+            result = lineages.get(0);
+        }
+        return result;
+    }
+
+    /**
+     * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the provided
+     * signed data portion of a signer in a V3 signature block.
+     *
+     * @throws IllegalArgumentException if the provided signed data does not contain a valid
+     * lineage.
+     */
+    public static SigningCertificateLineage readFromSignedData(ByteBuffer signedData)
+            throws IOException, ApkFormatException {
+        // FORMAT:
+        //   * length-prefixed sequence of length-prefixed digests:
+        //   * length-prefixed sequence of certificates:
+        //     * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
+        //   * uint-32: minSdkVersion
+        //   * uint-32: maxSdkVersion
+        //   * length-prefixed sequence of length-prefixed additional attributes:
+        //     * uint32: ID
+        //     * (length - 4) bytes: value
+        //     * uint32: Proof-of-rotation ID: 0x3ba06f8c
+        //     * length-prefixed proof-of-rotation structure
+        // consume the digests through the maxSdkVersion to reach the lineage in the attributes
+        getLengthPrefixedSlice(signedData);
+        getLengthPrefixedSlice(signedData);
+        signedData.getInt();
+        signedData.getInt();
+        // iterate over the additional attributes adding any lineages to the List
+        ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
+        List<SigningCertificateLineage> lineages = new ArrayList<>(1);
+        while (additionalAttributes.hasRemaining()) {
+            ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
+            int id = attribute.getInt();
+            if (id == V3SchemeSigner.PROOF_OF_ROTATION_ATTR_ID) {
+                byte[] value = ByteBufferUtils.toByteArray(attribute);
+                SigningCertificateLineage lineage = readFromV3AttributeValue(value);
+                lineages.add(lineage);
+            }
+        }
+        SigningCertificateLineage result;
+        // There should only be a single attribute with the lineage, but if there are multiple then
+        // attempt to consolidate the lineages.
+        if (lineages.isEmpty()) {
+            throw new IllegalArgumentException("The signed data does not contain a valid lineage.");
+        } else if (lineages.size() > 1) {
+            result = consolidateLineages(lineages);
+        } else {
+            result = lineages.get(0);
+        }
+        return result;
     }
 
     public void writeToFile(File file) throws IOException {
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
index 5545d6c..fc70a0a 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
@@ -59,7 +59,7 @@
  */
 public abstract class V3SchemeSigner {
 
-    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+    public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
 
     /** Hidden constructor to prevent instantiation. */
     private V3SchemeSigner() {}
diff --git a/src/test/java/com/android/apksig/AllTests.java b/src/test/java/com/android/apksig/AllTests.java
index cd5c909..4a9243d 100644
--- a/src/test/java/com/android/apksig/AllTests.java
+++ b/src/test/java/com/android/apksig/AllTests.java
@@ -23,6 +23,7 @@
 @Suite.SuiteClasses({
     ApkSignerTest.class,
     ApkVerifierTest.class,
+    SigningCertificateLineageTest.class,
     com.android.apksig.apk.AllTests.class,
     com.android.apksig.internal.AllTests.class,
     com.android.apksig.util.AllTests.class,
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 80f35ba..1434017 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -16,10 +16,12 @@
 
 package com.android.apksig;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.apksig.ApkVerifier.Issue;
 import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.internal.util.ByteBufferDataSource;
 import com.android.apksig.internal.util.Resources;
 import com.android.apksig.util.DataSinks;
 import com.android.apksig.util.DataSource;
@@ -35,6 +37,7 @@
 import java.security.PrivateKey;
 import java.security.SignatureException;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
@@ -50,6 +53,15 @@
      */
     private static final boolean KEEP_FAILING_OUTPUT_AS_FILES = false;
 
+    // All signers with the same prefix and an _X suffix were signed with the private key of the
+    // (X-1) signer.
+    private static final String FIRST_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048";
+    private static final String SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_2";
+    private static final String THIRD_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_3";
+
+    private static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
+            "rsa-2048-lineage-2-signers";
+
     public static void main(String[] params) throws Exception {
         File outDir = (params.length > 0) ? new File(params[0]) : new File(".");
         generateGoldenFiles(outDir);
@@ -63,7 +75,13 @@
             throw new IOException("Failed to create directory: " + outDir);
         }
         List<ApkSigner.SignerConfig> rsa2048SignerConfig =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+                Collections.singletonList(
+                        getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+        List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = Arrays.asList(
+                rsa2048SignerConfig.get(0),
+                getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(
+                ApkSignerTest.class, LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME);
 
         signGolden(
                 "golden-unaligned-in.apk",
@@ -83,58 +101,205 @@
                 new File(outDir, "golden-unaligned-v1-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-legacy-aligned-in.apk",
                 new File(outDir, "golden-legacy-aligned-v1-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-aligned-in.apk",
                 new File(outDir, "golden-aligned-v1-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
 
         signGolden(
                 "golden-unaligned-in.apk",
                 new File(outDir, "golden-unaligned-v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-legacy-aligned-in.apk",
                 new File(outDir, "golden-legacy-aligned-v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-aligned-in.apk",
                 new File(outDir, "golden-aligned-v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
 
         signGolden(
                 "golden-unaligned-in.apk",
                 new File(outDir, "golden-unaligned-v1v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-legacy-aligned-in.apk",
                 new File(outDir, "golden-legacy-aligned-v1v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
         signGolden(
                 "golden-aligned-in.apk",
                 new File(outDir, "golden-aligned-v1v2-out.apk"),
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
 
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v1v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v1v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v1v2v3-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v1v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v1v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v1v2v3-lineage-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
 
         signGolden(
                 "original.apk", new File(outDir, "golden-rsa-out.apk"),
@@ -169,8 +334,13 @@
         // NOTE: Expected output files can be re-generated by running the "main" method.
 
         List<ApkSigner.SignerConfig> rsa2048SignerConfig =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
-
+                Collections.singletonList(
+                        getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+        List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = Arrays.asList(
+                rsa2048SignerConfig.get(0),
+                getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+                LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME);
         // Uncompressed entries in this input file are not aligned -- the file was created using
         // the jar utility. temp4.txt entry was then manually added into the archive. This entry's
         // ZIP Local File Header "extra" field declares that the entry's data must be aligned to
@@ -182,17 +352,59 @@
                 "golden-unaligned-in.apk", "golden-unaligned-v1-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
         assertGolden(
                 "golden-unaligned-in.apk", "golden-unaligned-v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
         assertGolden(
                 "golden-unaligned-in.apk", "golden-unaligned-v1v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v1v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v1v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
 
         // Uncompressed entries in this input file are aligned by zero-padding the "extra" field, as
         // performed by zipalign at the time of writing. This padding technique produces ZIP
@@ -205,17 +417,59 @@
                 "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
         assertGolden(
                 "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
         assertGolden(
                 "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
 
         // Uncompressed entries in this input file are aligned by padding the "extra" field, as
         // generated by signapk and apksigner. This padding technique produces "extra" fields which
@@ -227,17 +481,59 @@
                 "golden-aligned-in.apk", "golden-aligned-v1-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(false));
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(false));
         assertGolden(
                 "golden-aligned-in.apk", "golden-aligned-v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(false)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(false)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
         assertGolden(
                 "golden-aligned-in.apk", "golden-aligned-v1v2-out.apk",
                 new ApkSigner.Builder(rsa2048SignerConfig)
                         .setV1SigningEnabled(true)
-                        .setV2SigningEnabled(true));
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(false));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v1v2v3-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v1v2v3-lineage-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setSigningCertificateLineage(lineage));
     }
 
     @Test
@@ -245,8 +541,8 @@
         // Regression tests for minSdkVersion-based signature/digest algorithm selection
         // NOTE: Expected output files can be re-generated by running the "main" method.
 
-        List<ApkSigner.SignerConfig> rsaSignerConfig =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> rsaSignerConfig = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         assertGolden("original.apk", "golden-rsa-out.apk", new ApkSigner.Builder(rsaSignerConfig));
         assertGolden(
                 "original.apk", "golden-rsa-minSdkVersion-1-out.apk",
@@ -265,8 +561,8 @@
 
     @Test
     public void testRsaSignedVerifies() throws Exception {
-        List<ApkSigner.SignerConfig> signers =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         String in = "original.apk";
 
         // Sign so that the APK is guaranteed to verify on API Level 1+
@@ -318,8 +614,8 @@
     public void testV1SigningRejectsInvalidZipEntryNames() throws Exception {
         // ZIP/JAR entry name cannot contain CR, LF, or NUL characters when the APK is being
         // JAR-signed.
-        List<ApkSigner.SignerConfig> signers =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         try {
             sign("v1-only-with-cr-in-entry-name.apk",
                     new ApkSigner.Builder(signers).setV1SigningEnabled(true));
@@ -344,8 +640,8 @@
         // Any ZIP compression method other than STORED is treated as DEFLATED by Android.
         // This APK declares compression method 21 (neither STORED nor DEFLATED) for CERT.RSA entry,
         // but the entry is actually Deflate-compressed.
-        List<ApkSigner.SignerConfig> signers =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         sign("weird-compression-method.apk", new ApkSigner.Builder(signers));
     }
 
@@ -355,8 +651,8 @@
         // uses the compressionMethod from Central Directory instead.
         // In this APK, compression method of CERT.RSA is declared as STORED in Local File Header
         // and as DEFLATED in Central Directory. The entry is actually Deflate-compressed.
-        List<ApkSigner.SignerConfig> signers =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         sign("mismatched-compression-method.apk", new ApkSigner.Builder(signers));
     }
 
@@ -364,8 +660,8 @@
     public void testDebuggableApk() throws Exception {
         // APK which uses a boolean value "true" in its android:debuggable
         String apk = "debuggable-boolean.apk";
-        List<ApkSigner.SignerConfig> signers =
-                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
         // Signing debuggable APKs is permitted by default
         sign(apk, new ApkSigner.Builder(signers));
         // Signing debuggable APK succeeds when explicitly requested
@@ -391,6 +687,125 @@
         } catch (SignatureException expected) {}
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testV3SigningWithSignersNotInLineageFails() throws Exception {
+        // APKs signed with the v3 scheme after a key rotation must specify the lineage containing
+        // the proof of rotation. This test verifies that the signing will fail if the provided
+        // signers are not in the specified lineage.
+        List<ApkSigner.SignerConfig> signers = Arrays.asList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
+                getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+                "rsa-1024-lineage-2-signers");
+        sign("original.apk", new ApkSigner.Builder(signers).setSigningCertificateLineage(lineage));
+    }
+
+    @Test
+    public void testSigningWithLineageRequiresOldestSignerForV1AndV2() throws Exception {
+        // After a key rotation the oldest signer must still be specified for v1 and v2 signing.
+        // The lineage contains the proof of rotation and will be used to determine the oldest
+        // signer.
+        ApkSigner.SignerConfig firstSigner = getDefaultSignerConfigFromResources(
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        ApkSigner.SignerConfig secondSigner = getDefaultSignerConfigFromResources(
+                SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+        ApkSigner.SignerConfig thirdSigner = getDefaultSignerConfigFromResources(
+                THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+                "rsa-2048-lineage-3-signers");
+
+        // Verifies that the v1 signing scheme requires the oldest signer after a key rotation.
+        List<ApkSigner.SignerConfig> signers = Collections.singletonList(thirdSigner);
+        try {
+            sign("original.apk", new ApkSigner.Builder(signers)
+                    .setV1SigningEnabled(true)
+                    .setV2SigningEnabled(false)
+                    .setV3SigningEnabled(true)
+                    .setSigningCertificateLineage(lineage));
+            fail("The signing should have failed due to the oldest signer in the lineage not being"
+                    + " provided for v1 signing");
+        } catch (IllegalArgumentException expected) {}
+
+        // Verifies that the v2 signing scheme requires the oldest signer after a key rotation.
+        try {
+            sign("original.apk", new ApkSigner.Builder(signers)
+                    .setV1SigningEnabled(false)
+                    .setV2SigningEnabled(true)
+                    .setV3SigningEnabled(true)
+                    .setSigningCertificateLineage(lineage));
+            fail("The signing should have failed due to the oldest signer in the lineage not being"
+                    + " provided for v2 signing");
+        } catch (IllegalArgumentException expected) {}
+
+        // Verifies that when only the v3 signing scheme is requested the oldest signer does not
+        // need to be provided.
+        sign("original.apk", new ApkSigner.Builder(signers)
+                .setV1SigningEnabled(false)
+                .setV2SigningEnabled(false)
+                .setV3SigningEnabled(true)
+                .setSigningCertificateLineage(lineage));
+
+        // Verifies that an intermediate signer in the lineage is not sufficient to satisfy the
+        // requirement that the oldest signer be provided for v1 and v2 signing.
+        signers = Arrays.asList(secondSigner, thirdSigner);
+        try {
+            sign("original.apk", new ApkSigner.Builder(signers)
+                    .setV1SigningEnabled(true)
+                    .setV2SigningEnabled(true)
+                    .setV3SigningEnabled(true)
+                    .setSigningCertificateLineage(lineage));
+            fail("The signing should have failed due to the oldest signer in the lineage not being"
+                    + " provided for v1/v2 signing");
+        } catch (IllegalArgumentException expected) {}
+
+        // Verifies that the signing is successful when the oldest and newest signers are provided
+        // and that intermediate signers are not required.
+        signers = Arrays.asList(firstSigner, thirdSigner);
+        sign("original.apk", new ApkSigner.Builder(signers)
+                .setV1SigningEnabled(true)
+                .setV2SigningEnabled(true)
+                .setV3SigningEnabled(true)
+                .setSigningCertificateLineage(lineage));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testV3SigningWithMultipleSignersAndNoLineageFails() throws Exception {
+        // The v3 signing scheme does not support multiple signers; if multiple signers are provided
+        // it is assumed these signers are part of the lineage. This test verifies v3 signing
+        // fails if multiple signers are provided without a lineage.
+        ApkSigner.SignerConfig firstSigner = getDefaultSignerConfigFromResources(
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        ApkSigner.SignerConfig secondSigner = getDefaultSignerConfigFromResources(
+                SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+        List<ApkSigner.SignerConfig> signers = Arrays.asList(firstSigner, secondSigner);
+        sign("original.apk", new ApkSigner.Builder(signers)
+                .setV1SigningEnabled(true)
+                .setV2SigningEnabled(true)
+                .setV3SigningEnabled(true));
+    }
+
+    @Test
+    public void testLineageCanBeReadAfterV3Signing() throws Exception {
+        SigningCertificateLineage.SignerConfig firstSigner = Resources.toLineageSignerConfig(
+                getClass(), FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        SigningCertificateLineage.SignerConfig secondSigner = Resources.toLineageSignerConfig(
+                getClass(), SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+        SigningCertificateLineage lineage = new SigningCertificateLineage.Builder(firstSigner,
+                secondSigner).build();
+        List<ApkSigner.SignerConfig> signerConfigs = Arrays.asList(
+                getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
+                getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+        DataSource out = sign("original.apk", new ApkSigner.Builder(signerConfigs)
+                .setV3SigningEnabled(true)
+                .setSigningCertificateLineage(lineage));
+        SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkDataSource(
+                out);
+        assertTrue("The first signer was not in the lineage from the signed APK",
+                lineageFromApk.isSignerInLineage((firstSigner)));
+        assertTrue("The second signer was not in the lineage from the signed APK",
+                lineageFromApk.isSignerInLineage((secondSigner)));
+    }
+
     /**
      * Asserts that signing the specified golden input file using the provided signing
      * configuration produces output identical to the specified golden output file.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 6f6c04d..1f9e208 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -258,6 +258,15 @@
     }
 
     @Test
+    public void testV3StrippedRejected() throws Exception {
+        // APK signed with v2 and v3 schemes, but v3 signature was stripped from the file by
+        // modifying the v3 block ID to be the verity padding block ID. Without the stripping
+        // protection this modification ignores the v3 signing scheme block.
+        assertVerificationFailure(
+                "v3-stripped.apk", Issue.V2_SIG_MISSING_APK_SIG_REFERENCED);
+    }
+
+    @Test
     public void testV2OneSignerOneSignatureAccepted() throws Exception {
         // APK signed with v2 scheme only, one signer, one signature
         assertVerifiedForEachForMinSdkVersion(
@@ -278,6 +287,22 @@
     }
 
     @Test
+    public void testV3OneSignerOneSignatureAccepted() throws Exception {
+        // APK signed with v3 scheme only, one signer, one signature
+        assertVerifiedForEachForMinSdkVersion(
+                "v3-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.P);
+        assertVerifiedForEachForMinSdkVersion(
+                "v3-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P);
+        assertVerifiedForEachForMinSdkVersion(
+                "v3-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P);
+
+        assertVerifiedForEachForMinSdkVersion(
+                "v3-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P);
+        assertVerifiedForEachForMinSdkVersion(
+                "v3-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P);
+    }
+
+    @Test
     public void testV2OneSignerOneRsaPssSignatureAccepted() throws Exception {
         assumeThatRsaPssAvailable();
         // APK signed with v2 scheme only, one signer, one signature
@@ -312,6 +337,27 @@
     }
 
     @Test
+    public void testV3SignatureDoesNotMatchSignedDataRejected() throws Exception {
+        // APK signed with v3 scheme only, but the signature over signed-data does not verify
+
+        // Bitflip in DSA signature. Based on v3-only-with-dsa-sha256-2048.apk.
+        assertVerificationFailure(
+                "v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk",
+                Issue.V3_SIG_DID_NOT_VERIFY);
+
+        // Bitflip in signed data. Based on v3-only-with-rsa-pkcs1-sha256-3072.apk
+        assertVerificationFailure(
+                "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk",
+                Issue.V3_SIG_DID_NOT_VERIFY);
+
+        // Based on v3-only-with-ecdsa-sha512-p521 with the signature ID changed to be ECDSA with
+        // SHA-256.
+        assertVerificationFailure(
+                "v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk",
+                Issue.V3_SIG_DID_NOT_VERIFY);
+    }
+
+    @Test
     public void testV2RsaPssSignatureDoesNotMatchSignedDataRejected() throws Exception {
         assumeThatRsaPssAvailable();
 
@@ -343,6 +389,24 @@
     }
 
     @Test
+    public void testV3ContentDigestMismatchRejected() throws Exception {
+        // APK signed with v3 scheme only, but the digest of contents does not match the digest
+        // stored in signed-data.
+
+        // Based on v3-only-with-rsa-pkcs1-sha512-8192. Obtained by flipping a bit in the local
+        // file header of the APK.
+        assertVerificationFailure(
+                "v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk",
+                Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY);
+
+        // Based on v3-only-with-dsa-sha256-3072.apk. Obtained by modifying APK signer to flip the
+        // leftmost bit in content digest before signing signed-data.
+        assertVerificationFailure(
+                "v3-only-with-dsa-sha256-3072-digest-mismatch.apk",
+                Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY);
+    }
+
+    @Test
     public void testNoApkSignatureSchemeBlockRejected() throws Exception {
         // APK signed with v2 scheme only, but the rules for verifying APK Signature Scheme v2
         // signatures say that this APK must not be verified using APK Signature Scheme v2.
@@ -373,6 +437,21 @@
         assertVerified(verify("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk"));
     }
 
+    @Test
+    public void testNoV3ApkSignatureSchemeBlockRejected() throws Exception {
+        // Obtained from v3-only-with-ecdsa-sha512-p384.apk by flipping a bit in the magic field
+        // in the footer of the APK Signing Block.
+        assertVerificationFailure(
+                "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk",
+                Issue.JAR_SIG_NO_MANIFEST);
+
+        // Obtained from v3-only-with-rsa-pkcs1-sha512-4096.apk by modifying the size in the APK
+        // Signature Block header and footer.
+        assertVerificationFailure(
+                "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk",
+                Issue.JAR_SIG_NO_MANIFEST);
+    }
+
     @Test(expected = ApkFormatException.class)
     public void testTruncatedZipCentralDirectoryRejected() throws Exception {
         // Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The
@@ -392,6 +471,16 @@
     }
 
     @Test
+    public void testV3UnknownPairIgnoredInApkSigningBlock() throws Exception {
+        // Obtained by modifying APK signer to emit an unknown ID value pair into APK Signing Block
+        // before the ID value pair containing the APK Signature Scheme v3 Block. The unknown
+        // ID value should be ignored.
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v3-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.P));
+    }
+
+    @Test
     public void testV2UnknownSignatureAlgorithmsIgnored() throws Exception {
         // APK is signed with a known signature algorithm and with a couple of unknown ones.
         // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to
@@ -402,6 +491,24 @@
     }
 
     @Test
+    public void testV3UnknownSignatureAlgorithmsIgnored() throws Exception {
+        // APK is signed with a known signature algorithm and a couple of unknown ones.
+        // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to
+        // known ones.
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v3-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.P));
+    }
+
+    @Test
+    public void testV3WithOnlyUnknownSignatureAlgorithmsRejected() throws Exception {
+        // APK is only signed with an unknown signature algorithm. Obtained by modifying APK
+        // signer's ID for a known signature algorithm.
+        assertVerificationFailure(
+                "v3-only-no-supported-sig-algs.apk", Issue.V3_SIG_NO_SUPPORTED_SIGNATURES);
+    }
+
+    @Test
     public void testV2UnknownAdditionalAttributeIgnored() throws Exception {
         // APK's v2 signature contains an unknown additional attribute, but is otherwise fine.
         // Obtained by modifying APK signer to output an additional attribute with ID 0x01020304
@@ -411,6 +518,19 @@
     }
 
     @Test
+    public void testV3UnknownAdditionalAttributeIgnored() throws Exception {
+        // APK's v3 signature contains unknown additional attributes before and after the lineage.
+        // Obtained by modifying APK signer to output additional attributes with IDs 0x11223344
+        // and 0x99aabbcc with values 0x55667788 and 0xddeeff00
+        assertVerified(
+                verifyForMinSdkVersion("v3-only-unknown-additional-attr.apk", AndroidSdkVersion.P));
+
+        // APK's v2 and v3 signatures contain unknown additional attributes before and after the
+        // anti-stripping and lineage attributes.
+        assertVerified(
+                verifyForMinSdkVersion("v2v3-unknown-additional-attr.apk", AndroidSdkVersion.P));    }
+
+    @Test
     public void testV2MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception {
         // APK is signed with a single signature algorithm, but the digests block claims that it is
         // signed with two different signature algorithms. Obtained by modifying APK Signer to
@@ -421,6 +541,16 @@
     }
 
     @Test
+    public void testV3MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception {
+        // APK is signed with a single signature algorithm, but the digests block claims that it is
+        // signed with two different signature algorithms. Obtained by modifying APK Signer to
+        // emit an additional digest record with signature algorithm 0x11223344.
+        assertVerificationFailure(
+                "v3-only-signatures-and-digests-block-mismatch.apk",
+                Issue.V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS);
+    }
+
+    @Test
     public void testV2MismatchBetweenPublicKeyAndCertificateRejected() throws Exception {
         // APK is signed with v2 only. The public key field does not match the public key in the
         // leaf certificate. Obtained by modifying APK signer to write out a modified leaf
@@ -431,6 +561,16 @@
     }
 
     @Test
+    public void testV3MismatchBetweenPublicKeyAndCertificateRejected() throws Exception {
+        // APK is signed with v3 only. The public key field does not match the public key in the
+        // leaf certificate. Obtained by modifying APK signer to write out a modified leaf
+        // certificate where the RSA modulus has a bitflip.
+        assertVerificationFailure(
+                "v3-only-cert-and-public-key-mismatch.apk",
+                Issue.V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD);
+    }
+
+    @Test
     public void testV2SignerBlockWithNoCertificatesRejected() throws Exception {
         // APK is signed with v2 only. There are no certificates listed in the signer block.
         // Obtained by modifying APK signer to output no certificates.
@@ -439,6 +579,14 @@
     }
 
     @Test
+    public void testV3SignerBlockWithNoCertificatesRejected() throws Exception {
+        // APK is signed with v3 only. There are no certificates listed in the signer block.
+        // Obtained by modifying APK signer to output no certificates.
+        assertVerificationFailure(
+                "v3-only-no-certs-in-sig.apk", Issue.V3_SIG_NO_CERTIFICATES);
+    }
+
+    @Test
     public void testTwoSignersAccepted() throws Exception {
         // APK signed by two different signers
         assertVerified(verify("two-signers.apk"));
@@ -593,6 +741,12 @@
             verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N);
             fail("ApkFormatException should've been thrown");
         } catch (ApkFormatException expected) {}
+
+        // APK Signature Scheme v3 signed empty ZIP archive
+        try {
+            verifyForMinSdkVersion("v3-only-empty.apk", AndroidSdkVersion.P);
+            fail("ApkFormatException should've been thrown");
+        } catch (ApkFormatException expected) {}
     }
 
     @Test
@@ -982,6 +1136,19 @@
                         .append(signerName).append(": ").append(issue);
             }
         }
+        for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) {
+            String signerName = "signer #" + (signer.getIndex() + 1);
+            for (IssueWithParams issue : signer.getErrors()) {
+                if (expectedIssue.equals(issue.getIssue())) {
+                    return;
+                }
+                if (msg.length() > 0) {
+                    msg.append('\n');
+                }
+                msg.append("APK Signature Scheme v3 signer ")
+                        .append(signerName).append(": ").append(issue);
+            }
+        }
 
         fail("APK failed verification for the wrong reason"
                 + ". Expected: " + expectedIssue + ", actual: " + msg);
diff --git a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
index 2f6c10d..2038421 100644
--- a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
+++ b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
@@ -19,7 +19,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import com.android.apksig.apk.ApkFormatException;
 import com.android.apksig.internal.apk.ApkSigningBlockUtils;
 import com.android.apksig.internal.apk.v3.V3SchemeSigner;
 import com.android.apksig.internal.util.ByteBufferDataSource;
@@ -76,7 +78,7 @@
         SigningCertificateLineage lineage = createLineageWithSignersFromResources(
                 FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
         assertLineageContainsExpectedSigners(lineage, mSigners);
-        SignerConfig unknownSigner = getSignerConfigFromResources(
+        SignerConfig unknownSigner = Resources.toLineageSignerConfig(getClass(),
                 THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
         assertFalse("The signer " + unknownSigner.getCertificate().getSubjectDN()
                 + " should not be in the lineage", lineage.isSignerInLineage(unknownSigner));
@@ -94,21 +96,26 @@
     @Test
     public void testLineageFromFileContainsExpectedSigners() throws Exception {
         // This file contains the lineage with the three rsa-2048 signers
-        DataSource lineageDataSource = getDataSourceFromResources("rsa-2048-lineage-3-signers");
+        DataSource lineageDataSource = Resources.toDataSource(getClass(),
+                "rsa-2048-lineage-3-signers");
         SigningCertificateLineage lineage = SigningCertificateLineage.readFromDataSource(
                 lineageDataSource);
         List<SignerConfig> signers = new ArrayList<>(3);
-        signers.add(getSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
-        signers.add(getSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
-        signers.add(getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME));
+        signers.add(
+                Resources.toLineageSignerConfig(getClass(), FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+        signers.add(
+                Resources.toLineageSignerConfig(getClass(), SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+        signers.add(
+                Resources.toLineageSignerConfig(getClass(), THIRD_RSA_2048_SIGNER_RESOURCE_NAME));
         assertLineageContainsExpectedSigners(lineage, signers);
     }
 
     @Test
     public void testLineageFromFileDoesNotContainUnknownSigner() throws Exception {
         // This file contains the lineage with the first two rsa-2048 signers
-        SigningCertificateLineage lineage = getLineageFromResources("rsa-2048-lineage-2-signers");
-        SignerConfig unknownSigner = getSignerConfigFromResources(
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+                "rsa-2048-lineage-2-signers");
+        SignerConfig unknownSigner = Resources.toLineageSignerConfig(getClass(),
                 THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
         assertFalse("The signer " + unknownSigner.getCertificate().getSubjectDN()
                 + " should not be in the lineage", lineage.isSignerInLineage(unknownSigner));
@@ -117,14 +124,14 @@
     @Test(expected = IllegalArgumentException.class)
     public void testLineageFromFileWithInvalidMagicFails() throws Exception {
         // This file contains the lineage with two rsa-2048 signers and a modified MAGIC value
-        getLineageFromResources("rsa-2048-lineage-invalid-magic");
+        Resources.toSigningCertificateLineage(getClass(), "rsa-2048-lineage-invalid-magic");
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testLineageFromFileWithInvalidVersionFails() throws Exception {
         // This file contains the lineage with two rsa-2048 signers and an invalid value of FF for
         // the version
-        getLineageFromResources("rsa-2048-lineage-invalid-version");
+        Resources.toSigningCertificateLineage(getClass(), "rsa-2048-lineage-invalid-version");
     }
 
     @Test
@@ -172,10 +179,11 @@
     public void testCapabilitiesAreNotUpdatedWithDefaultValues() throws Exception {
         // This file contains the lineage with the first two rsa-2048 signers with the first signer
         // having all of the capabilities set to false.
-        SigningCertificateLineage lineage = getLineageFromResources(
+        SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
                 "rsa-2048-lineage-no-capabilities-first-signer");
         List<Boolean> expectedCapabilityValues = Arrays.asList(false, false, false, false, false);
-        SignerConfig oldSignerConfig = getSignerConfigFromResources("rsa-2048");
+        SignerConfig oldSignerConfig = Resources.toLineageSignerConfig(getClass(),
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
         SignerCapabilities oldSignerCapabilities = lineage.getSignerCapabilities(oldSignerConfig);
         assertExpectedCapabilityValues(oldSignerCapabilities, expectedCapabilityValues);
         // The builder is called directly to ensure all of the capabilities are set to the default
@@ -188,8 +196,10 @@
 
     @Test
     public void testFirstRotationWitNonDefaultCapabilitiesForSigners() throws Exception {
-        SignerConfig oldSigner = getSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
-        SignerConfig newSigner = getSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig oldSigner = Resources.toLineageSignerConfig(getClass(),
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+                SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
         List<Boolean> oldSignerCapabilityValues = Arrays.asList(false, false, false, false, false);
         List<Boolean> newSignerCapabilityValues = Arrays.asList(false, true, false, false, false);
         SigningCertificateLineage lineage = new SigningCertificateLineage.Builder(oldSigner,
@@ -209,7 +219,8 @@
         SigningCertificateLineage lineage = createLineageWithSignersFromResources(
                 FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
         SignerConfig oldSigner = mSigners.get(mSigners.size() - 1);
-        SignerConfig newSigner = getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+                THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
         List<Boolean> newSignerCapabilityValues = Arrays.asList(false, false, false, false, false);
         lineage = lineage.spawnDescendant(oldSigner, newSigner,
                 buildSignerCapabilities(newSignerCapabilityValues));
@@ -225,7 +236,8 @@
         SigningCertificateLineage lineage = createLineageWithSignersFromResources(
                 FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
         SignerConfig oldestSigner = mSigners.get(0);
-        SignerConfig newSigner = getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+                THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
         lineage.spawnDescendant(oldestSigner, newSigner);
     }
 
@@ -344,6 +356,66 @@
         SigningCertificateLineage.consolidateLineages(lineages);
     }
 
+    @Test
+    public void testLineageFromAPKContainsExpectedSigners() throws Exception {
+        SignerConfig firstSigner = getSignerConfigFromResources(
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig secondSigner = getSignerConfigFromResources(
+                SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+        SignerConfig thirdSigner = getSignerConfigFromResources(
+                THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+        List<SignerConfig> expectedSigners = Arrays.asList(firstSigner, secondSigner, thirdSigner);
+        DataSource apkDataSource = Resources.toDataSource(getClass(),
+                "v1v2v3-with-rsa-2048-lineage-3-signers.apk");
+        SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkDataSource(
+                apkDataSource);
+        assertLineageContainsExpectedSigners(lineageFromApk, expectedSigners);
+    }
+
+    @Test(expected = ApkFormatException.class)
+    public void testLineageFromAPKWithInvalidZipCDSizeFails() throws Exception {
+        // This test verifies that attempting to read the lineage from an APK where the zip
+        // sections cannot be parsed fails. This APK is based off the
+        // v1v2v3-with-rsa-2048-lineage-3-signers.apk with a modified CD size in the EoCD.
+        DataSource apkDataSource = Resources.toDataSource(getClass(),
+                "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk");
+        SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+    }
+
+    @Test
+    public void testLineageFromAPKWithNoLineageFails() throws Exception {
+        // This test verifies that attempting to read the lineage from an APK without a lineage
+        // fails.
+        // This is a valid APK that has only been signed with the V1 and V2 signature schemes;
+        // since the lineage is an attribute in the V3 signature block this test should fail.
+        DataSource apkDataSource = Resources.toDataSource(getClass(),
+                "golden-aligned-v1v2-out.apk");
+        try {
+            SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+            fail("A failure should have been reported due to the APK not containing a V3 signing "
+                    + "block");
+        } catch (IllegalArgumentException expected) {}
+
+        // This is a valid APK signed with the V1, V2, and V3 signature schemes, but there is no
+        // lineage in the V3 signature block.
+        apkDataSource = Resources.toDataSource(getClass(), "golden-aligned-v1v2v3-out.apk");
+        try {
+            SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+            fail("A failure should have been reported due to the APK containing a V3 signing "
+                    + "block without the lineage attribute");
+        } catch (IllegalArgumentException expected) {}
+
+        // This APK is based off the v1v2v3-with-rsa-2048-lineage-3-signers.apk with a bit flip
+        // in the lineage attribute ID in the V3 signature block.
+        apkDataSource = Resources.toDataSource(getClass(),
+                "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk");
+        try {
+            SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+            fail("A failure should have been reported due to the APK containing a V3 signing "
+                    + "block with a modified lineage attribute ID");
+        } catch (IllegalArgumentException expected) {}
+    }
+
     /**
      * Builds a new {@code SigningCertificateLinage.SignerCapabilities} object using the values in
      * the provided {@code List}. The {@code List} should contain {@code boolean} values to be
@@ -410,10 +482,10 @@
      */
     private SigningCertificateLineage createLineageWithSignersFromResources(
             String oldSignerResourceName, String newSignerResourceName) throws Exception {
-        SignerConfig oldSignerConfig = getSignerConfigFromResources(
+        SignerConfig oldSignerConfig = Resources.toLineageSignerConfig(getClass(),
                 oldSignerResourceName);
         mSigners.add(oldSignerConfig);
-        SignerConfig newSignerConfig = getSignerConfigFromResources(
+        SignerConfig newSignerConfig = Resources.toLineageSignerConfig(getClass(),
                 newSignerResourceName);
         mSigners.add(newSignerConfig);
         return new SigningCertificateLineage.Builder(oldSignerConfig, newSignerConfig).build();
@@ -432,7 +504,8 @@
         assertTrue("The mSigners list did not contain the expected signers to update the lineage",
                 mSigners.size() >= 2);
         SignerConfig oldSignerConfig = mSigners.get(mSigners.size() - 1);
-        SignerConfig newSignerConfig = getSignerConfigFromResources(newSignerResourceName);
+        SignerConfig newSignerConfig = Resources.toLineageSignerConfig(getClass(),
+                newSignerResourceName);
         mSigners.add(newSignerConfig);
         return lineage.spawnDescendant(oldSignerConfig, newSignerConfig);
     }
@@ -467,16 +540,4 @@
         return new DefaultApkSignerEngine.SignerConfig.Builder(resourcePrefix, privateKey,
                 Collections.singletonList(cert)).build();
     }
-
-    private static DataSource getDataSourceFromResources(String dataSourceResourceName)
-            throws IOException {
-        return new ByteBufferDataSource(ByteBuffer.wrap(Resources
-                .toByteArray(SigningCertificateLineageTest.class, dataSourceResourceName)));
-    }    
-
-    private SigningCertificateLineage getLineageFromResources(String fileResourceName)
-            throws IOException {
-        DataSource lineageDataSource = getDataSourceFromResources(fileResourceName);
-        return SigningCertificateLineage.readFromDataSource(lineageDataSource);
-    }
 }
diff --git a/src/test/java/com/android/apksig/internal/util/Resources.java b/src/test/java/com/android/apksig/internal/util/Resources.java
index 8a761ff..09c206d 100644
--- a/src/test/java/com/android/apksig/internal/util/Resources.java
+++ b/src/test/java/com/android/apksig/internal/util/Resources.java
@@ -16,8 +16,13 @@
 
 package com.android.apksig.internal.util;
 
+import com.android.apksig.ApkSignerTest;
+import com.android.apksig.SigningCertificateLineage;
+import com.android.apksig.util.DataSource;
+
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -109,4 +114,24 @@
 
         return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
     }
+
+    public static SigningCertificateLineage.SignerConfig toLineageSignerConfig(Class<?> cls,
+            String resourcePrefix) throws Exception {
+        PrivateKey privateKey = toPrivateKey(cls, resourcePrefix + ".pk8");
+        X509Certificate cert = Resources.toCertificate(cls,
+                resourcePrefix + ".x509.pem");
+        return new SigningCertificateLineage.SignerConfig.Builder(privateKey, cert).build();
+    }
+
+    public static DataSource toDataSource(Class<?> cls, String dataSourceResourceName)
+            throws IOException {
+        return new ByteBufferDataSource(ByteBuffer.wrap(Resources
+                .toByteArray(ApkSignerTest.class, dataSourceResourceName)));
+    }
+
+    public static SigningCertificateLineage toSigningCertificateLineage(Class<?> cls,
+            String fileResourceName) throws IOException {
+        DataSource lineageDataSource = toDataSource(cls, fileResourceName);
+        return SigningCertificateLineage.readFromDataSource(lineageDataSource);
+    }
 }
diff --git a/src/test/resources/com/android/apksig/golden-aligned-out.apk b/src/test/resources/com/android/apksig/golden-aligned-out.apk
index ca7041b..2396782 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
index e413b4e..403e45a 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
index ca7041b..5133049 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..b9dc782
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk
new file mode 100644
index 0000000..2396782
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
index 664d198..d947e3c 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..88c571b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk
new file mode 100644
index 0000000..25f35cc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk
new file mode 100644
index 0000000..30e1f72
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk
new file mode 100644
index 0000000..f97cbeb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
index fbae100..d177361 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
index bdc2165..e8a0bf2 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
index fbae100..cc03744 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..e359da7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk
new file mode 100644
index 0000000..d177361
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
index 5956da5..68f07ed 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..4b51e4f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk
new file mode 100644
index 0000000..7177862
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk
new file mode 100644
index 0000000..bd3e668
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk
new file mode 100644
index 0000000..67a7d3f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
index 1269499..7289853 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-out.apk b/src/test/resources/com/android/apksig/golden-rsa-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-out.apk
index 1b10755..0bd34c4 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
index 1309566..6ddc448 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
index 1b10755..c708211 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..dd6324b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk
new file mode 100644
index 0000000..0bd34c4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
index bd56d9f..4fdc18c 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..4e523ca
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk
new file mode 100644
index 0000000..74e7dbc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk
new file mode 100644
index 0000000..831c756
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk
new file mode 100644
index 0000000..3196267
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers b/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers
new file mode 100644
index 0000000..4f4315c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk
new file mode 100644
index 0000000..1bd3828
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk
new file mode 100644
index 0000000..2621f21
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk
new file mode 100644
index 0000000..17dea33
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk
new file mode 100644
index 0000000..fd141b5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk b/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk
new file mode 100644
index 0000000..751c67d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk b/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk
new file mode 100644
index 0000000..cb080f4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk
new file mode 100644
index 0000000..2291e7e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-empty.apk b/src/test/resources/com/android/apksig/v3-only-empty.apk
new file mode 100644
index 0000000..15cb0ec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-empty.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk b/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk
new file mode 100644
index 0000000..86e7971
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk b/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk
new file mode 100644
index 0000000..f0debf3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk
new file mode 100644
index 0000000..31aea2f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk b/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk
new file mode 100644
index 0000000..2245922
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk b/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk
new file mode 100644
index 0000000..49eeaf3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk
new file mode 100644
index 0000000..af6b0d7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk
new file mode 100644
index 0000000..50dbab2
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk
new file mode 100644
index 0000000..3d2161e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk
new file mode 100644
index 0000000..42f885b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk
new file mode 100644
index 0000000..c58902d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk
new file mode 100644
index 0000000..5ef4fec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk
new file mode 100644
index 0000000..75135af
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk
new file mode 100644
index 0000000..74071f0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk
new file mode 100644
index 0000000..543c1f3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk
new file mode 100644
index 0000000..ce79751
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk
new file mode 100644
index 0000000..36fa0ee
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk
new file mode 100644
index 0000000..8e89c98
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk
new file mode 100644
index 0000000..b74b4fb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk b/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk
new file mode 100644
index 0000000..88ae376
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk
new file mode 100644
index 0000000..7a62c24
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk
new file mode 100644
index 0000000..825cfba
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk
new file mode 100644
index 0000000..1ab85f8
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk
new file mode 100644
index 0000000..ddaaccd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk
new file mode 100644
index 0000000..8bcc82c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk
new file mode 100644
index 0000000..0c9391c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk
new file mode 100644
index 0000000..41db21b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk
new file mode 100644
index 0000000..776d366
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk
new file mode 100644
index 0000000..85146f1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk
new file mode 100644
index 0000000..8b1b915
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk
new file mode 100644
index 0000000..5b364fd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk
new file mode 100644
index 0000000..52d5a67
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk
new file mode 100644
index 0000000..c210b70
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
new file mode 100644
index 0000000..2800929
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk
new file mode 100644
index 0000000..3c2cc79
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-stripped.apk b/src/test/resources/com/android/apksig/v3-stripped.apk
new file mode 100644
index 0000000..751c67d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-stripped.apk
Binary files differ